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