/*
 * $Id: AbstractSerialDelegatingDefinitionParser.java 10494 2008-01-23 21:09:56Z acooke $
 * --------------------------------------------------------------------------------------
 * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */

package org.mule.config.spring.parsers.delegate;

import org.mule.config.spring.MuleHierarchicalBeanDefinitionParserDelegate;
import org.mule.config.spring.parsers.AbstractMuleBeanDefinitionParser;
import org.mule.config.spring.parsers.MuleDefinitionParser;
import org.mule.config.spring.parsers.MuleDefinitionParserConfiguration;
import org.mule.config.spring.parsers.PreProcessor;
import org.mule.config.spring.parsers.assembly.configuration.PropertyConfiguration;
import org.mule.util.StringUtils;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import edu.emory.mathcs.backport.java.util.Arrays;

import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

/**
 * This allows a set of definition parsers to be used, one after another, to process
 * the same element.  This lets multiple beans be generated from a single element.
 *
 * <p>Since each bean typically needs a spearate name, this class guarantees that the
 * name and id attributes are reset before each call.  Delegates can then modify these
 * on the element without worrying about interfering with other parsers.</p>
 *
 * <p>Typically, subclasses will add additional processing with
 * {@link org.mule.config.spring.parsers.PreProcessor} and
 * {@link org.mule.config.spring.parsers.PostProcessor} anonymous classes.</p>
 */
public abstract class AbstractSerialDelegatingDefinitionParser extends AbstractDelegatingDefinitionParser
{

    private int index = 0;
    private boolean first;
    private boolean doReset;
    private String originalId;
    private String originalName;
    private Set handledExceptions = new HashSet();

    public AbstractSerialDelegatingDefinitionParser()
    {
        this(true); // by default, reset name
    }

    /**
     * @param doReset Should the name be reset after called.  This is typically true (it protects the
     * parent from changes made by children) unless this is itself nested.
     */
    public AbstractSerialDelegatingDefinitionParser(boolean doReset)
    {
        this.doReset = doReset;
    }

    public AbstractBeanDefinition muleParse(Element element, ParserContext parserContext)
    {
        if (index == 0 || index >= size())
        {
            first = true;
            index = 0;
        }
        else
        {
            first = false;
        }
        AbstractBeanDefinition bean = null;
        while (null == bean && index < size())
        {
            try
            {
                MuleDefinitionParser parser = getDelegate(index);
                bean = doSingleBean(index++, parser, element, parserContext);
            }
            catch (RuntimeException e)
            {
                if (isExceptionHandled(e))
                {
                    bean = null;
                }
                else
                {
                    throw e;
                }
            }
        }
        if (null != bean)
        {
            if (index == size())
            {
                bean.removeAttribute(MuleHierarchicalBeanDefinitionParserDelegate.MULE_REPEAT_PARSE);
            }
            else
            {
                bean.setAttribute(MuleHierarchicalBeanDefinitionParserDelegate.MULE_REPEAT_PARSE, Boolean.TRUE);
            }
        }
        return bean;
    }

    protected boolean isExceptionHandled(Exception e)
    {
        return handledExceptions.contains(e.getClass());
    }

    protected AbstractBeanDefinition doSingleBean(int index, MuleDefinitionParser parser,
                                                  Element element, ParserContext parserContext)
    {
        return parser.muleParse(element, parserContext);
    }

    protected MuleDefinitionParserConfiguration addDelegate(MuleDefinitionParser delegate)
    {
        delegate.registerPreProcessor(new PreProcessor()
        {
            public void preProcess(PropertyConfiguration config, Element element)
            {
                if (first)
                {
                    originalId = element.getAttribute(AbstractMuleBeanDefinitionParser.ATTRIBUTE_ID);
                    originalName = element.getAttribute(AbstractMuleBeanDefinitionParser.ATTRIBUTE_NAME);
                }
                else if (doReset)
                {
                    resetNameAndId(element);
                }
            }
        });
        return super.addDelegate(delegate);
    }

    protected void resetNameAndId(Element element)
    {
        resetAttribute(element, AbstractMuleBeanDefinitionParser.ATTRIBUTE_ID, originalId);
        resetAttribute(element, AbstractMuleBeanDefinitionParser.ATTRIBUTE_NAME, originalName);
    }

    protected void resetAttribute(Element element, String name, String value)
    {
        if (StringUtils.isEmpty(value))
        {
            if (element.hasAttribute(name))
            {
                element.removeAttribute(name);
            }
        }
        else
        {
            element.setAttribute(name, value);
        }
    }

    protected void addHandledException(Class exception)
    {
        handledExceptions.add(exception);
    }

    /**
     * A utility class for selecting certain attributes.  If the attributes are enabled,
     * the default is set to block others; if specific attributes are disabled the default
     * is set to allow others.
     *
     * @param delegate
     * @param attributes
     * @param enable
     */
    public static void enableAttributes(MuleDefinitionParser delegate, String[] attributes, boolean enable)
    {
        // if enabling specific attributes, block globally
        delegate.setIgnoredDefault(enable);

        Iterator names = Arrays.asList(attributes).iterator();
        while (names.hasNext())
        {
            String name = (String) names.next();
            if (enable)
            {
                delegate.removeIgnored(name);
            }
            else
            {
                delegate.addIgnored(name);
            }
        }
    }

    public static void enableAttributes(MuleDefinitionParser delegate, String[][] attributes)
    {
        for (int i = 0; i < attributes.length; ++i)
        {
            enableAttributes(delegate, attributes[i], true);
        }
    }

    public static void enableAttributes(MuleDefinitionParser delegate, String[] attributes)
    {
        enableAttributes(delegate, attributes, true);
    }

    public static void enableAttribute(MuleDefinitionParser delegate, String attribute)
    {
        enableAttributes(delegate, new String[]{attribute}, true);
    }

    public static void disableAttributes(MuleDefinitionParser delegate, String[][] attributes)
    {
        for (int i = 0; i < attributes.length; ++i)
        {
            enableAttributes(delegate, attributes[i], false);
        }
    }

    public static void disableAttributes(MuleDefinitionParser delegate, String[] attributes)
    {
        enableAttributes(delegate, attributes, false);
    }

    public static void disableAttribute(MuleDefinitionParser delegate, String attribute)
    {
        enableAttributes(delegate, new String[]{attribute}, false);
    }

}
