1 /* 2 * Copyright (c) 2014, 2015, 2016 Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 */ 5 package com.oracle.appbundlers.utils; 6 7 import static com.oracle.appbundlers.utils.Config.CONFIG_INSTANCE; 8 import static java.lang.String.format; 9 import static java.nio.file.Files.newDirectoryStream; 10 import static java.util.stream.Collectors.joining; 11 import static java.util.stream.Collectors.toList; 12 13 import java.io.ByteArrayOutputStream; 14 import java.io.File; 15 import java.io.IOException; 16 import java.nio.file.DirectoryStream; 17 import java.nio.file.FileVisitResult; 18 import java.nio.file.Files; 19 import java.nio.file.Path; 20 import java.nio.file.Paths; 21 import java.nio.file.SimpleFileVisitor; 22 import java.nio.file.attribute.BasicFileAttributes; 23 import java.util.ArrayList; 24 import java.util.Arrays; 25 import java.util.Collections; 26 import java.util.HashSet; 27 import java.util.List; 28 import java.util.Map; 29 import java.util.Map.Entry; 30 import java.util.Set; 31 import java.util.concurrent.ExecutionException; 32 import java.util.jar.Attributes; 33 import java.util.jar.JarEntry; 34 import java.util.jar.JarFile; 35 import java.util.jar.JarOutputStream; 36 import java.util.jar.Manifest; 37 import java.util.logging.Level; 38 import java.util.logging.Logger; 39 import java.util.stream.Collectors; 40 import java.util.stream.Stream; 41 import java.util.zip.ZipEntry; 42 43 import javax.tools.JavaCompiler; 44 import javax.tools.ToolProvider; 45 46 import javafx.util.Pair; 47 48 /** 49 * @author Dmitry Ginzburg <dmitry.x.ginzburg@oracle.com> 50 */ 51 public class AppWrapper implements Constants { 52 53 private static final Logger LOG = Logger 54 .getLogger(AppWrapper.class.getName()); 55 56 private Path workDir; 57 private final List<Source> sources; 58 private final String mainJar; 59 private final String mainClass; 60 61 private String paramsForJarsCompilation; 62 63 public AppWrapper(Path workDir, String mainClass, Source... source) { 64 this.workDir = workDir; 65 this.sources = Arrays.asList(source); 66 this.mainClass = mainClass; 67 List<String> jars = sources.stream() 68 .filter(src -> src.getFullName().equals(mainClass)) 69 .map(Source::getJarName).collect(toList()); 70 if (jars.size() != 1) { 71 throw new IllegalArgumentException( 72 format("Can not determine main jar : " + jars)); 73 } 74 mainJar = jars.get(0); 75 } 76 77 public AppWrapper(Path workDir, String mainClass, 78 String paramsForCompilation, Source... source) { 79 this(workDir, mainClass, source); 80 this.paramsForJarsCompilation = paramsForCompilation; 81 } 82 83 /** 84 * copy the old AppWrapper, but change the work dir 85 */ 86 public AppWrapper(AppWrapper copied, Path workDir) { 87 this.workDir = workDir; 88 sources = new ArrayList<>(copied.sources); 89 mainJar = copied.mainJar; 90 mainClass = copied.mainClass; 91 } 92 93 public String getAppName() { 94 return mainClass.substring(mainClass.lastIndexOf('.') + 1); 95 } 96 97 public void preinstallApp(ExtensionType extension) throws IOException { 98 createSrcBundleAndBinDirs(); 99 100 if (!getJarTempSources().isEmpty()) { 101 Utils.createDir(getJarDir()); 102 } 103 104 if (!getModuleTempSources().isEmpty()) { 105 Utils.createDir( 106 Paths.get(getModulePathBasedOnExtension(extension))); 107 } 108 } 109 110 private void createSrcBundleAndBinDirs() throws IOException { 111 Utils.createDir(getSrcDir()); 112 Utils.createDir(getBundlesDir()); 113 Utils.createDir(getBinDir()); 114 } 115 116 public void preinstallApp(ExtensionType[] extensionArray) throws IOException { 117 createSrcBundleAndBinDirs(); 118 119 for (ExtensionType extension : extensionArray) { 120 Utils.createDir(Paths.get(getModulePathBasedOnExtension(extension))); 121 } 122 } 123 124 125 private List<Source> getJarTempSources() { 126 return sources.stream().filter((source) -> !source.isModule()) 127 .collect(Collectors.toList()); 128 } 129 130 public Path getWorkDir() { 131 return workDir; 132 } 133 134 public void setWorkDir(Path workDir) { 135 this.workDir = workDir; 136 } 137 138 public List<Source> getSources() { 139 return sources; 140 } 141 142 public String getMainClass() { 143 return this.mainClass; 144 } 145 146 public Path getBundlesDir() { 147 return getWorkDir().resolve(BUNDLES); 148 } 149 150 private Path getSrcDir() { 151 return getWorkDir().resolve(SOURCE); 152 } 153 154 public Path getJarDir() { 155 return getWorkDir().resolve(JARS); 156 } 157 158 private Path getBinDir() { 159 return getWorkDir().resolve(BIN); 160 } 161 162 public Path getMainJarFile() { 163 return getJarDir().resolve(mainJar + ".jar"); 164 } 165 166 public void writeSourcesToAppDirectory() throws IOException { 167 Path appDir = getSrcDir(); 168 for (Source source : getModuleTempSources()) { 169 source.generateSourceForModule(appDir); 170 } 171 172 for (Source source : getJarTempSources()) { 173 source.generateSourceForJar(appDir); 174 } 175 } 176 177 public int compileApp(ExtensionType extension, Path... classpath) 178 throws IOException { 179 return compileApp(new String[0], extension, classpath); 180 } 181 182 private int compileApp(String[] javacOptions, ExtensionType extension, 183 Path... classpath) throws IOException { 184 int resultForModule = compileAppForModules(javacOptions, extension, 185 classpath); 186 int resultForJar = compileAppForJars(javacOptions, extension, 187 classpath); 188 int result = 0; 189 if (resultForJar < 0 || resultForModule < 0) { 190 result = -1; 191 } 192 return result; 193 } 194 195 public int compileApp(Path... classpath) throws IOException { 196 return compileApp(new String[0], classpath); 197 } 198 199 public int compileApp(String[] javacOptions, Path... classpath) 200 throws IOException { 201 return compileApp(javacOptions, null, classpath); 202 } 203 204 public int compileAndCreateExtensionEndProduct(ExtensionType extension, Path... classpath) throws IOException, ExecutionException { 205 int resultForModule = compileAppForModules(new String[0], extension, classpath); 206 jarApp(extension); 207 compileAppForJars(new String[0], extension, classpath); 208 jarApp(ExtensionType.NormalJar); 209 return resultForModule; 210 } 211 212 private int compileAppForJars(String[] javacOptions, 213 ExtensionType extension, Path[] classpath) throws IOException { 214 if (getJarTempSources().isEmpty()) { 215 return 0; 216 } 217 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 218 final List<String> argsList = new ArrayList<>(); 219 220 if (javacOptions != null && javacOptions.length > 0) { 221 for (int i = 0; i < javacOptions.length; i++) { 222 String javacOption = javacOptions[i]; 223 argsList.add(javacOption); 224 } 225 } 226 227 argsList.add("-d"); 228 argsList.add(getBinDir().toString()); 229 230 int result = 0; 231 for (Source tempSource : getJarTempSources()) { 232 List<String> newArgs = new ArrayList<String>(); 233 newArgs.addAll(argsList); 234 235 236 newArgs.add("-classpath"); 237 if (classpath.length != 0) { 238 newArgs.add(Stream.of(classpath) 239 .map(eachPath -> eachPath.toAbsolutePath().toString()) 240 .collect(joining(File.pathSeparator))); 241 } else { 242 newArgs.add(getBinDir().toString()); 243 } 244 245 if (paramsForJarsCompilation != null) { 246 newArgs.add(this.paramsForJarsCompilation); 247 } 248 249 if (extension != null) { 250 newArgs.add("-mp"); 251 newArgs.add(String.join(File.pathSeparator, getModulePathBasedOnExtension(extension), JMODS_PATH_IN_JDK)); 252 } 253 254 String string = getSrcDir() + File.separator 255 + tempSource.getPackageName().replace(".", File.separator); 256 Path path = Paths.get(string); 257 Files.walkFileTree(path, new SimpleFileVisitor<Path>() { 258 @Override 259 public FileVisitResult visitFile(Path file, 260 BasicFileAttributes attr) { 261 262 if (file.toString().endsWith(".java")) { 263 newArgs.add(file.toString()); 264 } 265 return FileVisitResult.CONTINUE; 266 } 267 }); 268 269 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 270 System.out.println( 271 "====================COMPILATION STARTS==========================="); 272 System.out.println("compilation command for jars is " + newArgs); 273 result = compiler.run(System.in, outputStream, System.err, 274 newArgs.toArray(new String[newArgs.size()])); 275 System.out.println( 276 "===================COMPILATION ENDS==============================="); 277 System.out.println(); 278 } 279 return result; 280 } 281 282 private int compileAppForModules(String[] javacOptions, 283 ExtensionType extension, Path... classpath) throws IOException { 284 if (getModuleTempSources().isEmpty()) { 285 return 0; 286 } 287 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 288 int result = 0; 289 for (Source source : getModuleTempSources()) { 290 final List<String> argsList = new ArrayList<>(); 291 292 if (javacOptions != null && javacOptions.length > 0) { 293 for (int i = 0; i < javacOptions.length; i++) { 294 String javacOption = javacOptions[i]; 295 argsList.add(javacOption); 296 } 297 } 298 299 argsList.add("-mp"); 300 argsList.add(String.join(File.pathSeparator, getBinDir().toString(), JMODS_PATH_IN_JDK)); 301 argsList.add("-d"); 302 argsList.add(String.join(File.separator, getBinDir().toString(), 303 source.getModuleName())); 304 Files.walkFileTree( 305 Paths.get(String.join(File.separator, 306 getSrcDir().toString(), source.getModuleName())), 307 new SimpleFileVisitor<Path>() { 308 public FileVisitResult visitFile(Path file, 309 BasicFileAttributes attr) { 310 if (file.toString().endsWith(".java")) { 311 argsList.add(file.toString()); 312 } 313 return FileVisitResult.CONTINUE; 314 } 315 }); 316 if (classpath.length != 0) { 317 argsList.add("-classpath"); 318 argsList.add(Stream.of(classpath) 319 .map(path -> path.toAbsolutePath().toString()) 320 .collect(joining(File.pathSeparator))); 321 } 322 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 323 System.out.println( 324 "====================COMPILATION STARTS==========================="); 325 System.out.println( 326 "compilation command for modules is " + argsList); 327 328 int tempResult = compiler.run(System.in, outputStream, System.err, 329 argsList.toArray(new String[argsList.size()])); 330 if (tempResult != 0) { 331 result = tempResult; 332 } 333 String out = outputStream.toString(); 334 if (!out.trim().isEmpty()) { 335 LOG.log(Level.INFO, out); 336 } 337 System.out.println( 338 "===================COMPILATION ENDS==============================="); 339 System.out.println(); 340 } 341 return result; 342 } 343 344 public List<Path> getJarFilesList() throws IOException { 345 try (DirectoryStream<Path> jarsStream = Files 346 .newDirectoryStream(getJarDir(), "*.jar")) { 347 List<Path> jars = new ArrayList<>(); 348 jarsStream.forEach(jars::add); 349 return jars; 350 } 351 } 352 353 private void createSimpleJar(List<Pair<String, String>> services, 354 boolean crossClassPath) throws IOException { 355 356 if (getJarTempSources().isEmpty()) { 357 return; 358 } 359 Map<String, List<Source>> jars = getJarTempSources().stream() 360 .collect(Collectors.groupingBy(Source::getJarName)); 361 362 for (Entry<String, List<Source>> entry : jars.entrySet()) { 363 String jarFileName = entry.getKey(); 364 Path jarFile = getJarDir().resolve(jarFileName + ".jar"); 365 Manifest manifest = new Manifest(); 366 manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, 367 "1.0"); 368 if (crossClassPath) 369 manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, 370 jars.keySet().stream() 371 .filter(str -> !str.equals(jarFileName)) 372 .map(str -> str + ".jar") 373 .collect(joining(File.pathSeparator))); 374 if (mainJar.equals(jarFileName)) { 375 manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, 376 mainClass); 377 } 378 379 try (JarOutputStream jarOutputStream = new JarOutputStream( 380 Files.newOutputStream(jarFile), manifest)) { 381 add(jarOutputStream, new HashSet<>(entry.getValue())); 382 for (Pair<String, String> service : services) { 383 if (!entry.getValue().stream().anyMatch(source -> source 384 .getFullName().equals(service.getValue()))) { 385 continue; 386 } 387 jarOutputStream.putNextEntry(new JarEntry( 388 "META-INF/services/" + service.getKey())); 389 jarOutputStream.write(service.getValue().getBytes()); 390 jarOutputStream.closeEntry(); 391 } 392 } 393 } 394 } 395 396 private void add(JarOutputStream jarStream, Set<Source> sources) 397 throws IOException { 398 Path classesDir = getBinDir(); 399 for (Source src : sources) { 400 Path packageDir = classesDir 401 .resolve(src.getPackageName().replace('.', '/')); 402 // we need to process inner classes like App1$1 403 DirectoryStream<Path> classFiles = newDirectoryStream(packageDir, 404 src.getSimpleName() + "*.class"); 405 for (Path classFile : classFiles) { 406 ZipEntry entry = new JarEntry( 407 src.getPackageName().replace('.', '/') + "/" 408 + classFile.getFileName().toString()); 409 jarStream.putNextEntry(entry); 410 jarStream.write(Files.readAllBytes(classFile)); 411 jarStream.closeEntry(); 412 } 413 } 414 } 415 416 public List<Source> getModuleTempSources() { 417 return sources.stream().filter((source) -> source.isModule()) 418 .collect(Collectors.toList()); 419 } 420 421 public Path getExplodedModsDir() { 422 return getBinDir(); 423 } 424 425 public Path getJmodsDir() { 426 return getWorkDir().resolve(JMODS_DIR); 427 } 428 429 public Path getModularJarsDir() { 430 return getWorkDir().resolve(MODULAR_JARS_DIR); 431 } 432 433 public List<Path> getModularJarFileList() throws IOException { 434 try (DirectoryStream<Path> jarsStream = Files 435 .newDirectoryStream(getModularJarsDir(), "*.jar")) { 436 List<Path> jars = new ArrayList<>(); 437 jarsStream.forEach(jars::add); 438 return jars; 439 } 440 } 441 442 public List<Path> getJmodFileList() throws IOException { 443 try (DirectoryStream<Path> jarsStream = Files 444 .newDirectoryStream(getJmodsDir(), "*.jmod")) { 445 List<Path> jmods = new ArrayList<>(); 446 jarsStream.forEach(jmods::add); 447 return jmods; 448 } 449 } 450 451 /* 452 * list mod directory 453 */ 454 455 public List<Path> getExplodedModFileList() throws IOException { 456 try (DirectoryStream<Path> jarsStream = Files 457 .newDirectoryStream(getExplodedModsDir(), "*")) { 458 List<Path> modFiles = new ArrayList<>(); 459 jarsStream.forEach(modFiles::add); 460 return modFiles; 461 } 462 } 463 464 public void jarApp(ExtensionType extension) 465 throws IOException, ExecutionException { 466 switch (extension) { 467 case NormalJar: 468 createSimpleJar(Collections.emptyList(), false); 469 break; 470 case ModularJar: 471 createModularJar(Collections.emptyList(), false); 472 break; 473 case ExplodedModules: /****************************************** 474 * bin directory itself is exploded modules 475 * directory 476 ******************************************/ 477 break; 478 case Jmods: 479 createJmod(); 480 break; 481 } 482 } 483 484 public void jarApp(List<Pair<String, String>> services, 485 boolean crossClassPath) throws IOException, ExecutionException { 486 createSimpleJar(services, crossClassPath); 487 createModularJar(services, crossClassPath); 488 } 489 490 private void createJmod() throws IOException, ExecutionException { 491 for (Source source : getModuleTempSources()) { 492 List<String> command = new ArrayList<String>(); 493 command.add("jmod"); 494 command.add("create"); 495 command.add("--class-path"); 496 command.add(getBinDir().toString() + File.separator 497 + source.getModuleName()); 498 command.add("--main-class"); 499 command.add(source.getFullName()); 500 command.add(getJmodsDir() + File.separator + source.getModuleName() 501 + ".jmod"); 502 System.out.println( 503 "=========================JMOD CREATION STARTS========================="); 504 Utils.runCommand(command, CONFIG_INSTANCE.getInstallTimeout()); 505 System.out.println( 506 "=========================JMOD CREATION ENDS==========================="); 507 System.out.println(); 508 } 509 } 510 511 private void createModularJar(List<Pair<String, String>> services, 512 boolean crossClassPath) throws IOException, ExecutionException { 513 514 if (getModuleTempSources().isEmpty()) { 515 return; 516 } 517 /* 518 * @TODO check whether services are required in modular jar Need to 519 * change below code Implementation pending. complete this 520 * implementation. 521 */ 522 if (!services.isEmpty()) { 523 Map<String, List<Source>> jars = sources.stream() 524 .collect(Collectors.groupingBy(Source::getJarName)); 525 526 // for (Map.Entry<String, List<TempSource>> entry : jars.entrySet()) 527 // { 528 // String jarFileName = entry.getKey(); 529 //// Path jarFile = getJarDir().resolve(jarFileName + ".jar"); 530 // Manifest manifest = new Manifest(); 531 // manifest.getMainAttributes() 532 // .put(Attributes.Name.MANIFEST_VERSION, "1.0"); 533 // if (crossClassPath) { 534 // manifest.getMainAttributes() 535 // .put(Attributes.Name.CLASS_PATH, 536 // jars.keySet().stream() 537 // .filter(str -> !str 538 // .equals(jarFileName)) 539 // .map(str -> str + ".jar") 540 // .collect(joining(File.pathSeparator))); 541 // } 542 // if (mainJar != null && jarFileName != null && 543 // mainJar.equals(jarFileName)) { 544 // manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, 545 // mainClass); 546 // } 547 // } 548 /* 549 * Dummy code 550 * 551 * @TODO complete implementation. 552 */ 553 554 for (Source tempSource : getModuleTempSources()) { 555 Manifest manifest = new Manifest(); 556 String jarFileName = tempSource.getJarName(); 557 manifest.getMainAttributes() 558 .put(Attributes.Name.MANIFEST_VERSION, "1.0"); 559 if (crossClassPath) { 560 manifest.getMainAttributes() 561 .put(Attributes.Name.CLASS_PATH, 562 jars.keySet().stream() 563 .filter(str -> !str 564 .equals(jarFileName)) 565 .map(str -> str + ".jar") 566 .collect(joining(File.pathSeparator))); 567 } 568 if (mainJar != null && jarFileName != null 569 && mainJar.equals(jarFileName)) { 570 manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, 571 mainClass); 572 } 573 574 // try (JarOutputStream jarOutputStream = new JarOutputStream( 575 // Files.newOutputStream(Paths.get(jarFileName)), manifest)) { 576 // Stream<Path> walk = 577 // Files.walk(Paths.get(getModularJarsDir().toString() + 578 // File.separatorChar + tempSource.getModuleName()), 579 // Integer.MAX_VALUE); 580 // String join = String.join(File.separator, 581 // getModularJarsDir().toString(), tempSource.getModuleName(), 582 // tempSource.getModuleName().replace('.', File.separatorChar)); 583 // Path moduleInfoPath = 584 // getModularJarsDir().resolve(tempSource.getModuleName().replace('.', 585 // '/')).resolve("module-info.class"); 586 // ZipEntry zipEntry = new JarEntry( 587 // moduleInfoPath.getFileName().toString()); 588 // jarOutputStream.putNextEntry(zipEntry); 589 // jarOutputStream.write(Files.readAllBytes(moduleInfoPath)); 590 // jarOutputStream.closeEntry(); 591 // 592 // String packageName = 593 // getModularJarsDir().toString().replace('.', '/'); 594 // DirectoryStream<Path> classFiles = newDirectoryStream( 595 // getModularJarsDir().resolve(packageName), "*.class"); 596 // for (Path eachClassFile : classFiles) { 597 // ZipEntry jarEntry = new JarEntry("com/greetings" + "/" 598 // + eachClassFile.getFileName().toString()); 599 // jarOutputStream.putNextEntry(jarEntry); 600 // jarOutputStream.write(Files.readAllBytes(eachClassFile)); 601 // jarOutputStream.closeEntry(); 602 // } 603 // } 604 605 // Files.walkFileTree(Paths.get(getModsDir().toString() + 606 // File.separatorChar + tempSource.getModuleName()), new 607 // SimpleFileVisitor<Path>() { 608 // @Override 609 // public FileVisitResult visitFile(Path file, 610 // BasicFileAttributes attr) { 611 // 612 // if ("module-info.java".equals(file.toString())) { 613 // ZipEntry zipEntry = new JarEntry( 614 // moduleInfoPath.getFileName().toString()); 615 // jarOutputStream.putNextEntry(zipEntry); 616 // jarOutputStream.write(Files.readAllBytes(moduleInfoPath)); 617 // jarOutputStream.closeEntry(); 618 // } 619 // return FileVisitResult.CONTINUE; 620 // } 621 // }); 622 623 } 624 625 } else { 626 /* 627 * JDK 9 jar creation procedure. $ jar --create 628 * --file=mlib/org.astro@1.0.jar --module-version=1.0 -C 629 * mods/org.astro . 630 */ 631 for (Source source : getModuleTempSources()) { 632 List<String> command = new ArrayList<String>(); 633 command.add("jar"); 634 command.add("--create"); 635 command.add("--file=" + getModularJarsDir() + File.separator 636 + source.getJarName() + ".jar"); 637 command.add("--module-version=1.0"); 638 if (source.getFullName() != null && this.mainClass != null 639 && source.getFullName().equals(this.mainClass)) { 640 command.add("--main-class=" + this.mainClass); 641 } 642 command.add("-C"); 643 String moduleAbsouleDirectoryPath = String.join(File.separator, 644 getBinDir().toString(), source.getModuleName()); 645 command.add(moduleAbsouleDirectoryPath); 646 command.add("."); 647 System.out.println( 648 "====================MODULAR JAR CREATION STARTS=================="); 649 Utils.runCommand(command, CONFIG_INSTANCE.getInstallTimeout()); 650 System.out.println( 651 "====================MODULAR JAR CREATION ENDS===================="); 652 System.out.println(); 653 } 654 } 655 } 656 657 public String getIdentifier() { 658 try (JarFile mainJar = new JarFile(getMainJarFile().toFile());) { 659 Manifest manifest = mainJar.getManifest(); 660 for (Map.Entry<Object, Object> entry : manifest.getMainAttributes() 661 .entrySet()) { 662 if (entry.getKey().toString().equals("Main-Class")) { 663 return entry.getValue().toString(); 664 } 665 } 666 } catch (IOException ex) { 667 throw new RuntimeException(ex); 668 } 669 return ""; 670 } 671 672 public String addAllModules() { 673 return getModuleTempSources().stream().map(Source::getModuleName) 674 .collect(Collectors.joining(File.pathSeparator)); 675 } 676 677 public List<String> getAllModuleNamesAsList() { 678 return getModuleTempSources().stream().map(Source::getModuleName) 679 .collect(Collectors.toList()); 680 } 681 682 public String getMainModuleName() { 683 List<Source> collect = sources.stream() 684 .filter((source) -> source.isMainModule()) 685 .collect(Collectors.toList()); 686 return !collect.isEmpty() ? collect.get(0).getModuleName() : null; 687 } 688 689 public boolean isAppContainsModules() { 690 return !getModuleTempSources().isEmpty(); 691 } 692 693 private String getModulePathBasedOnExtension(ExtensionType extension) { 694 if (extension == null) { 695 throw new NullPointerException("Extension cannot be null"); 696 } 697 switch (extension) { 698 case ModularJar: 699 return getModularJarsDir().toString(); 700 case ExplodedModules: 701 return getExplodedModsDir().toString(); 702 case Jmods: 703 return getJmodsDir().toString(); 704 default: 705 return getJarDir().toString(); 706 } 707 } 708 }