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.extra.dfu.v2;
018
019import static java.util.Objects.requireNonNull;
020
021import com.google.common.collect.ImmutableMap;
022import com.mojang.datafixers.DSL;
023import com.mojang.datafixers.Dynamic;
024import com.mojang.datafixers.types.DynamicOps;
025import com.mojang.datafixers.types.Type;
026import org.checkerframework.checker.nullness.qual.Nullable;
027import org.spongepowered.configurate.CommentedConfigurationNode;
028import org.spongepowered.configurate.ConfigurationNode;
029import org.spongepowered.configurate.ConfigurationOptions;
030import org.spongepowered.configurate.serialize.TypeSerializerCollection;
031
032import java.util.Map;
033import java.util.Optional;
034import java.util.function.Function;
035import java.util.function.Supplier;
036import java.util.stream.Stream;
037
038/**
039 * Implementation of DataFixerUpper's DynamicOps.
040 *
041 * <p>When possible, the first node's {@link ConfigurationNode#copy()}  method
042 * will be used to create a new node to contain results. Otherwise, the provided
043 * factory will be used. The default factory creates a
044 * {@link CommentedConfigurationNode} with the {@link TypeSerializerCollection#defaults() default TypeSerializer collection},
045 * but a custom factory may be provided.
046 *
047 * @since 4.0.0
048 */
049public final class ConfigurateOps implements DynamicOps<ConfigurationNode> {
050
051    private static final ConfigurateOps INSTANCE = new ConfigurateOps(CommentedConfigurationNode::root);
052
053    private final Supplier<? extends ConfigurationNode> factory;
054
055    /**
056     * Get the shared instance of this class, which creates new nodes using
057     * the default factory.
058     *
059     * @return the shared instance
060     * @since 4.0.0
061     */
062    public static DynamicOps<ConfigurationNode> instance() {
063        return INSTANCE;
064    }
065
066    /**
067     * Create a new instance of the ops, with a custom node factory.
068     *
069     * @param factory the factory function
070     * @return a new ops instance
071     * @since 4.0.0
072     */
073    public static DynamicOps<ConfigurationNode> withNodeFactory(final Supplier<? extends ConfigurationNode> factory) {
074        return new ConfigurateOps(factory);
075    }
076
077    /**
078     * Wrap a ConfigurationNode in a {@link Dynamic} instance. The returned Dynamic will use the same type
079     * serializer collection as the original node for its operations.
080     *
081     * @param node the node to wrap
082     * @return a wrapped node
083     * @since 4.0.0
084     */
085    public static Dynamic<ConfigurationNode> wrap(final ConfigurationNode node) {
086        if (node.options().serializers().equals(TypeSerializerCollection.defaults())) {
087            return new Dynamic<>(instance(), node);
088        } else {
089            final ConfigurationOptions opts = node.options();
090            return new Dynamic<>(withNodeFactory(() -> CommentedConfigurationNode.root(opts)), node);
091        }
092    }
093
094    ConfigurateOps(final Supplier<? extends ConfigurationNode> factory) {
095        this.factory = factory;
096    }
097
098    private static String unwrapKey(final ConfigurationNode node) {
099        return requireNonNull(node.getString(), "Kep nodes must have a value!");
100    }
101
102    @Override
103    public ConfigurationNode empty() {
104        return this.factory.get();
105    }
106
107    @Override
108    public Type<?> getType(final ConfigurationNode input) {
109        requireNonNull(input, "input");
110
111        if (input.isMap()) {
112            return DSL.compoundList(DSL.remainderType(), DSL.remainderType());
113        } else if (input.isList()) {
114            return DSL.list(DSL.remainderType());
115        } else {
116            final @Nullable Object value = input.rawScalar();
117            if (value == null) {
118                return DSL.nilType();
119            } else if (value instanceof String) {
120                return DSL.string();
121            } else if (value instanceof Boolean) {
122                return DSL.bool();
123            } else if (value instanceof Short) {
124                return DSL.shortType();
125            } else if (value instanceof Integer) {
126                return DSL.intType();
127            } else if (value instanceof Long) {
128                return DSL.longType();
129            } else if (value instanceof Float) {
130                return DSL.floatType();
131            } else if (value instanceof Double) {
132                return DSL.doubleType();
133            } else if (value instanceof Byte) {
134                return DSL.byteType();
135            } else {
136                throw new IllegalArgumentException("Scalar value '" + input + "' has an unknown type: " + value.getClass().getName());
137            }
138        }
139    }
140
141    @Override
142    public Optional<Number> getNumberValue(final ConfigurationNode input) {
143        if (!(input.isMap() || input.isList())) {
144            final @Nullable Object raw = input.rawScalar();
145            if (raw instanceof Number) {
146                return Optional.of((Number) raw);
147            } else if (raw instanceof Boolean) {
148                return Optional.of(input.getBoolean() ? 1 : 0);
149            }
150        }
151
152        return Optional.empty();
153    }
154
155    @Override
156    public ConfigurationNode createNumeric(final Number i) {
157        return empty().raw(i);
158    }
159
160    @Override
161    public ConfigurationNode createBoolean(final boolean value) {
162        return empty().raw(value);
163    }
164
165    @Override
166    public Optional<String> getStringValue(final ConfigurationNode input) {
167        return Optional.ofNullable(input.getString());
168    }
169
170    @Override
171    public ConfigurationNode createString(final String value) {
172        return empty().raw(value);
173    }
174
175    @Override
176    public ConfigurationNode mergeInto(final ConfigurationNode input, final ConfigurationNode value) {
177        if (input.isList()) {
178            final ConfigurationNode ret = input.copy();
179            ret.appendListNode().from(value);
180            return ret;
181        }
182        return input;
183    }
184
185    @Override
186    public ConfigurationNode mergeInto(final ConfigurationNode input, final ConfigurationNode key, final ConfigurationNode value) {
187        return input.copy().node(unwrapKey(key)).from(value);
188    }
189
190    /**
191     * Merge into a newly created node.
192     *
193     * @param first the primary node
194     * @param second the second node, with values that will override those in
195     *               the first node
196     * @return a newly created node
197     */
198    @Override
199    public ConfigurationNode merge(final ConfigurationNode first, final ConfigurationNode second) {
200        return first.copy().mergeFrom(second);
201
202    }
203
204    @Override
205    public Optional<Map<ConfigurationNode, ConfigurationNode>> getMapValues(final ConfigurationNode input) {
206        if (input.isMap()) {
207            final ImmutableMap.Builder<ConfigurationNode, ConfigurationNode> builder = ImmutableMap.builder();
208            for (final Map.Entry<Object, ? extends ConfigurationNode> entry : input.childrenMap().entrySet()) {
209                builder.put(empty().raw(entry.getKey()), entry.getValue().copy());
210            }
211            return Optional.of(builder.build());
212        }
213
214        return Optional.empty();
215    }
216
217    @Override
218    public ConfigurationNode createMap(final Map<ConfigurationNode, ConfigurationNode> map) {
219        final ConfigurationNode ret = empty();
220
221        for (final Map.Entry<ConfigurationNode, ConfigurationNode> entry : map.entrySet()) {
222            ret.node(unwrapKey(entry.getKey())).from(entry.getValue());
223        }
224
225        return ret;
226    }
227
228    @Override
229    public Optional<Stream<ConfigurationNode>> getStream(final ConfigurationNode input) {
230        if (input.isList()) {
231            final Stream<ConfigurationNode> stream = input.childrenList().stream().map(it -> it);
232            return Optional.of(stream);
233        }
234
235        return Optional.empty();
236    }
237
238    @Override
239    public ConfigurationNode createList(final Stream<ConfigurationNode> input) {
240        final ConfigurationNode ret = empty();
241        input.forEach(it -> ret.appendListNode().from(it));
242        return ret;
243    }
244
245    @Override
246    public ConfigurationNode remove(final ConfigurationNode input, final String key) {
247        if (input.isMap()) {
248            final ConfigurationNode ret = input.copy();
249            ret.node(key).raw(null);
250            return ret;
251        }
252
253        return input;
254    }
255
256    @Override
257    public Optional<ConfigurationNode> get(final ConfigurationNode input, final String key) {
258        final ConfigurationNode ret = input.node(key);
259        return ret.virtual() ? Optional.empty() : Optional.of(ret);
260    }
261
262    @Override
263    public Optional<ConfigurationNode> getGeneric(final ConfigurationNode input, final ConfigurationNode key) {
264        final ConfigurationNode ret = input.node(unwrapKey(key));
265        return ret.virtual() ? Optional.empty() : Optional.of(ret);
266    }
267
268    @Override
269    public ConfigurationNode set(final ConfigurationNode input, final String key, final ConfigurationNode value) {
270        final ConfigurationNode ret = input.copy();
271        ret.node(key).from(value);
272        return ret;
273    }
274
275    @Override
276    public ConfigurationNode update(final ConfigurationNode input, final String key, final Function<ConfigurationNode, ConfigurationNode> function) {
277        if (input.node(key).virtual()) {
278            return input;
279        }
280
281        final ConfigurationNode ret = input.copy();
282
283        final ConfigurationNode child = ret.node(key);
284        child.from(function.apply(child));
285        return ret;
286    }
287
288    @Override
289    public ConfigurationNode updateGeneric(final ConfigurationNode input, final ConfigurationNode wrappedKey,
290                                           final Function<ConfigurationNode, ConfigurationNode> function) {
291        final Object key = unwrapKey(wrappedKey);
292        if (input.node(key).virtual()) {
293            return input;
294        }
295
296        final ConfigurationNode ret = input.copy();
297
298        final ConfigurationNode child = ret.node(key);
299        child.from(function.apply(child));
300        return ret;
301    }
302
303    @Override
304    public String toString() {
305        return "Configurate";
306    }
307
308}