# FOAttributes.java

    package org.apache.fop.fo;

    import org.apache.fop.apps.FOPException;
    import org.apache.fop.fo.expr.PropertyException;
    import org.apache.fop.fo.PropertyConsts;
    import org.apache.fop.fo.FObjectNames;
    import org.apache.fop.datatypes.Ints;
    import org.apache.fop.messaging.MessageHandler;
    import org.apache.fop.xml.FoXMLEvent;
    import org.apache.fop.xml.XMLNamespaces;

    import org.xml.sax.Attributes;

    import java.util.Iterator;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Collections;
    import java.util.Arrays;

    /*
     * FOAttributes.java
     * $Id$
     *
     * Created: Wed Nov 14 15:19:51 2001
     * Copyright (C) 2001 The Apache Software Foundation. All rights reserved.
     * For details on use and redistribution please refer to the
     * LICENSE file included with these sources.
     *
     * @author <a href="mailto:pbwest@powerup.com.au">Peter B. West</a>
     * @version $Revision$ $Name$
     */
    /**
     * The FO Attributes data structures and methods needed to manage the
     * Attributes associated with FO nodes.
     */

    public class FOAttributes {

        private static final String tag = "$Name$";
        private static final String revision = "$Revision$";

        /**
         * <i>nSpaceAttrMaps</i> is an <tt>ArrayList</tt> to hold the array of
         * <tt>HashMap</tt>s which contain the attribute lists for each
         * namespace which may be active for a particular FO element.  The
         * <tt>ArrayList</tt> is indexed by the URIIndex for this namespace
         * which is maintained in an <tt>XMLNamespaces</tt> object by the
         * <tt>FOTree</tt> object which is processing the FO input.  The
         * values in the <tt>HashMap</tt>s are indexed by the local name of the
         * attribute.
         * The <tt>ArrayList</tt> will not be created for a particular instance
         * of <tt>FOAttributes</tt> unless a namespace other than the standard
         * XSL namespace is activated for this instance.
         * See <i>foAttrMap</i>.
         */
        private ArrayList nSpaceAttrMaps;

        /**
         * <i>foAttrMap</i> is a <tt>HashMap</tt> to hold the FO namespace
         * attribute list specified in the FO element with which this list is
         * associated.  The <tt>String</tt> attribute value is stored
         * indexed by the integer constant property identifier from
         * <tt>PropertyConsts</tt>.
         */
        private HashMap foAttrMap = new HashMap(0);

        /**
         * A sorted array of the keys (property indices) of the values in
         * <i>foAttrMap</i>.
         */
        private Integer[] foAttrKeys = null;

        /**
         * A static array of <tt>Integer</tt> as a template for the generation
         * of the <i>foAttrKeys</i> array.
         */
        private static Integer[] integerArray
                                        = new Integer[] { Ints.consts.get(0) };

        /**
         * Construct an <i>FOAttributes</i> object.
         * <p>The <tt>Attributes</tt> object on the event is scanned, and each
         * attribute is examined.  If the attribute is in the default namespace
         * for fo: attributes, it is an fo: property, and its value is entered
         * into the <i>foAttrMap</i> <tt>Hashmap</tt> indexed by the property
         * index.
         * <p>If the attribute does not belong to the default namespace, its
         * value is entered into the appropriate <tt>HashMap</tt> in the
         * <tt>ArrayList</tt> <i>nSpaceAttrMaps</i>, indexed by the attribute's
         * local name.
         * @param event - the FO XML event which triggered construction of the
         * parent <tt>FONode</tt>.
         * @param foNode - the <tt>FONode</tt> with which these attributes are
         * associated.
         */
        public FOAttributes(FoXMLEvent event, FONode foNode) throws FOPException {

            // If the event is null, there is no event associated with this
            // node, probably because this is a manufactured node; e.g.,
            // an "invented" FopageSequenceMaster.  The default initialisation
            // includes an empty foAttrMap HashMap.
            if (event == null) return;

            if (event.getFoType() == FObjectNames.PCDATA)
                return;  // go with the empty foAttrMap

            // Create the foAttrMap.
            Attributes attributes = event.getAttributes();
            if (attributes == null) throw new FOPException
                                           ("No Attributes in XMLEvent");
            int propIndex;
            HashMap tmpHash;
            for (int i = 0; i < attributes.getLength(); i++) {
                String attrUri = attributes.getURI(i);
                String attrLocalname = attributes.getLocalName(i);
                String attrQName = attributes.getQName(i);
                int sep = attrQName.indexOf(':');
                String prefix = attrQName.substring(0, (sep == -1 ? 0 : sep));
                if (prefix.equals("xmlns")) break;
                String attrValue = attributes.getValue(i);
                int attrUriIndex = foNode.namespaces.getURIIndex(attrUri);

                //System.out.println("FONode:" + event);
                if (attrUriIndex == XMLNamespaces.DefAttrNSIndex) {
                    // Standard FO namespace
                    // Catch default namespace declaration here.
                    if (attrLocalname.equals("xmlns")) break;
                    // Is this a known (valid) property?
                    propIndex = PropNames.getPropertyIndex(attrLocalname);
                        // Known attribute name
                    foAttrMap.put(Ints.consts.get(propIndex), attrValue);
                } else { // Not the XSL FO namespace
                    int j;
                    if (nSpaceAttrMaps == null) {
                        //Create the list
                        System.out.println("Creating nSpaceAttrMaps");
                        nSpaceAttrMaps = new ArrayList(attrUriIndex + 1);
                        // Add the fo list
                        for (j = 0; j < XMLNamespaces.DefAttrNSIndex; j++)
                            nSpaceAttrMaps.add(new HashMap(0));

                        System.out.println("Adding foAttrMap");
                        nSpaceAttrMaps.add(foAttrMap);
                    }
                    // Make sure there are elements between the last current
                    // and the one to be added
                    for (j = nSpaceAttrMaps.size(); j <= attrUriIndex; j++)
                        nSpaceAttrMaps.add(new HashMap(0));

                    // Does a HashMap exist for this namespace?
                    if ((tmpHash =
                         (HashMap)nSpaceAttrMaps.get(attrUriIndex)) == null) {
                        tmpHash = new HashMap(1);
                        nSpaceAttrMaps.set(attrUriIndex, tmpHash);
                    }
                    // Now put this value in the HashMap
                    tmpHash.put(attrLocalname, attrValue);
                }
            }
            // Set up the sorted array of the foAttr keys, if foAttrMap has
            // any entries.
            if (foAttrMap.size() > 0) {
                foAttrKeys = (Integer[])(foAttrMap.keySet().toArray(integerArray));
                Arrays.sort(foAttrKeys);
            }
            // Finished with the Attributes object
            event.setAttributes(null);
        }

        /**
         * Get the default namespace attribute values as an unmodifiable
         * <tt>Map</tt>.
         * @return a unmodifiable <tt>Map</tt> containing the the attribute
         * values for all of the default attribute namespace attributes in this
         * attribute list, indexed by the property name index from
         * <tt>PropNames</tt>.
         */
        public Map getFixedFoAttrMap() {
            return Collections.unmodifiableMap((Map)foAttrMap);
        }

        /**
         * Get the <tt>HashMap</tt> of all default namespace attributes.
         * @return <tt>HashMap</tt> <i>foAttrMap</i> containing the the attribute
         * values for all of the default attribute namespace attributes in this
         * attribute list, indexed by the property name index from
         * <tt>PropNames</tt>.  This HashMap may be changed by the calling
         * process.
         */
        public HashMap getFoAttrMap() {
            return foAttrMap;
        }

        /**
         * Get the sorted array of property index keys for the default namespace
         * attributes.
         * @return <tt>Integer[]</tt> <i>foAttrKeys</i> containing the the
         * sorted keys (the property indices from <tt>PropNames</tt>) of the
         * attribute values for all of the default attribute namespace attributes
         * in this attribute list.
         * Warning: This array may be changed by the calling process.
         */
        public Integer[] getFoAttrKeys() {
            return foAttrKeys;
        }

        /**
         * A convenience method for accessing attribute values from the default
         * attribute namespace.
         * @param property an <tt>int</tt> containing the property name index
         * from <tt>PropNames</tt>.
         * @return a <tt>String</tt> containing the associated property value.
         */
        public String getFoAttrValue(int property) {
            return (String)(foAttrMap.get(Ints.consts.get(property)));
        }

        /**
         * A convenience method for accessing attribute values from the default
         * attribute namespace.
         * @param propertyName a <tt>String</tt> containing the property name.
         * @return a <tt>String</tt> containing the associated property value.
         */
        public String getFoAttrValue(String propertyName)
            throws PropertyException
        {
            return getFoAttrValue(PropNames.getPropertyIndex(propertyName));
        }

        /**
         * Get an unmodifiable <tt>Map</tt> of the attribute values for a
         * particular namespace.
         * @param uriIndex an <tt>int</tt> containing the index of the attribute
         * values namespace, maintained in an <tt>XMLEvent</tt> <tt>static</tt>
         * array.
         * @return an unmodifiable <tt>Map</tt> of the attribute values
         * within the indexed namespace, for this attribute list, indexed by the
         * local name of the attribute.  The <tt>Map</tt> returned is
         * derived from the one maintained in <i>nSpaceAttrMaps</i>.
         */
        public Map getAttrMap(int uriIndex) {
            if (uriIndex == XMLNamespaces.DefAttrNSIndex)
                return Collections.unmodifiableMap((Map)foAttrMap);
            if (nSpaceAttrMaps != null) {
                if (uriIndex >= nSpaceAttrMaps.size()) return null;
                return Collections.unmodifiableMap
                        ((Map)(nSpaceAttrMaps.get(uriIndex)));
            } else {
                return null;
            }
        }

        /**
         * Get the value of an attribute in a particular namespace.
         * @param uriIndex an <tt>int</tt> index of the URIs maintained
         * by <tt>XMLEvent</tt>.
         * @param localName a <tt>String</tt> with the local name of the
         * attribute.  In the case of the default attribute namespace, this
         * will be the fo property name.
         * @return a <tt>String</tt> containing the value of the attribute.
         */
        public String getUriAttrValue(int uriIndex, String localName)
            throws PropertyException
        {
            if (uriIndex == XMLNamespaces.DefAttrNSIndex)
                return getFoAttrValue(PropNames.getPropertyIndex(localName));
            return (String)
                    (((HashMap)nSpaceAttrMaps.get(uriIndex)).get(localName));
        }

        /**
         * Get the size of the <i>foAttrMap</i> <tt>HashMap</tt>
         * containing attributes for the default fo: namespace
         * @return an <tt>int</tt> containing the size.
         */
        public int getFoAttrMapSize() {
            return foAttrMap.size();
        }

        /**
         * Get the size of the <i>nSpaceAttrMaps</i> <tt>ArrayList</tt>
         * containing attribute namespaces active in this set of attributes.
         * <i>N.B.</i> this may be zero if only the default attribute
         * namespace has been seen in the attribute set.
         * @return an <tt>int</tt> containing the size.
         */
        public int getNSpaceAttrMapsSize() {
            if (nSpaceAttrMaps == null)
                return 0;
            return nSpaceAttrMaps.size();
        }

        /**
         * Merge attributes from another <tt>FOAttributes</tt> into <i>this</i>.
         * @param foAttrs the <tt>FOAttributes</tt> containing the attributes
         * to merge.
         */
        public void merge(FOAttributes foAttrs) {
            foAttrMap.putAll(foAttrs.getFoAttrMap());
            int attrLen = foAttrs.getNSpaceAttrMapsSize();
            if (attrLen != 0) {
                // something to copy
                if (nSpaceAttrMaps == null) {
                    // no "foreign" attribute lists in this
                    // copy the others in
                    nSpaceAttrMaps = new ArrayList(attrLen);
                }
                // If the merging ArrayList of namespaces is larger, add the
                // extra elements and initialise each to an empty HashMap
                for (int i = nSpaceAttrMaps.size(); i < attrLen; i++)
                    nSpaceAttrMaps.add(new HashMap(0));
                // Except for foAttrs, which has already been merged, merge
                // the entries from the merging foAttrs
                for (int i = 0; i < attrLen; i++) {
                    // skip foAttrMap
                    if (i == XMLNamespaces.DefAttrNSIndex) continue;
                   ((HashMap) nSpaceAttrMaps.get(i))
                           .putAll(foAttrs.getAttrMap(i));
                }
            }
        }

    }// FOAttributes