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