/*
 * reserved comment block
 * DO NOT REMOVE OR ALTER!
 */
/*
 * Copyright  1999-2004 The Apache Software Foundation.
 *
 *  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 com.sun.org.apache.xml.internal.security.utils;

import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.WeakHashMap;
import java.util.Map;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;


/**
 * Purpose of this class is to enable the XML Parser to keep track of ID
 * attributes. This is done by 'registering' attributes of type ID at the
 * IdResolver. This is necessary if we create a document from scratch and we
 * sign some resources with a URI using a fragent identifier...
 * <BR />
 * The problem is that if you do not validate a document, you cannot use the
 * <CODE>getElementByID</CODE> functionality. So this modules uses some implicit
 * knowledge on selected Schemas and DTDs to pick the right Element for a given
 * ID: We know that all <CODE>@Id</CODE> attributes in an Element from the XML
 * Signature namespace are of type <CODE>ID</CODE>.
 *
 * @author $Author: mullan $
 * @see <A HREF="http://www.xml.com/lpt/a/2001/11/07/id.html">"Identity Crisis" on xml.com</A>
 */
public class IdResolver {

    /** {@link java.util.logging} logging facility */
    private static java.util.logging.Logger log =
        java.util.logging.Logger.getLogger(IdResolver.class.getName());

    private static Map<Document, Map<String, WeakReference<Element>>> docMap =
                    new WeakHashMap<Document, Map<String, WeakReference<Element>>>();

    /**
     * Constructor IdResolver
     *
     */
    private IdResolver() {
       // we don't allow instantiation
    }

    /**
     * Method registerElementById
     *
     * @param element the element to register
     * @param idValue the value of the ID attribute
     */
    public static void registerElementById(Element element, String idValue) {
        Document doc = element.getOwnerDocument();
        Map<String, WeakReference<Element>> elementMap;
        synchronized (docMap) {
            elementMap = docMap.get(doc);
            if (elementMap == null) {
                elementMap = new WeakHashMap<String, WeakReference<Element>>();
                docMap.put(doc, elementMap);
            }
        }
        elementMap.put(idValue, new WeakReference<Element>(element));
    }

    /**
     * Method registerElementById
     *
     * @param element the element to register
     * @param id the ID attribute
     */
    public static void registerElementById(Element element, Attr id) {
        IdResolver.registerElementById(element, id.getNodeValue());
    }

    /**
     * Method getElementById
     *
     * @param doc the document
     * @param id the value of the ID
     * @return the element obtained by the id, or null if it is not found.
     */
    public static Element getElementById(Document doc, String id) {

        Element result = IdResolver.getElementByIdType(doc, id);

        if (result != null) {
            log.log(java.util.logging.Level.FINE,
            "I could find an Element using the simple getElementByIdType method: "
            + result.getTagName());

            return result;
        }

        result = IdResolver.getElementByIdUsingDOM(doc, id);

        if (result != null) {
            log.log(java.util.logging.Level.FINE,
             "I could find an Element using the simple getElementByIdUsingDOM method: "
            + result.getTagName());

            return result;
        }
        // this must be done so that Xalan can catch ALL namespaces
        //XMLUtils.circumventBug2650(doc);
        result = IdResolver.getElementBySearching(doc, id);

        if (result != null) {
            IdResolver.registerElementById(result, id);

            return result;
        }

        return null;
    }


    /**
     * Method getElementByIdUsingDOM
     *
     * @param doc the document
     * @param id the value of the ID
     * @return the element obtained by the id, or null if it is not found.
     */
    private static Element getElementByIdUsingDOM(Document doc, String id) {
        if (log.isLoggable(java.util.logging.Level.FINE))
            log.log(java.util.logging.Level.FINE, "getElementByIdUsingDOM() Search for ID " + id);
        return doc.getElementById(id);
    }

    /**
     * Method getElementByIdType
     *
     * @param doc the document
     * @param id the value of the ID
     * @return the element obtained by the id, or null if it is not found.
     */
    private static Element getElementByIdType(Document doc, String id) {
        if (log.isLoggable(java.util.logging.Level.FINE))
            log.log(java.util.logging.Level.FINE, "getElementByIdType() Search for ID " + id);
        Map<String, WeakReference<Element>> elementMap;
        synchronized (docMap) {
            elementMap = docMap.get(doc);
        }
        if (elementMap != null) {
            WeakReference<Element> weakReference =  elementMap.get(id);
            if (weakReference != null) {
                return weakReference.get();
            }
        }
        return null;
    }

    private static java.util.List<String> names;
    private static int namesLength;
    static {
        String namespaces[]={
            Constants.SignatureSpecNS,
            EncryptionConstants.EncryptionSpecNS,
            "http://schemas.xmlsoap.org/soap/security/2000-12",
            "http://www.w3.org/2002/03/xkms#",
            "urn:oasis:names:tc:SAML:1.0:assertion",
            "urn:oasis:names:tc:SAML:1.0:protocol"
        };
        names = Arrays.asList(namespaces);
        namesLength = names.size();
    }


    private static Element getElementBySearching(Node root,String id) {
        Element []els=new Element[namesLength + 1];
        getEl(root,id,els);
        for (int i=0;i<els.length;i++) {
            if (els[i]!=null) {
                return els[i];
            }
        }
        return null;
    }

    private static int getEl(Node currentNode,String id,Element []els) {
        Node sibling=null;
        Node parentNode=null;
        do {
                switch (currentNode.getNodeType()) {
                case Node.DOCUMENT_FRAGMENT_NODE :
                case Node.DOCUMENT_NODE :
                        sibling= currentNode.getFirstChild();
                        break;


                case Node.ELEMENT_NODE :
                        Element currentElement = (Element) currentNode;
                        if (isElement(currentElement, id, els)==1)
                                return 1;
                        sibling= currentNode.getFirstChild();
                        if (sibling==null) {
                            if (parentNode != null) {
                                        sibling= currentNode.getNextSibling();
                                    }
                        } else {
                                parentNode=currentElement;
                        }
                        break;
        } while (sibling==null  && parentNode!=null) {
                        sibling=parentNode.getNextSibling();
                        parentNode=parentNode.getParentNode();
                        if (!(parentNode instanceof Element)) {
                                parentNode=null;
                        }
                }
                if (sibling==null)
                        return 1;
                currentNode=sibling;
                sibling=currentNode.getNextSibling();
        } while(true);

    }
    public static int isElement(Element el, String id,Element[] els) {
        if (!el.hasAttributes()) {
                return 0;
        }
        NamedNodeMap ns=el.getAttributes();
        int elementIndex=names.indexOf(el.getNamespaceURI());
            elementIndex=(elementIndex<0) ? namesLength : elementIndex;
        for (int length=ns.getLength(), i=0; i<length; i++) {
                Attr n=(Attr)ns.item(i);
                String s=n.getNamespaceURI();

                    int index=s==null ? elementIndex : names.indexOf(n.getNamespaceURI());
                    index=(index<0) ? namesLength : index;
                    String name=n.getLocalName();
                    if (name == null)
                        name = n.getName();
                    if (name.length()>2)
                        continue;
                    String value=n.getNodeValue();
                    if (name.charAt(0)=='I') {
                        char ch=name.charAt(1);
                        if (ch=='d' && value.equals(id)) {
                                els[index]=el;
                                if (index==0) {
                                        return 1;
                                }
                        } else if (ch=='D' &&value.endsWith(id)) {
                                if (index!=3) {
                                    index=namesLength;
                                }
                                els[index]=el;
                        }
                    } else if ( "id".equals(name) && value.equals(id) ) {
                        if (index!=2) {
                                index=namesLength;
                        }
                        els[index]=el;
                    }
        }
        //For an element namespace search for importants
        if ((elementIndex==3)&&(
                    el.getAttribute("OriginalRequestID").equals(id) ||
                    el.getAttribute("RequestID").equals(id) ||
                    el.getAttribute("ResponseID").equals(id))) {
                    els[3]=el;
        } else if ((elementIndex==4)&&(
                    el.getAttribute("AssertionID").equals(id))) {
                    els[4]=el;
        } else if ((elementIndex==5)&&(
                    el.getAttribute("RequestID").equals(id) ||
                    el.getAttribute("ResponseID").equals(id))) {
                    els[5]=el;
                 }
        return 0;
    }
}
