001 /*
002 * Copyright 2003-2008 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package com.sun.tools.javac.file;
027
028 import java.io.File;
029 import java.io.IOException;
030 import java.util.HashMap;
031 import java.util.HashSet;
032 import java.util.Map;
033 import java.util.Set;
034 import java.util.Collection;
035 import java.util.Collections;
036 import java.util.LinkedHashSet;
037 import java.util.zip.ZipFile;
038 import javax.tools.JavaFileManager.Location;
039
040 import com.sun.tools.javac.code.Lint;
041 import com.sun.tools.javac.util.Context;
042 import com.sun.tools.javac.util.ListBuffer;
043 import com.sun.tools.javac.util.Log;
044 import com.sun.tools.javac.util.Options;
045
046 import static javax.tools.StandardLocation.*;
047 import static com.sun.tools.javac.main.OptionName.*;
048
049 /** This class converts command line arguments, environment variables
050 * and system properties (in File.pathSeparator-separated String form)
051 * into a boot class path, user class path, and source path (in
052 * Collection<String> form).
053 *
054 * <p><b>This is NOT part of any API supported by Sun Microsystems. If
055 * you write code that depends on this, you do so at your own risk.
056 * This code and its internal interfaces are subject to change or
057 * deletion without notice.</b>
058 */
059 public class Paths {
060
061 /** The context key for the todo list */
062 protected static final Context.Key<Paths> pathsKey =
063 new Context.Key<Paths>();
064
065 /** Get the Paths instance for this context.
066 * @param context the context
067 * @return the Paths instance for this context
068 */
069 public static Paths instance(Context context) {
070 Paths instance = context.get(pathsKey);
071 if (instance == null)
072 instance = new Paths(context);
073 return instance;
074 }
075
076 /** The log to use for warning output */
077 private Log log;
078
079 /** Collection of command-line options */
080 private Options options;
081
082 /** Handler for -Xlint options */
083 private Lint lint;
084
085 /** Access to (possibly cached) file info */
086 private FSInfo fsInfo;
087
088 protected Paths(Context context) {
089 context.put(pathsKey, this);
090 pathsForLocation = new HashMap<Location,Path>(16);
091 setContext(context);
092 }
093
094 void setContext(Context context) {
095 log = Log.instance(context);
096 options = Options.instance(context);
097 lint = Lint.instance(context);
098 fsInfo = FSInfo.instance(context);
099 }
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 }