/*
 * Copyright 1996-2007 Sun Microsystems, Inc.  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.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

/*****************************************************************************/
/*                    Copyright (c) IBM Corporation 1998                     */
/*                                                                           */
/* (C) Copyright IBM Corp. 1998                                              */
/*                                                                           */
/*****************************************************************************/

package sun.rmi.rmic;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.jar.Attributes;
import sun.tools.java.ClassPath;

/**
 * BatchEnvironment for rmic extends javac's version in four ways:
 * 1. It overrides errorString() to handle looking for rmic-specific
 * error messages in rmic's resource bundle
 * 2. It provides a mechanism for recording intermediate generated
 * files so that they can be deleted later.
 * 3. It holds a reference to the Main instance so that generators
 * can refer to it.
 * 4. It provides access to the ClassPath passed to the constructor.
 *
 * WARNING: The contents of this source file are not part of any
 * supported API.  Code that depends on them does so at its own risk:
 * they are subject to change or removal without notice.
 */

public class BatchEnvironment extends sun.tools.javac.BatchEnvironment {

    /** instance of Main which created this environment */
    private Main main;

    /**
     * Create a ClassPath object for rmic from a class path string.
     */
    public static ClassPath createClassPath(String classPathString) {
        ClassPath[] paths = classPaths(null, classPathString, null, null);
        return paths[1];
    }

    /**
     * Create a ClassPath object for rmic from the relevant command line
     * options for class path, boot class path, and extension directories.
     */
    public static ClassPath createClassPath(String classPathString,
                                            String sysClassPathString,
                                            String extDirsString)
    {
        /**
         * Previously, this method delegated to the
         * sun.tools.javac.BatchEnvironment.classPaths method in order
         * to supply default values for paths not specified on the
         * command line, expand extensions directories into specific
         * JAR files, and construct the ClassPath object-- but as part
         * of the fix for 6473331, which adds support for Class-Path
         * manifest entries in JAR files, those steps are now handled
         * here directly, with the help of a Path utility class copied
         * from the new javac implementation (see below).
         */
        Path path = new Path();

        if (sysClassPathString == null) {
            sysClassPathString = System.getProperty("sun.boot.class.path");
        }
        if (sysClassPathString != null) {
            path.addFiles(sysClassPathString);
        }

        /*
         * Class-Path manifest entries are supported for JAR files
         * everywhere except in the boot class path.
         */
        path.expandJarClassPaths(true);

        if (extDirsString == null) {
            extDirsString = System.getProperty("java.ext.dirs");
        }
        if (extDirsString != null) {
            path.addDirectories(extDirsString);
        }

        /*
         * In the application class path, an empty element means
         * the current working directory.
         */
        path.emptyPathDefault(".");

        if (classPathString == null) {
            // The env.class.path property is the user's CLASSPATH
            // environment variable, and it set by the wrapper (ie,
            // javac.exe).
            classPathString = System.getProperty("env.class.path");
            if (classPathString == null) {
                classPathString = ".";
            }
        }
        path.addFiles(classPathString);

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

    /**
     * Create a BatchEnvironment for rmic with the given class path,
     * stream for messages and Main.
     */
    public BatchEnvironment(OutputStream out, ClassPath path, Main main) {
        super(out, new ClassPath(""), path);
                                // use empty "sourcePath" (see 4666958)
        this.main = main;
    }

    /**
     * Get the instance of Main which created this environment.
     */
    public Main getMain() {
        return main;
    }

    /**
     * Get the ClassPath.
     */
    public ClassPath getClassPath() {
        return binaryPath;
    }

    /** list of generated source files created in this environment */
    private Vector generatedFiles = new Vector();

    /**
     * Remember a generated source file generated so that it
     * can be removed later, if appropriate.
     */
    public void addGeneratedFile(File file) {
        generatedFiles.addElement(file);
    }

    /**
     * Delete all the generated source files made during the execution
     * of this environment (those that have been registered with the
     * "addGeneratedFile" method).
     */
    public void deleteGeneratedFiles() {
        synchronized(generatedFiles) {
            Enumeration enumeration = generatedFiles.elements();
            while (enumeration.hasMoreElements()) {
                File file = (File) enumeration.nextElement();
                file.delete();
            }
            generatedFiles.removeAllElements();
        }
    }

    /**
     * Release resources, if any.
     */
    public void shutdown() {
        main = null;
        generatedFiles = null;
        super.shutdown();
    }

    /**
     * Return the formatted, localized string for a named error message
     * and supplied arguments.  For rmic error messages, with names that
     * being with "rmic.", look up the error message in rmic's resource
     * bundle; otherwise, defer to java's superclass method.
     */
    public String errorString(String err,
                              Object arg0, Object arg1, Object arg2)
    {
        if (err.startsWith("rmic.") || err.startsWith("warn.rmic.")) {
            String result =  Main.getText(err,
                                          (arg0 != null ? arg0.toString() : null),
                                          (arg1 != null ? arg1.toString() : null),
                                          (arg2 != null ? arg2.toString() : null));

            if (err.startsWith("warn.")) {
                result = "warning: " + result;
            }
            return result;
        } else {
            return super.errorString(err, arg0, arg1, arg2);
        }
    }
    public void reset() {
    }

    /**
     * Utility for building paths of directories and JAR files.  This
     * class was copied from com.sun.tools.javac.util.Paths as part of
     * the fix for 6473331, which adds support for Class-Path manifest
     * entries in JAR files.  Diagnostic code is simply commented out
     * because rmic silently ignored these conditions historically.
     */
    private static class Path extends LinkedHashSet<String> {
        private static final long serialVersionUID = 0;
        private static final boolean warn = false;

        private static class PathIterator implements Collection<String> {
            private int pos = 0;
            private final String path;
            private final String emptyPathDefault;

            public PathIterator(String path, String emptyPathDefault) {
                this.path = path;
                this.emptyPathDefault = emptyPathDefault;
            }
            public PathIterator(String path) { this(path, null); }
            public Iterator<String> iterator() {
                return new Iterator<String>() {
                    public boolean hasNext() {
                        return pos <= path.length();
                    }
                    public String next() {
                        int beg = pos;
                        int end = path.indexOf(File.pathSeparator, beg);
                        if (end == -1)
                            end = path.length();
                        pos = end + 1;

                        if (beg == end && emptyPathDefault != null)
                            return emptyPathDefault;
                        else
                            return path.substring(beg, end);
                    }
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }

            // required for Collection.
            public int size() {
                throw new UnsupportedOperationException();
            }
            public boolean isEmpty() {
                throw new UnsupportedOperationException();
            }
            public boolean contains(Object o) {
                throw new UnsupportedOperationException();
            }
            public Object[] toArray() {
                throw new UnsupportedOperationException();
            }
            public <T> T[] toArray(T[] a) {
                throw new UnsupportedOperationException();
            }
            public boolean add(String o) {
                throw new UnsupportedOperationException();
            }
            public boolean remove(Object o) {
                throw new UnsupportedOperationException();
            }
            public boolean containsAll(Collection<?> c) {
                throw new UnsupportedOperationException();
            }
            public boolean addAll(Collection<? extends String> c) {
                throw new UnsupportedOperationException();
            }
            public boolean removeAll(Collection<?> c) {
                throw new UnsupportedOperationException();
            }
            public boolean retainAll(Collection<?> c) {
                throw new UnsupportedOperationException();
            }
            public void clear() {
                throw new UnsupportedOperationException();
            }
            public boolean equals(Object o) {
                throw new UnsupportedOperationException();
            }
            public int hashCode() {
                throw new UnsupportedOperationException();
            }
        }

        /** Is this the name of a zip file? */
        private static boolean isZip(String name) {
            return new File(name).isFile();
        }

        private boolean expandJarClassPaths = false;

        public Path expandJarClassPaths(boolean x) {
            expandJarClassPaths = x;
            return this;
        }

        /** What to use when path element is the empty string */
        private String emptyPathDefault = null;

        public Path emptyPathDefault(String x) {
            emptyPathDefault = x;
            return this;
        }

        public Path() { super(); }

        public Path addDirectories(String dirs, boolean warn) {
            if (dirs != null)
                for (String dir : new PathIterator(dirs))
                    addDirectory(dir, warn);
            return this;
        }

        public Path addDirectories(String dirs) {
            return addDirectories(dirs, warn);
        }

        private void addDirectory(String dir, boolean warn) {
            if (! new File(dir).isDirectory()) {
//              if (warn)
//                  log.warning(Position.NOPOS,
//                              "dir.path.element.not.found", dir);
                return;
            }

            for (String direntry : new File(dir).list()) {
                String canonicalized = direntry.toLowerCase();
                if (canonicalized.endsWith(".jar") ||
                    canonicalized.endsWith(".zip"))
                    addFile(dir + File.separator + direntry, warn);
            }
        }

        public Path addFiles(String files, boolean warn) {
            if (files != null)
                for (String file : new PathIterator(files, emptyPathDefault))
                    addFile(file, warn);
            return this;
        }

        public Path addFiles(String files) {
            return addFiles(files, warn);
        }

        private void addFile(String file, boolean warn) {
            if (contains(file)) {
                /* Discard duplicates and avoid infinite recursion */
                return;
            }

            File ele = new File(file);
            if (! ele.exists()) {
                /* No such file or directory exist */
                if (warn)
//                      log.warning(Position.NOPOS,
//                          "path.element.not.found", file);
                    return;
            }

            if (ele.isFile()) {
                /* File is an ordinay file  */
                String arcname = file.toLowerCase();
                if (! (arcname.endsWith(".zip") ||
                       arcname.endsWith(".jar"))) {
                    /* File name don't have right extension */
//                      if (warn)
//                          log.warning(Position.NOPOS,
//                              "invalid.archive.file", file);
                    return;
                }
            }

            /* Now what we have left is either a directory or a file name
               confirming to archive naming convention */

            super.add(file);
            if (expandJarClassPaths && isZip(file))
                addJarClassPath(file, warn);
        }

        // Adds referenced classpath elements from a jar's Class-Path
        // Manifest entry.  In some future release, we may want to
        // update this code to recognize URLs rather than simple
        // filenames, but if we do, we should redo all path-related code.
        private void addJarClassPath(String jarFileName, boolean warn) {
            try {
                String jarParent = new File(jarFileName).getParent();
                JarFile jar = new JarFile(jarFileName);

                try {
                    Manifest man = jar.getManifest();
                    if (man == null) return;

                    Attributes attr = man.getMainAttributes();
                    if (attr == null) return;

                    String path = attr.getValue(Attributes.Name.CLASS_PATH);
                    if (path == null) return;

                    for (StringTokenizer st = new StringTokenizer(path);
                        st.hasMoreTokens();) {
                        String elt = st.nextToken();
                        if (jarParent != null)
                            elt = new File(jarParent, elt).toString();
                        addFile(elt, warn);
                    }
                } finally {
                    jar.close();
                }
            } catch (IOException e) {
//              log.error(Position.NOPOS,
//                        "error.reading.file", jarFileName,
//                        e.getLocalizedMessage());
            }
        }
    }
}
