001/*
002 * Configurate
003 * Copyright (C) zml and Configurate contributors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.spongepowered.configurate.loader;
018
019import static java.util.Objects.requireNonNull;
020
021import org.checkerframework.checker.nullness.qual.Nullable;
022import org.spongepowered.configurate.ConfigurationNode;
023import org.spongepowered.configurate.serialize.Scalars;
024import org.spongepowered.configurate.serialize.SerializationException;
025
026import java.util.Arrays;
027
028/**
029 * A provider for {@link ConfigurationLoader} options.
030 *
031 * <p>This allows initializing a loader from sources like system properties,
032 * environment variables, or an existing configuration node.</p>
033 *
034 * <p>To allow working with primitive value sources, only
035 * scalar values are supported.</p>
036 *
037 * @since 4.2.0
038 */
039@SuppressWarnings("checkstyle:NoGetSetPrefix") // we need something to not overlap with primitives
040public interface LoaderOptionSource {
041
042    /**
043     * Retrieve loader options from environment variables.
044     *
045     * <p>When reading environment variables, property keys will be converted to
046     * upper case and joined by '{@code _}' (an underscore).</p>
047     *
048     * <p>This source will use a default prefix of {@code CONFIGURATE}.</p>
049     *
050     * @return a source retrieving options from environment variables
051     * @since 4.2.0
052     */
053    static LoaderOptionSource environmentVariables() {
054        return LoaderOptionSources.ENVIRONMENT;
055    }
056
057    /**
058     * Retrieve loader options from environment variables.
059     *
060     * <p>When reading environment variables, property keys will be converted to
061     * upper case and joined by '{@code _}' (an underscore).</p>
062     *
063     * @param prefix the prefix to prepend to option paths
064     * @return a source retrieving options from environment variables
065     * @since 4.2.0
066     */
067    static LoaderOptionSource environmentVariables(final String prefix) {
068        return new LoaderOptionSources.EnvironmentVariables(requireNonNull(prefix, "prefix"));
069    }
070
071    /**
072     * Retrieve loader options from system properties.
073     *
074     * <p>When reading system properties, property keys will be joined by
075     * '{@code .}' (a dot).</p>
076     *
077     * <p>This source will use a default prefix of {@code configurate}.</p>
078     *
079     * @return a source retrieving options from system properties
080     * @since 4.2.0
081     */
082    static LoaderOptionSource systemProperties() {
083        return LoaderOptionSources.SYSTEM_PROPERTIES;
084    }
085
086    /**
087     * Retrieve loader options from system properties.
088     *
089     * <p>When reading system properties, property keys will be joined by
090     * '{@code .}' (a dot).</p>
091     *
092     * @param prefix the prefix to prepend to option paths
093     * @return a source retrieving options from system properties
094     * @since 4.2.0
095     */
096    static LoaderOptionSource systemProperties(final String prefix) {
097        return new LoaderOptionSources.SystemProperties(requireNonNull(prefix, "prefix"));
098    }
099
100    /**
101     * Create an option source that will read from an existing
102     * configuration node.
103     *
104     * <p>Option paths will be converted directly to node paths.</p>
105     *
106     * @param node the node to read options from
107     * @return a source retrieving options from the provided node
108     * @since 4.2.0
109     */
110    static LoaderOptionSource node(final ConfigurationNode node) {
111        return new LoaderOptionSources.Node(requireNonNull(node, "node"));
112    }
113
114    /**
115     * Create an option source that will try the provided sources in order.
116     *
117     * <p>The first source with a present value will be used.</p>
118     *
119     * @param sources the option sources to delegate to
120     * @return a new option source
121     * @since 4.2.0
122     */
123    static LoaderOptionSource composite(final LoaderOptionSource... sources) {
124        return new LoaderOptionSources.Composite(Arrays.copyOf(sources, sources.length));
125    }
126
127    /**
128     * Get the value at the provided path.
129     *
130     * @param path the path for options.
131     * @return a value, or {@code null} if none is present
132     * @throws IllegalArgumentException if the provided path is an empty array
133     * @since 4.2.0
134     */
135    @Nullable String get(String... path);
136
137    /**
138     * Get the value at the provided path.
139     *
140     * <p>If no value is present, {@code defaultValue} will be returned.</p>
141     *
142     * @param path the path for options.
143     * @param defaultValue the value to return if none is present
144     * @return a value, or {@code defaultValue} if none is present
145     * @throws IllegalArgumentException if the provided path is an empty array
146     * @since 4.2.0
147     */
148    default String getOr(final String defaultValue, final String... path) {
149        final @Nullable String value = this.get(path);
150        return value == null ? defaultValue : value;
151    }
152
153    /**
154     * Get the value at the provided path as an enum constant.
155     *
156     * @param <T> enum type
157     * @param enumClazz the enum type's class
158     * @param path the path for options.
159     * @return a value, or {@code null} if none is present or the provided value
160     *     is not a member of the enum
161     * @throws IllegalArgumentException if the provided path is an empty array
162     * @since 4.2.0
163     */
164    @SuppressWarnings("unchecked")
165    default <T extends Enum<T>> @Nullable T getEnum(final Class<T> enumClazz, final String... path) {
166        final @Nullable String value = this.get(path);
167        try {
168            return value == null ? null : (T) Scalars.ENUM.deserialize(enumClazz, value);
169        } catch (SerializationException e) {
170            return null;
171        }
172    }
173
174    /**
175     * Get the value at the provided path as an enum constant.
176     *
177     * <p>If no value is present or the provided value is not a known enum
178     * constant, {@code defaultValue} will be returned.</p>
179     *
180     * @param <T> enum type
181     * @param enumClazz the enum type's class
182     * @param path the path for options.
183     * @param defaultValue the value to return if none is present
184     * @return a value, or {@code defaultValue} if none is present
185     * @throws IllegalArgumentException if the provided path is an empty array
186     * @since 4.2.0
187     */
188    default <T extends Enum<T>> T getEnum(final Class<T> enumClazz, final T defaultValue, final String... path) {
189        final @Nullable T value = this.getEnum(enumClazz, path);
190        return value == null ? defaultValue : value;
191    }
192
193    // For primitives, a default is required since there is no null value
194
195    /**
196     * Get the value at the provided path as an integer.
197     *
198     * <p>If no value is present or the value is not a valid integer,
199     * {@code defaultValue} will be returned.</p>
200     *
201     * @param path the path for options.
202     * @param defaultValue the value to return if none is available
203     * @return a value, or {@code defaultValue} if none is present
204     * @throws IllegalArgumentException if the provided path is an empty array
205     * @since 4.2.0
206     */
207    default int getInt(final int defaultValue, final String... path) {
208        final @Nullable String value = this.get(path);
209        if (value == null) {
210            return defaultValue;
211        }
212        final @Nullable Integer attempt = Scalars.INTEGER.tryDeserialize(value);
213        return attempt == null ? defaultValue : attempt;
214    }
215
216    /**
217     * Get the value at the provided path as a double.
218     *
219     * <p>If no value is present or the value is not a valid double,
220     * {@code defaultValue} will be returned.</p>
221     *
222     * @param path the path for options.
223     * @param defaultValue the value to return if none is available
224     * @return a value, or {@code defaultValue} if none is present
225     * @throws IllegalArgumentException if the provided path is an empty array
226     * @since 4.2.0
227     */
228    default double getDouble(final double defaultValue, final String... path) {
229        final @Nullable String value = this.get(path);
230        if (value == null) {
231            return defaultValue;
232        }
233        final @Nullable Double attempt = Scalars.DOUBLE.tryDeserialize(value);
234        return attempt == null ? defaultValue : attempt;
235    }
236
237    /**
238     * Get the value at the provided path as a boolean.
239     *
240     * <p>If no value is present or the value is not a valid boolean,
241     * {@code defaultValue} will be returned.</p>
242     *
243     * @param path the path for options.
244     * @param defaultValue the value to return if none is available
245     * @return a value, or {@code defaultValue} if none is present
246     * @throws IllegalArgumentException if the provided path is an empty array
247     * @since 4.2.0
248     */
249    default boolean getBoolean(final boolean defaultValue, final String... path) {
250        final @Nullable String value = this.get(path);
251        if (value == null) {
252            return defaultValue;
253        }
254        final @Nullable Boolean attempt = Scalars.BOOLEAN.tryDeserialize(value);
255        return attempt == null ? defaultValue : attempt;
256    }
257
258}