001package io.jstach.jstachio.spi;
002
003import java.util.ArrayList;
004import java.util.Comparator;
005import java.util.List;
006
007import org.eclipse.jdt.annotation.Nullable;
008
009import io.jstach.jstache.JStache;
010import io.jstach.jstachio.JStachio;
011import io.jstach.jstachio.Template;
012import io.jstach.jstachio.TemplateInfo;
013
014/**
015 * Finds templates based on the model type (class).
016 * <p>
017 * The default {@link JStachio} uses a combination of relection and the ServiceLoader to
018 * find templates.
019 * <p>
020 * Other implementations may want to use their DI framework like Spring or CDI to find
021 * templates.
022 *
023 * @author agentgt
024 *
025 */
026public non-sealed interface JStachioTemplateFinder extends JStachioExtension {
027
028        /**
029         * Finds a {@link Template} if possible otherwise possibly falling back to a
030         * {@link TemplateInfo} based on annotation metadata or some other mechanism.
031         *
032         * @apiNote Callers can do an <code>instanceof Template t</code> to see if a generated
033         * template was returned instead of the fallback {@link TemplateInfo} metadata.
034         * @param modelType the models class (<em>the one annotated with {@link JStache} and
035         * not the Templates class</em>)
036         * @return the template info which might be a {@link Template} if the generated
037         * template was found.
038         * @throws Exception if any reflection error happes or the template is not found
039         */
040        public TemplateInfo findTemplate(Class<?> modelType) throws Exception;
041
042        /**
043         * Determines if this template finder has a template for the model type (the class
044         * annotated by JStache).
045         * @param modelType the models class (<em>the one annotated with {@link JStache} and
046         * not the Templates class</em>)
047         * @return true if this finder has template for modelType
048         */
049        default boolean supportsType(Class<?> modelType) {
050                try {
051                        findTemplate(modelType);
052                        return true;
053                }
054                catch (Exception e) {
055                        return false;
056                }
057        }
058
059        /**
060         * Hint on order of template finders. The found {@link JStachioTemplateFinder}s are
061         * sorted naturally (lower number comes first) based on the returned number. Thus a
062         * template finder with a lower order number that {@link #supportsType(Class)} the
063         * model class will be used.
064         * @return default returns zero
065         */
066        default int order() {
067                return 0;
068        }
069
070        /**
071         * The default template finder that uses reflection and or the ServiceLoader.
072         * <p>
073         * <em>This implementation performs no caching. If you would like caching call
074         * {@link #cachedTemplateFinder(JStachioTemplateFinder)} on the returned finder.</em>
075         * @param config used to help find templates as well as logging.
076         * @return default template finder.
077         */
078        public static JStachioTemplateFinder defaultTemplateFinder(JStachioConfig config) {
079                return new DefaultTemplateFinder(config);
080        }
081
082        /**
083         * Decorates a template finder with a cache using {@link ClassValue} with the
084         * modelType as the key.
085         * <p>
086         * <em>While the finder does not provide any eviction the cache will not prevent
087         * garbage collection of the model classes.</em>
088         * @param finder to be decorated unless the finder is already decorated thus it is a
089         * noop to repeateadly call this method on already cached template finder.
090         * @return caching template finder
091         */
092        public static JStachioTemplateFinder cachedTemplateFinder(JStachioTemplateFinder finder) {
093                if (finder instanceof ClassValueCacheTemplateFinder) {
094                        return finder;
095                }
096                return new ClassValueCacheTemplateFinder(finder);
097        }
098
099}
100
101final class DefaultTemplateFinder implements JStachioTemplateFinder {
102
103        private final JStachioConfig config;
104
105        DefaultTemplateFinder(JStachioConfig config) {
106                super();
107                this.config = config;
108        }
109
110        @Override
111        public TemplateInfo findTemplate(Class<?> modelType) throws Exception {
112                return Templates.findTemplate(modelType, config);
113        }
114
115        @Override
116        public int order() {
117                return Integer.MAX_VALUE;
118        }
119
120}
121
122final class ClassValueCacheTemplateFinder implements JStachioTemplateFinder {
123
124        private final ClassValue<TemplateInfo> cache;
125
126        private final JStachioTemplateFinder delegate;
127
128        public ClassValueCacheTemplateFinder(JStachioTemplateFinder delegate) {
129                super();
130                this.delegate = delegate;
131                this.cache = new ClassValue<TemplateInfo>() {
132
133                        @Override
134                        protected @Nullable TemplateInfo computeValue(@Nullable Class<?> type) {
135                                try {
136                                        return delegate.findTemplate(type);
137                                }
138                                catch (Exception e) {
139                                        Templates.sneakyThrow(e);
140                                        throw new RuntimeException();
141                                }
142                        }
143                };
144        }
145
146        @Override
147        public TemplateInfo findTemplate(Class<?> modelType) throws Exception {
148                return cache.get(modelType);
149        }
150
151        @Override
152        public int order() {
153                return delegate.order();
154        }
155
156}
157
158final class CompositeTemplateFinder implements JStachioTemplateFinder {
159
160        private final List<JStachioTemplateFinder> finders;
161
162        private CompositeTemplateFinder(List<JStachioTemplateFinder> finders) {
163                super();
164                this.finders = finders;
165        }
166
167        public static JStachioTemplateFinder of(List<? extends JStachioTemplateFinder> finders) {
168                if (finders.size() == 1) {
169                        return finders.get(0);
170                }
171                ArrayList<JStachioTemplateFinder> sorted = new ArrayList<>();
172                sorted.addAll(finders);
173                sorted.sort(Comparator.comparingInt(JStachioTemplateFinder::order));
174                return new CompositeTemplateFinder(List.copyOf(sorted));
175        }
176
177        @Override
178        public TemplateInfo findTemplate(Class<?> modelType) throws Exception {
179                for (var f : finders) {
180                        if (f.supportsType(modelType)) {
181                                return f.findTemplate(modelType);
182                        }
183                }
184                throw new RuntimeException("Template not found for type: " + modelType);
185        }
186
187}