/*

Derby - Class org.apache.derbyDemo.vtis.core.XmlVTI

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.

*/

package org.apache.derbyDemo.vtis.core;

import java.io.*;
import java.lang.reflect.*;
import java.net.URL;
import java.sql.*;
import java.text.DateFormat;
import java.text.ParseException;
import javax.xml.parsers.*;
import org.w3c.dom.*;

import org.apache.derby.vti.StringColumnVTI;
    
/**
 * <p>
 * This is a VTI designed to read XML files which are structured like row sets.
 * </p>
 *
  */
public  class   XmlVTI  extends StringColumnVTI
{
    ///////////////////////////////////////////////////////////////////////////////////
    //
    // CONSTANTS
    //
    ///////////////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////////////
    //
    // STATE
    //
    ///////////////////////////////////////////////////////////////////////////////////

    private String      _rowTag;
    private String      _xmlResourceName;

    private int             _rowIdx = -1;
    private int             _rowCount = -1;
    private String[]    _currentRow;
    
    private DocumentBuilder _builder;
    private NodeList    _rawRows;

    ///////////////////////////////////////////////////////////////////////////////////
    //
    // CONSTRUCTORS
    //
    ///////////////////////////////////////////////////////////////////////////////////

    /**
     * <p>
     * Build a XmlVTI given the name of an xml resource, the  tag of the row
     * element, and an array of attribute-names/element-tags underneath the row element
     * </p>
     */
    public  XmlVTI( String xmlResourceName, String rowTag, String[] childTags )
    {
        super( childTags );

        _xmlResourceName = xmlResourceName;
        _rowTag = rowTag;
    }
    
    ///////////////////////////////////////////////////////////////////////////////////
    //
    // DATABASE PROCEDURES
    //
    ///////////////////////////////////////////////////////////////////////////////////

    /**
     * <p>
     * Register all of the XMLRow table functions in a class. This method is exposed as a
     * database procedure.
     * </p>
     */
    public  static  void  registerXMLRowVTIs( String className )
        throws Exception
    {
        // find public static methods which return ResultSet
        Class           theClass = Class.forName( className );
        Method[]        methods = theClass.getMethods();
        int             count = methods.length;
        Method          candidate = null;
        XMLRow          xmlRowAnnotation = null;

        for ( int i = 0; i < count; i++ )
        {
            candidate = methods[ i ];

            int         modifiers = candidate.getModifiers();

            if (
                Modifier.isPublic( modifiers ) &&
                Modifier.isStatic( modifiers ) &&
                candidate.getReturnType() == ResultSet.class
                )
            {
                xmlRowAnnotation = candidate.getAnnotation( XMLRow.class );

                if ( xmlRowAnnotation != null )
                {
                    VTIHelper.unregisterVTI( candidate );

                    registerVTI( candidate );
                }
            }            
        }
    }
    
    ///////////////////////////////////////////////////////////////////////////////////
    //
    // StringColumnVTI BEHAVIOR TO BE IMPLEMENTED BY SUBCLASSES
    //
    ///////////////////////////////////////////////////////////////////////////////////

    /**
     * <p>
     * Get the string value of the column in the current row identified by the 1-based columnNumber.
     * </p>
     */
    protected  String  getRawColumn( int columnNumber ) throws SQLException
    {
        try {
            return  _currentRow[ columnNumber - 1 ];
        } catch (Throwable t) { throw new SQLException( t.getMessage() ); }
    }
    
    ///////////////////////////////////////////////////////////////////////////////////
    //
    // ResultSet BEHAVIOR
    //
    ///////////////////////////////////////////////////////////////////////////////////

    public  void close() throws SQLException
    {
        _builder = null;
        _rawRows = null;
    }

    public  ResultSetMetaData   getMetaData() throws SQLException
    {
        throw new SQLException( "Not implemented." );
    }
    
    public  boolean next() throws SQLException
    {
        try {
            if ( _rowIdx < 0 ) { readRows(); }

            if ( ++_rowIdx < _rowCount )
            {
                parseRow( _rowIdx );
                return true;
            }
            else { return false; }
        } catch (Throwable t)
        {
            t.printStackTrace( System.out );
            throw new SQLException( t.getMessage() );
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////
    //
    // PUBLIC BEHAVIOR
    //
    ///////////////////////////////////////////////////////////////////////////////////

    /**
     * <p>
     * Register a method as a Derby Table Function. We assume the following:
     * </p>
     *
     * <ul>
     * <li>The method is public and static.</li>
     * <li>The method returns a ResultSet.</li>
     * <li>The method is annotated as an XMLRow.</li>
     * </ul>
     *
     */
    public  static  void  registerVTI( Method method )
        throws Exception
    {
        XMLRow          annotation = method.getAnnotation( XMLRow.class );

        String          methodName = method.getName();
        String          sqlName = methodName;
        Class           methodClass = method.getDeclaringClass();
        Class[]         parameterTypes = method.getParameterTypes();
        int             parameterCount = parameterTypes.length;
        String[]        columnNames = annotation.childTags();
        String[]        columnTypes = annotation.childTypes();
        int             columnCount = columnNames.length;
        int             typeCount = columnTypes.length;
        StringBuilder   buffer = new StringBuilder();

        if ( columnCount != typeCount )
        {
            throw new Exception
                (
                 "Bad XMLRow annotation for " +
                 methodName +
                 ". The number of childTags (" +
                 columnCount +
                 ") does not equal the number of childTypes (" +
                 typeCount +
                 ")."
                 );
        }

        VTIHelper.registerVTI( method, columnNames, columnTypes, false );        
    }
    
    /**
     * <p>
     * Create a VTI ResultSet. It is assumed that our caller is an
     * XMLRow-annotated method with one String argument. It
     * is assumed that ResultSet is an instance of vtiClass and that
     * vtiClass extends XmlVTI and has a constructor with the same
     * arguments as the constructor of XmlVTI.
     * </p>
     *
     */
    public  static  ResultSet  instantiateVTI( String xmlResourceName )
        throws SQLException
    {
        try {
            // look up the method on top of us
            StackTraceElement[]     stack = (new Throwable()).getStackTrace();
            StackTraceElement       caller = stack[ 1 ];
            Class<?> callerClass = Class.forName( caller.getClassName() );
            String                              methodName = caller.getMethodName();
            Method                          method = callerClass.getMethod
                ( methodName, new Class[] { String.class } );
            XMLRow          annotation = method.getAnnotation( XMLRow.class );
            String              rowTag = annotation.rowTag();
            String[]            childTags = annotation.childTags();
            String              vtiClassName = annotation.vtiClassName();
            Class<?>            vtiClass = Class.forName( vtiClassName );
            Constructor     constructor = vtiClass.getConstructor
                ( new Class[] { String.class, String.class, String[].class } );

            return (ResultSet) constructor.newInstance( xmlResourceName, rowTag, childTags );
            
        } catch (Throwable t) { throw new SQLException( t.getMessage() ); }
    }
    
    ///////////////////////////////////////////////////////////////////////////////////
    //
    // MINIONS
    //
    ///////////////////////////////////////////////////////////////////////////////////

    //////////////////////////
    //
    // XML MINIONS
    //
    //////////////////////////

    /**
     * <p>
     * Fault in the list of rows.
     * </p>
     */
     private    void    readRows() throws Exception
    {
        DocumentBuilderFactory  factory = DocumentBuilderFactory.newInstance();
        
        _builder = factory.newDocumentBuilder();

        URL                 url = new URL( _xmlResourceName );
        InputStream     is = url.openStream();
        Document        doc = _builder.parse( is );
        Element             root = doc.getDocumentElement();

        _rawRows = root.getElementsByTagName( _rowTag );
        _rowCount = _rawRows.getLength();

        is.close();
    }
    
    /**
     * <p>
     * Parse a row into columns.
     * </p>
     */
     private    void    parseRow( int rowNumber ) throws Exception
    {
        Element         rawRow = (Element) _rawRows.item( rowNumber );
        int                 columnCount = getColumnCount();
        
        _currentRow = new String[ columnCount ];

        for ( int i = 0; i < columnCount; i++ )
        {
            // first look for an attribute by the column name
            String      columnName = getColumnName( i + 1 );
            String      contents = rawRow.getAttribute( columnName );

            // if there is not attribute by that name, then look for descendent elements by
            // that name. concatenate them all.
            if ( (contents == null) ||  "".equals( contents ) )
            {
                NodeList    children = rawRow.getElementsByTagName( columnName );

                if ( (children != null) && (children.getLength() > 0) )
                {
                    int                 childCount = children.getLength();
                    StringBuffer    buffer = new StringBuffer();

                    for ( int j = 0; j < childCount; j++ )
                    {
                        Element     child = (Element) children.item( j );
                        // separate values with spaces.
                        if (j != 0)
                            buffer.append(" ");
                        buffer.append( squeezeText( child ) );
                    }
                    contents = buffer.toString();
                }
            }

            _currentRow[ i ] = contents;
        }
    }
    
    /**
     * <p>
     * Squeeze the text out of an Element.
     * </p>
     */
    private String squeezeText( Element node )
        throws Exception
    {
        String      text = null;
        Node        textChild = node.getFirstChild();

        if ( textChild != null ) { text = textChild.getNodeValue(); }

        return text;
    }


}

