/**************************************************************************************
 * 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.annotation;


import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaParameter;

import org.apache.tools.ant.BuildException;
import org.codehaus.aspectwerkz.annotation.instrumentation.AttributeEnhancer;
import org.codehaus.aspectwerkz.annotation.instrumentation.asm.AsmAttributeEnhancer;
import org.codehaus.aspectwerkz.exception.DefinitionException;
import org.codehaus.aspectwerkz.joinpoint.JoinPoint;
import org.codehaus.aspectwerkz.joinpoint.StaticJoinPoint;
import org.codehaus.aspectwerkz.DeploymentModel;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;

/**
 * <p/>Annotation compiler. <p/>Extracts the annotations from JavaDoc tags and inserts them into the bytecode of the
 * class.
 *
 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
 * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
 */
public class AnnotationC {
    private static final String COMMAND_LINE_OPTION_DASH = "-";
    private static final String COMMAND_LINE_OPTION_VERBOSE = "-verbose";
    private static final String COMMAND_LINE_OPTION_CUSTOM = "-custom";
    private static final String COMMAND_LINE_OPTION_SRC = "-src";
    private static final String COMMAND_LINE_OPTION_SRCFILES = "-srcfiles";
    private static final String COMMAND_LINE_OPTION_SRCINCLUDES = "-srcincludes";
    private static final String COMMAND_LINE_OPTION_CLASSES = "-classes";
    private static final String COMMAND_LINE_OPTION_DEST = "-dest";

    static final String[] SYSTEM_ANNOTATIONS = new String[]{
        AnnotationConstants.ASPECT, AnnotationConstants.AROUND, AnnotationConstants.BEFORE,
        AnnotationConstants.AFTER, AnnotationConstants.AFTER_FINALLY,
        AnnotationConstants.AFTER_RETURNING, AnnotationConstants.AFTER_THROWING,
        AnnotationConstants.EXPRESSION, AnnotationConstants.INTRODUCE, AnnotationConstants.MIXIN
    };

    /**
     * The annotations properties file define by the user.
     */
    public static final Properties ANNOTATION_DEFINITION = new Properties();

    /**
     * Verbose logging.
     */
    private static boolean s_verbose = false;

    /**
     * The class loader.
     */
    private static URLClassLoader s_loader;

    /**
     * The custom annotations.
     */
    private static Map s_customAnnotations = new HashMap();
    private static final String FILE_SEPARATOR = File.separator;

    /**
     * Runs the compiler from the command line.
     *
     * @param args
     */
    public static void main(String[] args) {
        if (args.length < 4) {
            printUsage();
        }
        Map commandLineOptions = parseCommandLineOptions(args);

        String propertiesFilesPath = (String) commandLineOptions.get(COMMAND_LINE_OPTION_CUSTOM);
        List propertiesFilesList = new ArrayList();
        if (propertiesFilesPath != null) {
            StringTokenizer st = new StringTokenizer(propertiesFilesPath, File.pathSeparator);
            while (st.hasMoreTokens()) {
                propertiesFilesList.add(st.nextToken());
            }
        }
        String[] propertiesFiles = (String[]) propertiesFilesList.toArray(new String[0]);

        compile(
                (String) commandLineOptions.get(COMMAND_LINE_OPTION_SRC),
                (String) commandLineOptions.get(COMMAND_LINE_OPTION_SRCFILES),
                (String) commandLineOptions.get(COMMAND_LINE_OPTION_SRCINCLUDES),
                (String) commandLineOptions.get(COMMAND_LINE_OPTION_CLASSES),
                (String) commandLineOptions.get(COMMAND_LINE_OPTION_DEST),
                propertiesFiles
        );
    }

    /**
     * Compiles the annotations.
     *
     * @param srcDirList
     * @param srcFileList
     * @param classPath
     * @param destDir
     * @param annotationPropetiesFiles
     */
    private static void compile(final String srcDirList,
                                final String srcFileList,
                                final String srcFileIncludes,
                                final String classPath,
                                String destDir,
                                final String[] annotationPropetiesFiles) {
        if (srcDirList == null && srcFileList == null && srcFileIncludes == null) {
            throw new IllegalArgumentException("one of src or srcfiles or srcincludes must be not null");
        }
        if ((srcDirList != null && srcFileList != null) ||
            (srcDirList != null && srcFileIncludes != null) ||
            (srcFileList != null && srcFileIncludes != null)) { // FIXME: refactor
            throw new IllegalArgumentException("maximum one of src, srcfiles or srcincludes must be not null");
        }
        if (classPath == null) {
            throw new IllegalArgumentException("class path can not be null");
        }
        if (destDir == null) {
            destDir = classPath;
        }

        String[] srcDirs = new String[0];
        String[] srcFiles = new String[0];
        if (srcDirList != null) {
            srcDirs = split(srcDirList, File.pathSeparator);
        } else if (srcFileList != null) {
            srcFiles = split(srcFileList, FILE_SEPARATOR);
        } else {
            srcFiles = loadSourceList(srcFileIncludes);
        }

        compile(s_verbose, srcDirs, srcFiles, split(classPath, File.pathSeparator), destDir, annotationPropetiesFiles);
    }

    /**
     * Compiles the annotations.
     *
     * @param verbose
     * @param srcDirs
     * @param srcFiles
     * @param classpath
     * @param destDir
     * @param annotationPropertiesFiles
     */
    public static void compile(final boolean verbose,
                               final String[] srcDirs,
                               final String[] srcFiles,
                               final String[] classpath,
                               final String destDir,
                               final String[] annotationPropertiesFiles) {

        s_verbose = verbose;
        URL[] classPath = new URL[classpath.length];
        try {
            for (int i = 0; i < classpath.length; i++) {
                classPath[i] = new File(classpath[i]).toURL();
            }
            s_loader = new URLClassLoader(classPath, AnnotationC.class.getClassLoader());
        } catch (MalformedURLException e) {
            String message = "URL [" + classPath + "] is not valid: " + e.toString();
            logError(message);
            throw new DefinitionException(message, e);
        }

        String destDirToUse = destDir;
        if (destDir == null) {
            if (classpath.length != 1) {
                throw new DefinitionException("destDir must be specified since classpath is composite");
            }
            destDirToUse = classpath[0];
        }

        final AnnotationManager manager = new AnnotationManager(s_loader);

        logInfo("parsing source dirs:");
        for (int i = 0; i < srcDirs.length; i++) {
            logInfo("    " + srcDirs[i]);
        }
        manager.addSourceTrees(srcDirs);

        for (int i = 0; i < srcFiles.length; i++) {
            logInfo("    " + srcFiles[i]);
            manager.addSource(srcFiles[i]);
        }

        doCompile(annotationPropertiesFiles, classPath, manager, destDirToUse);
    }

    /**
     * Compiles the annotations.
     *
     * @param annotationPropetiesFiles
     * @param classPath
     * @param manager
     * @param destDir
     */
    private static void doCompile(final String[] annotationPropetiesFiles,
                                  final URL[] classPath,
                                  final AnnotationManager manager,
                                  final String destDir) {

        logInfo("compiling annotations...");
        logInfo("note: if no output is seen, then nothing is compiled");

        // register annotations
        registerSystemAnnotations(manager);
        registerUserDefinedAnnotations(manager, annotationPropetiesFiles);

        // get all the classes
        JavaClass[] classes = manager.getAllClasses();
        for (int i = 0; i < classes.length; i++) {
            JavaClass clazz = classes[i];
            logInfo("class [" + clazz.getFullyQualifiedName() + ']');
            try {
                AttributeEnhancer enhancer = new AsmAttributeEnhancer();
                if (enhancer.initialize(clazz.getFullyQualifiedName(), classPath)) {
                    handleClassAnnotations(manager, enhancer, clazz);
                    handleInnerClassAnnotations(manager, enhancer, clazz);
                    JavaMethod[] methods = clazz.getMethods();
                    for (int j = 0; j < methods.length; j++) {
                        JavaMethod method = methods[j];
                        if (method.isConstructor()) {
                            handleConstructorAnnotations(manager, enhancer, method);
                        } else {
                            handleMethodAnnotations(manager, enhancer, method);
                        }
                    }
                    JavaField[] fields = clazz.getFields();
                    for (int j = 0; j < fields.length; j++) {
                        handleFieldAnnotations(manager, enhancer, fields[j]);
                    }

                    // write enhanced class to disk
                    enhancer.write(destDir);
                }
            } catch (Throwable e) {
                e.printStackTrace();
                logWarning(
                        "could not compile annotations for class ["
                        + clazz.getFullyQualifiedName() + "] due to: " + e.toString()
                );
            }
        }
        logInfo("compiled classes written to " + destDir);
        logInfo("compilation successful");
    }

    /**
     * Handles the class annotations.
     *
     * @param manager
     * @param enhancer
     * @param clazz
     */
    private static void handleClassAnnotations(final AnnotationManager manager,
                                               final AttributeEnhancer enhancer,
                                               final JavaClass clazz) {
        parseCustomClassAnnotations(clazz, manager, enhancer);
        parseAspectAnnotations(clazz, manager, enhancer);
        parseMixinAnnotations(clazz, manager, enhancer);
    }

    /**
     * Parses the custom annotations.
     *
     * @param clazz
     * @param manager
     * @param enhancer
     */
    private static void parseCustomClassAnnotations(final JavaClass clazz,
                                                    final AnnotationManager manager,
                                                    final AttributeEnhancer enhancer) {
        for (Iterator it = s_customAnnotations.keySet().iterator(); it.hasNext();) {
            String annotationName = (String) it.next();
            Annotation[] customAnnotations = manager.getAnnotations(annotationName, clazz);
            for (int i = 0; i < customAnnotations.length; i++) {
                Annotation customAnnotation = customAnnotations[i];
                if (customAnnotation != null) {
                    enhancer.insertClassAttribute(
                            new AnnotationInfo(
                                    annotationName,
                                    customAnnotation
                            )
                    );
                    logInfo(
                            "    custom class annotation [" + annotationName + " @ "
                            + clazz.getFullyQualifiedName() + ']'
                    );
                }
            }
        }
    }

    /**
     * Parses the aspect annotations.
     *
     * @param clazz
     * @param manager
     * @param enhancer
     */
    private static void parseAspectAnnotations(final JavaClass clazz,
                                               final AnnotationManager manager,
                                               final AttributeEnhancer enhancer) {
        Annotation[] annotations = manager.getAnnotations(AnnotationConstants.ASPECT, clazz);
        for (int i = 0; i < annotations.length; i++) {
            Annotation annotation = annotations[i];
            if (annotation != null) {
                enhancer.insertClassAttribute(
                        new AnnotationInfo(AnnotationConstants.ASPECT, annotation)
                );
                String name = ((Aspect) annotation).name();
                if (name == null) {
                    name = clazz.getFullyQualifiedName();
                }
                String deploymentModel = ((Aspect) annotation).deploymentModel();
                if (((Aspect) annotation).value() != null) {
                    deploymentModel = ((Aspect) annotation).value();
                }
                logInfo("aspect [" + name + ']');
                logInfo("    deployment model [" + deploymentModel + ']');
            }
        }
    }

    /**
     * Parses mixin annotations.
     *
     * @param clazz
     * @param manager
     * @param enhancer
     */
    private static void parseMixinAnnotations(final JavaClass clazz,
                                              final AnnotationManager manager,
                                              final AttributeEnhancer enhancer) {
        final Annotation[] introduceAnnotations = manager.getAnnotations(
                AnnotationConstants.MIXIN_DOCLET, clazz
        );
        final String className = clazz.getFullyQualifiedName();
        for (int k = 0; k < introduceAnnotations.length; k++) {
            Annotation introduceAnnotation = introduceAnnotations[k];
            if (introduceAnnotation != null && introduceAnnotation instanceof Mixin) {
                //directly implemented interfaces
                JavaClass[] introducedInterfaceClasses = clazz.getImplementedInterfaces();
                String[] introducedInterfaceNames = new String[introducedInterfaceClasses.length];
                for (int j = 0; j < introducedInterfaceClasses.length; j++) {
                    introducedInterfaceNames[j] = introducedInterfaceClasses[j].getFullyQualifiedName();
                    logInfo("    interface introduction [" + introducedInterfaceNames[j] + ']');
                }
                if (introducedInterfaceNames.length == 0) {
                    introducedInterfaceNames = enhancer.getNearestInterfacesInHierarchy(className);
                    if (introducedInterfaceNames.length == 0) {
                        throw new RuntimeException("no implicit interfaces found for " + className);
                    }
                    for (int j = 0; j < introducedInterfaceNames.length; j++) {
                        logInfo("    interface introduction [" + introducedInterfaceNames[j] + ']');
                    }
                }
                String deploymentModel = DeploymentModel.PER_JVM.toString();
                if (((Mixin) introduceAnnotation).deploymentModel() != null) {
                    deploymentModel = ((Mixin) introduceAnnotation).deploymentModel();
                }
                String expression = ((Mixin) introduceAnnotation).pointcut();
                if (expression == null) {
                    expression = ((Mixin) introduceAnnotation).value();
                }
                logInfo(
                        "    mixin introduction [" + clazz.getFullyQualifiedName()
                        + " :: " + expression +
                        "] deployment model ["
                        + deploymentModel + ']'
                );
                enhancer.insertClassAttribute(
                        new AnnotationInfo(AnnotationConstants.MIXIN, introduceAnnotation)
                );
            }
        }
    }

    /**
     * Handles the method annotations.
     *
     * @param manager
     * @param enhancer
     * @param method
     */
    private static void handleMethodAnnotations(final AnnotationManager manager,
                                                final AttributeEnhancer enhancer,
                                                final JavaMethod method) {
        // Pointcut with signature
        Annotation[] expressionAnnotations = manager.getAnnotations(
                AnnotationConstants.EXPRESSION_DOCLET, method
        );
        for (int i = 0; i < expressionAnnotations.length; i++) {
            Annotation expressionAnnotation = expressionAnnotations[i];
            if (expressionAnnotation != null && expressionAnnotation instanceof Expression) {
                enhancer.insertMethodAttribute(
                        method, new AnnotationInfo(
                                AnnotationConstants.EXPRESSION,
                                expressionAnnotation
                        )
                );
                logInfo(
                        "    pointcut [" + AnnotationC.getShortCallSignature(method) + " :: "
                        + ((Expression) expressionAnnotation).value() + ']'
                );
            }
        }
        Annotation[] aroundAnnotations = manager.getAnnotations(AnnotationConstants.AROUND_DOCLET, method);
        for (int i = 0; i < aroundAnnotations.length; i++) {
            Annotation aroundAnnotation = aroundAnnotations[i];
            if (aroundAnnotation != null && aroundAnnotation instanceof Around) {
                enhancer.insertMethodAttribute(
                        method, new AnnotationInfo(
                                AnnotationConstants.AROUND,
                                aroundAnnotation
                        )
                );
                logInfo(
                        "    around advice [" + AnnotationC.getShortCallSignature(method) + " :: "
                        + ((Around) aroundAnnotation).value() + ']'
                );
            }
        }
        Annotation[] beforeAnnotations = manager.getAnnotations(AnnotationConstants.BEFORE_DOCLET, method);
        for (int i = 0; i < beforeAnnotations.length; i++) {
            Annotation beforeAnnotation = beforeAnnotations[i];
            if (beforeAnnotation != null && beforeAnnotation instanceof Before) {
                enhancer.insertMethodAttribute(
                        method, new AnnotationInfo(
                                AnnotationConstants.BEFORE,
                                beforeAnnotation
                        )
                );
                logInfo(
                        "    before [" + AnnotationC.getShortCallSignature(method) + " :: "
                        + ((Before) beforeAnnotation).value() + ']'
                );
            }
        }
        Annotation[] afterAnnotations = manager.getAnnotations(AnnotationConstants.AFTER_DOCLET, method);
        for (int i = 0; i < afterAnnotations.length; i++) {
            Annotation afterAnnotation = afterAnnotations[i];
            if (afterAnnotation != null && afterAnnotation instanceof After) {
                enhancer.insertMethodAttribute(
                        method, new AnnotationInfo(AnnotationConstants.AFTER, afterAnnotation)
                );
                logInfo(
                        "    after advice [" + AnnotationC.getShortCallSignature(method) + " :: "
                        + ((After) afterAnnotation).value() + ']'
                );
            }
        }
        Annotation[] afterFinallyAnnotations = manager.getAnnotations(
                AnnotationConstants.AFTER_FINALLY_DOCLET, method
        );
        for (int i = 0; i < afterFinallyAnnotations.length; i++) {
            Annotation afterAnnotation = afterFinallyAnnotations[i];
            if (afterAnnotation != null && afterAnnotation instanceof AfterFinally) {
                enhancer.insertMethodAttribute(
                        method, new AnnotationInfo(AnnotationConstants.AFTER_FINALLY, afterAnnotation)
                );
                logInfo(
                        "    after finally advice [" + AnnotationC.getShortCallSignature(method) + " :: "
                        + ((AfterFinally) afterAnnotation).value() + ']'
                );
            }
        }
        Annotation[] afterReturningAnnotations = manager.getAnnotations(
                AnnotationConstants.AFTER_RETURNING_DOCLET, method
        );
        for (int i = 0; i < afterReturningAnnotations.length; i++) {
            Annotation afterAnnotation = afterReturningAnnotations[i];
            if (afterAnnotation != null && afterAnnotation instanceof AfterReturning) {
                enhancer.insertMethodAttribute(
                        method,
                        new AnnotationInfo(AnnotationConstants.AFTER_RETURNING, afterAnnotation)
                );
                logInfo(
                        "    after returning advice [" + AnnotationC.getShortCallSignature(method) + " :: "
                        +
                        AspectAnnotationParser.getExpressionElseValue(
                                ((AfterReturning) afterAnnotation).value(),
                                ((AfterReturning) afterAnnotation).pointcut()
                        )
                        + " :: " + ((AfterReturning) afterAnnotation).type() + ']'
                );
            }
        }
        Annotation[] afterThrowingAnnotations = manager.getAnnotations(
                AnnotationConstants.AFTER_THROWING_DOCLET, method
        );
        for (int i = 0; i < afterThrowingAnnotations.length; i++) {
            Annotation afterAnnotation = afterThrowingAnnotations[i];
            if (afterAnnotation != null && afterAnnotation instanceof AfterThrowing) {
                enhancer.insertMethodAttribute(
                        method,
                        new AnnotationInfo(AnnotationConstants.AFTER_THROWING, afterAnnotation)
                );
                logInfo(
                        "    after throwing advice [" + AnnotationC.getShortCallSignature(method) + " :: "
                        +
                        AspectAnnotationParser.getExpressionElseValue(
                                ((AfterThrowing) afterAnnotation).value(),
                                ((AfterThrowing) afterAnnotation).pointcut()
                        )
                        + " :: " + ((AfterThrowing) afterAnnotation).type() + ']'
                );
            }
        }
        for (Iterator it = s_customAnnotations.keySet().iterator(); it.hasNext();) {
            String annotationName = (String) it.next();
            Annotation[] customAnnotations = manager.getAnnotations(annotationName, method);
            for (int i = 0; i < customAnnotations.length; i++) {
                Annotation customAnnotation = customAnnotations[i];
                if (customAnnotation != null) {
                    enhancer.insertMethodAttribute(method, new AnnotationInfo(
                                annotationName,
                                customAnnotation)
                    );
                    logInfo(
                            "    custom method annotation [" + annotationName + " @ "
                            + method.getParentClass().getName() + '.' + method.getName() +
                            ']'
                    );
                }
            }
        }
    }

    /**
     * Handles the constructor annotations.
     *
     * @param manager
     * @param enhancer
     * @param constructor
     */
    private static void handleConstructorAnnotations(final AnnotationManager manager,
                                                     final AttributeEnhancer enhancer,
                                                     final JavaMethod constructor) {
        for (Iterator it = s_customAnnotations.keySet().iterator(); it.hasNext();) {
            String annotationName = (String) it.next();
            Annotation[] customAnnotations = manager.getAnnotations(annotationName, constructor);
            for (int i = 0; i < customAnnotations.length; i++) {
                Annotation customAnnotation = customAnnotations[i];
                if (customAnnotation != null) {
                    enhancer.insertConstructorAttribute(
                            constructor, new AnnotationInfo(annotationName,
                                                            customAnnotation)
                    );
                    logInfo(
                            "    custom constructor annotation [" + annotationName + " @ "
                            + constructor.getParentClass().getName() + '.' +
                            constructor.getName()
                            + ']'
                    );
                }
            }
        }
    }

    /**
     * Handles the field annotations.
     *
     * @param manager
     * @param enhancer
     * @param field
     */
    private static void handleFieldAnnotations(final AnnotationManager manager,
                                               final AttributeEnhancer enhancer,
                                               final JavaField field) {

        Annotation[] expressionAnnotations = manager.getAnnotations(
                AnnotationConstants.EXPRESSION_DOCLET, field
        );
        for (int i = 0; i < expressionAnnotations.length; i++) {
            Annotation expressionAnnotation = expressionAnnotations[i];
            if (expressionAnnotation != null && expressionAnnotation instanceof Expression) {
                enhancer.insertFieldAttribute(
                        field, new AnnotationInfo(
                                AnnotationConstants.EXPRESSION,
                                expressionAnnotation
                        )
                );
                logInfo(
                        "    pointcut [" + field.getName() + " :: " + ((Expression) expressionAnnotation).value()
                        + ']'
                );
            }
        }
        Annotation[] introduceAnnotations = manager.getAnnotations(
                AnnotationConstants.INTRODUCE_DOCLET, field
        );
        for (int i = 0; i < introduceAnnotations.length; i++) {
            Annotation introduceAnnotation = introduceAnnotations[i];
            if (introduceAnnotation != null && introduceAnnotation instanceof Introduce) {
                enhancer.insertFieldAttribute(
                        field, new AnnotationInfo(
                                AnnotationConstants.INTRODUCE,
                                introduceAnnotation
                        )
                );
                logInfo(
                        "    interface introduction [" + field.getName() + " :: "
                        + ((Introduce) introduceAnnotation).value() + ']'
                );
            }
        }
        for (Iterator it = s_customAnnotations.keySet().iterator(); it.hasNext();) {
            String annotationName = (String) it.next();
            Annotation[] customAnnotations = manager.getAnnotations(annotationName, field);
            for (int i = 0; i < customAnnotations.length; i++) {
                Annotation customAnnotation = customAnnotations[i];
                if (customAnnotation != null) {
                    enhancer.insertFieldAttribute(
                            field, new AnnotationInfo(
                                    annotationName,
                                    customAnnotation
                            )
                    );
                    logInfo(
                            "    custom field annotation [" + annotationName + " @ "
                            + field.getName() + ']'
                    );
                }
            }
        }
    }

    /**
     * Handles the inner class annotations.
     *
     * @param manager
     * @param enhancer
     * @param clazz
     */
    private static void handleInnerClassAnnotations(final AnnotationManager manager,
                                                    final AttributeEnhancer enhancer,
                                                    final JavaClass clazz) {
        JavaClass[] innerClasses = clazz.getInnerClasses();
        for (int i = 0; i < innerClasses.length; i++) {
            parseMixinAnnotations(innerClasses[i], manager, enhancer);
        }
    }

    /**
     * Registers the system annotations.
     *
     * @param manager the annotations manager
     */
    private static void registerSystemAnnotations(final AnnotationManager manager) {
        manager.registerAnnotationProxy(Aspect.class, AnnotationConstants.ASPECT_DOCLET);
        manager.registerAnnotationProxy(Around.class, AnnotationConstants.AROUND_DOCLET);
        manager.registerAnnotationProxy(Before.class, AnnotationConstants.BEFORE_DOCLET);
        manager.registerAnnotationProxy(After.class, AnnotationConstants.AFTER_DOCLET);
        manager.registerAnnotationProxy(AfterReturning.class, AnnotationConstants.AFTER_RETURNING_DOCLET);
        manager.registerAnnotationProxy(AfterThrowing.class, AnnotationConstants.AFTER_THROWING_DOCLET);
        manager.registerAnnotationProxy(AfterFinally.class, AnnotationConstants.AFTER_FINALLY_DOCLET);
        manager.registerAnnotationProxy(Expression.class, AnnotationConstants.EXPRESSION_DOCLET);
        manager.registerAnnotationProxy(Introduce.class, AnnotationConstants.INTRODUCE_DOCLET);
        manager.registerAnnotationProxy(Mixin.class, AnnotationConstants.MIXIN_DOCLET);
    }

    /**
     * Registers the user defined annotations.
     *
     * @param manager         the annotations manager
     * @param propertiesFiles
     */
    private static void registerUserDefinedAnnotations(final AnnotationManager manager,
                                                       final String[] propertiesFiles) {
        if (propertiesFiles == null) {
            return;
        }
        InputStream in = null;
        for (int i = 0; i < propertiesFiles.length; i++) {
            String propertiesFile = propertiesFiles[i];
            try {
                in = new FileInputStream(propertiesFile);
                ANNOTATION_DEFINITION.load(in);
            } catch (Exception e) {
                String message = "custom annotation properties " + propertiesFile + " can not be loaded: " +
                                 e.toString();
                logWarning(message);
                throw new DefinitionException(message);
            } finally {
                try {
                    in.close();
                } catch (Exception e) {
                    ;
                }
            }
        }

        for (Iterator it = ANNOTATION_DEFINITION.entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            String name = ((String) entry.getKey()).trim();
            String className = ((String) entry.getValue()).trim();
            Class klass;
            if (className.equals("")) {
                // use default untyped annotation
                klass = UntypedAnnotation.class;
                className = klass.getName();
            } else {
                try {
                    klass = Class.forName(className, false, s_loader);
                } catch (ClassNotFoundException e) {
                    String message = className
                                     +
                                     " could not be found on system classpath or class path provided as argument to the compiler";
                    logError(message);
                    throw new DefinitionException(message);
                }
            }
            logInfo("register custom annotation [" + name + " :: " + className + ']');
            manager.registerAnnotationProxy(klass, name);
            s_customAnnotations.put(name, className);
        }
    }

    /**
     * Prints the usage.
     */
    private static void printUsage() {
        System.out.println("AspectWerkz (c) 2002-2005 Jonas BonŽr, Alexandre Vasseur");
        System.out.println(
                "usage: java [options...] org.codehaus.aspectwerkz.annotation.AnnotationC [-verbose] -src <path to src dir> | -srcfiles <list of files> | -srcincludes <path to file> -classes <path to classes dir> [-dest <path to destination dir>] [-custom <property file for custom annotations>]"
        );
        System.out.println(
                "       -src <path to src dir> provides the list of source directories separated by File.pathSeparator"
        );
        System.out.println("       -srcpath <list of files> provides a comma separated list of source files");
        System.out.println(
                "       -srcincludes <path to file> provides the path to a file containing the list of source files (one name per line)"
        );
        System.out.println(
                "       -dest <path to destination dir> is optional, if omitted the compiled classes will be written to the initial directory"
        );
        System.out.println(
                "       -custom <property file for cutom annotations> is optional, only needed if you have custom annotations you want to compile"
        );
        System.out.println("       -verbose activates compilation status information");
        System.out.println("");
        System.out.println("Note: only one of -src -srcpath and -srcincludes may be used");

        System.exit(0);
    }

    /**
     * Parses the command line options.
     *
     * @param args the arguments
     * @return a map with the options
     */
    private static Map parseCommandLineOptions(final String[] args) {
        final Map arguments = new HashMap();
        try {
            for (int i = 0; i < args.length; i++) {
                if (args[i].equals(COMMAND_LINE_OPTION_VERBOSE)) {
                    s_verbose = true;
                } else if (args[i].startsWith(COMMAND_LINE_OPTION_DASH)) {
                    String option = args[i++];
                    String value = args[i];
                    arguments.put(option, value);
                }
            }
        } catch (Exception e) {
            logError("options list to compiler is not valid");
            System.exit(1);
        }
        return arguments;
    }

    /**
     * Logs an INFO message.
     *
     * @param message the message
     */
    private static void logInfo(final String message) {
        if (s_verbose) {
            System.out.println("AnnotationC::INFO - " + message);
        }
    }

    /**
     * Logs an ERROR message.
     *
     * @param message the message
     */
    private static void logError(final String message) {
        if (s_verbose) {
            System.err.println("AnnotationC::ERROR - " + message);
        }
    }

    /**
     * Logs an WARNING message.
     *
     * @param message the message
     */
    private static void logWarning(final String message) {
        if (s_verbose) {
            System.err.println("AnnotationC::WARNING - " + message);
        }
    }

    /**
     * Helper method to get a pretty printable method signature (no FQN class names)
     *
     * @param method
     * @return
     */
    private static String getShortCallSignature(final JavaMethod method) {
        StringBuffer buffer = new StringBuffer(method.getName());
        buffer.append("(");
        for (int i = 0; i < method.getParameters().length; i++) {
            JavaParameter javaParameter = method.getParameters()[i];
            if (javaParameter.getType().toString().equals(JoinPoint.class.getName())) {
                buffer.append("JoinPoint");
            } else if (javaParameter.getType().toString().equals(StaticJoinPoint.class.getName())) {
                buffer.append("StaticJoinPoint");
            } else {
                buffer.append(javaParameter.getType().toString());
                buffer.append(" ");
                buffer.append(javaParameter.getName());
            }
            if (i + 1 < method.getParameters().length) {
                buffer.append(", ");
            }
        }
        buffer.append(")");
        return buffer.toString();
    }

    private static String[] split(String str, String sep) {
        if (str == null || str.length() == 0) {
            return new String[0];
        }

        int start = 0;
        int idx = str.indexOf(sep, start);
        int len = sep.length();
        List strings = new ArrayList();

        while (idx != -1) {
            strings.add(str.substring(start, idx));
            start = idx + len;
            idx = str.indexOf(sep, start);
        }

        strings.add(str.substring(start));

        return (String[]) strings.toArray(new String[strings.size()]);
    }

    /**
     * Load and solve relative to working directory the list of files.
     *
     * @param srcIncludes
     * @return
     */
    private static String[] loadSourceList(final String srcIncludes) {
        File currentDir = new File(".");
        List files = new ArrayList();
        BufferedReader reader = null;

        try {
            reader = new BufferedReader(new FileReader(srcIncludes));

            String line = reader.readLine();
            File tmpFile = null;
            while (line != null) {
                if (line.length() > 0) {
                    tmpFile = new File(currentDir, line);
                    if (!tmpFile.isFile()) {
                        logWarning("file not found: [" + tmpFile + "]");
                    } else {
                        files.add(tmpFile.getAbsolutePath());
                    }
                }
                line = reader.readLine();
            }
        } catch (IOException ioe) {
            throw new BuildException(
                    "an error occured while reading from pattern file: "
                    + srcIncludes, ioe
            );
        } finally {
            if (null != reader) {
                try {
                    reader.close();
                } catch (IOException ioe) {
                    //Ignore exception
                }
            }
        }

        return (String[]) files.toArray(new String[files.size()]);
    }

}