All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ninja.leaping.configurate.SimpleConfigurationNode Maven / Gradle / Ivy

There is a newer version: 3.7.1
Show newest version
/**
 * Configurate
 * Copyright (C) zml and Configurate contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ninja.leaping.configurate;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;
import ninja.leaping.configurate.objectmapping.ObjectMappingException;
import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;


/**
 * Simple implementation of a configuration node
 */
public class SimpleConfigurationNode implements ConfigurationNode {
    private final ConfigurationOptions options;
    volatile boolean attached;
    /**
     * Path of this node.
     *
     * Internally, may only be modified when an operation that adds or removes a node at the same or higher level in the node tree
     */
    volatile Object key;
    private SimpleConfigurationNode parent;
    private volatile ConfigValue value;

    public static SimpleConfigurationNode root() {
        return root(ConfigurationOptions.defaults());
    }

    public static SimpleConfigurationNode root(ConfigurationOptions options) {
        return new SimpleConfigurationNode(null, null, options);
    }

    protected SimpleConfigurationNode(Object key, SimpleConfigurationNode parent, ConfigurationOptions options) {
        Preconditions.checkNotNull(options, "options");

        this.key = key;
        if (parent == null) {
            attached = true;
        }
        this.options = options;
        this.parent = parent == null ? null : parent;

        value = new NullConfigValue(this);

    }

    @Override
    public Object getValue(Object def) {
        Object ret = value.getValue();
        return ret == null ? calculateDef(def) : ret;
    }

    @Override
    public Object getValue(Supplier defSupplier) {
        Object ret = value.getValue();
        return ret == null ? calculateDef(defSupplier.get()) : ret;
    }

    // {{{ Typed values

    @Override
    public  T getValue(Function transformer, T def) {
        T ret = transformer.apply(getValue());
        return ret == null ? calculateDef(def) : ret;
    }

    @Override
    public  T getValue(Function transformer, Supplier defSupplier) {
        T ret = transformer.apply(getValue());
        return ret == null ? calculateDef(defSupplier.get()) : ret;
    }

    private  T calculateDef(T defValue) {
        if (defValue != null && getOptions().shouldCopyDefaults()) {
            setValue(defValue);
        }
        return defValue;
    }

    @Override
    public  List getList(Function transformer) {
        final ImmutableList.Builder build = ImmutableList.builder();
        ConfigValue value = this.value;
        if (value instanceof ListConfigValue) {
            for (SimpleConfigurationNode o : value.iterateChildren()) {
                T transformed = transformer.apply(o.getValue());
                if (transformed != null) {
                    build.add(transformed);
                }
            }
        } else {
            T transformed = transformer.apply(value.getValue());
            if (transformed != null) {
                build.add(transformed);
            }
        }

        return build.build();
    }

    @Override
    public  List getList(Function transformer, List def) {
        List ret = getList(transformer);
        return ret.isEmpty() ? calculateDef(def) : ret;
    }

    @Override
    public  List getList(Function transformer, Supplier> defSupplier) {
        List ret = getList(transformer);
        return ret.isEmpty() ? calculateDef(defSupplier.get()) : ret;
    }

    @Override
    public  List getList(TypeToken type, List def) throws ObjectMappingException {
        List ret = getValue(new TypeToken>() {}
                .where(new TypeParameter() {}, type), def);
        return ret.isEmpty() ? calculateDef(def) : ret;
    }

    @Override
    public  List getList(TypeToken type, Supplier> defSupplier) throws ObjectMappingException {
        List ret = getValue(new TypeToken>() {}
                .where(new TypeParameter() {}, type), defSupplier);
        return ret.isEmpty() ? calculateDef(defSupplier.get()) : ret;
    }

    // }}}

    @Override
    public SimpleConfigurationNode setValue(Object newValue) {
        if (newValue instanceof ConfigurationNode) {
            ConfigurationNode newNode = (ConfigurationNode) newValue;
            if (newNode.hasListChildren()) {
                attachIfNecessary();
                ListConfigValue newList = new ListConfigValue(this);
                synchronized (newNode) {
                    List children = newNode.getChildrenList();
                    for (int i = 0; i < children.size(); ++i) {
                        SimpleConfigurationNode child = createNode(i);
                        child.attached = true;
                        newList.putChild(i, child);
                        child.setValue(children.get(i));
                    }
                }
                this.value = newList;
                return this;
            } else if (newNode.hasMapChildren()) {
                attachIfNecessary();
                MapConfigValue newMap = new MapConfigValue(this);
                synchronized (newNode) {
                    Map children = newNode.getChildrenMap();
                    for (Map.Entry ent : children.entrySet()) {
                        SimpleConfigurationNode child = createNode(ent.getKey());
                        child.attached = true;
                        newMap.putChild(ent.getKey(), child);
                        child.setValue(ent.getValue());
                    }
                }
                this.value = newMap;
                return this;
            } else {
                newValue = newNode.getValue();
            }
        }

        if (newValue == null) {
            if (parent == null) {
                clear();
            } else {
                parent.removeChild(key);
            }
            return this;
        }

        insertNewValue(newValue, false);
        return this;
    }

    @Override
    @SuppressWarnings("unchecked")
    public  T getValue(TypeToken type, T def) throws ObjectMappingException {
        Object value = getValue();
        if (value == null) {
            if (def != null && getOptions().shouldCopyDefaults()) {
                setValue(type, def);
            }
            return def;
        }
        TypeSerializer serial = getOptions().getSerializers().get(type);
        if (serial == null) {
            if (type.getRawType().isInstance(value)) {
                return (T) type.getRawType().cast(value);
            } else {
                if (def != null && getOptions().shouldCopyDefaults()) {
                    setValue(type, def);
                }
                return def;
            }
        }
        return (T) serial.deserialize(type, this);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  T getValue(TypeToken type, Supplier defSupplier) throws ObjectMappingException {
        Object value = getValue();
        if (value == null) {
            T def = defSupplier.get();
            if (def != null && getOptions().shouldCopyDefaults()) {
                setValue(type, def);
            }
            return def;
        }
        TypeSerializer serial = getOptions().getSerializers().get(type);
        if (serial == null) {
            if (type.getRawType().isInstance(value)) {
                return (T) type.getRawType().cast(value);
            } else {
                T def = defSupplier.get();
                if (def != null && getOptions().shouldCopyDefaults()) {
                    setValue(type, def);
                }
                return def;
            }
        }
        return (T) serial.deserialize(type, this);
    }

    private void insertNewValue(Object newValue, boolean onlyIfNull) {
        attachIfNecessary();
        synchronized (this) {
            ConfigValue oldValue, value;
            oldValue = value = this.value;
            if (onlyIfNull && !(oldValue instanceof NullConfigValue)){
                return;
            }
            if (newValue instanceof Collection) {
                if (!(value instanceof ListConfigValue)) {
                    value = new ListConfigValue(this);
                }
            } else if (newValue instanceof Map) {
                if (!(value instanceof MapConfigValue)) {
                    value = new MapConfigValue(this);
                }
            } else if (!(value instanceof ScalarConfigValue)) {
                value = new ScalarConfigValue(this);
            }
            value.setValue(newValue);

            /*if (oldValue != null && oldValue != value) {
                oldValue.clear();
            }*/
            this.value = value;
        }
    }

    @Override
    public ConfigurationNode mergeValuesFrom(ConfigurationNode other) {
        /*if (other.hasListChildren()) {
            ConfigValue oldValue, newValue;
            do {
                oldValue = newValue = value.get();
                if (!(oldValue instanceof ListConfigValue)) {
                    if (oldValue instanceof NullConfigValue) {
                        oldValue = new ListConfigValue(this);
                    } else {
                        break;
                    }
                }
               // TODO: How to merge list values?

            } while (!this.value.compareAndSet(oldValue, newValue));

        } else */if (other.hasMapChildren()) {
            ConfigValue oldValue, newValue;
            synchronized (this) {
                oldValue = newValue = value;
                if (!(oldValue instanceof MapConfigValue)) {
                    if (oldValue instanceof NullConfigValue) {
                        newValue = new MapConfigValue(this);
                    } else {
                        return this;
                    }
                }
                for (Map.Entry ent : other.getChildrenMap().entrySet()) {
                    SimpleConfigurationNode newChild = createNode(ent.getKey());
                    newChild.attached = true;
                    newChild.setValue(ent.getValue());
                    SimpleConfigurationNode existing = newValue.putChildIfAbsent(ent.getKey(), newChild);
                    if (existing != null) {
                        existing.mergeValuesFrom(newChild);
                    }
                }
                this.value = newValue;
            }
        } else if (other.getValue() != null) {
            insertNewValue(other.getValue(), true);
        }
        return this;
    }

    // {{{ Children
    @Override
    public SimpleConfigurationNode getNode(Object... path) {
        SimpleConfigurationNode ret = this;
        for (Object el : path) {
            ret = ret.getChild(el, false);
        }
        return ret;
    }

    @Override
    public boolean isVirtual() {
        return !attached;
    }

    @Override
    public boolean hasListChildren() {
        return this.value instanceof ListConfigValue;
    }

    @Override
    public boolean hasMapChildren() {
        return this.value instanceof MapConfigValue;
    }

    @Override
    @SuppressWarnings("unchecked")
    public List getChildrenList() {
        ConfigValue value = this.value;
        return value instanceof ListConfigValue ? ImmutableList.copyOf(((ListConfigValue) value).values.get()) : Collections
                .emptyList();
    }

    @Override
    @SuppressWarnings("unchecked")
    public Map getChildrenMap() {
        ConfigValue value = this.value;
        return value instanceof MapConfigValue ? ImmutableMap.copyOf(((MapConfigValue) value).values) : Collections
                .emptyMap();
    }

    protected SimpleConfigurationNode getChild(Object key, boolean attach) {
        SimpleConfigurationNode child = value.getChild(key);

        if (child == null) { // Does not currently exist!
            if (attach) {
                attachIfNecessary();
                SimpleConfigurationNode existingChild = value.putChildIfAbsent(key, (child = createNode(key)));
                if (existingChild != null) {
                    child = existingChild;
                } else {
                    attachChild(child);
                }
            } else {
                child = createNode(key);
            }
        }

        return child;
    }

    @Override
    public boolean removeChild(Object key) {
        return possiblyDetach(value.putChild(key, null)) != null;
    }

    private SimpleConfigurationNode possiblyDetach(SimpleConfigurationNode node) {
        if (node != null) {
            node.attached = false;
            node.clear();
        }
        return node;
    }

    @Override
    public SimpleConfigurationNode getAppendedNode() {
        return getChild(-1, false);
    }

    @Override
    public Object getKey() {
        return this.key;
    }

    @Override
    public Object[] getPath() {
        LinkedList pathElements = new LinkedList<>();
        ConfigurationNode ptr = this;
        if (ptr.getParent() == null) {
            return new Object[] {this.getKey()};
        }

        do {
            pathElements.addFirst(ptr.getKey());
        } while ((ptr = ptr.getParent()).getParent() != null);
        return pathElements.toArray();
    }

    public SimpleConfigurationNode getParent() {
        return this.parent;
    }

    @Override
    public ConfigurationOptions getOptions() {
        return this.options;
    }
    // }}}

    // {{{ Internal methods
    SimpleConfigurationNode getParentAttached() {
        SimpleConfigurationNode parent = this.parent;
        if (parent.isVirtual()) {
            parent = parent.getParentAttached().attachChildIfAbsent(parent);

        }
        return this.parent = parent;
    }

    private SimpleConfigurationNode attachChildIfAbsent(SimpleConfigurationNode child) {
        if (isVirtual()) {
            throw new IllegalStateException("This parent is not currently attached. This is an internal state violation.");
        }
        if (!child.getParentAttached().equals(this)) {
            throw new IllegalStateException("Child " +  child +
                    " path is not a direct parent of me (" + this + "), cannot attach");
        }
        ConfigValue oldValue, newValue;
        synchronized (this) {
            newValue = oldValue = this.value;
            if (!(oldValue instanceof MapConfigValue)) {
                if (child.key instanceof Integer) {
                    if (oldValue instanceof NullConfigValue) {
                        newValue = new ListConfigValue(this);

                    } else if (!(oldValue instanceof ListConfigValue)) {
                        newValue = new ListConfigValue(this, oldValue.getValue());
                    }
                } else {
                    newValue = new MapConfigValue(this);
                }
            }
            SimpleConfigurationNode oldChild = newValue.putChildIfAbsent(child.key, child);
            if (oldChild != null) {
                return oldChild;
            }
            value = newValue;
        }
        if (newValue != oldValue) {
            oldValue.clear();
        }
        child.attached = true;
        return child;
    }

    protected SimpleConfigurationNode createNode(Object path) {
        return new SimpleConfigurationNode(path, this, options);
    }

    protected void attachIfNecessary() {
        if (!attached) {
            getParentAttached().attachChild(this);
        }
    }

    protected void attachChild(SimpleConfigurationNode child) {
        if (isVirtual()) {
            throw new IllegalStateException("This parent is not currently attached. This is an internal state violation.");
        }
        if (!child.getParentAttached().equals(this)) {
            throw new IllegalStateException("Child " +  child +
                    " path is not a direct parent of me (" + this + "), cannot attach");
        }
        ConfigValue oldValue, newValue;
        synchronized (this) {
            newValue = oldValue = this.value;
                if (!(oldValue instanceof MapConfigValue)) {
                    if (child.key instanceof Integer) {
                        if (oldValue instanceof NullConfigValue) {
                            newValue = new ListConfigValue(this);

                        } else if (!(oldValue instanceof ListConfigValue)) {
                            newValue = new ListConfigValue(this, oldValue.getValue());
                        }
                    } else {
                        newValue = new MapConfigValue(this);
                    }
                }
                possiblyDetach(newValue.putChild(child.key, child));
            value = newValue;
        }
        if (newValue != oldValue) {
            oldValue.clear();
        }
        child.attached = true;
    }

    protected void clear() {
        synchronized (this) {
            ConfigValue oldValue = this.value;
            value = new NullConfigValue(this);
            oldValue.clear();
        }
    }
    // }}}


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof SimpleConfigurationNode)) return false;

        SimpleConfigurationNode that = (SimpleConfigurationNode) o;

        if (attached != that.attached) return false;
        if (key != null ? !key.equals(that.key) : that.key != null) return false;
        if (!options.equals(that.options)) return false;
        if (parent != null ? !parent.equals(that.parent) : that.parent != null)
            return false;
        if (!value.equals(that.value)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = options.hashCode();
        result = 31 * result + (attached ? 1 : 0);
        result = 31 * result + (key != null ? key.hashCode() : 0);
        result = 31 * result + (parent != null ? parent.hashCode() : 0);
        result = 31 * result + value.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return "SimpleConfigurationNode{" +
                "options=" + options +
                ", attached=" + attached +
                ", key=" + key +
                ", parent=" + parent +
                ", value=" + value +
                '}';
    }
}