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