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 * @hidden 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 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 Iterable<JStachioExtension> it = ServiceLoader.load(JStachioExtension.class); 072 return new Holder(JStachioExtensions.of(it)); 073 } 074 075 @Override 076 public JStachioExtensions extensions() { 077 return this.extensions; 078 } 079 080 } 081 082 /** 083 * Builder for creating a custom JStachio. 084 * 085 * <pre><code class="language-java"> 086 * JStachio jstachio = JStachioFactory.builder() 087 * .add(extension1) 088 * .add(extension2) 089 * .build(); 090 * </code></pre> 091 * 092 * <em> The order of adding extensions is important such that primacy order takes 093 * precedence as composite extensions such as config will be created if multiple of 094 * the same extension type are added. </em> If you would like to share the JStachio in 095 * a service locator style you may want to set it as the default via 096 * {@link JStachio#setStatic(java.util.function.Supplier)} which will make all calls 097 * of {@link JStachio#of()} use the custom one. 098 * 099 * @author agentgt 100 * @see JStacheCatalog 101 * @see JStachioTemplateFinder 102 * @see JStachioConfig 103 * @see JStachio#setStatic(java.util.function.Supplier) 104 */ 105 public static class Builder { 106 107 private List<JStachioExtension> extensions = new ArrayList<>(); 108 109 private List<TemplateInfo> templates = new ArrayList<>(); 110 111 /** 112 * Constructor is hidden for now. 113 */ 114 private Builder() { 115 } 116 117 /** 118 * Adds an extension 119 * @param extension not null 120 * @return this 121 */ 122 public Builder add(JStachioExtension extension) { 123 extensions.add(extension); 124 return this; 125 } 126 127 /** 128 * Add extensions. 129 * 130 * Useful for adding ServiceLoader results: <pre> 131 * <code class="language-java"> 132 * builder.add(ServiceLoader.load(JStachioExtension.class)); 133 * </code> </pre> 134 * @param extensions not null 135 * @return this 136 */ 137 public Builder add(Iterable<? extends JStachioExtension> extensions) { 138 extensions.forEach(this.extensions::add); 139 return this; 140 } 141 142 /** 143 * Registers an instantiated template. The templates will be added to 144 * {@link JStachioTemplateFinder} with order <code>-1</code> when {@link #build()} 145 * is called. 146 * @param template usually a generated renderer. 147 * @return this 148 */ 149 public Builder add(TemplateInfo template) { 150 Objects.requireNonNull(template, "template"); 151 templates.add(template); 152 return this; 153 } 154 155 /** 156 * Registers instantiated templates. The templates will be added 157 * {@link JStachioTemplateFinder} with order <code>-1</code> when {@link #build()} 158 * is called. 159 * @param templates usually a generated renderer. 160 * @return this 161 */ 162 public Builder add(Collection<? extends TemplateInfo> templates) { 163 this.templates.addAll(templates); 164 return this; 165 } 166 167 /** 168 * Builds a JStachio by coalescing the extensions and registered templates. 169 * 170 * @apiNote See {@link JStachioExtensions} for logic on how the extensions are 171 * consolidated. 172 * @return resolved JStachio 173 */ 174 public JStachio build() { 175 List<JStachioExtension> resolved = new ArrayList<>(); 176 if (!templates.isEmpty()) { 177 var templatesCopy = List.copyOf(templates); 178 JStachioTemplateFinder f = JStachioTemplateFinder.of(templatesCopy, -1); 179 f = JStachioTemplateFinder.cachedTemplateFinder(f); 180 resolved.add(f); 181 } 182 resolved.addAll(extensions); 183 return new DefaultJStachio(JStachioExtensions.of(resolved)); 184 } 185 186 } 187 188}