/**************************************************************************************
 * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved.                 *
 * http://aspectwerkz.codehaus.org                                                    *
 * ---------------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the LGPL license      *
 * a copy of which has been included with this distribution in the license.txt file.  *
 **************************************************************************************/
package org.codehaus.aspectwerkz.definition;

import org.codehaus.aspectwerkz.util.Strings;
import org.codehaus.aspectwerkz.aspect.AdviceType;
import org.codehaus.aspectwerkz.DeploymentModel;
import org.codehaus.aspectwerkz.intercept.AdvisableImpl;
import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
import org.codehaus.aspectwerkz.reflect.impl.java.JavaMethodInfo;
import org.codehaus.aspectwerkz.reflect.impl.java.JavaClassInfo;
import org.codehaus.aspectwerkz.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.ClassInfoHelper;
import org.codehaus.aspectwerkz.reflect.MethodInfo;
import org.codehaus.aspectwerkz.expression.regexp.Pattern;
import org.codehaus.aspectwerkz.expression.ExpressionNamespace;
import org.codehaus.aspectwerkz.expression.ExpressionInfo;
import org.codehaus.aspectwerkz.annotation.AspectAnnotationParser;
import org.codehaus.aspectwerkz.annotation.MixinAnnotationParser;
import org.codehaus.aspectwerkz.exception.DefinitionException;
import org.codehaus.aspectwerkz.transform.TransformationConstants;
import org.codehaus.aspectwerkz.transform.inlining.AspectModelManager;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.StringTokenizer;

/**
 * Parses the XML definition using <tt>dom4j</tt>.
 *
 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
 * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
 */
public class DocumentParser {

    /**
     * Parses aspect class names.
     *
     * @param document the defintion as a document
     * @return the aspect class names
     */
    public static List parseAspectClassNames(final Document document) {
        final List aspectClassNames = new ArrayList();
        for (Iterator it1 = document.getRootElement().elementIterator("system"); it1.hasNext();) {
            Element system = (Element) it1.next();
            final String basePackage = getBasePackage(system);
            for (Iterator it11 = system.elementIterator("aspect"); it11.hasNext();) {
                String className = null;
                Element aspect = (Element) it11.next();
                for (Iterator it2 = aspect.attributeIterator(); it2.hasNext();) {
                    Attribute attribute = (Attribute) it2.next();
                    final String name = attribute.getName().trim();
                    final String value = attribute.getValue().trim();
                    if (name.equalsIgnoreCase("class")) {
                        className = value;
                    }
                }
                aspectClassNames.add(basePackage + className);
            }
            for (Iterator it11 = system.elementIterator("package"); it11.hasNext();) {
                final Element packageElement = ((Element) it11.next());
                final String packageName = getPackage(packageElement);
                for (Iterator it12 = packageElement.elementIterator("aspect"); it12.hasNext();) {
                    String className = null;
                    Element aspect = (Element) it12.next();
                    for (Iterator it2 = aspect.attributeIterator(); it2.hasNext();) {
                        Attribute attribute = (Attribute) it2.next();
                        final String name = attribute.getName().trim();
                        final String value = attribute.getValue().trim();
                        if (name.equalsIgnoreCase("class")) {
                            className = value;
                        }
                    }
                    aspectClassNames.add(packageName + className);
                }
            }
        }
        aspectClassNames.add(Virtual.class.getName());

        return aspectClassNames;
    }

    /**
     * Parses the definition DOM document.
     *
     * @param document    the defintion as a document
     * @param systemDef   the system definition
     * @param aspectClass the aspect class
     * @return the definition
     */
    public static AspectDefinition parseAspectDefinition(final Document document,
                                                         final SystemDefinition systemDef,
                                                         final Class aspectClass) {

        final Element aspect = document.getRootElement();

        if (!aspect.getName().equals("aspect")) {
            throw new DefinitionException("XML definition for aspect is not well-formed: " + document.asXML());
        }
        String specialAspectName = null;
        String className = null;
        String deploymentModelAsString = null;
        String containerClassName = null;
        for (Iterator it2 = aspect.attributeIterator(); it2.hasNext();) {
            Attribute attribute = (Attribute) it2.next();
            final String name = attribute.getName().trim();
            final String value = attribute.getValue().trim();
            if (name.equalsIgnoreCase("class")) {
                className = value;
            } else if (name.equalsIgnoreCase("deployment-model")) {
                deploymentModelAsString = value;
            } else if (name.equalsIgnoreCase("name")) {
                specialAspectName = value;
            } else if (name.equalsIgnoreCase("container")) {
                containerClassName = value;
            }
        }
        if (specialAspectName == null) {
            specialAspectName = className;
        }

        final ClassInfo classInfo = JavaClassInfo.getClassInfo(aspectClass);
        final ClassLoader loader = aspectClass.getClassLoader();

        // create the aspect definition
        final AspectDefinition aspectDef = new AspectDefinition(specialAspectName, classInfo, systemDef);
        //TODO: if this XML centric deployment is supposed to PRESERVE @Aspect values, then it is broken
        aspectDef.setContainerClassName(containerClassName);
        aspectDef.setDeploymentModel(DeploymentModel.getDeploymentModelFor(deploymentModelAsString));

        parsePointcutElements(aspect, aspectDef); //needed to support undefined named pointcut in Attributes AW-152

        // load the different aspect model and let them define their aspects
        AspectModelManager.defineAspect(classInfo, aspectDef, loader);

        // parse the aspect info
        parseParameterElements(aspect, aspectDef);
        parsePointcutElements(aspect, aspectDef); //reparse pc for XML override (AW-152)
        parseAdviceElements(aspect, aspectDef, JavaClassInfo.getClassInfo(aspectClass));
        parseIntroduceElements(aspect, aspectDef, "", aspectClass.getClassLoader());

        systemDef.addAspect(aspectDef);
        return aspectDef;
    }

    /**
     * Parses the definition DOM document.
     *
     * @param loader   the current class loader
     * @param document the defintion as a document
     * @return the definitions
     */
    public static Set parse(final ClassLoader loader, final Document document) {
        final Element root = document.getRootElement();

        // parse the transformation scopes
        return parseSystemElements(loader, root);
    }

    /**
     * Parses the <tt>system</tt> elements.
     *
     * @param loader the current class loader
     * @param root   the root element
     */
    private static Set parseSystemElements(final ClassLoader loader, final Element root) {
        final Set systemDefs = new HashSet();
        for (Iterator it1 = root.elementIterator("system"); it1.hasNext();) {
            Element system = (Element) it1.next();
            SystemDefinition definition = parseSystemElement(loader, system, getBasePackage(system));
            if (definition != null) {
                systemDefs.add(definition);
            }
        }
        return systemDefs;
    }

    /**
     * Parses the <tt>system</tt> elements.
     *
     * @param loader        the current class loader
     * @param systemElement the system element
     * @param basePackage   the base package
     * @return the definition for the system
     */
    private static SystemDefinition parseSystemElement(final ClassLoader loader,
                                                       final Element systemElement,
                                                       final String basePackage) {
        String uuid = systemElement.attributeValue("id");
        if ((uuid == null) || uuid.equals("")) {
            throw new DefinitionException("system UUID must be specified");
        }
        final SystemDefinition definition = new SystemDefinition(uuid);

        // add the virtual aspect
        addVirtualAspect(definition);

        // parse the global pointcuts
        List globalPointcuts = parseGlobalPointcutDefs(systemElement);
        //FIXME: systemDef should link a namespace, + remove static hashmap in Namespace (uuid clash in parallel CL)
        ExpressionNamespace systemNamespace = ExpressionNamespace.getNamespace(definition.getUuid());
        for (Iterator iterator = globalPointcuts.iterator(); iterator.hasNext();) {
            PointcutInfo pointcutInfo = (PointcutInfo) iterator.next();
            systemNamespace.addExpressionInfo(
                    pointcutInfo.name, new ExpressionInfo(pointcutInfo.expression, systemNamespace.getName())
            );
        }

        // parse the global deployment scopes definitions
        parseDeploymentScopeDefs(systemElement, definition);

        // parse the global advisable definitions
        parseAdvisableDefs(systemElement, definition);

        // parse the include, exclude and prepare elements
        parseIncludePackageElements(systemElement, definition, basePackage);
        parseExcludePackageElements(systemElement, definition, basePackage);
        parsePrepareElements(systemElement, definition, basePackage);

        // parse without package elements
        parseAspectElements(loader, systemElement, definition, basePackage, globalPointcuts);

        // parse without package elements
        parseMixinElements(loader, systemElement, definition, basePackage);

        // parse with package elements
        parsePackageElements(loader, systemElement, definition, basePackage, globalPointcuts);

        // add all deployment scopes to the virtual advice
        DefinitionParserHelper.attachDeploymentScopeDefsToVirtualAdvice(definition);

        return definition;
    }

    /**
     * Parses the global pointcuts.
     *
     * @param systemElement the system element
     * @return a list with the pointcuts
     */
    private static List parseGlobalPointcutDefs(final Element systemElement) {
        final List globalPointcuts = new ArrayList();
        for (Iterator it11 = systemElement.elementIterator("pointcut"); it11.hasNext();) {
            PointcutInfo pointcutInfo = new PointcutInfo();
            Element globalPointcut = (Element) it11.next();
            for (Iterator it2 = globalPointcut.attributeIterator(); it2.hasNext();) {
                Attribute attribute = (Attribute) it2.next();
                final String name = attribute.getName().trim();
                final String value = attribute.getValue().trim();
                if (name.equalsIgnoreCase("name")) {
                    pointcutInfo.name = value;
                } else if (name.equalsIgnoreCase("expression")) {
                    pointcutInfo.expression = value;
                }
            }
            // pointcut CDATA is expression unless already specified as an attribute
            if (pointcutInfo.expression == null) {
                pointcutInfo.expression = globalPointcut.getTextTrim();
            }
            globalPointcuts.add(pointcutInfo);
        }
        return globalPointcuts;
    }

    /**
     * Parses the global deployment-scope elements.
     *
     * @param systemElement the system element
     * @param definition
     */
    private static void parseDeploymentScopeDefs(final Element systemElement,
                                                 final SystemDefinition definition) {
        for (Iterator it11 = systemElement.elementIterator("deployment-scope"); it11.hasNext();) {
            String expression = null;
            String name = null;
            Element globalPointcut = (Element) it11.next();
            for (Iterator it2 = globalPointcut.attributeIterator(); it2.hasNext();) {
                Attribute attribute = (Attribute) it2.next();
                final String attrName = attribute.getName().trim();
                final String attrValue = attribute.getValue().trim();
                if (attrName.equalsIgnoreCase("name")) {
                    name = attrValue;
                } else if (attrName.equalsIgnoreCase("expression")) {
                    expression = attrValue;
                }
            }
            // pointcut CDATA is expression unless already specified as an attribute
            if (expression == null) {
                expression = globalPointcut.getTextTrim();
            }
            DefinitionParserHelper.createAndAddDeploymentScopeDef(name, expression, definition);
        }
    }

    /**
     * Parses the global advisable elements.
     *
     * @param systemElement the system element
     * @param definition
     */
    private static void parseAdvisableDefs(final Element systemElement,
                                           final SystemDefinition definition) {
        for (Iterator it11 = systemElement.elementIterator("advisable"); it11.hasNext();) {
            Element advisableElement = (Element) it11.next();
            String expression = "";
            String pointcutTypes = "all";
            for (Iterator it2 = advisableElement.attributeIterator(); it2.hasNext();) {
                Attribute attribute = (Attribute) it2.next();
                final String name = attribute.getName().trim();
                final String value = attribute.getValue().trim();
                if (name.equalsIgnoreCase("expression")) {
                    expression = value;
                } else if (name.equalsIgnoreCase("pointcut-type")) {
                    pointcutTypes = value;
                }
            }
            // pointcut CDATA is expression unless already specified as an attribute
            if (expression == null) {
                expression = advisableElement.getTextTrim();
            }
            handleAdvisableDefinition(definition, expression, pointcutTypes);
        }
    }

    /**
     * Parses the definition DOM document.
     *
     * @param loader          the current class loader
     * @param systemElement   the system element
     * @param definition      the definition
     * @param basePackage     the base package
     * @param globalPointcuts the global pointcuts
     */
    private static void parsePackageElements(final ClassLoader loader,
                                             final Element systemElement,
                                             final SystemDefinition definition,
                                             final String basePackage,
                                             final List globalPointcuts) {
        for (Iterator it1 = systemElement.elementIterator("package"); it1.hasNext();) {
            final Element packageElement = ((Element) it1.next());
            final String packageName = basePackage + getPackage(packageElement);
            parseAspectElements(loader, packageElement, definition, packageName, globalPointcuts);
            parseMixinElements(loader, packageElement, definition, packageName);
            parseAdvisableDefs(packageElement, definition);
        }
    }

    /**
     * Parses the <tt>aspect</tt> elements.
     *
     * @param loader          the current class loader
     * @param systemElement   the system element
     * @param definition      the definition object
     * @param packageName     the package name
     * @param globalPointcuts the global pointcuts
     */
    private static void parseAspectElements(final ClassLoader loader,
                                            final Element systemElement,
                                            final SystemDefinition definition,
                                            final String packageName,
                                            final List globalPointcuts) {

        for (Iterator it1 = systemElement.elementIterator("aspect"); it1.hasNext();) {
            String aspectName = null;
            String className = null;
            String deploymentModel = null;
            String containerClassName = null;
            Element aspect = (Element) it1.next();
            for (Iterator it2 = aspect.attributeIterator(); it2.hasNext();) {
                Attribute attribute = (Attribute) it2.next();
                final String name = attribute.getName().trim();
                final String value = attribute.getValue().trim();
                if (name.equalsIgnoreCase("class")) {
                    className = value;
                } else if (name.equalsIgnoreCase("deployment-model")) {
                    deploymentModel = value;
                } else if (name.equalsIgnoreCase("name")) {
                    aspectName = value;
                } else if (name.equalsIgnoreCase("container")) {
                    containerClassName = value;
                }
            }
            String aspectClassName = packageName + className;
            if (aspectName == null) {
                aspectName = aspectClassName;
            }

            // create the aspect definition
            ClassInfo aspectClassInfo;
            try {
                aspectClassInfo = AsmClassInfo.getClassInfo(aspectClassName, loader);
            } catch (Exception e) {
                System.err.println(
                        "Warning: could not load aspect "
                        + aspectClassName
                        + " from "
                        + loader
                        + "due to: "
                        + e.toString()
                );
                e.printStackTrace();
                continue;
            }

            final AspectDefinition aspectDef = new AspectDefinition(aspectName, aspectClassInfo, definition);

            // add the global pointcuts to the aspect
            for (Iterator it = globalPointcuts.iterator(); it.hasNext();) {
                PointcutInfo pointcutInfo = (PointcutInfo) it.next();
                DefinitionParserHelper.createAndAddPointcutDefToAspectDef(
                        pointcutInfo.name,
                        pointcutInfo.expression,
                        aspectDef
                );
            }
            parsePointcutElements(aspect, aspectDef); //needed to support undefined named pointcut in Attributes AW-152

            // load the different aspect model and let them define their aspects
            AspectModelManager.defineAspect(aspectClassInfo, aspectDef, loader);

            // parse the class bytecode annotations
            AspectAnnotationParser.parse(aspectClassInfo, aspectDef, loader);

            // XML definition settings always overrides attribute definition settings
            // AW-357
            if (!Strings.isNullOrEmpty(deploymentModel)) {
                aspectDef.setDeploymentModel(DeploymentModel.getDeploymentModelFor(deploymentModel));
            }
            if (!Strings.isNullOrEmpty(aspectName)) {
                aspectDef.setName(aspectName);
            }
            if (!Strings.isNullOrEmpty(containerClassName)) {
                aspectDef.setContainerClassName(containerClassName);
            }

            // parse the aspect info
            parseParameterElements(aspect, aspectDef);
            parsePointcutElements(aspect, aspectDef); //reparse pc for XML override (AW-152)
            parseAdviceElements(aspect, aspectDef, aspectClassInfo);
            parseIntroduceElements(aspect, aspectDef, packageName, loader);

            definition.addAspect(aspectDef);
        }
    }

    /**
     * Parses the <tt>mixin</tt> elements.
     *
     * @param loader           the current class loader
     * @param systemElement    the system element
     * @param systemDefinition the system definition
     * @param packageName      the package name
     */
    private static void parseMixinElements(final ClassLoader loader,
                                           final Element systemElement,
                                           final SystemDefinition systemDefinition,
                                           final String packageName) {

        for (Iterator it1 = systemElement.elementIterator("mixin"); it1.hasNext();) {
            String className = null;
            String deploymentModelAsString = null;
            boolean isTransient = false;
            boolean isTransientSetInXML = false;
            String factoryClassName = null;
            String expression = null;
            Element mixin = (Element) it1.next();
            for (Iterator it2 = mixin.attributeIterator(); it2.hasNext();) {
                Attribute attribute = (Attribute) it2.next();
                final String name = attribute.getName().trim();
                final String value = attribute.getValue().trim();
                if (name.equalsIgnoreCase("class")) {
                    className = value;
                } else if (name.equalsIgnoreCase("deployment-model") && value != null) {
                    deploymentModelAsString = value;
                } else if (name.equalsIgnoreCase("transient")) {
                    if (value != null && value.equalsIgnoreCase("true")) {
                        isTransient = true;
                        isTransientSetInXML = true;
                    }
                } else if (name.equalsIgnoreCase("factory")) {
                    factoryClassName = value;
                } else if (name.equalsIgnoreCase("bind-to")) {
                    expression = value;
                }
            }
            String mixinClassName = packageName + className;

            // create the mixin definition
            ClassInfo mixinClassInfo;
            try {
                mixinClassInfo = AsmClassInfo.getClassInfo(mixinClassName, loader);
            } catch (Exception e) {
                System.err.println(
                        "Warning: could not load mixin "
                        + mixinClassName
                        + " from "
                        + loader
                        + "due to: "
                        + e.toString()
                );
                e.printStackTrace();
                continue;
            }

            final DeploymentModel deploymentModel =
                    (deploymentModelAsString != null) ? DeploymentModel.getDeploymentModelFor(deploymentModelAsString)
                    : DeploymentModel.PER_INSTANCE;

            final MixinDefinition mixinDefinition =
                    DefinitionParserHelper.createAndAddMixinDefToSystemDef(
                            mixinClassInfo,
                            expression,
                            deploymentModel,
                            isTransient,
                            systemDefinition
                    );

            // parse the class bytecode annotations
            MixinAnnotationParser.parse(mixinClassInfo, mixinDefinition);

            // XML definition settings always overrides attribute definition settings if present
            if (!Strings.isNullOrEmpty(deploymentModelAsString)) {
                mixinDefinition.setDeploymentModel(DeploymentModel.getDeploymentModelFor(deploymentModelAsString));
            }
            if (!Strings.isNullOrEmpty(factoryClassName)) {
                mixinDefinition.setFactoryClassName(factoryClassName);
            }
            if (isTransientSetInXML) {
                mixinDefinition.setTransient(isTransient);
            }

            parseParameterElements(mixin, mixinDefinition);
        }
    }

    /**
     * Adds a virtual system aspect to the definition. Needed to do various tricks.
     *
     * @param definition
     */
    public static void addVirtualAspect(final SystemDefinition definition) {
        final Class clazz = Virtual.class;
        final String aspectName = clazz.getName();
        ClassInfo aspectClassInfo = JavaClassInfo.getClassInfo(clazz);
        final AspectDefinition aspectDef = new AspectDefinition(aspectName, aspectClassInfo, definition);
        try {
            MethodInfo methodInfo = JavaMethodInfo.getMethodInfo(clazz.getDeclaredMethod("virtual", new Class[]{}));
            aspectDef.addBeforeAdviceDefinition(
                    new AdviceDefinition(
                            methodInfo.getName(),
                            AdviceType.BEFORE,
                            null,
                            aspectName,
                            aspectName,
                            null,
                            methodInfo,
                            aspectDef
                    )
            );
        } catch (NoSuchMethodException e) {
            throw new Error("virtual aspect [" + aspectName + "] does not have expected method: " + e.toString());
        }
        definition.addAspect(aspectDef);
    }

    /**
     * Parses the aspectElement parameters.
     *
     * @param aspectElement the aspect element
     * @param aspectDef     the aspect def
     */
    private static void parseParameterElements(final Element aspectElement,
                                               final AspectDefinition aspectDef) {
        for (Iterator it2 = aspectElement.elementIterator(); it2.hasNext();) {
            Element parameterElement = (Element) it2.next();
            if (parameterElement.getName().trim().equals("param")) {
                aspectDef.addParameter(
                        parameterElement.attributeValue("name"),
                        parameterElement.attributeValue("value")
                );
            }
        }
    }

    /**
     * Parses the mixinElement parameters.
     *
     * @param mixinElement the mixin element
     * @param mixinDef     the mixin def
     */
    private static void parseParameterElements(final Element mixinElement,
                                               final MixinDefinition mixinDef) {
        for (Iterator it2 = mixinElement.elementIterator(); it2.hasNext();) {
            Element parameterElement = (Element) it2.next();
            if (parameterElement.getName().trim().equals("param")) {
                mixinDef.addParameter(
                        parameterElement.attributeValue("name"),
                        parameterElement.attributeValue("value")
                );
            }
        }
    }

    /**
     * Parses the pointcuts.
     *
     * @param aspectElement the aspect element
     * @param aspectDef     the system definition
     */
    private static void parsePointcutElements(final Element aspectElement, final AspectDefinition aspectDef) {
        for (Iterator it2 = aspectElement.elementIterator(); it2.hasNext();) {
            Element pointcutElement = (Element) it2.next();
            if (pointcutElement.getName().trim().equals("pointcut")) {
                String name = pointcutElement.attributeValue("name");
                String expression = pointcutElement.attributeValue("expression");
                // pointcut CDATA is expression unless already specified as an attribute
                if (expression == null) {
                    expression = pointcutElement.getTextTrim();
                }
                DefinitionParserHelper.createAndAddPointcutDefToAspectDef(name, expression, aspectDef);
            } else if (pointcutElement.getName().trim().equals("deployment-scope")) {
                String name = pointcutElement.attributeValue("name");
                String expression = pointcutElement.attributeValue("expression");
                // pointcut CDATA is expression unless already specified as an attribute
                if (expression == null) {
                    expression = pointcutElement.getTextTrim();
                }
                DefinitionParserHelper.createAndAddDeploymentScopeDef(
                        name, expression, aspectDef.getSystemDefinition()
                );
            } else if (pointcutElement.getName().trim().equals("advisable")) {
                String expression = pointcutElement.attributeValue("expression");
                String pointcutTypes = pointcutElement.attributeValue("pointcut-type");
                if (expression == null) {
                    expression = pointcutElement.getTextTrim();
                }
                handleAdvisableDefinition(aspectDef.getSystemDefinition(), expression, pointcutTypes);
            }
        }
    }

    /**
     * Parses the advices.
     *
     * @param aspectElement   the aspect element
     * @param aspectDef       the system definition
     * @param aspectClassInfo the aspect class
     */
    private static void parseAdviceElements(final Element aspectElement,
                                            final AspectDefinition aspectDef,
                                            final ClassInfo aspectClassInfo) {
        List methodList = ClassInfoHelper.createMethodList(aspectClassInfo);
        for (Iterator it2 = aspectElement.elementIterator(); it2.hasNext();) {
            Element adviceElement = (Element) it2.next();
            if (adviceElement.getName().trim().equals("advice")) {
                String name = adviceElement.attributeValue("name");
                String type = adviceElement.attributeValue("type");
                String bindTo = adviceElement.attributeValue("bind-to");

                String adviceName = name;
                MethodInfo method = null;
                for (Iterator it3 = methodList.iterator(); it3.hasNext();) {
                    MethodInfo methodCurrent = (MethodInfo) it3.next();
                    if (aspectDef.isAspectWerkzAspect()) {
                        if (matchMethodAsAdvice(methodCurrent, name)) {
                            method = methodCurrent;
                            break;
                        }
                    } else {
                        // TODO support matchMethodAsAdvice(..) for all aspect models? if so use stuff below
//                        AspectModel aspectModel = AspectModelManager.getModelFor(aspectDef.getAspectModel());
//                        if (aspectModel.matchMethodAsAdvice(methodCurrent, name)) {
//                            method = methodCurrent;
//                            break;
//                        }
                        if (methodCurrent.getName().equals(name)) {
                            method = methodCurrent;
                            break;
                        }
                    }
                }
                if (method == null) {
                    throw new DefinitionException(
                            "could not find advice method [" + name + "] in [" + aspectClassInfo.getName() +
                            "] (are you using a compiler extension that you have not registered?)"+
                            " (are you using XML defined advice, with StaticJoinPoint bindings without specifying the full" +
                            "source like signature?)"
                    );
                }
                createAndAddAdviceDefsToAspectDef(type, bindTo, adviceName, method, aspectDef);
                for (Iterator it1 = adviceElement.elementIterator("bind-to"); it1.hasNext();) {
                    Element bindToElement = (Element) it1.next();
                    String pointcut = bindToElement.attributeValue("pointcut");
                    createAndAddAdviceDefsToAspectDef(type, pointcut, adviceName, method, aspectDef);
                }
            }
        }
    }

    /**
     * Parses the interface introductions.
     *
     * @param aspectElement the aspect element
     * @param aspectDef     the system definition
     * @param packageName
     * @param loader
     */
    private static void parseIntroduceElements(final Element aspectElement,
                                               final AspectDefinition aspectDef,
                                               final String packageName,
                                               final ClassLoader loader) {
        for (Iterator it2 = aspectElement.elementIterator(); it2.hasNext();) {
            Element introduceElement = (Element) it2.next();
            if (introduceElement.getName().trim().equals("introduce")) {
                String klass = introduceElement.attributeValue("class");
                String name = introduceElement.attributeValue("name");
                String bindTo = introduceElement.attributeValue("bind-to");

                // default name = FQN
                final String fullClassName = packageName + klass;
                if ((name == null) || (name.length() <= 0)) {
                    name = fullClassName;
                }

                // load the class info to determine if it is a pure interface introduction
                ClassInfo introductionClassInfo;
                try {
                    introductionClassInfo = AsmClassInfo.getClassInfo(fullClassName, loader);
                } catch (Exception e) {
                    throw new DefinitionException(
                            "could not find interface introduction: "
                            + packageName
                            + klass
                            + " "
                            + e.getMessage()
                    );
                }

                // pure interface introduction
                if (introductionClassInfo.isInterface()) {
                    DefinitionParserHelper.createAndAddInterfaceIntroductionDefToAspectDef(
                            bindTo,
                            name,
                            fullClassName,
                            aspectDef
                    );

                    // handles nested "bind-to" elements
                    for (Iterator it1 = introduceElement.elementIterator("bind-to"); it1.hasNext();) {
                        Element bindToElement = (Element) it1.next();
                        String pointcut = bindToElement.attributeValue("pointcut");
                        DefinitionParserHelper.createAndAddInterfaceIntroductionDefToAspectDef(
                                pointcut,
                                name,
                                fullClassName,
                                aspectDef
                        );
                    }
                }
            }
        }
    }

    /**
     * Creates the advice definitions and adds them to the aspect definition.
     *
     * @param type      the type of advice
     * @param bindTo    the pointcut expresion
     * @param name      the name of the advice
     * @param method    the method implementing the advice
     * @param aspectDef the aspect definition
     */
    private static void createAndAddAdviceDefsToAspectDef(final String type,
                                                          final String bindTo,
                                                          final String name,
                                                          final MethodInfo method,
                                                          final AspectDefinition aspectDef) {
        try {
            if (type.equalsIgnoreCase("around")) {
                final String aspectName = aspectDef.getName();
                AdviceDefinition adviceDef = DefinitionParserHelper.createAdviceDefinition(
                        name,
                        AdviceType.AROUND,
                        bindTo,
                        null,
                        aspectName,
                        aspectDef.getClassName(),
                        method,
                        aspectDef
                );
                aspectDef.addAroundAdviceDefinition(adviceDef);

            } else if (type.equalsIgnoreCase("before")) {
                final String aspectName = aspectDef.getName();
                AdviceDefinition adviceDef = DefinitionParserHelper.createAdviceDefinition(
                        name,
                        AdviceType.BEFORE,
                        bindTo,
                        null,
                        aspectName,
                        aspectDef.getClassName(),
                        method,
                        aspectDef
                );
                aspectDef.addBeforeAdviceDefinition(adviceDef);

            } else if (type.startsWith("after")) {
                String specialArgumentType = null;
                AdviceType adviceType = AdviceType.AFTER;
                if (type.startsWith("after returning(")) {
                    adviceType = AdviceType.AFTER_RETURNING;
                    int start = type.indexOf('(');
                    int end = type.indexOf(')');
                    specialArgumentType = type.substring(start + 1, end).trim();
                } else if (type.startsWith("after throwing(")) {
                    adviceType = AdviceType.AFTER_THROWING;
                    int start = type.indexOf('(');
                    int end = type.indexOf(')');
                    specialArgumentType = type.substring(start + 1, end).trim();
                } else if (type.startsWith("after returning")) {
                    adviceType = AdviceType.AFTER_RETURNING;
                } else if (type.startsWith("after throwing")) {
                    adviceType = AdviceType.AFTER_THROWING;
                } else if (type.startsWith("after")) {
                    adviceType = AdviceType.AFTER_FINALLY;
                } else if (type.startsWith("after finally")) {
                    adviceType = AdviceType.AFTER_FINALLY;
                }
                if (specialArgumentType != null && specialArgumentType.indexOf(' ') > 0) {
                    throw new DefinitionException(
                            "argument to after (returning/throwing) can only be a type (parameter name binding should be done using args(..))"
                    );
                }
                final String aspectName = aspectDef.getName();
                AdviceDefinition adviceDef = DefinitionParserHelper.createAdviceDefinition(
                        name,
                        adviceType,
                        bindTo,
                        specialArgumentType,
                        aspectName,
                        aspectDef.getClassName(),
                        method,
                        aspectDef
                );

                aspectDef.addAfterAdviceDefinition(adviceDef);
            } else {
                throw new DefinitionException("Unkonw type for advice : " + type);
            }
        } catch (DefinitionException e) {
            System.err.println(
                    "WARNING: unable to register advice " + aspectDef.getName() + "." + name +
                    " at pointcut [" + bindTo + "] due to: " + e.getMessage()
            );
            // TODO ALEX - better handling of reg issue (f.e. skip the whole aspect, in DocumentParser, based on DefinitionE
        }
    }

    /**
     * Retrieves and returns the package.
     *
     * @param packageElement the package element
     * @return the package as a string ending with DOT, or empty string
     */
    private static String getPackage(final Element packageElement) {
        String packageName = "";
        for (Iterator it2 = packageElement.attributeIterator(); it2.hasNext();) {
            Attribute attribute = (Attribute) it2.next();
            if (attribute.getName().trim().equalsIgnoreCase("name")) {
                packageName = attribute.getValue().trim();
                if (packageName.endsWith(".*")) {
                    packageName = packageName.substring(0, packageName.length() - 1);
                } else if (packageName.endsWith(".")) {
                    ; // skip
                } else {
                    packageName += ".";
                }
                break;
            } else {
                continue;
            }
        }
        return packageName;
    }

    /**
     * Parses the <tt>include</tt> elements.
     *
     * @param root        the root element
     * @param definition  the definition object
     * @param packageName the package name
     */
    private static void parseIncludePackageElements(final Element root,
                                                    final SystemDefinition definition,
                                                    final String packageName) {
        for (Iterator it1 = root.elementIterator("include"); it1.hasNext();) {
            String includePackage = "";
            Element includeElement = (Element) it1.next();
            for (Iterator it2 = includeElement.attributeIterator(); it2.hasNext();) {
                Attribute attribute = (Attribute) it2.next();
                if (attribute.getName().trim().equalsIgnoreCase("package")) {
                    // handle base package
                    if (packageName.endsWith(".*")) {
                        includePackage = packageName.substring(0, packageName.length() - 2);
                    } else if (packageName.endsWith(".")) {
                        includePackage = packageName.substring(0, packageName.length() - 1);
                    }

                    // handle exclude package
                    includePackage = packageName + attribute.getValue().trim();
                    if (includePackage.endsWith(".*")) {
                        includePackage = includePackage.substring(0, includePackage.length() - 2);
                    } else if (includePackage.endsWith(".")) {
                        includePackage = includePackage.substring(0, includePackage.length() - 1);
                    }
                    break;
                } else {
                    continue;
                }
            }
            if (includePackage.length() != 0) {
                definition.addIncludePackage(includePackage);
            }
        }
    }

    /**
     * Parses the <tt>exclude</tt> elements.
     *
     * @param root        the root element
     * @param definition  the definition object
     * @param packageName the package name
     */
    private static void parseExcludePackageElements(final Element root,
                                                    final SystemDefinition definition,
                                                    final String packageName) {
        for (Iterator it1 = root.elementIterator("exclude"); it1.hasNext();) {
            String excludePackage = "";
            Element excludeElement = (Element) it1.next();
            for (Iterator it2 = excludeElement.attributeIterator(); it2.hasNext();) {
                Attribute attribute = (Attribute) it2.next();
                if (attribute.getName().trim().equalsIgnoreCase("package")) {
                    // handle base package
                    if (packageName.endsWith(".*")) {
                        excludePackage = packageName.substring(0, packageName.length() - 2);
                    } else if (packageName.endsWith(".")) {
                        excludePackage = packageName.substring(0, packageName.length() - 1);
                    }

                    // handle exclude package
                    excludePackage = packageName + attribute.getValue().trim();
                    if (excludePackage.endsWith(".*")) {
                        excludePackage = excludePackage.substring(0, excludePackage.length() - 2);
                    } else if (excludePackage.endsWith(".")) {
                        excludePackage = excludePackage.substring(0, excludePackage.length() - 1);
                    }
                    break;
                } else {
                    continue;
                }
            }
            if (excludePackage.length() != 0) {
                definition.addExcludePackage(excludePackage);
            }
        }
    }

    /**
     * Parses the <tt>prepare</tt> elements.
     *
     * @param root        the root element
     * @param definition  the definition object
     * @param packageName the base package name
     */
    public static void parsePrepareElements(final Element root,
                                            final SystemDefinition definition,
                                            final String packageName) {
        for (Iterator it1 = root.elementIterator("prepare"); it1.hasNext();) {
            String preparePackage = "";
            Element prepareElement = (Element) it1.next();
            for (Iterator it2 = prepareElement.attributeIterator(); it2.hasNext();) {
                Attribute attribute = (Attribute) it2.next();
                if (attribute.getName().trim().equals("package")) {
                    // handle base package
                    if (packageName.endsWith(".*")) {
                        preparePackage = packageName.substring(0, packageName.length() - 2);
                    } else if (packageName.endsWith(".")) {
                        preparePackage = packageName.substring(0, packageName.length() - 1);
                    }

                    // handle prepare package
                    preparePackage = packageName + attribute.getValue().trim();
                    if (preparePackage.endsWith(".*")) {
                        preparePackage = preparePackage.substring(0, preparePackage.length() - 2);
                    } else if (preparePackage.endsWith(".")) {
                        preparePackage = preparePackage.substring(0, preparePackage.length() - 1);
                    }
                    break;
                } else {
                    continue;
                }
            }
            if (preparePackage.length() != 0) {
                definition.addPreparePackage(preparePackage);
            }
        }
    }

    /**
     * Retrieves and returns the base package for a system element
     *
     * @param system a system element
     * @return the base package
     */
    private static String getBasePackage(final Element system) {
        String basePackage = "";
        for (Iterator it2 = system.attributeIterator(); it2.hasNext();) {
            Attribute attribute = (Attribute) it2.next();
            if (attribute.getName().trim().equalsIgnoreCase("base-package")) {
                basePackage = attribute.getValue().trim();
                if (basePackage.endsWith(".*")) {
                    basePackage = basePackage.substring(0, basePackage.length() - 1);
                } else if (basePackage.endsWith(".")) {
                    ; // skip
                } else {
                    basePackage += ".";
                }
                break;
            } else {
                continue;
            }
        }
        return basePackage;
    }

    /**
     * Struct with pointcut info.
     */
    private static class PointcutInfo {
        public String name;
        public String expression;
    }

    /**
     * Check if a method from an aspect class match a given advice signature.
     * <br/>
     * If the signature is just a method name, then we have a match even if JoinPoint is sole method parameter.
     * Else we match both method name and parameters type, with abbreviation support (java.lang.* and JoinPoint)
     *
     * @param method
     * @param adviceSignature
     * @return
     */
    private static boolean matchMethodAsAdvice(MethodInfo method, String adviceSignature) {
        // grab components from adviceSignature
        //TODO catch AOOBE for better syntax error reporting
        String[] signatureElements = Strings.extractMethodSignature(adviceSignature);

        // check method name
        if (!method.getName().equals(signatureElements[0])) {
            return false;
        }
        // check number of args
        if (method.getParameterTypes().length * 2 != signatureElements.length - 1) {
            // we still match if method has "JoinPoint" has sole parameter
            // and adviceSignature has none
            if (signatureElements.length == 1 &&
                method.getParameterTypes().length == 1 &&
                (method.getParameterTypes()[0].getName().equals(TransformationConstants.JOIN_POINT_JAVA_CLASS_NAME)
                 || method.getParameterTypes()[0].getName().equals(TransformationConstants.STATIC_JOIN_POINT_JAVA_CLASS_NAME))) {
                return true;
            } else {
                return false;
            }
        }
        int argIndex = 0;
        for (int i = 1; i < signatureElements.length; i++) {
            String paramType = signatureElements[i++];
            String methodParamType = method.getParameterTypes()[argIndex++].getName();
            // handle shortcuts for java.lang.* and JoinPoint, StaticJoinPoint and Rtti
            String paramTypeResolved = (String) Pattern.ABBREVIATIONS.get(paramType);
            if (methodParamType.equals(paramType) || methodParamType.equals(paramTypeResolved)) {
                continue;
            } else {
                return false;
            }
        }
        return true;
    }

    /**
     * Handles the advisable definition.
     *
     * @param definition
     * @param withinPointcut
     * @param pointcutTypes
     */
    private static void handleAdvisableDefinition(final SystemDefinition definition,
                                                  final String withinPointcut,
                                                  final String pointcutTypes) {
        // add the Advisable Mixin with the expression defined to the system definitions
        definition.addMixinDefinition(
                DefinitionParserHelper.createAndAddMixinDefToSystemDef(
                        AdvisableImpl.CLASS_INFO,
                        withinPointcut,
                        DeploymentModel.PER_INSTANCE,
                        false, // advisble mixin is NOT transient
                        definition
                )
        );

        boolean hasAllPointcuts = false;
        boolean hasExecutionPointcut = false;
        boolean hasCallPointcut = false;
        boolean hasSetPointcut = false;
        boolean hasGetPointcut = false;
        boolean hasHandlerPointcut = false;
        if (pointcutTypes == null ||
            pointcutTypes.equals("") ||
            pointcutTypes.equalsIgnoreCase("all")) {
            hasAllPointcuts = true;
        } else {
            StringTokenizer tokenizer = new StringTokenizer(pointcutTypes, "|");
            while (tokenizer.hasMoreTokens()) {
                String token = tokenizer.nextToken();
                if (token.trim().equalsIgnoreCase("all")) {
                    hasAllPointcuts = true;
                    break;
                } else if (token.trim().equalsIgnoreCase("execution")) {
                    hasExecutionPointcut = true;
                } else if (token.trim().equalsIgnoreCase("call")) {
                    hasCallPointcut = true;
                } else if (token.trim().equalsIgnoreCase("set")) {
                    hasSetPointcut = true;
                } else if (token.trim().equalsIgnoreCase("get")) {
                    hasGetPointcut = true;
                } else if (token.trim().equalsIgnoreCase("handler")) {
                    hasHandlerPointcut = true;
                }
            }
        }
        if (hasAllPointcuts || hasExecutionPointcut) {
            DefinitionParserHelper.createAndAddAdvisableDef(
                    // TODO add ctor to expression - BUT: problem with mixin and ctor, ordering issue, Jp.invoke() calls field instance that has not been init yet in ctor (since body not invoked)
                    //"(( execution(!static * *.*(..)) || execution(*.new(..)) ) && " + withinPointcut + ')',
                    // we exclude static method execution since we need the advisable instance
                    "(execution(!static * *.*(..)) && " + withinPointcut + ')',
                    definition
            );
        }
        if (hasAllPointcuts || hasCallPointcut) {
            DefinitionParserHelper.createAndAddAdvisableDef(
                    // TODO add ctor to expression - BUT: problem with mixin and ctor, ordering issue, Jp.invoke() calls field instance that has not been init yet in ctor (since body not invoked)                    //"(call(!static * " + typePattern + ".*(..)) || call(" + typePattern + ".new(..)))",
                    // we exclude static method withincode since we need the advisable instance
                    // as a consequence, withincode(staticinitialization(..)) is also excluded
                    "(call(* *.*(..)) && withincode(!static * *.*(..)) && " + withinPointcut + ')',
                    definition
            );
        }
        if (hasAllPointcuts || hasSetPointcut) {
            DefinitionParserHelper.createAndAddAdvisableDef(
                    // we exclude static method withincode since we need the advisable instance
                    // as a consequence, withincode(staticinitialization(..)) is also excluded
                    "(set(* *.*) && withincode(!static * *.*(..)) && " + withinPointcut + ')',
                    definition
            );
        }
        if (hasAllPointcuts || hasGetPointcut) {
            DefinitionParserHelper.createAndAddAdvisableDef(
                    // we exclude static method withincode since we need the advisable instance
                    // as a consequence, withincode(staticinitialization(..)) is also excluded
                    "(get(* *.*) && withincode(!static * *.*(..))  && " + withinPointcut + ')',
                    definition
            );
        }
        if (hasAllPointcuts || hasHandlerPointcut) {
            DefinitionParserHelper.createAndAddAdvisableDef(
                    // we exclude static method withincode since we need the advisable instance
                    // as a consequence, withincode(staticinitialization(..)) is also excluded
                    "(handler(*..*) && withincode(!static * *.*(..))  && " + withinPointcut + ')',
                    definition
            );
        }
    }
}