/*
 * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.tools.apt.comp;

import com.sun.tools.javac.code.*;
import com.sun.tools.javac.comp.*;
import com.sun.tools.javac.tree.*;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.tree.TreeScanner;
import com.sun.tools.javac.util.Context;
import com.sun.tools.apt.util.Bark;
import com.sun.tools.javac.util.Position;

import java.util.*;
import java.util.regex.*;
import java.lang.reflect.*;
import java.lang.reflect.InvocationTargetException;
import java.io.IOException;

import com.sun.tools.apt.*;
import com.sun.tools.apt.comp.*;
import com.sun.tools.javac.code.Symbol.*;

import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
import com.sun.mirror.apt.*;
// import com.sun.mirror.apt.AnnotationProcessorFactory;
import com.sun.mirror.apt.AnnotationProcessors;

import com.sun.tools.apt.mirror.AptEnv;
import com.sun.tools.apt.mirror.apt.FilerImpl;
import com.sun.tools.apt.mirror.apt.AnnotationProcessorEnvironmentImpl;


import static com.sun.tools.apt.mirror.declaration.DeclarationMaker.isJavaIdentifier;

/**
 * Apt compiler phase.
 *
 *  <p><b>This is NOT part of any supported API.
 *  If you write code that depends on this, you do so at your own
 *  risk.  This code and its internal interfaces are subject to change
 *  or deletion without notice.</b>
 */
@SuppressWarnings("deprecation")
public class Apt extends ListBuffer<Env<AttrContext>> {
    java.util.Set<String> genSourceFileNames = new java.util.LinkedHashSet<String>();
    public java.util.Set<String> getSourceFileNames() {
        return genSourceFileNames;
    }

    /** List of names of generated class files.
     */
    java.util.Set<String> genClassFileNames  = new java.util.LinkedHashSet<String>();
    public java.util.Set<String> getClassFileNames() {
        return genClassFileNames;
    }

    /* AptEnvironment */
    AptEnv aptenv;

    private Context context;

    /** The context key for the todo list. */

    protected static final Context.Key<Apt> aptKey =
        new Context.Key<Apt>();

    /** Get the Apt instance for this context. */
    public static Apt instance(Context context) {
        Apt instance = context.get(aptKey);
        if (instance == null)
            instance = new Apt(context);
        return instance;
    }

    /** Create a new apt list. */
    protected Apt(Context context) {
        this.context = context;

        context.put(aptKey, this);
        aptenv = AptEnv.instance(context);
    }

    /**
     * Used to scan javac trees to build data structures needed for
     * bootstrapping the apt environment.  In particular:
     *
     * <ul>
     *
     * <li> Generate list of canonical names of annotation types that
     * appear in source files given on the command line
     *
     * <li> Collect list of javac symbols representing source files
     * given on the command line
     *
     * </ul>
     */
    static class AptTreeScanner extends TreeScanner {

        // Set of fully qualified names of annotation types present in
        // examined source
        private Set<String> annotationSet;

        // Symbols to build bootstrapping declaration list
        private Collection<ClassSymbol> specifiedDeclCollection;
        private Collection<ClassSymbol> declCollection;

        public Set<String> getAnnotationSet() {
            return annotationSet;
        }

        public AptTreeScanner() {
            annotationSet = new  LinkedHashSet<String>();
            specifiedDeclCollection = new LinkedHashSet<ClassSymbol>();
            declCollection = new LinkedHashSet<ClassSymbol>();
        }

        public void visitTopLevel(JCTree.JCCompilationUnit tree) {
            super.visitTopLevel(tree);
            // Print out contents -- what are we dealing with?

            for(JCTree d: tree.defs) {
                if (d instanceof JCTree.JCClassDecl)
                    specifiedDeclCollection.add(((JCTree.JCClassDecl) d).sym);
            }

        }

        public void visitBlock(JCTree.JCBlock tree) {
            ; // Do nothing.
        }


        // should add nested classes to packages, etc.
        public void visitClassDef(JCTree.JCClassDecl tree) {
            if (tree.sym == null) {
                // could be an anon class w/in an initializer
                return;
            }

            super.visitClassDef(tree);

            declCollection.add(tree.sym);
        }

        public void visitMethodDef(JCTree.JCMethodDecl tree) {
            super.visitMethodDef(tree);
        }

        public void visitVarDef(JCTree.JCVariableDecl tree) {
            super.visitVarDef(tree);
        }

        public void visitAnnotation(JCTree.JCAnnotation tree) {
            super.visitAnnotation(tree);
            annotationSet.add(tree.type.tsym.toString());
        }
    }

    Set<String> computeAnnotationSet(Collection<ClassSymbol> classSymbols) {
        Set<String> annotationSet = new HashSet<String>();

        for(ClassSymbol classSymbol: classSymbols) {
            computeAnnotationSet(classSymbol, annotationSet);
        }
        return annotationSet;
    }

    void computeAnnotationSet(Symbol symbol, Set<String> annotationSet) {
        if (symbol != null ) {
            if (symbol.getAnnotationMirrors() != null)
                for(Attribute.Compound compound: symbol.getAnnotationMirrors())
                    annotationSet.add(compound.type.tsym.toString()); // should fullName be used instead of toString?

            if (symbol instanceof Symbol.MethodSymbol) // add parameter annotations
                for(Symbol param: ((MethodSymbol) symbol).params())
                    computeAnnotationSet(param, annotationSet);

            if (symbol.members() != null) {
                for(Scope.Entry e = symbol.members().elems; e != null; e = e.sibling)
                    computeAnnotationSet(e.sym, annotationSet);
            }
        }
    }

    public void main(com.sun.tools.javac.util.List<JCTree.JCCompilationUnit> treeList,
                     ListBuffer<ClassSymbol> classes,
                     Map<String, String> origOptions,
                     ClassLoader aptCL,
                     AnnotationProcessorFactory providedFactory,
                     java.util.Set<Class<? extends AnnotationProcessorFactory> > productiveFactories) {
        Bark bark = Bark.instance(context);
        java.io.PrintWriter out = bark.warnWriter;
        Options options = Options.instance(context);

        Collection<TypeDeclaration> spectypedecls =     new LinkedHashSet<TypeDeclaration>();
        Collection<TypeDeclaration> typedecls =         new LinkedHashSet<TypeDeclaration>();
        Set<String> unmatchedAnnotations =              new LinkedHashSet<String>();
        Set<AnnotationTypeDeclaration> emptyATDS =      Collections.emptySet();
        Set<Class<? extends AnnotationProcessorFactory> > currentRoundFactories =
            new LinkedHashSet<Class<? extends AnnotationProcessorFactory> >();

        // Determine what annotations are present on the input source
        // files, create collections of specified type declarations,
        // and type declarations.
        AptTreeScanner ats = new AptTreeScanner();
        for(JCTree t: treeList) {
            t.accept(ats);
        }

        // Turn collection of ClassSymbols into Collection of apt decls
        for (ClassSymbol cs : ats.specifiedDeclCollection) {
            TypeDeclaration decl = aptenv.declMaker.getTypeDeclaration(cs);
            spectypedecls.add(decl);
        }

        for (ClassSymbol cs : ats.declCollection) {
            TypeDeclaration decl = aptenv.declMaker.getTypeDeclaration(cs);
            typedecls.add(decl);
        }

        unmatchedAnnotations.addAll(ats.getAnnotationSet());

        // Process input class files
        for(ClassSymbol cs : classes) {
            TypeDeclaration decl = aptenv.declMaker.getTypeDeclaration(cs);
            // System.out.println("Adding a class to spectypedecls");
            spectypedecls.add(decl);
            typedecls.add(decl);
            computeAnnotationSet(cs, unmatchedAnnotations);
        }

        if (options.get("-XListAnnotationTypes") != null) {
            out.println("Set of annotations found:" +
                        (new TreeSet<String>(unmatchedAnnotations)).toString());
        }

        AnnotationProcessorEnvironmentImpl trivAPE =
            new AnnotationProcessorEnvironmentImpl(spectypedecls, typedecls, origOptions, context);

        if (options.get("-XListDeclarations") != null) {
            out.println("Set of Specified Declarations:" +
                        spectypedecls);

            out.println("Set of Included Declarations: " +
                           typedecls);
        }

        if (options.get("-print") != null) {
            if (spectypedecls.size() == 0 )
                throw new UsageMessageNeededException();

            // Run the printing processor
            AnnotationProcessor proc = (new BootstrapAPF()).getProcessorFor(new HashSet<AnnotationTypeDeclaration>(),
                                                                            trivAPE);
            proc.process();
        } else {
            // Discovery process

            // List of annotation processory factory instances
            java.util.Iterator<AnnotationProcessorFactory> providers = null;
            {
                /*
                 * If a factory is provided by the user, the
                 * "-factory" and "-factorypath" options are not used.
                 *
                 * Otherwise, if the "-factory" option is used, search
                 * the appropriate path for the named class.
                 * Otherwise, use sun.misc.Service to implement the
                 * default discovery policy.
                 */

                java.util.List<AnnotationProcessorFactory> list =
                    new LinkedList<AnnotationProcessorFactory>();
                String factoryName = options.get("-factory");

                if (providedFactory != null) {
                    list.add(providedFactory);
                    providers = list.iterator();
                } else if (factoryName != null) {
                    try {
                        AnnotationProcessorFactory factory =
                            (AnnotationProcessorFactory) (aptCL.loadClass(factoryName).newInstance());
                        list.add(factory);
                    } catch (ClassNotFoundException cnfe) {
                        bark.aptWarning("FactoryNotFound", factoryName);
                    } catch (ClassCastException cce) {
                        bark.aptWarning("FactoryWrongType", factoryName);
                    } catch (Exception e ) {
                        bark.aptWarning("FactoryCantInstantiate", factoryName);
                    } catch(Throwable t) {
                        throw new AnnotationProcessingError(t);
                    }

                    providers = list.iterator();
                } else {
                    @SuppressWarnings("unchecked")
                    Iterator<AnnotationProcessorFactory> iter =
                            sun.misc.Service.providers(AnnotationProcessorFactory.class, aptCL);
                    providers = iter;

                }
            }

            java.util.Map<AnnotationProcessorFactory, Set<AnnotationTypeDeclaration>> factoryToAnnotation =
                new LinkedHashMap<AnnotationProcessorFactory, Set<AnnotationTypeDeclaration>>();

            if (!providers.hasNext() && productiveFactories.size() == 0) {
                if (unmatchedAnnotations.size() > 0)
                    bark.aptWarning("NoAnnotationProcessors");
                if (spectypedecls.size() == 0)
                    throw new UsageMessageNeededException();
                return; // no processors; nothing else to do
            } else {
                // If there are no annotations, still give
                // processors that match everything a chance to
                // run.

                if(unmatchedAnnotations.size() == 0)
                    unmatchedAnnotations.add("");

                Set<String> emptyStringSet = new HashSet<String>();
                emptyStringSet.add("");
                emptyStringSet = Collections.unmodifiableSet(emptyStringSet);

                while (providers.hasNext() ) {
                    Object provider = providers.next();
                    try {
                        Set<String> matchedStrings = new HashSet<String>();

                        AnnotationProcessorFactory apf = (AnnotationProcessorFactory) provider;
                        Collection<String> supportedTypes = apf.supportedAnnotationTypes();

                        Collection<Pattern> supportedTypePatterns = new LinkedList<Pattern>();
                        for(String s: supportedTypes)
                            supportedTypePatterns.add(importStringToPattern(s));

                        for(String s: unmatchedAnnotations) {
                            for(Pattern p: supportedTypePatterns) {
                                if (p.matcher(s).matches()) {
                                    matchedStrings.add(s);
                                    break;
                                }
                            }
                        }

                        unmatchedAnnotations.removeAll(matchedStrings);

                        if (options.get("-XPrintFactoryInfo") != null) {
                            out.println("Factory " + apf.getClass().getName() +
                                        " matches " +
                                        ((matchedStrings.size() == 0)?
                                         "nothing.": matchedStrings));
                        }

                        if (matchedStrings.size() > 0) {
                            // convert annotation names to annotation
                            // type decls
                            Set<AnnotationTypeDeclaration> atds = new HashSet<AnnotationTypeDeclaration>();

                            // If a "*" processor is called on the
                            // empty string, pass in an empty set of
                            // annotation type declarations.
                            if (!matchedStrings.equals(emptyStringSet)) {
                                for(String s: matchedStrings) {
                                    TypeDeclaration decl = aptenv.declMaker.getTypeDeclaration(s);
                                    AnnotationTypeDeclaration annotdecl;
                                    if (decl == null) {
                                        bark.aptError("DeclarationCreation", s);
                                    } else {
                                        try {
                                            annotdecl = (AnnotationTypeDeclaration)decl;
                                            atds.add(annotdecl);

                                        } catch (ClassCastException cce) {
                                            bark.aptError("BadDeclaration", s);
                                        }
                                    }
                                }
                            }

                            currentRoundFactories.add(apf.getClass());
                            productiveFactories.add(apf.getClass());
                            factoryToAnnotation.put(apf, atds);
                        } else if (productiveFactories.contains(apf.getClass())) {
                            // If a factory provided a processor in a
                            // previous round but doesn't match any
                            // annotations this round, call it with an
                            // empty set of declarations.
                            currentRoundFactories.add(apf.getClass());
                            factoryToAnnotation.put(apf, emptyATDS );
                        }

                        if (unmatchedAnnotations.size() == 0)
                            break;

                    } catch (ClassCastException cce) {
                        bark.aptWarning("BadFactory", cce);
                    }
                }

                unmatchedAnnotations.remove("");
            }

            // If the set difference of productiveFactories and
            // currentRoundFactories is non-empty, call the remaining
            // productive factories with an empty set of declarations.
            {
                java.util.Set<Class<? extends AnnotationProcessorFactory> > neglectedFactories =
                    new LinkedHashSet<Class<? extends AnnotationProcessorFactory>>(productiveFactories);
                neglectedFactories.removeAll(currentRoundFactories);
                for(Class<? extends AnnotationProcessorFactory> working : neglectedFactories) {
                    try {
                        AnnotationProcessorFactory factory = working.newInstance();
                        factoryToAnnotation.put(factory, emptyATDS);
                    } catch (Exception e ) {
                        bark.aptWarning("FactoryCantInstantiate", working.getName());
                    } catch(Throwable t) {
                        throw new AnnotationProcessingError(t);
                    }
                }
            }

            if (unmatchedAnnotations.size() > 0)
                bark.aptWarning("AnnotationsWithoutProcessors", unmatchedAnnotations);

            Set<AnnotationProcessor> processors = new LinkedHashSet<AnnotationProcessor>();

            // If there were no source files AND no factory matching "*",
            // make sure the usage message is printed
            if (spectypedecls.size() == 0 &&
                factoryToAnnotation.keySet().size() == 0 )
                throw new UsageMessageNeededException();

            try {
                for(Map.Entry<AnnotationProcessorFactory, Set<AnnotationTypeDeclaration>> entry :
                        factoryToAnnotation.entrySet()) {
                    AnnotationProcessorFactory  apFactory = entry.getKey();
                    AnnotationProcessor processor = apFactory.getProcessorFor(entry.getValue(),
                                                                              trivAPE);
                    if (processor != null)
                        processors.add(processor);
                    else
                        bark.aptWarning("NullProcessor", apFactory.getClass().getName());
                }
            } catch(Throwable t) {
                throw new AnnotationProcessingError(t);
            }

            LinkedList<AnnotationProcessor> temp = new LinkedList<AnnotationProcessor>();
            temp.addAll(processors);

            AnnotationProcessor proc = AnnotationProcessors.getCompositeAnnotationProcessor(temp);

            try {
                proc.process();
            } catch (Throwable t) {
                throw new AnnotationProcessingError(t);
            }

            // Invoke listener callback mechanism
            trivAPE.roundComplete();

            FilerImpl filerimpl = (FilerImpl)trivAPE.getFiler();
            genSourceFileNames = filerimpl.getSourceFileNames();
            genClassFileNames = filerimpl.getClassFileNames();
            filerimpl.flush(); // Make sure new files are written out
        }
    }

    /**
     * Convert import-style string to regex matching that string.  If
     * the string is a valid import-style string, return a regex that
     * won't match anything.
     */
    Pattern importStringToPattern(String s) {
        if (com.sun.tools.javac.processing.JavacProcessingEnvironment.isValidImportString(s)) {
            return com.sun.tools.javac.processing.JavacProcessingEnvironment.validImportStringToPattern(s);
        } else {
            Bark bark = Bark.instance(context);
            bark.aptWarning("MalformedSupportedString", s);
            return com.sun.tools.javac.processing.JavacProcessingEnvironment.noMatches;
        }
    }
}
