org.apache.torque.engine.database.model.Table.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.torque.engine.database.model.Table.java

Source

package org.apache.torque.engine.database.model;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.map.ListOrderedMap;
import org.apache.commons.lang.StringUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.torque.engine.EngineException;

import org.xml.sax.Attributes;

/**
 * Data about a table used in an application.
 *
 * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
 * @author <a href="mailto:mpoeschl@marmot.at>Martin Poeschl</a>
 * @author <a href="mailto:jmcnally@collab.net>John McNally</a>
 * @author <a href="mailto:dlr@collab.net>Daniel Rall</a>
 * @author <a href="mailto:byron_foster@byron_foster@yahoo.com>Byron Foster</a>
 * @author <a href="mailto:monroe@dukece.com>Greg Monroe</a>
 * @version $Id: Table.java,v 1.1 2007-10-21 07:57:27 abyrne Exp $
 */
public class Table implements IDMethod {
    /** Logging class from commons.logging */
    private static Log log = LogFactory.getLog(Table.class);

    //private AttributeListImpl attributes;
    private List columnList;
    private List foreignKeys;
    private List indices;
    private List unices;
    private List idMethodParameters;
    private String name;
    private String description;
    private String javaName;
    private String idMethod;
    private String javaNamingMethod;
    private Database tableParent;
    private List referrers;
    private List foreignTableNames;
    private boolean containsForeignPK;
    private Column inheritanceColumn;
    private boolean skipSql;
    private boolean abstractValue;
    private String alias;
    private String enterface;
    private String pkg;
    private String baseClass;
    private String basePeer;
    private Hashtable columnsByName;
    private Hashtable columnsByJavaName;
    private boolean needsTransactionInPostgres;
    private boolean heavyIndexing;
    private boolean forReferenceOnly;
    private Map options;

    /**
     * Default Constructor
     */
    public Table() {
        this(null);
    }

    /**
     * Constructs a table object with a name
     *
     * @param name table name
     */
    public Table(String name) {
        this.name = name;
        columnList = new ArrayList();
        foreignKeys = new ArrayList(5);
        indices = new ArrayList(5);
        unices = new ArrayList(5);
        columnsByName = new Hashtable();
        columnsByJavaName = new Hashtable();
        options = Collections.synchronizedMap(new ListOrderedMap());
    }

    /**
     * Load the table object from an xml tag.
     *
     * @param attrib xml attributes
     * @param defaultIdMethod defined at db level
     */
    public void loadFromXML(Attributes attrib, String defaultIdMethod) {
        name = attrib.getValue("name");
        javaName = attrib.getValue("javaName");
        idMethod = attrib.getValue("idMethod");

        // retrieves the method for converting from specified name to
        // a java name.
        javaNamingMethod = attrib.getValue("javaNamingMethod");
        if (javaNamingMethod == null) {
            javaNamingMethod = getDatabase().getDefaultJavaNamingMethod();
        }

        if ("null".equals(idMethod)) {
            idMethod = defaultIdMethod;
        }
        skipSql = "true".equals(attrib.getValue("skipSql"));
        // pkg = attrib.getValue("package");
        abstractValue = "true".equals(attrib.getValue("abstract"));
        baseClass = attrib.getValue("baseClass");
        basePeer = attrib.getValue("basePeer");
        alias = attrib.getValue("alias");
        heavyIndexing = "true".equals(attrib.getValue("heavyIndexing"))
                || (!"false".equals(attrib.getValue("heavyIndexing")) && getDatabase().isHeavyIndexing());
        description = attrib.getValue("description");
        enterface = attrib.getValue("interface");
    }

    /**
     * <p>A hook for the SAX XML parser to call when this table has
     * been fully loaded from the XML, and all nested elements have
     * been processed.</p>
     *
     * <p>Performs heavy indexing and naming of elements which weren't
     * provided with a name.</p>
     */
    public void doFinalInitialization() {
        // Heavy indexing must wait until after all columns composing
        // a table's primary key have been parsed.
        if (heavyIndexing) {
            doHeavyIndexing();
        }

        // Name any indices which are missing a name using the
        // appropriate algorithm.
        doNaming();
    }

    /**
     * <p>Adds extra indices for multi-part primary key columns.</p>
     *
     * <p>For databases like MySQL, values in a where clause must
     * match key part order from the left to right.  So, in the key
     * definition <code>PRIMARY KEY (FOO_ID, BAR_ID)</code>,
     * <code>FOO_ID</code> <i>must</i> be the first element used in
     * the <code>where</code> clause of the SQL query used against
     * this table for the primary key index to be used.  This feature
     * could cause problems under MySQL with heavily indexed tables,
     * as MySQL currently only supports 16 indices per table (i.e. it
     * might cause too many indices to be created).</p>
     *
     * <p>See <a href="http://www.mysql.com/doc/E/X/EXPLAIN.html">the
     * manual</a> for a better description of why heavy indexing is
     * useful for quickly searchable database tables.</p>
     */
    private void doHeavyIndexing() {
        if (log.isDebugEnabled()) {
            log.debug("doHeavyIndex() called on table " + name);
        }

        List pk = getPrimaryKey();
        int size = pk.size();

        try {
            // We start at an offset of 1 because the entire column
            // list is generally implicitly indexed by the fact that
            // it's a primary key.
            for (int i = 1; i < size; i++) {
                addIndex(new Index(this, pk.subList(i, size)));
            }
        } catch (EngineException e) {
            log.error(e, e);
        }
    }

    /**
     * Names composing objects which haven't yet been named.  This
     * currently consists of foreign-key and index entities.
     */
    private void doNaming() {
        int i;
        int size;
        String name;

        // Assure names are unique across all databases.
        try {
            for (i = 0, size = foreignKeys.size(); i < size; i++) {
                ForeignKey fk = (ForeignKey) foreignKeys.get(i);
                name = fk.getName();
                if (StringUtils.isEmpty(name)) {
                    name = acquireConstraintName("FK", i + 1);
                    fk.setName(name);
                }
            }

            for (i = 0, size = indices.size(); i < size; i++) {
                Index index = (Index) indices.get(i);
                name = index.getName();
                if (StringUtils.isEmpty(name)) {
                    name = acquireConstraintName("I", i + 1);
                    index.setName(name);
                }
            }

            for (i = 0, size = unices.size(); i < size; i++) {
                Unique unique = (Unique) unices.get(i);
                name = unique.getName();
                if (StringUtils.isEmpty(name)) {
                    name = acquireConstraintName("U", i + 1);
                    unique.setName(name);
                }
            }
        } catch (EngineException nameAlreadyInUse) {
            log.error(nameAlreadyInUse, nameAlreadyInUse);
        }
    }

    /**
     * Macro to a constraint name.
     *
     * @param nameType constraint type
     * @param nbr unique number for this constraint type
     * @return unique name for constraint
     * @throws EngineException
     */
    private final String acquireConstraintName(String nameType, int nbr) throws EngineException {
        List inputs = new ArrayList(4);
        inputs.add(getDatabase());
        inputs.add(getName());
        inputs.add(nameType);
        inputs.add(new Integer(nbr));
        return NameFactory.generateName(NameFactory.CONSTRAINT_GENERATOR, inputs);
    }

    /**
     * Gets the value of base class for classes produced from this table.
     *
     * @return The base class for classes produced from this table.
     */
    public String getBaseClass() {
        if (isAlias() && baseClass == null) {
            return alias;
        } else if (baseClass == null) {
            return getDatabase().getBaseClass();
        } else {
            return baseClass;
        }
    }

    /**
     * Set the value of baseClass.
     * @param v  Value to assign to baseClass.
     */
    public void setBaseClass(String v) {
        this.baseClass = v;
    }

    /**
     * Get the value of basePeer.
     * @return value of basePeer.
     */
    public String getBasePeer() {
        if (isAlias() && basePeer == null) {
            return alias + "Peer";
        } else if (basePeer == null) {
            return getDatabase().getBasePeer();
        } else {
            return basePeer;
        }
    }

    /**
     * Set the value of basePeer.
     * @param v  Value to assign to basePeer.
     */
    public void setBasePeer(String v) {
        this.basePeer = v;
    }

    /**
     * A utility function to create a new column from attrib and add it to this
     * table.
     *
     * @param attrib xml attributes for the column to add
     * @return the added column
     */
    public Column addColumn(Attributes attrib) {
        Column col = new Column();
        col.setTable(this);
        col.setCorrectGetters(false);
        col.loadFromXML(attrib);
        addColumn(col);
        return col;
    }

    /**
     * Adds a new column to the column list and set the
     * parent table of the column to the current table
     *
     * @param col the column to add
     */
    public void addColumn(Column col) {
        col.setTable(this);
        if (col.isInheritance()) {
            inheritanceColumn = col;
        }
        columnList.add(col);
        columnsByName.put(col.getName(), col);
        columnsByJavaName.put(col.getJavaName(), col);
        col.setPosition(columnList.size());
        needsTransactionInPostgres |= col.requiresTransactionInPostgres();
    }

    /**
     * A utility function to create a new foreign key
     * from attrib and add it to this table.
     *
     * @param attrib the xml attributes
     * @return the created ForeignKey
     */
    public ForeignKey addForeignKey(Attributes attrib) {
        ForeignKey fk = new ForeignKey();
        fk.loadFromXML(attrib);
        addForeignKey(fk);
        return fk;
    }

    /**
     * Gets the column that subclasses of the class representing this
     * table can be produced from.
     */
    public Column getChildrenColumn() {
        return inheritanceColumn;
    }

    /**
     * Get the objects that can be created from this table.
     */
    public List getChildrenNames() {
        if (inheritanceColumn == null || !inheritanceColumn.isEnumeratedClasses()) {
            return null;
        }
        List children = inheritanceColumn.getChildren();
        List names = new ArrayList(children.size());
        for (int i = 0; i < children.size(); i++) {
            names.add(((Inheritance) children.get(i)).getClassName());
        }
        return names;
    }

    /**
     * Adds the foreign key from another table that refers to this table.
     *
     * @param fk A foreign key refering to this table
     */
    public void addReferrer(ForeignKey fk) {
        if (referrers == null) {
            referrers = new ArrayList(5);
        }
        referrers.add(fk);
    }

    /**
     * Get list of references to this table.
     *
     * @return A list of references to this table
     */
    public List getReferrers() {
        return referrers;
    }

    /**
     * Set whether this table contains a foreign PK
     *
     * @param b
     */
    public void setContainsForeignPK(boolean b) {
        containsForeignPK = b;
    }

    /**
     * Determine if this table contains a foreign PK
     */
    public boolean getContainsForeignPK() {
        return containsForeignPK;
    }

    /**
     * A list of tables referenced by foreign keys in this table
     *
     * @return A list of tables
     */
    public List getForeignTableNames() {
        if (foreignTableNames == null) {
            foreignTableNames = new ArrayList(1);
        }
        return foreignTableNames;
    }

    /**
     * Adds a new FK to the FK list and set the
     * parent table of the column to the current table
     *
     * @param fk A foreign key
     */
    public void addForeignKey(ForeignKey fk) {
        fk.setTable(this);
        foreignKeys.add(fk);

        if (foreignTableNames == null) {
            foreignTableNames = new ArrayList(5);
        }
        if (!foreignTableNames.contains(fk.getForeignTableName())) {
            foreignTableNames.add(fk.getForeignTableName());
        }
    }

    /**
     * Return true if the column requires a transaction in Postgres
     */
    public boolean requiresTransactionInPostgres() {
        return needsTransactionInPostgres;
    }

    /**
     * A utility function to create a new id method parameter
     * from attrib and add it to this table.
     */
    public IdMethodParameter addIdMethodParameter(Attributes attrib) {
        IdMethodParameter imp = new IdMethodParameter();
        imp.loadFromXML(attrib);
        addIdMethodParameter(imp);
        return imp;
    }

    /**
     * Adds a new ID method parameter to the list and sets the parent
     * table of the column associated with the supplied parameter to this table.
     *
     * @param imp The column to add as an ID method parameter.
     */
    public void addIdMethodParameter(IdMethodParameter imp) {
        imp.setTable(this);
        if (idMethodParameters == null) {
            idMethodParameters = new ArrayList(2);
        }
        idMethodParameters.add(imp);
    }

    /**
     * Adds a new index to the index list and set the
     * parent table of the column to the current table
     */
    public void addIndex(Index index) {
        index.setTable(this);
        indices.add(index);
    }

    /**
     * A utility function to create a new index
     * from attrib and add it to this table.
     */
    public Index addIndex(Attributes attrib) {
        Index index = new Index();
        index.loadFromXML(attrib);
        addIndex(index);
        return index;
    }

    /**
     * Adds a new Unique to the Unique list and set the
     * parent table of the column to the current table
     */
    public void addUnique(Unique unique) {
        unique.setTable(this);
        unices.add(unique);
    }

    /**
     * A utility function to create a new Unique
     * from attrib and add it to this table.
     *
     * @param attrib the xml attributes
     */
    public Unique addUnique(Attributes attrib) {
        Unique unique = new Unique();
        unique.loadFromXML(attrib);
        addUnique(unique);
        return unique;
    }

    /**
     * Get the name of the Table
     */
    public String getName() {
        return name;
    }

    /**
     * Set the name of the Table
     */
    public void setName(String newName) {
        name = newName;
    }

    /**
     * Get the description for the Table
     */
    public String getDescription() {
        return description;
    }

    /**
     * Set the description for the Table
     *
     * @param newDescription description for the Table
     */
    public void setDescription(String newDescription) {
        description = newDescription;
    }

    /**
     * Get name to use in Java sources
     */
    public String getJavaName() {
        if (javaName == null) {
            List inputs = new ArrayList(2);
            inputs.add(name);
            inputs.add(javaNamingMethod);
            try {
                javaName = NameFactory.generateName(NameFactory.JAVA_GENERATOR, inputs);
            } catch (EngineException e) {
                log.error(e, e);
            }
        }
        return javaName;
    }

    /**
     * Set name to use in Java sources
     */
    public void setJavaName(String javaName) {
        this.javaName = javaName;
    }

    /**
     * Get the method for generating pk's
     */
    public String getIdMethod() {
        if (idMethod == null) {
            return IDMethod.NO_ID_METHOD;
        } else {
            return idMethod;
        }
    }

    /**
     * Set the method for generating pk's
     */
    public void setIdMethod(String idMethod) {
        this.idMethod = idMethod;
    }

    /**
     * Skip generating sql for this table (in the event it should
     * not be created from scratch).
     * @return value of skipSql.
     */
    public boolean isSkipSql() {
        return (skipSql || isAlias() || isForReferenceOnly());
    }

    /**
     * Set whether this table should have its creation sql generated.
     * @param v  Value to assign to skipSql.
     */
    public void setSkipSql(boolean v) {
        this.skipSql = v;
    }

    /**
     * JavaName of om object this entry references.
     * @return value of external.
     */
    public String getAlias() {
        return alias;
    }

    /**
     * Is this table specified in the schema or is there just
     * a foreign key reference to it.
     * @return value of external.
     */
    public boolean isAlias() {
        return (alias != null);
    }

    /**
     * Set whether this table specified in the schema or is there just
     * a foreign key reference to it.
     * @param v  Value to assign to alias.
     */
    public void setAlias(String v) {
        this.alias = v;
    }

    /**
     * Interface which objects for this table will implement
     * @return value of interface.
     */
    public String getInterface() {
        return enterface;
    }

    /**
     * Interface which objects for this table will implement
     * @param v  Value to assign to interface.
     */
    public void setInterface(String v) {
        this.enterface = v;
    }

    /**
     * When a table is abstract, it marks the business object class that is
     * generated as being abstract. If you have a table called "FOO", then the
     * Foo BO will be <code>public abstract class Foo</code>
     * This helps support class hierarchies
     *
     * @return value of abstractValue.
     */
    public boolean isAbstract() {
        return abstractValue;
    }

    /**
     * When a table is abstract, it marks the business object
     * class that is generated as being abstract. If you have a
     * table called "FOO", then the Foo BO will be
     * <code>public abstract class Foo</code>
     * This helps support class hierarchies
     *
     * @param v  Value to assign to abstractValue.
     */
    public void setAbstract(boolean v) {
        this.abstractValue = v;
    }

    /**
     * Get the value of package.
     *
     * @return value of package.
     */
    public String getPackage() {
        if (pkg != null) {
            return pkg;
        } else {
            return this.getDatabase().getPackage();
        }
    }

    /**
     * Set the value of package.
     *
     * @param v  Value to assign to package.
     */
    public void setPackage(String v) {
        this.pkg = v;
    }

    /**
     * Returns a List containing all the columns in the table
     *
     * @return a List containing all the columns
     */
    public List getColumns() {
        return columnList;
    }

    /**
     * Utility method to get the number of columns in this table
     */
    public int getNumColumns() {
        return columnList.size();
    }

    /**
     * Returns a List containing all the FKs in the table
     *
     * @return a List containing all the FKs
     */
    public List getForeignKeys() {
        return foreignKeys;
    }

    /**
     * Returns a Collection of parameters relevant for the chosen
     * id generation method.
     */
    public List getIdMethodParameters() {
        return idMethodParameters;
    }

    /**
     * A name to use for creating a sequence if one is not specified.
     *
     * @return name of the sequence
     */
    public String getSequenceName() {
        String result = null;
        if (getIdMethod().equals(NATIVE)) {
            List idMethodParams = getIdMethodParameters();
            if (idMethodParams == null) {
                result = getName() + "_SEQ";
            } else {
                result = ((IdMethodParameter) idMethodParams.get(0)).getValue();
            }
        }
        return result;
    }

    /**
     * Returns a List containing all the indices in the table
     *
     * @return A List containing all the indices
     */
    public List getIndices() {
        return indices;
    }

    /**
     * Returns a List containing all the UKs in the table
     *
     * @return A List containing all the UKs
     */
    public List getUnices() {
        return unices;
    }

    /**
     * Returns a specified column.
     *
     * @param name name of the column
     * @return Return a Column object or null if it does not exist.
     */
    public Column getColumn(String name) {
        return (Column) columnsByName.get(name);
    }

    /**
     * Returns a specified column.
     *
     * @param javaName java name of the column
     * @return Return a Column object or null if it does not exist.
     */
    public Column getColumnByJavaName(String javaName) {
        return (Column) columnsByJavaName.get(javaName);
    }

    /**
     * Return the first foreign key that includes col in it's list
     * of local columns.  Eg. Foreign key (a,b,c) refrences tbl(x,y,z)
     * will be returned of col is either a,b or c.
     *
     * @param col column name included in the key
     * @return Return a Column object or null if it does not exist.
     */
    public ForeignKey getForeignKey(String col) {
        ForeignKey firstFK = null;
        for (Iterator iter = foreignKeys.iterator(); iter.hasNext();) {
            ForeignKey key = (ForeignKey) iter.next();
            if (key.getLocalColumns().contains(col)) {
                if (firstFK == null) {
                    firstFK = key;
                } else {
                    //System.out.println(col+" is in multiple FKs.  This is not"
                    //                   + " being handled properly.");
                    //throw new IllegalStateException("Cannot call method if " +
                    //    "column is referenced multiple times");
                }
            }
        }
        return firstFK;
    }

    /**
     * Returns true if the table contains a specified column
     *
     * @param col the column
     * @return true if the table contains the column
     */
    public boolean containsColumn(Column col) {
        return columnList.contains(col);
    }

    /**
     * Returns true if the table contains a specified column
     *
     * @param name name of the column
     * @return true if the table contains the column
     */
    public boolean containsColumn(String name) {
        return (getColumn(name) != null);
    }

    /**
     * Set the parent of the table
     *
     * @param parent the parant database
     */
    public void setDatabase(Database parent) {
        tableParent = parent;
    }

    /**
     * Get the parent of the table
     *
     * @return the parant database
     */
    public Database getDatabase() {
        return tableParent;
    }

    /**
     * Flag to determine if code/sql gets created for this table.
     * Table will be skipped, if return true.
     * @return value of forReferenceOnly.
     */
    public boolean isForReferenceOnly() {
        return forReferenceOnly;
    }

    /**
     * Flag to determine if code/sql gets created for this table.
     * Table will be skipped, if set to true.
     * @param v  Value to assign to forReferenceOnly.
     */
    public void setForReferenceOnly(boolean v) {
        this.forReferenceOnly = v;
    }

    /**
     * Returns a XML representation of this table.
     *
     * @return XML representation of this table
     */
    public String toString() {
        StringBuffer result = new StringBuffer();

        result.append("<table name=\"").append(name).append('\"');

        if (javaName != null) {
            result.append(" javaName=\"").append(javaName).append('\"');
        }

        if (idMethod != null) {
            result.append(" idMethod=\"").append(idMethod).append('\"');
        }

        if (skipSql) {
            result.append(" skipSql=\"").append(new Boolean(skipSql)).append('\"');
        }

        if (abstractValue) {
            result.append(" abstract=\"").append(new Boolean(abstractValue)).append('\"');
        }

        if (baseClass != null) {
            result.append(" baseClass=\"").append(baseClass).append('\"');
        }

        if (basePeer != null) {
            result.append(" basePeer=\"").append(basePeer).append('\"');
        }

        result.append(">\n");

        if (columnList != null) {
            for (Iterator iter = columnList.iterator(); iter.hasNext();) {
                result.append(iter.next());
            }
        }

        if (foreignKeys != null) {
            for (Iterator iter = foreignKeys.iterator(); iter.hasNext();) {
                result.append(iter.next());
            }
        }

        if (idMethodParameters != null) {
            Iterator iter = idMethodParameters.iterator();
            while (iter.hasNext()) {
                result.append(iter.next());
            }
        }

        result.append("</table>\n");

        return result.toString();
    }

    /**
     * Returns the collection of Columns which make up the single primary
     * key for this table.
     *
     * @return A list of the primary key parts.
     */
    public List getPrimaryKey() {
        List pk = new ArrayList(columnList.size());

        Iterator iter = columnList.iterator();
        while (iter.hasNext()) {
            Column col = (Column) iter.next();
            if (col.isPrimaryKey()) {
                pk.add(col);
            }
        }
        return pk;
    }

    /**
     * Determine whether this table has a primary key.
     *
     * @return Whether this table has any primary key parts.
     */
    public boolean hasPrimaryKey() {
        return (getPrimaryKey().size() > 0);
    }

    /**
     * Returns all parts of the primary key, separated by commas.
     *
     * @return A CSV list of primary key parts.
     */
    public String printPrimaryKey() {
        return printList(columnList);
    }

    /**
     * Returns the elements of the list, separated by commas.
     *
     * @param list a list of Columns
     * @return A CSV list.
     */
    private String printList(List list) {
        StringBuffer result = new StringBuffer();
        boolean comma = false;
        for (Iterator iter = list.iterator(); iter.hasNext();) {
            Column col = (Column) iter.next();
            if (col.isPrimaryKey()) {
                if (comma) {
                    result.append(',');
                } else {
                    comma = true;
                }
                result.append(col.getName());
            }
        }
        return result.toString();
    }

    /**
     * Force all columns to set the correctGetters property.
     *
     * @param value The new value of the correctGetters property.
     * @since 3.2
     */
    public void setCorrectGetters(Boolean value) {
        boolean correctGetters = value != null && value.booleanValue();
        for (Iterator it = columnList.iterator(); it.hasNext();) {
            Column col = (Column) it.next();
            col.setCorrectGetters(correctGetters);
        }
    }

    /**
     * Add an XML Specified option key/value pair to this element's option set.
     *
     * @param key the key of the option.
     * @param value the value of the option.
     */
    public void addOption(String key, String value) {
        options.put(key, value);
    }

    /**
     * Get the value that was associated with this key in an XML option
     * element.
     *
     * @param key the key of the option.
     * @return The value for the key or a null.
     */
    public String getOption(String key) {
        return (String) options.get(key);
    }

    /**
     * Gets the full ordered hashtable array of items specified by XML option
     * statements under this element.<p>
     *
     * Note, this is not thread save but since it's only used for
     * generation which is single threaded, there should be minimum
     * danger using this in Velocity.
     *
     * @return An Map of all options. Will not be null but may be empty.
     */
    public Map getOptions() {
        return options;
    }
}