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
010/**
011 * Runtime Config Service.
012 * <p>
013 * While a majority of jstachio config is static and done at compile time some config like
014 * logging and disabling extensions is needed at runtime. Config and DI agnostic
015 * extensions should use this facility for simple key value based config.
016 * <p>
017 * The default resolved config uses System properties but can be replaced by implementing
018 * this extension.
019 * 
020 * @see JStachioExtension
021 * @author agentgt
022 */
023public non-sealed interface JStachioConfig extends JStachioExtension {
024
025        /**
026         * Config key to disable reflection based lookup of templates for other fallback
027         * mechanisms
028         */
029        public static String REFLECTION_TEMPLATE_DISABLE = "jstachio.reflection.template.disable";
030
031        /**
032         * Config key to disable if logging. By default logging is enabled.
033         */
034        public static String LOGGING_DISABLE = "jstachio.logging.disable";
035
036        /**
037         * Gets a property from some config implementation.
038         * @param key the key to use to lookup
039         * @return if not found <code>null</code>.
040         */
041        public @Nullable String getProperty(String key);
042
043        /**
044         * See {@link Boolean#getBoolean(String)}.
045         * @param key the property key
046         * @return only true if string is "true"
047         */
048        default boolean getBoolean(String key) {
049                String prop = getProperty(key);
050                return Boolean.parseBoolean(prop);
051        }
052
053        /**
054         * Gets the property as a boolean and if no property value is found the fallback is
055         * used.
056         * @param key property key
057         * @param fallback if property has no value this value is used.
058         * @return the parsed boolean or the fallback
059         */
060        default boolean getBoolean(String key, boolean fallback) {
061                String prop = getProperty(key);
062                if (prop == null) {
063                        return fallback;
064                }
065                return Boolean.parseBoolean(prop);
066        }
067
068        /**
069         * A NonNull friendly analog of {@link System#getProperty(String, String)} that will
070         * never return null unlike System.getProperty which is PolyNull.
071         * @param key checked if null and will NPE immediatly if it is
072         * @param fallback used if the retrieved property is null
073         * @return property or fallback if property is not found (<code>null</code>).
074         * @throws NullPointerException if the fallback is null or if the key is null.
075         */
076        default String requireProperty(String key, String fallback) {
077                if (key == null) {
078                        throw new NullPointerException("key is null");
079                }
080                String v = getProperty(key);
081                if (v == null) {
082                        v = fallback;
083                }
084                if (v == null) {
085                        throw new NullPointerException("fallback is null. key: " + key);
086                }
087                return v;
088        }
089
090        /**
091         * Gets a system logger if the property {@link #LOGGING_DISABLE} is
092         * <code>false</code>. If the property is set to a <code>true</code> value a NOOP
093         * Logger <em>that will not trigger initialization of the System {@link Logger}
094         * facilities</em> will be returned. The NOOP logger is always disabled at every level
095         * and will not produce any output.
096         * @param name the name of the logger usually the class.
097         * @return the System logger.
098         * @see #noopLogger()
099         */
100        default Logger getLogger(String name) {
101                if (!getBoolean(LOGGING_DISABLE)) {
102                        return System.getLogger(name);
103                }
104                return noopLogger();
105        }
106
107        /**
108         * NOOP Logger <em>that will not trigger initialization of the System {@link Logger}
109         * facilities</em>. The NOOP logger is always disabled at every level and will not
110         * produce any output.
111         * <p>
112         * Extensions might find this useful to set a nonnull Logger field like: <pre>
113         * private Logger logger = JStacheConfig.noopLogger();
114         * public void init(JStacheConfig config) {
115         *     logger = config.getLogger(getClass().getName());
116         * }
117         * </pre>
118         * @return singleton instance of noop logger
119         */
120        public static Logger noopLogger() {
121                return NOOPLogger.INSTANCE;
122        }
123
124}
125
126enum NOOPLogger implements Logger {
127
128        INSTANCE;
129
130        @Override
131        public @NonNull String getName() {
132                return "NOOPLogger";
133        }
134
135        @Override
136        public boolean isLoggable(@NonNull Level level) {
137                return false;
138        }
139
140        @Override
141        public void log(@NonNull Level level, @Nullable ResourceBundle bundle, @Nullable String msg,
142                        @Nullable Throwable thrown) {
143
144        }
145
146        @Override
147        public void log(@NonNull Level level, @Nullable ResourceBundle bundle, @Nullable String format,
148                        @Nullable Object @NonNull... params) {
149
150        }
151
152}
153
154enum SystemPropertyConfig implements JStachioConfig {
155
156        INSTANCE;
157
158        @Override
159        public @Nullable String getProperty(String key) {
160                return System.getProperty(key);
161        }
162
163}
164
165class CompositeConfig implements JStachioConfig {
166
167        private final List<JStachioConfig> configs;
168
169        CompositeConfig(List<JStachioConfig> configs) {
170                super();
171                this.configs = configs;
172        }
173
174        @Override
175        public @Nullable String getProperty(String key) {
176                for (var c : configs) {
177                        String v = c.getProperty(key);
178                        if (v != null) {
179                                return v;
180                        }
181                }
182                return null;
183        }
184
185}