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 ninja.leaping.configurate;
018
019import com.google.common.collect.ImmutableList;
020import com.google.common.collect.ImmutableMap;
021import com.google.common.reflect.TypeParameter;
022import com.google.common.reflect.TypeToken;
023import ninja.leaping.configurate.objectmapping.ObjectMappingException;
024import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer;
025import org.checkerframework.checker.nullness.qual.NonNull;
026import org.checkerframework.checker.nullness.qual.Nullable;
027
028import java.util.Collection;
029import java.util.Collections;
030import java.util.LinkedList;
031import java.util.List;
032import java.util.Map;
033import java.util.Objects;
034import java.util.function.Function;
035import java.util.function.Supplier;
036
037import static java.util.Objects.requireNonNull;
038
039/**
040 * Simple implementation of {@link ConfigurationNode}.
041 */
042public class SimpleConfigurationNode implements ConfigurationNode {
043
044    /**
045     * The options determining the behaviour of this node
046     */
047    @NonNull
048    private final ConfigurationOptions options;
049
050    /**
051     * If this node is attached to a wider configuration structure
052     */
053    volatile boolean attached;
054
055    /**
056     * Path of this node.
057     *
058     * Internally, may only be modified when an operation that adds or removes a node at the same
059     * or higher level in the node tree
060     */
061    @Nullable
062    volatile Object key;
063
064    /**
065     * The parent of this node
066     */
067    @Nullable
068    private SimpleConfigurationNode parent;
069
070    /**
071     * The current value of this node
072     */
073    @NonNull
074    private volatile ConfigValue value;
075
076    /**
077     * Create a new node with no parent and {@link ConfigurationOptions#defaults() default} options
078     *
079     * @return The newly created node
080     * @deprecated Use {@link ConfigurationNode#root()} instead
081     */
082    @Deprecated
083    @NonNull
084    public static SimpleConfigurationNode root() {
085        return root(ConfigurationOptions.defaults());
086    }
087
088    /**
089     * Create a new node with no parent and defined options
090     *
091     * @param options The options to use in this node.
092     * @return The newly created node
093     * @deprecated Use {@link ConfigurationNode#root(ConfigurationOptions)} instead
094     */
095    @Deprecated
096    @NonNull
097    public static SimpleConfigurationNode root(@NonNull ConfigurationOptions options) {
098        return new SimpleConfigurationNode(null, null, options);
099    }
100
101    protected SimpleConfigurationNode(@Nullable Object key, @Nullable SimpleConfigurationNode parent, @NonNull ConfigurationOptions options) {
102        requireNonNull(options, "options");
103        this.key = key;
104        this.options = options;
105        this.parent = parent;
106        this.value = NullConfigValue.instance();
107
108        // if the parent is null, this node is a root node, and is therefore "attached"
109        if (parent == null) {
110            attached = true;
111        }
112    }
113
114    protected SimpleConfigurationNode(@Nullable  SimpleConfigurationNode parent, SimpleConfigurationNode copyOf) {
115        this.options = copyOf.options;
116        this.attached = true; // copies are always attached
117        this.key = copyOf.key;
118        this.parent = parent;
119        this.value = copyOf.value.copy(this);
120    }
121
122    /**
123     * Handles the copying of applied defaults, if enabled.
124     *
125     * @param defValue the default value
126     * @param <V> the value type
127     * @return the same value
128     */
129    private <V> V storeDefault(V defValue) {
130        if (defValue != null && getOptions().shouldCopyDefaults()) {
131            setValue(defValue);
132        }
133        return defValue;
134    }
135
136    private <V> V storeDefault(TypeToken<V> type, V defValue) throws ObjectMappingException {
137        if (defValue != null && getOptions().shouldCopyDefaults()) {
138            setValue(type, defValue);
139        }
140        return defValue;
141    }
142
143    @Override
144    public Object getValue(Object def) {
145        Object ret = value.getValue();
146        return ret == null ? storeDefault(def) : ret;
147    }
148
149    @Override
150    public Object getValue(@NonNull Supplier<Object> defSupplier) {
151        Object ret = value.getValue();
152        return ret == null ? storeDefault(defSupplier.get()) : ret;
153    }
154
155    @Override
156    public <T> T getValue(@NonNull Function<Object, T> transformer, T def) {
157        T ret = transformer.apply(getValue());
158        return ret == null ? storeDefault(def) : ret;
159    }
160
161    @Override
162    public <T> T getValue(@NonNull Function<Object, T> transformer, @NonNull Supplier<T> defSupplier) {
163        T ret = transformer.apply(getValue());
164        return ret == null ? storeDefault(defSupplier.get()) : ret;
165    }
166
167    @NonNull
168    @Override
169    public <T> List<T> getList(@NonNull Function<Object, T> transformer) {
170        final ImmutableList.Builder<T> ret = ImmutableList.builder();
171        ConfigValue value = this.value;
172        if (value instanceof ListConfigValue) {
173            // transform each value individually if the node is a list
174            for (SimpleConfigurationNode o : value.iterateChildren()) {
175                T transformed = transformer.apply(o.getValue());
176                if (transformed != null) {
177                    ret.add(transformed);
178                }
179            }
180        } else {
181            // transfer the value as a whole
182            T transformed = transformer.apply(value.getValue());
183            if (transformed != null) {
184                ret.add(transformed);
185            }
186        }
187
188        return ret.build();
189    }
190
191    @Override
192    public <T> List<T> getList(@NonNull Function<Object, T> transformer, List<T> def) {
193        List<T> ret = getList(transformer);
194        return ret.isEmpty() ? storeDefault(def) : ret;
195    }
196
197    @Override
198    public <T> List<T> getList(@NonNull Function<Object, T> transformer, @NonNull Supplier<List<T>> defSupplier) {
199        List<T> ret = getList(transformer);
200        return ret.isEmpty() ? storeDefault(defSupplier.get()) : ret;
201    }
202
203    @Override
204    public <T> List<T> getList(@NonNull TypeToken<T> type, List<T> def) throws ObjectMappingException {
205        List<T> ret = getValue(new TypeToken<List<T>>() {}
206                .where(new TypeParameter<T>() {}, type), def);
207        return ret == null || ret.isEmpty() ? storeDefault(def) : ret;
208    }
209
210    @Override
211    public <T> List<T> getList(@NonNull TypeToken<T> type, @NonNull Supplier<List<T>> defSupplier) throws ObjectMappingException {
212        List<T> ret = getValue(new TypeToken<List<T>>(){}.where(new TypeParameter<T>(){}, type), defSupplier);
213        return ret == null || ret.isEmpty() ? storeDefault(defSupplier.get()) : ret;
214    }
215
216    @Override
217    @SuppressWarnings("unchecked")
218    public <T> T getValue(@NonNull TypeToken<T> type, T def) throws ObjectMappingException {
219        Object value = getValue();
220        if (value == null) {
221            return storeDefault(type, def);
222        }
223
224        TypeSerializer<?> serial = getOptions().getSerializers().get(type);
225        if (serial == null) {
226            if (type.getRawType().isInstance(value)) {
227                return (T) type.getRawType().cast(value);
228            } else {
229                return storeDefault(type, def);
230            }
231        }
232        return (T) serial.deserialize(type, this);
233    }
234
235    @Override
236    @SuppressWarnings("unchecked")
237    public <T> T getValue(@NonNull TypeToken<T> type, @NonNull Supplier<T> defSupplier) throws ObjectMappingException {
238        Object value = getValue();
239        if (value == null) {
240            return storeDefault(type, defSupplier.get());
241        }
242
243        TypeSerializer<?> serial = getOptions().getSerializers().get(type);
244        if (serial == null) {
245            if (type.getRawType().isInstance(value)) {
246                return (T) type.getRawType().cast(value);
247            } else {
248                return storeDefault(type, defSupplier.get());
249            }
250        }
251        return (T) serial.deserialize(type, this);
252    }
253
254    @NonNull
255    @Override
256    public SimpleConfigurationNode setValue(@Nullable Object newValue) {
257        // if the value to be set is a configuration node already, unwrap and store the raw data
258        if (newValue instanceof ConfigurationNode) {
259            ConfigurationNode newValueAsNode = (ConfigurationNode) newValue;
260            if (newValueAsNode == this) { // this would be a no-op whoop
261                return this;
262            }
263
264            if (newValueAsNode.isList()) {
265                // handle list
266                attachIfNecessary();
267                ListConfigValue newList = new ListConfigValue(this);
268                synchronized (newValueAsNode) {
269                    newList.setValue(newValueAsNode.getChildrenList());
270                }
271                this.value = newList;
272                return this;
273
274            } else if (newValueAsNode.isMap()) {
275                // handle map
276                attachIfNecessary();
277                MapConfigValue newMap = new MapConfigValue(this);
278                synchronized (newValueAsNode) {
279                    newMap.setValue(newValueAsNode.getChildrenMap());
280                }
281                this.value = newMap;
282                return this;
283
284            } else {
285                // handle scalar/null
286                newValue = newValueAsNode.getValue();
287            }
288        }
289
290        // if the new value is null, handle detaching from this nodes parent
291        if (newValue == null) {
292            if (parent == null) {
293                clear();
294            } else {
295                parent.removeChild(key);
296            }
297            return this;
298        }
299
300        insertNewValue(newValue, false);
301        return this;
302    }
303
304    /**
305     * Handles the process of setting a new value for this node.
306     *
307     * @param newValue The new value
308     * @param onlyIfNull If the insertion should only take place if the current value is null
309     */
310    private void insertNewValue(Object newValue, boolean onlyIfNull) {
311        attachIfNecessary();
312
313        synchronized (this) {
314            ConfigValue oldValue, value;
315            oldValue = value = this.value;
316
317            if (onlyIfNull && !(oldValue instanceof NullConfigValue)){
318                return;
319            }
320
321            // init new config value backing for the new value type if necessary
322            if (newValue instanceof Collection) {
323                if (!(value instanceof ListConfigValue)) {
324                    value = new ListConfigValue(this);
325                }
326            } else if (newValue instanceof Map) {
327                if (!(value instanceof MapConfigValue)) {
328                    value = new MapConfigValue(this);
329                }
330            } else if (!(value instanceof ScalarConfigValue)) {
331                value = new ScalarConfigValue(this);
332            }
333
334            // insert the data into the config value
335            value.setValue(newValue);
336
337            /*if (oldValue != null && oldValue != value) {
338                oldValue.clear();
339            }*/
340            this.value = value;
341        }
342    }
343
344    @NonNull
345    @Override
346    public ConfigurationNode mergeValuesFrom(@NonNull ConfigurationNode other) {
347        if (other.isMap()) {
348            ConfigValue oldValue, newValue;
349            synchronized (this) {
350                oldValue = newValue = value;
351
352                // ensure the current type is applicable.
353                if (!(oldValue instanceof MapConfigValue)) {
354                    if (oldValue instanceof NullConfigValue) {
355                        newValue = new MapConfigValue(this);
356                    } else {
357                        return this;
358                    }
359                }
360
361                // merge values from 'other'
362                for (Map.Entry<Object, ? extends ConfigurationNode> ent : other.getChildrenMap().entrySet()) {
363                    SimpleConfigurationNode currentChild = newValue.getChild(ent.getKey());
364                    // Never allow null values to overwrite non-null values
365                    if ((currentChild != null && currentChild.getValue() != null) && ent.getValue().getValue() == null) {
366                        continue;
367                    }
368
369                    // create a new child node for the value
370                    SimpleConfigurationNode newChild = this.createNode(ent.getKey());
371                    newChild.attached = true;
372                    newChild.setValue(ent.getValue());
373                    // replace the existing value, if absent
374                    SimpleConfigurationNode existing = newValue.putChildIfAbsent(ent.getKey(), newChild);
375                    // if an existing value was present, attempt to merge the new value into it
376                    if (existing != null) {
377                        existing.mergeValuesFrom(newChild);
378                    }
379                }
380                this.value = newValue;
381            }
382        } else if (other.getValue() != null) {
383            // otherwise, replace the value of this node, only if currently null
384            insertNewValue(other.getValue(), true);
385        }
386        return this;
387    }
388
389    @NonNull
390    @Override
391    public SimpleConfigurationNode getNode(@NonNull Object @NonNull... path) {
392        SimpleConfigurationNode pointer = this;
393        for (Object el : path) {
394            pointer = pointer.getChild(el, false);
395        }
396        return pointer;
397    }
398
399    @Override
400    public @NonNull SimpleConfigurationNode getNode(@NonNull Iterable<?> path) {
401        SimpleConfigurationNode pointer = this;
402        for (Object el : path) {
403            pointer = pointer.getChild(el, false);
404        }
405        return pointer;
406    }
407
408    @Override
409    public boolean isVirtual() {
410        return !attached;
411    }
412
413    @NonNull
414    @Override
415    public ValueType getValueType() {
416        return this.value.getType();
417    }
418
419    @NonNull
420    @Override
421    @SuppressWarnings("unchecked")
422    public List<? extends SimpleConfigurationNode> getChildrenList() {
423        ConfigValue value = this.value;
424        return value instanceof ListConfigValue ? ImmutableList.copyOf(((ListConfigValue) value).values.get()) : Collections.emptyList();
425    }
426
427    @NonNull
428    @Override
429    @SuppressWarnings("unchecked")
430    public Map<Object, ? extends SimpleConfigurationNode> getChildrenMap() {
431        ConfigValue value = this.value;
432        return value instanceof MapConfigValue ? ImmutableMap.copyOf(((MapConfigValue) value).values) : Collections.emptyMap();
433    }
434
435    @Override
436    public boolean isEmpty() {
437        return this.value.isEmpty();
438    }
439
440    /**
441     * Gets a child node, relative to this.
442     *
443     * @param key The key
444     * @param attach If the resultant node should be automatically attached
445     * @return The child node
446     */
447    protected SimpleConfigurationNode getChild(Object key, boolean attach) {
448        SimpleConfigurationNode child = value.getChild(key);
449
450        // child doesn't currently exist
451        if (child == null) {
452            if (attach) {
453                // attach ourselves first
454                attachIfNecessary();
455                // insert the child node into the value
456                SimpleConfigurationNode existingChild = value.putChildIfAbsent(key, (child = createNode(key)));
457                if (existingChild != null) {
458                    child = existingChild;
459                } else {
460                    attachChild(child);
461                }
462            } else {
463                // just create a new virtual (detached) node
464                child = createNode(key);
465            }
466        }
467
468        return child;
469    }
470
471    @Override
472    public boolean removeChild(@NonNull Object key) {
473        return detachIfNonNull(value.putChild(key, null)) != null;
474    }
475
476    private static SimpleConfigurationNode detachIfNonNull(SimpleConfigurationNode node) {
477        if (node != null) {
478            node.attached = false;
479            node.clear();
480        }
481        return node;
482    }
483
484    @NonNull
485    @Override
486    @Deprecated
487    public SimpleConfigurationNode getAppendedNode() {
488        // the appended node can have a key of -1
489        // the "real" key will be determined when the node is inserted into a list config value
490        return getChild(-1, false);
491    }
492
493    @Nullable
494    @Override
495    public Object getKey() {
496        return this.key;
497    }
498
499    @NonNull
500    @Override
501    public Object[] getPath() {
502        LinkedList<Object> pathElements = new LinkedList<>();
503        ConfigurationNode pointer = this;
504        if (pointer.getParent() == null) {
505            return new Object[]{}; // we're the root node, no key here
506        }
507
508        do {
509            pathElements.addFirst(pointer.getKey());
510        } while ((pointer = pointer.getParent()).getParent() != null);
511        return pathElements.toArray();
512    }
513
514    @Nullable
515    public SimpleConfigurationNode getParent() {
516        return this.parent;
517    }
518
519    @NonNull
520    @Override
521    public ConfigurationOptions getOptions() {
522        return this.options;
523    }
524
525    @NonNull
526    @Override
527    public SimpleConfigurationNode copy() {
528        return copy(null);
529    }
530
531    @NonNull
532    protected SimpleConfigurationNode copy(@Nullable SimpleConfigurationNode parent) {
533        return new SimpleConfigurationNode(parent, this);
534    }
535
536    /**
537     * The same as {@link #getParent()} - but ensuring that 'parent' is attached via
538     * {@link #attachChildIfAbsent(SimpleConfigurationNode)}.
539     *
540     * @return The parent
541     */
542    SimpleConfigurationNode getParentEnsureAttached() {
543        SimpleConfigurationNode parent = this.parent;
544        if (parent.isVirtual()) {
545            parent = parent.getParentEnsureAttached().attachChildIfAbsent(parent);
546
547        }
548        return this.parent = parent;
549    }
550
551    protected void attachIfNecessary() {
552        if (!attached) {
553            getParentEnsureAttached().attachChild(this);
554        }
555    }
556
557    protected SimpleConfigurationNode createNode(Object path) {
558        return new SimpleConfigurationNode(path, this, options);
559    }
560
561    protected SimpleConfigurationNode attachChildIfAbsent(SimpleConfigurationNode child) {
562        return attachChild(child, true);
563    }
564
565    private void attachChild(SimpleConfigurationNode child) {
566        attachChild(child, false);
567    }
568
569    /**
570     * Attaches a child to this node
571     *
572     * @param child The child
573     * @return The resultant value
574     */
575    private SimpleConfigurationNode attachChild(SimpleConfigurationNode child, boolean onlyIfAbsent) {
576        // ensure this node is attached
577        if (isVirtual()) {
578            throw new IllegalStateException("This parent is not currently attached. This is an internal state violation.");
579        }
580
581        // ensure the child actually is a child
582        if (!child.getParentEnsureAttached().equals(this)) {
583            throw new IllegalStateException("Child " +  child + " path is not a direct parent of me (" + this + "), cannot attach");
584        }
585
586        // update the value
587        ConfigValue oldValue, newValue;
588        synchronized (this) {
589            newValue = oldValue = this.value;
590
591            // if the existing value isn't a map, we need to update it's type
592            if (!(oldValue instanceof MapConfigValue)) {
593                if (child.key instanceof Integer) {
594                    // if child.key is an integer, we can infer that the type of this node should be a list
595                    if (oldValue instanceof NullConfigValue) {
596                        // if the oldValue was null, we can just replace it with an empty list
597                        newValue = new ListConfigValue(this);
598                    } else if (!(oldValue instanceof ListConfigValue)) {
599                        // if the oldValue contained a value, we add it as the first element of the
600                        // new list
601                        newValue = new ListConfigValue(this, oldValue.getValue());
602                    }
603                } else {
604                    // if child.key isn't an integer, assume map
605                    newValue = new MapConfigValue(this);
606                }
607            }
608
609            /// now the value has been updated to an appropriate type, we can insert the value
610            if (onlyIfAbsent) {
611                SimpleConfigurationNode oldChild = newValue.putChildIfAbsent(child.key, child);
612                if (oldChild != null) {
613                    return oldChild;
614                }
615            } else {
616                detachIfNonNull(newValue.putChild(child.key, child));
617            }
618            this.value = newValue;
619        }
620
621        if (newValue != oldValue) {
622            oldValue.clear();
623        }
624        child.attached = true;
625        return child;
626    }
627
628    protected void clear() {
629        synchronized (this) {
630            ConfigValue oldValue = this.value;
631            value = NullConfigValue.instance();
632            oldValue.clear();
633        }
634    }
635
636    @Override
637    public <S, T, E extends Exception> T visit(ConfigurationVisitor<S, T, E> visitor, S state) throws E {
638        return visitInternal(visitor, state);
639    }
640
641    @Override
642    public <S, T> T visit(ConfigurationVisitor.Safe<S, T> visitor, S state) {
643        try {
644            return visitInternal(visitor, state);
645        } catch (VisitorSafeNoopException e) {
646            throw new Error("Exception was thrown on a Safe visitor");
647        }
648    }
649
650    private <S, T, E extends Exception> T visitInternal(ConfigurationVisitor<S, T, E> visitor, S state) throws E {
651        visitor.beginVisit(this, state);
652        if (!(this.value instanceof NullConfigValue)) { // only visit if we have an actual value
653            LinkedList<Object> toVisit = new LinkedList<>();
654            toVisit.add(this);
655
656            @Nullable Object active;
657            while ((active = toVisit.pollFirst()) != null) {
658                // try to pop a node from the stack, or handle the node exit if applicable
659                @Nullable SimpleConfigurationNode current = VisitorNodeEnd.popFromVisitor(active, visitor, state);
660                if (current == null) {
661                    continue;
662                }
663
664                visitor.enterNode(current, state);
665                ConfigValue value = current.value;
666                if (value instanceof MapConfigValue) {
667                    visitor.enterMappingNode(current, state);
668                    toVisit.addFirst(new VisitorNodeEnd(current, true));
669                    toVisit.addAll(0, ((MapConfigValue) value).values.values());
670                } else if (value instanceof ListConfigValue) {
671                    visitor.enterListNode(current, state);
672                    toVisit.addFirst(new VisitorNodeEnd(current, false));
673                    toVisit.addAll(0, ((ListConfigValue) value).values.get());
674                } else if (value instanceof ScalarConfigValue) {
675                    visitor.enterScalarNode(current, state);
676                } else if (value instanceof NullConfigValue) {
677                    // no-op. this shouldn't happen, but in most cases it's harmless
678                } else {
679                    throw new IllegalStateException("Unknown value type " + value.getClass());
680                }
681            }
682        }
683        return visitor.endVisit(state);
684    }
685
686    @Override
687    public boolean equals(Object o) {
688        if (this == o) return true;
689        if (!(o instanceof SimpleConfigurationNode)) return false;
690        SimpleConfigurationNode that = (SimpleConfigurationNode) o;
691
692        return Objects.equals(this.key, that.key) && Objects.equals(this.value, that.value);
693    }
694
695    @Override
696    public int hashCode() {
697        return Objects.hashCode(key) ^ Objects.hashCode(value);
698    }
699
700    @Override
701    public String toString() {
702        return "AbstractConfigurationNode{key=" + key + ", value=" + value + '}';
703    }
704}