1 /*
   2  * Copyright 2003-2008 Sun Microsystems, Inc.  All Rights Reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package com.sun.tools.javac.file;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.util.HashMap;
  31 import java.util.HashSet;
  32 import java.util.Map;
  33 import java.util.Set;
  34 import java.util.Collection;
  35 import java.util.Collections;
  36 import java.util.LinkedHashSet;
  37 import java.util.zip.ZipFile;
  38 import javax.tools.JavaFileManager.Location;
  39 
  40 import com.sun.tools.javac.code.Lint;
  41 import com.sun.tools.javac.util.Context;
  42 import com.sun.tools.javac.util.ListBuffer;
  43 import com.sun.tools.javac.util.Log;
  44 import com.sun.tools.javac.util.Options;
  45 
  46 import static javax.tools.StandardLocation.*;
  47 import static com.sun.tools.javac.main.OptionName.*;
  48 
  49 /** This class converts command line arguments, environment variables
  50  *  and system properties (in File.pathSeparator-separated String form)
  51  *  into a boot class path, user class path, and source path (in
  52  *  Collection<String> form).
  53  *
  54  *  <p><b>This is NOT part of any API supported by Sun Microsystems.  If
  55  *  you write code that depends on this, you do so at your own risk.
  56  *  This code and its internal interfaces are subject to change or
  57  *  deletion without notice.</b>
  58  */
  59 public class Paths {
  60 
  61     /** The context key for the todo list */
  62     protected static final Context.Key<Paths> pathsKey =
  63         new Context.Key<Paths>();
  64 
  65     /** Get the Paths instance for this context.
  66      *  @param context the context
  67      *  @return the Paths instance for this context
  68      */
  69     static Paths instance(Context context) {
  70         Paths instance = context.get(pathsKey);
  71         if (instance == null)
  72             instance = new Paths(context);
  73         return instance;
  74     }
  75 
  76     /** The log to use for warning output */
  77     private Log log;
  78 
  79     /** Collection of command-line options */
  80     private Options options;
  81 
  82     /** Handler for -Xlint options */
  83     private Lint lint;
  84 
  85     /** Access to (possibly cached) file info */
  86     private FSInfo fsInfo;
  87 
  88     protected Paths(Context context) {
  89         context.put(pathsKey, this);
  90         pathsForLocation = new HashMap<Location,Path>(16);
  91         setContext(context);
  92     }
  93 
  94     void setContext(Context context) {
  95         log = Log.instance(context);
  96         options = Options.instance(context);
  97         lint = Lint.instance(context);
  98         fsInfo = FSInfo.instance(context);
  99     }
 100 
 101     /** Whether to warn about non-existent path elements */
 102     private boolean warn;
 103 
 104     private Map<Location, Path> pathsForLocation;
 105 
 106     private boolean inited = false; // TODO? caching bad?
 107 
 108     /**
 109      * rt.jar as found on the default bootclass path.  If the user specified a
 110      * bootclasspath, null is used.
 111      */
 112     private File bootClassPathRtJar = null;
 113 
 114     Path getPathForLocation(Location location) {
 115         Path path = pathsForLocation.get(location);
 116         if (path == null)
 117             setPathForLocation(location, null);
 118         return pathsForLocation.get(location);
 119     }
 120 
 121     void setPathForLocation(Location location, Iterable<? extends File> path) {
 122         // TODO? if (inited) throw new IllegalStateException
 123         // TODO: otherwise reset sourceSearchPath, classSearchPath as needed
 124         Path p;
 125         if (path == null) {
 126             if (location == CLASS_PATH)
 127                 p = computeUserClassPath();
 128             else if (location == PLATFORM_CLASS_PATH)
 129                 p = computeBootClassPath();
 130             else if (location == ANNOTATION_PROCESSOR_PATH)
 131                 p = computeAnnotationProcessorPath();
 132             else if (location == SOURCE_PATH)
 133                 p = computeSourcePath();
 134             else
 135                 // no defaults for other paths
 136                 p = null;
 137         } else {
 138             p = new Path();
 139             for (File f: path)
 140                 p.addFile(f, warn); // TODO: is use of warn appropriate?
 141         }
 142         pathsForLocation.put(location, p);
 143     }
 144 
 145     protected void lazy() {
 146         if (!inited) {
 147             warn = lint.isEnabled(Lint.LintCategory.PATH);
 148 
 149             pathsForLocation.put(PLATFORM_CLASS_PATH, computeBootClassPath());
 150             pathsForLocation.put(CLASS_PATH, computeUserClassPath());
 151             pathsForLocation.put(SOURCE_PATH, computeSourcePath());
 152 
 153             inited = true;
 154         }
 155     }
 156 
 157     public Collection<File> bootClassPath() {
 158         lazy();
 159         return Collections.unmodifiableCollection(getPathForLocation(PLATFORM_CLASS_PATH));
 160     }
 161     public Collection<File> userClassPath() {
 162         lazy();
 163         return Collections.unmodifiableCollection(getPathForLocation(CLASS_PATH));
 164     }
 165     public Collection<File> sourcePath() {
 166         lazy();
 167         Path p = getPathForLocation(SOURCE_PATH);
 168         return p == null || p.size() == 0
 169             ? null
 170             : Collections.unmodifiableCollection(p);
 171     }
 172 
 173     boolean isBootClassPathRtJar(File file) {
 174         return file.equals(bootClassPathRtJar);
 175     }
 176 
 177     /**
 178      * Split a path into its elements. Empty path elements will be ignored.
 179      * @param path The path to be split
 180      * @return The elements of the path
 181      */
 182     private static Iterable<File> getPathEntries(String path) {
 183         return getPathEntries(path, null);
 184     }
 185 
 186     /**
 187      * Split a path into its elements. If emptyPathDefault is not null, all
 188      * empty elements in the path, including empty elements at either end of
 189      * the path, will be replaced with the value of emptyPathDefault.
 190      * @param path The path to be split
 191      * @param emptyPathDefault The value to substitute for empty path elements,
 192      *  or null, to ignore empty path elements
 193      * @return The elements of the path
 194      */
 195     private static Iterable<File> getPathEntries(String path, File emptyPathDefault) {
 196         ListBuffer<File> entries = new ListBuffer<File>();
 197         int start = 0;
 198         while (start <= path.length()) {
 199             int sep = path.indexOf(File.pathSeparatorChar, start);
 200             if (sep == -1)
 201                 sep = path.length();
 202             if (start < sep)
 203                 entries.add(new File(path.substring(start, sep)));
 204             else if (emptyPathDefault != null)
 205                 entries.add(emptyPathDefault);
 206             start = sep + 1;
 207         }
 208         return entries;
 209     }
 210 
 211     private class Path extends LinkedHashSet<File> {
 212         private static final long serialVersionUID = 0;
 213 
 214         private boolean expandJarClassPaths = false;
 215         private Set<File> canonicalValues = new HashSet<File>();
 216 
 217         public Path expandJarClassPaths(boolean x) {
 218             expandJarClassPaths = x;
 219             return this;
 220         }
 221 
 222         /** What to use when path element is the empty string */
 223         private File emptyPathDefault = null;
 224 
 225         public Path emptyPathDefault(File x) {
 226             emptyPathDefault = x;
 227             return this;
 228         }
 229 
 230         public Path() { super(); }
 231 
 232         public Path addDirectories(String dirs, boolean warn) {
 233             if (dirs != null)
 234                 for (File dir : getPathEntries(dirs))
 235                     addDirectory(dir, warn);
 236             return this;
 237         }
 238 
 239         public Path addDirectories(String dirs) {
 240             return addDirectories(dirs, warn);
 241         }
 242 
 243         private void addDirectory(File dir, boolean warn) {
 244             if (!dir.isDirectory()) {
 245                 if (warn)
 246                     log.warning("dir.path.element.not.found", dir);
 247                 return;
 248             }
 249 
 250             File[] files = dir.listFiles();
 251             if (files == null)
 252                 return;
 253 
 254             for (File direntry : files) {
 255                 if (isArchive(direntry))
 256                     addFile(direntry, warn);
 257             }
 258         }
 259 
 260         public Path addFiles(String files, boolean warn) {
 261             if (files != null)
 262                 for (File file : getPathEntries(files, emptyPathDefault))
 263                     addFile(file, warn);
 264             return this;
 265         }
 266 
 267         public Path addFiles(String files) {
 268             return addFiles(files, warn);
 269         }
 270 
 271         public void addFile(File file, boolean warn) {
 272             File canonFile = fsInfo.getCanonicalFile(file);
 273             if (contains(file) || canonicalValues.contains(canonFile)) {
 274                 /* Discard duplicates and avoid infinite recursion */
 275                 return;
 276             }
 277 
 278             if (! fsInfo.exists(file)) {
 279                 /* No such file or directory exists */
 280                 if (warn)
 281                     log.warning("path.element.not.found", file);
 282             } else if (fsInfo.isFile(file)) {
 283                 /* File is an ordinary file. */
 284                 if (!isArchive(file)) {
 285                     /* Not a recognized extension; open it to see if
 286                      it looks like a valid zip file. */
 287                     try {
 288                         ZipFile z = new ZipFile(file);
 289                         z.close();
 290                         if (warn)
 291                             log.warning("unexpected.archive.file", file);
 292                     } catch (IOException e) {
 293                         // FIXME: include e.getLocalizedMessage in warning
 294                         if (warn)
 295                             log.warning("invalid.archive.file", file);
 296                         return;
 297                     }
 298                 }
 299             }
 300 
 301             /* Now what we have left is either a directory or a file name
 302                confirming to archive naming convention */
 303             super.add(file);
 304             canonicalValues.add(canonFile);
 305 
 306             if (expandJarClassPaths && fsInfo.exists(file) && fsInfo.isFile(file))
 307                 addJarClassPath(file, warn);
 308         }
 309 
 310         // Adds referenced classpath elements from a jar's Class-Path
 311         // Manifest entry.  In some future release, we may want to
 312         // update this code to recognize URLs rather than simple
 313         // filenames, but if we do, we should redo all path-related code.
 314         private void addJarClassPath(File jarFile, boolean warn) {
 315             try {
 316                 for (File f: fsInfo.getJarClassPath(jarFile)) {
 317                     addFile(f, warn);
 318                 }
 319             } catch (IOException e) {
 320                 log.error("error.reading.file", jarFile, e.getLocalizedMessage());
 321             }
 322         }
 323     }
 324 
 325     private Path computeBootClassPath() {
 326         bootClassPathRtJar = null;
 327         String optionValue;
 328         Path path = new Path();
 329 
 330         path.addFiles(options.get(XBOOTCLASSPATH_PREPEND));
 331 
 332         if ((optionValue = options.get(ENDORSEDDIRS)) != null)
 333             path.addDirectories(optionValue);
 334         else
 335             path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
 336 
 337         if ((optionValue = options.get(BOOTCLASSPATH)) != null) {
 338             path.addFiles(optionValue);
 339         } else {
 340             // Standard system classes for this compiler's release.
 341             String files = System.getProperty("sun.boot.class.path");
 342             path.addFiles(files, false);
 343             File rt_jar = new File("rt.jar");
 344             for (File file : getPathEntries(files)) {
 345                 if (new File(file.getName()).equals(rt_jar))
 346                     bootClassPathRtJar = file;
 347             }
 348         }
 349 
 350         path.addFiles(options.get(XBOOTCLASSPATH_APPEND));
 351 
 352         // Strictly speaking, standard extensions are not bootstrap
 353         // classes, but we treat them identically, so we'll pretend
 354         // that they are.
 355         if ((optionValue = options.get(EXTDIRS)) != null)
 356             path.addDirectories(optionValue);
 357         else
 358             path.addDirectories(System.getProperty("java.ext.dirs"), false);
 359 
 360         return path;
 361     }
 362 
 363     private Path computeUserClassPath() {
 364         String cp = options.get(CLASSPATH);
 365 
 366         // CLASSPATH environment variable when run from `javac'.
 367         if (cp == null) cp = System.getProperty("env.class.path");
 368 
 369         // If invoked via a java VM (not the javac launcher), use the
 370         // platform class path
 371         if (cp == null && System.getProperty("application.home") == null)
 372             cp = System.getProperty("java.class.path");
 373 
 374         // Default to current working directory.
 375         if (cp == null) cp = ".";
 376 
 377         return new Path()
 378             .expandJarClassPaths(true)        // Only search user jars for Class-Paths
 379             .emptyPathDefault(new File("."))  // Empty path elt ==> current directory
 380             .addFiles(cp);
 381     }
 382 
 383     private Path computeSourcePath() {
 384         String sourcePathArg = options.get(SOURCEPATH);
 385         if (sourcePathArg == null)
 386             return null;
 387 
 388         return new Path().addFiles(sourcePathArg);
 389     }
 390 
 391     private Path computeAnnotationProcessorPath() {
 392         String processorPathArg = options.get(PROCESSORPATH);
 393         if (processorPathArg == null)
 394             return null;
 395 
 396         return new Path().addFiles(processorPathArg);
 397     }
 398 
 399     /** The actual effective locations searched for sources */
 400     private Path sourceSearchPath;
 401 
 402     public Collection<File> sourceSearchPath() {
 403         if (sourceSearchPath == null) {
 404             lazy();
 405             Path sourcePath = getPathForLocation(SOURCE_PATH);
 406             Path userClassPath = getPathForLocation(CLASS_PATH);
 407             sourceSearchPath = sourcePath != null ? sourcePath : userClassPath;
 408         }
 409         return Collections.unmodifiableCollection(sourceSearchPath);
 410     }
 411 
 412     /** The actual effective locations searched for classes */
 413     private Path classSearchPath;
 414 
 415     public Collection<File> classSearchPath() {
 416         if (classSearchPath == null) {
 417             lazy();
 418             Path bootClassPath = getPathForLocation(PLATFORM_CLASS_PATH);
 419             Path userClassPath = getPathForLocation(CLASS_PATH);
 420             classSearchPath = new Path();
 421             classSearchPath.addAll(bootClassPath);
 422             classSearchPath.addAll(userClassPath);
 423         }
 424         return Collections.unmodifiableCollection(classSearchPath);
 425     }
 426 
 427     /** The actual effective locations for non-source, non-class files */
 428     private Path otherSearchPath;
 429 
 430     Collection<File> otherSearchPath() {
 431         if (otherSearchPath == null) {
 432             lazy();
 433             Path userClassPath = getPathForLocation(CLASS_PATH);
 434             Path sourcePath = getPathForLocation(SOURCE_PATH);
 435             if (sourcePath == null)
 436                 otherSearchPath = userClassPath;
 437             else {
 438                 otherSearchPath = new Path();
 439                 otherSearchPath.addAll(userClassPath);
 440                 otherSearchPath.addAll(sourcePath);
 441             }
 442         }
 443         return Collections.unmodifiableCollection(otherSearchPath);
 444     }
 445 
 446     /** Is this the name of an archive file? */
 447     private boolean isArchive(File file) {
 448         String n = file.getName().toLowerCase();
 449         return fsInfo.isFile(file)
 450             && (n.endsWith(".jar") || n.endsWith(".zip"));
 451     }
 452 }