/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2015 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.enterprise.tools.verifier.apiscan.classfile;

import java.util.*;
import java.util.logging.Logger;
import java.io.IOException;
import java.io.File;

/**
 * This is single most important class of the apiscan package. This class is
 * used to compute the complete closure of a set of classes. It uses {@link
 * ClassFile} to find out dependencies of a class. For each references class
 * name, it loads the corresponding ClassFile using the ClassFileLoader passed
 * to it in constructor. Then it recurssively computes the dependencies until it
 * visits either Java primitive classes or the class name matches the exclude
 * list. Example of using this class is given below...
 * <blockquote><pre>
 * String classpath="your own classpath";
 * ClassFileLoader cfl=ClassFileLoaderFactory.newInstance(new
 * Object[]{classpath});
 * ClosureCompilerImpl cc=new ClosureCompilerImpl(cfl);
 * cc.addExcludePattern("java.");//exclude all classes that start with java.
 * Most of the J2SE classes will be excluded thus.
  * cc.addExcludePattern("javax.");//Most of the J2EE classes can be excluded
 * like this.
 * cc.addExcludePackage("org.omg.CORBA");//Exclude classes whose package name is
 * org.omg.CORBA
 * cc.addExcludeClass("mypackage.Foo");//Exclude class whose name is
 * myPackage.Foo
 * boolean successful=cc.buildClosure("a.b.MyEjb");
 * successful=cc.buildClosure("a.b.YourEjb") && successful; //note the order of
 * &&.
 * Collection closure=cc.getClosure();//now closure contains the union of
 * closure for the two classes.
 * Map failed=cc.getFailed();//now failure contains the union of failures for
 * the two classes.
 * cc.reset();//clear the results collected so far so that we can start afresh.
 * //Now you can again start computing closure of another set of classes.
 * //The excluded stuff are still valid.
 * </pre></blockquote>
 *
 * @author Sanjeeb.Sahoo@Sun.COM
 */
public class ClosureCompilerImpl implements ClosureCompiler {

   /*
    * BRIDGE design pattern.
    * This is an abstraction. It delegates to an implementation.
    * It is bound to an implementation at runtime. See the constructor.
    */

    /**
     * an implementation to whom this abstraction delegates.
     * refer to bridge design pattern.
     */
    private ClosureCompilerImplBase imp;
    private static String resourceBundleName = "com.sun.enterprise.tools.verifier.apiscan.LocalStrings";
    private static Logger logger = Logger.getLogger("apiscan.classfile", resourceBundleName); // NOI18N
    private static final String myClassName = "ClosureCompilerImpl"; // NOI18N

    /**
     * @param loader the ClassFileLoader that is used to load the referenced
     *               classes.
     */
    public ClosureCompilerImpl(ClassFileLoader loader) {
        /*
         * See how it binds to a runtime implementation
         * TODO: Ideally we should introduce an AbstractFactory for
         * both ClassFileLoader & ClassClosureCompiler product types.
         */
        if(loader instanceof BCELClassFileLoader ||
                loader instanceof BCELClassFileLoader1) {
            imp = new BCELClosureCompilerImpl(loader);
        } else {
            throw new RuntimeException("Unknown loader type [" + loader + "]");
        }
    }

    /**
     * I don't expect this constructor to be used. Only defined for
     * testing purpose.
     * @param imp the implementation in the bridge design pattern.
     */
    public ClosureCompilerImpl(ClosureCompilerImplBase imp) {
        this.imp = imp;
    }

    /**
     * @param className the class name to be excluded from closure
     *                     computation. It is in the external class name format
     *                     (i.e. java.util.Map$Entry instead of java.util.Map.Entry).
     *                     When the closure compiler sees a class matches this
     *                     name, it does not try to compute its closure any
     *                     more. It merely adds this name to the closure. So the
     *                     final closure will contain this class name, but not
     *                     its dependencies.
     */
    public void addExcludedClass(String className) {
        imp.addExcludedClass(className);
    }

    //takes in external format, i.e. java.util
    /**
     * @param pkgName the package name of classes to be excluded from
     *                     closure computation. It is in the external format
     *                     (i.e. java.lang (See no trailing '.'). When the
     *                     closure compiler sees a class whose package name
     *                     matches this name, it does not try to compute the
     *                     closure of that class any more. It merely adds that
     *                     class name to the closure. So the final closure will
     *                     contain that class name, but not its dependencies.
     */
    public void addExcludedPackage(String pkgName) {
        imp.addExcludedPackage(pkgName);
    }

    /**
     * @param pattern the pattern for the names of classes to be excluded from
     *                closure computation. It is in the external format (i.e.
     *                org.apache.). When the closure compiler sees a class whose
     *                name begins with this pattern, it does not try to compute
     *                the closure of that class any more. It merely adds that
     *                class name to the closure. So the final closure will
     *                contain that class name, but not its dependencies. Among
     *                all the excluded list, it is given the lowest priority in
     *                search order.
     */
    public void addExcludedPattern(String pattern) {
        imp.addExcludedPattern(pattern);
    }

    //See corresponding method of ClosureCompiler for javadocs
    public boolean buildClosure(String className) {
        logger.entering(myClassName, "buildClosure", className); // NOI18N
        return imp.buildClosure(className);
    }

    /**
     * @param jar whose classes it will try to build closure of. This is a
     *            convenience method which iterates over all the entries in a
     *            jar file and computes their closure.
     */
    public boolean buildClosure(java.util.jar.JarFile jar) throws IOException {
        return imp.buildClosure(jar);
    }

    //See corresponding method of ClosureCompiler for javadocs
    public Collection getClosure() {
        return imp.getClosure();
    }

    //See corresponding method of ClosureCompiler for javadocs
    public Map getFailed() {
        return imp.getFailed();
    }

    /**
     * Reset the closure for next closure computation.
     * Clear the internal cache. It includes the result it has collected since
     * last reset(). But it does not clear the excludedd list. If you want to
     * reset the excluded list, create a new ClosureCompiler.
     */
    public void reset() {
        imp.reset();
    }

    public Collection<String> getNativeMethods() {
        return imp.getNativeMethods();
    }

    public String toString() {
        return imp.toString();
    }

    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.println(
                    "Usage : java " + com.sun.enterprise.tools.verifier.apiscan.classfile.ClosureCompilerImpl.class.getName() + // NOI18N
                    " <classpath> <external class name(s)>"); // NOI18N
            System.out.println("Example: to find the closure of " + // NOI18N
                    "mypkg.MySessionBean which is packaged in myejb.jar run\n" + // NOI18N
                    " java " + com.sun.enterprise.tools.verifier.apiscan.classfile.ClosureCompilerImpl.class.getName() + // NOI18N
                    " path_to_j2ee.jar"+File.pathSeparator+"path_to_myejb.jar"+ // NOI18N
                    " mypkg.MySessionBean"); // NOI18N
            System.exit(1);
        }

        String cp=args[0];
        System.out.println("Using classpath " + cp); // NOI18N
        ClassFileLoader cfl = ClassFileLoaderFactory.newInstance(
                new Object[]{cp});
        ClosureCompilerImpl closure = new ClosureCompilerImpl(cfl);
        closure.addExcludedPattern("java."); // NOI18N
        for (int i = 1; i < args.length; i++) {
            String clsName = args[i];
            System.out.println("Building closure for " + clsName); // NOI18N
            closure.reset();
            closure.buildClosure(clsName);
            System.out.println("The closure is [" + closure+"\n]"); // NOI18N
        }
    }

}
