1 /* 2 * Copyright (c) 2013, 2018, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package toolbox; 25 26 import java.io.BufferedWriter; 27 import java.io.ByteArrayOutputStream; 28 import java.io.FilterOutputStream; 29 import java.io.FilterWriter; 30 import java.io.IOException; 31 import java.io.OutputStream; 32 import java.io.PrintStream; 33 import java.io.StringWriter; 34 import java.io.Writer; 35 import java.net.URI; 36 import java.nio.charset.Charset; 37 import java.nio.file.DirectoryNotEmptyException; 38 import java.nio.file.FileVisitResult; 39 import java.nio.file.Files; 40 import java.nio.file.NoSuchFileException; 41 import java.nio.file.Path; 42 import java.nio.file.Paths; 43 import java.nio.file.SimpleFileVisitor; 44 import java.nio.file.StandardCopyOption; 45 import java.nio.file.attribute.BasicFileAttributes; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.Collection; 49 import java.util.Collections; 50 import java.util.Deque; 51 import java.util.HashMap; 52 import java.util.LinkedList; 53 import java.util.List; 54 import java.util.Locale; 55 import java.util.Map; 56 import java.util.Objects; 57 import java.util.Set; 58 import java.util.TreeSet; 59 import java.util.regex.Matcher; 60 import java.util.regex.Pattern; 61 import java.util.stream.Collectors; 62 import java.util.stream.StreamSupport; 63 64 import javax.tools.FileObject; 65 import javax.tools.ForwardingJavaFileManager; 66 import javax.tools.JavaFileManager; 67 import javax.tools.JavaFileObject; 68 import javax.tools.JavaFileObject.Kind; 69 import javax.tools.JavaFileManager.Location; 70 import javax.tools.SimpleJavaFileObject; 71 import javax.tools.ToolProvider; 72 73 /** 74 * Utility methods and classes for writing jtreg tests for 75 * javac, javah, javap, and sjavac. (For javadoc support, 76 * see JavadocTester.) 77 * 78 * <p>There is support for common file operations similar to 79 * shell commands like cat, cp, diff, mv, rm, grep. 80 * 81 * <p>There is also support for invoking various tools, like 82 * javac, javah, javap, jar, java and other JDK tools. 83 * 84 * <p><em>File separators</em>: for convenience, many operations accept strings 85 * to represent filenames. On all platforms on which JDK is supported, 86 * "/" is a legal filename component separator. In particular, even 87 * on Windows, where the official file separator is "\", "/" is a legal 88 * alternative. It is therefore recommended that any client code using 89 * strings to specify filenames should use "/". 90 * 91 * @author Vicente Romero (original) 92 * @author Jonathan Gibbons (revised) 93 */ 94 public class ToolBox { 95 /** The platform line separator. */ 96 public static final String lineSeparator = System.getProperty("line.separator"); 97 /** The platform OS name. */ 98 public static final String osName = System.getProperty("os.name"); 99 100 /** The location of the class files for this test, or null if not set. */ 101 public static final String testClasses = System.getProperty("test.classes"); 102 /** The location of the source files for this test, or null if not set. */ 103 public static final String testSrc = System.getProperty("test.src"); 104 /** The location of the test JDK for this test, or null if not set. */ 105 public static final String testJDK = System.getProperty("test.jdk"); 106 /** The timeout factor for slow systems. */ 107 public static final float timeoutFactor; 108 static { 109 String ttf = System.getProperty("test.timeout.factor"); 110 timeoutFactor = (ttf == null) ? 1.0f : Float.valueOf(ttf); 111 } 112 113 /** The current directory. */ 114 public static final Path currDir = Paths.get("."); 115 116 /** The stream used for logging output. */ 117 public PrintStream out = System.err; 118 119 /** 120 * Checks if the host OS is some version of Windows. 121 * @return true if the host OS is some version of Windows 122 */ isWindows()123 public static boolean isWindows() { 124 return osName.toLowerCase(Locale.ENGLISH).startsWith("windows"); 125 } 126 127 /** 128 * Splits a string around matches of the given regular expression. 129 * If the string is empty, an empty list will be returned. 130 * @param text the string to be split 131 * @param sep the delimiting regular expression 132 * @return the strings between the separators 133 */ split(String text, String sep)134 public List<String> split(String text, String sep) { 135 if (text.isEmpty()) 136 return Collections.emptyList(); 137 return Arrays.asList(text.split(sep)); 138 } 139 140 /** 141 * Checks if two lists of strings are equal. 142 * @param l1 the first list of strings to be compared 143 * @param l2 the second list of strings to be compared 144 * @throws Error if the lists are not equal 145 */ checkEqual(List<String> l1, List<String> l2)146 public void checkEqual(List<String> l1, List<String> l2) throws Error { 147 if (!Objects.equals(l1, l2)) { 148 // l1 and l2 cannot both be null 149 if (l1 == null) 150 throw new Error("comparison failed: l1 is null"); 151 if (l2 == null) 152 throw new Error("comparison failed: l2 is null"); 153 // report first difference 154 for (int i = 0; i < Math.min(l1.size(), l2.size()); i++) { 155 String s1 = l1.get(i); 156 String s2 = l2.get(i); 157 if (!Objects.equals(s1, s2)) { 158 throw new Error("comparison failed, index " + i + 159 ", (" + s1 + ":" + s2 + ")"); 160 } 161 } 162 throw new Error("comparison failed: l1.size=" + l1.size() + ", l2.size=" + l2.size()); 163 } 164 } 165 166 /** 167 * Filters a list of strings according to the given regular expression. 168 * @param regex the regular expression 169 * @param lines the strings to be filtered 170 * @return the strings matching the regular expression 171 */ grep(String regex, List<String> lines)172 public List<String> grep(String regex, List<String> lines) { 173 return grep(Pattern.compile(regex), lines); 174 } 175 176 /** 177 * Filters a list of strings according to the given regular expression. 178 * @param pattern the regular expression 179 * @param lines the strings to be filtered 180 * @return the strings matching the regular expression 181 */ grep(Pattern pattern, List<String> lines)182 public List<String> grep(Pattern pattern, List<String> lines) { 183 return lines.stream() 184 .filter(s -> pattern.matcher(s).find()) 185 .collect(Collectors.toList()); 186 } 187 188 /** 189 * Copies a file. 190 * If the given destination exists and is a directory, the copy is created 191 * in that directory. Otherwise, the copy will be placed at the destination, 192 * possibly overwriting any existing file. 193 * <p>Similar to the shell "cp" command: {@code cp from to}. 194 * @param from the file to be copied 195 * @param to where to copy the file 196 * @throws IOException if any error occurred while copying the file 197 */ copyFile(String from, String to)198 public void copyFile(String from, String to) throws IOException { 199 copyFile(Paths.get(from), Paths.get(to)); 200 } 201 202 /** 203 * Copies a file. 204 * If the given destination exists and is a directory, the copy is created 205 * in that directory. Otherwise, the copy will be placed at the destination, 206 * possibly overwriting any existing file. 207 * <p>Similar to the shell "cp" command: {@code cp from to}. 208 * @param from the file to be copied 209 * @param to where to copy the file 210 * @throws IOException if an error occurred while copying the file 211 */ copyFile(Path from, Path to)212 public void copyFile(Path from, Path to) throws IOException { 213 if (Files.isDirectory(to)) { 214 to = to.resolve(from.getFileName()); 215 } else { 216 Files.createDirectories(to.getParent()); 217 } 218 Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING); 219 } 220 221 /** 222 * Creates one of more directories. 223 * For each of the series of paths, a directory will be created, 224 * including any necessary parent directories. 225 * <p>Similar to the shell command: {@code mkdir -p paths}. 226 * @param paths the directories to be created 227 * @throws IOException if an error occurred while creating the directories 228 */ createDirectories(String... paths)229 public void createDirectories(String... paths) throws IOException { 230 if (paths.length == 0) 231 throw new IllegalArgumentException("no directories specified"); 232 for (String p : paths) 233 Files.createDirectories(Paths.get(p)); 234 } 235 236 /** 237 * Creates one or more directories. 238 * For each of the series of paths, a directory will be created, 239 * including any necessary parent directories. 240 * <p>Similar to the shell command: {@code mkdir -p paths}. 241 * @param paths the directories to be created 242 * @throws IOException if an error occurred while creating the directories 243 */ createDirectories(Path... paths)244 public void createDirectories(Path... paths) throws IOException { 245 if (paths.length == 0) 246 throw new IllegalArgumentException("no directories specified"); 247 for (Path p : paths) 248 Files.createDirectories(p); 249 } 250 251 /** 252 * Deletes one or more files, awaiting confirmation that the files 253 * no longer exist. Any directories to be deleted must be empty. 254 * <p>Similar to the shell command: {@code rm files}. 255 * @param files the names of the files to be deleted 256 * @throws IOException if an error occurred while deleting the files 257 */ deleteFiles(String... files)258 public void deleteFiles(String... files) throws IOException { 259 deleteFiles(List.of(files).stream().map(Paths::get).collect(Collectors.toList())); 260 } 261 262 /** 263 * Deletes one or more files, awaiting confirmation that the files 264 * no longer exist. Any directories to be deleted must be empty. 265 * <p>Similar to the shell command: {@code rm files}. 266 * @param paths the paths for the files to be deleted 267 * @throws IOException if an error occurred while deleting the files 268 */ deleteFiles(Path... paths)269 public void deleteFiles(Path... paths) throws IOException { 270 deleteFiles(List.of(paths)); 271 } 272 273 /** 274 * Deletes one or more files, awaiting confirmation that the files 275 * no longer exist. Any directories to be deleted must be empty. 276 * <p>Similar to the shell command: {@code rm files}. 277 * @param paths the paths for the files to be deleted 278 * @throws IOException if an error occurred while deleting the files 279 */ deleteFiles(List<Path> paths)280 public void deleteFiles(List<Path> paths) throws IOException { 281 if (paths.isEmpty()) 282 throw new IllegalArgumentException("no files specified"); 283 IOException ioe = null; 284 for (Path path : paths) { 285 ioe = deleteFile(path, ioe); 286 } 287 if (ioe != null) { 288 throw ioe; 289 } 290 ensureDeleted(paths); 291 } 292 293 /** 294 * Deletes all content of a directory (but not the directory itself), 295 * awaiting confirmation that the content has been deleted. 296 * @param root the directory to be cleaned 297 * @throws IOException if an error occurs while cleaning the directory 298 */ cleanDirectory(Path root)299 public void cleanDirectory(Path root) throws IOException { 300 if (!Files.isDirectory(root)) { 301 throw new IOException(root + " is not a directory"); 302 } 303 Files.walkFileTree(root, new SimpleFileVisitor<Path>() { 304 private IOException ioe = null; 305 // for each directory we visit, maintain a list of the files that we try to delete 306 private Deque<List<Path>> dirFiles = new LinkedList<>(); 307 308 @Override 309 public FileVisitResult visitFile(Path file, BasicFileAttributes a) throws IOException { 310 ioe = deleteFile(file, ioe); 311 dirFiles.peekFirst().add(file); 312 return FileVisitResult.CONTINUE; 313 } 314 315 @Override 316 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes a) throws IOException { 317 if (!dir.equals(root)) { 318 dirFiles.peekFirst().add(dir); 319 } 320 dirFiles.addFirst(new ArrayList<>()); 321 return FileVisitResult.CONTINUE; 322 } 323 324 @Override 325 public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { 326 if (e != null) { 327 throw e; 328 } 329 if (ioe != null) { 330 throw ioe; 331 } 332 ensureDeleted(dirFiles.removeFirst()); 333 if (!dir.equals(root)) { 334 ioe = deleteFile(dir, ioe); 335 } 336 return FileVisitResult.CONTINUE; 337 } 338 }); 339 } 340 341 /** 342 * Internal method to delete a file, using {@code Files.delete}. 343 * It does not wait to confirm deletion, nor does it retry. 344 * If an exception occurs it is either returned or added to the set of 345 * suppressed exceptions for an earlier exception. 346 * @param path the path for the file to be deleted 347 * @param ioe the earlier exception, or null 348 * @return the earlier exception or an exception that occurred while 349 * trying to delete the file 350 */ deleteFile(Path path, IOException ioe)351 private IOException deleteFile(Path path, IOException ioe) { 352 try { 353 Files.delete(path); 354 } catch (IOException e) { 355 if (ioe == null) { 356 ioe = e; 357 } else { 358 ioe.addSuppressed(e); 359 } 360 } 361 return ioe; 362 } 363 364 /** 365 * Wait until it is confirmed that a set of files have been deleted. 366 * @param paths the paths for the files to be deleted 367 * @throws IOException if a file has not been deleted 368 */ ensureDeleted(Collection<Path> paths)369 private void ensureDeleted(Collection<Path> paths) 370 throws IOException { 371 for (Path path : paths) { 372 ensureDeleted(path); 373 } 374 } 375 376 /** 377 * Wait until it is confirmed that a file has been deleted. 378 * @param path the path for the file to be deleted 379 * @throws IOException if problems occur while deleting the file 380 */ ensureDeleted(Path path)381 private void ensureDeleted(Path path) throws IOException { 382 long startTime = System.currentTimeMillis(); 383 do { 384 // Note: Files.notExists is not the same as !Files.exists 385 if (Files.notExists(path)) { 386 return; 387 } 388 System.gc(); // allow finalizers and cleaners to run 389 try { 390 Thread.sleep(RETRY_DELETE_MILLIS); 391 } catch (InterruptedException e) { 392 throw new IOException("Interrupted while waiting for file to be deleted: " + path, e); 393 } 394 } while ((System.currentTimeMillis() - startTime) <= MAX_RETRY_DELETE_MILLIS); 395 396 throw new IOException("File not deleted: " + path); 397 } 398 399 private static final int RETRY_DELETE_MILLIS = isWindows() ? (int)(500 * timeoutFactor): 0; 400 private static final int MAX_RETRY_DELETE_MILLIS = isWindows() ? (int)(15 * 1000 * timeoutFactor) : 0; 401 402 /** 403 * Moves a file. 404 * If the given destination exists and is a directory, the file will be moved 405 * to that directory. Otherwise, the file will be moved to the destination, 406 * possibly overwriting any existing file. 407 * <p>Similar to the shell "mv" command: {@code mv from to}. 408 * @param from the file to be moved 409 * @param to where to move the file 410 * @throws IOException if an error occurred while moving the file 411 */ moveFile(String from, String to)412 public void moveFile(String from, String to) throws IOException { 413 moveFile(Paths.get(from), Paths.get(to)); 414 } 415 416 /** 417 * Moves a file. 418 * If the given destination exists and is a directory, the file will be moved 419 * to that directory. Otherwise, the file will be moved to the destination, 420 * possibly overwriting any existing file. 421 * <p>Similar to the shell "mv" command: {@code mv from to}. 422 * @param from the file to be moved 423 * @param to where to move the file 424 * @throws IOException if an error occurred while moving the file 425 */ moveFile(Path from, Path to)426 public void moveFile(Path from, Path to) throws IOException { 427 if (Files.isDirectory(to)) { 428 to = to.resolve(from.getFileName()); 429 } else { 430 Files.createDirectories(to.getParent()); 431 } 432 Files.move(from, to, StandardCopyOption.REPLACE_EXISTING); 433 } 434 435 /** 436 * Reads the lines of a file. 437 * The file is read using the default character encoding. 438 * @param path the file to be read 439 * @return the lines of the file 440 * @throws IOException if an error occurred while reading the file 441 */ readAllLines(String path)442 public List<String> readAllLines(String path) throws IOException { 443 return readAllLines(path, null); 444 } 445 446 /** 447 * Reads the lines of a file. 448 * The file is read using the default character encoding. 449 * @param path the file to be read 450 * @return the lines of the file 451 * @throws IOException if an error occurred while reading the file 452 */ readAllLines(Path path)453 public List<String> readAllLines(Path path) throws IOException { 454 return readAllLines(path, null); 455 } 456 457 /** 458 * Reads the lines of a file using the given encoding. 459 * @param path the file to be read 460 * @param encoding the encoding to be used to read the file 461 * @return the lines of the file. 462 * @throws IOException if an error occurred while reading the file 463 */ readAllLines(String path, String encoding)464 public List<String> readAllLines(String path, String encoding) throws IOException { 465 return readAllLines(Paths.get(path), encoding); 466 } 467 468 /** 469 * Reads the lines of a file using the given encoding. 470 * @param path the file to be read 471 * @param encoding the encoding to be used to read the file 472 * @return the lines of the file 473 * @throws IOException if an error occurred while reading the file 474 */ readAllLines(Path path, String encoding)475 public List<String> readAllLines(Path path, String encoding) throws IOException { 476 return Files.readAllLines(path, getCharset(encoding)); 477 } 478 getCharset(String encoding)479 private Charset getCharset(String encoding) { 480 return (encoding == null) ? Charset.defaultCharset() : Charset.forName(encoding); 481 } 482 483 /** 484 * Find .java files in one or more directories. 485 * <p>Similar to the shell "find" command: {@code find paths -name \*.java}. 486 * @param paths the directories in which to search for .java files 487 * @return the .java files found 488 * @throws IOException if an error occurred while searching for files 489 */ findJavaFiles(Path... paths)490 public Path[] findJavaFiles(Path... paths) throws IOException { 491 return findFiles(".java", paths); 492 } 493 494 /** 495 * Find files matching the file extension, in one or more directories. 496 * <p>Similar to the shell "find" command: {@code find paths -name \*.ext}. 497 * @param fileExtension the extension to search for 498 * @param paths the directories in which to search for files 499 * @return the files matching the file extension 500 * @throws IOException if an error occurred while searching for files 501 */ findFiles(String fileExtension, Path... paths)502 public Path[] findFiles(String fileExtension, Path... paths) throws IOException { 503 Set<Path> files = new TreeSet<>(); // use TreeSet to force a consistent order 504 for (Path p : paths) { 505 Files.walkFileTree(p, new SimpleFileVisitor<Path>() { 506 @Override 507 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 508 throws IOException { 509 if (file.getFileName().toString().endsWith(fileExtension)) { 510 files.add(file); 511 } 512 return FileVisitResult.CONTINUE; 513 } 514 }); 515 } 516 return files.toArray(new Path[files.size()]); 517 } 518 519 /** 520 * Writes a file containing the given content. 521 * Any necessary directories for the file will be created. 522 * @param path where to write the file 523 * @param content the content for the file 524 * @throws IOException if an error occurred while writing the file 525 */ writeFile(String path, String content)526 public void writeFile(String path, String content) throws IOException { 527 writeFile(Paths.get(path), content); 528 } 529 530 /** 531 * Writes a file containing the given content. 532 * Any necessary directories for the file will be created. 533 * @param path where to write the file 534 * @param content the content for the file 535 * @throws IOException if an error occurred while writing the file 536 */ writeFile(Path path, String content)537 public void writeFile(Path path, String content) throws IOException { 538 Path dir = path.getParent(); 539 if (dir != null) 540 Files.createDirectories(dir); 541 try (BufferedWriter w = Files.newBufferedWriter(path)) { 542 w.write(content); 543 } 544 } 545 546 /** 547 * Writes one or more files containing Java source code. 548 * For each file to be written, the filename will be inferred from the 549 * given base directory, the package declaration (if present) and from the 550 * the name of the first class, interface or enum declared in the file. 551 * <p>For example, if the base directory is /my/dir/ and the content 552 * contains "package p; class C { }", the file will be written to 553 * /my/dir/p/C.java. 554 * <p>Note: the content is analyzed using regular expressions; 555 * errors can occur if any contents have initial comments that might trip 556 * up the analysis. 557 * @param dir the base directory 558 * @param contents the contents of the files to be written 559 * @throws IOException if an error occurred while writing any of the files. 560 */ writeJavaFiles(Path dir, String... contents)561 public void writeJavaFiles(Path dir, String... contents) throws IOException { 562 if (contents.length == 0) 563 throw new IllegalArgumentException("no content specified for any files"); 564 for (String c : contents) { 565 new JavaSource(c).write(dir); 566 } 567 } 568 569 /** 570 * Returns the path for the binary of a JDK tool within {@link testJDK}. 571 * @param tool the name of the tool 572 * @return the path of the tool 573 */ getJDKTool(String tool)574 public Path getJDKTool(String tool) { 575 return Paths.get(testJDK, "bin", tool); 576 } 577 578 /** 579 * Returns a string representing the contents of an {@code Iterable} as a list. 580 * @param <T> the type parameter of the {@code Iterable} 581 * @param items the iterable 582 * @return the string 583 */ toString(Iterable<T> items)584 <T> String toString(Iterable<T> items) { 585 return StreamSupport.stream(items.spliterator(), false) 586 .map(Objects::toString) 587 .collect(Collectors.joining(",", "[", "]")); 588 } 589 590 591 /** 592 * An in-memory Java source file. 593 * It is able to extract the file name from simple source text using 594 * regular expressions. 595 */ 596 public static class JavaSource extends SimpleJavaFileObject { 597 private final String source; 598 599 /** 600 * Creates a in-memory file object for Java source code. 601 * @param className the name of the class 602 * @param source the source text 603 */ JavaSource(String className, String source)604 public JavaSource(String className, String source) { 605 super(URI.create(className), JavaFileObject.Kind.SOURCE); 606 this.source = source; 607 } 608 609 /** 610 * Creates a in-memory file object for Java source code. 611 * The name of the class will be inferred from the source code. 612 * @param source the source text 613 */ JavaSource(String source)614 public JavaSource(String source) { 615 super(URI.create(getJavaFileNameFromSource(source)), 616 JavaFileObject.Kind.SOURCE); 617 this.source = source; 618 } 619 620 /** 621 * Writes the source code to a file in the current directory. 622 * @throws IOException if there is a problem writing the file 623 */ write()624 public void write() throws IOException { 625 write(currDir); 626 } 627 628 /** 629 * Writes the source code to a file in a specified directory. 630 * @param dir the directory 631 * @throws IOException if there is a problem writing the file 632 */ write(Path dir)633 public void write(Path dir) throws IOException { 634 Path file = dir.resolve(getJavaFileNameFromSource(source)); 635 Files.createDirectories(file.getParent()); 636 try (BufferedWriter out = Files.newBufferedWriter(file)) { 637 out.write(source.replace("\n", lineSeparator)); 638 } 639 } 640 641 @Override getCharContent(boolean ignoreEncodingErrors)642 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 643 return source; 644 } 645 646 private static Pattern commentPattern = 647 Pattern.compile("(?s)(\\s+//.*?\n|/\\*.*?\\*/)"); 648 private static Pattern modulePattern = 649 Pattern.compile("module\\s+((?:\\w+\\.)*)"); 650 private static Pattern packagePattern = 651 Pattern.compile("package\\s+(((?:\\w+\\.)*)(?:\\w+))"); 652 private static Pattern classPattern = 653 Pattern.compile("(?:public\\s+)?(?:class|enum|interface)\\s+(\\w+)"); 654 655 /** 656 * Extracts the Java file name from the class declaration. 657 * This method is intended for simple files and uses regular expressions. 658 * Comments in the source are stripped before looking for the 659 * declarations from which the name is derived. 660 */ getJavaFileNameFromSource(String source)661 static String getJavaFileNameFromSource(String source) { 662 StringBuilder sb = new StringBuilder(); 663 Matcher matcher = commentPattern.matcher(source); 664 int start = 0; 665 while (matcher.find()) { 666 sb.append(source.substring(start, matcher.start())); 667 start = matcher.end(); 668 } 669 sb.append(source.substring(start)); 670 source = sb.toString(); 671 672 String packageName = null; 673 674 matcher = modulePattern.matcher(source); 675 if (matcher.find()) 676 return "module-info.java"; 677 678 matcher = packagePattern.matcher(source); 679 if (matcher.find()) 680 packageName = matcher.group(1).replace(".", "/"); 681 682 matcher = classPattern.matcher(source); 683 if (matcher.find()) { 684 String className = matcher.group(1) + ".java"; 685 return (packageName == null) ? className : packageName + "/" + className; 686 } else if (packageName != null) { 687 return packageName + "/package-info.java"; 688 } else { 689 throw new Error("Could not extract the java class " + 690 "name from the provided source"); 691 } 692 } 693 } 694 695 /** 696 * Extracts the Java file name from the class declaration. 697 * This method is intended for simple files and uses regular expressions, 698 * so comments matching the pattern can make the method fail. 699 * @deprecated This is a legacy method for compatibility with ToolBox v1. 700 * Use {@link JavaSource#getName JavaSource.getName} instead. 701 * @param source the source text 702 * @return the Java file name inferred from the source 703 */ 704 @Deprecated getJavaFileNameFromSource(String source)705 public static String getJavaFileNameFromSource(String source) { 706 return JavaSource.getJavaFileNameFromSource(source); 707 } 708 709 /** 710 * A memory file manager, for saving generated files in memory. 711 * The file manager delegates to a separate file manager for listing and 712 * reading input files. 713 */ 714 public static class MemoryFileManager extends ForwardingJavaFileManager { 715 private interface Content { getBytes()716 byte[] getBytes(); getString()717 String getString(); 718 } 719 720 /** 721 * Maps binary class names to generated content. 722 */ 723 private final Map<Location, Map<String, Content>> files; 724 725 /** 726 * Construct a memory file manager which stores output files in memory, 727 * and delegates to a default file manager for input files. 728 */ MemoryFileManager()729 public MemoryFileManager() { 730 this(ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null)); 731 } 732 733 /** 734 * Construct a memory file manager which stores output files in memory, 735 * and delegates to a specified file manager for input files. 736 * @param fileManager the file manager to be used for input files 737 */ MemoryFileManager(JavaFileManager fileManager)738 public MemoryFileManager(JavaFileManager fileManager) { 739 super(fileManager); 740 files = new HashMap<>(); 741 } 742 743 @Override getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling)744 public JavaFileObject getJavaFileForOutput(Location location, 745 String name, 746 JavaFileObject.Kind kind, 747 FileObject sibling) 748 { 749 return new MemoryFileObject(location, name, kind); 750 } 751 752 /** 753 * Returns the set of names of files that have been written to a given 754 * location. 755 * @param location the location 756 * @return the set of file names 757 */ getFileNames(Location location)758 public Set<String> getFileNames(Location location) { 759 Map<String, Content> filesForLocation = files.get(location); 760 return (filesForLocation == null) 761 ? Collections.emptySet() : filesForLocation.keySet(); 762 } 763 764 /** 765 * Returns the content written to a file in a given location, 766 * or null if no such file has been written. 767 * @param location the location 768 * @param name the name of the file 769 * @return the content as an array of bytes 770 */ getFileBytes(Location location, String name)771 public byte[] getFileBytes(Location location, String name) { 772 Content content = getFile(location, name); 773 return (content == null) ? null : content.getBytes(); 774 } 775 776 /** 777 * Returns the content written to a file in a given location, 778 * or null if no such file has been written. 779 * @param location the location 780 * @param name the name of the file 781 * @return the content as a string 782 */ getFileString(Location location, String name)783 public String getFileString(Location location, String name) { 784 Content content = getFile(location, name); 785 return (content == null) ? null : content.getString(); 786 } 787 getFile(Location location, String name)788 private Content getFile(Location location, String name) { 789 Map<String, Content> filesForLocation = files.get(location); 790 return (filesForLocation == null) ? null : filesForLocation.get(name); 791 } 792 save(Location location, String name, Content content)793 private void save(Location location, String name, Content content) { 794 Map<String, Content> filesForLocation = files.get(location); 795 if (filesForLocation == null) 796 files.put(location, filesForLocation = new HashMap<>()); 797 filesForLocation.put(name, content); 798 } 799 800 /** 801 * A writable file object stored in memory. 802 */ 803 private class MemoryFileObject extends SimpleJavaFileObject { 804 private final Location location; 805 private final String name; 806 807 /** 808 * Constructs a memory file object. 809 * @param name binary name of the class to be stored in this file object 810 */ MemoryFileObject(Location location, String name, JavaFileObject.Kind kind)811 MemoryFileObject(Location location, String name, JavaFileObject.Kind kind) { 812 super(URI.create("mfm:///" + name.replace('.','/') + kind.extension), 813 Kind.CLASS); 814 this.location = location; 815 this.name = name; 816 } 817 818 @Override openOutputStream()819 public OutputStream openOutputStream() { 820 return new FilterOutputStream(new ByteArrayOutputStream()) { 821 @Override 822 public void close() throws IOException { 823 out.close(); 824 byte[] bytes = ((ByteArrayOutputStream) out).toByteArray(); 825 save(location, name, new Content() { 826 @Override 827 public byte[] getBytes() { 828 return bytes; 829 } 830 @Override 831 public String getString() { 832 return new String(bytes); 833 } 834 835 }); 836 } 837 }; 838 } 839 840 @Override openWriter()841 public Writer openWriter() { 842 return new FilterWriter(new StringWriter()) { 843 @Override 844 public void close() throws IOException { 845 out.close(); 846 String text = ((StringWriter) out).toString(); 847 save(location, name, new Content() { 848 @Override 849 public byte[] getBytes() { 850 return text.getBytes(); 851 } 852 @Override 853 public String getString() { 854 return text; 855 } 856 857 }); 858 } 859 }; 860 } 861 } 862 } 863 } 864 865