001package io.jstach.jstachio.spi;
002
003import java.lang.System.Logger;
004import java.util.List;
005import java.util.ResourceBundle;
006
007import org.eclipse.jdt.annotation.NonNull;
008import org.eclipse.jdt.annotation.Nullable;
009
010import io.jstach.jstache.JStacheConfig;
011
012/**
013 * Runtime Config Service.
014 * <p>
015 * While a majority of jstachio config is static and done at compile time some config like
016 * logging and disabling extensions is needed at runtime. Config and DI agnostic
017 * extensions should use this facility for simple key value based config.
018 * <p>
019 * The default resolved config uses System properties but can be replaced by implementing
020 * this extension.
021 * <p>
022 * Core runtime configuration properties for the {@link JStachioFactory#defaultJStachio()
023 * default JStachio} are:
024 * <ul id="_jstachio_config_properties">
025 * <li>{@link #REFLECTION_TEMPLATE_DISABLE}</li>
026 * <li>{@link #SERVICELOADER_TEMPLATE_DISABLE}</li>
027 * <li>{@link #LOGGING_DISABLE}</li>
028 * </ul>
029 * <strong>This configuration is for runtime only and NOT {@linkplain JStacheConfig static
030 * configuration} needed for code generation.</strong>
031 *
032 * @see JStachioExtension
033 * @author agentgt
034 */
035public non-sealed interface JStachioConfig extends JStachioExtension {
036
037        /**
038         * Config key to disable non service loader reflection based lookup of templates. If a
039         * custom JStachio is being used this configuration property maybe irrelevant.
040         * <p>
041         * Valid values are <code>true</code> or <code>false</code>. The default is
042         * <code>false</code>.
043         */
044        public static String REFLECTION_TEMPLATE_DISABLE = "jstachio.reflection.template.disable";
045
046        /**
047         * Config key to disable service loader based lookup of templates. If a custom
048         * JStachio is being used this configuration property maybe irrelevant.
049         * <p>
050         * Valid values are <code>true</code> or <code>false</code>. The default is
051         * <code>false</code>.
052         */
053        public static String SERVICELOADER_TEMPLATE_DISABLE = "jstachio.serviceloader.template.disable";
054
055        /**
056         * Config key to disable logging. By default logging is enabled and will use the
057         * {@link System.Logger}. If a custom {@link JStachioConfig} is being used this
058         * configuration property maybe irrelevant.
059         * <p>
060         * Valid values are <code>true</code> or <code>false</code>. The default is
061         * <code>false</code>.
062         */
063        public static String LOGGING_DISABLE = "jstachio.logging.disable";
064
065        /**
066         * Gets a property from some config implementation.
067         * @param key the key to use to lookup
068         * @return if not found <code>null</code>.
069         */
070        public @Nullable String getProperty(String key);
071
072        /**
073         * See {@link Boolean#getBoolean(String)}.
074         * @param key the property key
075         * @return only true if string is "true"
076         */
077        default boolean getBoolean(String key) {
078                String prop = getProperty(key);
079                return Boolean.parseBoolean(prop);
080        }
081
082        /**
083         * Gets the property as a boolean and if no property value is found the fallback is
084         * used.
085         * @param key property key
086         * @param fallback if property has no value this value is used.
087         * @return the parsed boolean or the fallback
088         */
089        default boolean getBoolean(String key, boolean fallback) {
090                String prop = getProperty(key);
091                if (prop == null) {
092                        return fallback;
093                }
094                return Boolean.parseBoolean(prop);
095        }
096
097        /**
098         * A NonNull friendly analog of {@link System#getProperty(String, String)} that will
099         * never return null unlike System.getProperty which is PolyNull.
100         * @param key checked if null and will NPE immediatly if it is
101         * @param fallback used if the retrieved property is null
102         * @return property or fallback if property is not found (<code>null</code>).
103         * @throws NullPointerException if the fallback is null or if the key is null.
104         */
105        default String requireProperty(String key, String fallback) {
106                if (isNull(key)) {
107                        throw new NullPointerException("key is null");
108                }
109                String v = getProperty(key);
110                if (v == null) {
111                        v = fallback;
112                }
113                if (isNull(v)) {
114                        throw new NullPointerException("fallback is null. key: " + key);
115                }
116                return v;
117        }
118
119        private static boolean isNull(Object o) {
120                return o == null;
121        }
122
123        /**
124         * Gets a system logger if the property {@link #LOGGING_DISABLE} is
125         * <code>false</code>. If the property is set to a <code>true</code> value a NOOP
126         * Logger <em>that will not trigger initialization of the System {@link Logger}
127         * facilities</em> will be returned. The NOOP logger is always disabled at every level
128         * and will not produce any output.
129         * @param name the name of the logger usually the class.
130         * @return the System logger.
131         * @see #noopLogger()
132         */
133        default Logger getLogger(String name) {
134                if (!getBoolean(LOGGING_DISABLE)) {
135                        return System.getLogger(name);
136                }
137                return noopLogger();
138        }
139
140        /**
141         * NOOP Logger <em>that will not trigger initialization of the System {@link Logger}
142         * facilities</em>. The NOOP logger is always disabled at every level and will not
143         * produce any output.
144         * <p>
145         * Extensions might find this useful to set a nonnull Logger field like:
146         * <pre><code class="language-java">
147         * private Logger logger = JStacheConfig.noopLogger();
148         * public void init(JStacheConfig config) {
149         *     logger = config.getLogger(getClass().getName());
150         * }
151         * </code> </pre>
152         * @return singleton instance of noop logger
153         */
154        public static Logger noopLogger() {
155                return NOOPLogger.INSTANCE;
156        }
157
158}
159
160enum NOOPLogger implements Logger {
161
162        INSTANCE;
163
164        @Override
165        public @NonNull String getName() {
166                return "NOOPLogger";
167        }
168
169        @Override
170        public boolean isLoggable(Level level) {
171                return false;
172        }
173
174        @Override
175        public void log(Level level, @Nullable ResourceBundle bundle, @Nullable String msg, @Nullable Throwable thrown) {
176                // Do nothing
177        }
178
179        @Override
180        public void log(Level level, @Nullable ResourceBundle bundle, @Nullable String format,
181                        @Nullable Object @NonNull... params) {
182                // Do nothing
183        }
184
185}
186
187enum SystemPropertyConfig implements JStachioConfig {
188
189        INSTANCE;
190
191        @Override
192        public @Nullable String getProperty(String key) {
193                return System.getProperty(key);
194        }
195
196}
197
198class CompositeConfig implements JStachioConfig {
199
200        private final List<JStachioConfig> configs;
201
202        static JStachioConfig of(List<JStachioConfig> configs) {
203                if (configs.isEmpty()) {
204                        return SystemPropertyConfig.INSTANCE;
205                }
206                if (configs.size() == 1) {
207                        return configs.get(0);
208                }
209                return new CompositeConfig(configs);
210
211        }
212
213        private CompositeConfig(List<JStachioConfig> configs) {
214                super();
215                this.configs = configs;
216        }
217
218        @Override
219        public @Nullable String getProperty(String key) {
220                for (var c : configs) {
221                        String v = c.getProperty(key);
222                        if (v != null) {
223                                return v;
224                        }
225                }
226                return null;
227        }
228
229        @Override
230        public Logger getLogger(String name) {
231                return configs.get(0).getLogger(name);
232        }
233
234}