/*
 * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Copyright 2005 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.xml.internal.stream.dtd.nonvalidating;

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

import com.sun.org.apache.xerces.internal.util.SymbolTable;
import com.sun.org.apache.xerces.internal.xni.Augmentations;
import com.sun.org.apache.xerces.internal.xni.QName;
import com.sun.org.apache.xerces.internal.util.XMLSymbols;
import com.sun.org.apache.xerces.internal.xni.XMLLocator;
import com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier;
import com.sun.org.apache.xerces.internal.xni.XMLString;
import com.sun.org.apache.xerces.internal.xni.XNIException;
import com.sun.org.apache.xerces.internal.xni.parser.XMLDTDContentModelSource;
import com.sun.org.apache.xerces.internal.xni.parser.XMLDTDSource;

/**
 * A DTD grammar. This class implements the XNI handler interfaces
 * for DTD information so that it can build the approprate validation
 * structures automatically from the callbacks.
 *
 * @author Eric Ye, IBM
 * @author Jeffrey Rodriguez, IBM
 * @author Andy Clark, IBM
 * @author Neil Graham, IBM
 *
 */
public class DTDGrammar {


    /** Top level scope (-1). */
    public static final int TOP_LEVEL_SCOPE = -1;

    // private

    /** Chunk shift (8). */
    private static final int CHUNK_SHIFT = 8; // 2^8 = 256

    /** Chunk size (1 << CHUNK_SHIFT). */
    private static final int CHUNK_SIZE = (1 << CHUNK_SHIFT);

    /** Chunk mask (CHUNK_SIZE - 1). */
    private static final int CHUNK_MASK = CHUNK_SIZE - 1;

    /** Initial chunk count (1 << (10 - CHUNK_SHIFT)). */
    private static final int INITIAL_CHUNK_COUNT = (1 << (10 - CHUNK_SHIFT)); // 2^10 = 1k

    /** List flag (0x80). */
    private static final short LIST_FLAG = 0x80;

    /** List mask (~LIST_FLAG). */
    private static final short LIST_MASK = ~LIST_FLAG;

    // debugging

    /** Debug DTDGrammar. */
    private static final boolean DEBUG = false;

    //
    // Data
    //

    protected XMLDTDSource fDTDSource = null;
    protected XMLDTDContentModelSource fDTDContentModelSource = null;

    /** Current element index. */
    protected int fCurrentElementIndex;

    /** Current attribute index. */
    protected int fCurrentAttributeIndex;

    /** fReadingExternalDTD */
    protected boolean fReadingExternalDTD = false;

    /** Symbol table. */
    private SymbolTable fSymbolTable;
    private ArrayList notationDecls = new ArrayList();

    // element declarations

    /** Number of element declarations. */
    private int fElementDeclCount = 0;

    /** Element declaration name. */
    private QName fElementDeclName[][] = new QName[INITIAL_CHUNK_COUNT][];

    /**
     * Element declaration type.
     * @see XMLElementDecl
     */
    private short fElementDeclType[][] = new short[INITIAL_CHUNK_COUNT][];


    /** First attribute declaration of an element declaration. */
    private int fElementDeclFirstAttributeDeclIndex[][] = new int[INITIAL_CHUNK_COUNT][];

    /** Last attribute declaration of an element declaration. */
    private int fElementDeclLastAttributeDeclIndex[][] = new int[INITIAL_CHUNK_COUNT][];

    // attribute declarations

    /** Number of attribute declarations. */
    private int fAttributeDeclCount = 0 ;

    /** Attribute declaration name. */
    private QName fAttributeDeclName[][] = new QName[INITIAL_CHUNK_COUNT][];

    /**
     * Attribute declaration type.
     * @see XMLAttributeDecl
     */
    private short fAttributeDeclType[][] = new short[INITIAL_CHUNK_COUNT][];

    /** Attribute declaration enumeration values. */
    private String[] fAttributeDeclEnumeration[][] = new String[INITIAL_CHUNK_COUNT][][];
    private short fAttributeDeclDefaultType[][] = new short[INITIAL_CHUNK_COUNT][];
    private String fAttributeDeclDefaultValue[][] = new String[INITIAL_CHUNK_COUNT][];
    private String fAttributeDeclNonNormalizedDefaultValue[][] = new String[INITIAL_CHUNK_COUNT][];
    private int fAttributeDeclNextAttributeDeclIndex[][] = new int[INITIAL_CHUNK_COUNT][];

    /** Element index mapping table. */
    private QNameHashtable fElementIndexMap = new QNameHashtable();

    /** Temporary qualified name. */
    private QName fQName = new QName();

    /** Temporary Attribute decl. */
    protected XMLAttributeDecl fAttributeDecl = new XMLAttributeDecl();

    /** Element declaration. */
    private XMLElementDecl fElementDecl = new XMLElementDecl();

    /** Simple type. */
    private XMLSimpleType fSimpleType = new XMLSimpleType();


    /** table of XMLElementDecl   */
    Hashtable   fElementDeclTab     = new Hashtable();

    /** Default constructor. */
    public DTDGrammar(SymbolTable symbolTable) {
        fSymbolTable = symbolTable;
    }

    public int getAttributeDeclIndex(int elementDeclIndex, String attributeDeclName) {
        if (elementDeclIndex == -1) {
            return -1;
        }
        int attDefIndex = getFirstAttributeDeclIndex(elementDeclIndex);
        while (attDefIndex != -1) {
            getAttributeDecl(attDefIndex, fAttributeDecl);

            if (fAttributeDecl.name.rawname == attributeDeclName
            || attributeDeclName.equals(fAttributeDecl.name.rawname) ) {
                return attDefIndex;
            }
            attDefIndex = getNextAttributeDeclIndex(attDefIndex);
        }
        return -1;
    }

    /**
     * The start of the DTD.
     *
     * @param locator  The document locator, or null if the document
     *                 location cannot be reported during the parsing of
     *                 the document DTD. However, it is <em>strongly</em>
     *                 recommended that a locator be supplied that can
     *                 at least report the base system identifier of the
     *                 DTD.
     *
     * @param augs Additional information that may include infoset
     *                      augmentations.
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void startDTD(XMLLocator locator, Augmentations augs) throws XNIException {
    } // startDTD(XMLLocator)

    // startExternalSubset(Augmentations)

    // endExternalSubset(Augmentations)

    /**
     * An element declaration.
     *
     * @param name         The name of the element.
     * @param contentModel The element content model.
     * @param augs Additional information that may include infoset
     *                      augmentations.
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void elementDecl(String name, String contentModel, Augmentations augs)
    throws XNIException {

        XMLElementDecl tmpElementDecl = (XMLElementDecl) fElementDeclTab.get(name) ;
        if ( tmpElementDecl != null ) {
            if (tmpElementDecl.type == -1) {
                fCurrentElementIndex = getElementDeclIndex(name);
            }
            else {
                // duplicate element, ignored.
                return;
            }
        }
        else {
            fCurrentElementIndex = createElementDecl();//create element decl
        }

        XMLElementDecl elementDecl       = new XMLElementDecl();
        QName          elementName       = new QName(null, name, name, null);

        elementDecl.name.setValues(elementName);
        elementDecl.scope= -1;
        if (contentModel.equals("EMPTY")) {
            elementDecl.type = XMLElementDecl.TYPE_EMPTY;
        }
        else if (contentModel.equals("ANY")) {
            elementDecl.type = XMLElementDecl.TYPE_ANY;
        }
        else if (contentModel.startsWith("(") ) {
            if (contentModel.indexOf("#PCDATA") > 0 ) {
                elementDecl.type = XMLElementDecl.TYPE_MIXED;
            }
            else {
                elementDecl.type = XMLElementDecl.TYPE_CHILDREN;
            }
        }


        //add(or set) this elementDecl to the local cache
        this.fElementDeclTab.put(name, elementDecl );

        fElementDecl = elementDecl;


        if ( DEBUG ) {
            System.out.println(  "name = " + fElementDecl.name.localpart );
            System.out.println(  "Type = " + fElementDecl.type );
        }

        setElementDecl(fCurrentElementIndex, fElementDecl );//set internal structure

        int chunk = fCurrentElementIndex >> CHUNK_SHIFT;
        ensureElementDeclCapacity(chunk);
    }

    /**
     * An attribute declaration.
     *
     * @param elementName   The name of the element that this attribute
     *                      is associated with.
     * @param attributeName The name of the attribute.
     * @param type          The attribute type. This value will be one of
     *                      the following: "CDATA", "ENTITY", "ENTITIES",
     *                      "ENUMERATION", "ID", "IDREF", "IDREFS",
     *                      "NMTOKEN", "NMTOKENS", or "NOTATION".
     * @param enumeration   If the type has the value "ENUMERATION", this
     *                      array holds the allowed attribute values;
     *                      otherwise, this array is null.
     * @param defaultType   The attribute default type. This value will be
     *                      one of the following: "#FIXED", "#IMPLIED",
     *                      "#REQUIRED", or null.
     * @param defaultValue  The attribute default value, or null if no
     *                      default value is specified.
     * @param nonNormalizedDefaultValue  The attribute default value with no normalization
     *                      performed, or null if no default value is specified.
     *
     * @param augs Additional information that may include infoset
     *                      augmentations.
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void attributeDecl(String elementName, String attributeName,
    String type, String[] enumeration,
    String defaultType, XMLString defaultValue,
    XMLString nonNormalizedDefaultValue, Augmentations augs) throws XNIException {

        if (type != XMLSymbols.fCDATASymbol && defaultValue != null) {
            normalizeDefaultAttrValue(defaultValue);
        }

        if ( this.fElementDeclTab.containsKey( (String) elementName) ) {
            //if ElementDecl has already being created in the Grammar then remove from table,
            //this.fElementDeclTab.remove( (String) elementName );
        }
        // then it is forward reference to a element decl, create the elementDecl first.
        else {
            fCurrentElementIndex = createElementDecl();//create element decl

            XMLElementDecl elementDecl       = new XMLElementDecl();
            elementDecl.name.setValues(null, elementName, elementName, null);

            elementDecl.scope= -1;

            //add(or set) this elementDecl to the local cache
            this.fElementDeclTab.put(elementName, elementDecl );

            //set internal structure
            setElementDecl(fCurrentElementIndex, elementDecl );
        }

        //Get Grammar index to grammar array
        int elementIndex       = getElementDeclIndex(elementName);

        //return, when more than one definition is provided for the same attribute of given element type
        //only the first declaration is binding and later declarations are ignored
        if (getAttributeDeclIndex(elementIndex, attributeName) != -1) {
            return;
        }

        fCurrentAttributeIndex = createAttributeDecl();// Create current Attribute Decl

        fSimpleType.clear();
        if ( defaultType != null ) {
            if ( defaultType.equals( "#FIXED") ) {
                fSimpleType.defaultType = fSimpleType.DEFAULT_TYPE_FIXED;
            } else if ( defaultType.equals( "#IMPLIED") ) {
                fSimpleType.defaultType = fSimpleType.DEFAULT_TYPE_IMPLIED;
            } else if ( defaultType.equals( "#REQUIRED") ) {
                fSimpleType.defaultType = fSimpleType.DEFAULT_TYPE_REQUIRED;
            }
        }
        if ( DEBUG ) {
            System.out.println("defaultvalue = " + defaultValue.toString() );
        }
        fSimpleType.defaultValue      = defaultValue!=null ?  defaultValue.toString() : null;
        fSimpleType.nonNormalizedDefaultValue      = nonNormalizedDefaultValue!=null ?  nonNormalizedDefaultValue.toString() : null;
        fSimpleType.enumeration       = enumeration;

        if (type.equals("CDATA")) {
            fSimpleType.type = XMLSimpleType.TYPE_CDATA;
        }
        else if ( type.equals("ID") ) {
            fSimpleType.type = XMLSimpleType.TYPE_ID;
        }
        else if ( type.startsWith("IDREF") ) {
            fSimpleType.type = XMLSimpleType.TYPE_IDREF;
            if (type.indexOf("S") > 0) {
                fSimpleType.list = true;
            }
        }
        else if (type.equals("ENTITIES")) {
            fSimpleType.type = XMLSimpleType.TYPE_ENTITY;
            fSimpleType.list = true;
        }
        else if (type.equals("ENTITY")) {
            fSimpleType.type = XMLSimpleType.TYPE_ENTITY;
        }
        else if (type.equals("NMTOKENS")) {
            fSimpleType.type = XMLSimpleType.TYPE_NMTOKEN;
            fSimpleType.list = true;
        }
        else if (type.equals("NMTOKEN")) {
            fSimpleType.type = XMLSimpleType.TYPE_NMTOKEN;
        }
        else if (type.startsWith("NOTATION") ) {
            fSimpleType.type = XMLSimpleType.TYPE_NOTATION;
        }
        else if (type.startsWith("ENUMERATION") ) {
            fSimpleType.type = XMLSimpleType.TYPE_ENUMERATION;
        }
        else {
            // REVISIT: Report error message. -Ac
            System.err.println("!!! unknown attribute type "+type);
        }
        // REVISIT: The datatype should be stored with the attribute value
        //          and not special-cased in the XMLValidator. -Ac
        //fSimpleType.datatypeValidator = fDatatypeValidatorFactory.createDatatypeValidator(type, null, facets, fSimpleType.list);

        fQName.setValues(null, attributeName, attributeName, null);
        fAttributeDecl.setValues( fQName, fSimpleType, false );

        setAttributeDecl(elementIndex, fCurrentAttributeIndex, fAttributeDecl);

        int chunk = fCurrentAttributeIndex >> CHUNK_SHIFT;
        ensureAttributeDeclCapacity(chunk);
    } // attributeDecl(String,String,String,String[],String,XMLString,XMLString, Augmentations)

    /** Returns the symbol table. */
    public SymbolTable getSymbolTable() {
        return fSymbolTable;
    } // getSymbolTable():SymbolTable

    /**
     * Returns the index of the first element declaration. This index
     * is then used to query more information about the element declaration.
     *
     * @see #getNextElementDeclIndex
     * @see #getElementDecl
     */
    public int getFirstElementDeclIndex() {
        return fElementDeclCount >= 0 ? 0 : -1;
    } // getFirstElementDeclIndex():int

    /**
     * Returns the next index of the element declaration following the
     * specified element declaration.
     *
     * @param elementDeclIndex The element declaration index.
     */
    public int getNextElementDeclIndex(int elementDeclIndex) {
        return elementDeclIndex < fElementDeclCount - 1
        ? elementDeclIndex + 1 : -1;
    } // getNextElementDeclIndex(int):int

    /**
     * getElementDeclIndex
     *
     * @param elementDeclName
     *
     * @return index of the elementDeclName in scope
     */
    public int getElementDeclIndex(String elementDeclName) {
        int mapping = fElementIndexMap.get(elementDeclName);
        //System.out.println("getElementDeclIndex("+elementDeclName+") -> "+mapping);
        return mapping;
    } // getElementDeclIndex(String):int

    /** Returns the element decl index.
     * @param elementDeclQName qualilfied name of the element
     */
    public int getElementDeclIndex(QName elementDeclQName) {
        return getElementDeclIndex(elementDeclQName.rawname);
    } // getElementDeclIndex(QName):int

    /** make separate function for getting contentSpecType of element.
     * we can avoid setting of the element values.
     */

    public short getContentSpecType(int elementIndex){
        if (elementIndex < 0 || elementIndex >= fElementDeclCount) {
            return -1 ;
        }

        int chunk = elementIndex >> CHUNK_SHIFT;
        int index = elementIndex &  CHUNK_MASK;

        if(fElementDeclType[chunk][index] == -1){
            return -1 ;
        }
        else{
            return (short) (fElementDeclType[chunk][index] & LIST_MASK);
        }
    }

    /**
     * getElementDecl
     *
     * @param elementDeclIndex
     * @param elementDecl The values of this structure are set by this call.
     *
     * @return True if find the element, False otherwise.
     */
    public boolean getElementDecl(int elementDeclIndex,
    XMLElementDecl elementDecl) {

        if (elementDeclIndex < 0 || elementDeclIndex >= fElementDeclCount) {
            return false;
        }

        int chunk = elementDeclIndex >> CHUNK_SHIFT;
        int index = elementDeclIndex &  CHUNK_MASK;

        elementDecl.name.setValues(fElementDeclName[chunk][index]);

        if (fElementDeclType[chunk][index] == -1) {
            elementDecl.type                    = -1;
            elementDecl.simpleType.list = false;
        } else {
            elementDecl.type            = (short) (fElementDeclType[chunk][index] & LIST_MASK);
            elementDecl.simpleType.list = (fElementDeclType[chunk][index] & LIST_FLAG) != 0;
        }

        elementDecl.simpleType.defaultType       = -1;
        elementDecl.simpleType.defaultValue      = null;
        return true;

    }

    // REVISIT: Make this getAttributeDeclCount/getAttributeDeclAt. -Ac

    /**
     * getFirstAttributeDeclIndex
     *
     * @param elementDeclIndex
     *
     * @return index of the first attribute for element declaration elementDeclIndex
     */
    public int getFirstAttributeDeclIndex(int elementDeclIndex) {
        int chunk = elementDeclIndex >> CHUNK_SHIFT;
        int index = elementDeclIndex &  CHUNK_MASK;

        return  fElementDeclFirstAttributeDeclIndex[chunk][index];
    } // getFirstAttributeDeclIndex

    /**
     * getNextAttributeDeclIndex
     *
     * @param attributeDeclIndex
     *
     * @return index of the next attribute of the attribute at attributeDeclIndex
     */
    public int getNextAttributeDeclIndex(int attributeDeclIndex) {
        int chunk = attributeDeclIndex >> CHUNK_SHIFT;
        int index = attributeDeclIndex &  CHUNK_MASK;

        return fAttributeDeclNextAttributeDeclIndex[chunk][index];
    }

    /**
     * getAttributeDecl
     *
     * @param attributeDeclIndex
     * @param attributeDecl The values of this structure are set by this call.
     *
     * @return true if getAttributeDecl was able to fill in the value of attributeDecl
     */
    public boolean getAttributeDecl(int attributeDeclIndex, XMLAttributeDecl attributeDecl) {
        if (attributeDeclIndex < 0 || attributeDeclIndex >= fAttributeDeclCount) {
            return false;
        }
        int chunk = attributeDeclIndex >> CHUNK_SHIFT;
        int index = attributeDeclIndex & CHUNK_MASK;

        attributeDecl.name.setValues(fAttributeDeclName[chunk][index]);

        short attributeType;
        boolean isList;

        if (fAttributeDeclType[chunk][index] == -1) {

            attributeType = -1;
            isList = false;
        } else {
            attributeType = (short) (fAttributeDeclType[chunk][index] & LIST_MASK);
            isList = (fAttributeDeclType[chunk][index] & LIST_FLAG) != 0;
        }
        attributeDecl.simpleType.setValues(attributeType,fAttributeDeclName[chunk][index].localpart,
        fAttributeDeclEnumeration[chunk][index],
        isList, fAttributeDeclDefaultType[chunk][index],
        fAttributeDeclDefaultValue[chunk][index],
        fAttributeDeclNonNormalizedDefaultValue[chunk][index]);
        return true;

    } // getAttributeDecl


    /**
     * Returns whether the given attribute is of type CDATA or not
     *
     * @param elName The element name.
     * @param atName The attribute name.
     *
     * @return true if the attribute is of type CDATA
     */
    public boolean isCDATAAttribute(QName elName, QName atName) {
        int elDeclIdx = getElementDeclIndex(elName);
        if (getAttributeDecl(elDeclIdx, fAttributeDecl)
        && fAttributeDecl.simpleType.type != XMLSimpleType.TYPE_CDATA){
            return false;
        }
        return true;
    }



    public void printElements(  ) {
        int elementDeclIndex = 0;
        XMLElementDecl elementDecl = new XMLElementDecl();
        while (getElementDecl(elementDeclIndex++, elementDecl)) {

            System.out.println("element decl: "+elementDecl.name+
            ", "+ elementDecl.name.rawname  );

        }
    }

    public void printAttributes(int elementDeclIndex) {
        int attributeDeclIndex = getFirstAttributeDeclIndex(elementDeclIndex);
        System.out.print(elementDeclIndex);
        System.out.print(" [");
        while (attributeDeclIndex != -1) {
            System.out.print(' ');
            System.out.print(attributeDeclIndex);
            printAttribute(attributeDeclIndex);
            attributeDeclIndex = getNextAttributeDeclIndex(attributeDeclIndex);
            if (attributeDeclIndex != -1) {
                System.out.print(",");
            }
        }
        System.out.println(" ]");
    }


    protected int createElementDecl() {
        int chunk = fElementDeclCount >> CHUNK_SHIFT;
        int index = fElementDeclCount & CHUNK_MASK;
        ensureElementDeclCapacity(chunk);
        fElementDeclName[chunk][index]                    = new QName();
        fElementDeclType[chunk][index]                    = -1;
        fElementDeclFirstAttributeDeclIndex[chunk][index] = -1;
        fElementDeclLastAttributeDeclIndex[chunk][index]  = -1;
        return fElementDeclCount++;
    }

    protected void setElementDecl(int elementDeclIndex, XMLElementDecl elementDecl) {
        if (elementDeclIndex < 0 || elementDeclIndex >= fElementDeclCount) {
            return;
        }
        int     chunk       = elementDeclIndex >> CHUNK_SHIFT;
        int     index       = elementDeclIndex &  CHUNK_MASK;

        int     scope       = elementDecl.scope;


        fElementDeclName[chunk][index].setValues(elementDecl.name);
        fElementDeclType[chunk][index]                  = elementDecl.type;



        if (elementDecl.simpleType.list  == true ) {
            fElementDeclType[chunk][index] |= LIST_FLAG;
        }

        fElementIndexMap.put(elementDecl.name.rawname, elementDeclIndex);
    }




    protected void setFirstAttributeDeclIndex(int elementDeclIndex, int newFirstAttrIndex){

        if (elementDeclIndex < 0 || elementDeclIndex >= fElementDeclCount) {
            return;
        }

        int chunk = elementDeclIndex >> CHUNK_SHIFT;
        int index = elementDeclIndex &  CHUNK_MASK;

        fElementDeclFirstAttributeDeclIndex[chunk][index] = newFirstAttrIndex;
    }


    protected int createAttributeDecl() {
        int chunk = fAttributeDeclCount >> CHUNK_SHIFT;
        int index = fAttributeDeclCount & CHUNK_MASK;

        ensureAttributeDeclCapacity(chunk);
        fAttributeDeclName[chunk][index]                    = new QName();
        fAttributeDeclType[chunk][index]                    = -1;
        fAttributeDeclEnumeration[chunk][index]             = null;
        fAttributeDeclDefaultType[chunk][index]             = XMLSimpleType.DEFAULT_TYPE_IMPLIED;
        fAttributeDeclDefaultValue[chunk][index]            = null;
        fAttributeDeclNonNormalizedDefaultValue[chunk][index]            = null;
        fAttributeDeclNextAttributeDeclIndex[chunk][index]  = -1;
        return fAttributeDeclCount++;
    }


    protected void setAttributeDecl(int elementDeclIndex, int attributeDeclIndex,
    XMLAttributeDecl attributeDecl) {
        int attrChunk = attributeDeclIndex >> CHUNK_SHIFT;
        int attrIndex = attributeDeclIndex &  CHUNK_MASK;
        fAttributeDeclName[attrChunk][attrIndex].setValues(attributeDecl.name);
        fAttributeDeclType[attrChunk][attrIndex]  =  attributeDecl.simpleType.type;

        if (attributeDecl.simpleType.list) {
            fAttributeDeclType[attrChunk][attrIndex] |= LIST_FLAG;
        }
        fAttributeDeclEnumeration[attrChunk][attrIndex]  =  attributeDecl.simpleType.enumeration;
        fAttributeDeclDefaultType[attrChunk][attrIndex]  =  attributeDecl.simpleType.defaultType;

        fAttributeDeclDefaultValue[attrChunk][attrIndex] = attributeDecl.simpleType.defaultValue;
        fAttributeDeclNonNormalizedDefaultValue[attrChunk][attrIndex] = attributeDecl.simpleType.nonNormalizedDefaultValue;

        int elemChunk     = elementDeclIndex >> CHUNK_SHIFT;
        int elemIndex     = elementDeclIndex &  CHUNK_MASK;
        int index         = fElementDeclFirstAttributeDeclIndex[elemChunk][elemIndex];
        while (index != -1) {
            if (index == attributeDeclIndex) {
                break;
            }
            attrChunk = index >> CHUNK_SHIFT;
            attrIndex = index & CHUNK_MASK;
            index = fAttributeDeclNextAttributeDeclIndex[attrChunk][attrIndex];
        }
        if (index == -1) {
            if (fElementDeclFirstAttributeDeclIndex[elemChunk][elemIndex] == -1) {
                fElementDeclFirstAttributeDeclIndex[elemChunk][elemIndex] = attributeDeclIndex;
            } else {
                index = fElementDeclLastAttributeDeclIndex[elemChunk][elemIndex];
                attrChunk = index >> CHUNK_SHIFT;
                attrIndex = index & CHUNK_MASK;
                fAttributeDeclNextAttributeDeclIndex[attrChunk][attrIndex] = attributeDeclIndex;
            }
            fElementDeclLastAttributeDeclIndex[elemChunk][elemIndex] = attributeDeclIndex;
        }
    }

    public void notationDecl(String name, XMLResourceIdentifier identifier,
    Augmentations augs) throws XNIException {


        XMLNotationDecl  notationDecl = new XMLNotationDecl();
        notationDecl.setValues(name,identifier.getPublicId(),identifier.getLiteralSystemId(),
        identifier.getBaseSystemId());
        notationDecls.add(notationDecl);
    }

    public List getNotationDecls(){
        return notationDecls;
    }

    //
    // Private methods
    //
    private void printAttribute(int attributeDeclIndex) {

        XMLAttributeDecl attributeDecl = new XMLAttributeDecl();
        if (getAttributeDecl(attributeDeclIndex, attributeDecl)) {
            System.out.print(" { ");
            System.out.print(attributeDecl.name.localpart);
            System.out.print(" }");
        }

    } // printAttribute(int)



    private void ensureElementDeclCapacity(int chunk) {
        if (chunk >= fElementDeclName.length) {

            fElementDeclName = resize(fElementDeclName, fElementDeclName.length * 2);
            fElementDeclType = resize(fElementDeclType, fElementDeclType.length * 2);
            fElementDeclFirstAttributeDeclIndex = resize(fElementDeclFirstAttributeDeclIndex, fElementDeclFirstAttributeDeclIndex.length * 2);
            fElementDeclLastAttributeDeclIndex = resize(fElementDeclLastAttributeDeclIndex, fElementDeclLastAttributeDeclIndex.length * 2);
        }
        else if (fElementDeclName[chunk] != null) {
            return;
        }

        fElementDeclName[chunk] = new QName[CHUNK_SIZE];
        fElementDeclType[chunk] = new short[CHUNK_SIZE];
        fElementDeclFirstAttributeDeclIndex[chunk] = new int[CHUNK_SIZE];
        fElementDeclLastAttributeDeclIndex[chunk] = new int[CHUNK_SIZE];
        return;
    }

    private void ensureAttributeDeclCapacity(int chunk) {

        if (chunk >= fAttributeDeclName.length) {
            fAttributeDeclName = resize(fAttributeDeclName, fAttributeDeclName.length * 2);
            fAttributeDeclType = resize(fAttributeDeclType, fAttributeDeclType.length * 2);
            fAttributeDeclEnumeration = resize(fAttributeDeclEnumeration, fAttributeDeclEnumeration.length * 2);
            fAttributeDeclDefaultType = resize(fAttributeDeclDefaultType, fAttributeDeclDefaultType.length * 2);
            fAttributeDeclDefaultValue = resize(fAttributeDeclDefaultValue, fAttributeDeclDefaultValue.length * 2);
            fAttributeDeclNonNormalizedDefaultValue = resize(fAttributeDeclNonNormalizedDefaultValue, fAttributeDeclNonNormalizedDefaultValue.length * 2);
            fAttributeDeclNextAttributeDeclIndex = resize(fAttributeDeclNextAttributeDeclIndex, fAttributeDeclNextAttributeDeclIndex.length * 2);
        }
        else if (fAttributeDeclName[chunk] != null) {
            return;
        }

        fAttributeDeclName[chunk] = new QName[CHUNK_SIZE];
        fAttributeDeclType[chunk] = new short[CHUNK_SIZE];
        fAttributeDeclEnumeration[chunk] = new String[CHUNK_SIZE][];
        fAttributeDeclDefaultType[chunk] = new short[CHUNK_SIZE];
        fAttributeDeclDefaultValue[chunk] = new String[CHUNK_SIZE];
        fAttributeDeclNonNormalizedDefaultValue[chunk] = new String[CHUNK_SIZE];
        fAttributeDeclNextAttributeDeclIndex[chunk] = new int[CHUNK_SIZE];
        return;
    }


    // resize chunks

    private static short[][] resize(short array[][], int newsize) {
        short newarray[][] = new short[newsize][];
        System.arraycopy(array, 0, newarray, 0, array.length);
        return newarray;
    }

    private static int[][] resize(int array[][], int newsize) {
        int newarray[][] = new int[newsize][];
        System.arraycopy(array, 0, newarray, 0, array.length);
        return newarray;
    }

    private static QName[][] resize(QName array[][], int newsize) {
        QName newarray[][] = new QName[newsize][];
        System.arraycopy(array, 0, newarray, 0, array.length);
        return newarray;
    }

    private static String[][] resize(String array[][], int newsize) {
        String newarray[][] = new String[newsize][];
        System.arraycopy(array, 0, newarray, 0, array.length);
        return newarray;
    }

    private static String[][][] resize(String array[][][], int newsize) {
        String newarray[][][] = new String[newsize] [][];
        System.arraycopy(array, 0, newarray, 0, array.length);
        return newarray;
    }

    //
    // Classes
    //


    /**
     * A simple Hashtable implementation that takes a tuple (String, String)
     * as the key and a int as value.
     *
     * @author Eric Ye, IBM
     * @author Andy Clark, IBM
     */
    protected static final class QNameHashtable {

        //
        // Constants
        //
        public static final boolean UNIQUE_STRINGS = true;

        /** Initial bucket size (4). */
        private static final int INITIAL_BUCKET_SIZE = 4;

        // NOTE: Changed previous hashtable size from 512 to 101 so
        //       that we get a better distribution for hashing. -Ac
        /** Hashtable size (101). */
        private static final int HASHTABLE_SIZE = 101;

        //
        // Data
        //
        private Object[][] fHashTable = new Object[HASHTABLE_SIZE][];

        //
        // Public methods
        //
        /** Associates the given value with the specified key tuple. */
        public void put(String key, int value) {

            // REVISIT: Why +2? -Ac
            int hash = (hash(key)+2) % HASHTABLE_SIZE;
            Object[] bucket = fHashTable[hash];

            if (bucket == null) {
                bucket = new Object[1 + 2*INITIAL_BUCKET_SIZE];
                bucket[0] = new int[]{1};
                bucket[1] = key;
                bucket[2] = new int[]{value};
                fHashTable[hash] = bucket;
            } else {
                int count = ((int[])bucket[0])[0];
                int offset = 1 + 2*count;
                if (offset == bucket.length) {
                    int newSize = count + INITIAL_BUCKET_SIZE;
                    Object[] newBucket = new Object[1 + 2*newSize];
                    System.arraycopy(bucket, 0, newBucket, 0, offset);
                    bucket = newBucket;
                    fHashTable[hash] = bucket;
                }
                boolean found = false;
                int j=1;
                for (int i=0; i<count; i++){
                    if ((String)bucket[j] == key) {
                        ((int[])bucket[j+1])[0] = value;
                        found = true;
                        break;
                    }
                    j += 2;
                }
                if (! found) {
                    bucket[offset++] = key;
                    bucket[offset]= new int[]{value};
                    ((int[])bucket[0])[0] = ++count;
                }

            }
            //System.out.println("put("+key+" -> "+value+')');
            //System.out.println("get("+key+") -> "+get(key));

        } // put(int,String,String,int)

        /** Returns the value associated with the specified key tuple. */
        public int get(String key) {
            int hash = (hash(key)+2) % HASHTABLE_SIZE;
            Object[] bucket = fHashTable[hash];

            if (bucket == null) {
                return -1;
            }
            int count = ((int[])bucket[0])[0];

            int j=1;
            for (int i=0; i<count; i++){
                if ((String)bucket[j] == key) {
                    return ((int[])bucket[j+1])[0];
                }
                j += 2;
            }
            return -1;

        } // get(int,String,String)

        //
        // Protected methods
        //

        /** Returns a hash value for the specified symbol. */
        protected int hash(String symbol) {

            if (symbol == null) {
                return 0;
            }
            int code = 0;
            int length = symbol.length();
            for (int i = 0; i < length; i++) {
                code = code * 37 + symbol.charAt(i);
            }
            return code & 0x7FFFFFF;

        } // hash(String):int

    }  // class QNameHashtable
    /**
     * Normalize the attribute value of a non CDATA default attribute
     * collapsing sequences of space characters (x20)
     *
     * @param value The value to normalize
     * @return Whether the value was changed or not.
     */
    private boolean normalizeDefaultAttrValue(XMLString value) {

        int oldLength = value.length;

        boolean skipSpace = true; // skip leading spaces
        int current = value.offset;
        int end = value.offset + value.length;
        for (int i = value.offset; i < end; i++) {
            if (value.ch[i] == ' ') {
                if (!skipSpace) {
                    // take the first whitespace as a space and skip the others
                    value.ch[current++] = ' ';
                    skipSpace = true;
                }
                else {
                    // just skip it.
                }
            }
            else {
                // simply shift non space chars if needed
                if (current != i) {
                    value.ch[current] = value.ch[i];
                }
                current++;
                skipSpace = false;
            }
        }
        if (current != end) {
            if (skipSpace) {
                // if we finished on a space trim it
                current--;
            }
            // set the new value length
            value.length = current - value.offset;
            return true;
        }
        return false;
    }
    public void endDTD(Augmentations augs) throws XNIException {

    }
}
