Locations.java revision 2675:4be0e35f385a
1132718Skan/* 2169689Skan * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved. 3117395Skan * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4117395Skan * 5117395Skan * This code is free software; you can redistribute it and/or modify it 6117395Skan * under the terms of the GNU General Public License version 2 only, as 7117395Skan * published by the Free Software Foundation. Oracle designates this 8117395Skan * particular file as subject to the "Classpath" exception as provided 9117395Skan * by Oracle in the LICENSE file that accompanied this code. 10117395Skan * 11117395Skan * This code is distributed in the hope that it will be useful, but WITHOUT 12117395Skan * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13117395Skan * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14117395Skan * version 2 for more details (a copy is included in the LICENSE file that 15117395Skan * accompanied this code). 16169689Skan * 17117395Skan * You should have received a copy of the GNU General Public License version 18132718Skan * 2 along with this work; if not, write to the Free Software Foundation, 19117395Skan * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20117395Skan * 21132718Skan * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22132718Skan * or visit www.oracle.com if you need additional information or have any 23132718Skan * questions. 24117395Skan */ 25117395Skanpackage com.sun.tools.javac.file; 26132718Skan 27117395Skanimport java.io.File; 28117395Skanimport java.io.FileNotFoundException; 29117395Skanimport java.io.IOException; 30117395Skanimport java.net.MalformedURLException; 31117395Skanimport java.net.URL; 32117395Skanimport java.util.Arrays; 33132718Skanimport java.util.Collection; 34117395Skanimport java.util.Collections; 35132718Skanimport java.util.EnumMap; 36117395Skanimport java.util.EnumSet; 37117395Skanimport java.util.HashMap; 38117395Skanimport java.util.HashSet; 39117395Skanimport java.util.Iterator; 40117395Skanimport java.util.LinkedHashSet; 41132718Skanimport java.util.Map; 42117395Skanimport java.util.Set; 43132718Skanimport java.util.StringTokenizer; 44132718Skanimport java.util.zip.ZipFile; 45117395Skan 46117395Skanimport javax.tools.JavaFileManager; 47117395Skanimport javax.tools.JavaFileManager.Location; 48117395Skanimport javax.tools.StandardJavaFileManager; 49117395Skanimport javax.tools.StandardLocation; 50117395Skan 51117395Skanimport com.sun.tools.javac.code.Lint; 52117395Skanimport com.sun.tools.javac.main.Option; 53117395Skanimport com.sun.tools.javac.util.ListBuffer; 54117395Skanimport com.sun.tools.javac.util.Log; 55117395Skanimport com.sun.tools.javac.util.StringUtils; 56117395Skan 57117395Skanimport static javax.tools.StandardLocation.CLASS_PATH; 58132718Skanimport static javax.tools.StandardLocation.PLATFORM_CLASS_PATH; 59132718Skanimport static javax.tools.StandardLocation.SOURCE_PATH; 60132718Skan 61132718Skanimport static com.sun.tools.javac.main.Option.BOOTCLASSPATH; 62117395Skanimport static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS; 63132718Skanimport static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS; 64117395Skanimport static com.sun.tools.javac.main.Option.ENDORSEDDIRS; 65132718Skanimport static com.sun.tools.javac.main.Option.EXTDIRS; 66132718Skanimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH; 67132718Skanimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND; 68132718Skanimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND; 69117395Skan 70169689Skan/** 71169689Skan * This class converts command line arguments, environment variables and system properties (in 72132718Skan * File.pathSeparator-separated String form) into a boot class path, user class path, and source 73117395Skan * path (in {@code Collection<String>} form). 74132718Skan * 75132718Skan * <p> 76169689Skan * <b>This is NOT part of any supported API. If you write code that depends on this, you do so at 77169689Skan * your own risk. This code and its internal interfaces are subject to change or deletion without 78132718Skan * notice.</b> 79132718Skan */ 80132718Skanpublic class Locations { 81132718Skan 82132718Skan /** 83117395Skan * The log to use for warning output 84117395Skan */ 85117395Skan private Log log; 86117395Skan 87117395Skan /** 88 * Access to (possibly cached) file info 89 */ 90 private FSInfo fsInfo; 91 92 /** 93 * Whether to warn about non-existent path elements 94 */ 95 private boolean warn; 96 97 public Locations() { 98 initHandlers(); 99 } 100 101 // could replace Lint by "boolean warn" 102 public void update(Log log, Lint lint, FSInfo fsInfo) { 103 this.log = log; 104 warn = lint.isEnabled(Lint.LintCategory.PATH); 105 this.fsInfo = fsInfo; 106 } 107 108 public Collection<File> bootClassPath() { 109 return getLocation(PLATFORM_CLASS_PATH); 110 } 111 112 public boolean isDefaultBootClassPath() { 113 BootClassPathLocationHandler h 114 = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH); 115 return h.isDefault(); 116 } 117 118 boolean isDefaultBootClassPathRtJar(File file) { 119 BootClassPathLocationHandler h 120 = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH); 121 return h.isDefaultRtJar(file); 122 } 123 124 public Collection<File> userClassPath() { 125 return getLocation(CLASS_PATH); 126 } 127 128 public Collection<File> sourcePath() { 129 Collection<File> p = getLocation(SOURCE_PATH); 130 // TODO: this should be handled by the LocationHandler 131 return p == null || p.isEmpty() ? null : p; 132 } 133 134 /** 135 * Split a path into its elements. Empty path elements will be ignored. 136 * 137 * @param path The path to be split 138 * @return The elements of the path 139 */ 140 private static Iterable<File> getPathEntries(String path) { 141 return getPathEntries(path, null); 142 } 143 144 /** 145 * Split a path into its elements. If emptyPathDefault is not null, all empty elements in the 146 * path, including empty elements at either end of the path, will be replaced with the value of 147 * emptyPathDefault. 148 * 149 * @param path The path to be split 150 * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore 151 * empty path elements 152 * @return The elements of the path 153 */ 154 private static Iterable<File> getPathEntries(String path, File emptyPathDefault) { 155 ListBuffer<File> entries = new ListBuffer<>(); 156 int start = 0; 157 while (start <= path.length()) { 158 int sep = path.indexOf(File.pathSeparatorChar, start); 159 if (sep == -1) { 160 sep = path.length(); 161 } 162 if (start < sep) { 163 entries.add(new File(path.substring(start, sep))); 164 } else if (emptyPathDefault != null) { 165 entries.add(emptyPathDefault); 166 } 167 start = sep + 1; 168 } 169 return entries; 170 } 171 172 /** 173 * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths 174 * can be expanded. 175 */ 176 private class SearchPath extends LinkedHashSet<File> { 177 178 private static final long serialVersionUID = 0; 179 180 private boolean expandJarClassPaths = false; 181 private final Set<File> canonicalValues = new HashSet<>(); 182 183 public SearchPath expandJarClassPaths(boolean x) { 184 expandJarClassPaths = x; 185 return this; 186 } 187 188 /** 189 * What to use when path element is the empty string 190 */ 191 private File emptyPathDefault = null; 192 193 public SearchPath emptyPathDefault(File x) { 194 emptyPathDefault = x; 195 return this; 196 } 197 198 public SearchPath addDirectories(String dirs, boolean warn) { 199 boolean prev = expandJarClassPaths; 200 expandJarClassPaths = true; 201 try { 202 if (dirs != null) { 203 for (File dir : getPathEntries(dirs)) { 204 addDirectory(dir, warn); 205 } 206 } 207 return this; 208 } finally { 209 expandJarClassPaths = prev; 210 } 211 } 212 213 public SearchPath addDirectories(String dirs) { 214 return addDirectories(dirs, warn); 215 } 216 217 private void addDirectory(File dir, boolean warn) { 218 if (!dir.isDirectory()) { 219 if (warn) { 220 log.warning(Lint.LintCategory.PATH, 221 "dir.path.element.not.found", dir); 222 } 223 return; 224 } 225 226 File[] files = dir.listFiles(); 227 if (files == null) { 228 return; 229 } 230 231 for (File direntry : files) { 232 if (isArchive(direntry)) { 233 addFile(direntry, warn); 234 } 235 } 236 } 237 238 public SearchPath addFiles(String files, boolean warn) { 239 if (files != null) { 240 addFiles(getPathEntries(files, emptyPathDefault), warn); 241 } 242 return this; 243 } 244 245 public SearchPath addFiles(String files) { 246 return addFiles(files, warn); 247 } 248 249 public SearchPath addFiles(Iterable<? extends File> files, boolean warn) { 250 if (files != null) { 251 for (File file : files) { 252 addFile(file, warn); 253 } 254 } 255 return this; 256 } 257 258 public SearchPath addFiles(Iterable<? extends File> files) { 259 return addFiles(files, warn); 260 } 261 262 public void addFile(File file, boolean warn) { 263 if (contains(file)) { 264 // discard duplicates 265 return; 266 } 267 268 if (!fsInfo.exists(file)) { 269 /* No such file or directory exists */ 270 if (warn) { 271 log.warning(Lint.LintCategory.PATH, 272 "path.element.not.found", file); 273 } 274 super.add(file); 275 return; 276 } 277 278 File canonFile = fsInfo.getCanonicalFile(file); 279 if (canonicalValues.contains(canonFile)) { 280 /* Discard duplicates and avoid infinite recursion */ 281 return; 282 } 283 284 if (fsInfo.isFile(file)) { 285 /* File is an ordinary file. */ 286 if (!isArchive(file)) { 287 /* Not a recognized extension; open it to see if 288 it looks like a valid zip file. */ 289 try { 290 ZipFile z = new ZipFile(file); 291 z.close(); 292 if (warn) { 293 log.warning(Lint.LintCategory.PATH, 294 "unexpected.archive.file", file); 295 } 296 } catch (IOException e) { 297 // FIXME: include e.getLocalizedMessage in warning 298 if (warn) { 299 log.warning(Lint.LintCategory.PATH, 300 "invalid.archive.file", file); 301 } 302 return; 303 } 304 } 305 } 306 307 /* Now what we have left is either a directory or a file name 308 conforming to archive naming convention */ 309 super.add(file); 310 canonicalValues.add(canonFile); 311 312 if (expandJarClassPaths && fsInfo.isFile(file)) { 313 addJarClassPath(file, warn); 314 } 315 } 316 317 // Adds referenced classpath elements from a jar's Class-Path 318 // Manifest entry. In some future release, we may want to 319 // update this code to recognize URLs rather than simple 320 // filenames, but if we do, we should redo all path-related code. 321 private void addJarClassPath(File jarFile, boolean warn) { 322 try { 323 for (File f : fsInfo.getJarClassPath(jarFile)) { 324 addFile(f, warn); 325 } 326 } catch (IOException e) { 327 log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e)); 328 } 329 } 330 } 331 332 /** 333 * Base class for handling support for the representation of Locations. Implementations are 334 * responsible for handling the interactions between the command line options for a location, 335 * and API access via setLocation. 336 * 337 * @see #initHandlers 338 * @see #getHandler 339 */ 340 protected abstract class LocationHandler { 341 342 final Location location; 343 final Set<Option> options; 344 345 /** 346 * Create a handler. The location and options provide a way to map from a location or an 347 * option to the corresponding handler. 348 * 349 * @param location the location for which this is the handler 350 * @param options the options affecting this location 351 * @see #initHandlers 352 */ 353 protected LocationHandler(Location location, Option... options) { 354 this.location = location; 355 this.options = options.length == 0 356 ? EnumSet.noneOf(Option.class) 357 : EnumSet.copyOf(Arrays.asList(options)); 358 } 359 360 /** 361 * @see JavaFileManager#handleOption 362 */ 363 abstract boolean handleOption(Option option, String value); 364 365 /** 366 * @see StandardJavaFileManager#getLocation 367 */ 368 abstract Collection<File> getLocation(); 369 370 /** 371 * @see StandardJavaFileManager#setLocation 372 */ 373 abstract void setLocation(Iterable<? extends File> files) throws IOException; 374 } 375 376 /** 377 * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and 378 * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.) The value is a single 379 * file, possibly null. 380 */ 381 private class OutputLocationHandler extends LocationHandler { 382 383 private File outputDir; 384 385 OutputLocationHandler(Location location, Option... options) { 386 super(location, options); 387 } 388 389 @Override 390 boolean handleOption(Option option, String value) { 391 if (!options.contains(option)) { 392 return false; 393 } 394 395 // TODO: could/should validate outputDir exists and is a directory 396 // need to decide how best to report issue for benefit of 397 // direct API call on JavaFileManager.handleOption(specifies IAE) 398 // vs. command line decoding. 399 outputDir = (value == null) ? null : new File(value); 400 return true; 401 } 402 403 @Override 404 Collection<File> getLocation() { 405 return (outputDir == null) ? null : Collections.singleton(outputDir); 406 } 407 408 @Override 409 void setLocation(Iterable<? extends File> files) throws IOException { 410 if (files == null) { 411 outputDir = null; 412 } else { 413 Iterator<? extends File> pathIter = files.iterator(); 414 if (!pathIter.hasNext()) { 415 throw new IllegalArgumentException("empty path for directory"); 416 } 417 File dir = pathIter.next(); 418 if (pathIter.hasNext()) { 419 throw new IllegalArgumentException("path too long for directory"); 420 } 421 if (!dir.exists()) { 422 throw new FileNotFoundException(dir + ": does not exist"); 423 } else if (!dir.isDirectory()) { 424 throw new IOException(dir + ": not a directory"); 425 } 426 outputDir = dir; 427 } 428 } 429 } 430 431 /** 432 * General purpose implementation for search path locations, such as -sourcepath/SOURCE_PATH and 433 * -processorPath/ANNOTATION_PROCESS_PATH. All options are treated as equivalent (i.e. aliases.) 434 * The value is an ordered set of files and/or directories. 435 */ 436 private class SimpleLocationHandler extends LocationHandler { 437 438 protected Collection<File> searchPath; 439 440 SimpleLocationHandler(Location location, Option... options) { 441 super(location, options); 442 } 443 444 @Override 445 boolean handleOption(Option option, String value) { 446 if (!options.contains(option)) { 447 return false; 448 } 449 searchPath = value == null ? null 450 : Collections.unmodifiableCollection(createPath().addFiles(value)); 451 return true; 452 } 453 454 @Override 455 Collection<File> getLocation() { 456 return searchPath; 457 } 458 459 @Override 460 void setLocation(Iterable<? extends File> files) { 461 SearchPath p; 462 if (files == null) { 463 p = computePath(null); 464 } else { 465 p = createPath().addFiles(files); 466 } 467 searchPath = Collections.unmodifiableCollection(p); 468 } 469 470 protected SearchPath computePath(String value) { 471 return createPath().addFiles(value); 472 } 473 474 protected SearchPath createPath() { 475 return new SearchPath(); 476 } 477 } 478 479 /** 480 * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH. If no value is given, a default 481 * is provided, based on system properties and other values. 482 */ 483 private class ClassPathLocationHandler extends SimpleLocationHandler { 484 485 ClassPathLocationHandler() { 486 super(StandardLocation.CLASS_PATH, 487 Option.CLASSPATH, Option.CP); 488 } 489 490 @Override 491 Collection<File> getLocation() { 492 lazy(); 493 return searchPath; 494 } 495 496 @Override 497 protected SearchPath computePath(String value) { 498 String cp = value; 499 500 // CLASSPATH environment variable when run from `javac'. 501 if (cp == null) { 502 cp = System.getProperty("env.class.path"); 503 } 504 505 // If invoked via a java VM (not the javac launcher), use the 506 // platform class path 507 if (cp == null && System.getProperty("application.home") == null) { 508 cp = System.getProperty("java.class.path"); 509 } 510 511 // Default to current working directory. 512 if (cp == null) { 513 cp = "."; 514 } 515 516 return createPath().addFiles(cp); 517 } 518 519 @Override 520 protected SearchPath createPath() { 521 return new SearchPath() 522 .expandJarClassPaths(true) // Only search user jars for Class-Paths 523 .emptyPathDefault(new File(".")); // Empty path elt ==> current directory 524 } 525 526 private void lazy() { 527 if (searchPath == null) { 528 setLocation(null); 529 } 530 } 531 } 532 533 /** 534 * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH. Various options are supported for 535 * different components of the platform class path. Setting a value with setLocation overrides 536 * all existing option values. Setting any option overrides any value set with setLocation, and 537 * reverts to using default values for options that have not been set. Setting -bootclasspath or 538 * -Xbootclasspath overrides any existing value for -Xbootclasspath/p: and -Xbootclasspath/a:. 539 */ 540 private class BootClassPathLocationHandler extends LocationHandler { 541 542 private Collection<File> searchPath; 543 final Map<Option, String> optionValues = new EnumMap<>(Option.class); 544 545 /** 546 * rt.jar as found on the default bootclasspath. If the user specified a bootclasspath, null 547 * is used. 548 */ 549 private File defaultBootClassPathRtJar = null; 550 551 /** 552 * Is bootclasspath the default? 553 */ 554 private boolean isDefaultBootClassPath; 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 isDefaultBootClassPath; 568 } 569 570 boolean isDefaultRtJar(File file) { 571 lazy(); 572 return file.equals(defaultBootClassPathRtJar); 573 } 574 575 @Override 576 boolean handleOption(Option option, String value) { 577 if (!options.contains(option)) { 578 return false; 579 } 580 581 option = canonicalize(option); 582 optionValues.put(option, value); 583 if (option == BOOTCLASSPATH) { 584 optionValues.remove(XBOOTCLASSPATH_PREPEND); 585 optionValues.remove(XBOOTCLASSPATH_APPEND); 586 } 587 searchPath = null; // reset to "uninitialized" 588 return true; 589 } 590 // where 591 // TODO: would be better if option aliasing was handled at a higher 592 // level 593 private Option canonicalize(Option option) { 594 switch (option) { 595 case XBOOTCLASSPATH: 596 return Option.BOOTCLASSPATH; 597 case DJAVA_ENDORSED_DIRS: 598 return Option.ENDORSEDDIRS; 599 case DJAVA_EXT_DIRS: 600 return Option.EXTDIRS; 601 default: 602 return option; 603 } 604 } 605 606 @Override 607 Collection<File> getLocation() { 608 lazy(); 609 return searchPath; 610 } 611 612 @Override 613 void setLocation(Iterable<? extends File> files) { 614 if (files == null) { 615 searchPath = null; // reset to "uninitialized" 616 } else { 617 defaultBootClassPathRtJar = null; 618 isDefaultBootClassPath = false; 619 SearchPath p = new SearchPath().addFiles(files, false); 620 searchPath = Collections.unmodifiableCollection(p); 621 optionValues.clear(); 622 } 623 } 624 625 SearchPath computePath() { 626 defaultBootClassPathRtJar = null; 627 SearchPath path = new SearchPath(); 628 629 String bootclasspathOpt = optionValues.get(BOOTCLASSPATH); 630 String endorseddirsOpt = optionValues.get(ENDORSEDDIRS); 631 String extdirsOpt = optionValues.get(EXTDIRS); 632 String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND); 633 String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND); 634 path.addFiles(xbootclasspathPrependOpt); 635 636 if (endorseddirsOpt != null) { 637 path.addDirectories(endorseddirsOpt); 638 } else { 639 path.addDirectories(System.getProperty("java.endorsed.dirs"), false); 640 } 641 642 if (bootclasspathOpt != null) { 643 path.addFiles(bootclasspathOpt); 644 } else { 645 // Standard system classes for this compiler's release. 646 String files = System.getProperty("sun.boot.class.path"); 647 path.addFiles(files, false); 648 File rt_jar = new File("rt.jar"); 649 for (File file : getPathEntries(files)) { 650 if (new File(file.getName()).equals(rt_jar)) { 651 defaultBootClassPathRtJar = file; 652 } 653 } 654 } 655 656 path.addFiles(xbootclasspathAppendOpt); 657 658 // Strictly speaking, standard extensions are not bootstrap 659 // classes, but we treat them identically, so we'll pretend 660 // that they are. 661 if (extdirsOpt != null) { 662 path.addDirectories(extdirsOpt); 663 } else { 664 path.addDirectories(System.getProperty("java.ext.dirs"), false); 665 } 666 667 isDefaultBootClassPath 668 = (xbootclasspathPrependOpt == null) 669 && (bootclasspathOpt == null) 670 && (xbootclasspathAppendOpt == null); 671 672 return path; 673 } 674 675 private void lazy() { 676 if (searchPath == null) { 677 searchPath = Collections.unmodifiableCollection(computePath()); 678 } 679 } 680 } 681 682 Map<Location, LocationHandler> handlersForLocation; 683 Map<Option, LocationHandler> handlersForOption; 684 685 void initHandlers() { 686 handlersForLocation = new HashMap<>(); 687 handlersForOption = new EnumMap<>(Option.class); 688 689 LocationHandler[] handlers = { 690 new BootClassPathLocationHandler(), 691 new ClassPathLocationHandler(), 692 new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCEPATH), 693 new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSORPATH), 694 new OutputLocationHandler((StandardLocation.CLASS_OUTPUT), Option.D), 695 new OutputLocationHandler((StandardLocation.SOURCE_OUTPUT), Option.S), 696 new OutputLocationHandler((StandardLocation.NATIVE_HEADER_OUTPUT), Option.H) 697 }; 698 699 for (LocationHandler h : handlers) { 700 handlersForLocation.put(h.location, h); 701 for (Option o : h.options) { 702 handlersForOption.put(o, h); 703 } 704 } 705 } 706 707 public boolean handleOption(Option option, String value) { 708 LocationHandler h = handlersForOption.get(option); 709 return (h == null ? false : h.handleOption(option, value)); 710 } 711 712 Collection<File> getLocation(Location location) { 713 LocationHandler h = getHandler(location); 714 return (h == null ? null : h.getLocation()); 715 } 716 717 File getOutputLocation(Location location) { 718 if (!location.isOutputLocation()) { 719 throw new IllegalArgumentException(); 720 } 721 LocationHandler h = getHandler(location); 722 return ((OutputLocationHandler) h).outputDir; 723 } 724 725 void setLocation(Location location, Iterable<? extends File> files) throws IOException { 726 LocationHandler h = getHandler(location); 727 if (h == null) { 728 if (location.isOutputLocation()) { 729 h = new OutputLocationHandler(location); 730 } else { 731 h = new SimpleLocationHandler(location); 732 } 733 handlersForLocation.put(location, h); 734 } 735 h.setLocation(files); 736 } 737 738 protected LocationHandler getHandler(Location location) { 739 location.getClass(); // null check 740 return handlersForLocation.get(location); 741 } 742 743 /** 744 * Is this the name of an archive file? 745 */ 746 private boolean isArchive(File file) { 747 String n = StringUtils.toLowerCase(file.getName()); 748 return fsInfo.isFile(file) 749 && (n.endsWith(".jar") || n.endsWith(".zip")); 750 } 751 752 /** 753 * Utility method for converting a search path string to an array of directory and JAR file 754 * URLs. 755 * 756 * Note that this method is called by apt and the DocletInvoker. 757 * 758 * @param path the search path string 759 * @return the resulting array of directory and JAR file URLs 760 */ 761 public static URL[] pathToURLs(String path) { 762 StringTokenizer st = new StringTokenizer(path, File.pathSeparator); 763 URL[] urls = new URL[st.countTokens()]; 764 int count = 0; 765 while (st.hasMoreTokens()) { 766 URL url = fileToURL(new File(st.nextToken())); 767 if (url != null) { 768 urls[count++] = url; 769 } 770 } 771 urls = Arrays.copyOf(urls, count); 772 return urls; 773 } 774 775 /** 776 * Returns the directory or JAR file URL corresponding to the specified local file name. 777 * 778 * @param file the File object 779 * @return the resulting directory or JAR file URL, or null if unknown 780 */ 781 private static URL fileToURL(File file) { 782 String name; 783 try { 784 name = file.getCanonicalPath(); 785 } catch (IOException e) { 786 name = file.getAbsolutePath(); 787 } 788 name = name.replace(File.separatorChar, '/'); 789 if (!name.startsWith("/")) { 790 name = "/" + name; 791 } 792 // If the file does not exist, then assume that it's a directory 793 if (!file.isFile()) { 794 name = name + "/"; 795 } 796 try { 797 return new URL("file", "", name); 798 } catch (MalformedURLException e) { 799 throw new IllegalArgumentException(file.toString()); 800 } 801 } 802} 803