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