/**
 * Copyright (C) 2010 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.processor;

import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.xml.XMLReceiver;
import org.orbeon.oxf.xml.NamingConvention;
import org.xml.sax.ContentHandler;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * This is the class that must be extended to create a custom processor. See the <a
 * href="http://wiki.orbeon.com/forms/doc/developer-guide/api-xpl-processor-api">Processor API section</a>  for more information.
 */
public abstract class SimpleProcessor extends ProcessorImpl {

    private static final String GENERATE = "generate";

    private Map<String, Method> outputToMethod = new HashMap<String, Method>();

    /**
     * This constructor must be called by subclasses.
     *
     * Iterate through the generateXxx() methods and stores a reference to the method in outputToMethod.
     *
     * This checks for the following signatures:
     *
     *  generateXxx(PipelineContext context, XMLReceiver xmlReceiver)
     *  generateXxx(PipelineContext context, ContentHandler contentHandler)
     *
     * For a given "Xxx", only one of the two methods should be implemented. If both are present, the ContentHandler
     * version is ignored.
     */
    public SimpleProcessor() {
        final Method[] methods = getClass().getDeclaredMethods();
        for (final Method method : methods) {
            final Class[] parameterTypes = method.getParameterTypes();
            if (method.getName().startsWith(GENERATE) && parameterTypes.length == 2
                    && parameterTypes[0].equals(PipelineContext.class)
                    && (parameterTypes[1].equals(XMLReceiver.class) || parameterTypes[1].equals(ContentHandler.class))) {
                final String javaName = method.getName().substring(GENERATE.length());
                outputToMethod.put(NamingConvention.javaToXMLName(javaName), method);
            }
        }
    }

    /**
     * Create an output. Delegate processing to the appropriate generateXXX() method.
     */
    @Override
    public ProcessorOutput createOutput(final String name) {
        final ProcessorOutput output = new ProcessorOutputImpl(SimpleProcessor.this, name) {
            public void readImpl(PipelineContext context, XMLReceiver xmlReceiver) {
                try {
                    final Method method = outputToMethod.get(name);
                    if (method == null)
                        throw new OXFException("Cannot find \"generate\" method for output \"" + name
                                + "\" in class \"" + SimpleProcessor.this.getClass().getName() + "\"");
                    method.invoke(SimpleProcessor.this, context, xmlReceiver);
                } catch (Exception e) {
                    throw new OXFException(e);
                }
            }
        };
        addOutput(name, output);
        return output;
    }
}
