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;
018
019import static java.util.Objects.requireNonNull;
020
021import com.google.auto.value.AutoValue;
022import com.google.errorprone.annotations.CheckReturnValue;
023import org.checkerframework.checker.nullness.qual.Nullable;
024import org.spongepowered.configurate.loader.ConfigurationLoader;
025import org.spongepowered.configurate.serialize.TypeSerializerCollection;
026import org.spongepowered.configurate.util.MapFactories;
027import org.spongepowered.configurate.util.MapFactory;
028import org.spongepowered.configurate.util.Types;
029import org.spongepowered.configurate.util.UnmodifiableCollections;
030
031import java.lang.reflect.Type;
032import java.util.Objects;
033import java.util.Set;
034import java.util.function.Consumer;
035
036/**
037 * This object is a holder for general configuration options.
038 *
039 * <p>This is meant to hold options that are used in configuring how the
040 * configuration data structures are handled, rather than the serialization
041 * configuration which is located in {@link ConfigurationLoader}s.</p>
042 *
043 * <p>This class is immutable.</p>
044 *
045 * @since 4.0.0
046 */
047@AutoValue
048@CheckReturnValue // No method has side effects, so any return value must be used.
049public abstract class ConfigurationOptions {
050
051    static class Lazy {
052
053        // avoid initialization cycles
054
055        static final ConfigurationOptions DEFAULTS = new AutoValue_ConfigurationOptions(MapFactories.insertionOrdered(), null,
056                TypeSerializerCollection.defaults(), null, true, true);
057
058    }
059
060    ConfigurationOptions() {
061    }
062
063    /**
064     * Get the default set of options. This may be overridden by your chosen
065     * configuration loader, so when building configurations it is recommended
066     * to access {@code AbstractConfigurationLoader.Builder#getDefaultOptions()}
067     * instead.
068     *
069     * @return the default options
070     * @since 4.0.0
071     */
072    public static ConfigurationOptions defaults() {
073        return Lazy.DEFAULTS;
074    }
075
076    /**
077     * Gets the {@link MapFactory} specified in these options.
078     *
079     * @return the map factory
080     * @since 4.0.0
081     */
082    public abstract MapFactory mapFactory();
083
084    /**
085     * Creates a new {@link ConfigurationOptions} instance, with the specified
086     * {@link MapFactory} set, and all other settings copied from this instance.
087     *
088     * @param mapFactory the new factory to use to create a map
089     * @return the new options object
090     * @since 4.0.0
091     */
092    public ConfigurationOptions mapFactory(final MapFactory mapFactory) {
093        requireNonNull(mapFactory, "mapFactory");
094        if (this.mapFactory() == mapFactory) {
095            return this;
096        }
097        return new AutoValue_ConfigurationOptions(mapFactory, this.header(), this.serializers(), this.nativeTypes(),
098                this.shouldCopyDefaults(), this.implicitInitialization());
099    }
100
101    /**
102     * Gets the header specified in these options.
103     *
104     * @return the current header. Lines are split by \n, with no
105     *         trailing newline
106     * @since 4.0.0
107     */
108    public abstract @Nullable String header();
109
110    /**
111     * Creates a new {@link ConfigurationOptions} instance, with the specified
112     * header set, and all other settings copied from this instance.
113     *
114     * @param header the new header to use
115     * @return the new options object
116     * @since 4.0.0
117     */
118    public ConfigurationOptions header(final @Nullable String header) {
119        if (Objects.equals(this.header(), header)) {
120            return this;
121        }
122        return new AutoValue_ConfigurationOptions(this.mapFactory(), header, this.serializers(), this.nativeTypes(),
123                this.shouldCopyDefaults(), this.implicitInitialization());
124    }
125
126    /**
127     * Gets the {@link TypeSerializerCollection} specified in these options.
128     *
129     * @return the type serializers
130     * @since 4.0.0
131     */
132    public abstract TypeSerializerCollection serializers();
133
134    /**
135     * Creates a new {@link ConfigurationOptions} instance, with the specified {@link TypeSerializerCollection}
136     * set, and all other settings copied from this instance.
137     *
138     * @param serializers the serializers to use
139     * @return the new options object
140     * @since 4.0.0
141     */
142    public ConfigurationOptions serializers(final TypeSerializerCollection serializers) {
143        requireNonNull(serializers, "serializers");
144        if (this.serializers().equals(serializers)) {
145            return this;
146        }
147        return new AutoValue_ConfigurationOptions(this.mapFactory(), this.header(), serializers, this.nativeTypes(),
148                this.shouldCopyDefaults(), this.implicitInitialization());
149    }
150
151    /**
152     * Creates a new {@link ConfigurationOptions} instance, with a new
153     * {@link TypeSerializerCollection} created as a child of this options'
154     * current collection. The provided function will be called with the builder
155     * for this new collection to allow registering more type serializers.
156     *
157     * @param serializerBuilder accepts a builder for the collection that will
158     *                          be used in the returned options object.
159     * @return the new options object
160     * @since 4.0.0
161     */
162    public final ConfigurationOptions serializers(final Consumer<TypeSerializerCollection.Builder> serializerBuilder) {
163        requireNonNull(serializerBuilder, "serializerBuilder");
164        final TypeSerializerCollection.Builder builder = this.serializers().childBuilder();
165        serializerBuilder.accept(builder);
166        return this.serializers(builder.build());
167    }
168
169    @SuppressWarnings("AutoValueImmutableFields") // we don't use guava
170    abstract @Nullable Set<Class<?>> nativeTypes();
171
172    /**
173     * Creates a new {@link ConfigurationOptions} instance, with the specified native types
174     * set, and all other settings copied from this instance.
175     *
176     * <p>Native types are format-dependent, and must be provided by a
177     * configuration loader's {@link ConfigurationLoader#defaultOptions() default options}</p>
178     *
179     * <p>Null indicates that all types are accepted.</p>
180     *
181     * @param nativeTypes the types that will be accepted to a
182     *                     call to {@link ConfigurationNode#set(Object)}
183     * @return updated options object
184     * @since 4.0.0
185     */
186    public ConfigurationOptions nativeTypes(final @Nullable Set<Class<?>> nativeTypes) {
187        if (Objects.equals(this.nativeTypes(), nativeTypes)) {
188            return this;
189        }
190        return new AutoValue_ConfigurationOptions(this.mapFactory(), this.header(), this.serializers(),
191                nativeTypes == null ? null : UnmodifiableCollections.copyOf(nativeTypes), this.shouldCopyDefaults(), this.implicitInitialization());
192    }
193
194    /**
195     * Gets whether objects of the provided type are natively accepted as values
196     * for nodes with this as their options object.
197     *
198     * @param type the type to check
199     * @return whether the type is accepted
200     * @since 4.0.0
201     */
202    public final boolean acceptsType(final Class<?> type) {
203        requireNonNull(type, "type");
204
205        final @Nullable Set<Class<?>> nativeTypes = this.nativeTypes();
206
207        if (nativeTypes == null) {
208            return true;
209        }
210
211        if (nativeTypes.contains(type)) {
212            return true;
213        }
214
215        if (type.isPrimitive() && nativeTypes.contains(Types.box(type))) {
216            return true;
217        }
218
219        final Type unboxed = Types.unbox(type);
220        if (unboxed != type && nativeTypes.contains(unboxed)) {
221            return true;
222        }
223
224        for (final Class<?> clazz : nativeTypes) {
225            if (clazz.isAssignableFrom(type)) {
226                return true;
227            }
228        }
229
230        return false;
231    }
232
233    /**
234     * Gets whether or not default parameters provided to {@link ConfigurationNode} getter methods
235     * should be set to the node when used.
236     *
237     * @return whether defaults should be copied into value
238     * @since 4.0.0
239     */
240    public abstract boolean shouldCopyDefaults();
241
242    /**
243     * Creates a new {@link ConfigurationOptions} instance, with the specified
244     * 'copy defaults' setting set, and all other settings copied from
245     * this instance.
246     *
247     * @param shouldCopyDefaults whether to copy defaults
248     * @return updated options object
249     * @see #shouldCopyDefaults() for information on what this method does
250     * @since 4.0.0
251     */
252    public ConfigurationOptions shouldCopyDefaults(final boolean shouldCopyDefaults) {
253        if (this.shouldCopyDefaults() == shouldCopyDefaults) {
254            return this;
255        }
256
257        return new AutoValue_ConfigurationOptions(this.mapFactory(), this.header(), this.serializers(), this.nativeTypes(),
258                shouldCopyDefaults, this.implicitInitialization());
259    }
260
261    /**
262     * Get whether values should be implicitly initialized.
263     *
264     * <p>When this is true, any value get operations will return an empty value
265     * rather than null. This extends through to fields loaded into
266     * object-mapped classes.</p>
267     *
268     * <p>This option is enabled by default.</p>
269     *
270     * @return if implicit initialization is enabled.
271     * @since 4.0.0
272     */
273    public abstract boolean implicitInitialization();
274
275    /**
276     * Create a new {@link ConfigurationOptions} instance with the specified
277     * implicit initialization setting.
278     *
279     * @param implicitInitialization whether to initialize implicitly
280     * @return a new options object
281     * @see #implicitInitialization() for more details
282     * @since 4.0.0
283     */
284    public ConfigurationOptions implicitInitialization(final boolean implicitInitialization) {
285        if (this.implicitInitialization() == implicitInitialization) {
286            return this;
287        }
288
289        return new AutoValue_ConfigurationOptions(this.mapFactory(), this.header(), this.serializers(), this.nativeTypes(),
290                this.shouldCopyDefaults(), implicitInitialization);
291    }
292
293}