/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 *      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 org.eclipse.webdav.dom;

import java.util.Enumeration;
import java.util.Vector;
import org.eclipse.webdav.Policy;
import org.eclipse.webdav.internal.utils.URLDecoder;
import org.eclipse.webdav.internal.utils.URLEncoder;
import org.w3c.dom.*;
import org.w3c.dom.CharacterData;

/**
 * Abstract superclass of all element editors.  An element editor is a
 * convenience for manipulating DOM elements.  Specific subclasses declared
 * in this package correspond to the major DOM element types for the WebDAV
 * protocol; e.g., the class ResponseBody corresponds to
 * <DAV:response>.
 *

 * Element editors contain no state of their own; all they they hang onto
 * is a single DOM Element; all information is stored in the DOM
 * itself.  Thus element editors are really just lightweight, throw-away
 * wrappers.

 *

 * Element editors assume that they are working with elements that are "up to
 * spec".  When an element editor encounters an element that is not how it
 * should be, it throws a MalformedElementException (a checked
 * exception).  The editor does not provide any further diagnosis of what's
 * wrong.  Clients catching this exception will generally conclude that they're
 * talking to a non-compliant (or malfunctioning) WebDAV server.

 *

 * Fully-capable element editors can be written using the DOM API.  No access
 * is required to any concrete implementation of the DOM.  The element editors
 * in this package all have this property.

 *

 * This class also provides rudimentary XML namespace support and element
 * navigation and manipulation methods that are useful when writing element
 * editors.

 *

 * Note: This class/interface is part of an interim API that is still under
 * development and expected to change significantly before reaching stability.
 * It is being made available at this early stage to solicit feedback from pioneering
 * adopters on the understanding that any code that uses this API will almost
 * certainly be broken (repeatedly) as the API evolves.
 *

 */
public abstract class ElementEditor {

    // The root element upon which this editor is based.
    // This field is never null.
    // However, it cannot be assumed that this element conforms
    // to the appropriate specification (the DOM can be changed
    // from underneath its editor).
    protected Element root;

    // The official URI for WebDAV namespace.
    protected static final String DAV_NS = "DAV:"; //$NON-NLS-1$

    // The official prefix for XML namespace declarations,
    // namely "xmlns" (note: no colon).
    protected static final String XML_PREFIX = "xmlns"; //$NON-NLS-1$

    // The official XML namespace prefix.
    protected static final String XML_NS_PREFIX = "xml"; //$NON-NLS-1$

    // The official XML namespace name.
    protected static final String XML_NS_NAME = "http://www.w3.org/XML/1998/namespace"; //$NON-NLS-1$

    /**
     * Constructs a new element editor for the given element. The element
     * must not be null.
     *
     * @param root the element to be edited
     * @throws        MalformedElementException if the element is not null
     */
    protected ElementEditor(Element root) throws MalformedElementException {
        Assert.isNotNull(root);
        this.root = root;
    }

    /**
     * Constructs a new element editor for the given element. The element
     * must not be null.
     *
     * @param root the element to be edited
     * @throws        MalformedElementException if the element is not null
     */
    protected ElementEditor(Element root, String expectedType) throws MalformedElementException {
        Assert.isNotNull(root);
        Assert.isNotNull(expectedType);
        this.root = root;
        ensureDAVElement(root, expectedType, Policy.bind("ensure.expectingAnElmt", expectedType)); //$NON-NLS-1$
    }

    /**
     *
Creates a WebDAV element with the given name and adds it as a
     * child of the given parent. Returns the child element.
     *
Children are positioned in the order specified by the given names.
     * If children with the same name as the child already exist, the child
     * is inserted after the rightmost child.  If firstToLast is true, the
     * search for the child's position starts at the parent's first child,
     * otherwise, the search starts at the parent's last child.
     *
The parent must not be null and must be a WebDAV
     * element.  The child's name must not be null.  The
     * parent's valid child names must not be null, and must
     * contain the name of the child.
     *
     * @param parent      the parent to which the child is added
     * @param name        the name of the child which is created and added
     *                    to the parent
     * @param names       the ordered collection of valid child names for
     *                    the parent
     * @param firstToLast a boolean specifying the direction to search for
     *                    the child's position among the parent's children
     * @return            the child element that is created
     */
    public static Element addChild(Element parent, String name, String[] names, boolean firstToLast) {
        Assert.isTrue(isDAVElement(parent));
        Assert.isNotNull(name);
        Assert.isNotNull(names);
        String nsPrefix = getNSPrefix(parent);
        String tagName = nsPrefix == null ? name : nsPrefix + ":" + name; //$NON-NLS-1$
        Element child = parent.getOwnerDocument().createElement(tagName);
        addChild(parent, child, names, firstToLast);
        return child;
    }

    /**
     *
Creates a WebDAV element with the given name and adds it as a
     * child of the given parent. In addition, a text node created from the
     * given data is created and appended to the child.  Returns the child
     * element.
     *
Children are positioned in the order specified by the given names.
     * If children with the same name as the child already exist, the child
     * is inserted after the rightmost child.  If firstToLast is true, the
     * search for the child's position starts at the parent's first child,
     * otherwise, the search starts at the parent's last child.
     *
The parent must not be null and must be a WebDAV
     * element.  The child's name and data must not be null.
     * The parent's valid child names must not be null, and
     * must contain the name of the child.
     *
     * @param parent      the parent to which the child is added
     * @param name        the name of the child which is created and added
     *                    to the parent
     * @param data        the data of the text node which is created and
     *                    added to the child
     * @param names       the ordered collection of valid child names for
     *                    the parent
     * @param firstToLast a boolean specifying the direction to search for
     *                    the child's position among the parent's children
     * @return            the child element that is created
     */
    public static Element addChild(Element parent, String name, String data, String[] names, boolean firstToLast) {
        Assert.isTrue(isDAVElement(parent));
        Assert.isNotNull(name);
        Assert.isNotNull(data);
        Assert.isNotNull(names);
        Element child = addChild(parent, name, names, firstToLast);
        child.appendChild(child.getOwnerDocument().createTextNode(data));
        return child;
    }

    /**
     * Adds the given child element as a child of the given parent.
     *

     * Children are positioned in the order specified by the given names.
     * If children with the same name as the child already exist, the child
     * is inserted after the rightmost child.  If firstToLast is true, the
     * search for the child's position starts at the parent's first child,
     * otherwise, the search starts at the parent's last child.

     *

     * The parent must not be null and must be a WebDAV
     * element. The child must not be null and its namespace prefix must
     * resolve to the WebDAV namespace URL in the parent. The parent's valid
     * child names must not be null, and must contain the name
     * of the child.

     *
     * @param parent      the parent to which the child is added
     * @param child       the child which is added to the parent
     * @param names       the ordered collection of valid child names for
     *                    the parent
     * @param firstToLast a boolean specifying the direction to search for
     *                    the child's position among the parent's children
     */
    public static void addChild(Element parent, Element child, String[] names, boolean firstToLast) {

        Assert.isTrue(isDAVElement(parent));
        Assert.isNotNull(child);
        Assert.isTrue(DAV_NS.equals(resolve(getNSPrefix(child), parent)));
        Assert.isNotNull(names);

        boolean found = false;
        String name = getNSLocalName(child);
        for (int i = 0; !found && i < names.length; ++i)
            found = names[i].equals(name);
        Assert.isTrue(found);

        Node sibling = getChild(parent, name, names, firstToLast);

        if (firstToLast) {
            if (sibling == null)
                parent.appendChild(child);
            else {
                Node lastTwin = null;
                while (isDAVElement(sibling, name)) {
                    lastTwin = sibling;
                    sibling = getTwin((Element) sibling, firstToLast);
                }
                if (lastTwin == null)
                    parent.insertBefore(child, sibling);
                else {
                    Node refChild = lastTwin.getNextSibling();
                    if (refChild == null)
                        parent.appendChild(child);
                    else
                        parent.insertBefore(child, refChild);
                }
            }
        } else {
            Node refChild = null;
            if (sibling == null) {
                refChild = parent.getFirstChild();
            } else {
                refChild = sibling.getNextSibling();
            }
            if (refChild == null) {
                parent.appendChild(child);
            } else {
                parent.insertBefore(child, refChild);
            }
        }
    }

    /**
     * Creates a WebDAV element with the given name and appends it as a
     * child of the given parent. Returns the child element. The parent must
     * not be null and must be a WebDAV element. The name of
     * the child must not be null.
     *
     * @param parent the parent element to which the child is added
     * @param name   the name of the child element that is created and added
     *               as a child of the parent
     * @return       the child element that is created
     */
    public static Element appendChild(Element parent, String name) {
        Assert.isTrue(isDAVElement(parent));
        Assert.isNotNull(name);
        String nsPrefix = getNSPrefix(parent);
        String tagName = nsPrefix == null ? name : nsPrefix + ":" + name; //$NON-NLS-1$
        Element child = parent.getOwnerDocument().createElement(tagName);
        parent.appendChild(child);
        return child;
    }

    /**
     * Creates a WebDAV element with the given name and appends it as a
     * child of the given parent. In addition, a text node created from the
     * given data is created and appended to the child. Returns the child
     * element. The parent must not be null and must be a
     * WebDAV element. The name of the child must not be null.
     * The data must not be null.
     *
     * @param parent the parent element to which the child is added
     * @param name   the name of the child element that is created and added
     *               as a child of the parent
     * @param data   the data of the text node which is created and added to
     *               the child
     * @return       the child element that is created
     */
    public static Element appendChild(Element parent, String name, String data) {
        Assert.isTrue(isDAVElement(parent));
        Assert.isNotNull(name);
        Assert.isNotNull(data);
        Element child = appendChild(parent, name);
        child.appendChild(child.getOwnerDocument().createTextNode(data));
        return child;
    }

    /**
     * Returns a clone of the given node.  The given document becomes the
     * owner document of the clone.
     *
     * @param document the owner document of the clone
     * @param node     the node to clone
     * @return         a clone of the given node
     */
    public static Node cloneNode(Document document, Node node) {

        Node nodeClone = null;

        switch (node.getNodeType()) {
            case Node.ELEMENT_NODE :
                {
                    nodeClone = document.createElement(((Element) node).getTagName());
                    NamedNodeMap namedNodeMap = node.getAttributes();
                    for (int i = 0; i < namedNodeMap.getLength(); ++i) {
                        Attr attr = (Attr) namedNodeMap.item(i);
                        Attr attrClone = document.createAttribute(attr.getName());
                        attrClone.setValue(attr.getValue());
                        ((Element) nodeClone).setAttributeNode(attrClone);
                    }
                }
                break;
            case Node.TEXT_NODE :
                nodeClone = document.createTextNode(((CharacterData) node).getData());
                break;
            case Node.CDATA_SECTION_NODE :
                nodeClone = document.createCDATASection(((CharacterData) node).getData());
                break;
            case Node.ENTITY_REFERENCE_NODE :
                nodeClone = document.createEntityReference(node.getNodeName());
                break;
            case Node.PROCESSING_INSTRUCTION_NODE :
                nodeClone = document.createProcessingInstruction(((ProcessingInstruction) node).getTarget(), ((ProcessingInstruction) node).getData());
                break;
            case Node.COMMENT_NODE :
                nodeClone = document.createComment(((CharacterData) node).getData());
                break;
            case Node.DOCUMENT_FRAGMENT_NODE :
                nodeClone = document.createDocumentFragment();
                break;
            case Node.DOCUMENT_NODE :
            case Node.DOCUMENT_TYPE_NODE :
            case Node.NOTATION_NODE :
            case Node.ATTRIBUTE_NODE :
            case Node.ENTITY_NODE :
                Assert.isTrue(false, Policy.bind("assert.notSupported")); //$NON-NLS-1$
                break;
            default :
                Assert.isTrue(false, Policy.bind("assert.unknownNodeType")); //$NON-NLS-1$
        }

        return nodeClone;
    }

    /**
     * Creates a WebDAV element with the given name and adds it as the root
     * of the given document. In addition, the WebDAV namespace is declared
     * on the new element. Returns the new element. The document must not be
     * null and must not already have a root element. The name
     * of the element to be created must not be null.
     *
     * @param document the document in which the new element is rooted
     * @param name     the name of the element to be created
     * @return         the rooted element
     */
    public static Element create(Document document, String name) {
        Assert.isNotNull(document);
        Assert.isTrue(document.getDocumentElement() == null);
        Assert.isNotNull(name);
        Element element = document.createElement(name);
        declareNS(element, null, DAV_NS);
        document.appendChild(element);
        return element;
    }

    /**
     * Adds a namespace declaration to the given element. If only the prefix
     * is null, a default namespace is declared. If the prefix
     * and the namespaceUrl are null the default namespace is
     * removed. The element must not be null. If the
     * namespaceUrl is null, the prefix must also
     * be null.
     *
     * @param element      the element to receive the namespace attribute
     * @param prefix       the prefix to be used (without a colon); for
     *                     example, "D", or null
     * @param namespaceUrl the URL of the namespace; for example, "DAV:", or
     *                     null
     */
    public static void declareNS(Element element, String prefix, String namespaceUrl) {
        Assert.isNotNull(element);
        Assert.isTrue(namespaceUrl != null || prefix == null && namespaceUrl == null);
        String name = XML_PREFIX + (prefix == null ? "" : ":" + prefix); //$NON-NLS-1$ //$NON-NLS-2$
        String value = namespaceUrl == null ? "" : namespaceUrl; //$NON-NLS-1$
        element.setAttribute(name, value);
    }

    /**
     * Decodes the given href from a form that is safe for transport.
     *
     * @param href the href to be decoded
     */
    public static String decodeHref(String href) {
        return URLDecoder.decode(href);
    }

    /**
     * Encodes the given href to a form that is safe for transport.
     *
     * @param href the href to be encoded
     */
    public static String encodeHref(String href) {
        return URLEncoder.encode(href);
    }

    /**
     * Ensures that the given value is true.
     *
     * @param message the message for the exception that is thrown if the
     *                given value is false
     * @param value   a boolean indicating whether or not a malformed
     *                element exception should be thrown
     * @throws        MalformedElementException with the given message if
     *                the given value is false
     * @deprecated
     */
    protected static void ensure(String message, boolean value) throws MalformedElementException {
        if (!value)
            throw new MalformedElementException(message);
    }

    /**
     * Ensures that the given value is true.
     *
     * @param message the message for the exception that is thrown if the
     *                given value is false
     * @param value   a boolean indicating whether or not a malformed
     *                element exception should be thrown
     * @throws        MalformedElementException with the given message if
     *                the given value is false
     */
    protected static void ensure(boolean value, String message) throws MalformedElementException {
        if (!value)
            throw new MalformedElementException(message);
    }

    /**
     * Ensures that the given node is a WebDAV element with the given name,
     * returning it as an Element if it is.
     *
     * @param message the message for the exception that is thrown if the
     *                node is not a WebDAV element with the given name
     * @param node    the node, or null
     * @param name    the expected local name of the WebDAV element
     * @returns       the node as an Element
     * @throws        MalformedElementException with the given message if it
     *                is not a WebDAV element with the given name
     * @deprecated
     */
    protected static Element ensureDAVElement(String message, Node node, String name) throws MalformedElementException {
        Assert.isNotNull(name);
        if (node == null || node.getNodeType() != Node.ELEMENT_NODE)
            throw new MalformedElementException(message);
        Element element = (Element) node;
        if (!name.equals(getNSLocalName(element)) || !DAV_NS.equals(getNSName(element)))
            throw new MalformedElementException(message);
        return element;
    }

    /**
     * Ensures that the given node is a WebDAV element with the given name.
     *
     * @param node    the node, or null
     * @param name    the expected local name of the WebDAV element
     * @param message the message for the exception that is thrown if the
     *                node is not a WebDAV element with the given name
     * @throws        MalformedElementException with the given message if it
     *                is not a WebDAV element with the given name
     */
    protected static void ensureDAVElement(Node node, String name, String message) throws MalformedElementException {
        Assert.isNotNull(name);
        if (node == null || node.getNodeType() != Node.ELEMENT_NODE)
            throw new MalformedElementException(message);
        Element element = (Element) node;
        if (!name.equals(getNSLocalName(element)) || !DAV_NS.equals(getNSName(element)))
            throw new MalformedElementException(message);
    }

    /**
     * Ensures that the given object is not null.
     *
     * @param message the message for the exception that is thrown if the
     *                object is null
     * @param object  the object, or null
     * @throws        MalformedElementException if the given object is
     *                null
     */
    protected static void ensureNotNull(String message, Object object) throws MalformedElementException {
        if (object == null)
            throw new MalformedElementException(message);
    }

    /**
     * Ensures that the given object is null.
     *
     * @param message the message for the exception that is thrown if the
     *                object is not null
     * @param object  the object, or null
     * @throws        MalformedElementException if the given object is not
     *                null
     */
    protected static void ensureNull(String message, Object object) throws MalformedElementException {
        if (object != null)
            throw new MalformedElementException(message);
    }

    /**
     * Ensures that the given node is a text node, returning it as a
     * Text node if it is.
     *
     * @param message the message for the exception that is thrown if the
     *                node is not a Text node
     * @param node    the node, or null
     * @returns       the node as a Text node
     * @exception     MalformedElementException if the node is
     *                null or not a text node
     */
    protected static Text ensureText(String message, Node node) throws MalformedElementException {
        if (node == null || node.getNodeType() != Node.TEXT_NODE)
            throw new MalformedElementException(message);
        return (Text) node;
    }

    /**
     * Clones the given element, and its subtrees, and sets it as the root
     * of the given document. Returns the cloned element. The document must
     * not have a root and must not be null. The element must
     * not be null.
     *
     * @param document the document that will become the owner and parent
     *                 of the cloned element
     * @param element  the element to be cloned
     * @returns        a clone of the element
     * @throws         MalformedElementException if an element exists in the
     *                 subtree to be cloned whose namespace can't be
     *                 resolved
     */
    public static Element extractElement(Document document, Element element) throws MalformedElementException {
        Assert.isNotNull(document);
        Assert.isTrue(document.getDocumentElement() == null);
        Assert.isNotNull(element);
        return (Element) extractNode(document, element);
    }

    /**
     * Clones the given node, and its subtrees, and sets it as the root of
     * the given parent node. Returns the cloned node. The parent node and
     * the node to be cloned must not be null.
     *
     * @param parent  the node that will become the parent of the cloned
     *                node
     * @param node the node to be cloned
     * @returns       a clone of the element
     * @throws        MalformedElementException if an element exists in the
     *                subtree to be cloned whose namespace can't be resolved
     */
    public static Node extractNode(Node parent, Node node) throws MalformedElementException {

        // Get a handle to the root of the parent node tree.
        Document document;
        if (parent.getNodeType() == Node.DOCUMENT_NODE)
            document = (Document) parent;
        else
            document = parent.getOwnerDocument();

        // Create a clone of the node owned by the document, and add to the parent.
        Node nodeClone = cloneNode(document, node);
        parent.appendChild(nodeClone);

        // If the node is an Element
        if (node.getNodeType() == Node.ELEMENT_NODE) {

            // Figure out its namespace information.
            String nsPrefix = getNSPrefix((Element) node);
            String nsName = getNSName((Element) node);

            // Is this namespace already defined on the clone?  If not declare it.
            String nsNameClone = resolve(nsPrefix, (Element) nodeClone);
            if (nsName != nsNameClone && (nsName == null || !nsName.equals(nsNameClone)))
                declareNS((Element) nodeClone, nsPrefix, nsName);

            // Do the same namespace fix-up for each of the node's attributes.
            NamedNodeMap nodeMap = nodeClone.getAttributes();
            for (int i = 0; i < nodeMap.getLength(); ++i) {
                Attr attr = (Attr) nodeMap.item(i);
                nsPrefix = getNSPrefix(attr.getName());
                if (nsPrefix != null && !nsPrefix.equals(XML_PREFIX)) {
                    nsName = resolve(nsPrefix, (Element) node);
                    nsNameClone = resolve(nsPrefix, (Element) nodeClone);
                    if (nsName != nsNameClone && (nsName == null || !nsName.equals(nsNameClone)))
                        declareNS((Element) nodeClone, nsPrefix, nsName);
                }
            }
        }

        // Recursively clone each of the node's children.
        Node child = node.getFirstChild();
        while (child != null) {
            extractNode(nodeClone, child);
            child = child.getNextSibling();
        }

        // Finished cloning this node.
        return nodeClone;
    }

    /**
     * Returns the first child of the given parent that is a WebDAV element
     * with one of the given names, or null if no such child
     * exists.
     *

     * If firstToLast is true, the search for the child starts at the parent's
     * first child, otherwise, the search starts at the parent's last child.
     * The parent must not be null and must be a WebDAV element.
     * The names of children to search for must not be null.

     *
     * @param parent      the parent of the child to search.
     * @param names       all possible names of the child to search for.
     * @param firstToLast a boolean specifying the direction to search for
     *                    the child among the parent's children.
     * @return            the specified child of the parent, or
     *                    null if no such child exists.
     */
    private static Element getChild(Element parent, String[] names, boolean firstToLast) {

        Assert.isTrue(isDAVElement(parent));
        Assert.isNotNull(names);

        // Get the first candidate.
        Node child = null;
        if (firstToLast)
            child = parent.getFirstChild();
        else
            child = parent.getLastChild();

        // While there are children left to consider.
        while (child != null) {

            // See if the child name matches any being sought.
            for (int i = 0; i < names.length; ++i)
                if (isDAVElement(child, names[i]))
                    return (Element) child;

            // Try the next child.
            if (firstToLast)
                child = child.getNextSibling();
            else
                child = child.getPreviousSibling();
        }

        // A matching child was not found.
        return null;
    }

    /**
     *
Returns the child WebDAV element of the given parent that is
     * nearest in position to a WebDAV element with the given name, or
     * null if no such child exists.
     *
Children are expected to be in the order specified by the given
     * names. If firstToLast is true, the search for the child starts at the
     * parent's first child, otherwise, the search starts at the parent's
     * last child.
     *
The parent must not be null and must be a WebDAV
     * element.  The name of the child to search for must not be
     * null. The parent's valid child names must not be
     * null, and must contain the name of the child being
     * searched for.
     *
The returned child is as follows:
     *

     *
Searching first to last

     *

     *
returns null if an element with the given name
     *       should appear as the last child

     *
returns the first occurring child if an element with the given
     *       name is a child

     *
returns a child if an element with the given name would appear
     *       before it

     *

     *
Searching last to first

     *

     *
returns null if an element with the given name
     *               would appear as the first child

     *
returns the last occurring child if an element with the given
     *       name is a child

     *
returns a child if an element with the given name would appear
     *       after it

     *

     *

     * @param parent      the parent of the child being searched for
     * @param name        the name of the child being searched for
     * @param names       the ordered collection of valid child names for
     *                    the parent
     * @param firstToLast a boolean specifying the direction to search for
     *                    the specified child among the parent's children
     * @return            a child of the parent, or null
     */
    public static Element getChild(Element parent, String name, String[] names, boolean firstToLast) {
        Assert.isNotNull(parent);
        Assert.isNotNull(name);
        Assert.isNotNull(names);

        boolean found = false;
        for (int i = 0; !found && i < names.length; ++i) {
            found = names[i].equals(name);
        }
        Assert.isTrue(found);

        int i;
        Node child = null;

        if (firstToLast) {
            i = 0;
            child = parent.getFirstChild();
        } else {
            i = names.length - 1;
            child = parent.getLastChild();
        }

        while (child != null && !names[i].equals(name)) {
            int mark = i;
            while (!isDAVElement(child, names[i]) && !names[i].equals(name)) {
                if (firstToLast) {
                    ++i;
                } else {
                    --i;
                }
            }

            if (!names[i].equals(name)) {
                if (firstToLast) {
                    child = child.getNextSibling();
                } else {
                    child = child.getPreviousSibling();
                }
            } else if (!isDAVElement(child, names[i])) {
                int pos = i;
                found = false;
                while (!found && (pos >= 0 && pos < names.length)) {
                    found = isDAVElement(child, names[pos]);
                    if (firstToLast) {
                        ++pos;
                    } else {
                        --pos;
                    }
                }
                if (!found) {
                    i = mark;
                    if (firstToLast) {
                        child = child.getNextSibling();
                    } else {
                        child = child.getPreviousSibling();
                    }
                }
            }
        }

        return (Element) child;
    }

    /**
     * Returns the first child element of the given parent element, or
     * null if no such child exists. If firstToLast is true,
     * the search for the child starts at the parent's first child,
     * otherwise, the search starts at the parent's last child. The parent
     * must not be null.
     *
     * @param parent      the parent of the child element to search for
     * @param firstToLast a boolean specifying the direction to search for
     *                    the child element among the parent's children
     * @return            the child element, or null if no such
     *                    child exists
     */
    public static Element getChildElement(Element parent, boolean firstToLast) {
        Assert.isNotNull(parent);
        Node child = null;
        if (firstToLast)
            child = parent.getFirstChild();
        else
            child = parent.getLastChild();
        while (child != null && !isElement(child)) {
            if (firstToLast)
                child = child.getNextSibling();
            else
                child = child.getPreviousSibling();
        }
        return (Element) child;
    }

    /**
     * Returns the data of the first child text node of the first WebDAV
     * child with the given name of the given parent, or the empty
     * String if no such child text node exists, or
     * null if no such child exists. If firstToLast is true, the
     * search for the child starts at the parent's first child, otherwise,
     * the search starts at the parent's last child. The parent must not be
     * null and must be a WebDAV element. The name of the child
     * must not be null.
     *
     * @param parent      the parent of the child with the child text node
     *                    to search for
     * @param name        the name of the child with the child text node to
     *                    search for
     * @param firstToLast a boolean specifying the direction to search for
     *                    the specified child among the parent's children
     * @return            the data of the child's child text node, or the
     *                    empty String if no such child exists,
     *                    or null if no such child exists
     */
    public static String getChildText(Element parent, String name, boolean firstToLast) {
        Assert.isTrue(isDAVElement(parent));
        Assert.isNotNull(name);
        Element child;
        if (firstToLast)
            child = getFirstChild(parent, name);
        else
            child = getLastChild(parent, name);
        if (child != null)
            return getText(child, firstToLast);
        return null;
    }

    /**
     * Returns the first WebDAV child of the given parent, or
     * null if no such child exists. The parent must not be
     * null and must be a WebDAV element.
     *
     * @param parent the parent of the child to search for
     * @return the first WebDAV child of the parent, or null
     */
    public static Element getDAVChild(Element parent) {
        Assert.isTrue(isDAVElement(parent));
        Node child = parent.getFirstChild();
        while (child != null && !isDAVElement(child))
            child = child.getNextSibling();
        return (Element) child;
    }

    /**
     * Returns the root element upon which this editor is based. It cannot
     * be assumed that this element necessarily conforms to the appropriate
     * specification.
     *
     * @return the root element upon which this editor is based
     */
    public Element getElement() {
        return root;
    }

    /**
     * Returns the first child of the given parent that is a WebDAV element
     * with one of the given names, or null if no such child
     * exists.
     *

     * The search for the child starts at the parent's first child.
     * The parent must not be null and must be a WebDAV element.
     * The names of children to search for must not be null.

     *
     * @param parent      the parent of the child to search.
     * @param names       all possible names of the child to search for.
     * @return            the specified child of the parent, or
     *                    null if no such child exists.
     */
    public static Element getFirstChild(Element parent, String[] names) {
        return getChild(parent, names, true);
    }

    /**
     * Returns the first child of the given parent that is a WebDAV element
     * with the given name, or null if no such child exists.
     *

     * The search for the child starts at the parent's first child.
     * The parent must not be null and must be a DAV: namespace
     * element. The name of the child to search for must not be
     * null.
     *
     * @param parent      the parent of the child to search.
     * @param name        the name of the child to search for.
     * @return            the specified child of the parent, or null
     *                     if no such child exists.
     */
    public static Element getFirstChild(Element parent, String name) {
        Assert.isNotNull(name);
        return getChild(parent, new String[] {name}, true);
    }

    /**
     * Returns the data of the given parent's first text node, or the empty
     * String if no such text node exists.  The search for the
     * text node starts at the parent's first child. The parent must not be
     * null.
     *
     * @param parent      the parent of the text node to search for
     * @return            the data of the text node being searched for, or
     *                    the empty String if no such text node
     *                    exists
     */
    public static String getFirstText(Element parent) {
        Assert.isNotNull(parent);
        Node child = parent.getFirstChild();
        while (child != null && !isText(child))
            child = child.getNextSibling();
        if (child == null)
            return ""; //$NON-NLS-1$
        return ((Text) child).getData();
    }

    /**
     * Returns the last child of the given parent that is a WebDAV element
     * with the given name, or null if no such child exists.
     *

     * The search starts at the parent's last child.
     * The parent must not be null and must be a DAV: namespace
     * element. The name of the child to search for must not be
     * null.
     *
     * @param parent      the parent of the child to search.
     * @param name        the name of the child to search for.
     * @return            the specified child of the parent, or null
     *                     if no such child exists.
     */
    public static Element getLastChild(Element parent, String name) {
        Assert.isNotNull(name);
        return getChild(parent, new String[] {name}, false);
    }

    /**
     * Returns the given element's namespace declarations. The element must
     * not be null.
     *
     * @param element the element whose namespace declarations are returned
     * @return        the element's namespace declarations
     */
    public static Namespaces getNamespaces(Element element) {
        Assert.isNotNull(element);
        Node parent = element.getParentNode();
        while (parent != null && !isElement(parent))
            parent = parent.getParentNode();
        Namespaces namespaces = null;
        if (parent != null)
            namespaces = getNamespaces((Element) parent);
        return getNamespaces(element, namespaces, false);
    }

    /**
     * Returns the given element's namespace declarations. The given
     * namespace declarations should be the element's parent's, or
     * null if the element has no parent. If
     * removeDuplicateNSDeclarations is true, duplicate
     * namespace declarations are removed from the element's attributes. The
     * element must not be null.
     *
     * @param element    the element whose namespace declaration is returned
     * @param namespaces the namespace declarations for the element's
     *                   parent's or null if it has no parent
     * @param removeDuplicateNSDeclarations a boolean indicating whether
     *                   duplicate namespace declarations should be removed
     *                   from the element's attributes
     * @return           the given element's namespace declarations
     */
    protected static Namespaces getNamespaces(Element element, Namespaces namespaces, boolean removeDuplicateNSDeclarations) {

        // Create a container to hold the new namespace definitions.
        Namespaces newNamespaces = null;
        if (namespaces == null)
            newNamespaces = new Namespaces();
        else
            newNamespaces = new Namespaces(namespaces);

        Vector oldAttributes = new Vector();

        // For each attribute on the given element.
        NamedNodeMap nodeMap = element.getAttributes();
        for (int i = 0; i < nodeMap.getLength(); ++i) {
            Attr attr = (Attr) nodeMap.item(i);

            // Is it a name space declaration?
            String name = attr.getName();
            if (name.startsWith(XML_PREFIX)) {
                String nsName = attr.getValue();

                // Is it setting or clearing the default namespace?
                // (i.e. has no prefix part)
                if (name.length() == XML_PREFIX.length()) {
                    if (nsName.equals("")) //$NON-NLS-1$
                        newNamespaces.setDefaultNSName(null);
                    else
                        newNamespaces.setDefaultNSName(nsName);
                } else if (name.charAt(XML_PREFIX.length()) == ':') {
                    // It is a namespace declaration.
                    String nsPrefix = name.substring(XML_PREFIX.length() + 1);
                    if (nsPrefix.length() > 0 && nsName.length() > 0) {
                        // Ensure it is in the new namespaces list.
                        newNamespaces.putNSName(nsPrefix, nsName);
                        boolean prefixExists = newNamespaces.getNSPrefix(nsName) != null;
                        if (!prefixExists)
                            newNamespaces.putNSPrefix(nsName, nsPrefix);
                        // If it is due for removal, rememebr it in the oldAttributes list.
                        if (removeDuplicateNSDeclarations && (prefixExists || nsName.equals(newNamespaces.getDefaultNSName())))
                            oldAttributes.addElement(attr);
                    }
                }
            }
        }

        // Remove all the duplicates on the given element.
        Enumeration e = oldAttributes.elements();
        while (e.hasMoreElements())
            element.removeAttributeNode((Attr) e.nextElement());

        // Answer the new list of namespaces for this element.
        return newNamespaces;
    }

    /**
     * Returns the next sibling element of the given element, or
     * null if no such sibling exists. Only the sibling's
     * next children are searched. The element must not be null.
     *
     * @param element     the element whose sibling element is being
     *                    searched for
     * @return            the sibling element, or null
     */
    public static Element getNextSibling(Element element) {
        Assert.isNotNull(element);
        Node sibling = element;
        do {
            sibling = sibling.getNextSibling();
        } while (sibling != null && !isElement(sibling));
        return (Element) sibling;
    }

    /**
     * Returns the first WebDAV sibling of the given element that has one of
     * the given names, or null if no such sibling exists.  Only
     * the sibling's next children (not the previous children) are searched.
     * The element must not be null and must be a WebDAV element.
     * The possible names of the sibling to search for must not be
     * null.
     *
     * @param element     the element whose sibling is being searched for
     * @param names       the possible names of the sibling being search for
     * @return            the sibling with the specified name, or
     *                    null
     */
    public static Element getNextSibling(Element element, String[] names) {
        Assert.isTrue(isDAVElement(element));
        Assert.isNotNull(names);
        Node sibling = element.getNextSibling();
        while (sibling != null) {
            for (int i = 0; i < names.length; ++i)
                if (isDAVElement(sibling, names[i]))
                    return (Element) sibling;
            sibling = sibling.getNextSibling();
        }
        return null;
    }

    /**
     * Returns the next WebDAV sibling of the given element that has the
     * given name, or null if no such sibling exists.  Only
     * the sibling's next children are searched. The element must not be
     * null and must be a WebDAV element.  The name of the
     * sibling to search for must not be null.
     *
     * @param element     the element whose sibling is being searched for
     * @param name       the name of the sibling being search for
     * @return            the sibling with the given name, or
     *                    null
     */
    public static Element getNextSibling(Element element, String name) {
        return getNextSibling(element, new String[] {name});
    }

    /**
     * Returns the local part of the given name, or null if its
     * name has no local part. The name must not be null.
     *

     *
Example

     *

     *   name
     *   local name
     *

     *   D:foo
     *   foo
     *

     *   foo
     *   foo
     *

     *   D:
     *   null
     *

     *   :foo
     *   foo
     *

     *   :
     *   null
     *

     *
     * @param name the name whose local part is returned
     * @return     the name's local part, or null
     */
    public static String getNSLocalName(String name) {
        Assert.isNotNull(name);
        int p = name.lastIndexOf(':');
        if (p == -1)
            return name;
        if (p == name.length() - 1)
            return null;
        return name.substring(p + 1);
    }

    /**
     * Returns the local part of the name of the given element, or
     * null if its name has no local part. The element must not
     * be null.
     *

     *
Example

     *

     *   tag name
     *   local name
     *

     *   D:foo
     *   foo
     *

     *   foo
     *   foo
     *

     *   D:
     *   null
     *

     *   :foo
     *   foo
     *

     *   :
     *   null
     *

     *
     * @param element the element whose local name is returned
     * @return        the element's local name, or null
     */
    public static String getNSLocalName(Element element) {
        Assert.isNotNull(element);
        return getNSLocalName(element.getTagName());
    }

    /**
     * Returns the URL of the given element's namespace, or
     * null if it has no namespace. The element must not be
     * null.
     *
     * @param element the element whose namespace is returned
     * @return        the namespace of the element, or null
     * @throws        MalformedElementException if the name of the given
     *                element has a namespace prefix which could not be
     *                resolved
     */
    public static String getNSName(Element element) throws MalformedElementException {
        Assert.isNotNull(element);
        String nsPrefix = getNSPrefix(element);
        String nsName = resolve(nsPrefix, element);
        if (nsPrefix != null && nsName == null)
            throw new MalformedElementException(Policy.bind("exception.namespacePrefixNotResolved", nsPrefix)); //$NON-NLS-1$
        return nsName;
    }

    /**
     * Returns the namespace prefix part of the given name, or
     * null if its name has no prefix. The name must not be
     * null.
     *

     *
Example

     *

     *   name
     *   namespace prefix
     *

     *   D:foo
     *   D
     *

     *   foo
     *   null
     *

     *   D:
     *   D
     *

     *   :foo
     *   null
     *

     *   :
     *   null
     *

     *
     * @param name the name whose namespace prefix is returned
     * @return     the name's namespace prefix, or null
     */
    public static String getNSPrefix(String name) {
        Assert.isNotNull(name);
        int p = name.lastIndexOf(':');
        if (p <= 0)
            return null;
        return name.substring(0, p);
    }

    /**
     * Returns the namespace prefix part of the name of the given element,
     * or null if its name has no prefix. The element must not
     * be null.
     *

     *
Example

     *

     *   tag name
     *   namespace prefix
     *

     *   D:foo
     *   D
     *

     *   foo
     *   null
     *

     *   D:
     *   D
     *

     *   :foo
     *   null
     *

     *   :
     *   null
     *

     *
     * @param element the element whose namespace prefix is returned
     * @return        the element's namespace prefix, or null
     */
    public static String getNSPrefix(Element element) {
        Assert.isNotNull(element);
        return getNSPrefix(element.getTagName());
    }

    /**
     * Returns a qualified name that is formed from the given element's
     * namespace name and namespace local name. The qualified name's
     * qualifier is the element's namespace name and the qualified name's
     * local name is the element's local name. The element must not be
     * null.
     *
     * @param     element the element whose qualified name is returned
     * @return            the qualified name that is formed from the given
     *                    element's namespace name and namespace local name
     * @exception MalformedElementException if the name of the element has a
     *            namespace prefix which could not be resolved, or the
     *            element does not have a local name
     */
    public static QualifiedName getQualifiedName(Element element) throws MalformedElementException {
        Assert.isNotNull(element);
        String nsName = getNSName(element);
        String nsLocalName = getNSLocalName(element);
        if (nsLocalName == null)
            throw new MalformedElementException(Policy.bind("exception.noLocalNameForElmt")); //$NON-NLS-1$
        return new QualifiedNameImpl(nsName, nsLocalName);
    }

    /**
     * Returns the first WebDAV sibling of the given element that has the
     * given name, or null if no such sibling exists. If
     * firstToLast is true, only the sibling's next children are searched,
     * otherwise, only the siblings previous children are searched. The
     * element must not be null and must be a WebDAV element.
     * The name of the sibling to search for must not be null.
     *
     * @param element the element whose sibling is being searched for
     * @param name the name of the sibling being search for
     * @param firstToLast a boolean specifying the direction to search for
     *                    the sibling among the element's siblings
     * @return            the sibling with the given name, or
     *                    null
     * @deprecated
     */
    public static Element getSibling(Element element, String name, boolean firstToLast) {
        Assert.isTrue(isDAVElement(element));
        Assert.isNotNull(name);
        Node sibling = element;
        do {
            if (firstToLast)
                sibling = sibling.getNextSibling();
            else
                sibling = sibling.getPreviousSibling();
        } while (sibling != null && !isDAVElement(sibling, name));
        return (Element) sibling;
    }

    /**
     * Returns the data of the given parent's first text node, or the empty
     * String if no such text node exists. If firstToLast is
     * true, the search for the text node starts at the parent's first
     * child, otherwise, the search starts at the parent's last child. The
     * parent must not be null.
     *
     * @param parent      the parent of the text node to search for
     * @param firstToLast a boolean specifying the direction to search for
     *                    the text node among the parent's children
     * @return            the data of the text node being searched for, or
     *                    the empty String if no such text node
     *                    exists
     * @deprecated
     */
    public static String getText(Element parent, boolean firstToLast) {
        Assert.isNotNull(parent);
        Node child = null;
        if (firstToLast)
            child = parent.getFirstChild();
        else
            child = parent.getLastChild();
        while (child != null && !isText(child)) {
            if (firstToLast)
                child = child.getNextSibling();
            else
                child = child.getPreviousSibling();
        }
        if (child != null)
            return ((Text) child).getData();
        return ""; //$NON-NLS-1$
    }

    /**
     * Returns the first WebDAV sibling of the given element that has the
     * same name as the element, or null if no such sibling
     * exists. If firstToLast is true, only the sibling's next children are
     * searched, otherwise, only the siblings previous children are
     * searched. The element must not be null and must be a
     * WebDAV element.
     *
     * @param element     the element whose sibling is being searched for
     * @param firstToLast a boolean specifying the direction to search for
     *                    the sibling among the element's siblings
     * @return            the sibling with the same name as the given
     *                    element, or null if no such element
     *                    exists
     */
    public static Element getTwin(Element element, boolean firstToLast) {
        Assert.isTrue(isDAVElement(element));
        String name = getNSLocalName(element);
        return getSibling(element, name, firstToLast);
    }

    public static boolean hasChild(Element parent, QualifiedName childName) throws MalformedElementException {
        // Get the first candidate.
        Node child = parent.getFirstChild();
        // While there are children left to consider.
        while (child != null) {
            if (child instanceof Element) {
                QualifiedName name = getQualifiedName((Element) child);
                if (name.equals(childName))
                    return true;
            }
            // Try the next child.
            child = child.getNextSibling();
        }
        // A matching child was not found.
        return false;
    }

    /**
     * Creates a WebDAV element with the given name and inserts it before
     * the given sibling. Returns the new sibling. The sibling must not be
     * null and must be a WebDAV element. The name of the
     * new sibling must not be null.
     *
     * @param sibling the existing sibling element
     * @param name    the name of the new sibling element that is created
     *                and inserted before the existing sibling element
     * @return        the new sibling element that is created
     */
    public static Element insertBefore(Element sibling, String name) {
        Assert.isTrue(isDAVElement(sibling));
        Assert.isNotNull(name);
        String nsPrefix = getNSPrefix(sibling);
        String tagName = nsPrefix == null ? name : nsPrefix + ":" + name; //$NON-NLS-1$
        Element newSibling = sibling.getOwnerDocument().createElement(tagName);
        sibling.getParentNode().insertBefore(newSibling, sibling);
        return newSibling;
    }

    /**
     * Creates a WebDAV element with the given name and inserts it before
     * the given sibling. In addition, a text node created from the given
     * data and appended to the new sibling. Returns the new sibling. The
     * sibling must not be null and must be a WebDAV element.
     * The name of the new sibling must not be null. The data
     * must not be null.
     *
     * @param sibling the existing sibling element
     * @param name    the name of the sibling element that is created and
     *                inserted before the existing sibling element
     * @param data    the data of the text node which is created and
     *                appended to the new sibling
     * @return        the new sibling element that is created
     */
    public static Element insertBefore(Element sibling, String name, String data) {
        Assert.isTrue(isDAVElement(sibling));
        Assert.isNotNull(name);
        Assert.isNotNull(data);
        Element child = insertBefore(sibling, name);
        child.appendChild(child.getOwnerDocument().createTextNode(data));
        return child;
    }

    /**
     * Returns a boolean indicating whether or not the given node is a
     * WebDAV element. The node may be null in which case
     * false is returned.
     *
     * @param node a node, or null
     * @return     a boolean indicating whether or not the given node is a
     *             WebDAV element
     */
    public static boolean isDAVElement(Node node) {
        if (node == null || node.getNodeType() != Node.ELEMENT_NODE)
            return false;
        try {
            if (!DAV_NS.equals(getNSName((Element) node)))
                return false;
        } catch (MalformedElementException e) {
            return false;
        }
        return true;
    }

    /**
     * Returns a boolean indicating whether or not the given node is a
     * WebDAV element with the given name. The node may be null
     * in which case false is returned. The name must not be
     * null.
     *
     * @param node a node, or null
     * @return     a boolean indicating whether or not the given node is a
     *             WebDAV element with the given name
     */
    public static boolean isDAVElement(Node node, String name) {
        Assert.isNotNull(name);
        if (node == null || node.getNodeType() != Node.ELEMENT_NODE)
            return false;
        try {
            Element element = (Element) node;
            if (!name.equals(getNSLocalName(element)) || !DAV_NS.equals(getNSName(element))) {
                return false;
            }
        } catch (MalformedElementException e) {
            return false;
        }
        return true;
    }

    /**
     * Returns a boolean indicating whether or not the given node is an
     * element. The node may be null in which case
     * false is returned.
     *
     * @param node a node, or null
     * @return     a boolean indicating whether or not the given node is an
     *             element
     */
    public static boolean isElement(Node node) {
        if (node == null || node.getNodeType() != Node.ELEMENT_NODE)
            return false;
        return true;
    }

    /**
     * Returns a boolean indicating whether or not the given node is a text
     * node. The node may be null in which case
     * false is returned.
     *
     * @param node a node, or null
     * @return     a boolean indicating whether or not the given node is a
     *             text node
     */
    public static boolean isText(Node node) {
        return (node != null) && (node.getNodeType() == Node.TEXT_NODE);
    }

    public static void makeNSStandalone(Element element) {
        Assert.isTrue(false, Policy.bind("assert.notImplemented")); //$NON-NLS-1$
    }

    /**
     * Removes redundant namespace declarations from this element and all
     * its children to maximum depth. The element must not be
     * null.
     *
     * @param element the element whose namespace declarations are to be
     *                reduced
     * @return        the element, or a new element with an equivalent
     *                qualified name but whose tag name has changed
     * @throws        MalformedElementException if the name of the given
     *                element, or one of its attributes, has a namespace
     *                prefix which could not be resolved
     */
    public static Element reduceNS(Element element) throws MalformedElementException {
        return (Element) reduceNS(element, null);
    }

    /**
     * Removes redundant namespace declarations from the given node and all
     * its children to maximum depth. The node must not be
     * null.
     *
     * @param node the node whose namespace declarations are to be reduced
     * @return     the node, or a new node (element) with an equivalent
     *             qualified name but whose tag name has changed
     * @throws     MalformedElementException if the name of the given node
     *             (element), or one of its attributes, has a namespace
     *             prefix which could not be resolved
     */
    public static Node reduceNS(Node node, Namespaces parentNamespaces) throws MalformedElementException {
        Namespaces namespaces = parentNamespaces;

        if (node.getNodeType() == Node.ELEMENT_NODE) {
            Element element = (Element) node;
            namespaces = getNamespaces(element, parentNamespaces, false);

            String nsPrefix = getNSPrefix(element);
            String nsLocalName = getNSLocalName(element);

            if (nsPrefix != null) {
                String nsName = namespaces.getNSName(nsPrefix);
                ensureNotNull(Policy.bind("ensure.missingNamespaceForPrefix", nsPrefix), nsName); //$NON-NLS-1$

                String tagName = null;
                if (nsName.equals(namespaces.getDefaultNSName())) {
                    tagName = nsLocalName;
                } else {
                    tagName = namespaces.getNSPrefix(nsName) + ":" + nsLocalName; //$NON-NLS-1$
                }

                if (!tagName.equals(element.getTagName())) {
                    Document document = element.getOwnerDocument();
                    Element newElement = document.createElement(tagName);
                    NamedNodeMap nodeMap = element.getAttributes();
                    for (int i = 0; i < nodeMap.getLength(); ++i) {
                        Attr attr = (Attr) nodeMap.item(i);
                        newElement.setAttribute(attr.getName(), attr.getValue());
                    }
                    Node child = element.getFirstChild();
                    while (child != null) {
                        element.removeChild(child);
                        newElement.appendChild(child);
                        child = element.getFirstChild();
                    }
                    element.getParentNode().replaceChild(newElement, element);
                    element = newElement;
                }
            }

            Vector oldAttributes = new Vector();
            Vector newAttributes = new Vector();
            NamedNodeMap nodeMap = element.getAttributes();
            for (int i = 0; i < nodeMap.getLength(); ++i) {
                Attr attr = (Attr) nodeMap.item(i);
                String name = attr.getName();
                String value = attr.getValue();
                String newName = name;
                nsPrefix = getNSPrefix(name);
                nsLocalName = getNSLocalName(name);

                if (nsPrefix != null && !nsPrefix.equals(XML_PREFIX)) {
                    String nsName = namespaces.getNSName(nsPrefix);
                    ensureNotNull(Policy.bind("ensure.missingNamespaceForPrefix", nsPrefix), nsName); //$NON-NLS-1$
                    String newNSPrefix = namespaces.getNSPrefix(nsName);
                    if (!newNSPrefix.equals(nsPrefix)) {
                        newName = newNSPrefix + ":" + nsLocalName; //$NON-NLS-1$
                    }
                }

                boolean newAttribute = true;
                if (parentNamespaces != null) {
                    if (nsPrefix == null && XML_PREFIX.equals(nsLocalName)) {
                        if (value.equals(parentNamespaces.getDefaultNSName())) {
                            newAttribute = false;
                        }
                    }
                    if (nsPrefix != null && XML_PREFIX.equals(nsPrefix)) {
                        if (parentNamespaces.getNSPrefix(value) != null) {
                            newAttribute = false;
                        }
                    }
                }

                oldAttributes.addElement(attr);
                if (newAttribute) {
                    newAttributes.addElement(new String[] {newName, value});
                }
            }

            Enumeration oldAttrs = oldAttributes.elements();
            while (oldAttrs.hasMoreElements()) {
                element.removeAttributeNode((Attr) oldAttrs.nextElement());
            }

            Enumeration newAttrs = newAttributes.elements();
            while (newAttrs.hasMoreElements()) {
                String[] newAttr = (String[]) newAttrs.nextElement();
                element.setAttribute(newAttr[0], newAttr[1]);
            }

            node = element;
        }

        Node child = node.getFirstChild();
        while (child != null) {
            child = reduceNS(child, namespaces);
            child = child.getNextSibling();
        }

        return node;
    }

    /**
     * Resolves the given namespace prefix in the namespace of the given
     * element. If the given prefix is null, the default
     * namespace is resolved. Returns the URL of the namespace, or
     * null if the prefix could not be resolved.
     *
     * @param prefix  the namespace prefix to be resolved, or
     *                null for the default namespace
     * @param element the element supplying the namespace
     * @return        the URL of the namespace, or null if the
     *                prefix could not be resolved
     */
    public static String resolve(String prefix, Element element) {
        Assert.isNotNull(element);

        /* The prefix xml is by definition bound to the namespace name
         * XML_NS_NAME.
         */
        if (XML_NS_PREFIX.equals(prefix)) {
            return XML_NS_NAME;
        }

        /* Search from given element up parent chain to root (document)
         * looking for a XML namespace declaration (represented as
         * an element attribute with a name beginning in
         * XML_PREFIX ("xmlns")).
         */
        Node current = element;
        do {
            NamedNodeMap attrs = current.getAttributes();
            int n = attrs.getLength();
            for (int i = 0; i < n; i++) {
                Attr attr = (Attr) attrs.item(i);
                String name = attr.getName();
                if (name.startsWith(XML_PREFIX)) {
                    if (name.length() == XML_PREFIX.length()) {
                        // no prefix e.g., xmlns="foo:"
                        if (prefix == null) {
                            String nsName = attr.getValue();
                            if (nsName.equals("")) { //$NON-NLS-1$
                                return null;
                            }
                            return nsName;
                        }
                    } else {
                        if (prefix != null && name.equals(XML_PREFIX + ":" + prefix)) { //$NON-NLS-1$
                            return attr.getValue();
                        }
                    }
                }
            }
            do {
                current = current.getParentNode();
            } while (current != null && current.getNodeType() != Node.ELEMENT_NODE);
        } while (current != null);
        return null;
    }

    /**
     *
Creates a WebDAV element with the given name and sets it as a
     * child of the given parent. Returns the child element.
     *
Children are positioned in the order specified by the given names.
     * If a child with the same name as the child already exist, the child
     * is replaced. If firstToLast is true, the search for the child's
     * position starts at the parent's first child, otherwise, the search
     * starts at the parent's last child.
     *
The parent must not be null and must be a WebDAV
     * element.  The child's name must not be null.  The
     * parent's valid child names must not be null, and must
     * contain the name of the child.
     *
     * @param parent      the parent to which the child is added
     * @param name        the name of the child which is created and added
     *                    to the parent
     * @param names       the ordered collection of valid child names for
     *                    the parent
     * @param firstToLast a boolean specifying the direction to search for
     *                    the child's position among the parent's children
     * @return            the child element that is created
     */
    public static Element setChild(Element parent, String name, String[] names, boolean firstToLast) {
        Assert.isTrue(isDAVElement(parent));
        Assert.isNotNull(name);
        Assert.isNotNull(names);
        String nsPrefix = getNSPrefix(parent);
        String tagName = nsPrefix == null ? name : nsPrefix + ":" + name; //$NON-NLS-1$
        Element child = parent.getOwnerDocument().createElement(tagName);
        setChild(parent, child, names, firstToLast);
        return child;
    }

    /**
     *
Creates a WebDAV element with the given name and sets it as a
     * child of the given parent. In addition, a text node created from the
     * given data is created and appended to the child.  Returns the child
     * element.
     *
Children are positioned in the order specified by the given names.
     * If a child with the same name already exists, it is replaced. If
     * firstToLast is true, the search for the child's position starts at the
     * parent's first child, otherwise, the search starts at the parent's
     * last child.
     *
The parent must not be null and must be a WebDAV
     * element.  The child's name and data must not be null.
     * The parent's valid child names must not be null, and
     * must contain the name of the child.
     *
     * @param parent      the parent to which the child is added
     * @param name        the name of the child which is created and added
     *                    to the parent
     * @param data        the data of the text node which is created and
     *                    added to the child
     * @param names       the ordered collection of valid child names for
     *                    the parent
     * @param firstToLast a boolean specifying the direction to search for
     *                    the child's position among the parent's children
     * @return            the child element that is created
     */
    public static Element setChild(Element parent, String name, String data, String[] names, boolean firstToLast) {
        Assert.isTrue(isDAVElement(parent));
        Assert.isNotNull(name);
        Assert.isNotNull(data);
        Assert.isNotNull(names);
        Element child = setChild(parent, name, names, firstToLast);
        child.appendChild(parent.getOwnerDocument().createTextNode(data));
        return child;
    }

    /**
     *
Sets the given child element as a child of the given parent.
     *
Children are positioned in the order specified by the given names.
     * If a child with the same name already exists, it is replaced. If
     * firstToLast is true, the search for the child's position starts at
     * the parent's first child, otherwise, the search starts at the
     * parent's last child.
     *
The parent must not be null and must be a WebDAV
     * element. The child must not be null and its namespace prefix must
     * resolve to the WebDAV namespace URL in the parent. The parent's valid
     * child names must not be null, and must contain the name
     * of the child.
     *
     * @param parent      the parent to which the child is added
     * @param child       the child which is added to the parent
     * @param names       the ordered collection of valid child names for
     *                    the parent
     * @param firstToLast a boolean specifying the direction to search for
     *                    the child's position among the parent's children
     */
    public static void setChild(Element parent, Element child, String[] names, boolean firstToLast) {
        Assert.isTrue(isDAVElement(parent));
        Assert.isNotNull(child);
        Assert.isTrue(DAV_NS.equals(resolve(getNSPrefix(child), parent)));
        Assert.isNotNull(names);

        boolean found = false;
        String name = getNSLocalName(child);
        for (int i = 0; !found && i < names.length; ++i) {
            found = names[i].equals(name);
        }
        Assert.isTrue(found);

        Node sibling = getChild(parent, name, names, firstToLast);

        if (isDAVElement(sibling, name)) {
            parent.replaceChild(child, sibling);
        } else if (firstToLast) {
            if (sibling == null) {
                parent.appendChild(child);
            } else {
                parent.insertBefore(child, sibling);
            }
        } else {
            Node refChild = null;
            if (sibling == null) {
                refChild = parent.getFirstChild();
            } else {
                refChild = sibling.getNextSibling();
            }
            if (refChild == null) {
                parent.appendChild(child);
            } else {
                parent.insertBefore(child, refChild);
            }
        }
    }
}


/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 *      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 org.eclipse.webdav.http.client;

import java.io.*;
import org.eclipse.webdav.client.Policy;
import org.eclipse.webdav.internal.kernel.utils.Assert;

/**
 * A resettable InputStream.
 *

 * Note: This class/interface is part of an interim API that is still under
 * development and expected to change significantly before reaching stability.
 * It is being made available at this early stage to solicit feedback from pioneering
 * adopters on the understanding that any code that uses this API will almost
 * certainly be broken (repeatedly) as the API evolves.
 *

 */
public class RequestInputStream extends InputStream {
    private long length = -1;
    private long totalBytesRead = 0;
    private InputStream is = null;
    private File file = null;
    private FileOutputStream fos = null;
    private boolean deleteFile = false;

    /**
     * Creates a RequestInputStream on the given byte array.
     *
     * @param b the underlying byte array
     */
    public RequestInputStream(byte[] b) {
        length = b.length;
        is = new ByteArrayInputStream(b);
    }

    /**
     * Creates a RequestInputStream on the given
     * ByteArrayInputStream.
     *
     * @param bais the underlying input stream
     */
    public RequestInputStream(ByteArrayInputStream bais) {
        Assert.isNotNull(bais);
        length = bais.available();
        is = bais;
    }

    /**
     * Creates a RequestInputStream on the given file.
     *
     * @param file the underlying file
     * @exception IOException if there is a problem opening the file
     */
    public RequestInputStream(File file) throws IOException {
        Assert.isNotNull(file);
        length = file.length();
        is = new FileInputStream(file);
        this.file = file;
    }

    /**
     * Creates a RequestInputStream on the given
     * InputStream. The length of the stream is set to be the
     * given length. If the length of the stream is unknown, the given length
     * must be -1.
     *
Note that to enable reset on streams created using this constructor,
     * the streams content is written to a temporary file while the stream
     * is read. This results in a loss of performance, so use this
     * constructor as a last resort.
     *
     * @param is the underlying input stream
     * @param length the length of the stream, or -1 if the length is unknown
     * @exception IOException if there is a problem creating or opening the
     * temporary file
     */
    public RequestInputStream(InputStream is, long length) throws IOException {
        Assert.isNotNull(is);
        Assert.isTrue(length >= -1);
        this.length = length;
        this.is = is;
        file = File.createTempFile("ris", ".tmp"); //$NON-NLS-1$ //$NON-NLS-2$
        fos = new FileOutputStream(file);
        deleteFile = true;
    }

    /**
     * @see InputStream#close()
     */
    public void close() throws IOException {
        is.close();
        if (fos != null)
            fos.close();
        if (deleteFile)
            file.delete();
    }

    /**
     * Returns the length of the stream, or -1 if the length of the stream is
     * unknown. Note the length of the stream is always known once the stream
     * has been reset.
     *
     * @return the length of the stream
     */
    public long length() {
        return length;
    }

    /**
     * @see InputStream#read()
     */
    public int read() throws IOException {
        int b = is.read();

        if (b == -1) {
            if (length != -1 && totalBytesRead < length) {
                throw new IOException(Policy.bind("exception.unexpectedEndStream")); //$NON-NLS-1$
            }
        } else {
            ++totalBytesRead;
            if (fos != null) {
                fos.write(b);
            }
        }

        return b;
    }

    /**
     * @see InputStream#read(byte[], int, int)
     */
    public int read(byte b[], int off, int len) throws IOException {
        int bytesRead = is.read(b, off, len);
        if (bytesRead == -1) {
            if (length != -1 && totalBytesRead < length) {
                throw new IOException(Policy.bind("exception.unexpectedEndStream")); //$NON-NLS-1$
            }
        } else {
            totalBytesRead += bytesRead;
            if (fos != null) {
                fos.write(b, off, len);
            }
        }
        return bytesRead;
    }

    /**
     * Resets the stream to its beginning so it can be read again.
     *
     * @exception IOException if there is an I/O error
     */
    public void reset() throws IOException {
        if (file == null) {
            ((ByteArrayInputStream) is).reset();
        } else {
            if (fos != null) {
                while (skip(4096) > 0);
                fos.close();
                fos = null;
                if (length == -1) {
                    length = totalBytesRead;
                }
            }
            is.close();
            is = new FileInputStream(file);
        }

        totalBytesRead = 0;
    }
}


/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * 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 org.drools.eclipse.editors;

import java.util.HashMap;
import java.util.List;

import org.drools.eclipse.DroolsEclipsePlugin;
import org.drools.eclipse.editors.outline.RuleContentOutlinePage;
import org.drools.eclipse.editors.scanners.RuleEditorMessages;
import org.drools.eclipse.preferences.IDroolsConstants;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.ui.actions.ToggleBreakpointAction;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.eclipse.jface.text.source.projection.ProjectionSupport;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.ui.texteditor.TextOperationAction;
import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;

/**
 * Abstract text-based rule editor.
 */
public class AbstractRuleEditor extends TextEditor {

    protected RuleContentOutlinePage ruleContentOutline = null;

    protected Annotation[] oldAnnotations;
    protected ProjectionAnnotationModel annotationModel;
    protected DroolsPairMatcher bracketMatcher = new DroolsPairMatcher();

    public AbstractRuleEditor() {
        setSourceViewerConfiguration(createSourceViewerConfiguration());
        setDocumentProvider(createDocumentProvider());
        getPreferenceStore().setDefault(IDroolsConstants.DRL_EDITOR_MATCHING_BRACKETS, true);
        PreferenceConverter.setDefault(getPreferenceStore(), IDroolsConstants.DRL_EDITOR_MATCHING_BRACKETS_COLOR, new RGB(192, 192, 192));
    }

    protected SourceViewerConfiguration createSourceViewerConfiguration() {
        return new DRLSourceViewerConfig(this);
    }

    protected IDocumentProvider createDocumentProvider() {
        return new DRLDocumentProvider();
    }

    public void createPartControl(Composite parent) {
        super.createPartControl(parent);
        ProjectionViewer viewer = (ProjectionViewer) getSourceViewer();
        ProjectionSupport projectionSupport = new ProjectionSupport(viewer,
            getAnnotationAccess(), getSharedColors());
        projectionSupport.install();
        // turn projection mode on
        viewer.doOperation(ProjectionViewer.TOGGLE);
        annotationModel = viewer.getProjectionAnnotationModel();
    }

    protected ISourceViewer createSourceViewer(Composite parent,
            IVerticalRuler ruler, int styles) {
        ISourceViewer viewer = new ProjectionViewer(parent, ruler,
                getOverviewRuler(), isOverviewRulerVisible(), styles);
        // ensure decoration support has been created and configured.
        getSourceViewerDecorationSupport(viewer);
        return viewer;
    }

    protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) {
        support.setCharacterPairMatcher(bracketMatcher);
        support.setMatchingCharacterPainterPreferenceKeys(
            IDroolsConstants.DRL_EDITOR_MATCHING_BRACKETS,
            IDroolsConstants.DRL_EDITOR_MATCHING_BRACKETS_COLOR);
        super.configureSourceViewerDecorationSupport(support);
    }

    public void updateFoldingStructure(List<Position> positions) {
        if (annotationModel != null) {
            Annotation[] annotations = new Annotation[positions.size()];
            // this will hold the new annotations along
            // with their corresponding positions
            HashMap<Annotation, Position> newAnnotations = new HashMap<Annotation, Position>();
            for (int i = 0; i < positions.size(); i++) {
                ProjectionAnnotation annotation = new ProjectionAnnotation();
                newAnnotations.put(annotation, positions.get(i));
                annotations[i] = annotation;
            }
            annotationModel.modifyAnnotations(oldAnnotations, newAnnotations, null);
            oldAnnotations = annotations;
        }
    }

    /** For user triggered content assistance */
    protected void createActions() {
        super.createActions();

        IAction a = new TextOperationAction(RuleEditorMessages
                .getResourceBundle(), "ContentAssistProposal.", this,
                ISourceViewer.CONTENTASSIST_PROPOSALS);
        a
                .setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
        setAction("ContentAssistProposal", a);

        a = new TextOperationAction(
                RuleEditorMessages.getResourceBundle(),
                "ContentAssistTip.", this, ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION); //$NON-NLS-1$
        a
                .setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_CONTEXT_INFORMATION);
        setAction("ContentAssistTip", a);

        a = new ToggleBreakpointAction(getSite().getPart() , null, getVerticalRuler());
        setAction(ITextEditorActionConstants.RULER_DOUBLE_CLICK, a);

    }

    protected ContentOutlinePage getContentOutline() {
        if (ruleContentOutline == null) {
            ruleContentOutline = new RuleContentOutlinePage(this);
            ruleContentOutline.update();
        }
        return ruleContentOutline;
    }

    public String getContent() {
        return getSourceViewer().getDocument().get();
    }

    public IResource getResource() {
        if (getEditorInput() instanceof IFileEditorInput) {
            return ((IFileEditorInput) getEditorInput()).getFile();
        }
        return null;
    }

    public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) {
        if (adapter.equals(IContentOutlinePage.class)) {
            return getContentOutline();
        }
        return super.getAdapter(adapter);
    }

    public void doSave(IProgressMonitor monitor) {
        // invalidate cached parsed rules
        DroolsEclipsePlugin.getDefault().invalidateResource(getResource());
        // save
        super.doSave(monitor);
        // update outline view
        if (ruleContentOutline != null) {
            ruleContentOutline.update();
        }
    }

    public void dispose() {
        super.dispose();
        if (bracketMatcher != null) {
            bracketMatcher.dispose();
            bracketMatcher = null;
        }
    }
}


/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * 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 org.drools.eclipse.editors.rete.model;

import java.util.ArrayList;
import java.util.List;

import org.drools.core.spi.Constraint;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.swt.graphics.Color;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.TextPropertyDescriptor;

/**
 * Visual vertex representation for ReteGraph.
 *
 * Base impl for all rete vertices.
 *
 * Can be connected with another BaseVertex with a Connector.
 */
abstract public class GraphicalVertex extends ModelElement {

    private static final String          VERTEX_NAME             = "BaseVertex ";

    private static final String          WIDTH                   = "Width";

    private static final String          HEIGHT                  = "Height";

    private static IPropertyDescriptor[] descriptors;

    /** ID for the Height property value (used for by the corresponding property descriptor). */
    private static final String          HEIGHT_PROP             = VERTEX_NAME + "." + HEIGHT;

    /** ID for the Width property value (used for by the corresponding property descriptor). */
    private static final String          WIDTH_PROP              = VERTEX_NAME + "." + WIDTH;

    /** Property ID to use when the location of this shape is modified. */
    public static final String           LOCATION_PROP           = VERTEX_NAME + ".Location";

    /** Property ID to use then the size of this shape is modified. */
    public static final String           SIZE_PROP               = VERTEX_NAME + ".Size";

    /** Property ID to use when the list of outgoing connections is modified. */
    public static final String           SOURCE_CONNECTIONS_PROP = VERTEX_NAME + ".SourceConn";

    /** Property ID to use when the list of incoming connections is modified. */
    public static final String           TARGET_CONNECTIONS_PROP = VERTEX_NAME + ".TargetConn";

    /** ID for the X property value (used for by the corresponding property descriptor).  */
    private static final String          XPOS_PROP               = VERTEX_NAME + ".xPos";

    /** ID for the Y property value (used for by the corresponding property descriptor).  */
    private static final String          YPOS_PROP               = VERTEX_NAME + ".yPos";

    /*
     * Initializes the property descriptors array.
     * @see #getPropertyDescriptors()
     * @see #getPropertyValue(Object)
     * @see #setPropertyValue(Object, Object)
     */
    static {
        descriptors = new IPropertyDescriptor[]{new TextPropertyDescriptor( XPOS_PROP,
                                                                            "X" ), // id and description pair
            new TextPropertyDescriptor( YPOS_PROP,
                                        "Y" ), new TextPropertyDescriptor( WIDTH_PROP,
                                                                           WIDTH ), new TextPropertyDescriptor( HEIGHT_PROP,
                                                                                HEIGHT ),};
    } // static

    /** Location of this vertex. */
    private Point                        location                = new Point( 0,
                                                                              0 );
    /** Size of this vertex. */
    private final static Dimension       size                    = new Dimension( 16,
                                                                                16 );
    /** List of outgoing Connections. */
    private List<Connection>             sourceConnections       = new ArrayList<Connection>();
    /** List of incoming Connections. */
    private List<Connection>             targetConnections       = new ArrayList<Connection>();

    /**
     * HTML formatted representation of this node
     *
     * @return #getHtml
     */
    abstract public String getHtml();

    /**
     * Color used for filling vertex figure
     *
     * @return color
     */
    abstract public Color getFillColor();

    /**
     * Add an incoming or outgoing connection to this vertex.
     * @param conn a non-null connection instance
     * @throws IllegalArgumentException if the connection is null or has not distinct endpoints
     */
    public void addConnection(Connection conn) {
        if ( conn == null || conn.getSource() == conn.getTarget() ) {
            throw new IllegalArgumentException();
        }
        if ( conn.getSource() == this ) {
            sourceConnections.add( conn );
            firePropertyChange( SOURCE_CONNECTIONS_PROP,
                                null,
                                conn );
        } else if ( conn.getTarget() == this ) {
            targetConnections.add( conn );
            firePropertyChange( TARGET_CONNECTIONS_PROP,
                                null,
                                conn );
        }
    }

    /**
     * Return the Location of this vertex.
     *
     * @return a non-null copy of location instance
     */
    public Point getLocation() {
        return location.getCopy();
    }

    /**
     * Returns an array of IPropertyDescriptors for this vertex.
     */
    public IPropertyDescriptor[] getPropertyDescriptors() {
        return descriptors;
    }

    /**
     * Return the property value for the given propertyId, or null.
     */
    public Object getPropertyValue(Object propertyId) {
        if ( XPOS_PROP.equals( propertyId ) ) {
            return Integer.toString( location.x );
        }
        if ( YPOS_PROP.equals( propertyId ) ) {
            return Integer.toString( location.y );
        }
        if ( HEIGHT_PROP.equals( propertyId ) ) {
            return Integer.toString( size.height );
        }
        if ( WIDTH_PROP.equals( propertyId ) ) {
            return Integer.toString( size.width );
        }
        return null;
    }

    /**
     * Return the Size of this vertex.
     * @return a non-null copy of Dimension instance
     */
    public Dimension getSize() {
        return size.getCopy();
    }

    /**
     * Return a List of outgoing Connections.
     */
    public List<Connection> getSourceConnections() {
        return new ArrayList<Connection>( sourceConnections );
    }

    /**
     * Return a List of incoming Connections.
     */
    public List<Connection> getTargetConnections() {
        return new ArrayList<Connection>( targetConnections );
    }

    /**
     * Remove an incoming or outgoing connection from this vertex.
     *
     * @param conn a non-null connection instance
     * @throws IllegalArgumentException if the parameter is null
     */
    public void removeConnection(Connection conn) {
        if ( conn == null ) {
            throw new IllegalArgumentException();
        }
        if ( conn.getSource() == this ) {
            sourceConnections.remove( conn );
            firePropertyChange( SOURCE_CONNECTIONS_PROP,
                                null,
                                conn );
        } else if ( conn.getTarget() == this ) {
            targetConnections.remove( conn );
            firePropertyChange( TARGET_CONNECTIONS_PROP,
                                null,
                                conn );
        }
    }

    /**
     * Set the Location of this vertex.
     * @param newLocation a non-null Point instance
     * @throws IllegalArgumentException if the parameter is null
     */
    public void setLocation(Point newLocation) {
        if ( newLocation == null ) {
            throw new IllegalArgumentException();
        }
        location.setLocation( newLocation );
        firePropertyChange( LOCATION_PROP,
                            null,
                            location );
    }

    /**
     * Set the property value for the given property id.
     */
    public void setPropertyValue(Object propertyId,
                                 Object value) {
        if ( XPOS_PROP.equals( propertyId ) ) {
            int x = Integer.parseInt( (String) value );
            setLocation( new Point( x,
                                    location.y ) );
        } else if ( YPOS_PROP.equals( propertyId ) ) {
            int y = Integer.parseInt( (String) value );
            setLocation( new Point( location.x,
                                    y ) );
        } else if ( HEIGHT_PROP.equals( propertyId ) ) {
            int height = Integer.parseInt( (String) value );
            setSize( new Dimension( size.width,
                                    height ) );
        } else if ( WIDTH_PROP.equals( propertyId ) ) {
            int width = Integer.parseInt( (String) value );
            setSize( new Dimension( width,
                                    size.height ) );
        }
    }

    /**
     * Set the Size of this vertex.
     * Will not update the size if newSize is null.
     * @param newSize a non-null Dimension instance or null
     */
    public void setSize(Dimension newSize) {
        if ( newSize != null ) {
            size.setSize( newSize );
            firePropertyChange( SIZE_PROP,
                                null,
                                size );
        }
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return VERTEX_NAME + hashCode();
    }

    /**
     * Color used for borders
     *
     * @return draw color
     */
    public Color getDrawColor() {
        return ColorConstants.black;
    }

    /**
     * Constructs constraints string
     *
     * @param constraints array of constraints
     * @return html-formatted constraints representation
     */
    public static String dumpConstraints(final Constraint[] constraints) {
        if ( constraints == null ) {
            return null;
        }
        final StringBuffer buffer = new StringBuffer();
        for ( int i = 0, length = constraints.length; i < length; i++ ) {
            buffer.append( constraints[i].toString() + "<br>" );
        }
        return buffer.toString();
    }

}


/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * 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 org.drools.eclipse.editors;

import org.eclipse.draw2d.ScalableFigure;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.ui.actions.ZoomInAction;

/**
 * Similar to ZoomInAction but adds setZoomManager(..) functionality.
 *
 * ZoomInAction2 provides default constructor for initializing
 * ZoomInAction without the need for ZoomManager.
 *
 * Please note that ZoomInAction2 is not very functional until it has
 * correct zoomManager set by setZoomManager(ZoomManager manager).<br/>
 *
 * setZoomManager(ZoomManager manager) can be used several times.
 */
public class ZoomInAction2 extends ZoomInAction {

    final private static ZoomManager FAKE_ZOOM_MANAGER = new ZoomManager( (ScalableFigure) null,
                                                                          null );

    /**
     * Default constructor to allow ZoomInActions without specified
     * ZoomManager.
     */
    public ZoomInAction2() {
        super( FAKE_ZOOM_MANAGER );
    }

    /**
     * Replaces existing zoomManager with the new one.
     *
     * Implementation is null-safe.
     *
     * @param newManager new zoom manager
     */
    public void setZoomManager(ZoomManager newManager) {

        if ( zoomManager != null ) {
            zoomManager.removeZoomListener( this );
        }

        zoomManager = newManager;

        if ( zoomManager != null ) {
            zoomManager.addZoomListener( this );
        }

    }

}