view j2se/src/share/classes/com/sun/tools/javac/util/Paths.java @ 7:807dfe9c366c trunk

[svn] Load openjdk/jdk7/b19 into jdk/trunk.
author xiomara
date Fri, 31 Aug 2007 00:44:13 +0000
parents 5937f8212f93
children
line wrap: on
line source

/*
 * Copyright 2003-2006 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.
 */

package com.sun.tools.javac.util;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.jar.Attributes;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import com.sun.tools.javac.code.Lint;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Options;
import com.sun.tools.javac.util.Position;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.tools.JavaFileManager.Location;

import static com.sun.tools.javac.main.OptionName.*;
import static javax.tools.StandardLocation.*;

/** This class converts command line arguments, environment variables
 *  and system properties (in File.pathSeparator-separated String form)
 *  into a boot class path, user class path, and source path (in
 *  Collection<String> form).
 *
 *  <p><b>This is NOT part of any API supported by Sun Microsystems.  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>
 */
public class Paths {

    /** The context key for the todo list */
    protected static final Context.Key<Paths> pathsKey =
	new Context.Key<Paths>();

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

    /** The log to use for warning output */
    private Log log;

    /** Collection of command-line options */
    private Options options;

    /** Handler for -Xlint options */
    private Lint lint;

    private static boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this.
    private static Map<File, PathEntry> pathExistanceCache = new ConcurrentHashMap<File, PathEntry>();
    private static Map<File, java.util.List<String>> manifestEntries = new ConcurrentHashMap<File, java.util.List<String>>();
    private static Map<File, Boolean> isDirectory = new ConcurrentHashMap<File, Boolean>();
    private static Lock lock = new ReentrantLock();
    
    public static void clearPathExistanceCache() {
            pathExistanceCache.clear();
    }

    static class PathEntry {
        boolean exists = false;
        boolean isFile = false;
        File cannonicalPath = null;
    }

    protected Paths(Context context) {
	context.put(pathsKey, this);
	pathsForLocation = new HashMap<Location,Path>(16);
        setContext(context);
    }

    void setContext(Context context) {
        log = Log.instance(context);
        options = Options.instance(context);
        lint = Lint.instance(context);
    }

    /** Whether to warn about non-existent path elements */
    private boolean warn;

    private Map<Location, Path> pathsForLocation;

    private boolean inited = false; // TODO? caching bad?

    /**
     * rt.jar as found on the default bootclass path.  If the user specified a
     * bootclasspath, null is used.
     */
    private File bootClassPathRtJar = null;

    Path getPathForLocation(Location location) {
        Path path = pathsForLocation.get(location);
        if (path == null)
            setPathForLocation(location, null);
	return pathsForLocation.get(location);
    }
    
    void setPathForLocation(Location location, Iterable<? extends File> path) {
	// TODO? if (inited) throw new IllegalStateException
	// TODO: otherwise reset sourceSearchPath, classSearchPath as needed
	Path p;
	if (path == null) {
	    if (location == CLASS_PATH)
		p = computeUserClassPath();
	    else if (location == PLATFORM_CLASS_PATH)
		p = computeBootClassPath();
	    else if (location == ANNOTATION_PROCESSOR_PATH)
		p = computeAnnotationProcessorPath();
	    else if (location == SOURCE_PATH)
		p = computeSourcePath();
	    else 
		// no defaults for other paths
		p = null;
	} else {
	    p = new Path();
	    for (File f: path)
		p.addFile(f, warn); // TODO: is use of warn appropriate? 
	}
	pathsForLocation.put(location, p);
    }

    protected void lazy() {
	if (!inited) {
	    warn = lint.isEnabled(Lint.LintCategory.PATH);

	    pathsForLocation.put(PLATFORM_CLASS_PATH, computeBootClassPath());
	    pathsForLocation.put(CLASS_PATH, computeUserClassPath());
	    pathsForLocation.put(SOURCE_PATH, computeSourcePath());

	    inited = true;
	}
    }

    public Collection<File> bootClassPath() {
        lazy();
        return Collections.unmodifiableCollection(getPathForLocation(PLATFORM_CLASS_PATH));
    }
    public Collection<File> userClassPath() {
        lazy();
        return Collections.unmodifiableCollection(getPathForLocation(CLASS_PATH));
    }
    public Collection<File> sourcePath() {
        lazy();
	Path p = getPathForLocation(SOURCE_PATH);
        return p == null || p.size() == 0
            ? null
            : Collections.unmodifiableCollection(p);
    }

    boolean isBootClassPathRtJar(File file) {
        return file.equals(bootClassPathRtJar);
    }

    private static class PathIterator implements Iterable<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();
		}
	    };
	}
    }

    private class Path extends LinkedHashSet<File> {
	private static final long serialVersionUID = 0;

	private boolean expandJarClassPaths = false;
        private Set<File> canonicalValues = new HashSet<File>();

	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("dir.path.element.not.found", dir);
		return;
	    }

            File[] files = new File(dir).listFiles();
            if (files == null)
                return;
            
	    for (File direntry : files) {
		if (isArchive(direntry))
		    addFile(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);
	}
	
	public Path addFile(String file, boolean warn) {
	    addFile(new File(file), warn);
	    return this;
	}

	public void addFile(File file, boolean warn) {
            boolean foundInCache = false;
            PathEntry pe = null;
            if (!NON_BATCH_MODE) {
                    pe = pathExistanceCache.get(file);
                    if (pe != null) {
                        foundInCache = true;
                    }
                    else {
                        pe = new PathEntry();
                    }
            }
            else {
                pe = new PathEntry();
            }

            File canonFile;
            try {
                if (!foundInCache) {
                    pe.cannonicalPath = file.getCanonicalFile();
                }
                else {
                   canonFile = pe.cannonicalPath;
                }
            } catch (IOException e) {
                pe.cannonicalPath = canonFile = file;
            }
            
	    if (contains(file) || canonicalValues.contains(pe.cannonicalPath)) {
		/* Discard duplicates and avoid infinite recursion */
		return;
	    } 

            if (!foundInCache) {
                pe.exists = file.exists();
                pe.isFile = file.isFile();
                if (!NON_BATCH_MODE) {
                    pathExistanceCache.put(file, pe);
                }
            }

            if (! pe.exists) {
		/* No such file or directory exists */
		if (warn)
		    log.warning("path.element.not.found", file);	
	    } else if (pe.isFile) {
		/* File is an ordinary file. */ 
		if (!isArchive(file)) {
		    /* Not a recognized extension; open it to see if
		     it looks like a valid zip file. */
		    try {
			ZipFile z = new ZipFile(file);
			z.close();
			if (warn)
			    log.warning("unexpected.archive.file", file);
		    } catch (IOException e) {
                        // FIXME: include e.getLocalizedMessage in warning
			if (warn)
			    log.warning("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);
            canonicalValues.add(pe.cannonicalPath);
            
	    if (expandJarClassPaths && file.exists() && file.isFile())
		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(File jarFile, boolean warn) {
	    try {
                java.util.List<String> manifestsList = manifestEntries.get(jarFile);
                if (!NON_BATCH_MODE) {
                    lock.lock();
                    try {
                        if (manifestsList != null) {
                            for (String entr : manifestsList) {
                                addFile(new File(entr), warn);
                            }
                            return;
                        }
                    }
                    finally {
                        lock.unlock();
                    }
                }
                
                if (!NON_BATCH_MODE) {
                    manifestsList = new ArrayList<String>();
                    manifestEntries.put(jarFile, manifestsList);
                }

		String jarParent = jarFile.getParent();
		JarFile jar = new JarFile(jarFile);

		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();
			File f = (jarParent == null ? new File(elt) : new File(jarParent, elt));
			addFile(f, warn);

                        if (!NON_BATCH_MODE) {
                            lock.lock();
                            try {
                                manifestsList.add(elt);
                            }
                            finally {
                                lock.unlock();
                            }
                        }
		    }
		} finally {
		    jar.close();
		}
	    } catch (IOException e) {
		log.error("error.reading.file", jarFile, e.getLocalizedMessage());
	    }
	}
    }

    private Path computeBootClassPath() {
        bootClassPathRtJar = null;
	String optionValue;
	Path path = new Path();

	path.addFiles(options.get(XBOOTCLASSPATH_PREPEND));

	if ((optionValue = options.get(ENDORSEDDIRS)) != null)
	    path.addDirectories(optionValue);
	else
	    path.addDirectories(System.getProperty("java.endorsed.dirs"), false);

        if ((optionValue = options.get(BOOTCLASSPATH)) != null) {
            path.addFiles(optionValue);
        } else {
            // Standard system classes for this compiler's release.
            String files = System.getProperty("sun.boot.class.path");
            path.addFiles(files, false);
            File rt_jar = new File("rt.jar");
            for (String file : new PathIterator(files, null)) {
                File f = new File(file);
                if (new File(f.getName()).equals(rt_jar))
                    bootClassPathRtJar = f;
            }
        }

	path.addFiles(options.get(XBOOTCLASSPATH_APPEND));

	// Strictly speaking, standard extensions are not bootstrap
	// classes, but we treat them identically, so we'll pretend
	// that they are.
	if ((optionValue = options.get(EXTDIRS)) != null)
	    path.addDirectories(optionValue);
	else
	    path.addDirectories(System.getProperty("java.ext.dirs"), false);

	return path;
    }

    private Path computeUserClassPath() {
	String cp = options.get(CLASSPATH);

	// CLASSPATH environment variable when run from `javac'.
	if (cp == null) cp = System.getProperty("env.class.path");

	// If invoked via a java VM (not the javac launcher), use the
	// platform class path
	if (cp == null && System.getProperty("application.home") == null)
	    cp = System.getProperty("java.class.path");

	// Default to current working directory.
	if (cp == null) cp = ".";

	return new Path()
	    .expandJarClassPaths(true) // Only search user jars for Class-Paths
	    .emptyPathDefault(".")     // Empty path elt ==> current directory
	    .addFiles(cp);
    }

    private Path computeSourcePath() {
	String sourcePathArg = options.get(SOURCEPATH);
	if (sourcePathArg == null)
	    return null;

	return new Path().addFiles(sourcePathArg);
    }

    private Path computeAnnotationProcessorPath() {
	String processorPathArg = options.get(PROCESSORPATH);
	if (processorPathArg == null)
	    return null;

	return new Path().addFiles(processorPathArg);
    }

    /** The actual effective locations searched for sources */
    private Path sourceSearchPath;

    public Collection<File> sourceSearchPath() {
	if (sourceSearchPath == null) {
	    lazy();
	    Path sourcePath = getPathForLocation(SOURCE_PATH);
	    Path userClassPath = getPathForLocation(CLASS_PATH);
	    sourceSearchPath = sourcePath != null ? sourcePath : userClassPath;
	}
	return Collections.unmodifiableCollection(sourceSearchPath);
    }

    /** The actual effective locations searched for classes */
    private Path classSearchPath;

    public Collection<File> classSearchPath() {
	if (classSearchPath == null) {
	    lazy();
	    Path bootClassPath = getPathForLocation(PLATFORM_CLASS_PATH);
	    Path userClassPath = getPathForLocation(CLASS_PATH);
	    classSearchPath = new Path();
	    classSearchPath.addAll(bootClassPath);
	    classSearchPath.addAll(userClassPath);
	}
	return Collections.unmodifiableCollection(classSearchPath);
    }
    
    /** The actual effective locations for non-source, non-class files */
    private Path otherSearchPath;
    
    Collection<File> otherSearchPath() {
	if (otherSearchPath == null) {
	    lazy();
	    Path userClassPath = getPathForLocation(CLASS_PATH);
	    Path sourcePath = getPathForLocation(SOURCE_PATH);
	    if (sourcePath == null)
		otherSearchPath = userClassPath;
	    else {
		otherSearchPath = new Path();
		otherSearchPath.addAll(userClassPath);
		otherSearchPath.addAll(sourcePath);
	    }
	}
	return Collections.unmodifiableCollection(otherSearchPath);
    }

    /** Is this the name of an archive file? */
    private static boolean isArchive(File file) {
	String n = file.getName().toLowerCase();
        boolean isFile = false;
        if (!NON_BATCH_MODE) {
            Boolean isf = isDirectory.get(file);
            if (isf == null) {
                isFile = file.isFile();
                isDirectory.put(file, isFile);
            }
            else {
                isFile = isf;
            }
        }
        else {
            isFile = file.isFile();
        }

        return isFile
	    && (n.endsWith(".jar") || n.endsWith(".zip"));
    }
}