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}