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