
com.tailf.jnc.Element Maven / Gradle / Ivy
package com.tailf.jnc;
import org.xml.sax.InputSource;
import java.io.*;
import java.util.*;
/**
* A configuration element sub-tree. Makes it possible to create and/or
* manipulate an element tree. The element tree is then to be used to issue
* NETCONF operations using the {@link NetconfSession} class.
*
* Example:
*
*
*
* // start (Netconf) sessions towards our device
* SSHConnection connection = new SSHConnection("127.0.0.1");
* connection.authenticateWithPassword("admin", "pass");
* SSHSession ssh = new SSHSession(connection);
* NetconfSession session = new NetconfSession(ssh);
*
* // get system configuration from session
* Element sysConfig = session.getConfig("/system").first();
*
* // manipulate the element tree
* sysConfig.setValue("dns", "83.100.1.1");
* sysConfig.setValue("gateway", "10.0.0.1");
*
* // Write back the updated element tree
* session.editConfig(sysConfig);
*
*/
public class Element implements Serializable {
private static final long serialVersionUID = 1L;
/**
* The NETCONF namespace. "urn:ietf:params:xml:ns:netconf:base:1.0".
*/
public static final String NETCONF_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0";
public static final String OPERATION = "operation";
public static final String CREATE = "create";
public static final String DELETE = "delete";
public static final String REPLACE = "replace";
public static final String MERGE = "merge";
/**
* The namespace this element name belongs to.
*/
public String namespace;
/**
* The name of the node.
*/
public String name;
/**
* The value of the element.
*/
public Object value;
/**
* Attributes on the node. ArrayList of Attribute.
*/
ArrayList attrs;
/**
* Prefix map are really xmlns attributes. For example:
*
* xmlns="http:tail-f.com/aaa" or xmlns:aaa="http:tail-f.com/aaa"
*/
public PrefixMap prefixes;
/**
* Static prefix map that is always a default and will be resolved at root
* level. For example the NETCONF prefix mapping "nc" is added here as a
* default.
*/
public static final PrefixMap defaultPrefixes = new PrefixMap(new Prefix[] {
new Prefix("nc", NETCONF_NAMESPACE),
new Prefix("pl", Capabilities.NS_PARTIAL_LOCK),
new Prefix("ncn", Capabilities.NS_NOTIFICATION) });
/**
* Children to the node, if container element.
*/
protected NodeSet children = null;
/**
* The parent to this node.
*/
protected Element parent = null;
/**
* Constructor that creates a new element tree. An element consists of a
* name that belongs to a namespace.
*
* @param ns Namespace
* @param name Name of the element
*/
public Element(String ns, String name) {
namespace = ns;
this.name = name;
}
public Element getRootElement() {
Element top = this;
while (top.parent != null) {
top = top.parent;
}
return top;
}
/**
* Static method that creates a new configuration element tree given a
* path. A prefix mapping for the namespace will be added to the top
* element of the created sub-tree. A prefix map is used for resolving
* prefix to namespace mappings for a given path.
*
* See {@link PathCreate} for more information about path create
* expressions.
*
* @param namespace Namespace
* @param pathStr A "path create" string
* @return A new configuration element tree
*/
public static Element create(String namespace, String pathStr)
throws JNCException {
final PrefixMap prefixMap = new PrefixMap();
prefixMap.add(new Prefix("", namespace));
return Element.create(prefixMap, pathStr);
}
/**
* Static method that creates a new configuration element tree, given a
* path. A prefix mapping will be added to the top element of the created
* sub-tree. A prefix map is used for resolving prefix to namespace
* mappings for a given path.
*
* See {@link PathCreate} for more information about path create
* expressions.
*
* @param prefix A prefix mapping.
* @param pathStr A "path create" string
* @return A new configuration element tree
*/
public static Element create(Prefix prefix, String pathStr)
throws JNCException {
final PrefixMap prefixMap = new PrefixMap();
prefixMap.add(prefix);
return Element.create(prefixMap, pathStr);
}
/**
* Static method that creates a new configuration element tree, given a
* path. A prefix map will be added to the top element in the created
* sub-tree. A prefix map is used for resolving prefix to namespace
* mappings for a given path.
*
* See {@link PathCreate} for more information about path create
* expressions.
*
* @param prefixMap Prefix mappings to be added
* @param pathStr A "path create" string
* @return A new configuration element tree
*/
public static Element create(PrefixMap prefixMap, String pathStr)
throws JNCException {
trace("create: \"" + pathStr + "\"");
final PathCreate path = new PathCreate(pathStr);
final Element t = path.eval(prefixMap);
t.setPrefix(prefixMap);
return t;
}
/**
* Creates a new path. This is a value for mode in
* {@link #createPath(int, String)}.
*/
public static final int CREATE_NEW = 1;
/**
* Creates a new path and merges with the existing nodes when possible.
* This is a value for mode in {@link #createPath(int, String)}.
*/
public static final int CREATE_MERGE = 2;
/**
* Creates a new path and merges with the existing nodes when possible.
* Several nodes may me matching the path, and a sub-tree will be created
* for all of them. This is a value for mode in
* {@link #createPath(int, String)}.
*/
public static final int CREATE_MERGE_MULTI = 3;
/**
* Creates a child element to the context node.
*
* @param name The name of the child element
*/
public Element createChild(String name) {
final Element elem = new Element(namespace, name);
addChild(elem);
return elem;
}
/**
* Creates a child element with specified value.
*
* @param name The name of the child element
* @param value The value of the element
*/
public Element createChild(String name, Object value) {
final Element elem = new Element(namespace, name);
elem.setValue(value);
addChild(elem);
return elem;
}
/**
* Creates a child element with specified value.
*
* @param namespace The namespace that the name belongs to
* @param name The name of the child element
* @param value The value of the element
*/
public Element createChild(String namespace, String name, Object value) {
final Element elem = new Element(namespace, name);
elem.setValue(value);
addChild(elem);
return elem;
}
/**
* Returns the path for this element including an appended sub-path.
*
* @param subPath A sub-path to be appended to the current path
*/
public String getElementPath(String subPath) {
return getElementPath() + "/" + subPath;
}
/**
* Creates an element tree as a child to the context node from a create
* path expression.
*
* See {@link PathCreate} for more information about path create
* expressions.
*
* @param pathStr A "path create" string.
* @return A new configuration element sub-tree that is a child of the
* context node
*/
public Element createPath(String pathStr) throws JNCException {
return createPath(CREATE_MERGE, null, pathStr);
}
/**
* Creates an element tree as a child to the context node from a create
* path expression. The mode parameter is one of: {@link #CREATE_NEW},
* {@link #CREATE_MERGE}, {@link #CREATE_MERGE_MULTI}
*
* @param mode The creation mode.
* @param pathStr A "path create" string.
* @return A new configuration element sub-tree that is a child of the
* context node
*/
public Element createPath(int mode, String pathStr) throws JNCException {
return createPath(mode, null, pathStr);
}
/**
* Creates an element tree as a child to the context node from a create
* path expression. A prefix map containing the provided namespace will be
* added to the context nodes prefix mapping. A prefix map is used for
* resolving prefix to namespace mappings for a given path.
*
* See {@link PathCreate} for more information about path create
* expressions.
*
* @param namespace Namespace.
* @param pathStr A "path create" string.
* @return A new configuration element sub-tree that is a child of the
* context node
*/
public Element createPath(String namespace, String pathStr)
throws JNCException {
final PrefixMap p = new PrefixMap();
p.add(new Prefix("", namespace));
return createPath(CREATE_MERGE, p, pathStr);
}
/**
* Creates an element tree as a child to the context node from a create
* path expression. A prefix containing the provided prefix mapping will be
* added to the context nodes prefix mappings. A prefix map is used for
* resolving prefix to namespace mappings for a given path.
*
* See {@link PathCreate} for more information about path create
* expressions.
*
* @param prefix A prefix mapping
* @param pathStr A "path create" string.
* @return A new configuration element sub-tree that is a child of the
* context node
*/
public Element createPath(Prefix prefix, String pathStr)
throws JNCException {
final PrefixMap p = new PrefixMap();
p.add(prefix);
return createPath(CREATE_MERGE, p, pathStr);
}
/**
* Creates an element tree as a child to the context node from a create
* path expression. A prefix map containing prefix mappings will be added
* to the context nodes prefix mappings. A prefix map is used for resolving
* prefix to namespace mappings for a given path.
*
* See {@link PathCreate} for more information about path create
* expressions.
*
* @param addPrefixes Prefix mappings
* @param pathStr A "path create" string.
* @return A new configuration element sub-tree that is a child of the
* context node
*/
public Element createPath(PrefixMap addPrefixes, String pathStr)
throws JNCException {
return createPath(CREATE_MERGE, addPrefixes, pathStr);
}
/**
* Creates an element tree as a child to the context node. A prefix map
* containing prefix mappings will be added to the context nodes prefix
* mappings. A prefix map is used for resolving prefix to namespace
* mappings for a given path.
*
* The mode parameter is one of: {@link #CREATE_NEW}, {@link #CREATE_MERGE}, {@link #CREATE_MERGE_MULTI}
*
* See {@link PathCreate} for more information about path create
* expressions.
*
* @param mode The creation mode.
* @param addPrefixes Prefix mappings
* @param pathStr A "path create" string.
* @return A new configuration element sub-tree that is a child of the
* context node
*/
public Element createPath(int mode, PrefixMap addPrefixes, String pathStr)
throws JNCException {
trace("createPath: \"" + pathStr + "\"");
final PathCreate path = new PathCreate(pathStr);
if (addPrefixes != null) {
setPrefix(addPrefixes);
}
if (mode == CREATE_MERGE || mode == CREATE_MERGE_MULTI) {
/* Step down the locationsteps until we cannot go further */
NodeSet nodeSet = new NodeSet(), deepest;
Element first_found = null;
nodeSet.add(this);
deepest = nodeSet;
int step = 0;
final int steps = path.steps();
while (nodeSet.size() > 0 && step < steps) {
nodeSet = ((Path) path).evalStep(nodeSet, step);
if (nodeSet.size() > 0) {
deepest = nodeSet;
if (first_found == null) {
first_found = nodeSet.getElement(0);
}
}
step++;
}
if (step == steps && nodeSet.size() > 0) {
return first_found; /* path already exist */
}
if (mode == CREATE_MERGE && deepest.size() > 1) {
throw new JNCException(JNCException.PATH_CREATE_ERROR,
"multiple nodes found by path: \"" + pathStr + "\"");
}
step--; // need to do last step again
for (Element parent : deepest) {
final PrefixMap prefixMap = parent.getContextPrefixMap();
for (int i = step; i < steps; i++) {
final Element elem = path.evalStep(prefixMap, i, parent);
parent.addChild(elem);
parent = elem;
if (first_found == null) {
first_found = elem;
}
}
}
return first_found;
} else { /* mode== CREATE_NEW */
final PrefixMap prefixMap = getContextPrefixMap();
final Element elem = path.eval(prefixMap);
addChild(elem);
return elem;
}
}
/**
* Sets the default prefix mapping on this node. xmlns= 'NAMESPACE' A
* prefix mapping is used for resolving prefix to namespace mappings for a
* given path.
*/
public void setDefaultPrefix() {
setPrefix(new Prefix("", namespace));
}
/**
* Removes the default prefix mapping on this node (if any). xmlns=
* 'NAMESPACE' A prefix mapping is used for resolving prefix to namespace
* mappings for a given path.
*/
public void removeDefaultPrefix() {
removePrefix("");
}
/**
* Sets a prefix mapping to the context node. A prefix map is used for
* resolving prefix to namespace mappings for a given path.
*
* @param prefix String prefix to be used for the namespace.
*/
public void setPrefix(String prefix) {
setPrefix(new Prefix(prefix, namespace));
}
/**
* Sets a prefix map to the context node. A prefix map is used for
* resolving prefix to namespace mappings for a given path.
*
* @param prefix A prefix mapping
*/
public void setPrefix(Prefix prefix) {
setPrefix(new PrefixMap(prefix));
}
/**
* Sets prefix mappings to the context node. A prefix map is used for
* resolving prefix to namespace mappings for a given path.
*
* @param prefixMap Prefix mappings
*/
public void setPrefix(PrefixMap prefixMap) {
if (prefixes == null) {
prefixes = new PrefixMap();
}
prefixes.set(prefixMap);
}
/**
* Removes a prefix map from the context node.
*/
public void removePrefix(String prefix) {
if (prefixes == null) {
return;
}
prefixes.remove(prefix);
}
/* Parent and Children */
/**
* Returns the parent node of this node. Or null
if none.
*
* @return Parent configuration element node or null
*/
public Element getParent() {
return parent;
}
/**
* Adds child to children and makes this element the parent of child.
*
* @param child Child element to be added
*/
public void addChild(Element child) {
if (children == null) {
children = new NodeSet();
}
children.add(child);
child.parent = this;
}
/**
* Inserts a child element and returns the position of (the first
* occurrence of) the inserted child in the list of children.
*
* Checks that child is not already in use.
*
* @param child Child element to be inserted
* @throws JNCException If child is already a child of another element or
* if child equals this element
*/
public int insertChild(Element child) throws JNCException {
if (child.parent != null || child == this) {
throw new JNCException(JNCException.ELEMENT_ALREADY_IN_USE, this);
}
addChild(child);
return children.indexOf(child);
}
/**
* Inserts a child element at a specific index in the list of children and
* returns that index upon success.
*
* @param child Child element to be inserted
* @param index Position in child list to insert child to. 0 is the first.
* @throws JNCException If child is already a child of another element.
*/
public int insertChild(Element child, int index) throws JNCException {
if (child.parent != null) {
throw new JNCException(JNCException.ELEMENT_ALREADY_IN_USE, this);
}
if (children == null) {
children = new NodeSet();
}
child.parent = this;
children.add(index, child);
return children.indexOf(child);
}
/**
* Inserts a child element at the correct position by providing structure
* information (the names of all the children, in order).
*
* @param child Child element to be inserted
* @param childrenNames The names of all children in order.
* @throws JNCException If child is already a child of another element.
*/
public int insertChild(Element child, String[] childrenNames)
throws JNCException {
if (child.parent != null) {
throw new JNCException(JNCException.ELEMENT_ALREADY_IN_USE, this);
}
if (children == null) {
children = new NodeSet();
}
child.parent = this;
int pos = 0;
int i = 0;
while (pos < children.size()) {
if (children.getElement(pos).name.equals(childrenNames[i])) {
pos++;
} else if (child.name.equals(childrenNames[i])) {
break;
} else {
i++;
}
}
children.add(pos, child);
return pos;
}
/**
* Inserts a child element first in the list of children. Always returns 0.
*
* @param child Child element to be inserted
* @throws JNCException If child is already a child of another element.
*/
public int insertFirst(Element child) throws JNCException {
return insertChild(child, 0);
}
/**
* Inserts a child element last in the list of children. Returns the
* position of the inserted child.
*
* @param child Child element to be inserted
* @throws JNCException If child is already a child of another element or
* if child equals this element
*/
public int insertLast(Element child) throws JNCException {
return insertChild(child);
}
/**
* Returns the position of this element in the the parent child list. '0'
* is the first position.
*/
public int position() {
return (parent == null) ? -1 : parent.children.indexOf(this);
}
/**
* Deletes child node(s). All children matching the path string will be
* deleted. An array of the deleted children is returned.
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string for children that will be deleted
* @return An array of the deleted element nodes.
*/
public NodeSet delete(String pathStr) throws JNCException {
final NodeSet nodes = get(pathStr);
if (nodes != null) {
for (int i = 0; i < nodes.size(); i++) {
nodes.getElement(i).delete();
}
}
return nodes;
}
/**
* Deletes this node. Means that the parent will no longer have reference
* to us. This node will be removed from our parents child list. This
* method will not do anything if this node is the root node of a tree.
*/
public void delete() {
if (parent != null) {
parent.deleteChild(this);
}
}
/**
* Deletes a child node, provided it is present in the children list.
*
* @param child Child to delete
*/
public void deleteChild(Element child) {
if (children == null) {
return;
}
for (int i = 0; i < children.size(); i++) {
if (child == children.getElement(i)) {
children.remove(i);
child.parent = null;
break;
}
}
}
/**
* Returns true
if this node has any children,
* false
otherwise.
*
* @return true
or false
*/
public boolean hasChildren() {
return children != null && children.size() > 0;
}
/* Attibutes */
/**
* Adds an attribute for this element.
*
*/
public void addAttr(Attribute attr) {
if (attrs == null) {
attrs = new ArrayList();
}
attrs.add(attr);
}
/**
* Gets all attributes for this element.
*
* @return An array of configuration attributes or null
*/
public Attribute[] getAttrs() {
if (attrs != null) {
return attrs.toArray(new Attribute[attrs.size()]);
}
return null;
}
/**
* Returns the string value of the named attribute. Returns
* null
if no such attribute is found or "" if no value is
* given to the attribute.
*
* @param name The name of the attribute
* @return String value of the attribute.
*/
public String getAttrValue(String name) {
return getAttr(name).getValue();
}
/**
* Gets an Attribute
*
* @param name Lookup using the name of attribute
*/
public Attribute getAttr(String name) {
if (attrs != null) {
for (final Attribute attr : attrs) {
if (attr.name.equals(name)) {
return attr;
}
}
}
return null; // not found
}
/**
* Sets an attribute on this element, treating xmlns attributes as prefix
* maps. If the attribute already exists, its value is changed, otherwise
* the attribute is added.
*
* @param name The name of the attribute
* @param value The value of the attribute
* @return The configuration attribute.
*/
public Attribute setAttr(String name, String value) {
trace("setAttr: " + name + "=\"" + value + "\"");
if (name.equals("xmlns")) {
// it's an xmlns attribute - treat this as a prefix map
final Prefix p = new Prefix("", value);
setPrefix(p);
return p;
} else if (name.startsWith("xmlns:")) {
final String prefix = name.substring(6);
final Prefix p = new Prefix(prefix, value);
setPrefix(p);
return p;
} else if (attrs == null) {
attrs = new ArrayList();
} else {
for (final Attribute attr : attrs) {
if (attr.name.equals(name)) {
attr.setValue(value);
return attr;
}
}
}
final Attribute attr = new Attribute(namespace, name, value);
addAttr(attr);
return attr;
}
/**
* Sets an attribute on this element, aware of its namespace. If an
* attribute with this name and ns already exists, its value is changed.
* Otherwise such an attribute is added.
*
* If name starts with xmlns and ns starts with the xmlns namespace
* (http://www.w3.org/2000/xmlns/), the value is set as a prefix map.
*
* @param ns The namespace that the attribute name belongs to
* @param name The name of the attribute
* @param value The value of the attribute
* @return The configuration attribute.
*/
public Attribute setAttr(String ns, String name, String value) {
trace("setAttr: (" + ns + ") " + name + "=\"" + value + "\"");
if (name.startsWith("xmlns") && ns.startsWith(Prefix.XMLNS_NAMESPACE)) {
return setAttr(name, value);
}
if (attrs == null) {
attrs = new ArrayList();
} else {
for (final Attribute attr : attrs) {
if (attr.ns.equals(ns) && attr.name.equals(name)) {
// Change existing attribute
attr.setValue(value);
return attr;
}
}
}
// Add new one
final Attribute attr = new Attribute(ns, name, value);
addAttr(attr);
return attr;
}
/**
* Removes an attribute with specified name. This method does not consider
* namespace so note that it will remove the first attribute which matches
* the name (and no other).
*
* @param name The name of the attribute to be removed.
*/
public void removeAttr(String name) {
if (attrs != null) {
for (int i = 0; i < attrs.size(); i++) {
final Attribute attr = attrs.get(i);
if (attr.name.equals(name)) {
trace("removeAttr: " + name);
attrs.remove(i);
return;
}
}
}
}
/**
* Removes an attribute with specified namespace and name from this
* element's attribute list.
*
* @param namespace the namespace the name belongs to.
* @param name The name of the attribute to be removed.
*/
public void removeAttr(String namespace, String name) {
if (attrs != null) {
for (int i = 0; i < attrs.size(); i++) {
final Attribute attr = attrs.get(i);
if (attr.name.equals(name) && attr.ns.equals(namespace)) {
trace("removeAttr: (" + namespace + ") " + name);
attrs.remove(i);
}
}
}
}
/* Values */
/**
* Finds the value of child with specified name, if it exists.
*
* @param childName Name of child
* @return Value of child, or null if none
*/
public Object getValueOfChild(String childName) {
for (final Element child : children) {
if (child.name.equals(childName)) {
return child.getValue();
}
}
return null;
}
/**
* Returns the value of this element.
*
* @return The value of the element.
*/
public Object getValue() {
return value;
}
/**
* Check if any nodes in this element matches a given path string.
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr The path to match
* @return true
if any node matches pathStr;
* false
otherwise.
*/
public boolean exists(String pathStr) throws JNCException {
final NodeSet nodes = get(pathStr);
return (nodes != null && !nodes.isEmpty());
}
/**
* Returns the value of a the first subnode matching a given path string,
* or null if there are no matches.
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string to find node
* @return The value of the (first) found element or null
*/
public Object getValue(String pathStr) throws JNCException {
final NodeSet nodes = get(pathStr);
// Don't call exists, to avoid computing matches twice
return (nodes != null && !nodes.isEmpty()) ? nodes.getElement(0)
.getValue() : null;
}
/**
* Returns the value(s) of nodes in a given path expression.
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string to find nodes
* @return An array of the values of the element nodes found by the
* expression (or null
)
*/
public Object[] getValues(String pathStr) throws JNCException {
final NodeSet nodes = get(pathStr);
Object[] values = null;
if (nodes != null) {
values = new String[nodes.size()];
for (int i = 0; i < nodes.size(); i++) {
values[i] = nodes.getElement(i).getValue();
}
}
return values;
}
/**
* Returns the values of nodes in a given path expression.
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string to find nodes
* @return A set with the values of the element nodes found by the
* expression (or null
)
*/
public Set getValuesAsSet(String pathStr) throws JNCException {
final String[] valuesBefore = (String[]) getValues(pathStr);
return new HashSet(Arrays.asList(valuesBefore));
}
/**
* Sets a new value for this node element.
*
* @param value Value to be set
*/
public void setValue(Object value) {
trace("setValue: " + name + "=\"" + value + "\"");
this.value = value;
}
/**
* Sets value for all node elements matching specified path string.
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string to find nodes
* @param value Value to be set
*/
public void setValue(String pathStr, Object value) throws JNCException {
for (final Element elem : get(pathStr)) {
elem.setValue(value);
}
}
/**
* Deletes value of node(s)
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string to find nodes
*/
public void deleteValue(String pathStr) throws JNCException {
for (final Element elem : get(pathStr)) {
elem.deleteValue();
}
}
/**
* Deletes the value for this node.
*/
public void deleteValue() {
value = null;
}
/* Get */
/**
* Returns first node that matches the path expression, or
* null
if no such node was found.
*
* Example:
*
*
* Element full = NetconfSession:getConfig();
*
* Element first_host = full.getFirst("/hosts/host");
* Element last_host = full.getLast("/hosts/host");
*
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string to find nodes
* @return The first element node found by the expression.
*/
public Element getFirst(String pathStr) throws JNCException {
final NodeSet nodeSet = get(pathStr);
if (nodeSet == null || nodeSet.isEmpty()) {
return null;
}
return nodeSet.getElement(0);
}
/**
* Returns the last node that matches the path expression, or
* null
if no such node was found.
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string to find nodes
* @return The last element node found by the expression.
*/
public Element getLast(String pathStr) throws JNCException {
final NodeSet nodeSet = get(pathStr);
if (nodeSet == null || nodeSet.isEmpty()) {
return null;
}
return nodeSet.get(nodeSet.size() - 1);
}
/**
* Gets all nodes matching a given path expression.
*
* Example:
*
*
* Element full_config = session.get();
* NodeSet calle_nodes = full_config.get("host[www='Calle']");
*
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string to find nodes
* @return An array of the element nodes found by the expression.
*/
public NodeSet get(String pathStr) throws JNCException {
final Path path = new Path(pathStr);
return path.eval(this);
}
/**
* Returns the children of this node.
*
* @return The children node set of this node or null
*/
public NodeSet getChildren() {
return children;
}
/**
* Get the children with specified name, from children list
*
* @param name Name of child
* @return a NodeSet with all chldren that has the name
*/
public NodeSet getChildren(String name) {
final NodeSet n = new NodeSet();
if (children != null) {
for (int i = 0; i < children.size(); i++) {
final Element elem = children.getElement(i);
if (elem.name.equals(name)) {
n.add(elem);
}
}
}
return n;
}
/**
* Get the (first) child with specified name, from children list
*
* @param name Name of child
* @return The found element or null
*/
public Element getChild(String name) {
if (children != null) {
for (int i = 0; i < children.size(); i++) {
final Element elem = children.getElement(i);
if (elem.name.equals(name)) {
return elem;
}
}
}
return null;
}
/**
* Clones the tree, making an exact copy. The entire tree is treated as if
* it was created.
*
* @return A copy of the element sub-tree.
*/
@Override
public Object clone() {
final Element copy = new Element(namespace, name);
// copy all children
if (children != null) {
if (copy.children == null) {
copy.children = new NodeSet();
}
for (int i = 0; i < children.size(); i++) {
final Element child = children.getElement(i);
final Element child_copy = (Element) child.clone();
copy.addChild(child_copy);
}
}
cloneAttrs(copy);
cloneValue(copy);
return copy;
}
/**
* Tries to find an element with the same namespace and name as x.
*
* @param child Element to compare against
* @return the matching element if it exists; null
otherwise.
*/
protected Element getChild(Element child) {
if (children != null) {
for (final Element other : children) {
if (child.compare(other) >= 0) {
return other;
}
}
}
return null;
}
/**
* Clones the tree, making an exact copy. Does only clone this level. not
* the children.
*
* @return A copy of the shallow element sub-tree.
*/
protected Element cloneShallow() {
final Element copy = new Element(namespace, name);
cloneAttrs(copy);
cloneValue(copy);
return copy;
}
/**
* Operation flag to be used with {@link #merge(Element,int)}.
*/
public final static int OP_CREATE = 1;
/**
* Operation flag to be used with {@link #merge(Element,int)}.
*/
public final static int OP_DELETE = 2;
/**
* Operation flag to be used with {@link #merge(Element,int)}.
*/
public final static int OP_REPLACE = 3;
/**
* Operation flag to be used with {@link #merge(Element,int)}.
*/
public final static int OP_MERGE = 4;
/**
* Merges a subtree into a resulting target subtree. The 'op' parameter
* controls how the nodes that are added in the target subtree should be
* marked. Either OP_CREATE, OP_DELETE, OP_MERGE or OP_REPLACE.
*
* @param root Target subtree. Must start from root node.
* @param op One of {@link #OP_CREATE}, {@link #OP_DELETE},
* {@link #OP_MERGE} or {@link #OP_REPLACE}
* @return Resulting target subtrees in NodeSet
*
*/
public Element merge(Element root, int op) throws JNCException {
// make list of nodes down to this node from root
final NodeSet list = new NodeSet();
Element a = this;
while (a != null) {
list.add(0, a);
a = a.parent;
}
// pop first element and assert that it's the same as root
Element x = list.getElement(0);
list.remove(0);
if (root == null) {
if (list.size() >= 1) {
root = x.cloneShallow();
} else {
// special case. only one path in root. Differ already
if (op == OP_CREATE) {
final Element x1 = (Element) x.clone();
x1.markCreate();
return x1;
} else if (op == OP_DELETE) {
final Element x1 = x.cloneShallow();
x1.markDelete();
return x1;
} else if (op == OP_REPLACE) {
final Element x1 = (Element) x.clone();
x1.markReplace();
return x1;
} else if (op == OP_MERGE) {
final Element x1 = (Element) x.clone();
x1.markMerge();
return x1;
}
}
}
if (x.compare(root) == -1) {
System.err.println(" x= " + x);
System.err.println(" root= " + root);
System.err.println(" compare: " + x.compare(root));
throw new JNCException(JNCException.ELEMENT_MISSING,
x.getElementPath() + ", " + (root != null ? root.getElementPath() : null));
}
// same now, go down
Element parent = root;
while (list.size() > 1) {
// pop first element from list
x = list.getElement(0);
list.remove(0);
Element child = parent != null ? parent.getChild(x) : null;
if (child == null) {
// need to create it
child = x.cloneShallow();
parent.addChild(child);
}
// go down
parent = child;
}
// last one
if (list.size() > 0) {
x = list.getElement(0);
// list.remove(0); // no need
if (op == OP_CREATE) {
// we know it's unique
final Element x1 = (Element) x.clone();
x1.markCreate();
parent.addChild(x1);
} else if (op == OP_DELETE) {
// we know it's unique, cut all children except keys
final Element x1 = x.cloneShallow();
x1.markDelete();
parent.addChild(x1);
} else if (op == OP_MERGE) {
final Element x1 = (Element) x.clone();
x1.markMerge();
parent.addChild(x1);
} else { // OP_REPLACE
final Element x1 = (Element) x.clone();
x1.markReplace();
parent.addChild(x1);
}
}
// done
return root;
}
/**
* Clones the attributes to the target copy. Used in clone methods of this
* class and YangElement.
*
* @param copy The target copy to clone the attributes to
*/
protected Element cloneAttrs(Element copy) {
// copy attrs
if (attrs != null) {
copy.attrs = new ArrayList();
for (int i = 0; i < attrs.size(); i++) {
final Attribute attr = attrs.get(i);
final Attribute copy_attr = (Attribute) attr.clone();
copy.attrs.add(copy_attr);
}
}
// copy xmlns attrs
if (prefixes != null) {
copy.prefixes = (PrefixMap) prefixes.clone();
}
return copy;
}
/**
* clones the value to the target copy. Used in clone methods of this
* class and YangElement.
*
* @param copy The target copy to clone the value field to
*/
protected Element cloneValue(Element copy) {
if (value instanceof YangBaseType>) {
copy.value = ((YangBaseType>) value).clone();
} else if (value != null) {
copy.value = value;
}
return copy;
}
/* Mark operations. Uses the nc:operations attribute */
/**
* Removes the operation attribute from a node. This is equivalent to:
* removeAttr(Element.NETCONF_NAMESPACE,"operation");
see @removeAttr
*/
public void removeMark() {
removeAttr(NETCONF_NAMESPACE, OPERATION);
}
/**
* Sets the operation attribute from a node. This is equivalent to:
* setAttr(Element.NETCONF_NAMESPACE,"operation",operation);
see @setAttr
*/
public void setMark(String operation) {
Element that = this;
while (that!=null&&that.prefixes==null){
that = that.getParent();
}
that.prefixes.merge(defaultPrefixes);
setAttr(NETCONF_NAMESPACE, OPERATION, operation);
}
/**
* Removes all operation attributes from a sub-tree.
*
*/
public void removeMarks() {
removeMark();
if (children != null) {
for (int i = 0; i < children.size(); i++) {
children.getElement(i).removeMarks();
}
}
}
/**
* Marks a node with operation delete.
*/
public void markDelete() {
setMark(DELETE);
}
/**
* Marks node(s) with operation delete.
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string to find nodes
*/
public void markDelete(String pathStr) throws JNCException {
final Path path = new Path(pathStr);
final NodeSet nodeSet = path.eval(this);
if (nodeSet != null) {
for (int i = 0; i < nodeSet.size(); i++) {
nodeSet.getElement(i).markDelete();
}
}
}
/**
* Marks a node with operation replace.
*/
public void markReplace() {
setMark(REPLACE);
}
/**
* Marks node(s) with operation replace.
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string to find nodes
*/
public void markReplace(String pathStr) throws JNCException {
final Path path = new Path(pathStr);
final NodeSet nodeSet = path.eval(this);
if (nodeSet != null) {
for (int i = 0; i < nodeSet.size(); i++) {
nodeSet.getElement(i).markReplace();
}
}
}
/**
* Marks a node with operation merge.
*/
public void markMerge() {
setMark(MERGE);
}
/**
* Marks node(s) with operation merge.
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string to find nodes
*/
public void markMerge(String pathStr) throws JNCException {
final Path path = new Path(pathStr);
final NodeSet nodeSet = path.eval(this);
if (nodeSet != null) {
for (int i = 0; i < nodeSet.size(); i++) {
nodeSet.getElement(i).markMerge();
}
}
}
/**
* Marks a node with operation create
*/
public void markCreate() {
setMark(CREATE);
}
/**
* Marks node(s) with operation create.
*
* See {@link Path} for more information about path expressions.
*
* @param pathStr Path string to find nodes
*/
public void markCreate(String pathStr) throws JNCException {
final Path path = new Path(pathStr);
final NodeSet nodeSet = path.eval(this);
if (nodeSet != null) {
for (int i = 0; i < nodeSet.size(); i++) {
nodeSet.getElement(i).markCreate();
}
}
}
/* Info methods */
/**
* A qualified name is a prefixed name. This method will find the prefix of
* this elements namespace and build a name in the format: "prefix:name".
* If no prefix is found the prefix "unknown" will be used.
*
* @return The qualified name of the element.
*/
public String qualifiedName() {
String qName;
String prefix = prefix();
if (prefix == null) {
prefix = "";
}
if (prefix.equals("")) {
qName = name;
} else {
qName = prefix + ":" + name;
}
return qName;
}
/**
* Returns the prefix name of this element.
*
* @return The prefix name that the namespace of this element is bound to
*/
public String prefix() {
return nsToPrefix(namespace);
}
/**
* Returns a prefix map, as it is in the current context. The prefix map is
* built up by traversing the parents.
*
* @return The prefix mappings available at this context node
*/
public PrefixMap getContextPrefixMap() {
final PrefixMap p = new PrefixMap();
Element node = this;
while (node != null) {
if (node.prefixes != null) {
p.merge(node.prefixes);
}
node = node.parent;
}
// merge in the default prefixes as well
p.merge(defaultPrefixes);
return p;
}
/**
* Lookups a prefix and returns the associated namespace, traverses up the
* parent links until the prefix is found. Returns null
if the
* prefix is not found.
*
* @param prefix Prefix string to lookup.
* @return The namespace of the specified prefix in the context of this
* node.
*/
public String lookupContextPrefix(String prefix) {
Element node = this;
while (node != null) {
if (node.prefixes != null) {
final String ns = node.prefixes.prefixToNs(prefix);
if (ns != null) {
return ns;
}
}
node = node.parent;
}
// as a last resort use the default prefix map.
return defaultPrefixes.prefixToNs(prefix);
}
/**
* This method will find the prefix of a specified namespace, from the
* given context node. If no prefix is found null
will be
* returned.
*
* @param ns Namespace string to lookup
* @return The prefix string of the given namespace at the context node.
*/
public String nsToPrefix(String ns) {
Element top = this;
String prefix = null;
while (prefix == null && top != null) {
if (top.prefixes != null) {
prefix = top.prefixes.nsToPrefix(ns);
}
top = top.parent;
}
if (prefix != null) {
return prefix;
}
// as a last resort use the default prefix map
return defaultPrefixes.nsToPrefix(ns);
}
/**
* Returns the path as a string
*
* @return The path of element
*/
public String getElementPath() {
Element top = this;
String s = null;
while (top != null) {
if (top.namespace == null) {
s = strConcat(top.name, s);
} else { // top.namespace!=null
if (top.parent != null && top.parent.namespace != null
&& top.namespace.equals(top.parent.namespace)) {
s = strConcat(top.name, s);
} else {
s = strConcat(top.qualifiedName(), s);
}
}
top = top.parent;
}
return "/" + s;
}
private String strConcat(String s1, String s2) {
if (s2 != null) {
return s1 + "/" + s2;
}
return s1;
}
/**
* Compare if equal to another Object. This method does not compare
* attributes and children. It will only compare:
*
* - name
*
- namespace
*
- value
*
*
* @param other Object to compare this element against.
* @return true
if other is an Element with same name,
* namespace and value; false
otherwise.
*/
@Override
public boolean equals(Object other) {
if (other instanceof Element) {
final Element b = (Element) other;
final boolean sameName = name.equals(b.name);
final boolean sameNamespace = namespace.equals(b.namespace);
if (sameName && sameNamespace) {
if (value != null) {
return value.equals(b.value);
} else {
return b.value == null;
}
}
}
return false;
}
/**
* Returns a hash code value for this object.
*
* @return The sum of the hash codes of the name, namespace and value of
* this element subtree node.
*/
@Override
public int hashCode() {
return (name.hashCode() + namespace.hashCode() + value.hashCode());
}
/**
* Compare two elements. Compares the name, namespace, and value. Returns:
*
*
* - -1 - if the two containers keys are not equal, which means that they
* are completely different.
*
- 0 - if the two elements are equal in name, namespace, and value.
*
- 1 - the two containers are the same except the value.
*
*
* @param b Element to compare this element against.
*/
public int compare(Element b) {
final boolean sameName = name.equals(b.name);
final boolean sameNamespace = namespace.equals(b.namespace);
if (sameName && sameNamespace) {
return equals(b) ? 0 : 1;
}
return -1;
}
/**
* Builds a string with the name, value, namespace, prefixes, other
* attributes, children and the path of this element subtree.
*
* @return String representation of this element
*/
@Override
public String toString() {
final StringBuffer s_attrs = new StringBuffer();
final StringBuffer s_children = new StringBuffer();
final StringBuffer res = new StringBuffer("Element{name=").append(name);
if (value != null) {
res.append(", value=").append(value);
}
res.append(", ns=").append(namespace);
// Attributes
if (prefixes != null) {
for (final Prefix p : prefixes) {
s_attrs.append(p.toXMLString());
s_attrs.append(", ");
}
}
if (attrs != null) {
for (final Attribute a : attrs) {
s_attrs.append(a.toXMLString(this));
s_attrs.append(", ");
}
}
if (s_attrs.length() >= ", ".length()) {
s_attrs.delete(s_attrs.length() - 3, s_attrs.length());
res.append(", attrs=[").append(s_attrs);
}
// Children
if (children != null) {
for (final Element child : children) {
s_children.append(child.name);
s_children.append(", ");
}
}
if (s_children.length() >= ", ".length()) {
s_children.delete(s_children.length() - 3, s_children.length());
res.append("], children=[").append(s_children);
}
res.append("], path=").append(getElementPath()).append("}");
return res.toString();
}
/**
* This will format the tree as an XML string, which can be printed. The
* XML code is nicely indented.
*
* @return This element sub-tree represented as an XML string
*/
public String toXMLString() {
final StringBuffer s = new StringBuffer();
toXMLString(0, s);
return s.toString();
}
public String toJSONString(){
final StringBuffer s = new StringBuffer();
toJSONString(true,false,false,0, s);
s.append("\n}");
return s.toString();
}
private void toJSONString(boolean first,boolean isEnd,boolean isArray,int indent, StringBuffer s){
final boolean flag = hasChildren();
boolean isArrayChild=false;
if(flag){
String name =null;
for (final Element child : children) {
if(name!=null&&name.equals(child.qualifiedName())){
isArrayChild =true;
break;
}
name =child.qualifiedName();
}
}
final String qName = qualifiedName();
s.append(getIndentationSpacing(true, indent));
if(first){
if(isArray){
s.append("[");
}else{
s.append("{");}
}
if(!isArray)
{
s.append("\n").append(getIndentationSpacing(true, indent)).append("\"").append(qName).append("\":");
}
// add xmlns attributes (prefixes)
// if (prefixes != null) {
// for (final Prefix p : prefixes) {
// s.append(" ").append(p.toXMLString());
// }
// }
// // add attributes
// if (attrs != null) {
// for (final Attribute attr : attrs) {
// s.append(" ").append(attr.toXMLString(this));
// }
// }
indent++;
// add children elements if any
if (flag) {
// s.append("").append(("\n"));
int index=0;
int endIndex =children.size()-1;
for (final Element child : children) {
child.toJSONString(index==0,index==endIndex,isArrayChild,indent, s);
if(index!=endIndex)
s.append(",");
s.append("\n");//.append(getIndentationSpacing(true, indent));
index++;
}
} else { // add value if any
if (value != null) {
s.append(("\""));
final String stringValue = value.toString().replaceAll("&",
"&");
s.append(getIndentationSpacing(false, indent));
s.append(stringValue).append(("\""));
} else {
// self-closing tag
s.append("null").append(("}"));
return;
}
}
if(flag){
if(isArrayChild ){
s.append(getIndentationSpacing(flag, indent)).append("]");
}else{
s.append(getIndentationSpacing(flag, indent)).append("}");
}
}
indent--;
}
private void toXMLString(int indent, StringBuffer s) {
final boolean flag = hasChildren();
final String qName = qualifiedName();
s.append(new String(new char[indent * 2]).replace("\0", " "));
s.append("<").append(qName);
// add xmlns attributes (prefixes)
if (prefixes != null) {
for (final Prefix p : prefixes) {
s.append(" ").append(p.toXMLString());
}
}
// add attributes
if (attrs != null) {
for (final Attribute attr : attrs) {
s.append(" ").append(attr.toXMLString(this));
}
}
indent++;
// add children elements if any
if (flag) {
s.append(">").append(("\n"));
for (final Element child : children) {
child.toXMLString(indent, s);
}
} else { // add value if any
if (value != null) {
s.append(">").append((""));
final String stringValue = value.toString().replaceAll("&",
"&");
s.append(getIndentationSpacing(false, indent));
s.append(stringValue).append((""));
} else {
// self-closing tag
s.append("/>").append((""));
return;
}
}
indent--;
s.append(getIndentationSpacing(flag, indent)).append("").append(qName).append(">\n");
}
/**
* Gets indentation spacing for any given indent level.
*
* @param shouldIndent Whether or not there should be any indentation.
* @param indent The indentation level.
* @return A string with indent * 2 number of spaces if shouldIndent is
* true
; otherwise an empty string.
*/
private String getIndentationSpacing(boolean shouldIndent, int indent) {
if (shouldIndent) {
return new String(new char[indent * 2]).replace("\0", " ");
}
return "";
}
/**
* Encode to XML and send it to the provided stream. Similar to the
* toXMLString(), but without the pretty printing.
*
* Equivalent to calling encode(out, true
, null
);
*
* @param out Stream to send the encoded version of this element to.
* @throws JNCException If a YangElement encode implementation fails
*/
protected void encode(Transport out) throws JNCException {
encode(out, true, null);
}
/**
* Encode to XML and send it to the provided stream. Similar to the
* toXMLString(), but without the pretty printing.
*
* Equivalent to calling encode(out, true
, c);
*
* @param out Stream to send the encoded version of this element to.
* @param c Capabilities, used by YangElement instances.
* @throws JNCException If a YangElement encode implementation fails.
*/
protected void encode(Transport out, Capabilities c) throws JNCException {
encode(out, true, c);
}
/**
* Encode to XML and send it to the provided stream. Similar to the
* toXMLString(), but without the pretty printing.
*
* The newline_at_end argument controls whether a newline char is permitted
* at the end or not.
*
* Equivalent to calling encode(out, newline_at_end, null
);
*
* @param out Stream to send the encoded version of this element to.
* @param newline_at_end If 'true' a newline is printed at the end.
* @throws JNCException If a YangElement encode implementation fails.
*/
protected void encode(Transport out, boolean newline_at_end)
throws JNCException {
encode(out, newline_at_end, null);
}
/**
* Encode to XML and send it to the provided stream. Similar to the
* toXMLString(), but without the pretty printing.
*
* The newline_at_end argument controls whether a newline char is permitted
* at the end or not.
*
* @param out Stream to send the encoded version of this element to.
* @param newline_at_end If 'true' a newline is printed at the end.
* @param c Capabilities, used by YangElement instances.
* @throws JNCException If a YangElement encode implementation fails.
*/
protected void encode(Transport out, boolean newline_at_end,
Capabilities capas) throws JNCException {
final String qName = qualifiedName();
out.print("<" + qName);
// add xmlns attributes (prefixes)
if (prefixes != null) {
for (final Prefix p : prefixes) {
out.print(" ");
p.encode(out);
}
}
// add attributes
if (attrs != null) {
for (final Attribute attr : attrs) {
out.print(" ");
attr.encode(out, this);
}
}
if (hasChildren()) {
// add children elements if any
out.println(">");
for (final Element child : children) {
child.encode(out, true, capas);
}
} else if (value != null) {
// otherwise, add value (if any)
out.print(">" + Utils.escapeXml(value.toString()));
} else {
// self-closing tag
out.print("/>" + (newline_at_end ? "\n" : ""));
return;
}
out.print("" + qName + ">" + (newline_at_end ? "\n" : ""));
}
/**
* Return the full tagpath for this Element
*
* @return Absolute tagpath to this node
*/
public Tagpath tagpath() {
Element e = this;
int ix = 0;
while (e != null) {
e = e.parent;
ix++;
}
final Tagpath tp = new Tagpath(ix);
e = this;
while (e != null) {
tp.p[--ix] = e.name;
e = e.parent;
}
return tp;
}
/**
* Return an iterator for the children of this node.
*
* @return A new iterator over this element's children
*/
public ElementChildrenIterator iterator() {
return new ElementChildrenIterator(children);
}
/**
* Return an iterator for the children of a specified name of this node.
*
* Example usage:
*
*
* ElementChildrenIterator hostIter = config.iterator("host");
* while (hostIter.hasNext()) {
* Element host = hostIter.next();
* System.out.println("Host: " + host);
* }
*
*
* @param name A filter name, return only children with this name.
* @return A new iterator over this element's children with specified name
*/
public ElementChildrenIterator iterator(String name) {
return new ElementChildrenIterator(children, name);
}
/**
* Write this configuration tree to a file. The configuration tree is
* written as XML text.
*
* @param filename File name.
* @see #readFile(String)
*/
public void writeFile(String filename) throws IOException {
final File file = new File(filename);
final FileOutputStream fos = new FileOutputStream(file);
final DataOutputStream dos = new DataOutputStream(fos);
dos.writeBytes(toXMLString());
dos.close();
}
/**
* Read file with XML text and parse it into a configuration tree.
*
* @param filename File name.
* @see #writeFile(String)
*/
public static Element readFile(String filename) throws JNCException {
final XMLParser p = new XMLParser();
return p.readFile(filename);
}
/**
*
* @param xmlContent
* @return
* @throws JNCException
*/
public static Element readXml(String xmlContent) throws JNCException {
final XMLParser p = new XMLParser();
return p.parse(new InputSource(new ByteArrayInputStream(xmlContent.getBytes())));
}
/* help functions */
/**
* Sets the debug level. 0 - no debug 1 - Element level: Element, Attribute
* 2 - Session level: 3 - Parser level: Path, PathCreate, LocationStep,
* Expr 4 - Other: Prefix, PrefixMap
*/
static final int DEBUG_LEVEL_ELEMENT = 1;
static final int DEBUG_LEVEL_ATTRIBUTE = 1;
static final int DEBUG_LEVEL_SESSION = 2;
static final int DEBUG_LEVEL_TRANSPORT = 2;
static final int DEBUG_LEVEL_PATH = 3;
static final int DEBUG_LEVEL_PATHCREATE = 3;
static final int DEBUG_LEVEL_EXPR = 3;
static final int DEBUG_LEVEL_LOCATIONSTEP = 3;
static final int DEBUG_LEVEL_PARSER = 3;
static final int DEBUG_LEVEL_PREFIX = 4;
static final int DEBUG_LEVEL_PREFIXMAP = 4;
public static void setDebugLevel(int level) {
debugLevel = level;
}
static int debugLevel = 0;
/**
* Printout trace if 'debug'-flag is enabled.
*/
private static void trace(String s) {
if (debugLevel >= DEBUG_LEVEL_ELEMENT) {
System.err.println("*Element: " + s);
}
}
}