Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/test/langtools/tools/lib/toolbox/ToolBox.java
41152 views
1
/*
2
* Copyright (c) 2013, 2021, 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
*/
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
*/
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
*/
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
* returning the strings that match the regular expression.
169
* @param regex the regular expression
170
* @param lines the strings to be filtered
171
* @return the strings matching the regular expression
172
*/
173
public List<String> grep(String regex, List<String> lines) {
174
return grep(Pattern.compile(regex), lines, true);
175
}
176
177
/**
178
* Filters a list of strings according to the given regular expression,
179
* returning the strings that match the regular expression.
180
* @param pattern the regular expression
181
* @param lines the strings to be filtered
182
* @return the strings matching the regular expression
183
*/
184
public List<String> grep(Pattern pattern, List<String> lines) {
185
return grep(pattern, lines, true);
186
}
187
188
/**
189
* Filters a list of strings according to the given regular expression,
190
* returning either the strings that match or the strings that do not match.
191
* @param regex the regular expression
192
* @param lines the strings to be filtered
193
* @param match if true, return the lines that match; otherwise if false, return the lines that do not match.
194
* @return the strings matching(or not matching) the regular expression
195
*/
196
public List<String> grep(String regex, List<String> lines, boolean match) {
197
return grep(Pattern.compile(regex), lines, match);
198
}
199
200
/**
201
* Filters a list of strings according to the given regular expression,
202
* returning either the strings that match or the strings that do not match.
203
* @param pattern the regular expression
204
* @param lines the strings to be filtered
205
* @param match if true, return the lines that match; otherwise if false, return the lines that do not match.
206
* @return the strings matching(or not matching) the regular expression
207
*/
208
public List<String> grep(Pattern pattern, List<String> lines, boolean match) {
209
return lines.stream()
210
.filter(s -> pattern.matcher(s).find() == match)
211
.collect(Collectors.toList());
212
}
213
214
/**
215
* Copies a file.
216
* If the given destination exists and is a directory, the copy is created
217
* in that directory. Otherwise, the copy will be placed at the destination,
218
* possibly overwriting any existing file.
219
* <p>Similar to the shell "cp" command: {@code cp from to}.
220
* @param from the file to be copied
221
* @param to where to copy the file
222
* @throws IOException if any error occurred while copying the file
223
*/
224
public void copyFile(String from, String to) throws IOException {
225
copyFile(Paths.get(from), Paths.get(to));
226
}
227
228
/**
229
* Copies a file.
230
* If the given destination exists and is a directory, the copy is created
231
* in that directory. Otherwise, the copy will be placed at the destination,
232
* possibly overwriting any existing file.
233
* <p>Similar to the shell "cp" command: {@code cp from to}.
234
* @param from the file to be copied
235
* @param to where to copy the file
236
* @throws IOException if an error occurred while copying the file
237
*/
238
public void copyFile(Path from, Path to) throws IOException {
239
if (Files.isDirectory(to)) {
240
to = to.resolve(from.getFileName());
241
} else {
242
Files.createDirectories(to.getParent());
243
}
244
Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);
245
}
246
247
/**
248
* Creates one of more directories.
249
* For each of the series of paths, a directory will be created,
250
* including any necessary parent directories.
251
* <p>Similar to the shell command: {@code mkdir -p paths}.
252
* @param paths the directories to be created
253
* @throws IOException if an error occurred while creating the directories
254
*/
255
public void createDirectories(String... paths) throws IOException {
256
if (paths.length == 0)
257
throw new IllegalArgumentException("no directories specified");
258
for (String p : paths)
259
Files.createDirectories(Paths.get(p));
260
}
261
262
/**
263
* Creates one or more directories.
264
* For each of the series of paths, a directory will be created,
265
* including any necessary parent directories.
266
* <p>Similar to the shell command: {@code mkdir -p paths}.
267
* @param paths the directories to be created
268
* @throws IOException if an error occurred while creating the directories
269
*/
270
public void createDirectories(Path... paths) throws IOException {
271
if (paths.length == 0)
272
throw new IllegalArgumentException("no directories specified");
273
for (Path p : paths)
274
Files.createDirectories(p);
275
}
276
277
/**
278
* Deletes one or more files, awaiting confirmation that the files
279
* no longer exist. Any directories to be deleted must be empty.
280
* <p>Similar to the shell command: {@code rm files}.
281
* @param files the names of the files to be deleted
282
* @throws IOException if an error occurred while deleting the files
283
*/
284
public void deleteFiles(String... files) throws IOException {
285
deleteFiles(List.of(files).stream().map(Paths::get).collect(Collectors.toList()));
286
}
287
288
/**
289
* Deletes one or more files, awaiting confirmation that the files
290
* no longer exist. Any directories to be deleted must be empty.
291
* <p>Similar to the shell command: {@code rm files}.
292
* @param paths the paths for the files to be deleted
293
* @throws IOException if an error occurred while deleting the files
294
*/
295
public void deleteFiles(Path... paths) throws IOException {
296
deleteFiles(List.of(paths));
297
}
298
299
/**
300
* Deletes one or more files, awaiting confirmation that the files
301
* no longer exist. Any directories to be deleted must be empty.
302
* <p>Similar to the shell command: {@code rm files}.
303
* @param paths the paths for the files to be deleted
304
* @throws IOException if an error occurred while deleting the files
305
*/
306
public void deleteFiles(List<Path> paths) throws IOException {
307
if (paths.isEmpty())
308
throw new IllegalArgumentException("no files specified");
309
IOException ioe = null;
310
for (Path path : paths) {
311
ioe = deleteFile(path, ioe);
312
}
313
if (ioe != null) {
314
throw ioe;
315
}
316
ensureDeleted(paths);
317
}
318
319
/**
320
* Deletes all content of a directory (but not the directory itself),
321
* awaiting confirmation that the content has been deleted.
322
* @param root the directory to be cleaned
323
* @throws IOException if an error occurs while cleaning the directory
324
*/
325
public void cleanDirectory(Path root) throws IOException {
326
if (!Files.isDirectory(root)) {
327
throw new IOException(root + " is not a directory");
328
}
329
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
330
private IOException ioe = null;
331
// for each directory we visit, maintain a list of the files that we try to delete
332
private Deque<List<Path>> dirFiles = new LinkedList<>();
333
334
@Override
335
public FileVisitResult visitFile(Path file, BasicFileAttributes a) throws IOException {
336
ioe = deleteFile(file, ioe);
337
dirFiles.peekFirst().add(file);
338
return FileVisitResult.CONTINUE;
339
}
340
341
@Override
342
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes a) throws IOException {
343
if (!dir.equals(root)) {
344
dirFiles.peekFirst().add(dir);
345
}
346
dirFiles.addFirst(new ArrayList<>());
347
return FileVisitResult.CONTINUE;
348
}
349
350
@Override
351
public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
352
if (e != null) {
353
throw e;
354
}
355
if (ioe != null) {
356
throw ioe;
357
}
358
ensureDeleted(dirFiles.removeFirst());
359
if (!dir.equals(root)) {
360
ioe = deleteFile(dir, ioe);
361
}
362
return FileVisitResult.CONTINUE;
363
}
364
});
365
}
366
367
/**
368
* Internal method to delete a file, using {@code Files.delete}.
369
* It does not wait to confirm deletion, nor does it retry.
370
* If an exception occurs it is either returned or added to the set of
371
* suppressed exceptions for an earlier exception.
372
* @param path the path for the file to be deleted
373
* @param ioe the earlier exception, or null
374
* @return the earlier exception or an exception that occurred while
375
* trying to delete the file
376
*/
377
private IOException deleteFile(Path path, IOException ioe) {
378
try {
379
Files.delete(path);
380
} catch (IOException e) {
381
if (ioe == null) {
382
ioe = e;
383
} else {
384
ioe.addSuppressed(e);
385
}
386
}
387
return ioe;
388
}
389
390
/**
391
* Wait until it is confirmed that a set of files have been deleted.
392
* @param paths the paths for the files to be deleted
393
* @throws IOException if a file has not been deleted
394
*/
395
private void ensureDeleted(Collection<Path> paths)
396
throws IOException {
397
for (Path path : paths) {
398
ensureDeleted(path);
399
}
400
}
401
402
/**
403
* Wait until it is confirmed that a file has been deleted.
404
* @param path the path for the file to be deleted
405
* @throws IOException if problems occur while deleting the file
406
*/
407
private void ensureDeleted(Path path) throws IOException {
408
long startTime = System.currentTimeMillis();
409
do {
410
// Note: Files.notExists is not the same as !Files.exists
411
if (Files.notExists(path)) {
412
return;
413
}
414
System.gc(); // allow finalizers and cleaners to run
415
try {
416
Thread.sleep(RETRY_DELETE_MILLIS);
417
} catch (InterruptedException e) {
418
throw new IOException("Interrupted while waiting for file to be deleted: " + path, e);
419
}
420
} while ((System.currentTimeMillis() - startTime) <= MAX_RETRY_DELETE_MILLIS);
421
422
throw new IOException("File not deleted: " + path);
423
}
424
425
private static final int RETRY_DELETE_MILLIS = isWindows() ? (int)(500 * timeoutFactor): 0;
426
private static final int MAX_RETRY_DELETE_MILLIS = isWindows() ? (int)(15 * 1000 * timeoutFactor) : 0;
427
428
/**
429
* Moves a file.
430
* If the given destination exists and is a directory, the file will be moved
431
* to that directory. Otherwise, the file will be moved to the destination,
432
* possibly overwriting any existing file.
433
* <p>Similar to the shell "mv" command: {@code mv from to}.
434
* @param from the file to be moved
435
* @param to where to move the file
436
* @throws IOException if an error occurred while moving the file
437
*/
438
public void moveFile(String from, String to) throws IOException {
439
moveFile(Paths.get(from), Paths.get(to));
440
}
441
442
/**
443
* Moves a file.
444
* If the given destination exists and is a directory, the file will be moved
445
* to that directory. Otherwise, the file will be moved to the destination,
446
* possibly overwriting any existing file.
447
* <p>Similar to the shell "mv" command: {@code mv from to}.
448
* @param from the file to be moved
449
* @param to where to move the file
450
* @throws IOException if an error occurred while moving the file
451
*/
452
public void moveFile(Path from, Path to) throws IOException {
453
if (Files.isDirectory(to)) {
454
to = to.resolve(from.getFileName());
455
} else {
456
Files.createDirectories(to.getParent());
457
}
458
Files.move(from, to, StandardCopyOption.REPLACE_EXISTING);
459
}
460
461
/**
462
* Reads the lines of a file.
463
* The file is read using the default character encoding.
464
* @param path the file to be read
465
* @return the lines of the file
466
* @throws IOException if an error occurred while reading the file
467
*/
468
public List<String> readAllLines(String path) throws IOException {
469
return readAllLines(path, null);
470
}
471
472
/**
473
* Reads the lines of a file.
474
* The file is read using the default character encoding.
475
* @param path the file to be read
476
* @return the lines of the file
477
* @throws IOException if an error occurred while reading the file
478
*/
479
public List<String> readAllLines(Path path) throws IOException {
480
return readAllLines(path, null);
481
}
482
483
/**
484
* Reads the lines of a file using the given encoding.
485
* @param path the file to be read
486
* @param encoding the encoding to be used to read the file
487
* @return the lines of the file.
488
* @throws IOException if an error occurred while reading the file
489
*/
490
public List<String> readAllLines(String path, String encoding) throws IOException {
491
return readAllLines(Paths.get(path), encoding);
492
}
493
494
/**
495
* Reads the lines of a file using the given encoding.
496
* @param path the file to be read
497
* @param encoding the encoding to be used to read the file
498
* @return the lines of the file
499
* @throws IOException if an error occurred while reading the file
500
*/
501
public List<String> readAllLines(Path path, String encoding) throws IOException {
502
return Files.readAllLines(path, getCharset(encoding));
503
}
504
505
private Charset getCharset(String encoding) {
506
return (encoding == null) ? Charset.defaultCharset() : Charset.forName(encoding);
507
}
508
509
/**
510
* Find .java files in one or more directories.
511
* <p>Similar to the shell "find" command: {@code find paths -name \*.java}.
512
* @param paths the directories in which to search for .java files
513
* @return the .java files found
514
* @throws IOException if an error occurred while searching for files
515
*/
516
public Path[] findJavaFiles(Path... paths) throws IOException {
517
return findFiles(".java", paths);
518
}
519
520
/**
521
* Find files matching the file extension, in one or more directories.
522
* <p>Similar to the shell "find" command: {@code find paths -name \*.ext}.
523
* @param fileExtension the extension to search for
524
* @param paths the directories in which to search for files
525
* @return the files matching the file extension
526
* @throws IOException if an error occurred while searching for files
527
*/
528
public Path[] findFiles(String fileExtension, Path... paths) throws IOException {
529
Set<Path> files = new TreeSet<>(); // use TreeSet to force a consistent order
530
for (Path p : paths) {
531
Files.walkFileTree(p, new SimpleFileVisitor<Path>() {
532
@Override
533
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
534
throws IOException {
535
if (file.getFileName().toString().endsWith(fileExtension)) {
536
files.add(file);
537
}
538
return FileVisitResult.CONTINUE;
539
}
540
});
541
}
542
return files.toArray(new Path[files.size()]);
543
}
544
545
/**
546
* Writes a file containing the given content.
547
* Any necessary directories for the file will be created.
548
* @param path where to write the file
549
* @param content the content for the file
550
* @throws IOException if an error occurred while writing the file
551
*/
552
public void writeFile(String path, String content) throws IOException {
553
writeFile(Paths.get(path), content);
554
}
555
556
/**
557
* Writes a file containing the given content.
558
* Any necessary directories for the file will be created.
559
* @param path where to write the file
560
* @param content the content for the file
561
* @throws IOException if an error occurred while writing the file
562
*/
563
public void writeFile(Path path, String content) throws IOException {
564
Path dir = path.getParent();
565
if (dir != null)
566
Files.createDirectories(dir);
567
try (BufferedWriter w = Files.newBufferedWriter(path)) {
568
w.write(content);
569
}
570
}
571
572
/**
573
* Writes one or more files containing Java source code.
574
* For each file to be written, the filename will be inferred from the
575
* given base directory, the package declaration (if present) and from the
576
* the name of the first class, interface or enum declared in the file.
577
* <p>For example, if the base directory is /my/dir/ and the content
578
* contains "package p; class C { }", the file will be written to
579
* /my/dir/p/C.java.
580
* <p>Note: the content is analyzed using regular expressions;
581
* errors can occur if any contents have initial comments that might trip
582
* up the analysis.
583
* @param dir the base directory
584
* @param contents the contents of the files to be written
585
* @throws IOException if an error occurred while writing any of the files.
586
*/
587
public void writeJavaFiles(Path dir, String... contents) throws IOException {
588
if (contents.length == 0)
589
throw new IllegalArgumentException("no content specified for any files");
590
for (String c : contents) {
591
new JavaSource(c).write(dir);
592
}
593
}
594
595
/**
596
* Returns the path for the binary of a JDK tool within {@link testJDK}.
597
* @param tool the name of the tool
598
* @return the path of the tool
599
*/
600
public Path getJDKTool(String tool) {
601
return Paths.get(testJDK, "bin", tool);
602
}
603
604
/**
605
* Returns a string representing the contents of an {@code Iterable} as a list.
606
* @param <T> the type parameter of the {@code Iterable}
607
* @param items the iterable
608
* @return the string
609
*/
610
<T> String toString(Iterable<T> items) {
611
return StreamSupport.stream(items.spliterator(), false)
612
.map(Objects::toString)
613
.collect(Collectors.joining(",", "[", "]"));
614
}
615
616
617
/**
618
* An in-memory Java source file.
619
* It is able to extract the file name from simple source text using
620
* regular expressions.
621
*/
622
public static class JavaSource extends SimpleJavaFileObject {
623
private final String source;
624
625
/**
626
* Creates a in-memory file object for Java source code.
627
* @param className the name of the class
628
* @param source the source text
629
*/
630
public JavaSource(String className, String source) {
631
super(URI.create(className), JavaFileObject.Kind.SOURCE);
632
this.source = source;
633
}
634
635
/**
636
* Creates a in-memory file object for Java source code.
637
* The name of the class will be inferred from the source code.
638
* @param source the source text
639
*/
640
public JavaSource(String source) {
641
super(URI.create(getJavaFileNameFromSource(source)),
642
JavaFileObject.Kind.SOURCE);
643
this.source = source;
644
}
645
646
/**
647
* Writes the source code to a file in the current directory.
648
* @throws IOException if there is a problem writing the file
649
*/
650
public void write() throws IOException {
651
write(currDir);
652
}
653
654
/**
655
* Writes the source code to a file in a specified directory.
656
* @param dir the directory
657
* @throws IOException if there is a problem writing the file
658
*/
659
public void write(Path dir) throws IOException {
660
Path file = dir.resolve(getJavaFileNameFromSource(source));
661
Files.createDirectories(file.getParent());
662
try (BufferedWriter out = Files.newBufferedWriter(file)) {
663
out.write(source.replace("\n", lineSeparator));
664
}
665
}
666
667
@Override
668
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
669
return source;
670
}
671
672
private static Pattern commentPattern =
673
Pattern.compile("(?s)(\\s+//.*?\n|/\\*.*?\\*/)");
674
private static Pattern modulePattern =
675
Pattern.compile("module\\s+((?:\\w+\\.)*)");
676
private static Pattern packagePattern =
677
Pattern.compile("package\\s+(((?:\\w+\\.)*)(?:\\w+))");
678
private static Pattern classPattern =
679
Pattern.compile("(?:public\\s+)?(?:class|enum|interface|record)\\s+(\\w+)");
680
681
/**
682
* Extracts the Java file name from the class declaration.
683
* This method is intended for simple files and uses regular expressions.
684
* Comments in the source are stripped before looking for the
685
* declarations from which the name is derived.
686
*/
687
static String getJavaFileNameFromSource(String source) {
688
StringBuilder sb = new StringBuilder();
689
Matcher matcher = commentPattern.matcher(source);
690
int start = 0;
691
while (matcher.find()) {
692
sb.append(source.substring(start, matcher.start()));
693
start = matcher.end();
694
}
695
sb.append(source.substring(start));
696
source = sb.toString();
697
698
String packageName = null;
699
700
matcher = modulePattern.matcher(source);
701
if (matcher.find())
702
return "module-info.java";
703
704
matcher = packagePattern.matcher(source);
705
if (matcher.find()) {
706
packageName = matcher.group(1).replace(".", "/");
707
validateName(packageName);
708
}
709
710
matcher = classPattern.matcher(source);
711
if (matcher.find()) {
712
String className = matcher.group(1) + ".java";
713
validateName(className);
714
return (packageName == null) ? className : packageName + "/" + className;
715
} else if (packageName != null) {
716
return packageName + "/package-info.java";
717
} else {
718
throw new Error("Could not extract the java class " +
719
"name from the provided source");
720
}
721
}
722
}
723
724
/**
725
* Extracts the Java file name from the class declaration.
726
* This method is intended for simple files and uses regular expressions,
727
* so comments matching the pattern can make the method fail.
728
* @deprecated This is a legacy method for compatibility with ToolBox v1.
729
* Use {@link JavaSource#getName JavaSource.getName} instead.
730
* @param source the source text
731
* @return the Java file name inferred from the source
732
*/
733
@Deprecated
734
public static String getJavaFileNameFromSource(String source) {
735
return JavaSource.getJavaFileNameFromSource(source);
736
}
737
738
private static final Set<String> RESERVED_NAMES = Set.of(
739
"con", "prn", "aux", "nul",
740
"com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9",
741
"lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9"
742
);
743
744
/**Validate if a given name is a valid file name
745
* or path name on known platforms.
746
*/
747
public static void validateName(String name) {
748
for (String part : name.split("\\.|/|\\\\")) {
749
if (RESERVED_NAMES.contains(part.toLowerCase(Locale.US))) {
750
throw new IllegalArgumentException("Name: " + name + " is" +
751
"a reserved name on Windows, " +
752
"and will not work!");
753
}
754
}
755
}
756
/**
757
* A memory file manager, for saving generated files in memory.
758
* The file manager delegates to a separate file manager for listing and
759
* reading input files.
760
*/
761
public static class MemoryFileManager extends ForwardingJavaFileManager {
762
private interface Content {
763
byte[] getBytes();
764
String getString();
765
}
766
767
/**
768
* Maps binary class names to generated content.
769
*/
770
private final Map<Location, Map<String, Content>> files;
771
772
/**
773
* Construct a memory file manager which stores output files in memory,
774
* and delegates to a default file manager for input files.
775
*/
776
public MemoryFileManager() {
777
this(ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null));
778
}
779
780
/**
781
* Construct a memory file manager which stores output files in memory,
782
* and delegates to a specified file manager for input files.
783
* @param fileManager the file manager to be used for input files
784
*/
785
public MemoryFileManager(JavaFileManager fileManager) {
786
super(fileManager);
787
files = new HashMap<>();
788
}
789
790
@Override
791
public JavaFileObject getJavaFileForOutput(Location location,
792
String name,
793
JavaFileObject.Kind kind,
794
FileObject sibling)
795
{
796
return new MemoryFileObject(location, name, kind);
797
}
798
799
/**
800
* Returns the set of names of files that have been written to a given
801
* location.
802
* @param location the location
803
* @return the set of file names
804
*/
805
public Set<String> getFileNames(Location location) {
806
Map<String, Content> filesForLocation = files.get(location);
807
return (filesForLocation == null)
808
? Collections.emptySet() : filesForLocation.keySet();
809
}
810
811
/**
812
* Returns the content written to a file in a given location,
813
* or null if no such file has been written.
814
* @param location the location
815
* @param name the name of the file
816
* @return the content as an array of bytes
817
*/
818
public byte[] getFileBytes(Location location, String name) {
819
Content content = getFile(location, name);
820
return (content == null) ? null : content.getBytes();
821
}
822
823
/**
824
* Returns the content written to a file in a given location,
825
* or null if no such file has been written.
826
* @param location the location
827
* @param name the name of the file
828
* @return the content as a string
829
*/
830
public String getFileString(Location location, String name) {
831
Content content = getFile(location, name);
832
return (content == null) ? null : content.getString();
833
}
834
835
private Content getFile(Location location, String name) {
836
Map<String, Content> filesForLocation = files.get(location);
837
return (filesForLocation == null) ? null : filesForLocation.get(name);
838
}
839
840
private void save(Location location, String name, Content content) {
841
Map<String, Content> filesForLocation = files.get(location);
842
if (filesForLocation == null)
843
files.put(location, filesForLocation = new HashMap<>());
844
filesForLocation.put(name, content);
845
}
846
847
/**
848
* A writable file object stored in memory.
849
*/
850
private class MemoryFileObject extends SimpleJavaFileObject {
851
private final Location location;
852
private final String name;
853
854
/**
855
* Constructs a memory file object.
856
* @param name binary name of the class to be stored in this file object
857
*/
858
MemoryFileObject(Location location, String name, JavaFileObject.Kind kind) {
859
super(URI.create("mfm:///" + name.replace('.','/') + kind.extension),
860
Kind.CLASS);
861
this.location = location;
862
this.name = name;
863
}
864
865
@Override
866
public OutputStream openOutputStream() {
867
return new FilterOutputStream(new ByteArrayOutputStream()) {
868
@Override
869
public void close() throws IOException {
870
out.close();
871
byte[] bytes = ((ByteArrayOutputStream) out).toByteArray();
872
save(location, name, new Content() {
873
@Override
874
public byte[] getBytes() {
875
return bytes;
876
}
877
@Override
878
public String getString() {
879
return new String(bytes);
880
}
881
882
});
883
}
884
};
885
}
886
887
@Override
888
public Writer openWriter() {
889
return new FilterWriter(new StringWriter()) {
890
@Override
891
public void close() throws IOException {
892
out.close();
893
String text = ((StringWriter) out).toString();
894
save(location, name, new Content() {
895
@Override
896
public byte[] getBytes() {
897
return text.getBytes();
898
}
899
@Override
900
public String getString() {
901
return text;
902
}
903
904
});
905
}
906
};
907
}
908
}
909
}
910
}
911
912
913