/*
 * 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.swing;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.XPath;

import org.jaxen.VariableContext;

/**
 * <p>
 * <code>XMLTableDefinition</code> represents a table definition based on
 * XPath expression evaluated on an XML document.
 * </p>
 * 
 * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
 * @version $Revision: 1.8 $
 */
public class XMLTableDefinition implements Serializable, VariableContext {
    /** Holds value of property rowXPath. */
    private XPath rowXPath;

    /** The columns to display in this table */
    private List<XMLTableColumnDefinition> columns = new ArrayList<XMLTableColumnDefinition>();

    /** integer index array cache */
    private XMLTableColumnDefinition[] columnArray;

    /** name index cache */
    private Map<String, XMLTableColumnDefinition> columnNameIndex;

    /** for cross-row variables */
    private VariableContext variableContext;

    /** stores the current row value for the variableContext */
    private Object rowValue;

    public XMLTableDefinition() {
    }

    /**
     * Loads an XML table definition from an XML definition document
     * 
     * @param definition
     *            DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
     */
    public static XMLTableDefinition load(Document definition) {
        return load(definition.getRootElement());
    }

    /**
     * Loads an XML table definition from an XML definition document
     * 
     * @param definition
     *            DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
     */
    public static XMLTableDefinition load(Element definition) {
        XMLTableDefinition answer = new XMLTableDefinition();
        answer.setRowExpression(definition.attributeValue("select"));

        for (Iterator<Element> iter = definition.elementIterator("column"); iter
                .hasNext();) {
            Element element = iter.next();
            String expression = element.attributeValue("select");
            String name = element.getText();
            String typeName = element.attributeValue("type", "string");
            String columnXPath = element.attributeValue("columnNameXPath");
            int type = XMLTableColumnDefinition.parseType(typeName);

            if (columnXPath != null) {
                answer.addColumnWithXPathName(columnXPath, expression, type);
            } else {
                answer.addColumn(name, expression, type);
            }
        }

        return answer;
    }

    public Class<?> getColumnClass(int columnIndex) {
        return getColumn(columnIndex).getColumnClass();
    }

    public int getColumnCount() {
        return columns.size();
    }

    /**
     * DOCUMENT ME!
     * 
     * @param columnIndex
     *            DOCUMENT ME!
     * 
     * @return the static column name. This is used if there is no
     *         columnNameXPath
     */
    public String getColumnName(int columnIndex) {
        return getColumn(columnIndex).getName();
    }

    /**
     * DOCUMENT ME!
     * 
     * @param columnIndex
     *            DOCUMENT ME!
     * 
     * @return the XPath expression used to evaluate the value of cells in this
     *         column
     */
    public XPath getColumnXPath(int columnIndex) {
        return getColumn(columnIndex).getXPath();
    }

    /**
     * DOCUMENT ME!
     * 
     * @param columnIndex
     *            DOCUMENT ME!
     * 
     * @return the XPath expresssion used to create the column name, if there is
     *         one or null if there is no XPath expression to name the column.
     */
    public XPath getColumnNameXPath(int columnIndex) {
        return getColumn(columnIndex).getColumnNameXPath();
    }

    public synchronized Object getValueAt(Object row, int columnIndex) {
        XMLTableColumnDefinition column = getColumn(columnIndex);
        Object answer = null;

        synchronized (this) {
            this.rowValue = row;
            answer = column.getValue(row);
            this.rowValue = null;
        }

        return answer;
    }

    public void addColumn(String name, String expression) {
        addColumn(name, expression, XMLTableColumnDefinition.OBJECT_TYPE);
    }

    public void addColumn(String name, String expression, int type) {
        XPath xpath = createColumnXPath(expression);
        addColumn(new XMLTableColumnDefinition(name, xpath, type));
    }

    public void addColumnWithXPathName(String columnNameXPathExpression,
            String expression, int type) {
        XPath columnNameXPath = createColumnXPath(columnNameXPathExpression);
        XPath xpath = createColumnXPath(expression);
        addColumn(new XMLTableColumnDefinition(columnNameXPath, xpath, type));
    }

    public void addStringColumn(String name, String expression) {
        addColumn(name, expression, XMLTableColumnDefinition.STRING_TYPE);
    }

    public void addNumberColumn(String name, String expression) {
        addColumn(name, expression, XMLTableColumnDefinition.NUMBER_TYPE);
    }

    public void addColumn(XMLTableColumnDefinition column) {
        clearCaches();
        columns.add(column);
    }

    public void removeColumn(XMLTableColumnDefinition column) {
        clearCaches();
        columns.remove(column);
    }

    public void clear() {
        clearCaches();
        columns.clear();
    }

    public XMLTableColumnDefinition getColumn(int index) {
        if (columnArray == null) {
            columnArray = new XMLTableColumnDefinition[columns.size()];
            columns.toArray(columnArray);
        }

        return columnArray[index];
    }

    public XMLTableColumnDefinition getColumn(String columnName) {
        if (columnNameIndex == null) {
            columnNameIndex = new HashMap<String, XMLTableColumnDefinition>();

            for (XMLTableColumnDefinition column : columns) {
                columnNameIndex.put(column.getName(), column);
            }
        }

        return (XMLTableColumnDefinition) columnNameIndex.get(columnName);
    }

    /**
     * Getter for property rowXPath.
     * 
     * @return Value of property rowXPath.
     */
    public XPath getRowXPath() {
        return rowXPath;
    }

    /**
     * Setter for property rowXPath.
     * 
     * @param rowXPath
     *            New value of property rowXPath.
     */
    public void setRowXPath(XPath rowXPath) {
        this.rowXPath = rowXPath;
    }

    public void setRowExpression(String xpath) {
        setRowXPath(createXPath(xpath));
    }

    // VariableContext interface
    // -------------------------------------------------------------------------
    public Object getVariableValue(String namespaceURI, String prefix,
            String localName) {
        XMLTableColumnDefinition column = getColumn(localName);

        if (column != null) {
            return column.getValue(rowValue);
        }

        return null;
    }

    // Implementation methods
    // -------------------------------------------------------------------------
    protected XPath createXPath(String expression) {
        return DocumentHelper.createXPath(expression);
    }

    protected XPath createColumnXPath(String expression) {
        XPath xpath = createXPath(expression);

        // associate my variable context
        xpath.setVariableContext(this);

        return xpath;
    }

    protected void clearCaches() {
        columnArray = null;
        columnNameIndex = null;
    }

    protected void handleException(Exception e) {
        // #### should use jakarta commons-logging
        System.out.println("Caught: " + e);
    }
}

/*
 * 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.
 */
