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

org.dsa.iot.dslink.node.Node Maven / Gradle / Ivy

There is a newer version: 0.24.2
Show newest version
package org.dsa.iot.dslink.node;

import org.dsa.iot.dslink.link.Linkable;
import org.dsa.iot.dslink.node.NodeListener.ValueUpdate;
import org.dsa.iot.dslink.node.actions.Action;
import org.dsa.iot.dslink.node.value.Value;
import org.dsa.iot.dslink.node.value.ValuePair;
import org.dsa.iot.dslink.node.value.ValueType;
import org.dsa.iot.dslink.serializer.SerializationManager;
import org.dsa.iot.dslink.util.StringUtils;

import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Contains information about a node and its data.
 *
 * @author Samuel Grenier
 */
public class Node {

    private static final char[] BANNED_CHARS = new char[] {
        '%', '.', '/', '\\', '?', '*', ':', '|', '<', '>', '$', '@', ','
    };

    private final Object roConfigLock = new Object();
    private final Object configLock = new Object();

    private final Object attributeLock = new Object();
    private final Object interfaceLock = new Object();
    private final Object childrenLock = new Object();
    private final Object passwordLock = new Object();
    private final Object valueLock = new Object();

    private final WeakReference parent;
    private final Linkable link;
    private final String path;
    private final String name;

    private boolean serializable = true;
    private Map children;
    private NodeListener listener;
    private Writable writable;
    private Object metaData;

    private Map roConfigs;
    private Map configs;
    private Map attribs;
    private Boolean hasChildren;
    private boolean hidden;

    private ValueType valueType;
    private Value value;

    private String displayName;
    private String profile;
    private Set interfaces;
    private Action action;
    private char[] pass;

    /**
     * Constructs a node object.
     *
     * @param name   Name of the node
     * @param parent Parent of this node
     * @param link Linkable class the node is handled on
     */
    public Node(String name, Node parent, Linkable link) {
        this(name, parent, link, true);
    }

    public Node(String name, Node parent, Linkable link, boolean shouldEncodeName) {
        this.parent = new WeakReference<>(parent);
        this.listener = new NodeListener(this);
        this.link = link;
        if (shouldEncodeName) {
            name = StringUtils.encodeName(name);
        }
        if (name == null) {
            throw new IllegalArgumentException("name");
        }
        if (parent != null) {
            if (name.isEmpty()) {
                throw new IllegalArgumentException("name");
            }
            this.name = name;
            if (parent instanceof NodeManager.SuperRoot) {
                this.path = "/" + name;
            } else {
                this.path = parent.getPath() + "/" + name;
            }
        } else {
            this.path = "/" + name;
            this.name = name;
        }
    }

    /**
     * @return Parent of this node, can be null if the parent was garbage
     *         collected or there is no parent.
     */
    public Node getParent() {
        return parent.get();
    }

    /**
     * @return The link this node is attached to.
     */
    public Linkable getLink() {
        return link;
    }

    /**
     * @return Encoded name of the node.
     * @see StringUtils#decodeName(String)
     */
    public String getName() {
        return name;
    }

    /**
     * @return Formalized path of this node.
     */
    public String getPath() {
        return path;
    }

    /**
     * @param name Display name of the node to set
     */
    public void setDisplayName(String name) {
        displayName = name;
        markChanged();
        if (link != null) {
            SubscriptionManager man = link.getSubscriptionManager();
            if (name != null) {
                man.postMetaUpdate(this, "$name", new Value(displayName));
            } else {
                man.postMetaUpdate(this, "$name", null);
            }
        }
    }

    /**
     * @return Display name of the node
     */
    public String getDisplayName() {
        return displayName;
    }

    /**
     * Sets the profile of the node
     *
     * @param profile Profile to set
     */
    public void setProfile(String profile) {
        this.profile = profile;
        markChanged();
    }

    /**
     * @return The profile this node belongs to
     */
    public String getProfile() {
        return profile;
    }

    /**
     * The listener API provides functionality for listening to changes
     * that occur within a node.
     *
     * @return The node's listener.
     */
    public NodeListener getListener() {
        return listener;
    }

    /**
     * Used to set the listener to allow the node builder to override
     * the internal listener.
     *
     * @param listener Listener to set.
     */
    protected void setListener(NodeListener listener) {
        if (listener == null) {
            throw new NullPointerException("listener");
        }
        this.listener = listener;
    }

    public void addInterface(String _interface) {
        synchronized (interfaceLock) {
            if (_interface == null) {
                throw new NullPointerException("_interface");
            } else if (interfaces == null) {
                interfaces = new HashSet<>();
            }
            interfaces.add(_interface);
            markChanged();
        }
    }

    @SuppressWarnings("unused")
    public void removeInterface(String _interface) {
        synchronized (interfaceLock) {
            if (_interface == null) {
                throw new NullPointerException("_interface");
            } else if (interfaces != null) {
                interfaces.remove(_interface);
                markChanged();
            }
        }
    }

    public void setInterfaces(String interfaces) {
        synchronized (interfaceLock) {
            if (interfaces == null) {
                this.interfaces = null;
                return;
            } else if (this.interfaces == null) {
                this.interfaces = new HashSet<>();
            }
            String[] split = interfaces.split("\\|");
            Collections.addAll(this.interfaces, split);
            markChanged();
        }
    }

    public Set getInterfaces() {
        Set i = this.interfaces;
        return i != null ? Collections.unmodifiableSet(i) : null;
    }

    public void setValue(Value value) {
        setValue(value, false);
    }

    public void setValue(Value value, boolean externalSource) {
        setValue(value, externalSource, true);
    }

    /**
     * @param value Value to set.
     * @param externalSource Whether the value was set from an external source
     *                       like an action that got invoked.
     * @param publish Whether to allow a publish to the network.
     * @return Whether a value was actually set.
     */
    protected boolean setValue(Value value,
                            boolean externalSource,
                            boolean publish) {
        ValueType type = valueType;
        if (type == null && value != null) {
            String err = "Value type not set on node (" + getPath() + ")";
            throw new RuntimeException(err);
        }

        ValuePair pair;
        synchronized (valueLock) {
            pair = new ValuePair(this.value, value, externalSource);
        }
        if (listener.postValueUpdate(pair)) {
            return false;
        }
        value = pair.getCurrent();
        if (value != null) {
            if (type == null) {
                String err = "Value type not set on node (" + getPath() + ")";
                throw new RuntimeException(err);
            }
            value.setImmutable();
            if (type.compare(ValueType.ENUM)) {
                if (!value.getType().compare(ValueType.STRING)) {
                    String err = "[" + getPath() + "] ";
                    err += "Node has enum value type, value must be string";
                    throw new RuntimeException(err);
                } else if (type.getEnums() == null
                        || !type.getEnums().contains(value.getString())) {
                    if (value.getString() != null) {
                        String err = "[" + getPath() + "] ";
                        err += "New value does not contain a valid enum value";
                        throw new RuntimeException(err);
                    }
                }
            } else if (type.compare(ValueType.TIME)) {
                if (!value.getType().compare(ValueType.STRING)) {
                    String err = "[" + getPath() + "] ";
                    err += "Node has time value type, value must be string";
                    throw new RuntimeException(err);
                }
            } else if (!(type.compare(ValueType.DYNAMIC)
                    || type.compare(value.getType()))) {
                String err = "[" + getPath() + "] ";
                err += "Expected value type ";
                err += "'" + type.toJsonString() + "' ";
                err += "got '" + value.getType().toJsonString() + "'";
                throw new RuntimeException(err);
            }
        }
        synchronized (valueLock) {
            Value prev = this.value;
            this.value = value;
            if ((prev != null && prev.isSerializable())
                    || (value != null && value.isSerializable())
                    || (prev == null && value == null)) {
                markChanged();
            }
            if (publish && link != null) {
                SubscriptionManager manager = link.getSubscriptionManager();
                if (manager != null) {
                    manager.postValueUpdate(this);
                }
            }
        }
        return true;
    }

    /**
     * @return The value of the node.
     */
    public Value getValue() {
        return value;
    }

    public void setValueType(ValueType type) {
        this.valueType = type;
        markChanged();
        if (link != null) {
            SubscriptionManager man = link.getSubscriptionManager();
            if (type != null) {
                String t = type.toJsonString();
                man.postMetaUpdate(this, "$type", new Value(t));
            } else {
                man.postMetaUpdate(this, "$type", null);
            }
        }
    }

    public ValueType getValueType() {
        return valueType;
    }

    /**
     * @param writable Permission level required to write.
     */
    public void setWritable(Writable writable) {
        this.writable = writable;
        markChanged();
    }

    /**
     * @return The permission level needed to be writable.
     */
    public Writable getWritable() {
        return writable;
    }

    /**
     * @return Children of the node, can be null
     */
    public Map getChildren() {
        Map children = this.children;
        return children != null ? Collections.unmodifiableMap(children) : null;
    }

    /**
     * Clears the children in the node.
     */
    @SuppressWarnings("unused")
    public void clearChildren() {
        synchronized (childrenLock) {
            if (children != null) {
                Map children = getChildren();
                for (Node child : children.values()) {
                    removeChild(child);
                }
            }
            markChanged();
        }
    }

    /**
     * @param name Child name
     * @return Child, or null if non-existent
     */
    @Deprecated
    public Node getChild(String name) {
        Map children = this.children;
        if (children != null) {
            name = StringUtils.encodeName(name);
            return children.get(name);
        }
        return null;
    }

    public Node getChild(String name, boolean encodeName) {
        Map children = this.children;
        if (children != null) {
            if (encodeName) {
                name = StringUtils.encodeName(name);
            }
            return children.get(name);
        }
        return null;
    }

    /**
     * Creates a child. The profile in the child node will be
     * inherited from the parent.
     *
     * @param name Name of the child
     * @return builder
     */
    @Deprecated
    public NodeBuilder createChild(String name) {
        return createChild(name, profile);
    }

    public NodeBuilder createChild(String name, boolean encodeName) {
        return createChild(name, profile, encodeName);
    }
    /**
     * Creates a node builder to allow setting up the node data before
     * any list subscriptions can be notified.
     *
     * @param name Name of the child.
     * @param profile Profile to set on the child
     * @return builder
     * @see NodeBuilder#build
     */
    public NodeBuilder createChild(String name, String profile) {
        NodeBuilder b = new NodeBuilder(this, new Node(name, this, link));
        if (profile != null) {
            b.setProfile(profile);
        }
        return b;
    }

    public NodeBuilder createChild(String name, String profile, boolean encodeName) {
        NodeBuilder b = new NodeBuilder(this, new Node(name, this, link, encodeName));

        if (profile != null) {
            b.setProfile(profile);
        }

        return b;
    }

    /**
     * The child will be added if the node doesn't exist. If the child
     * already exists then it will be returned and no new node will be
     * created. This can be used as a special getter.
     *
     * @param node Child node to add.
     * @return The node
     */
    public Node addChild(Node node) {
        synchronized (childrenLock) {
            String name = node.getName();
            maybeInitializeChildren();
            if (children.containsKey(name)) {
                return children.get(name);
            }

            SubscriptionManager manager = null;
            if (link != null) {
                manager = link.getSubscriptionManager();
            }

            if (node.getProfile() == null) {
                node.setProfile(profile);
            }
            children.put(name, node);
            if (manager != null) {
                manager.postChildUpdate(node, false);
            }
            if (node.isSerializable()) {
                markChanged();
            }
            return node;
        }
    }

    /**
     * Add multiple children at once.
     * @param nodes Nodes to add.
     */
    public void addChildren(List nodes) {
        SubscriptionManager manager = null;
        if (link != null) {
            manager = link.getSubscriptionManager();
        }
        boolean reserialize = false;

        synchronized (childrenLock) {
            for (Node node : nodes) {
                String name = node.getName();
                maybeInitializeChildren();
                if (children.containsKey(name)) {
                    continue;
                }

                node.maybeInitializeProfile(profile);
                children.put(name, node);

                if (node.isSerializable()) {
                    reserialize = true;
                }
            }
        }

        if (manager != null) {
            manager.postMultiChildUpdate(this, nodes);
        }

        if (reserialize) {
            markChanged();
        }
    }

    private void maybeInitializeProfile(String profile) {
        if (getProfile() == null) {
            setProfile(profile);
        }
    }

    private void maybeInitializeChildren() {
        if (children == null) {
            children = new ConcurrentHashMap<>();
        }
    }

    /**
     * Deletes this node from its parent.
     */
    @Deprecated
    public void delete() {
        Node parent = getParent();
        if (parent != null) {
            parent.removeChild(this);
        }
    }

    public void delete(boolean encodeName) {
        Node parent = getParent();
        if (parent != null) {
            parent.removeChild(this, encodeName);
        }
    }

    /**
     * @param node Node to remove.
     * @return The node if it existed.
     */
    @Deprecated
    public Node removeChild(Node node) {
        if (node != null) {
            return removeChild(node.getName());
        } else {
            return null;
        }
    }

    public Node removeChild(Node node, boolean encodeName) {
        if (node != null) {
            return removeChild(node.getName(), encodeName);
        } else {
            return null;
        }
    }

    /**
     * @param name Node to remove.
     * @return The node if it existed.
     */
    @Deprecated
    public Node removeChild(String name) {
        return removeChild(name, true);
    }

    public Node removeChild(String name, boolean encodeName) {
        synchronized (childrenLock) {
            if (encodeName) {
                name = StringUtils.encodeName(name);
            }
            Node child = children != null ? children.remove(name) : null;
            SubscriptionManager manager = null;
            if (link != null) {
                manager = link.getSubscriptionManager();
            }

            if (child != null) {
                child.getListener().postNodeRemoved();
                child.getListener().kill();

                if (manager != null) {
                    manager.postChildUpdate(child, true);
                    manager.removeValueSub(child);
                    manager.removePathSub(child);
                }
                if (isSerializable()) {
                    markChanged();
                }
            }
            return child;
        }
    }

    /**
     * @param name Name of the child.
     * @return Whether this node has the child or not.
     */
    @Deprecated
    public boolean hasChild(String name) {
        Map children = this.children;
        if (children != null) {
            name = StringUtils.encodeName(name);
            return children.containsKey(name);
        }
        return false;
    }

    public boolean hasChild(String name, boolean encodeName) {
        Map children = this.children;
        if (children != null) {
            if (encodeName) {
                name = StringUtils.encodeName(name);
            }
            return children.containsKey(name);
        }
        return false;
    }

    /**
     * @return The configurations in this node.
     */
    public Map getConfigurations() {
        Map c = this.configs;
        return c != null ? Collections.unmodifiableMap(c) : null;
    }

    /**
     * @param name Configuration name to get
     * @return Value of the configuration, if it exists
     */
    public Value getConfig(String name) {
        Map c = configs;
        if (c != null) {
            name = StringUtils.encodeName(name);
            return c.get(name);
        }
        return null;
    }

    /**
     * @param name Configuration name to remove
     * @return Configuration value, or null if it didn't exist
     */
    public Value removeConfig(String name) {
        name = StringUtils.encodeName(name);
        Value ret;
        synchronized (configLock) {
            ret = configs != null ? configs.remove(name) : null;
        }
        postRemoval("$", name, ret);
        return ret;
    }

    /**
     * Clears all the configurations from the node.
     *
     * @return All the previous configurations.
     */
    public Map clearConfigs() {
        Map configs;
        synchronized (configLock) {
            if (this.configs == null) {
                return null;
            }
            configs = new HashMap<>(this.configs);
            this.configs.clear();
        }
        for (Map.Entry entry : configs.entrySet()) {
            postRemoval("$", entry.getKey(), entry.getValue());
        }
        return configs;
    }

    /**
     * The name will be checked for validity. Certain names that are set
     * through other APIs cannot be set here, otherwise it will throw an
     * exception.
     *
     * @param name  Name of the configuration
     * @param value Value to set
     * @return The previous configuration value, if any
     * @see Action
     */
    public Value setConfig(String name, Value value) {
        synchronized (configLock) {
            name = checkAndEncodeName(name);
            if (value == null) {
                throw new NullPointerException("value");
            } else if (configs == null) {
                configs = new ConcurrentHashMap<>();
            }
            switch (name) {
                case "params":
                case "columns":
                case "name":
                case "is":
                case "invokable":
                case "interface":
                case "permission":
                case "result":
                case "type":
                case "writable":
                case "hidden":
                    String err = "Config `" + name + "` has special methods"
                            + " for setting these properties";
                    throw new IllegalArgumentException(err);
            }
            value.setImmutable();
            ValueUpdate update = new ValueUpdate(name, value, false);
            NodeListener listener = this.listener;
            if (listener != null) {
                listener.postConfigUpdate(update);
            }

            SubscriptionManager man = link.getSubscriptionManager();
            if (man != null) {
                man.postMetaUpdate(this, "$" + name, value);
            }

            markChanged();
            return configs.put(name, value);
        }
    }

    /**
     * @return The read-only configurations in this node.
     */
    public Map getRoConfigurations() {
        Map c = this.roConfigs;
        return c != null ? Collections.unmodifiableMap(c) : null;
    }

    /**
     * Removes a read-only configuration.
     *
     * @param name Name of the configuration.
     * @return Previous value of the configuration, if any.
     */
    public Value removeRoConfig(String name) {
        name = StringUtils.encodeName(name);
        Value ret;
        synchronized (roConfigLock) {
            ret = roConfigs != null ? roConfigs.remove(name) : null;
        }
        postRemoval("$$", name, ret);
        return ret;
    }

    /**
     * Clears all the read-only configurations from the node.
     *
     * @return All the previous read-only configurations.
     */
    public Map clearRoConfigs() {
        Map roConfigs;
        synchronized (roConfigLock) {
            if (this.roConfigs == null) {
                return null;
            }
            roConfigs = new HashMap<>(this.roConfigs);
            this.roConfigs.clear();
        }
        for (Map.Entry entry : roConfigs.entrySet()) {
            postRemoval("$$", entry.getKey(), entry.getValue());
        }
        return roConfigs;
    }

    /**
     * Retrieves a read-only configuration.
     *
     * @param name Name of the configuration.
     * @return The value of the configuration name, if any.
     */
    @SuppressWarnings("unused")
    public Value getRoConfig(String name) {
        Map c = roConfigs;
        if (c != null) {
            name = StringUtils.encodeName(name);
            return c.get(name);
        }
        return null;
    }

    /**
     * Sets a read-only configuration.
     *
     * @param name Name of the configuration.
     * @param value Value to set.
     * @return The previous value, if any.
     */
    public Value setRoConfig(String name, Value value) {
        synchronized (roConfigLock) {
            name = checkAndEncodeName(name);
            if (value == null) {
                throw new NullPointerException("value");
            } else if (roConfigs == null) {
                roConfigs = new ConcurrentHashMap<>();
            }

            switch (name) {
                case "password":
                    String err = "Config `" + name + "` has special methods"
                            + " for setting these properties";
                    throw new IllegalArgumentException(err);
            }

            SubscriptionManager man = link.getSubscriptionManager();
            if (man != null) {
                man.postMetaUpdate(this, "$$" + name, value);
            }

            markChanged();
            return roConfigs.put(name, value);
        }
    }

    /**
     * @return The attributes in this node.
     */
    public Map getAttributes() {
        Map a = attribs;
        return a != null ? Collections.unmodifiableMap(a) : null;
    }

    /**
     * @param name Attribute name to get
     * @return Value of the attribute, if it exists
     */
    public Value getAttribute(String name) {
        Map a = attribs;
        if (a != null) {
            name = StringUtils.encodeName(name);
            return a.get(name);
        }
        return null;
    }

    /**
     * @param name Attribute name to remove.
     * @return Attribute value or null if it didn't exist
     */
    public Value removeAttribute(String name) {
        name = StringUtils.encodeName(name);
        Value ret;
        synchronized (attributeLock) {
            ret = attribs != null ? attribs.remove(name) : null;
        }
        postRemoval("@", name, ret);
        return ret;
    }

    /**
     * Clears all the attributes in the node.
     *
     * @return All the previous attributes that were cleared.
     */
    public Map clearAttributes() {
        Map attribs;
        synchronized (attributeLock) {
            if (this.attribs == null) {
                return null;
            }
            attribs = new HashMap<>(this.attribs);
            this.attribs.clear();
        }
        for (Map.Entry entry : attribs.entrySet()) {
            postRemoval("@", entry.getKey(), entry.getValue());
        }
        return attribs;
    }

    /**
     * @param name  Name of the attribute
     * @param value Value to set
     * @return The previous attribute value, if any
     */
    public Value setAttribute(String name, Value value) {
        synchronized (attributeLock) {
            name = checkAndEncodeName(name);
            if (value == null) {
                throw new NullPointerException("value");
            } else if (attribs == null) {
                attribs = new ConcurrentHashMap<>();
            }
            value.setImmutable();
            ValueUpdate update = new ValueUpdate(name, value, false);
            listener.postAttributeUpdate(update);

            SubscriptionManager man = link.getSubscriptionManager();
            if (man != null) {
                man.postMetaUpdate(this, "@" + name, value);
            }

            markChanged();
            return attribs.put(name, value);
        }
    }

    /**
     * @return Action this node can invoke
     */
    public Action getAction() {
        return action;
    }

    /**
     * Sets the action of the node.
     *
     * @param action Action to set. Use {@code null} to remove an action.
     */
    public void setAction(Action action) {
        this.action = action;
        markChanged();
        if (link == null) {
            return;
        }
        SubscriptionManager man = link.getSubscriptionManager();
        if (man != null) {
            if (!(action == null || action.isHidden())) {
                Value params = new Value(action.getParams());
                Value cols = new Value(action.getColumns());
                man.postMetaUpdate(this, "$params", params);
                man.postMetaUpdate(this, "$columns", cols);
                action.setSubscriptionManager(this, man);
            } else {
                man.postMetaUpdate(this, "$params", null);
                man.postMetaUpdate(this, "$columns", null);
            }
        }
    }

    /**
     * Gets the password the node is configured to use. This is necessary
     * for authentication to servers.
     *
     * @return Password the node is configured to use.
     */
    public char[] getPassword() {
        synchronized (passwordLock) {
            return pass != null ? pass.clone() : null;
        }
    }

    /**
     * If this node accesses servers and requires authentication, the password
     * must be set here. This will censor the password from being retrieved
     * through the responder.
     *
     * @param password Password to set.
     */
    public void setPassword(char[] password) {
        synchronized (passwordLock) {
            this.pass = password != null ? password.clone() : null;
            markChanged();
        }
    }

    /**
     * Forcibly sets whether the node has children or not. This does NOT
     * affect whether the node has children internally.
     *
     * @param hasChildren Whether the node hsa children or not.
     */
    public void setHasChildren(Boolean hasChildren) {
        this.hasChildren = hasChildren;
        markChanged();
    }

    /**
     * Checks whether the node is forced to have children or not. This does
     * NOT check if the node has children internally. To check whether
     * the node has children internally, use {@link #getChildren()}.
     *
     * @return Whether the node has children or not.
     */
    public Boolean getHasChildren() {
        return hasChildren;
    }

    /**
     * @param hidden Whether the node is marked as hidden.
     */
    public void setHidden(boolean hidden) {
        this.hidden = hidden;
        markChanged();
    }

    /**
     * When a node is marked as hidden the UI should not display
     * the node.
     *
     * @return Whether the node is marked as hidden.
     */
    public boolean isHidden() {
        return hidden;
    }

    /**
     * Creates a fake node builder that wraps its methods around
     * this node. This allows fitting a {@link Node} into a {@link NodeBuilder}
     * when necessary.
     *
     * @return A fake node builder.
     */
    public NodeBuilder createFakeBuilder() {
        return new NodeBuilder(getParent(), this) {
            @Override
            public Node build() {
                return Node.this;
            }
        };
    }

    /**
     * If this node is not serializable, none of the children will be either
     * by default.
     *
     * @return Whether this node should be serialized or not
     */
    public boolean isSerializable() {
        return serializable;
    }

    /**
     * Sets whether this node and its children should be serialized.
     *
     * @param serializable Whether this node can be serialized.
     */
    public void setSerializable(boolean serializable) {
        this.serializable = serializable;
        markChanged();
    }

    /**
     * Sets the meta data of the node. Used for attaching extra information
     * to a node. This meta data is not serialized. The sole purpose of meta
     * data is to attach a custom instance that operates on this node.
     *
     * @param object Meta data object.
     */
    public void setMetaData(Object object) {
        if (object instanceof MetaData) {
            ((MetaData) object).setNode(this);
        }
        this.metaData = object;
    }

    /**
     * @param  Meta data to cast to.
     * @return The attached meta data of this node.
     */
    @SuppressWarnings("unchecked")
    public  T getMetaData() {
        return (T) metaData;
    }

    /**
     * Resets the node's exposed data.
     */
    public void reset() {
        clearChildren();
        clearConfigs();
        clearRoConfigs();
        clearAttributes();
        setPassword(null);
        setDisplayName(null);
        setAction(null);
        setInterfaces(null);
        setValue(null);
        setValueType(null);
        setWritable(null);
    }

    private void markChanged() {
        if (!isSerializable()) {
            return;
        }
        Linkable link = getLink();
        if (link != null) {
            SerializationManager sm = link.getSerialManager();
            if (sm != null) {
                sm.markChanged();
            }
        }
    }

    private void postRemoval(String prefix, String name, Value value) {
        if (value == null) {
            return;
        }

        ValueUpdate update = new ValueUpdate(name, value, true);
        if ("$".equals(prefix)) {
            listener.postConfigUpdate(update);
        } else if ("@".equals(prefix)) {
            listener.postAttributeUpdate(update);
        }

        SubscriptionManager man = link.getSubscriptionManager();
        if (man != null) {
            man.postMetaUpdate(this, prefix + name, null);
        }

        markChanged();
    }

    /**
     * Checks the string and then returns it. An exception is thrown if the
     * name is invalid in any way.
     *
     * @param name Name to check
     * @return Name
     */
    public static String checkAndEncodeName(String name) {
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("name");
        }
        return StringUtils.encodeName(name);
    }

    /**
     * @return The banned characters not allowed to be in names.
     */
    public static char[] getBannedCharacters() {
        return BANNED_CHARS.clone();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy