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