001package io.jstach.jstachio.spi;
002
003import java.util.ArrayList;
004import java.util.List;
005import java.util.Optional;
006import java.util.ServiceLoader;
007import java.util.stream.Stream;
008import java.util.stream.StreamSupport;
009
010import org.eclipse.jdt.annotation.Nullable;
011
012/**
013 * A container that will hold all resolved {@link JStachioExtension}s and consolidate them
014 * to a single instances of various services.
015 *
016 * @apiNote While this interface looks similar to {@link JStachioExtension} it is not an
017 * extension but rather an immutable bean like container. The methods are purposely java
018 * bean style (which is not the default in JStachio as JStachio prefers newer record like
019 * accessor method names) to support as many frameworks as possible.
020 * @author agentgt
021 */
022public interface JStachioExtensions {
023
024        /**
025         * A marker interface used for JStachio implementations that provide access to
026         * extensions.
027         *
028         * @author agentgt
029         *
030         */
031        public interface Provider {
032
033                /**
034                 * The available extensions.
035                 * @return The avaiable resolved extensions.
036                 */
037                public JStachioExtensions extensions();
038
039        }
040
041        /**
042         * Resolve from an iterable of extensions that usually come from some discovery
043         * mechanism like the {@link ServiceLoader} or a DI framework. <em>The order of the
044         * extensions is important and primacy order takes precedence!</em>
045         * @param extensions found extensions.
046         * @return bean like container of services.
047         */
048        public static JStachioExtensions of(Iterable<? extends JStachioExtension> extensions) {
049                return of(StreamSupport.stream(extensions.spliterator(), false));
050        }
051
052        /**
053         * Resolve from a stream of extensions that usually come from some discovery mechanism
054         * like the {@link ServiceLoader} or a DI framework. <em>The order of the extensions
055         * is important and primacy order takes precedence!</em>
056         * @param extensions found extensions.
057         * @return bean like container of services.
058         */
059        public static JStachioExtensions of(Stream<? extends JStachioExtension> extensions) {
060                return DefaultJStachioExtensions.of(extensions);
061        }
062
063        /**
064         * Resolves extensions from the {@link ServiceLoader} with {@link JStachioExtension}
065         * as the SPI.
066         * @return jstachio extensions found by the ServiceLoader
067         */
068        public static JStachioExtensions of() {
069                Iterable<JStachioExtension> it = ServiceLoader.load(JStachioExtension.class);
070                return of(it);
071        }
072
073        /**
074         * Composite Config where the first config that returns a nonnull for
075         * {@link JStachioConfig#getProperty(String)} is used.
076         * @return config
077         */
078        JStachioConfig getConfig();
079
080        /**
081         * Composite Filter where the ordering of the filter is based on a combination of
082         * {@link JStachioFilter#order()} first and then the order in the iterable passed to
083         * {@link #of(Iterable)}.
084         * @return filter
085         */
086        JStachioFilter getFilter();
087
088        /**
089         * Composite Template finder where the first template finder that finds a template is
090         * used.
091         * @return template finder
092         */
093        JStachioTemplateFinder getTemplateFinder();
094
095        /**
096         * The orignal contained extensions excluding the composites.
097         * @return found services
098         */
099        List<JStachioExtension> getExtensions();
100
101        /**
102         * Finds a specific implementation using {@link Class#isAssignableFrom(Class)}.
103         * @param <T> the implementation type
104         * @param c the implementation type.
105         * @return an implementation if found
106         */
107        default <T extends JStachioExtension> Optional<T> findExtension(Class<T> c) {
108                return getExtensions().stream().filter(s -> c.isAssignableFrom(s.getClass())).map(c::cast).findFirst();
109        }
110
111}
112
113class DefaultJStachioExtensions implements JStachioExtensions {
114
115        private final List<JStachioExtension> services;
116
117        private final JStachioConfig config;
118
119        private final JStachioFilter filter;
120
121        private final JStachioTemplateFinder templateFinder;
122
123        private DefaultJStachioExtensions(List<JStachioExtension> services, JStachioConfig config, JStachioFilter filter,
124                        JStachioTemplateFinder templateFinder) {
125                super();
126                this.services = services;
127                this.config = config;
128                this.filter = filter;
129                this.templateFinder = templateFinder;
130        }
131
132        /**
133         * Create a container from service providers.
134         * @param it services
135         * @return bean like container of services.
136         */
137        static JStachioExtensions of(Stream<? extends JStachioExtension> it) {
138                List<JStachioExtensionProvider> svs = new ArrayList<>();
139                it.forEach(s -> svs.add(JStachioExtensionProvider.of(s)));
140
141                List<JStachioConfig> configs = new ArrayList<>();
142                List<JStachioFilter> filters = new ArrayList<>();
143                List<JStachioTemplateFinder> finders = new ArrayList<>();
144
145                for (var sv : svs) {
146                        var c = sv.provideConfig();
147                        if (c != null) {
148                                configs.add(c);
149                        }
150                }
151                JStachioConfig config = CompositeConfig.of(configs);
152
153                for (var sv : svs) {
154                        sv.init(config);
155                        @Nullable
156                        JStachioFilter filt = sv.provideFilter();
157                        if (filt != null) {
158                                filters.add(filt);
159                        }
160                        @Nullable
161                        JStachioTemplateFinder find = sv.provideTemplateFinder();
162                        if (find != null) {
163                                finders.add(find);
164                        }
165                }
166                JStachioFilter filter = JStachioFilter.compose(filters);
167                if (finders.isEmpty()) {
168                        finders.add(
169                                        JStachioTemplateFinder.cachedTemplateFinder(JStachioTemplateFinder.defaultTemplateFinder(config)));
170                }
171                JStachioTemplateFinder templateFinder = CompositeTemplateFinder.of(finders);
172                return new DefaultJStachioExtensions(List.copyOf(svs), config, filter, templateFinder);
173        }
174
175        /**
176         * Composite Config
177         * @return config
178         */
179        @Override
180        public JStachioConfig getConfig() {
181                return config;
182        }
183
184        /**
185         * Composite Filter
186         * @return filter
187         */
188        @Override
189        public JStachioFilter getFilter() {
190                return filter;
191        }
192
193        /**
194         * Composite Template finder
195         * @return template finder
196         */
197        @Override
198        public JStachioTemplateFinder getTemplateFinder() {
199                return templateFinder;
200        }
201
202        /**
203         * Services
204         * @return found services
205         */
206        @Override
207        public List<JStachioExtension> getExtensions() {
208                return services;
209        }
210
211}