001package io.jstach.jstachio.spi; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.List; 006import java.util.Objects; 007import java.util.ServiceLoader; 008 009import io.jstach.jstache.JStacheCatalog; 010import io.jstach.jstachio.JStachio; 011import io.jstach.jstachio.TemplateInfo; 012 013/** 014 * Creates JStachios mainly with the {@link ServiceLoader} or a {@link Builder}. 015 * 016 * @author agentgt 017 * @see JStacheCatalog 018 * @see JStachioExtensions 019 */ 020public final class JStachioFactory { 021 022 /** 023 * Ignore 024 */ 025 private JStachioFactory() { 026 027 } 028 029 /** 030 * Provides a singleton JStachio resolved by the {@link ServiceLoader}. 031 * <p> 032 * Because of differences to how the {@link ServiceLoader} works with modular 033 * applications registration of generated templates is different. For modular 034 * applications you can either allow reflective access to JStachio: 035 * 036 * <pre><code class="language-java"> 037 * // module-info.java 038 * opens packagewith.jstachemodels to io.jstach.jstachio; 039 * </code> </pre> 040 * 041 * Or you can generate a catalog of all templates and register them. See 042 * {@link JStacheCatalog} for details. 043 * @return service loader based jstachio. 044 */ 045 public static JStachio defaultJStachio() { 046 return Holder.INSTANCE; 047 } 048 049 /** 050 * A <em>mutable</em> builder to create {@link JStachio} from 051 * {@link JStachioExtension}s. Once {@link Builder#build()} is called the returned 052 * JStachio will be immutable. If no extensions are added the returned JStachio will 053 * be resolved in a simlar manner to the {@link #defaultJStachio() default JStachio}. 054 * @return empty builder 055 */ 056 public static Builder builder() { 057 return new Builder(); 058 } 059 060 private static class Holder extends AbstractJStachio { 061 062 private static final Holder INSTANCE = Holder.of(); 063 064 private final JStachioExtensions extensions; 065 066 public Holder(JStachioExtensions extensions) { 067 this.extensions = extensions; 068 } 069 070 private static Holder of() { 071 return new Holder(JStachioExtensions.of()); 072 } 073 074 @Override 075 public JStachioExtensions extensions() { 076 return this.extensions; 077 } 078 079 @Override 080 public String toString() { 081 return "ServiceLoaderJStachio"; 082 } 083 084 } 085 086 /** 087 * Builder for creating a custom JStachio. 088 * 089 * <pre><code class="language-java"> 090 * JStachio jstachio = JStachioFactory.builder() 091 * .add(extension1) 092 * .add(extension2) 093 * .build(); 094 * </code></pre> 095 * 096 * <em> The order of adding extensions is important such that primacy order takes 097 * precedence as composite extensions such as config will be created if multiple of 098 * the same extension type are added. </em> If you would like to share the JStachio in 099 * a service locator style you may want to set it as the default via 100 * {@link JStachio#setStatic(java.util.function.Supplier)} which will make all calls 101 * of {@link JStachio#of()} use the custom one. 102 * 103 * @author agentgt 104 * @see JStacheCatalog 105 * @see JStachioTemplateFinder 106 * @see JStachioConfig 107 * @see JStachio#setStatic(java.util.function.Supplier) 108 */ 109 public static class Builder { 110 111 private List<JStachioExtension> extensions = new ArrayList<>(); 112 113 private List<TemplateInfo> templates = new ArrayList<>(); 114 115 /** 116 * Constructor is hidden for now. 117 */ 118 private Builder() { 119 } 120 121 /** 122 * Adds an extension 123 * @param extension not null 124 * @return this 125 */ 126 public Builder add(JStachioExtension extension) { 127 extensions.add(extension); 128 return this; 129 } 130 131 /** 132 * Add extensions. 133 * 134 * Useful for adding ServiceLoader results: <pre> 135 * <code class="language-java"> 136 * builder.add(ServiceLoader.load(JStachioExtension.class)); 137 * </code> </pre> 138 * @param extensions not null 139 * @return this 140 */ 141 public Builder add(Iterable<? extends JStachioExtension> extensions) { 142 extensions.forEach(this.extensions::add); 143 return this; 144 } 145 146 /** 147 * Registers an instantiated template. The templates will be added to 148 * {@link JStachioTemplateFinder} with order <code>-1</code> when {@link #build()} 149 * is called. 150 * @param template usually a generated renderer. 151 * @return this 152 */ 153 public Builder add(TemplateInfo template) { 154 Objects.requireNonNull(template, "template"); 155 templates.add(template); 156 return this; 157 } 158 159 /** 160 * Registers instantiated templates. The templates will be added 161 * {@link JStachioTemplateFinder} with order <code>-1</code> when {@link #build()} 162 * is called. 163 * @param templates usually a generated renderer. 164 * @return this 165 */ 166 public Builder add(Collection<? extends TemplateInfo> templates) { 167 this.templates.addAll(templates); 168 return this; 169 } 170 171 /** 172 * Builds a JStachio by coalescing the extensions and registered templates. 173 * 174 * @apiNote See {@link JStachioExtensions} for logic on how the extensions are 175 * consolidated. 176 * @return resolved JStachio 177 */ 178 public JStachio build() { 179 List<JStachioExtension> resolved = new ArrayList<>(); 180 if (!templates.isEmpty()) { 181 var templatesCopy = List.copyOf(templates); 182 JStachioTemplateFinder f = JStachioTemplateFinder.of(templatesCopy, -1); 183 f = JStachioTemplateFinder.cachedTemplateFinder(f); 184 resolved.add(f); 185 } 186 resolved.addAll(extensions); 187 return new DefaultJStachio(JStachioExtensions.of(resolved)); 188 } 189 190 } 191 192}