package org.apache.torque.generator.configuration.source;

/*
 * 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 static org.apache.torque.generator.configuration.source.SourceConfigurationTags.ELEMENTS_ATTRIBUTE;
import static org.apache.torque.generator.configuration.source.SourceConfigurationTags.TRANSFORMER_CLASS_ATTRIBUTE;
import static org.apache.torque.generator.configuration.source.SourceConfigurationTags.TRANSFORMER_TAG;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.torque.generator.configuration.ConfigurationProvider;
import org.apache.torque.generator.configuration.paths.ProjectPaths;
import org.apache.torque.generator.source.transform.SourceTransformer;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Reads a source transformer from the controller configuration file.
 */
public class SourceTransformerSaxHandler extends DefaultHandler
{
    /** The logger for this class. */
    private static Log log = LogFactory.getLog(DefaultHandler.class);

    /** The SourceTransformer which is currently configured. */
    private SourceTransformer sourceTransformer;

    /** On which Elements the sourceTransformer should be applied. */
    private String elements;

    /** The current nesting level inside the processed element. */
    private int level = 0;

    /**
     * The name of the currently read bean property of the transformer,
     * or null if no property is currently read.
     */
    private String propertyName;

    /**
     * The value of the currently read bean property of the transformer,
     * or null if no property is currently read or the property is a
     * list property.
     */
    private StringBuilder simplePropertyValue;

    /**
     * The value of the currently read bean property of the transformer,
     * or null if no property is currently read or the property is a
     * simple property.
     */
    private ArrayList<String> listPropertyValue;

    /**
     * The value of the currently read list entry of the currently read
     * property of the transformer,
     * or null if no list entry is currently read or the property is a
     * simple property.
     */
    private StringBuilder listPropertyEntry;

    /**
     * Constructor.
     *
     * @param configurationProvider The access object for the configuration
     *        files, not null.
     * @param projectPaths The paths of the surrounding project, not null.
     *
     * @throws NullPointerException if an argument is null.
     */
    public SourceTransformerSaxHandler(
            ConfigurationProvider configurationProvider,
            ProjectPaths projectPaths)
    {
        if (configurationProvider == null)
        {
            throw new NullPointerException("configurationProvider must not be null");
        }
        if (projectPaths == null)
        {
            throw new NullPointerException("projectPaths must not be null");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void startElement(String uri, String localName, String rawName,
                             Attributes attributes)
            throws SAXException
    {
        if (level == 0)
        {
            level++;
            if (rawName.equals(TRANSFORMER_TAG))
            {
                String className = attributes.getValue(
                        TRANSFORMER_CLASS_ATTRIBUTE);
                if (className == null)
                {
                    throw new SAXException(
                            "The attribute " + TRANSFORMER_CLASS_ATTRIBUTE
                            + " must not be null for the element "
                            + TRANSFORMER_TAG);
                }
                elements = attributes.getValue(ELEMENTS_ATTRIBUTE);
                sourceTransformer = createJavaSourceTransformer(className);
            }
            else
            {
                throw new SAXException("Unknown element " + rawName);
            }
        }
        else if (level == 1)
        {
            level++;
            propertyName = rawName;
            simplePropertyValue = new StringBuilder();
        }
        else if (level == 2)
        {
            level++;
            if (simplePropertyValue.length() > 0
                    && !StringUtils.isWhitespace(simplePropertyValue.toString()))
            {
                throw new SAXException(
                        "Cannot parse both text content and child elements "
                        + " in element " + propertyName);
            }
            simplePropertyValue = null;
            if (listPropertyValue == null)
            {
                listPropertyValue = new ArrayList<String>();
            }
            listPropertyEntry = new StringBuilder();
        }
        else
        {
            throw new SAXException("unknown Element " + rawName);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void endElement(String uri, String localName, String rawName)
        throws SAXException
    {
        level--;
        if (level == 2)
        {
            listPropertyValue.add(listPropertyEntry.toString());
            listPropertyEntry = null;
        }
        else if (level == 1)
        {
            if (!PropertyUtils.isWriteable(sourceTransformer, propertyName))
            {
                throw new SAXException("No setter found for property "
                        + propertyName
                        + " in class "
                        + sourceTransformer.getClass().getName());
            }
            Object propertyValue;
            if (simplePropertyValue != null)
            {
                propertyValue = simplePropertyValue.toString();
            }
            else
            {
                propertyValue = listPropertyValue;
            }
            try
            {
                BeanUtils.copyProperty(
                        sourceTransformer,
                        propertyName,
                        propertyValue);
            }
            catch (InvocationTargetException e)
            {
                throw new SAXException("error while setting Property "
                        + propertyName
                        + " for java transformer "
                        + sourceTransformer.getClass().getName()
                        + " with value "
                        + propertyValue.toString(),
                    e);
            }
            catch (IllegalAccessException e)
            {
                throw new SAXException("error while setting Property "
                        + propertyName
                        + " for java transformer "
                        + sourceTransformer.getClass().getName()
                        + " with value "
                        + propertyValue.toString(),
                    e);
            }
            propertyName = null;
            propertyValue = null;
        }
        else if (level != 0)
        {
            throw new SAXException("endElemend reached Level " + level);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void characters(char[] ch, int start, int length)
            throws SAXException
    {
        if (simplePropertyValue != null)
        {
            for (int i = start; i < start + length; ++i)
            {
                simplePropertyValue.append(ch[i]);
            }
            return;
        }
        if (listPropertyEntry != null)
        {
            for (int i = start; i < start + length; ++i)
            {
                listPropertyEntry.append(ch[i]);
            }
            return;
        }
    }

    /**
     * Returns the configuration filled with the contents of the parsed snippet.
     *
     * @return the configuration which was filled, not null if a
     *         matching snippet was processed.
     */
    public SourceTransformer getSourceTransformer()
    {
        return sourceTransformer;
    }

    /**
     * Returns the path to source elements to which the transformer is applied.
     *
     * @return the path to the source element, or null to apply the transformer
     *         to the root element.
     */
    public String getElements()
    {
        return elements;
    }

    /**
     * Returns the configuration filled with the contents of the parsed snippet.
     *
     * @return the configuration which was filled, not null if a
     *         matching snippet was processed.
     */
    public boolean isFinished()
    {
        return (sourceTransformer != null && level == 0);
    }

    /**
     * Creates an instance of a transformer.
     *
     * @param className the name of the transformer to be created.
     *
     * @return the instance of the transformer, not null.
     *
     * @throws ExceptionInInitializerError if an error occurs in the
     *        initializer of the transformer.
     * @throws LinkageError if the linkage fails.
     * @throws SAXException if any other  error occurs during instantiation
     *         of the class.
     */
    private static SourceTransformer createJavaSourceTransformer(
            String className)
        throws SAXException
    {
        Class<?> transformerClass;
        try
        {
            transformerClass = Class.forName(className);
        }
        catch (ClassNotFoundException e)
        {
            throw new SAXException(
                    "Error  while initializing a source transformer :"
                    + " Could not load class " + className, e);
        }
        catch (ExceptionInInitializerError e)
        {
            log.error(
                    "Error  while initializing a source transformer :"
                    + " Could not initialize class " + className
                    + " : " + e.getMessage());
            throw e;
        }
        catch (LinkageError e)
        {
            log.error(
                    "Error  while initializing a source transformer :"
                    + " Could not link class " + className
                    + " : " + e.getMessage());
            throw e;
        }

        SourceTransformer result;
        try
        {
            Constructor<?> constructor
                    = transformerClass.getConstructor();
            result = (SourceTransformer) constructor.newInstance();
        }
        catch (NoSuchMethodException e)
        {
            throw new SAXException(
                    "Error  while instantiating a source transformer :"
                    + " The class " + className
                    + " has no default constructor",
                e);
        }
        catch (ClassCastException e)
        {
            throw new SAXException(
                    "Error  while instantiating a source transformer :"
                    + " The class " + className
                    + " is not an instance of "
                    + SourceTransformer.class.getName(),
                e);
        }
        catch (IllegalAccessException e)
        {
            throw new SAXException(
                    "Error  while instantiating a source transformer :"
                    + " The constructor of class "
                    + className + " could not be accessed",
                e);
        }
        catch (InvocationTargetException e)
        {
            throw new SAXException(
                    "Error  while instantiating a source transformer :"
                    + " The constructor of class "
                    + className + " could not be called",
                e);
        }
        catch (InstantiationException e)
        {
            throw new SAXException(
                    "Error  while instantiating a source transformer :"
                    + " The class " + className
                    + " represents an abstract class, "
                    + "an interface, an array class, a primitive type, "
                    + "or void, or the class has no parameterless constructor, "
                    + "or the instantiation fails for some other reason.",
                e);
        }
        catch (SecurityException e)
        {
            throw new SAXException(
                    "Error  while instantiating a source transformer :"
                    + " The security manager denies instantiation of the class "
                    + className,
                e);
        }

        return result;
    }
}
