Fri, 31 Aug 2012 10:37:46 +0100
7151010: Add compiler support for repeating annotations
Reviewed-by: jjg, mcimadamore
1 /*
2 * Copyright (c) 2003, 2012, Oracle and/or its affiliates. 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package com.sun.tools.javac.file;
28 import java.io.FileNotFoundException;
29 import java.util.Iterator;
30 import java.io.File;
31 import java.io.IOException;
32 import java.net.MalformedURLException;
33 import java.net.URL;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.EnumMap;
38 import java.util.EnumSet;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.LinkedHashSet;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.StringTokenizer;
45 import java.util.zip.ZipFile;
46 import javax.tools.JavaFileManager.Location;
47 import javax.tools.StandardLocation;
49 import com.sun.tools.javac.code.Lint;
50 import com.sun.tools.javac.main.Option;
51 import com.sun.tools.javac.util.ListBuffer;
52 import com.sun.tools.javac.util.Log;
53 import com.sun.tools.javac.util.Options;
55 import javax.tools.JavaFileManager;
56 import static javax.tools.StandardLocation.*;
57 import static com.sun.tools.javac.main.Option.*;
59 /** This class converts command line arguments, environment variables
60 * and system properties (in File.pathSeparator-separated String form)
61 * into a boot class path, user class path, and source path (in
62 * Collection<String> form).
63 *
64 * <p><b>This is NOT part of any supported API.
65 * If you write code that depends on this, you do so at your own risk.
66 * This code and its internal interfaces are subject to change or
67 * deletion without notice.</b>
68 */
69 public class Locations {
71 /** The log to use for warning output */
72 private Log log;
74 /** Collection of command-line options */
75 private Options options;
77 /** Handler for -Xlint options */
78 private Lint lint;
80 /** Access to (possibly cached) file info */
81 private FSInfo fsInfo;
83 /** Whether to warn about non-existent path elements */
84 private boolean warn;
86 // TODO: remove need for this
87 private boolean inited = false; // TODO? caching bad?
89 public Locations() {
90 initHandlers();
91 }
93 public void update(Log log, Options options, Lint lint, FSInfo fsInfo) {
94 this.log = log;
95 this.options = options;
96 this.lint = lint;
97 this.fsInfo = fsInfo;
98 }
100 public Collection<File> bootClassPath() {
101 return getLocation(PLATFORM_CLASS_PATH);
102 }
104 public boolean isDefaultBootClassPath() {
105 BootClassPathLocationHandler h =
106 (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH);
107 return h.isDefault();
108 }
110 boolean isDefaultBootClassPathRtJar(File file) {
111 BootClassPathLocationHandler h =
112 (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH);
113 return h.isDefaultRtJar(file);
114 }
116 public Collection<File> userClassPath() {
117 return getLocation(CLASS_PATH);
118 }
120 public Collection<File> sourcePath() {
121 Collection<File> p = getLocation(SOURCE_PATH);
122 // TODO: this should be handled by the LocationHandler
123 return p == null || p.isEmpty() ? null : p;
124 }
126 /**
127 * Split a path into its elements. Empty path elements will be ignored.
128 * @param path The path to be split
129 * @return The elements of the path
130 */
131 private static Iterable<File> getPathEntries(String path) {
132 return getPathEntries(path, null);
133 }
135 /**
136 * Split a path into its elements. If emptyPathDefault is not null, all
137 * empty elements in the path, including empty elements at either end of
138 * the path, will be replaced with the value of emptyPathDefault.
139 * @param path The path to be split
140 * @param emptyPathDefault The value to substitute for empty path elements,
141 * or null, to ignore empty path elements
142 * @return The elements of the path
143 */
144 private static Iterable<File> getPathEntries(String path, File emptyPathDefault) {
145 ListBuffer<File> entries = new ListBuffer<File>();
146 int start = 0;
147 while (start <= path.length()) {
148 int sep = path.indexOf(File.pathSeparatorChar, start);
149 if (sep == -1)
150 sep = path.length();
151 if (start < sep)
152 entries.add(new File(path.substring(start, sep)));
153 else if (emptyPathDefault != null)
154 entries.add(emptyPathDefault);
155 start = sep + 1;
156 }
157 return entries;
158 }
160 /**
161 * Utility class to help evaluate a path option.
162 * Duplicate entries are ignored, jar class paths can be expanded.
163 */
164 private class Path extends LinkedHashSet<File> {
165 private static final long serialVersionUID = 0;
167 private boolean expandJarClassPaths = false;
168 private Set<File> canonicalValues = new HashSet<File>();
170 public Path expandJarClassPaths(boolean x) {
171 expandJarClassPaths = x;
172 return this;
173 }
175 /** What to use when path element is the empty string */
176 private File emptyPathDefault = null;
178 public Path emptyPathDefault(File x) {
179 emptyPathDefault = x;
180 return this;
181 }
183 public Path() { super(); }
185 public Path addDirectories(String dirs, boolean warn) {
186 boolean prev = expandJarClassPaths;
187 expandJarClassPaths = true;
188 try {
189 if (dirs != null)
190 for (File dir : getPathEntries(dirs))
191 addDirectory(dir, warn);
192 return this;
193 } finally {
194 expandJarClassPaths = prev;
195 }
196 }
198 public Path addDirectories(String dirs) {
199 return addDirectories(dirs, warn);
200 }
202 private void addDirectory(File dir, boolean warn) {
203 if (!dir.isDirectory()) {
204 if (warn)
205 log.warning(Lint.LintCategory.PATH,
206 "dir.path.element.not.found", dir);
207 return;
208 }
210 File[] files = dir.listFiles();
211 if (files == null)
212 return;
214 for (File direntry : files) {
215 if (isArchive(direntry))
216 addFile(direntry, warn);
217 }
218 }
220 public Path addFiles(String files, boolean warn) {
221 if (files != null) {
222 addFiles(getPathEntries(files, emptyPathDefault), warn);
223 }
224 return this;
225 }
227 public Path addFiles(String files) {
228 return addFiles(files, warn);
229 }
231 public Path addFiles(Iterable<? extends File> files, boolean warn) {
232 if (files != null) {
233 for (File file: files)
234 addFile(file, warn);
235 }
236 return this;
237 }
239 public Path addFiles(Iterable<? extends File> files) {
240 return addFiles(files, warn);
241 }
243 public void addFile(File file, boolean warn) {
244 if (contains(file)) {
245 // discard duplicates
246 return;
247 }
249 if (! fsInfo.exists(file)) {
250 /* No such file or directory exists */
251 if (warn) {
252 log.warning(Lint.LintCategory.PATH,
253 "path.element.not.found", file);
254 }
255 super.add(file);
256 return;
257 }
259 File canonFile = fsInfo.getCanonicalFile(file);
260 if (canonicalValues.contains(canonFile)) {
261 /* Discard duplicates and avoid infinite recursion */
262 return;
263 }
265 if (fsInfo.isFile(file)) {
266 /* File is an ordinary file. */
267 if (!isArchive(file)) {
268 /* Not a recognized extension; open it to see if
269 it looks like a valid zip file. */
270 try {
271 ZipFile z = new ZipFile(file);
272 z.close();
273 if (warn) {
274 log.warning(Lint.LintCategory.PATH,
275 "unexpected.archive.file", file);
276 }
277 } catch (IOException e) {
278 // FIXME: include e.getLocalizedMessage in warning
279 if (warn) {
280 log.warning(Lint.LintCategory.PATH,
281 "invalid.archive.file", file);
282 }
283 return;
284 }
285 }
286 }
288 /* Now what we have left is either a directory or a file name
289 conforming to archive naming convention */
290 super.add(file);
291 canonicalValues.add(canonFile);
293 if (expandJarClassPaths && fsInfo.isFile(file))
294 addJarClassPath(file, warn);
295 }
297 // Adds referenced classpath elements from a jar's Class-Path
298 // Manifest entry. In some future release, we may want to
299 // update this code to recognize URLs rather than simple
300 // filenames, but if we do, we should redo all path-related code.
301 private void addJarClassPath(File jarFile, boolean warn) {
302 try {
303 for (File f: fsInfo.getJarClassPath(jarFile)) {
304 addFile(f, warn);
305 }
306 } catch (IOException e) {
307 log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e));
308 }
309 }
310 }
312 /**
313 * Base class for handling support for the representation of Locations.
314 * Implementations are responsible for handling the interactions between
315 * the command line options for a location, and API access via setLocation.
316 * @see #initHandlers
317 * @see #getHandler
318 */
319 protected abstract class LocationHandler {
320 final Location location;
321 final Set<Option> options;
323 /**
324 * Create a handler. The location and options provide a way to map
325 * from a location or an option to the corresponding handler.
326 * @see #initHandlers
327 */
328 protected LocationHandler(Location location, Option... options) {
329 this.location = location;
330 this.options = options.length == 0 ?
331 EnumSet.noneOf(Option.class):
332 EnumSet.copyOf(Arrays.asList(options));
333 }
335 // TODO: TEMPORARY, while Options still used for command line options
336 void update(Options optionTable) {
337 for (Option o: options) {
338 String v = optionTable.get(o);
339 if (v != null) {
340 handleOption(o, v);
341 }
342 }
343 }
345 /** @see JavaFileManager#handleOption. */
346 abstract boolean handleOption(Option option, String value);
347 /** @see JavaFileManager#getLocation. */
348 abstract Collection<File> getLocation();
349 /** @see JavaFileManager#setLocation. */
350 abstract void setLocation(Iterable<? extends File> files) throws IOException;
351 }
353 /**
354 * General purpose implementation for output locations,
355 * such as -d/CLASS_OUTPUT and -s/SOURCE_OUTPUT.
356 * All options are treated as equivalent (i.e. aliases.)
357 * The value is a single file, possibly null.
358 */
359 private class OutputLocationHandler extends LocationHandler {
360 private File outputDir;
362 OutputLocationHandler(Location location, Option... options) {
363 super(location, options);
364 }
366 @Override
367 boolean handleOption(Option option, String value) {
368 if (!options.contains(option))
369 return false;
371 // TODO: could/should validate outputDir exists and is a directory
372 // need to decide how best to report issue for benefit of
373 // direct API call on JavaFileManager.handleOption(specifies IAE)
374 // vs. command line decoding.
375 outputDir = new File(value);
376 return true;
377 }
379 @Override
380 Collection<File> getLocation() {
381 return (outputDir == null) ? null : Collections.singleton(outputDir);
382 }
384 @Override
385 void setLocation(Iterable<? extends File> files) throws IOException {
386 if (files == null) {
387 outputDir = null;
388 } else {
389 Iterator<? extends File> pathIter = files.iterator();
390 if (!pathIter.hasNext())
391 throw new IllegalArgumentException("empty path for directory");
392 File dir = pathIter.next();
393 if (pathIter.hasNext())
394 throw new IllegalArgumentException("path too long for directory");
395 if (!dir.exists())
396 throw new FileNotFoundException(dir + ": does not exist");
397 else if (!dir.isDirectory())
398 throw new IOException(dir + ": not a directory");
399 outputDir = dir;
400 }
401 }
402 }
404 /**
405 * General purpose implementation for search path locations,
406 * such as -sourcepath/SOURCE_PATH and -processorPath/ANNOTATION_PROCESS_PATH.
407 * All options are treated as equivalent (i.e. aliases.)
408 * The value is an ordered set of files and/or directories.
409 */
410 private class SimpleLocationHandler extends LocationHandler {
411 protected Collection<File> searchPath;
413 SimpleLocationHandler(Location location, Option... options) {
414 super(location, options);
415 }
417 @Override
418 boolean handleOption(Option option, String value) {
419 if (!options.contains(option))
420 return false;
421 searchPath = value == null ? null :
422 Collections.unmodifiableCollection(computePath(value));
423 return true;
424 }
426 protected Path computePath(String value) {
427 return new Path().addFiles(value);
428 }
430 @Override
431 Collection<File> getLocation() {
432 return searchPath;
433 }
435 @Override
436 void setLocation(Iterable<? extends File> files) {
437 Path p;
438 if (files == null) {
439 p = computePath(null);
440 } else {
441 p = new Path().addFiles(files);
442 }
443 searchPath = Collections.unmodifiableCollection(p);
444 }
445 }
447 /**
448 * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH.
449 * If no value is given, a default is provided, based on system properties
450 * and other values.
451 */
452 private class ClassPathLocationHandler extends SimpleLocationHandler {
453 ClassPathLocationHandler() {
454 super(StandardLocation.CLASS_PATH,
455 Option.CLASSPATH, Option.CP);
456 }
458 @Override
459 Collection<File> getLocation() {
460 lazy();
461 return searchPath;
462 }
464 @Override
465 protected Path computePath(String value) {
466 String cp = value;
468 // CLASSPATH environment variable when run from `javac'.
469 if (cp == null) cp = System.getProperty("env.class.path");
471 // If invoked via a java VM (not the javac launcher), use the
472 // platform class path
473 if (cp == null && System.getProperty("application.home") == null)
474 cp = System.getProperty("java.class.path");
476 // Default to current working directory.
477 if (cp == null) cp = ".";
479 return new Path()
480 .expandJarClassPaths(true) // Only search user jars for Class-Paths
481 .emptyPathDefault(new File(".")) // Empty path elt ==> current directory
482 .addFiles(cp);
483 }
485 private void lazy() {
486 if (searchPath == null)
487 setLocation(null);
488 }
489 }
491 /**
492 * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH.
493 * Various options are supported for different components of the
494 * platform class path.
495 * Setting a value with setLocation overrides all existing option values.
496 * Setting any option overrides any value set with setLocation, and reverts
497 * to using default values for options that have not been set.
498 * Setting -bootclasspath or -Xbootclasspath overrides any existing
499 * value for -Xbootclasspath/p: and -Xbootclasspath/a:.
500 */
501 private class BootClassPathLocationHandler extends LocationHandler {
502 private Collection<File> searchPath;
503 final Map<Option, String> optionValues = new EnumMap<Option,String>(Option.class);
505 /**
506 * rt.jar as found on the default bootclasspath.
507 * If the user specified a bootclasspath, null is used.
508 */
509 private File defaultBootClassPathRtJar = null;
511 /**
512 * Is bootclasspath the default?
513 */
514 private boolean isDefaultBootClassPath;
516 BootClassPathLocationHandler() {
517 super(StandardLocation.PLATFORM_CLASS_PATH,
518 Option.BOOTCLASSPATH, Option.XBOOTCLASSPATH,
519 Option.XBOOTCLASSPATH_PREPEND,
520 Option.XBOOTCLASSPATH_APPEND,
521 Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
522 Option.EXTDIRS, Option.DJAVA_EXT_DIRS);
523 }
525 boolean isDefault() {
526 lazy();
527 return isDefaultBootClassPath;
528 }
530 boolean isDefaultRtJar(File file) {
531 lazy();
532 return file.equals(defaultBootClassPathRtJar);
533 }
535 @Override
536 boolean handleOption(Option option, String value) {
537 if (!options.contains(option))
538 return false;
540 option = canonicalize(option);
541 optionValues.put(option, value);
542 if (option == BOOTCLASSPATH) {
543 optionValues.remove(XBOOTCLASSPATH_PREPEND);
544 optionValues.remove(XBOOTCLASSPATH_APPEND);
545 }
546 searchPath = null; // reset to "uninitialized"
547 return true;
548 }
549 // where
550 // TODO: would be better if option aliasing was handled at a higher
551 // level
552 private Option canonicalize(Option option) {
553 switch (option) {
554 case XBOOTCLASSPATH:
555 return Option.BOOTCLASSPATH;
556 case DJAVA_ENDORSED_DIRS:
557 return Option.ENDORSEDDIRS;
558 case DJAVA_EXT_DIRS:
559 return Option.EXTDIRS;
560 default:
561 return option;
562 }
563 }
565 @Override
566 Collection<File> getLocation() {
567 lazy();
568 return searchPath;
569 }
571 @Override
572 void setLocation(Iterable<? extends File> files) {
573 if (files == null) {
574 searchPath = null; // reset to "uninitialized"
575 } else {
576 defaultBootClassPathRtJar = null;
577 isDefaultBootClassPath = false;
578 Path p = new Path().addFiles(files, false);
579 searchPath = Collections.unmodifiableCollection(p);
580 optionValues.clear();
581 }
582 }
584 Path computePath() {
585 defaultBootClassPathRtJar = null;
586 Path path = new Path();
588 String bootclasspathOpt = optionValues.get(BOOTCLASSPATH);
589 String endorseddirsOpt = optionValues.get(ENDORSEDDIRS);
590 String extdirsOpt = optionValues.get(EXTDIRS);
591 String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND);
592 String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND);
594 path.addFiles(xbootclasspathPrependOpt);
596 if (endorseddirsOpt != null)
597 path.addDirectories(endorseddirsOpt);
598 else
599 path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
601 if (bootclasspathOpt != null) {
602 path.addFiles(bootclasspathOpt);
603 } else {
604 // Standard system classes for this compiler's release.
605 String files = System.getProperty("sun.boot.class.path");
606 path.addFiles(files, false);
607 File rt_jar = new File("rt.jar");
608 for (File file : getPathEntries(files)) {
609 if (new File(file.getName()).equals(rt_jar))
610 defaultBootClassPathRtJar = file;
611 }
612 }
614 path.addFiles(xbootclasspathAppendOpt);
616 // Strictly speaking, standard extensions are not bootstrap
617 // classes, but we treat them identically, so we'll pretend
618 // that they are.
619 if (extdirsOpt != null)
620 path.addDirectories(extdirsOpt);
621 else
622 path.addDirectories(System.getProperty("java.ext.dirs"), false);
624 isDefaultBootClassPath =
625 (xbootclasspathPrependOpt == null) &&
626 (bootclasspathOpt == null) &&
627 (xbootclasspathAppendOpt == null);
629 return path;
630 }
632 private void lazy() {
633 if (searchPath == null)
634 searchPath = Collections.unmodifiableCollection(computePath());
635 }
636 }
638 Map<Location, LocationHandler> handlersForLocation;
639 Map<Option, LocationHandler> handlersForOption;
641 void initHandlers() {
642 handlersForLocation = new HashMap<Location, LocationHandler>();
643 handlersForOption = new EnumMap<Option, LocationHandler>(Option.class);
645 LocationHandler[] handlers = {
646 new BootClassPathLocationHandler(),
647 new ClassPathLocationHandler(),
648 new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCEPATH),
649 new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSORPATH),
650 new OutputLocationHandler((StandardLocation.CLASS_OUTPUT), Option.D),
651 new OutputLocationHandler((StandardLocation.SOURCE_OUTPUT), Option.S),
652 new OutputLocationHandler((StandardLocation.NATIVE_HEADER_OUTPUT), Option.H)
653 };
655 for (LocationHandler h: handlers) {
656 handlersForLocation.put(h.location, h);
657 for (Option o: h.options)
658 handlersForOption.put(o, h);
659 }
660 }
662 boolean handleOption(Option option, String value) {
663 LocationHandler h = handlersForOption.get(option);
664 return (h == null ? false : h.handleOption(option, value));
665 }
667 Collection<File> getLocation(Location location) {
668 LocationHandler h = getHandler(location);
669 return (h == null ? null : h.getLocation());
670 }
672 File getOutputLocation(Location location) {
673 if (!location.isOutputLocation())
674 throw new IllegalArgumentException();
675 LocationHandler h = getHandler(location);
676 return ((OutputLocationHandler) h).outputDir;
677 }
679 void setLocation(Location location, Iterable<? extends File> files) throws IOException {
680 LocationHandler h = getHandler(location);
681 if (h == null) {
682 if (location.isOutputLocation())
683 h = new OutputLocationHandler(location);
684 else
685 h = new SimpleLocationHandler(location);
686 handlersForLocation.put(location, h);
687 }
688 h.setLocation(files);
689 }
691 protected LocationHandler getHandler(Location location) {
692 location.getClass(); // null check
693 lazy();
694 return handlersForLocation.get(location);
695 }
697 // TOGO
698 protected void lazy() {
699 if (!inited) {
700 warn = lint.isEnabled(Lint.LintCategory.PATH);
702 for (LocationHandler h: handlersForLocation.values()) {
703 h.update(options);
704 }
706 inited = true;
707 }
708 }
710 /** Is this the name of an archive file? */
711 private boolean isArchive(File file) {
712 String n = file.getName().toLowerCase();
713 return fsInfo.isFile(file)
714 && (n.endsWith(".jar") || n.endsWith(".zip"));
715 }
717 /**
718 * Utility method for converting a search path string to an array
719 * of directory and JAR file URLs.
720 *
721 * Note that this method is called by apt and the DocletInvoker.
722 *
723 * @param path the search path string
724 * @return the resulting array of directory and JAR file URLs
725 */
726 public static URL[] pathToURLs(String path) {
727 StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
728 URL[] urls = new URL[st.countTokens()];
729 int count = 0;
730 while (st.hasMoreTokens()) {
731 URL url = fileToURL(new File(st.nextToken()));
732 if (url != null) {
733 urls[count++] = url;
734 }
735 }
736 if (urls.length != count) {
737 URL[] tmp = new URL[count];
738 System.arraycopy(urls, 0, tmp, 0, count);
739 urls = tmp;
740 }
741 return urls;
742 }
744 /**
745 * Returns the directory or JAR file URL corresponding to the specified
746 * local file name.
747 *
748 * @param file the File object
749 * @return the resulting directory or JAR file URL, or null if unknown
750 */
751 private static URL fileToURL(File file) {
752 String name;
753 try {
754 name = file.getCanonicalPath();
755 } catch (IOException e) {
756 name = file.getAbsolutePath();
757 }
758 name = name.replace(File.separatorChar, '/');
759 if (!name.startsWith("/")) {
760 name = "/" + name;
761 }
762 // If the file does not exist, then assume that it's a directory
763 if (!file.isFile()) {
764 name = name + "/";
765 }
766 try {
767 return new URL("file", "", name);
768 } catch (MalformedURLException e) {
769 throw new IllegalArgumentException(file.toString());
770 }
771 }
772 }