/*
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 *
 * This software is open source.
 * See the bottom of this file for the licence.
 */

package org.dom4j;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Map;

import org.dom4j.rule.Pattern;
import org.dom4j.tree.AbstractDocument;
import org.dom4j.tree.DefaultAttribute;
import org.dom4j.tree.DefaultCDATA;
import org.dom4j.tree.DefaultComment;
import org.dom4j.tree.DefaultDocument;
import org.dom4j.tree.DefaultDocumentType;
import org.dom4j.tree.DefaultElement;
import org.dom4j.tree.DefaultEntity;
import org.dom4j.tree.DefaultProcessingInstruction;
import org.dom4j.tree.DefaultText;
import org.dom4j.tree.QNameCache;
import org.dom4j.util.SimpleSingleton;
import org.dom4j.util.SingletonStrategy;
import org.dom4j.xpath.DefaultXPath;
import org.dom4j.xpath.XPathPattern;
import org.jaxen.VariableContext;

/**
 * <code>DocumentFactory</code> is a collection of factory methods to allow
 * easy custom building of DOM4J trees. The default tree that is built uses a
 * doubly linked tree.
 *
 * The tree built allows full XPath expressions from anywhere on the tree.
 *
 * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
 */
@SuppressWarnings("unused")
public class DocumentFactory implements Serializable {
    private static SingletonStrategy<DocumentFactory> singleton = null;

    protected transient QNameCache cache;

    /** Default namespace prefix → URI mappings for XPath expressions to use */
    private Map<String, String> xpathNamespaceURIs;

    private static SingletonStrategy<DocumentFactory> createSingleton() {
        SingletonStrategy<DocumentFactory> result;
        
        String documentFactoryClassName;
        try {
            documentFactoryClassName = System.getProperty("org.dom4j.factory",
                    "org.dom4j.DocumentFactory");
        } catch (Exception e) {
            documentFactoryClassName = "org.dom4j.DocumentFactory";
        }

        try {
            String singletonClass = System.getProperty(
                    "org.dom4j.DocumentFactory.singleton.strategy",
                    "org.dom4j.util.SimpleSingleton");
            Class<SingletonStrategy> clazz = (Class<SingletonStrategy>) Class.forName(singletonClass);
            result = clazz.newInstance();
        } catch (Exception e) {
            result = new SimpleSingleton<DocumentFactory>();
        }

        result.setSingletonClassName(documentFactoryClassName);
        
        return result;
    }

    public DocumentFactory() {
        init();
    }

    /**
     * Access to singleton implementation of DocumentFactory which is used if no
     * DocumentFactory is specified when building using the standard builders.
     *
     * @return the default singleon instance
     */
    public static synchronized DocumentFactory getInstance() {
        if (singleton == null) {
            singleton = createSingleton();
        }
        return singleton.instance();
    }

    // Factory methods
    public Document createDocument() {
        DefaultDocument answer = new DefaultDocument();
        answer.setDocumentFactory(this);

        return answer;
    }

    /**
     * DOCUMENT ME!
     * 
     * @param encoding
     *            DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
     * 
     * @since 1.5
     */
    public Document createDocument(String encoding) {
        // to keep the DocumentFactory backwards compatible, we have to do this
        // in this not so nice way, since subclasses only need to extend the
        // createDocument() method.
        Document answer = createDocument();

        answer.setXMLEncoding(encoding);
        return answer;
    }

    public Document createDocument(Element rootElement) {
        Document answer = createDocument();
        answer.setRootElement(rootElement);

        return answer;
    }

    public DocumentType createDocType(String name, String publicId,
            String systemId) {
        return new DefaultDocumentType(name, publicId, systemId);
    }

    public Element createElement(QName qname) {
        return new DefaultElement(qname);
    }

    public Element createElement(String name) {
        return createElement(createQName(name));
    }

    public Element createElement(String qualifiedName, String namespaceURI) {
        return createElement(createQName(qualifiedName, namespaceURI));
    }

    public Attribute createAttribute(Element owner, QName qname, String value) {
        return new DefaultAttribute(qname, value);
    }

    public Attribute createAttribute(Element owner, String name, String value) {
        return createAttribute(owner, createQName(name), value);
    }

    public CDATA createCDATA(String text) {
        return new DefaultCDATA(text);
    }

    public Comment createComment(String text) {
        return new DefaultComment(text);
    }

    public Text createText(String text) {
        if (text == null) {
            String msg = "Adding text to an XML document must not be null";
            throw new IllegalArgumentException(msg);
        }

        return new DefaultText(text);
    }

    public Entity createEntity(String name, String text) {
        return new DefaultEntity(name, text);
    }

    public Namespace createNamespace(String prefix, String uri) {
        return Namespace.get(prefix, uri);
    }

    public ProcessingInstruction createProcessingInstruction(String target,
            String data) {
        return new DefaultProcessingInstruction(target, data);
    }

    public ProcessingInstruction createProcessingInstruction(String target,
            Map<String, String> data) {
        return new DefaultProcessingInstruction(target, data);
    }

    public QName createQName(String localName, Namespace namespace) {
        return cache.get(localName, namespace);
    }

    public QName createQName(String localName) {
        return cache.get(localName);
    }

    public QName createQName(String name, String prefix, String uri) {
        return cache.get(name, Namespace.get(prefix, uri));
    }

    public QName createQName(String qualifiedName, String uri) {
        return cache.get(qualifiedName, uri);
    }

    /**
     * <p>
     * <code>createXPath</code> parses an XPath expression and creates a new
     * XPath <code>XPath</code> instance.
     * </p>
     * 
     * @param xpathExpression
     *            is the XPath expression to create
     * 
     * @return a new <code>XPath</code> instance
     * 
     * @throws InvalidXPathException
     *             if the XPath expression is invalid
     */
    public XPath createXPath(String xpathExpression)
            throws InvalidXPathException {
        DefaultXPath xpath = new DefaultXPath(xpathExpression);

        if (xpathNamespaceURIs != null) {
            xpath.setNamespaceURIs(xpathNamespaceURIs);
        }

        return xpath;
    }

    /**
     * <p>
     * <code>createXPath</code> parses an XPath expression and creates a new
     * XPath <code>XPath</code> instance.
     * </p>
     * 
     * @param xpathExpression
     *            is the XPath expression to create
     * @param variableContext
     *            is the variable context to use when evaluating the XPath
     * 
     * @return a new <code>XPath</code> instance
     */
    public XPath createXPath(String xpathExpression,
            VariableContext variableContext) {
        XPath xpath = createXPath(xpathExpression);
        xpath.setVariableContext(variableContext);

        return xpath;
    }

    /**
     * <p>
     * <code>createXPathFilter</code> parses a NodeFilter from the given XPath
     * filter expression. XPath filter expressions occur within XPath
     * expressions such as <code>self::node()[ filterExpression ]</code>
     * </p>
     * 
     * @param xpathFilterExpression
     *            is the XPath filter expression to create
     * @param variableContext
     *            is the variable context to use when evaluating the XPath
     * 
     * @return a new <code>NodeFilter</code> instance
     */
    public NodeFilter createXPathFilter(String xpathFilterExpression,
            VariableContext variableContext) {
        XPath answer = createXPath(xpathFilterExpression);

        // DefaultXPath answer = new DefaultXPath( xpathFilterExpression );
        answer.setVariableContext(variableContext);

        return answer;
    }

    /**
     * <p>
     * <code>createXPathFilter</code> parses a NodeFilter from the given XPath
     * filter expression. XPath filter expressions occur within XPath
     * expressions such as <code>self::node()[ filterExpression ]</code>
     * </p>
     * 
     * @param xpathFilterExpression
     *            is the XPath filter expression to create
     * 
     * @return a new <code>NodeFilter</code> instance
     */
    public NodeFilter createXPathFilter(String xpathFilterExpression) {
        return createXPath(xpathFilterExpression);

        // return new DefaultXPath( xpathFilterExpression );
    }

    /**
     * <p>
     * <code>createPattern</code> parses the given XPath expression to create
     * an XSLT style {@link Pattern}instance which can then be used in an XSLT
     * processing model.
     * </p>
     * 
     * @param xpathPattern
     *            is the XPath pattern expression to create
     * 
     * @return a new <code>Pattern</code> instance
     */
    public Pattern createPattern(String xpathPattern) {
        return new XPathPattern(xpathPattern);
    }

    // Properties
    // -------------------------------------------------------------------------

    /**
     * Returns a list of all the QName instances currently used by this document
     * factory
     * 
     * @return DOCUMENT ME!
     */
    public List<QName> getQNames() {
        return cache.getQNames();
    }

    /**
     * DOCUMENT ME!
     * 
     * @return the Map of namespace URIs that will be used by by XPath
     *         expressions to resolve namespace prefixes into namespace URIs.
     *         The map is keyed by namespace prefix and the value is the
     *         namespace URI. This value could well be null to indicate no
     *         namespace URIs are being mapped.
     */
    public Map<String, String> getXPathNamespaceURIs() {
        return xpathNamespaceURIs;
    }

    /**
     * Sets the namespace URIs to be used by XPath expressions created by this
     * factory or by nodes associated with this factory. The keys are namespace
     * prefixes and the values are namespace URIs.
     * 
     * @param namespaceURIs
     *            DOCUMENT ME!
     */
    public void setXPathNamespaceURIs(Map<String, String> namespaceURIs) {
        this.xpathNamespaceURIs = namespaceURIs;
    }

    // Implementation methods
    // -------------------------------------------------------------------------

    /**
     * <p>
     * <code>createSingleton</code> creates the singleton instance from the
     * given class name.
     * </p>
     * 
     * @param className
     *            is the name of the DocumentFactory class to use
     * 
     * @return a new singleton instance.
     */
    protected static DocumentFactory createSingleton(String className) {
        // let's try and class load an implementation?
        try {
            // I'll use the current class loader
            // that loaded me to avoid problems in J2EE and web apps
            Class<DocumentFactory> theClass = (Class<DocumentFactory>) Class.forName(className, true,
                    DocumentFactory.class.getClassLoader());

            return theClass.newInstance();
        } catch (Throwable e) {
            System.out.println("WARNING: Cannot load DocumentFactory: "
                    + className);

            return new DocumentFactory();
        }
    }

    /**
     * DOCUMENT ME!
     * 
     * @param qname
     *            DOCUMENT ME!
     * 
     * @return the cached QName instance if there is one or adds the given qname
     *         to the cache if not
     */
    protected QName intern(QName qname) {
        return cache.intern(qname);
    }

    /**
     * Factory method to create the QNameCache. This method should be overloaded
     * if you wish to use your own derivation of QName.
     * 
     * @return DOCUMENT ME!
     */
    protected QNameCache createQNameCache() {
        return new QNameCache(this);
    }

    private void readObject(ObjectInputStream in) throws IOException,
            ClassNotFoundException {
        in.defaultReadObject();
        init();
    }

    protected void init() {
        cache = createQNameCache();
    }
}

/*
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided that the
 * following conditions are met:
 * 
 * 1. Redistributions of source code must retain copyright statements and
 * notices. Redistributions must also contain a copy of this document.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. The name "DOM4J" must not be used to endorse or promote products derived
 * from this Software without prior written permission of MetaStuff, Ltd. For
 * written permission, please contact dom4j-info@metastuff.com.
 * 
 * 4. Products derived from this Software may not be called "DOM4J" nor may
 * "DOM4J" appear in their names without prior written permission of MetaStuff,
 * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
 * 
 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
 * 
 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 */