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