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