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         * &#47;&#47; 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}