/*
 * 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.client;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import org.eclipse.webdav.*;
import org.eclipse.webdav.dom.*;
import org.eclipse.webdav.internal.kernel.*;
import org.eclipse.webdav.internal.kernel.utils.Assert;
import org.eclipse.webdav.internal.kernel.utils.EmptyEnumeration;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * The AbstractResourceHandle class is the abstract
 * superclass for all types of resource references.  A resource
 * handle is a client-side 'proxy' for the server resource.  Instances
 * of handle classes understand the methods appropriate for the
 * corresponding server resource and provide a convenient high-level
 * API for sending WebDAV methods to the server to manipulate
 * the resource.
 *

 * It is certainly posible to create a stale or invalid handle in
 * numerous ways.  For example, the existance of a handle does not
 * imply the existance of a  corresponding server resource (indeed
 * resource can be created by sending create() to a handle, or deleted
 * using delete() -- so the life cycles are not coupled.  It is also
 * possible to create, say, a collection handle on a regular resource
 * and invoke invalid WebDAV methods.  These will typically result
 * in an exception.

 *

 * The API on these classes are intended to convenience methods for
 * the most common operations on server resources.  They make some
 * assumptions about the way you want to receive the results.  To
 * get finer (but possibly less convenient) control over the WebDAV
 * methods use the Server interface of DAVClient
 *  directly.

 */
public abstract class AbstractResourceHandle implements WebDAVPropertyNames, WebDAVPropertyValues {

    // The DAVClient that is used to access the WebDAV server.
    // Given during initialization, this object contains the
    // authentication and proxy information etc.
    protected DAVClient davClient;

    // The Locator represents the universal identifier of the server
    // resource.
    protected ILocator locator;

    /**
     * Creates a new AbstractResourceHandle with the given
     * DAV client and Locator.
     *
     * @param davClient the DAVClient that contains the server
     * reference and proxy/authentication information.
     * @param locator the Locator identity of the resource.
     */
    public AbstractResourceHandle(DAVClient davClient, ILocator locator) {
        Assert.isNotNull(davClient);
        Assert.isNotNull(locator);
        this.davClient = davClient;
        this.locator = locator;
    }

    /**
     * Answer a new collection handle on the same underlying server resource.
     * Since the handle represents a means of accessing the resource, it is valid to
     * consider a collection resource as a collection or regular resource depending
     * upon how it is being accessed.  Note that not all resources have collection
     * semantics.
     *
     * @return an equivalent collection handle on the resource.
     */
    public CollectionHandle asCollectionHandle() {
        return new CollectionHandle(davClient, locator);
    }

    /**
     * Answer a new resource handle on the same underlying server resource.
     * Since the handle represents a means of accessing the resource, it is valid to
     * consider a collection resource as a collection or regular resource depending
     * upon how it is being accessed.
     *
     * @return an equivalent handle on the resource.
     */
    public ResourceHandle asResourceHandle() {
        return new ResourceHandle(davClient, locator);
    }

    /**
     * Return a boolean value indicating whether or not the server for this resource
     * is DAV compliant.
     *
     * @return boolean true if the server can respond to DAV
     *            requests, or false otherwise.
     * @exception DAVException if there was a problem checking for DAV compliance
     */
    public boolean canTalkDAV() throws DAVException {
        IResponse response = null;
        try {
            // Send an options request.
            response = davClient.options(locator, newContext());
            examineResponse(response);

            // Check for at least DAV level 1.
            String davHeader = response.getContext().getDAV();
            return !((davHeader == null) || (davHeader.indexOf("1") == -1)); //$NON-NLS-1$
        } catch (IOException exception) {
            throw new SystemException(exception);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Check-in this resource.  Returns a handle on the new version.
     *

     * Note that versioned collections do not have internal members
     * so they are represented by ResourceHandle handles.

     *

     * If the receiver is a working resource it becomes invalid after
     * the check in (because the server deletes the working resource),
     * however, if the receiver is a version-controlled resource the
     * receiver can be used as a checked-in resource.

     *
     * @return a handle to the newly created version.
     * @throws DAVException if a problem occurs with the check in on
     * the WebDAV server.
     */
    public ResourceHandle checkIn() throws DAVException {
        ILocator versionLocator = protectedCheckIn();
        return new ResourceHandle(davClient, versionLocator);
    }

    /**
     * Check out this resource. Returns a resource handle on the checked out
     * version-controlled resource, or the working resource if a version is checked
     * out.
     *

     * Note that a checked-out version-controlled collection has members that are
     * themselves version-controlled resources, or unversioned resources; however,
     * working collection members are always version history resources.

     *
     * @throws DAVException if a problem occurs checking out the resource.
     */
    public abstract AbstractResourceHandle checkOut() throws DAVException;

    /**
     * Helper method to close a response from the server.
     *

     * Note that the argument MAY be null in which case
     * the call has no effect.

     *
     * @param response the response from the server, or <cod>null
     * denoting a no-op.
     * @throws SystemException if a problem occurred closing the response.
     */
    protected void closeResponse(IResponse response) throws SystemException {
        if (response == null)
            return;
        try {
            response.close();
        } catch (IOException e) {
            throw new SystemException(e);
        }
    }

    /**
     * Make a copy of this resource and place it at the location defined
     * by the given locator.
     *

     * Uses default values of depth: infinity and overwrite: false for
     * the copy.

     *
     * @param destination the Locator to the destination of the copy.
     * @exception DAVException if there was a problem copying this resource.
     * @see IServer#copy(ILocator, ILocator, IContext, Document)
     */
    public void copy(ILocator destination) throws DAVException {
        copy(destination, IContext.DEPTH_INFINITY, false, null);
    }

    /**
     * Make a copy of this resource and place it at the location specified
     * by the given destination locator.
     *
     * @param destination the location to put the copy.
     * @param depth how deep to make the copy.
     * @param overwrite how to react if a resource already exists at the destination.
     * @param propertyNames Collection of QualifiedNames
     * of properties that MUST be copied as live properties.  Specifying null
     * mean that all properties must be kept alive; specifying an empty collection allows for
     * no properties to be kept live. (ref http://andrew2.andrew.cmu.edu/rfc/rfc2518.html#sec-12.12.1)
     * @exception DAVException if there was a problem copying this resource.
     * @see IServer#copy(ILocator, ILocator, IContext, Document)
     */
    public void copy(ILocator destination, String depth, boolean overwrite, Collection propertyNames) throws DAVException {
        // Define the request context.
        IContext context = newContext();
        context.setDepth(depth);
        context.setOverwrite(overwrite);

        // Set up the request body to specify which properties should be kept alive.
        Document document = newDocument();
        PropertyBehavior propertyBehavior = PropertyBehavior.create(document);

        if (propertyNames == null)
            propertyBehavior.setIsKeepAllAlive(true);
        else {
            Iterator namesItr = propertyNames.iterator();
            while (namesItr.hasNext()) {
                QualifiedName name = (QualifiedName) namesItr.next();
                String nameURI = name.getQualifier() + "/" + name.getLocalName(); //$NON-NLS-1$
                propertyBehavior.addProperty(nameURI);
            } // end-while
        } // end-if

        // Call the server to perform the copy.
        IResponse response = null;
        try {
            response = davClient.copy(locator, destination, context, document);
            examineResponse(response);
            examineMultiStatusResponse(response);
        } catch (IOException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Create this resource in the repository.
     *

     * Subclasses should override this method with the correct behavior
     * for their type.

     *
     * @exception DAVException if there was a problem creating
     * this resource.
     */
    public abstract void create() throws DAVException;

    /**
     * Delete this resource from the repository.
     *

     * As a convenience, if the resource does not exist this method will
     * do nothing (rather than throw an exception).  If the caller needs to know
     * if a resource was deleted they can use delete(boolean).
     *
     * @exception DAVException if there was a problem deleting this resource.
     * @see #delete(boolean)
     * @see IServer#delete(Locator, Context)
     */
    public void delete() throws DAVException {
        // As a convenience, we will assume an attempt to
        // delete a missing resource is a successful outcome.
        delete(false);
    }

    /**
     * Answers true iff the receiver and the argument are considered equal,
     * otherwise answers false.
     *

     * Note that this is a handle equivalence test, and does not imply
     * that the resources are the same resource on the server.  Indeed,
     * the method does not contact the server.

     *
     * @param obj the target of the comparison.
     * @return whether the two objects are equal.
     */
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!(obj instanceof AbstractResourceHandle))
            return false;
        AbstractResourceHandle otherHandle = (AbstractResourceHandle) obj;
        // It all comes down to locator equality.
        return locator.equals(otherHandle.locator);
    }

    /**
     * Answers the hashcode of the receiver as defined by Object#hashCode().
     *
     * @return the receiver's hash code.
     */
    public int hashCode() {
        return locator.hashCode();
    }

    /**
     * If the given response contains a multistatus body, the bodies status'
     * are checked for errors. If an error is found, an exception is thrown.
     *
     * @param response the response from the server to examine.
     * @throws DAVException if the given response contains a multistatus
     * body that contains a status code signalling an error.
     */
    protected void examineMultiStatusResponse(IResponse response) throws DAVException {
        // If it is not a multistatus we don't look at it.
        if (response.getStatusCode() != IResponse.SC_MULTI_STATUS)
            return;

        // It is declared a multistatus, so if there is no response body
        // then that is a problem.
        if (!response.hasDocumentBody())
            throw new DAVException(Policy.bind("exception.responseMustHaveDocBody")); //$NON-NLS-1$

        // Extract the XML document from the response.
        Element documentElement;
        try {
            documentElement = response.getDocumentBody().getDocumentElement();
            if (documentElement == null)
                throw new DAVException(Policy.bind("exception.invalidDoc")); //$NON-NLS-1$
        } catch (IOException exception) {
            throw new SystemException(exception);
        }

        // Enumerate all the responses in the multistat and check that
        // they are indicating success (i.e. are 200-series response codes).
        try {
            MultiStatus multistatus = new MultiStatus(documentElement);
            Enumeration responseEnum = multistatus.getResponses();
            while (responseEnum.hasMoreElements()) {
                ResponseBody responseBody = (ResponseBody) responseEnum.nextElement();
                Enumeration propstatEnum = responseBody.getPropStats();
                while (propstatEnum.hasMoreElements()) {
                    PropStat propstat = (PropStat) propstatEnum.nextElement();
                    examineStatusCode(propstat.getStatusCode(), propstat.getResponseDescription());
                } // end-while
            } // end-while
        } catch (MalformedElementException e) {
            throw new SystemException(e);
        }
    }

    /**
     * Check the status code of the given response and throw a WebDAV
     * exception if the code indicates failure.
     *
     * @param response the response to check
     * @exception WebDAVException if the server returned an HTTP/WebDAV
     * error code (i.e., anything outside the 200-series codes).
     */
    protected void examineResponse(IResponse response) throws WebDAVException {
        examineStatusCode(response.getStatusCode(), response.getStatusMessage());
    }

    /**
     * Helper method to extract the property status response from
     * a multi status reponse, and populate a URLTable with the
     * results.
     *
     * @param multiStatus an editor on the response from the server.
     * @return all the property status in a URLTable.
     * @throws IOException if there is a problem parsing the resource URLs.
     * @throws MalformedElementException if the XML is badly formed.
     */
    protected URLTable extractPropStats(MultiStatus multiStatus) throws IOException, MalformedElementException {
        // Construct a URLTable to return to the user.
        URLTable reply = new URLTable();

        // For each response (resource).
        Enumeration responses = multiStatus.getResponses();
        while (responses.hasMoreElements()) {
            ResponseBody responseBody = (ResponseBody) responses.nextElement();
            String href = responseBody.getHref();

            // The href may be relative to the request URL.
            URL resourceURL = new URL(new URL(locator.getResourceURL()), href);
            Hashtable properties = new Hashtable();
            reply.put(resourceURL, properties);

            // For each property status grouping.
            Enumeration propstats = responseBody.getPropStats();
            while (propstats.hasMoreElements()) {
                PropStat propstat = (PropStat) propstats.nextElement();
                org.eclipse.webdav.dom.Status status = new org.eclipse.webdav.dom.Status(propstat.getStatus());

                // For each property with this status.
                Enumeration elements = propstat.getProp().getProperties();
                while (elements.hasMoreElements()) {
                    Element element = (Element) elements.nextElement();
                    QualifiedName name = ElementEditor.getQualifiedName(element);
                    // Add a property status object to the result set.
                    PropertyStatus propertyStatus = new PropertyStatus(element, status.getStatusCode(), status.getStatusMessage());
                    properties.put(name, propertyStatus);
                } // end-while
            } // end-while
        } // end-while

        return reply;
    }

    /**
     * Return the content of this resource as an input stream. The input
     * stream should be closed by the user.
     *
     * @return the input stream
     * @exception DAVException if there was a problem getting the contents
     * @see IServer#get(Locator, Context)
     */
    public ResponseInputStream getContent() throws DAVException {
        IResponse response = null;
        try {
            response = davClient.get(locator, newContext());
            examineResponse(response);
        } catch (IOException e) {
            closeResponse(response);
            throw new SystemException(e);
        }
        return new ResponseInputStream(response);
    }

    /**
     * Answer the DAVClient being used by this resource handle for accessing
     * the resource.
     *
     * @return the receiver's DAVClient.
     */
    public DAVClient getDAVClient() {
        return davClient;
    }

    /**
     * Return the locator for this resource.
     *
     * @return the locator for this resource
     */
    public ILocator getLocator() {
        return locator;
    }

    /**
     * Return an Enumeration over ActiveLocks which lists the locks currently
     * held on this resource. Return an empty enumeration if the lock discovery
     * property is not found on the resource.
     *
     * @return the enumeration of active locks
     * @exception DAVException if there was a problem getting the locks
     * @see #getProperty(QualifiedName)
     */
    public Enumeration getLocks() throws DAVException {
        LockDiscovery lockdiscovery = null;
        try {
            Element element = getProperty(DAV_LOCK_DISCOVERY).getProperty();
            lockdiscovery = new LockDiscovery(element);
            return lockdiscovery.getActiveLocks();
        } catch (WebDAVException exception) {
            if (exception.getStatusCode() == IResponse.SC_NOT_FOUND)
                return new EmptyEnumeration();
            throw exception;
        } catch (MalformedElementException elemException) {
            throw new SystemException(elemException);
        }
    }

    /**
     * Returns a collection handle for the parent of this resource.
     *

     * Note that this method does NOT perform a method call to the
     * server to ensure that the collection exists.

     *

     * Returns null if this resource is the root.
     *
     * NOTE
     * The parent of a resource is, in general, ambiguous and may not
     * be immediately discernable from a resource locator.  For example,
     * a locator with a 'label' qualifier will identify a version of a version-
     * controlled resource, and the parent will not be found by a simple URL
     * operation.  Where a handle is created on a stable URL (i.e. a version URL)
     * there is no concept of a 'parent' resource.
     * Clients require further contextual information to determine
     * the 'parent' of a resource in these cases.
     *
     * @return the handle for the parent of this resource, or
     * null.
     */
    public CollectionHandle getParent() throws DAVException {
        Assert.isTrue(locator.getLabel() == null);
        Assert.isTrue(!locator.isStable());

        try {
            URL url = URLTool.getParent(locator.getResourceURL());
            if (url == null)
                return null;
            String parentName = url.toString();
            ILocator parentLocator = davClient.getDAVFactory().newLocator(parentName);
            return new CollectionHandle(davClient, parentLocator);

        } catch (MalformedURLException e) {
            throw new SystemException(e);
        }
    }

    /**
     * Return a URLTable which contains all of this resources
     * properties to the given depth. The returned URLTable
     * maps resource URLs to Hashtables
     * which in turn maps property QualifiedNames to
     * PropertyStatus.
     *
     * @param depth the depth of the request; for example,
     * Context.DEPTH_ZERO.
     * @return a URLTable containing properties.
     * @exception DAVException if there was a problem retrieving the properties.
     * @see #getProperties(Collection, String)
     */
    public URLTable getProperties(String depth) throws DAVException {
        return getProperties((Collection) null, depth);
    }

    /**
     * Fetches and returns the specified properties for this resource and its
     * children to the given depth. The returned table is a URLTable of
     * hashtables. The keys in the first table are the URLs of
     * the resources. The nested table is a table where the keys are the names
     * (QualifiedName) of the properties and the values are the
     * properties' values (PropertyStatus).
     *
     * @param propertyNames collection of property names to search for
     * (QualifiedName), or null to retrieve
     * all properties.
     * @param depth the depth of the search (eg. Context.DEPTH_INFINITY)
     * @return URLTable of hashtables keyed by resource URLKey
     * then by property name.
     * @exception DAVException if there was a problem fetching the properties.
     * @see IServer#propfind(Locator, Context, Document)
     */
    public URLTable getProperties(Collection propertyNames, String depth) throws DAVException {
        // Set up the request context.
        IContext context = newContext();
        context.setDepth(depth);

        // Set up the request body.
        Document document = newDocument();
        PropFind propfind = PropFind.create(document);

        // null is a special value meaning 'all properties'.
        if (propertyNames == null)
            propfind.setIsAllProp(true);
        else {
            // Add all the property names to the request body.
            Prop prop = propfind.setProp();
            Iterator namesItr = propertyNames.iterator();
            while (namesItr.hasNext())
                prop.addPropertyName((QualifiedName) namesItr.next());
        }

        // Were ready to make the server call.
        IResponse response = null;
        try {
            // This contacts the server.
            response = davClient.propfind(locator, context, document);
            examineResponse(response);

            // Create a multi-status element editor on the response.
            if (!response.hasDocumentBody())
                throw new DAVException(Policy.bind("exception.respMustShareXMLDoc")); //$NON-NLS-1$
            Element documentElement = response.getDocumentBody().getDocumentElement();
            if (documentElement == null)
                throw new DAVException(Policy.bind("exception.respHasInvalidDoc")); //$NON-NLS-1$
            MultiStatus multiStatus = new MultiStatus(documentElement);

            // Construct a URLTable of results to return to the user.
            return extractPropStats(multiStatus);

        } catch (IOException e) {
            throw new SystemException(e);
        } catch (MalformedElementException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Return the property status for the property with the given name.
     *
     * @param propertyName the name of the property
     * @return the property status
     * @exception DAVException if there was a problem getting the property
     * @see #getProperties(Collection, String)
     */
    public PropertyStatus getProperty(QualifiedName propertyName) throws DAVException {
        Collection names = new HashSet();
        names.add(propertyName);
        URLTable result = getProperties(names, IContext.DEPTH_ZERO);

        URL url = null;
        try {
            url = new URL(locator.getResourceURL());
        } catch (MalformedURLException e) {
            throw new SystemException(e);
        }

        Hashtable propTable = (Hashtable) result.get(url);
        if (propTable == null)
            throw new DAVException(Policy.bind("exception.lookup", url.toExternalForm())); //$NON-NLS-1$
        return (PropertyStatus) propTable.get(propertyName);
    }

    /**
     * Fetch and return the property names for the resource, and the children
     * resources to the specified depth. Returns URLTable
     * mapping resource URLs to enumerations over the property names for that
     * resource.
     *
     * @param depth eg. Context.DEPTH_ZERO
     * @return a URLTable of Enumerations over
     * QualfiedNames
     * @throws DAVException if there was a problem getting the property names
     * @see IServer#propfind(Locator, Context, Document)
     */
    public URLTable getPropertyNames(String depth) throws DAVException {

        // create and send the request
        IContext context = newContext();
        context.setDepth(depth);

        IResponse response = null;
        try {
            Document document = newDocument();
            PropFind propfind = PropFind.create(document);
            propfind.setIsPropName(true);

            response = davClient.propfind(locator, context, document);
            examineResponse(response);

            if (!response.hasDocumentBody()) {
                throw new DAVException(Policy.bind("exception.respMustHaveElmtBody")); //$NON-NLS-1$
            }
            Element documentElement = response.getDocumentBody().getDocumentElement();
            if (documentElement == null) {
                throw new DAVException(Policy.bind("exception.bodyMustHaveElmt")); //$NON-NLS-1$
            }
            MultiStatus multistatus = new MultiStatus(documentElement);

            //construct the URLTable to return to the user
            URLTable reply = new URLTable(10);
            Enumeration responses = multistatus.getResponses();
            while (responses.hasMoreElements()) {
                ResponseBody responseBody = (ResponseBody) responses.nextElement();
                String href = responseBody.getHref();
                URL resourceUrl = new URL(new URL(locator.getResourceURL()), href);
                Enumeration propstats = responseBody.getPropStats();
                Vector vector = new Vector();
                while (propstats.hasMoreElements()) {
                    PropStat propstat = (PropStat) propstats.nextElement();
                    Prop prop = propstat.getProp();
                    Enumeration names = prop.getPropertyNames();
                    while (names.hasMoreElements()) {
                        QualifiedName dname = (QualifiedName) names.nextElement();
                        vector.addElement(dname);
                    }
                }
                reply.put(resourceUrl, vector.elements());
            }
            return reply;
        } catch (IOException e) {
            throw new SystemException(e);
        } catch (MalformedElementException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Retrieve the version tree infomration for the receiver, assuming
     * that the receiver is a version or a version-controlled resource.
     *

     * The version tree info comprises a URLTable whose keys
     * are the URLs of each version in the version history,
     * and whose values are Vectors of the resource's immediate
     * predecessor URLs.  Note that the root version is
     * (uniquely) identified by an empty set of predecessors.

     *
     * @return the map from resource URL to predecessor set.
     */
    public URLTable getVersionTree() throws DAVException {

        // Issue a version tree report against the receiver to retrieve
        // the successor set of all the versions.
        Document document = newDocument();
        Element root = ElementEditor.create(document, "version-tree"); //$NON-NLS-1$
        Element propElement = ElementEditor.appendChild(root, "prop"); //$NON-NLS-1$
        ElementEditor.appendChild(propElement, "predecessor-set"); //$NON-NLS-1$

        IResponse response = null;
        try {
            // Run the REPORT and check for errors.
            response = davClient.report(locator, newContext(), document);
            examineResponse(response);

            if (!response.hasDocumentBody())
                throw new DAVException(Policy.bind("exception.respMustHaveElmtBody")); //$NON-NLS-1$

            // Get the body as a MultiStatus.
            Element documentElement = response.getDocumentBody().getDocumentElement();
            if (documentElement == null)
                throw new DAVException(Policy.bind("exception.bodyMustHaveElmt")); //$NON-NLS-1$
            MultiStatus multistatus = new MultiStatus(documentElement);

            // Construct the predecessor table.
            // This will contain the result.
            URLTable predecessorTable = new URLTable();

            // For each response.
            Enumeration responses = multistatus.getResponses();
            while (responses.hasMoreElements()) {
                ResponseBody responseBody = (ResponseBody) responses.nextElement();

                // Get the absolute URL of the resource.
                String href = responseBody.getHref();
                URL resourceURL = new URL(new URL(locator.getResourceURL()), href);

                // Add an entry to the predecessor table.
                Vector predecessors = new Vector();
                predecessorTable.put(resourceURL, predecessors);

                // For each propstat.
                Enumeration propstats = responseBody.getPropStats();
                while (propstats.hasMoreElements()) {
                    PropStat propstat = (PropStat) propstats.nextElement();

                    // We are going to assume that the status is OK, or error out.
                    if (propstat.getStatusCode() != IResponse.SC_OK)
                        throw new DAVException(Policy.bind("exception.errorRetrievingProp")); //$NON-NLS-1$

                    // For each property in the prop (there should only be one).
                    Prop prop = propstat.getProp();
                    Enumeration elements = prop.getProperties();
                    while (elements.hasMoreElements()) {
                        Element element = (Element) elements.nextElement();

                        //  Look explicitly for the DAV:predecessor-set
                        QualifiedName name = ElementEditor.getQualifiedName(element);
                        if (name.equals(DAV_PREDECESSOR_SET)) {
                            Enumeration e = new HrefSet(element, DAV_PREDECESSOR_SET).getHrefs();
                            while (e .hasMoreElements()) {
                                URL predURL = new URL((String) e.nextElement());
                                predecessors.add(predURL);
                            } // end-while
                        } //end-if
                    } // end-while
                } // end-while
            } //end-while

            // Phew, were done.
            return predecessorTable;

        } catch (IOException e) {
            throw new SystemException(e);
        } catch (MalformedElementException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    public CollectionHandle[] getWorkspaceCollections() throws DAVException {

        PropertyStatus propertyStatus = getProperty(DAV_WORKSPACE_COLLECTION_SET);

        Vector v = new Vector(5);
        Element element = propertyStatus.getProperty();

        if (!ElementEditor.isDAVElement(element, "workspace-collection-set")) //$NON-NLS-1$
            throw new DAVException(Policy.bind("exception.malformedElement")); //$NON-NLS-1$

        Element child = ElementEditor.getFirstChild(element, "href"); //$NON-NLS-1$
        while (child != null) {
            String href = ElementEditor.getFirstText(child);
            ILocator locator = davClient.getDAVFactory().newLocator(href);
            v.addElement(new CollectionHandle(davClient, locator));
            child = ElementEditor.getNextSibling(child);
        }

        CollectionHandle[] result = new CollectionHandle[v.size()];
        v.copyInto(result);
        return result;
    }

    /**
     * Return the header from a message send to the server.
     *
     * @return a context with the message header contents
     * @exception DAVException if there was a problem sending the message
     * @see IServer#head(Locator, Context)
     */
    public IContext head() throws DAVException {
        IResponse response = null;
        try {
            response = davClient.head(locator, newContext());
            examineResponse(response);
            return response.getContext();
        } catch (IOException exception) {
            throw new SystemException(exception);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Return a boolean value indicating whether or not this resource is a
     * collection.
     *

     * A resource is a collection (i.e., implements collection semantics) if
     * it's resource type includes a <DAV:collection> element.

     *
     * @return boolean true if the resource implements collection
     * semantics, and false otherwise.
     */
    public boolean isCollection() throws DAVException {
        return propertyHasChild(DAV_RESOURCE_TYPE, DAV_COLLECTION_RESOURCE_TYPE);
    }

    /**
     * Return a boolean value indicating whether or not this resource
     * is currently locked.
     *
     * @return boolean indicator
     * @exception DAVException if there was a problem getting the locks
     * @see #getLocks()
     */
    public boolean isLocked() throws DAVException {
        // see if there are any active locks
        return getLocks().hasMoreElements();
    }

    /**
     * Lock this resource with default values.
     *
     *
Note: default values of DEPTH_ZERO for depth and -1 for timeout are used.

     *
     * @return the lock token
     * @exception DAVException if there was a problem locking this resource
     * @see #lock(boolean, String, int, String)
     */
    public LockToken lock() throws DAVException {
        return lock(false, IContext.DEPTH_ZERO, -1, null);
    }

    /**
     * Lock this resource using the specified parameters.
     *
     * @param isShared true if the lock is shared, false if the lock is exclusive
     * @param depth eg. Context.DEPTH_ZERO
     * @param timeout the timeout value for the lock
     * @param owner the owner of the lock
     * @return the lock token
     * @exception DAVException if there was a problem locking this resource
     * @see IServer#lock(Locator, Context, Document)
     */
    public LockToken lock(boolean isShared, String depth, int timeout, String owner) throws DAVException {

        // Define the request context.
        IContext context = newContext();
        context.setDepth(depth);
        context.setTimeout(timeout);

        // Create the request body.
        Document document = newDocument();
        LockInfo lockinfo = LockInfo.create(document);
        lockinfo.setIsShared(isShared);

        // Add the owner if it is given.
        if (owner != null) {
            Owner ownerEditor = lockinfo.setOwner();
            ownerEditor.getElement().appendChild(document.createTextNode(owner));
        }

        // Send the lock request.
        IResponse response = null;
        try {
            response = davClient.lock(locator, context, document);
            examineResponse(response);
        } catch (IOException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }

        // Extract the token from the resulting context.
        LockToken token = new LockToken(response.getContext().getLockToken());
        //fServerManager.addLock(newURL(fLocator.getResourceURL()), token, depth);
        return token;
    }

    /**
     * Move this resource to the destination specified by the given locator.
     * Use default values for overwrite and properties to move.
     *
     * @param destination the location to move this resource to
     * @exception DAVException if there was a problem moving this resource
     * @see #move(Locator, boolean, Enumeration)
     */
    public void move(ILocator destination) throws DAVException {
        move(destination, false, null);
    }

    /**
     * Move this resource to the location specified by the given locator.
     * If a resource already exists at the destination and the overwrite
     * boolean is true, then write over top of the existing resource. Otherwise
     * do not. The enumeration is over qualified names which are the names of
     * the properties to move.
     *
     * @param destination the location to move to
     * @param overwrite how to react if a resource already exists at the
     *   destination
     * @param names Enumeration over QualifiedNames
     * @exception DAVException if there was a problem moving this resource
     * @see IServer#move(Locator, Locator, Context, Document)
     */
    public void move(ILocator destination, boolean overwrite, Enumeration names) throws DAVException {
        IContext context = newContext();
        context.setOverwrite(overwrite);

        Document document = newDocument();
        PropertyBehavior propertyBehavior = PropertyBehavior.create(document);

        if (names == null) {
            propertyBehavior.setIsKeepAllAlive(true);
        } else {
            while (names.hasMoreElements()) {
                Object obj = names.nextElement();
                Assert.isTrue(obj instanceof QualifiedName, Policy.bind("assert.propNameMustBeEnumOverQual")); //$NON-NLS-1$
                // fix this...can we really add property names to href elements?
                propertyBehavior.addProperty(((QualifiedName) obj).getLocalName());
            }
        }

        IResponse response = null;
        try {
            response = davClient.move(locator, destination, context, document);
            examineResponse(response);
            examineMultiStatusResponse(response);
        } catch (IOException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Answer a new empty context for requests sent to the
     * receivers server.
     *
     * @return a new request Context.
     */
    protected IContext newContext() {
        return davClient.getDAVFactory().newContext();
    }

    /**
     * Answer a new empty DOM Document suitable for creating requests
     * to the receiver's server.
     *
     * @return a new DOM Document.
     */
    protected Document newDocument() {
        return davClient.getDAVFactory().newDocument();
    }

    /**
     * Check in the receiver and answer a new Locator on the
     * resulting version resource.
     *
     * @return the Locator to the receiver's version.
     * @throws DAVException if a problem occurs with the check in request.
     */
    protected ILocator protectedCheckIn() throws DAVException {
        IResponse response = null;
        try {
            response = davClient.checkin(locator, newContext(), null);
            examineResponse(response);
            String versionUrl = response.getContext().getLocation();
            return davClient.getDAVFactory().newStableLocator(versionUrl);
        } catch (IOException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Check out the receiver and answer a new Locator on the
     * resulting checked out resource.  The result MAY be the same
     * as the receiver's Locator if the server did not create
     * a new resource as a consequence of the check out (i.e.
     * if it was checking out a vesion-controlled resource rather
     * than a version).
     *
     * @return the Locator to the receiver's version.
     * @throws DAVException if a problem occurs with the check in request.
     */
    protected ILocator protectedCheckOut() throws DAVException {
        IResponse response = null;
        try {
            response = davClient.checkout(locator, newContext(), null);
            examineResponse(response);
            String resourceUrl = response.getContext().getLocation();
            return davClient.getDAVFactory().newStableLocator(resourceUrl);
        } catch (IOException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Refresh the lock on this resource with the given lock token. Use
     * the specified timeout value.
     *
     * @param lockToken the lock token to refresh
     * @param timeout the new timeout value to use
     * @exception DAVException if there was a problem refreshing the lock
     */
    public void refreshLock(LockToken lockToken, int timeout) throws DAVException {
        // Set up the request in the context.
        IContext context = newContext();
        context.setTimeout(timeout);
        context.setLockToken(lockToken.getToken());

        // Send the request to the server.
        IResponse response = null;
        try {
            response = davClient.lock(locator, context, null);
            examineResponse(response);
        } catch (IOException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Remove the properties with the given names, from this resource.
     *
     * @param propertyNames Enumeration over
     *   QualifiedNames
     * @exception DAVException if there was a problem removing the
     *   properties
     * @see IServer#proppatch(Locator, Context, Document)
     */
    public void removeProperties(Collection propertyNames) throws DAVException {
        Assert.isNotNull(propertyNames);

        // Removing no properties is easy.
        if (propertyNames.isEmpty())
            return;

        // Add the names of the properties to remove to the request body.
        Document document = newDocument();
        PropertyUpdate propertyUpdate = PropertyUpdate.create(document);
        Prop prop = propertyUpdate.addRemove();
        Iterator namesItr = propertyNames.iterator();
        while (namesItr.hasNext())
            prop.addPropertyName((QualifiedName) namesItr.next());

        // Send the PROPPATCH request.
        IResponse response = null;
        try {
            response = davClient.proppatch(locator, newContext(), document);
            examineResponse(response);
            examineMultiStatusResponse(response);
        } catch (IOException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Remove the property with the given name from this resource.
     *
     * @param propertyName the name of the property to remove
     * @exception DAVException if there was a problem removing the property
     * @see #removeProperties(Collection)
     */
    public void removeProperty(QualifiedName propertyName) throws DAVException {
        Collection propertyNames = new Vector(1);
        propertyNames.add(propertyName);
        removeProperties(propertyNames);
    }

    /**
     * Set the content of this resource to be the untyped data stored in the given
     * input stream.
     * The stream will automatically be closed after the data
     * is consumed.  If the resource does not exist it is created with the
     * given content.
     *
     * @param input the inputstream containing the resource contents.
     * @exception DAVException if there was a problem setting the contents.
     * @see #setContent(String, InputStream)
     * @see IServer#put(Locator, Context, InputStream)
     */
    public void setContent(InputStream input) throws DAVException {
        setContent("application/octet-stream", input); //$NON-NLS-1$
    }

    /**
     * Set the content of this resource to be the data stored in the given
     * input stream. The type encoding is given in the content type argument, and
     * should be in the media format described by RFC2616 Sec 3.7.
     * The stream will automatically be closed after the data
     * is consumed.  If the resource does not exist it is created with the
     * given content.
     *
     * @param contentType the media type for the data on the input stream.
     * @param input the inputstream containing the resource contents.
     * @exception DAVException if there was a problem setting the contents.
     * @see IServer#put(Locator, Context, InputStream)
     */
    public void setContent(String contentType, InputStream input) throws DAVException {
        IResponse response = null;
        try {
            IContext context = newContext();
            context.setContentType(contentType);
            response = davClient.put(locator, context, input);
            examineResponse(response);
        } catch (IOException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Set the given properties on this resource.
     *
     * @param properties a Collection of property Elements.
     * @exception DAVException if there was a problem setting the properties.
     * @see IServer#proppatch(Locator, Context, Document)
     */
    public void setProperties(Collection properties) throws DAVException {
        Assert.isNotNull(properties);

        // Setting no properties is a no-op.
        if (properties.isEmpty())
            return;

        // Build the request body to describe the properties to set.
        Document document = newDocument();
        PropertyUpdate propertyUpdate = PropertyUpdate.create(document);
        Prop prop = propertyUpdate.addSet();

        Iterator propertiesItr = properties.iterator();
        while (propertiesItr.hasNext()) {
            Element element = (Element) propertiesItr.next();
            try {
                prop.addProperty(element);
            } catch (MalformedElementException exception) {
                throw new SystemException(exception);
            }
        } // end-while

        // Send the request to the server and examine the response for failures.
        IResponse response = null;
        try {
            response = davClient.proppatch(locator, newContext(), document);
            examineResponse(response);
            examineMultiStatusResponse(response);
        } catch (IOException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Set the given property on this resource.
     *
     * @param property the property to set
     * @exception DAVException if there was a problem setting the property
     * @see #setProperties(Collection)
     */
    public void setProperty(Element property) throws DAVException {
        Collection properties = new Vector(1);
        properties.add(property);
        setProperties(properties);
    }

    /**
     * Return a string representation of this resource. Used for
     * debugging purposes only.
     *
     * @return this resource, as a string
     */
    public String toString() {
        return locator.getResourceURL();
    }

    /**
     * Send a message to the server. The contents of the resulting
     * input stream should be the message that was sent, echoed
     * back to the client.
     *

     * The input stream should be closed by the user.

     *
     * @return an input stream on the request as received.
     * @exception DAVException if there was a problem sending the
     * request to the server.
     * @see IServer#trace(Locator, Context)
     */
    public ResponseInputStream trace() throws DAVException {

        IResponse response = null;
        try {
            response = davClient.trace(locator, newContext());
            examineResponse(response);
        } catch (IOException e) {
            throw new SystemException(e);
        } catch (DAVException e) {
            closeResponse(response);
            throw e;
        }
        return new ResponseInputStream(response);
    }

    /**
     * Unlock this resource with the given lock token.
     *
     * @param token the lock token to remove from this resource
     * @exception DAVException if there was a problem unlocking this resource
     * @see IServer#unlock(Locator, Context)
     */
    public void unlock(LockToken token) throws DAVException {
        // Send the lock token in the header of the request.
        IContext context = newContext();
        context.setLockToken("<" + token.getToken() + ">"); //$NON-NLS-1$ //$NON-NLS-2$

        IResponse response = null;
        try {
            response = davClient.unlock(locator, context);
            examineResponse(response);
        } catch (IOException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Perform an UPDATE on the receiver to set the version it
     * is based upon.
     *
     * @param version the Locator of the version that
     * is the target of the update request.
     * @throws DAVException if a problem occurs executing the update
     * on the WebDAV server.
     */
    public void update(ILocator version) throws DAVException {
        Document document = newDocument();
        Update.createVersion(document, version.getResourceURL());

        IResponse response = null;
        try {
            response = davClient.update(locator, newContext(), document);
            examineResponse(response);
        } catch (IOException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Bring the receiver under version control.  This means that
     * the receiver is replaced by a version-controlled resource.
     * Note that the client may send version control to a resource
     * that is already under version control with no adverse effects.
     *
     * @throws DAVException if a problem occurs bringing the
     * resource under version control.
     */
    public void versionControl() throws DAVException {
        IResponse response = null;
        try {
            response = davClient.versionControl(locator, newContext(), null);
            examineResponse(response);
        } catch (IOException e) {
            throw new SystemException(e);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Delete this resource from the repository, optionally succeeding
     * in the delete if the resource was not found on the server.
     *
     * @param mustExist if true then the delete will
     * fail if the resource was not on the server at the time of the
     * delete request.  If false the delete will succeed if
     * there was no such resource to delete.
     * @exception DAVException if there was a problem deleting
     * this resource.
     * @see IServer#delete(Locator, Context)
     */
    public void delete(boolean mustExist) throws DAVException {
        IResponse response = null;
        try {
            response = davClient.delete(locator, newContext());
            if (!mustExist && (response.getStatusCode() == IResponse.SC_NOT_FOUND))
                return;
            examineResponse(response);
            examineMultiStatusResponse(response);
        } catch (IOException exception) {
            throw new SystemException(exception);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Check the given status code and throw a WebDAV
     * exception if the code indicates failure.  If the code
     * is success, this method does nothing.
     *
     * @param code the status code to check.
     * @param message the status message accompanying the code.
     * @exception WebDAVException if the server returned an HTTP/WebDAV
     * error code (i.e., anything outside the 200-series codes).
     */
    protected void examineStatusCode(int code, String message) throws WebDAVException {
        if (code >= 300 && code <= 399)
            throw new RedirectionException(code, message);
        if (code >= 400 && code <= 499)
            throw new ClientException(code, message);
        if (code >= 500 && code <= 599)
            throw new ServerException(code, message);
    }

    /**
     * Return a boolean value indicating whether or not this resource
     * exists on the server.
     *

     * This implementation uses the HTTP HEAD method so the URL may or
     * may not exist in the DAV namespace. The DAV RESOURCE_TYPE property
     * is NOT checked.

     *
     * @return boolean true if the resource exists on the server
     * or false otherwise.
     * @exception DAVException if there was a problem checking for
     * existence.
     */
    public boolean exists() throws DAVException {

        // Test existance by issuing a HEAD request.
        IResponse response = null;
        try {
            response = davClient.head(locator, newContext());
            // If the resource was not found, then that answers the question.
            if (response.getStatusCode() == IResponse.SC_NOT_FOUND)
                return false;

            // Otherwise check for errors.
            examineResponse(response);

            // No problems by this point, so the resource is there and OK.
            return true;
        } catch (IOException exception) {
            throw new SystemException(exception);
        } finally {
            closeResponse(response);
        }
    }

    /**
     * Check to see if the resource is checked in (i.e., is an immutable
     * resource).
     *

     * The resource is checked in if it has a <DAV:checked-in>
     * property.

     *
     * @return true if the resource is checked in
     * and false otherwise.
     * @throws DAVException if a problem occurs determining the state
     * of the resource.
     */
    public boolean isCheckedIn() throws DAVException {
        return supportsLiveProperty(DAV_CHECKED_IN);
    }

    /**
     * Check to see if the resource is checked-out.
     *

     * The resource is checked out if it has a <DAV:checked-out>
     * property.

     *
     * @return true if the resource is checked out
     * and false otherwise.
     * @throws DAVException if a problem occurs determining the state
     * of the resource.
     */
    public boolean isCheckedOut() throws DAVException {
        return supportsLiveProperty(DAV_CHECKED_OUT);
    }

    /**
     * Check to see if the resource is a version.
     *

     * The resource is a version if it has <DAV:version-name>
     * in the <DAV:supported-live-properties-set>.

     *
     * @return true if the resource is a version
     * and false otherwise.
     * @throws DAVException if a problem occurs determining the state
     * of the resource.
     */
    public boolean isVersion() throws DAVException {
        return supportsLiveProperty(DAV_VERSION_NAME);
    }

    /**
     * Check to see if the resource is under version control.
     *

     * The resource is version controlled if it has <DAV:auto-checkout>
     * in the <DAV:supported-live-properties-set>.

     *
     * @return true if the resource is under version
     * control and false otherwise.
     * @throws DAVException if a problem occurs determining the state
     * of the resource.
     */
    public boolean isVersionControlled() throws DAVException {
        return supportsLiveProperty(DAV_AUTO_CHECKOUT);
    }

    /**
     * Check to see if the resource is a working resource.
     *

     * The resource is a working resource if it has
     * <DAV:checked-out> and does not have <DAV:auto-checkout>
     * in the <DAV:supported-live-properties-set>.

     *
     * @return true if the resource is a working resource
     * and false otherwise.
     * @throws DAVException if a problem occurs determining the state
     * of the resource.
     */
    public boolean isWorkingResource() throws DAVException {
        PropertyStatus propertyStat = getProperty(DAV_SUPPORTED_LIVE_PROPERTY_SET);
        // If the live-property-set is not supported, then the answer is 'no'.
        if (propertyStat.getStatusCode() == IResponse.SC_NOT_FOUND)
            return false;
        // If there was a problem getting the live property set, throw an exception.
        examineStatusCode(propertyStat.getStatusCode(), propertyStat.getStatusMessage());
        // Check to see if the required properties are/are not in the supported set.
        try {
            Element propertySet = propertyStat.getProperty();
            return ((ElementEditor.hasChild(propertySet, DAV_CHECKED_OUT)) && !(ElementEditor.hasChild(propertySet, DAV_AUTO_CHECKOUT)));
        } catch (MalformedElementException exception) {
            throw new SystemException(exception);
        }
    }

    /**
     * This is a helper method to check to see if the resource has a
     * property with the given name that in turn has a child with a
     * given name.
     *
     * @return true if the resource does have such a
     * property with a named child and false if it does
     * not have such a property or does not have such a child of the
     * property.
     * @throws DAVException if a problem occurs determining result
     * from the server.
     */
    protected boolean propertyHasChild(QualifiedName propertyName, QualifiedName childName) throws DAVException {
        // If the property is not found, then the answer is 'no'.
        PropertyStatus propertyStat = getProperty(propertyName);
        if (propertyStat.getStatusCode() == IResponse.SC_NOT_FOUND)
            return false;
        // If there was a problem getting the property, throw an exception.
        examineStatusCode(propertyStat.getStatusCode(), propertyStat.getStatusMessage());
        // Check to see if the named child is in the retrieved property.
        try {
            return ElementEditor.hasChild(propertyStat.getProperty(), childName);
        } catch (MalformedElementException exception) {
            throw new SystemException(exception);
        }
    }

    /**
     * Check to see if the resource supports the named live property.
     *
     * @return true if the resource does support the live
     * property and false otherwise.
     * @throws DAVException if a problem occurs determining result
     * from the server.
     */
    public boolean supportsLiveProperty(QualifiedName propertyName) throws DAVException {
        return propertyHasChild(DAV_SUPPORTED_LIVE_PROPERTY_SET, propertyName);
    }

}


/*
 * 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.NoSuchElementException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * An element editor for the WebDAV bindings element. See INTERNET DRAFT
 * draft-ietf-webdav-binding-protocol-02 section 13.1 for the element's
 * definition.
 *

 * 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 Bindings extends Property {
    /**
     * An ordered collection of the element names of the bindings
     * element's children.
     */
    protected static final String[] childNames = new String[] {"href", "segment"}; //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * This class represent a single binding.
     *
     * @see Bindings#getBindings()
     */
    public class Binding {
        private String fHref = null;
        private String fSegment = null;

        /**
         * Construct a new binding with the given href and segment.  The
         * href and segment must not be null.
         *
         * @param href    the binding's href
         * @param segment the binding's segment
         */
        public Binding(String href, String segment) {
            Assert.isNotNull(href);
            Assert.isNotNull(segment);
            fHref = href;
            fSegment = segment;
        }

        /**
         * Return this binding's href.
         */
        public String getHref() {
            return fHref;
        }

        /**
         * Return this binding's segment.
         */
        public String getSegment() {
            return fSegment;
        }
    }

    /**
     * Creates a new editor on the given WebDAV bindings element. The
     * element is assumed to be well formed.
     *
     * @param root bindings element
     * @throws      MalformedElementException if there is reason to
     *              believe that the element is not well formed
     */
    public Bindings(Element root) throws MalformedElementException {
        super(root, "bindings"); //$NON-NLS-1$
    }

    /**
     * Adds the specified binding to this editor's bindings element. The
     * given href and segment must not be null.
     *
     * @param href the href part of the binding to add
     * @param segment the segment part of the binding to add
     */
    public void addBinding(String href, String segment) {
        Assert.isNotNull(href);
        Assert.isNotNull(segment);
        appendChild(root, "href", encodeHref(href)); //$NON-NLS-1$
        appendChild(root, "segment", segment); //$NON-NLS-1$
    }

    /**
     * Returns an Enumeration over this bindings
     * Bindings.
     *
     * @return an Enumeration of Bindings.Bindings
     * @throws MalformedElementException if there is reason to believe that
     *         this editor's underlying element is not well formed
     */
    public Enumeration getBindings() throws MalformedElementException {

        final Node firstHref = getFirstChild(root, "href"); //$NON-NLS-1$
        Node segment = null;
        if (firstHref != null)
            segment = getNextSibling((Element) firstHref, "segment"); //$NON-NLS-1$

        final Node firstSegment = segment;

        Enumeration e = new Enumeration() {
            Node fCurrentHref = firstHref;
            Node fCurrentSegment = firstSegment;

            public boolean hasMoreElements() {

                return fCurrentHref != null && fCurrentSegment != null;
            }

            public Object nextElement() {

                if (!hasMoreElements())
                    throw new NoSuchElementException();

                String nextHref = getFirstText((Element) fCurrentHref);
                String nextSegment = getFirstText((Element) fCurrentSegment);
                Binding nextBinding = new Binding(decodeHref(nextHref), nextSegment);

                fCurrentHref = getNextSibling((Element) fCurrentSegment, "href"); //$NON-NLS-1$
                fCurrentSegment = null;
                if (fCurrentHref != null)
                    fCurrentSegment = getNextSibling((Element) fCurrentHref, "segment"); //$NON-NLS-1$

                return nextBinding;
            }
        };

        return e;
    }
}


/*
 * 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.internal.kernel;

import java.util.Enumeration;
import org.eclipse.webdav.dom.QualifiedName;
import org.eclipse.webdav.internal.kernel.utils.Assert;
import org.w3c.dom.*;

public class ElementHelper implements WebDAVConstants {

    // This W3C Document is the factory for Nodes
    // produced by this class.
    protected Document nodeFactory;

    protected final static String DAV_PREFIX = "D"; //$NON-NLS-1$
    protected final static String NS_SEPARATOR = ":"; //$NON-NLS-1$
    protected final static String DAV_NS_ATTRIB = "xmlns" + NS_SEPARATOR + DAV_PREFIX; //$NON-NLS-1$

    public ElementHelper() {
        super();
        IDocumentFactory factory = new DocumentFactory();
        this.nodeFactory = factory.newDocument();
    }

    public String extractText(Element element) {
        NodeList childNodes = element.getChildNodes();
        Assert.isTrue(childNodes.getLength() == 1, Policy.bind("error.requireElementWithSingleChild")); //$NON-NLS-1$
        Node child = childNodes.item(0);
        Assert.isTrue(child instanceof Text, Policy.bind("error.extractFromElementWithoutText")); //$NON-NLS-1$
        return ((Text) child).getData();
    }

    public Element newDAVElement(QualifiedName name) {
        Assert.isTrue(name.getQualifier().equals(DAV_URI), Policy.bind("error.davElementsNeedQualifier")); //$NON-NLS-1$
        Element element = nodeFactory.createElement(DAV_PREFIX + NS_SEPARATOR + name.getLocalName());
        element.setAttribute(DAV_NS_ATTRIB, DAV_URI);
        return element;
    }

    /**
     * Answer a DOM Element that represents a set of String.
     *

     * The set is represented by a single Element whose name is
     * given as the setName argument.  Each member of the set is
     * a child Element named memberName that has
     * text taken from the memberEnum an Enumeration
     * of String.
     */
    public Element newDAVElementSet(QualifiedName setName, QualifiedName memberName, Enumeration memberEnum) {
        Element setElement = newDAVElement(setName);
        while (memberEnum.hasMoreElements()) {
            String member = (String) memberEnum.nextElement();
            Element memberElement = newDAVTextElement(memberName, member);
            setElement.appendChild(memberElement);
        }
        return setElement;
    }

    public Element newDAVProperty(QualifiedName name, String value) {
        Element element = newDAVElement(name);
        element.appendChild(nodeFactory.createTextNode(value));
        return element;
    }

    public Element newDAVProperty(QualifiedName name, Element value) {
        Element element = newDAVElement(name);
        element.appendChild(value);
        return element;
    }

    public Element newDAVTextElement(QualifiedName name, String value) {
        Element element = newDAVElement(name);
        element.appendChild(nodeFactory.createTextNode(value));
        return element;
    }

    public Element newTextElement(String name, String value) {
        Element element = nodeFactory.createElement(name);
        element.appendChild(nodeFactory.createTextNode(value));
        return element;
    }
}


/*
 * 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.part;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;

import org.drools.eclipse.editors.rete.commands.NodeSetConstraintCommand;
import org.drools.eclipse.editors.rete.model.ModelElement;
import org.drools.eclipse.editors.rete.model.ReteGraph;
import org.drools.eclipse.reteoo.BaseVertex;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FreeformLayer;
import org.eclipse.draw2d.FreeformLayout;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.NodeEditPart;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.gef.editpolicies.RootComponentEditPolicy;
import org.eclipse.gef.editpolicies.XYLayoutEditPolicy;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gef.requests.CreateRequest;

/**
 * EditPart for ReteGraph
 */
public class DiagramEditPart extends AbstractGraphicalEditPart
    implements
    PropertyChangeListener {

    /* (non-Javadoc)
     * @see org.eclipse.gef.editparts.AbstractGraphicalEditPart#activate()
     */
    public void activate() {
        if ( !isActive() ) {
            super.activate();
            ((ModelElement) getModel()).addPropertyChangeListener( this );
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.gef.editparts.AbstractEditPart#createEditPolicies()
     */
    protected void createEditPolicies() {
        installEditPolicy( EditPolicy.COMPONENT_ROLE,
                           new RootComponentEditPolicy() );
        installEditPolicy( EditPolicy.LAYOUT_ROLE,
                           new NodesXYLayoutEditPolicy() );
    }

    /* (non-Javadoc)
     * @see org.eclipse.gef.editparts.AbstractGraphicalEditPart#createFigure()
     */
    protected IFigure createFigure() {
        Figure f = new FreeformLayer();
        f.setBorder( new MarginBorder( 3 ) );
        f.setLayoutManager( new FreeformLayout() );
        return f;
    }

    /* (non-Javadoc)
     * @see org.eclipse.gef.editparts.AbstractGraphicalEditPart#deactivate()
     */
    public void deactivate() {
        if ( isActive() ) {
            super.deactivate();
            ((ModelElement) getModel()).removePropertyChangeListener( this );
        }
    }

    private ReteGraph getCastedModel() {
        return (ReteGraph) getModel();
    }

    /* (non-Javadoc)
     * @see org.eclipse.gef.editparts.AbstractEditPart#getModelChildren()
     */
    protected List<BaseVertex> getModelChildren() {
        return getCastedModel().getChildren(); // return a list of nodes
    }

    /* (non-Javadoc)
     * @see java.beans.PropertyChangeListener#propertyChange(PropertyChangeEvent)
     */
    public void propertyChange(PropertyChangeEvent evt) {
        String prop = evt.getPropertyName();
        if ( ReteGraph.PROP_CHILD_ADDED.equals( prop ) || ReteGraph.PROP_CHILD_REMOVED.equals( prop ) ) {
            refreshChildren();
        }
    }

    private static class NodesXYLayoutEditPolicy extends XYLayoutEditPolicy {

        /* (non-Javadoc)
         * @see ConstrainedLayoutEditPolicy#createChangeConstraintCommand(ChangeBoundsRequest, EditPart, Object)
         */
        protected Command createChangeConstraintCommand(ChangeBoundsRequest request,
                                                        EditPart child,
                                                        Object constraint) {
            if ( child instanceof NodeEditPart && constraint instanceof Rectangle ) {
                // return a command that can move and/or resize a BaseVertex
                return new NodeSetConstraintCommand( (BaseVertex) child.getModel(),
                                                     request,
                                                     (Rectangle) constraint );
            }
            return super.createChangeConstraintCommand( request,
                                                        child,
                                                        constraint );
        }

        /* (non-Javadoc)
         * @see ConstrainedLayoutEditPolicy#createChangeConstraintCommand(EditPart, Object)
         */
        protected Command createChangeConstraintCommand(EditPart child,
                                                        Object constraint) {
            return null;
        }

        /* (non-Javadoc)
         * @see LayoutEditPolicy#getCreateCommand(CreateRequest)
         */
        protected Command getCreateCommand(CreateRequest request) {
            return null;
        }

    }

    /* (non-Javadoc)
     * @see org.eclipse.gef.editparts.AbstractGraphicalEditPart#getAdapter(java.lang.Class)
     */
    public Object getAdapter(@SuppressWarnings("rawtypes") Class key) {
        return super.getAdapter( key );
    }

}


/*
 * 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 java.net.URL;
import org.eclipse.webdav.IContext;
import org.eclipse.webdav.internal.kernel.utils.Assert;

/**
 * An HTTP request message.
 *

 * 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 Request extends Message {
    private String method;
    private URL resourceUrl;
    private IRequestBodyWriter requestBodyWriter;

    /**
     * Creates a request.
     *
     * @param method the type of request, for example: "PUT"
     * @param resourceUrl the URL of the target resource
     * @param context the request's header, or null for an empty
     * header
     */
    public Request(String method, URL resourceUrl, IContext context) {
        super(context, new RequestInputStream(new byte[0]));
        Assert.isNotNull(method);
        Assert.isNotNull(resourceUrl);
        this.method = method;
        this.resourceUrl = resourceUrl;
    }

    /**
     * Creates a request. For efficiency, the given input stream should be a
     * RequestInputStream or a ByteArrayInputStream.
     *
     * @param method the type of request, for example: "PUT"
     * @param resourceUrl the URL of the target resource
     * @param context the request's header, or null for an empty
     * header
     * @param is an input stream containing the message's body, or
     * null for an empty body
     * @exception IOException if there is an I/O error
     * @see RequestInputStream
     */
    public Request(String method, URL resourceUrl, IContext context, InputStream is) throws IOException {
        super(context, is == null ? new RequestInputStream(new byte[0]) : is instanceof RequestInputStream ? is : is instanceof ByteArrayInputStream ? new RequestInputStream((ByteArrayInputStream) is) : new RequestInputStream(is, -1));
        Assert.isNotNull(method);
        Assert.isNotNull(resourceUrl);
        this.method = method;
        this.resourceUrl = resourceUrl;
    }

    /**
     * Creates a request.
     *
     * @param method the type of request, for example: "PUT"
     * @param resourceUrl the URL of the target resource
     * @param context the message header, or null for an empty
     * header
     * @param requestBodyWriter for obtaining the message's body
     */
    public Request(String method, URL resourceUrl, IContext context, IRequestBodyWriter requestBodyWriter) {
        super(context, new RequestInputStream(new byte[0]));
        Assert.isNotNull(method);
        Assert.isNotNull(resourceUrl);
        Assert.isNotNull(requestBodyWriter);
        this.method = method;
        this.resourceUrl = resourceUrl;
        this.requestBodyWriter = requestBodyWriter;
    }

    /**
     * Returns the content length of this message's body, or -1 if the
     * content length is unknown.
     *
     * @return the content length of this message's body
     */
    public long getContentLength() {
        long contentLength = super.getContentLength();
        if (contentLength != -1)
            return contentLength;
        if (requestBodyWriter == null)
            return ((RequestInputStream) is).length();
        return -1;
    }

    /**
     * Returns the type of this request, for example: "PUT".
     *
     * @return the type of this request, for example: "PUT"
     */
    public String getMethod() {
        return method;
    }

    /**
     * Returns this request's request body writer, or null if
     * this request does not have one.
     *
     * @return this request's request body writer, or null
     */
    public IRequestBodyWriter getRequestBodyWriter() {
        return requestBodyWriter;
    }

    /**
     * Returns the URL of this request's target resource.
     *
     * @return the URL of this request's target resource
     */
    public URL getResourceUrl() {
        return resourceUrl;
    }

    void setResourceUrl(URL resourceUrl) {
        this.resourceUrl = resourceUrl;
    }

    public String toString() {
        StringBuffer buffer = new StringBuffer();
        buffer.append(method);
        buffer.append(' ');
        buffer.append(resourceUrl);
        buffer.append('\n');
        buffer.append(super.toString());
        return buffer.toString();
    }

    /**
     * Writes this request's body to the given output stream. This method may
     * be called more than once during the lifetime of this request.
     *
     * @param os an output stream
     * @exception IOException if there is an I/O error
     */
    public void write(OutputStream os) throws IOException {
        if (requestBodyWriter == null) {
            if (inputRead) {
                is.reset();
                inputRead = false;
            }
            super.write(os);
        } else {
            requestBodyWriter.writeRequestBody(os);
        }
    }
}


/*
 * 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.internal.authentication;

/**
 * This class provides a function that converts byte arrays to their
 * equivalent hexadecimal string.
 */
public final class HexConverter {
    /**
     * Converts the given byte array to its equivalent hexadecimal string
     * and returns the result.
     */
    public static String toHex(byte[] arr) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < arr.length; ++i) {
            buf.append(Integer.toHexString((arr[i] >> 4) & 0x0f));
            buf.append(Integer.toHexString(arr[i] & 0x0f));
        }
        return buf.toString();
    }

    /**
     * Converts the given int array to its equivalent hexadecimal string
     * and returns the result.
     */
    public static String toHex(int[] arr) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < arr.length; ++i) {
            buf.append(Integer.toHexString((arr[i] >> 28) & 0x0000000f));
            buf.append(Integer.toHexString((arr[i] >> 24) & 0x0000000f));
            buf.append(Integer.toHexString((arr[i] >> 20) & 0x0000000f));
            buf.append(Integer.toHexString((arr[i] >> 16) & 0x0000000f));
            buf.append(Integer.toHexString((arr[i] >> 12) & 0x0000000f));
            buf.append(Integer.toHexString((arr[i] >> 8) & 0x0000000f));
            buf.append(Integer.toHexString((arr[i] >> 4) & 0x0000000f));
            buf.append(Integer.toHexString((arr[i]) & 0x0000000f));
        }
        return buf.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.guvnor.tools.views;

import java.util.List;

import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.ui.progress.DeferredTreeContentManager;
import org.guvnor.tools.Activator;
import org.guvnor.tools.GuvnorRepository;
import org.guvnor.tools.utils.webdav.ResourceProperties;
import org.guvnor.tools.views.model.TreeObject;
import org.guvnor.tools.views.model.TreeParent;

/**
 * Content provider for representing Guvnor structure.
 */
public class RepositoryContentProvider implements IStructuredContentProvider,
                                                  ITreeContentProvider {
    private TreeParent invisibleRoot;
    private DeferredTreeContentManager manager;
    private AbstractTreeViewer viewer;

    private String repUrl;

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
     */
    public void inputChanged(Viewer v, Object oldInput, Object newInput) {
        if (v instanceof AbstractTreeViewer) {
            viewer = (AbstractTreeViewer)v;
            manager = new DeferredTreeContentManager(this, viewer);
        }
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.IContentProvider#dispose()
     */
    public void dispose() {
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
     */
    public Object[] getElements(Object parent) {
        if (parent.equals(viewer)) {
            if (invisibleRoot == null) initialize();
            return getChildren(invisibleRoot);
        }
        return getChildren(parent);
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
     */
    public Object getParent(Object child) {
        if (child instanceof TreeObject) {
            return ((TreeObject)child).getParent();
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
     */
    public Object [] getChildren(Object parent) {
        if (parent == invisibleRoot) {
            return ((TreeParent)invisibleRoot).getChildren();
        } else if (parent instanceof TreeParent) {
            return manager.getChildren(parent);
        }
        return new Object[0];
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
     */
    public boolean hasChildren(Object parent) {
        if (parent instanceof TreeParent) {
            return manager.mayHaveChildren(parent);
        }
        return false;
    }

    private void initialize() {
        boolean shouldAdd = true;
        invisibleRoot = new TreeParent("", TreeObject.Type.NONE); //$NON-NLS-1$
        List<GuvnorRepository> reps = Activator.getLocationManager().getRepositories();
        for (int i = 0; i < reps.size(); i++) {
            if (repUrl != null) {
                if (repUrl.equals(reps.get(i).getLocation())) {
                    shouldAdd = true;
                } else {
                    shouldAdd = false;
                }
            } else {
                shouldAdd = true;
            }
            if (shouldAdd) {
                TreeParent p = new TreeParent(reps.get(i).getLocation(),
                                             TreeObject.Type.REPOSITORY);
                p.setGuvnorRepository(reps.get(i));
                ResourceProperties props = new ResourceProperties();
                props.setBase(""); //$NON-NLS-1$
                p.setResourceProps(props);
                invisibleRoot.addChild(p);
            }
        }
    }

    public void setRepositorySelection(String repUrl) {
        this.repUrl = repUrl;
    }
}


/*
 * 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 org.eclipse.webdav.Policy;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * An element editor for the WebDAV propfind element. See RFC2518
 * section 12.14 for the element's definition.
 *

 * 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.
 *

 *
 * @see Prop
 */
public class PropFind extends ElementEditor {
    /**
     * An ordered collection of the element names of the propfind
     * element's children.
     */
    protected static final String[] childNames = new String[] {"allprop", "propname", "prop"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

    /**
     * Creates a new editor on the given WebDAV propfind element. The
     * element is assumed to be well formed.
     *
     * @param root a propfind element
     * @throws        MalformedElementException if there is reason to
     *                believe that the element is not well formed
     */
    public PropFind(Element root) throws MalformedElementException {
        super(root, "propfind"); //$NON-NLS-1$
    }

    /**
     * Creates a new WebDAV propfind element and sets it as the root of the
     * given document. Returns an editor on the new propfind element. The
     * document must not be null, and must not already have a
     * root element.
     *
     * @param document the document that will become the root of a new
     *                 propfind element
     * @return         an element editor on a propfind element
     */
    public static PropFind create(Document document) {
        Assert.isNotNull(document);
        Assert.isTrue(document.getDocumentElement() == null);
        Element element = create(document, "propfind"); //$NON-NLS-1$
        try {
            return new PropFind(element);
        } catch (MalformedElementException e) {
            Assert.isTrue(false, Policy.bind("assert.internalError")); //$NON-NLS-1$
            return null;
        }
    }

    /**
     * Returns this propfind's prop.
     *
     * @return this propfind's prop
     * @throws MalformedElementException if there is reason to believe that
     *         this editor's underlying element is not well formed, or if
     *         this propfind is not in the "prop" form
     */
    public Prop getProp() throws MalformedElementException {
        Element prop = getFirstChild(root, "prop"); //$NON-NLS-1$
        ensureNotNull(Policy.bind("ensure.missingPropElmt"), prop); //$NON-NLS-1$
        return new Prop(prop);
    }

    /**
     * Returns true iff this propfind is in the "all prop"
     * form.
     *
     * @return a boolean indicating whether this propfind is in the "all
     *         prop" form
     * @throws MalformedElementException if there is reason to believe that
     *         this editor's underlying element is not well formed
     */
    public boolean isAllProp() throws MalformedElementException {
        Element child = getFirstChild(root, childNames);
        ensureNotNull(Policy.bind("ensure.missingAllpropOrPropnameOrPropElmt"), child); //$NON-NLS-1$
        boolean isAllProp = isDAVElement(child, "allprop"); //$NON-NLS-1$
        child = getNextSibling(child, childNames);
        ensureNull(Policy.bind("ensure.conflictingAllpropOrPropnameOrPropElmt"), child); //$NON-NLS-1$
        return isAllProp;
    }

    /**
     * Returns true iff this propfind is in the "prop" form.
     *
     * @return a boolean indicating whether this propfind is in the "prop"
     *         form
     * @throws MalformedElementException if there is reason to believe that
     *         this editor's underlying element is not well formed
     */
    public boolean isProp() throws MalformedElementException {
        Element child = getFirstChild(root, childNames);
        ensureNotNull(Policy.bind("ensure.missingAllpropOrPropnameOrPropElmt"), child); //$NON-NLS-1$
        boolean isProp = isDAVElement(child, "prop"); //$NON-NLS-1$
        child = getNextSibling(child, childNames);
        ensureNull(Policy.bind("ensure.conflictingAllpropOrPropnameOrPropElmt"), child); //$NON-NLS-1$
        return isProp;
    }

    /**
     * Returns true iff this propfind is in the "prop name"
     * form.
     *
     * @return a boolean indicating whether this propfind is in the
     *         "propname" form
     * @throws MalformedElementException if there is reason to believe that
     *         this editor's underlying element is not well formed
     */
    public boolean isPropName() throws MalformedElementException {
        Element child = getFirstChild(root, childNames);
        ensureNotNull(Policy.bind("ensure.missingAllpropOrPropnameOrPropElmt"), child); //$NON-NLS-1$
        boolean isPropName = isDAVElement(child, "propname"); //$NON-NLS-1$
        child = getNextSibling(child, childNames);
        ensureNull(Policy.bind("ensure.conflictingAllpropOrPropnameOrPropElmt"), child); //$NON-NLS-1$
        return isPropName;
    }

    /**
     * Sets whether this propfind is in the "all prop" form.
     *
     * @param isAllProp boolean indicating whether this propfind will be in the
     *        "all prop" form
     */
    public void setIsAllProp(boolean isAllProp) {
        Element child = getFirstChild(root, childNames);
        boolean isAlreadyAllProp = isDAVElement(child, "allprop"); //$NON-NLS-1$
        if (isAllProp) {
            if (!isAlreadyAllProp) {
                if (child != null)
                    root.removeChild(child);
                appendChild(root, "allprop"); //$NON-NLS-1$
            }
        } else if (isAlreadyAllProp)
            root.removeChild(child);
    }

    /**
     * Sets whether this propfind is in the "prop name" form.
     *
     * @param isPropName boolean indicating whether this propfind will be in the
     *        "prop name" form
     */
    public void setIsPropName(boolean isPropName) {
        Element child = getFirstChild(root, childNames);
        boolean isAlreadyPropName = isDAVElement(child, "propname"); //$NON-NLS-1$
        if (isPropName) {
            if (!isAlreadyPropName) {
                if (child != null)
                    root.removeChild(child);
                appendChild(root, "propname"); //$NON-NLS-1$
            }
        } else if (isAlreadyPropName)
            root.removeChild(child);
    }

    /**
     * Creates and sets a new prop on this propfind and returns an editor on
     * it. This propfind must not already be in the "all prop" or "prop
     * name" form.
     *
     * @return an editor on a new prop element
     */
    public Prop setProp() {
        Assert.isTrue(getFirstChild(root, new String[] {"allprop", "propname"}) == null); //$NON-NLS-1$ //$NON-NLS-2$
        Element prop = setChild(root, "prop", new String[] {"prop"}, true); //$NON-NLS-1$ //$NON-NLS-2$
        try {
            return new Prop(prop);
        } catch (MalformedElementException e) {
            Assert.isTrue(false, Policy.bind("assert.internalError")); //$NON-NLS-1$
            return null; // Never reached.
        }
    }
}