1 /*
2  * Copyright (c) 2013, 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 import java.io.BufferedReader;
25 import java.io.File;
26 import java.io.FileNotFoundException;
27 import java.io.FileWriter;
28 import java.io.IOException;
29 import java.io.InputStreamReader;
30 import java.io.PrintWriter;
31 import java.io.StringWriter;
32 import java.net.URI;
33 import java.nio.charset.Charset;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.nio.file.Paths;
37 import java.nio.file.StandardOpenOption;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collection;
41 import java.util.Collections;
42 import java.util.EnumSet;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.regex.Matcher;
47 import java.util.regex.Pattern;
48 
49 import javax.tools.JavaCompiler;
50 import javax.tools.JavaFileObject;
51 import javax.tools.SimpleJavaFileObject;
52 import javax.tools.ToolProvider;
53 
54 import com.sun.source.util.JavacTask;
55 import com.sun.tools.javac.api.JavacTaskImpl;
56 
57 import sun.tools.jar.Main;
58 
59 import static java.nio.file.StandardCopyOption.*;
60 
61 /**
62  * Toolbox for jtreg tests.
63  */
64 
65 public class ToolBox {
66 
67     public static final String lineSeparator = System.getProperty("line.separator");
68     public static final String jdkUnderTest = System.getProperty("test.jdk");
69     public static final Path javaBinary = Paths.get(jdkUnderTest, "bin", "java");
70     public static final Path javacBinary = Paths.get(jdkUnderTest, "bin", "javac");
71 
72     public static final List<String> testToolVMOpts;
73     public static final List<String> testVMOpts;
74 
75     private static final Charset defaultCharset = Charset.defaultCharset();
76 
77     static final JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
78 
79     static {
80         String sysProp = System.getProperty("test.tool.vm.opts");
81         if (sysProp != null && sysProp.length() > 0) {
82             testToolVMOpts = Arrays.asList(sysProp.split("\\s+"));
83         } else {
84             testToolVMOpts = Collections.<String>emptyList();
85         }
86 
87         sysProp = System.getProperty("test.vm.opts");
88         if (sysProp != null && sysProp.length() > 0) {
89             testVMOpts = Arrays.asList(sysProp.split("\\s+"));
90         } else {
91             testVMOpts = Collections.<String>emptyList();
92         }
93     }
94 
95     /**
96      * The expected result of command-like method execution.
97      */
98     public enum Expect {SUCCESS, FAIL}
99 
100     enum AcceptedParams {
101         EXPECT,
102         SOURCES,
103         OPTIONS,
104         STD_OUTPUT,
105         ERR_OUTPUT,
106         EXTRA_ENV,
107     }
108 
109     enum OutputKind {STD, ERR}
110 
111     /**
112      * Helper class to abstract the processing of command's output.
113      */
114     static abstract class WriterHelper {
115         OutputKind kind;
pipeOutput(ProcessBuilder pb)116         public abstract void pipeOutput(ProcessBuilder pb);
readFromStream(Process p)117         public abstract void readFromStream(Process p) throws IOException;
addAll(Collection<? extends String> c)118         public abstract void addAll(Collection<? extends String> c) throws IOException;
119     }
120 
121     /**
122      * Helper class for redirecting command's output to a file.
123      */
124     static class FileWriterHelper extends WriterHelper {
125         File file;
126 
FileWriterHelper(File file, OutputKind kind)127         FileWriterHelper(File file, OutputKind kind) {
128             this.file = file;
129             this.kind = kind;
130         }
131 
132         @Override
pipeOutput(ProcessBuilder pb)133         public void pipeOutput(ProcessBuilder pb) {
134             if (file != null) {
135                 switch (kind) {
136                     case STD:
137                         pb.redirectInput(file);
138                         break;
139                     case ERR:
140                         pb.redirectError(file);
141                         break;
142                 }
143             }
144         }
145 
146         @Override
readFromStream(Process p)147         public void readFromStream(Process p) throws IOException {}
148 
149         @Override
addAll(Collection<? extends String> c)150         public void addAll(Collection<? extends String> c) throws IOException {
151             if (file.exists())
152                 Files.write(file.toPath(), c, defaultCharset,
153                         StandardOpenOption.WRITE, StandardOpenOption.APPEND);
154             else
155                 Files.write(file.toPath(), c, defaultCharset);
156         }
157     }
158 
159     /**
160      * Helper class for redirecting command's output to a String list.
161      */
162     static class ListWriterHelper extends WriterHelper {
163         List<String> list;
164 
ListWriterHelper(List<String> list, OutputKind kind)165         public ListWriterHelper(List<String> list, OutputKind kind) {
166             this.kind = kind;
167             this.list = list;
168         }
169 
170         @Override
pipeOutput(ProcessBuilder pb)171         public void pipeOutput(ProcessBuilder pb) {}
172 
173         @Override
readFromStream(Process p)174         public void readFromStream(Process p) throws IOException {
175             BufferedReader br = null;
176             switch (kind) {
177                 case STD:
178                     br = new BufferedReader(new InputStreamReader(p.getInputStream()));
179                     break;
180                 case ERR:
181                     br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
182                     break;
183             }
184             String line;
185             while ((line = br.readLine()) != null) {
186                 list.add(line);
187             }
188         }
189 
addAll(Collection<? extends String> c)190         public void addAll(Collection<? extends String> c) {
191             list.addAll(c);
192         }
193     }
194 
195     /**
196      * Simple factory class for creating a WriterHelper instance.
197      */
198     static class WriterHelperFactory {
make(File file, OutputKind kind)199         static WriterHelper make(File file, OutputKind kind) {
200             return new FileWriterHelper(file, kind);
201         }
202 
make(List<String> list, OutputKind kind)203         static WriterHelper make(List<String> list, OutputKind kind) {
204             return new ListWriterHelper(list, kind);
205         }
206     }
207 
208     /**
209      * A generic class for holding command's arguments.
210      */
211     public static abstract class GenericArgs <T extends GenericArgs> {
212         protected static List<Set<AcceptedParams>> minAcceptedParams;
213 
214         protected Set<AcceptedParams> currentParams =
215                 EnumSet.<AcceptedParams>noneOf(AcceptedParams.class);
216 
217         protected Expect whatToExpect;
218         protected WriterHelper stdOutput;
219         protected WriterHelper errOutput;
220         protected List<String> args = new ArrayList<>();
221         protected String[] argsArr;
222 
GenericArgs()223         protected GenericArgs() {
224             set(Expect.SUCCESS);
225         }
226 
set(Expect whatToExpt)227         public T set(Expect whatToExpt) {
228             currentParams.add(AcceptedParams.EXPECT);
229             this.whatToExpect = whatToExpt;
230             return (T)this;
231         }
232 
setStdOutput(List<String> stdOutput)233         public T setStdOutput(List<String> stdOutput) {
234             currentParams.add(AcceptedParams.STD_OUTPUT);
235             this.stdOutput = WriterHelperFactory.make(stdOutput, OutputKind.STD);
236             return (T)this;
237         }
238 
setStdOutput(File output)239         public T setStdOutput(File output) {
240             currentParams.add(AcceptedParams.STD_OUTPUT);
241             this.stdOutput = WriterHelperFactory.make(output, OutputKind.STD);
242             return (T)this;
243         }
244 
setErrOutput(List<String> errOutput)245         public T setErrOutput(List<String> errOutput) {
246             currentParams.add(AcceptedParams.ERR_OUTPUT);
247             this.errOutput = WriterHelperFactory.make(errOutput, OutputKind.ERR);
248             return (T)this;
249         }
250 
setErrOutput(File errOutput)251         public T setErrOutput(File errOutput) {
252             currentParams.add(AcceptedParams.ERR_OUTPUT);
253             this.errOutput = WriterHelperFactory.make(errOutput, OutputKind.ERR);
254             return (T)this;
255         }
256 
setAllArgs(String... args)257         public T setAllArgs(String... args) {
258             currentParams.add(AcceptedParams.OPTIONS);
259             this.argsArr = args;
260             return (T) this;
261         }
262 
263 
appendArgs(String... args)264         public T appendArgs(String... args) {
265             appendArgs(Arrays.asList(args));
266             return (T)this;
267         }
268 
appendArgs(Path... args)269         public T appendArgs(Path... args) {
270             if (args != null) {
271                 List<String> list = new ArrayList<>();
272                 for (int i = 0; i < args.length; i++) {
273                     if (args[i] != null) {
274                         list.add(args[i].toString());
275                     }
276                 }
277                 appendArgs(list);
278             }
279             return (T)this;
280         }
281 
appendArgs(List<String> args)282         public T appendArgs(List<String> args) {
283             if (args != null && args.size() > 0) {
284                 currentParams.add(AcceptedParams.OPTIONS);
285                 for (int i = 0; i < args.size(); i++) {
286                     if (args.get(i) != null) {
287                         this.args.add(args.get(i));
288                     }
289                 }
290             }
291             return (T)this;
292         }
293 
setOptions(List<String> options)294         public T setOptions(List<String> options) {
295             currentParams.add(AcceptedParams.OPTIONS);
296             this.args = options;
297             return (T)this;
298         }
299 
setOptions(String... options)300         public T setOptions(String... options) {
301             currentParams.add(AcceptedParams.OPTIONS);
302             this.args = Arrays.asList(options);
303             return (T)this;
304         }
305 
hasMinParams()306         public boolean hasMinParams() {
307             for (Set<AcceptedParams> minSet : minAcceptedParams) {
308                 if (currentParams.containsAll(minSet)) {
309                     return true;
310                 }
311             }
312             return false;
313         }
314     }
315 
316     /**
317      * A more specific class for holding javac-like command's arguments.
318      */
319     public static class JavaToolArgs extends GenericArgs<JavaToolArgs> {
320 
321         static {
322             minAcceptedParams = new ArrayList<>();
of(AcceptedParams.EXPECT, AcceptedParams.OPTIONS)323             minAcceptedParams.add(EnumSet.<AcceptedParams>of(
324                     AcceptedParams.EXPECT, AcceptedParams.OPTIONS));
of(AcceptedParams.EXPECT, AcceptedParams.SOURCES)325             minAcceptedParams.add(EnumSet.<AcceptedParams>of(
326                     AcceptedParams.EXPECT, AcceptedParams.SOURCES));
327         }
328 
329         protected List<? extends JavaFileObject> sources;
330 
JavaToolArgs()331         public JavaToolArgs() {
332             super();
333         }
334 
JavaToolArgs(Expect whatToExpt)335         public JavaToolArgs(Expect whatToExpt) {
336             super.set(whatToExpt);
337         }
338 
setSources(List<? extends JavaFileObject> sources)339         public JavaToolArgs setSources(List<? extends JavaFileObject> sources) {
340             currentParams.add(AcceptedParams.SOURCES);
341             this.sources = sources;
342             return this;
343         }
344 
setSources(JavaSource... sources)345         public JavaToolArgs setSources(JavaSource... sources) {
346             return setSources(Arrays.asList(sources));
347         }
348 
setSources(String... sources)349         public JavaToolArgs setSources(String... sources) {
350             List<JavaSource> javaSrcs = new ArrayList<>();
351             for (String source : sources) {
352                 javaSrcs.add(new JavaSource(source));
353             }
354             return setSources(javaSrcs);
355         }
356     }
357 
358     /**
359      * A more specific class for holding any command's arguments.
360      */
361     public static class AnyToolArgs extends GenericArgs<AnyToolArgs> {
362 
363         static {
364             minAcceptedParams = new ArrayList<>();
of(AcceptedParams.EXPECT, AcceptedParams.OPTIONS)365             minAcceptedParams.add(EnumSet.<AcceptedParams>of(
366                     AcceptedParams.EXPECT, AcceptedParams.OPTIONS));
367         }
368 
369         Map<String, String> extraEnv;
370 
AnyToolArgs()371         public AnyToolArgs() {
372             super();
373         }
374 
AnyToolArgs(Expect whatToExpt)375         public AnyToolArgs(Expect whatToExpt) {
376             set(whatToExpt);
377         }
378 
set(Map<String, String> extraEnv)379         public AnyToolArgs set(Map<String, String> extraEnv) {
380             currentParams.add(AcceptedParams.EXTRA_ENV);
381             this.extraEnv = extraEnv;
382             return this;
383         }
384     }
385 
386     /**
387      * Custom exception for bad command execution.
388      */
389     public static class CommandExecutionException extends Exception {
CommandExecutionException(List<String> command, Expect whatToExpt)390         CommandExecutionException(List<String> command, Expect whatToExpt) {
391             super(createMessage(command, whatToExpt));
392         }
393 
CommandExecutionException(Expect whatToExpt, String... command)394         CommandExecutionException(Expect whatToExpt, String... command) {
395             this(Arrays.asList(command), whatToExpt);
396         }
397 
createMessage(List<String> command, Expect whatToExpt)398         private static String createMessage(List<String> command, Expect whatToExpt) {
399             StringBuilder sb = new StringBuilder().append("Command : ");
400             sb.append(command.toString()).append(lineSeparator);
401             switch (whatToExpt) {
402                 case SUCCESS:
403                     sb.append("    has unexpectedly failed");
404                     break;
405                 case FAIL:
406                     sb.append("    has been unexpectedly successful");
407                     break;
408             }
409             return sb.toString();
410         }
411     }
412 
413     /**
414      * Custom exception for not equal resources.
415      */
416     public static class ResourcesNotEqualException extends Exception {
ResourcesNotEqualException(List<String> res1, List<String> res2)417         public ResourcesNotEqualException(List<String> res1, List<String> res2) {
418             super(createMessage(res1, res2));
419         }
420 
ResourcesNotEqualException(String line1, String line2)421         public ResourcesNotEqualException(String line1, String line2) {
422             super(createMessage(line1, line2));
423         }
424 
ResourcesNotEqualException(Path path1, Path path2)425         public ResourcesNotEqualException(Path path1, Path path2) {
426             super(createMessage(path1, path2));
427         }
428 
createMessage(Path path1, Path path2)429         private static String createMessage(Path path1, Path path2) {
430             return new StringBuilder()
431                     .append("The resources provided for comparison in paths \n")
432                     .append(path1.toString()).append(" and \n")
433                     .append(path2.toString()).append("are different").toString();
434         }
435 
createMessage(String line1, String line2)436         private static String createMessage(String line1, String line2) {
437             return new StringBuilder()
438                     .append("The resources provided for comparison are different at lines: \n")
439                     .append(line1).append(" and \n")
440                     .append(line2).toString();
441         }
442 
createMessage(List<String> res1, List<String> res2)443         private static String createMessage(List<String> res1, List<String> res2) {
444             return new StringBuilder()
445                     .append("The resources provided for comparison are different: \n")
446                     .append("Resource 1 is: ").append(res1).append("\n and \n")
447                     .append("Resource 2 is: ").append(res2).append("\n").toString();
448         }
449     }
450 
451     /**
452      * A javac compiler caller method.
453      */
javac(JavaToolArgs params)454     public static int javac(JavaToolArgs params)
455             throws CommandExecutionException, IOException {
456         if (params.hasMinParams()) {
457             if (params.argsArr != null) {
458                 return genericJavaCMD(JavaCMD.JAVAC, params);
459             } else {
460                 return genericJavaCMD(JavaCMD.JAVAC_API, params);
461             }
462         }
463         throw new AssertionError("javac command has been invoked with less parameters than needed");
464     }
465 
466     /**
467      * A javap calling method.
468      */
javap(JavaToolArgs params)469     public static String javap(JavaToolArgs params)
470             throws CommandExecutionException, IOException {
471         if (params.hasMinParams()) {
472             List<String> list = new ArrayList<>();
473             params.setErrOutput(list);
474             genericJavaCMD(JavaCMD.JAVAP, params);
475             return listToString(list);
476         }
477         throw new AssertionError("javap command has been invoked with less parameters than needed");
478     }
479 
480     /**
481      * A javah calling method.
482      */
javah(JavaToolArgs params)483     public static int javah(JavaToolArgs params)
484             throws CommandExecutionException, IOException {
485         if (params.hasMinParams()) {
486             return genericJavaCMD(JavaCMD.JAVAH, params);
487         }
488         throw new AssertionError("javah command has been invoked with less parameters than needed");
489     }
490 
491     /**
492      * A enum class for langtools commands.
493      */
494     enum JavaCMD {
495         JAVAC {
496             @Override
run(JavaToolArgs params, PrintWriter pw)497             int run(JavaToolArgs params, PrintWriter pw) {
498                 return com.sun.tools.javac.Main.compile(params.argsArr, pw);
499             }
500         },
501         JAVAC_API {
502             @Override
run(JavaToolArgs params, PrintWriter pw)503             int run(JavaToolArgs params, PrintWriter pw) {
504                 JavacTask ct = (JavacTask)comp.getTask(pw, null, null,
505                         params.args, null, params.sources);
506                 return ((JavacTaskImpl)ct).doCall().exitCode;
507             }
508 
509             @Override
getName()510             String getName() {
511                 return "javac";
512             }
513 
514             @Override
getExceptionMsgContent(JavaToolArgs params)515             List<String> getExceptionMsgContent(JavaToolArgs params) {
516                 List<String> result = super.getExceptionMsgContent(params);
517                 for (JavaFileObject source : params.sources) {
518                     if (source instanceof JavaSource) {
519                         result.add(((JavaSource)source).name);
520                     }
521                 }
522                 return result;
523             }
524         },
525         JAVAH {
526             @Override
run(JavaToolArgs params, PrintWriter pw)527             int run(JavaToolArgs params, PrintWriter pw) {
528                 return com.sun.tools.javah.Main.run(params.argsArr, pw);
529             }
530         },
531         JAVAP {
532             @Override
run(JavaToolArgs params, PrintWriter pw)533             int run(JavaToolArgs params, PrintWriter pw) {
534                 return com.sun.tools.javap.Main.run(params.argsArr, pw);
535             }
536         };
537 
run(JavaToolArgs params, PrintWriter pw)538         abstract int run(JavaToolArgs params, PrintWriter pw);
539 
getName()540         String getName() {
541             return this.name().toLowerCase();
542         }
543 
getExceptionMsgContent(JavaToolArgs params)544         List<String> getExceptionMsgContent(JavaToolArgs params) {
545             List<String> result = new ArrayList<>();
546             result.add(getName());
547             result.addAll(params.argsArr != null ?
548                     Arrays.asList(params.argsArr) :
549                     params.args);
550             return result;
551         }
552     }
553 
554     /**
555      * A helper method for executing langtools commands.
556      */
genericJavaCMD( JavaCMD cmd, JavaToolArgs params)557     private static int genericJavaCMD(
558             JavaCMD cmd,
559             JavaToolArgs params)
560             throws CommandExecutionException, IOException {
561         int rc = 0;
562         StringWriter sw = null;
563         try (PrintWriter pw = (params.errOutput == null) ?
564                 null : new PrintWriter(sw = new StringWriter())) {
565             rc = cmd.run(params, pw);
566         }
567         String out = (sw == null) ? null : sw.toString();
568 
569         if (params.errOutput != null && (out != null) && !out.isEmpty()) {
570             params.errOutput.addAll(splitLines(out, lineSeparator));
571         }
572 
573         if ( (rc == 0 && params.whatToExpect == Expect.SUCCESS) ||
574              (rc != 0 && params.whatToExpect == Expect.FAIL) ) {
575             return rc;
576         }
577 
578         throw new CommandExecutionException(cmd.getExceptionMsgContent(params),
579                 params.whatToExpect);
580     }
581 
582     /**
583      * A jar calling method.
584      */
jar(String... params)585     public static boolean jar(String... params) throws CommandExecutionException {
586         Main jarGenerator = new Main(System.out, System.err, "jar");
587         boolean result = jarGenerator.run(params);
588         if (!result) {
589             List<String> command = new ArrayList<>();
590             command.add("jar");
591             command.addAll(Arrays.asList(params));
592             throw new CommandExecutionException(command, Expect.SUCCESS);
593         }
594         return result;
595     }
596 
597     /**
598      * A general command calling method.
599      */
executeCommand(AnyToolArgs params)600     public static int executeCommand(AnyToolArgs params)
601             throws CommandExecutionException, IOException, InterruptedException {
602         if (params.hasMinParams()) {
603             List<String> cmd = (params.args != null) ?
604                     params.args :
605                     Arrays.asList(params.argsArr);
606             return executeCommand(cmd, params.extraEnv, params.stdOutput,
607                     params.errOutput, params.whatToExpect);
608         }
609         throw new AssertionError("command has been invoked with less parameters than needed");
610     }
611 
612     /**
613      * A helper method for calling a general command.
614      */
executeCommand( List<String> command, Map<String, String> extraEnv, WriterHelper stdOutput, WriterHelper errOutput, Expect whatToExpt)615     private static int executeCommand(
616             List<String> command,
617             Map<String, String> extraEnv,
618             WriterHelper stdOutput,
619             WriterHelper errOutput,
620             Expect whatToExpt)
621             throws IOException, InterruptedException, CommandExecutionException {
622         ProcessBuilder pb = new ProcessBuilder(command);
623 
624         if (stdOutput != null) stdOutput.pipeOutput(pb);
625         if (errOutput != null) errOutput.pipeOutput(pb);
626 
627         if (extraEnv != null) {
628             pb.environment().putAll(extraEnv);
629         }
630 
631         Process p = pb.start();
632 
633         if (stdOutput != null) stdOutput.readFromStream(p);
634         if (errOutput != null) errOutput.readFromStream(p);
635 
636         int result = p.waitFor();
637         if ( (result == 0 && whatToExpt == Expect.SUCCESS) ||
638              (result != 0 && whatToExpt == Expect.FAIL) ) {
639             return result;
640         }
641 
642         throw new CommandExecutionException(command, whatToExpt);
643     }
644 
645     /**
646      * This set of methods can be used instead of diff when the only needed
647      * result is the equality or inequality of the two given resources.
648      *
649      * A resource can be a file or a String list.
650      */
compareLines(Path aPath, Path otherPath, String encoding)651     public static void compareLines(Path aPath, Path otherPath, String encoding)
652             throws FileNotFoundException, IOException, ResourcesNotEqualException {
653         compareLines(aPath, otherPath, encoding, false);
654     }
655 
compareLines( Path aPath, Path otherPath, String encoding, boolean trim)656     public static void compareLines(
657             Path aPath, Path otherPath, String encoding, boolean trim)
658             throws FileNotFoundException, IOException, ResourcesNotEqualException {
659         Charset charset = encoding != null ?
660                 Charset.forName(encoding) :
661                 defaultCharset;
662         List<String> list1 = Files.readAllLines(aPath, charset);
663         List<String> list2 = Files.readAllLines(otherPath, charset);
664         compareLines(list1, list2, trim);
665     }
666 
compareLines(Path path, List<String> strings, String encoding)667     public static void compareLines(Path path, List<String> strings, String encoding)
668             throws FileNotFoundException, IOException, ResourcesNotEqualException {
669         compareLines(path, strings, encoding, false);
670     }
671 
compareLines(Path path, List<String> strings, String encoding, boolean trim)672     public static void compareLines(Path path, List<String> strings,
673             String encoding, boolean trim)
674             throws FileNotFoundException, IOException, ResourcesNotEqualException {
675         Charset charset = encoding != null ?
676                 Charset.forName(encoding) :
677                 defaultCharset;
678         List<String> list = Files.readAllLines(path, charset);
679         compareLines(list, strings, trim);
680     }
681 
compareLines(List<String> list1, List<String> list2)682     public static void compareLines(List<String> list1, List<String> list2)
683             throws ResourcesNotEqualException {
684         compareLines(list1, list2, false);
685     }
686 
compareLines(List<String> list1, List<String> list2, boolean trim)687     public static void compareLines(List<String> list1,
688             List<String> list2, boolean trim) throws ResourcesNotEqualException {
689         if ((list1 == list2) || (list1 == null && list2 == null)) return;
690         if (list1.size() != list2.size())
691             throw new ResourcesNotEqualException(list1, list2);
692         int i = 0;
693         int j = 0;
694         while (i < list1.size() &&
695                j < list2.size() &&
696                equals(list1.get(i), list2.get(j), trim)) {
697             i++; j++;
698         }
699         if (!(i == list1.size() && j == list2.size()))
700             throw new ResourcesNotEqualException(list1, list2);
701     }
702 
equals(String s1, String s2, boolean trim)703     private static boolean equals(String s1, String s2, boolean trim) {
704         return (trim ? s1.trim().equals(s2.trim()) : s1.equals(s2));
705     }
706 
707     /**
708      * A set of simple grep-like methods, looks for regExpr in text.
709      * The content of text is split using the new line character as a pattern
710      * and later the regExpr is seek in every split line. If a match is found,
711      * the whole line is added to the result.
712      */
grep(String regExpr, String text, String sep)713     public static List<String> grep(String regExpr, String text, String sep) {
714         return grep(regExpr, splitLines(text, sep));
715     }
716 
grep(String regExpr, List<String> text)717     public static List<String> grep(String regExpr, List<String> text) {
718         List<String> result = new ArrayList<>();
719         Pattern pattern = Pattern.compile(regExpr);
720         for (String s : text) {
721             if (pattern.matcher(s).find()) {
722                 result.add(s);
723             }
724         }
725         return result;
726     }
727 
grep(String regExpr, File f)728     public static List<String> grep(String regExpr, File f)
729             throws IOException {
730         List<String> lines = Files.readAllLines(f.toPath(), defaultCharset);
731         return grep(regExpr, lines);
732     }
733 
734     /**
735      * A touch-like method.
736      */
touch(String fileName)737     public static boolean touch(String fileName) {
738         File file = new File(fileName);
739         return touch(file);
740     }
741 
touch(File file)742     public static boolean touch(File file) {
743         if (file.exists()) {
744             file.setLastModified(System.currentTimeMillis());
745             return true;
746         }
747         return false;
748     }
749 
createJavaFile(File outFile)750     public static void createJavaFile(File outFile) throws IOException {
751         createJavaFile(outFile, null);
752     }
753 
754     /**
755      * A method for creating a valid but very simple java file.
756      */
createJavaFile(File outFile, File superClass)757     public static void createJavaFile(File outFile, File superClass)
758             throws IOException {
759         String srcStr = "public class " + getSimpleName(outFile) + " ";
760         if (superClass != null) {
761             srcStr = srcStr.concat("extends " + getSimpleName(superClass) + " ");
762         }
763         srcStr = srcStr.concat("{}");
764         try (PrintWriter ps = new PrintWriter(new FileWriter(outFile))) {
765             ps.println(srcStr);
766         }
767     }
768 
769     /**
770      * Creates a java file name given its source.
771      * The file is created in the working directory, creating a directory
772      * tree if there is a package declaration.
773      */
createJavaFileFromSource(String source)774     public static void createJavaFileFromSource(String source) throws IOException {
775         createJavaFileFromSource(null, source);
776     }
777 
778     /**
779      * Creates a java file name given its source.
780      * The file is created in the working directory, creating a directory
781      * tree if there is a package declaration or the argument initialPath
782      * has a valid path.
783      *
784      * e.i. if initialPath is foo/ and the source is:
785      * package bar;
786      *
787      * public class bazz {}
788      *
789      * this method will create the file foo/bar/bazz.java in the working
790      * directory.
791      */
createJavaFileFromSource(Path initialPath, String source)792     public static void createJavaFileFromSource(Path initialPath,
793             String source) throws IOException {
794         String fileName = getJavaFileNameFromSource(source);
795         String dirTree = getDirTreeFromSource(source);
796         Path path = (dirTree != null) ?
797                 Paths.get(dirTree, fileName) :
798                 Paths.get(fileName);
799         path = (initialPath != null) ?
800                 initialPath.resolve(path):
801                 path;
802         writeFile(path, source);
803     }
804 
805     static Pattern publicClassPattern =
806             Pattern.compile("public\\s+(?:class|enum|interface){1}\\s+(\\w+)");
807     static Pattern packageClassPattern =
808             Pattern.compile("(?:class|enum|interface){1}\\s+(\\w+)");
809 
810     /**
811      * Extracts the java file name from the class declaration.
812      * This method is intended for simple files and uses regular expressions,
813      * so comments matching the pattern can make the method fail.
814      */
getJavaFileNameFromSource(String source)815     private static String getJavaFileNameFromSource(String source) {
816         String className = null;
817         Matcher matcher = publicClassPattern.matcher(source);
818         if (matcher.find()) {
819             className = matcher.group(1) + ".java";
820         } else {
821             matcher = packageClassPattern.matcher(source);
822             if (matcher.find()) {
823                 className = matcher.group(1) + ".java";
824             } else {
825                 throw new AssertionError("Could not extract the java class " +
826                         "name from the provided source");
827             }
828         }
829         return className;
830     }
831 
832     static Pattern packagePattern =
833             Pattern.compile("package\\s+(((?:\\w+\\.)*)(?:\\w+))");
834 
835     /**
836      * Extracts the path from the package declaration if present.
837      * This method is intended for simple files and uses regular expressions,
838      * so comments matching the pattern can make the method fail.
839      */
getDirTreeFromSource(String source)840     private static String getDirTreeFromSource(String source) {
841         Matcher matcher = packagePattern.matcher(source);
842         return matcher.find() ?
843             matcher.group(1).replace(".", File.separator) :
844             null;
845     }
846 
847     /**
848      * A method for creating a jar's manifest file with supplied data.
849      */
mkManifestWithClassPath(String mainClass, String... classes)850     public static void mkManifestWithClassPath(String mainClass,
851             String... classes) throws IOException {
852         List <String> lines = new ArrayList<>();
853 
854         StringBuilder sb = new StringBuilder("Class-Path: ".length() +
855                 classes[0].length()).append("Class-Path: ").append(classes[0]);
856         for (int i = 1; i < classes.length; i++) {
857             sb.append(" ").append(classes[i]);
858         }
859         lines.add(sb.toString());
860         if (mainClass != null) {
861             lines.add(new StringBuilder("Main-Class: ".length() +
862                       mainClass.length())
863                       .append("Main-Class: ")
864                       .append(mainClass).toString());
865         }
866         Files.write(Paths.get("MANIFEST.MF"), lines, null);
867     }
868 
869     /**
870      * A utility method to obtain the file name.
871      */
getSimpleName(File inFile)872     static String getSimpleName(File inFile) {
873         return inFile.toPath().getFileName().toString();
874     }
875 
876     /**
877      * A method to write to a file, the directory tree is created if needed.
878      */
writeFile(Path path, String body)879     public static File writeFile(Path path, String body) throws IOException {
880         File result;
881         if (path.getParent() != null) {
882             Files.createDirectories(path.getParent());
883         }
884         try (FileWriter out = new FileWriter(result = path.toAbsolutePath().toFile())) {
885             out.write(body);
886         }
887         return result;
888     }
889 
writeFile(String path, String body)890     public static File writeFile(String path, String body) throws IOException {
891         return writeFile(Paths.get(path), body);
892     }
893 
894     /**
895      * A rm-like method, the file is deleted only if it exists.
896      */
rm(Path path)897     public static void rm(Path path) throws Exception {
898         Files.deleteIfExists(path);
899     }
900 
rm(String filename)901     public static void rm(String filename) throws Exception {
902         rm(Paths.get(filename));
903     }
904 
rm(File f)905     public static void rm(File f) throws Exception {
906         rm(f.toPath());
907     }
908 
909     /**
910      * Copy source file to destination file.
911      */
copyFile(File destfile, File srcfile)912     public static void copyFile(File destfile, File srcfile)
913         throws IOException {
914         copyFile(destfile.toPath(), srcfile.toPath());
915     }
916 
copyFile(Path destPath, Path srcPath)917     public static void copyFile(Path destPath, Path srcPath)
918         throws IOException {
919         Files.createDirectories(destPath);
920         Files.copy(srcPath, destPath, REPLACE_EXISTING);
921     }
922 
923     /**
924      * Splits a String using the System's line separator character as splitting point.
925      */
splitLines(String lines, String sep)926     public static List<String> splitLines(String lines, String sep) {
927         return Arrays.asList(lines.split(sep));
928     }
929 
930     /**
931      * Converts a String list into one String by appending the System's line separator
932      * character after each component.
933      */
listToString(List<String> lines)934     private static String listToString(List<String> lines) {
935         StringBuilder sb = new StringBuilder();
936         for (String s : lines) {
937             sb.append(s).append(lineSeparator);
938         }
939         return sb.toString();
940     }
941 
942     /**
943      * Returns true if the OS is a Windows version.
944      */
isWindows()945     public static boolean isWindows() {
946         String osName = System.getProperty("os.name");
947         return osName.toUpperCase().startsWith("WINDOWS");
948     }
949 
950     /**
951      * Class representing an in-memory java source file. It is able to extract
952      * the file name from simple source codes using regular expressions.
953      */
954     public static class JavaSource extends SimpleJavaFileObject {
955         String source;
956         String name;
957 
JavaSource(String className, String source)958         public JavaSource(String className, String source) {
959             super(URI.create(className),
960                     JavaFileObject.Kind.SOURCE);
961             this.name = className;
962             this.source = source;
963         }
964 
JavaSource(String source)965         public JavaSource(String source) {
966             super(URI.create(getJavaFileNameFromSource(source)),
967                     JavaFileObject.Kind.SOURCE);
968             this.name = getJavaFileNameFromSource(source);
969             this.source = source;
970         }
971 
972         @Override
getCharContent(boolean ignoreEncodingErrors)973         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
974             return source;
975         }
976     }
977 }
978