1 /*
2  * Copyright (c) 2012, 2020, 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.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package jdk.incubator.jpackage.internal;
27 
28 import java.io.*;
29 import java.lang.reflect.InvocationHandler;
30 import java.lang.reflect.Method;
31 import java.lang.reflect.Proxy;
32 import java.nio.file.FileVisitResult;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.SimpleFileVisitor;
36 import java.nio.file.StandardCopyOption;
37 import java.nio.file.attribute.BasicFileAttributes;
38 import java.util.*;
39 import javax.xml.stream.XMLOutputFactory;
40 import javax.xml.stream.XMLStreamException;
41 import javax.xml.stream.XMLStreamWriter;
42 
43 /**
44  * IOUtils
45  *
46  * A collection of static utility methods.
47  */
48 public class IOUtils {
49 
deleteRecursive(File path)50     public static void deleteRecursive(File path) throws IOException {
51         if (!path.exists()) {
52             return;
53         }
54         Path directory = path.toPath();
55         Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
56             @Override
57             public FileVisitResult visitFile(Path file,
58                             BasicFileAttributes attr) throws IOException {
59                 if (Platform.getPlatform() == Platform.WINDOWS) {
60                     Files.setAttribute(file, "dos:readonly", false);
61                 }
62                 Files.delete(file);
63                 return FileVisitResult.CONTINUE;
64             }
65 
66             @Override
67             public FileVisitResult preVisitDirectory(Path dir,
68                             BasicFileAttributes attr) throws IOException {
69                 if (Platform.getPlatform() == Platform.WINDOWS) {
70                     Files.setAttribute(dir, "dos:readonly", false);
71                 }
72                 return FileVisitResult.CONTINUE;
73             }
74 
75             @Override
76             public FileVisitResult postVisitDirectory(Path dir, IOException e)
77                             throws IOException {
78                 Files.delete(dir);
79                 return FileVisitResult.CONTINUE;
80             }
81         });
82     }
83 
copyRecursive(Path src, Path dest)84     public static void copyRecursive(Path src, Path dest) throws IOException {
85         copyRecursive(src, dest, List.of());
86     }
87 
copyRecursive(Path src, Path dest, final List<String> excludes)88     public static void copyRecursive(Path src, Path dest,
89             final List<String> excludes) throws IOException {
90         Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
91             @Override
92             public FileVisitResult preVisitDirectory(final Path dir,
93                     final BasicFileAttributes attrs) throws IOException {
94                 if (excludes.contains(dir.toFile().getName())) {
95                     return FileVisitResult.SKIP_SUBTREE;
96                 } else {
97                     Files.createDirectories(dest.resolve(src.relativize(dir)));
98                     return FileVisitResult.CONTINUE;
99                 }
100             }
101 
102             @Override
103             public FileVisitResult visitFile(final Path file,
104                     final BasicFileAttributes attrs) throws IOException {
105                 if (!excludes.contains(file.toFile().getName())) {
106                     Files.copy(file, dest.resolve(src.relativize(file)));
107                 }
108                 return FileVisitResult.CONTINUE;
109             }
110         });
111     }
112 
copyFile(File sourceFile, File destFile)113     public static void copyFile(File sourceFile, File destFile)
114             throws IOException {
115         Files.createDirectories(destFile.getParentFile().toPath());
116 
117         Files.copy(sourceFile.toPath(), destFile.toPath(),
118                    StandardCopyOption.REPLACE_EXISTING,
119                    StandardCopyOption.COPY_ATTRIBUTES);
120     }
121 
122     // run "launcher paramfile" in the directory where paramfile is kept
run(String launcher, File paramFile)123     public static void run(String launcher, File paramFile)
124             throws IOException {
125         if (paramFile != null && paramFile.exists()) {
126             ProcessBuilder pb =
127                     new ProcessBuilder(launcher, paramFile.getName());
128             pb = pb.directory(paramFile.getParentFile());
129             exec(pb);
130         }
131     }
132 
exec(ProcessBuilder pb)133     public static void exec(ProcessBuilder pb)
134             throws IOException {
135         exec(pb, false, null, false);
136     }
137 
138     // See JDK-8236282
139     // Reading output from some processes (currently known "hdiutil attach")
140     // might hang even if process already exited. Only possible workaround found
141     // in "hdiutil attach" case is to redirect the output to a temp file and then
142     // read this file back.
exec(ProcessBuilder pb, boolean writeOutputToFile)143     public static void exec(ProcessBuilder pb, boolean writeOutputToFile)
144             throws IOException {
145         exec(pb, false, null, writeOutputToFile);
146     }
147 
exec(ProcessBuilder pb, boolean testForPresenceOnly, PrintStream consumer)148     static void exec(ProcessBuilder pb, boolean testForPresenceOnly,
149             PrintStream consumer) throws IOException {
150         exec(pb, testForPresenceOnly, consumer, false);
151     }
152 
exec(ProcessBuilder pb, boolean testForPresenceOnly, PrintStream consumer, boolean writeOutputToFile)153     static void exec(ProcessBuilder pb, boolean testForPresenceOnly,
154             PrintStream consumer, boolean writeOutputToFile) throws IOException {
155         List<String> output = new ArrayList<>();
156         Executor exec = Executor.of(pb).setWriteOutputToFile(writeOutputToFile)
157                 .setOutputConsumer(lines -> {
158             lines.forEach(output::add);
159             if (consumer != null) {
160                 output.forEach(consumer::println);
161             }
162         });
163 
164         if (testForPresenceOnly) {
165             exec.execute();
166         } else {
167             exec.executeExpectSuccess();
168         }
169     }
170 
getProcessOutput(List<String> result, String... args)171     public static int getProcessOutput(List<String> result, String... args)
172             throws IOException, InterruptedException {
173 
174         ProcessBuilder pb = new ProcessBuilder(args);
175 
176         final Process p = pb.start();
177 
178         List<String> list = new ArrayList<>();
179 
180         final BufferedReader in =
181                 new BufferedReader(new InputStreamReader(p.getInputStream()));
182         final BufferedReader err =
183                 new BufferedReader(new InputStreamReader(p.getErrorStream()));
184 
185         Thread t = new Thread(() -> {
186             try {
187                 String line;
188                 while ((line = in.readLine()) != null) {
189                     list.add(line);
190                 }
191             } catch (IOException ioe) {
192                 Log.verbose(ioe);
193             }
194 
195             try {
196                 String line;
197                 while ((line = err.readLine()) != null) {
198                     Log.error(line);
199                 }
200             } catch (IOException ioe) {
201                   Log.verbose(ioe);
202             }
203         });
204         t.setDaemon(true);
205         t.start();
206 
207         int ret = p.waitFor();
208 
209         result.clear();
210         result.addAll(list);
211 
212         return ret;
213     }
214 
writableOutputDir(Path outdir)215     static void writableOutputDir(Path outdir) throws PackagerException {
216         File file = outdir.toFile();
217 
218         if (!file.isDirectory() && !file.mkdirs()) {
219             throw new PackagerException("error.cannot-create-output-dir",
220                     file.getAbsolutePath());
221         }
222         if (!file.canWrite()) {
223             throw new PackagerException("error.cannot-write-to-output-dir",
224                     file.getAbsolutePath());
225         }
226     }
227 
replaceSuffix(Path path, String suffix)228     public static Path replaceSuffix(Path path, String suffix) {
229         Path parent = path.getParent();
230         String filename = path.getFileName().toString().replaceAll("\\.[^.]*$", "")
231                 + Optional.ofNullable(suffix).orElse("");
232         return parent != null ? parent.resolve(filename) : Path.of(filename);
233     }
234 
addSuffix(Path path, String suffix)235     public static Path addSuffix(Path path, String suffix) {
236         Path parent = path.getParent();
237         String filename = path.getFileName().toString() + suffix;
238         return parent != null ? parent.resolve(filename) : Path.of(filename);
239     }
240 
getSuffix(Path path)241     public static String getSuffix(Path path) {
242         String filename = replaceSuffix(path.getFileName(), null).toString();
243         return path.getFileName().toString().substring(filename.length());
244     }
245 
246     @FunctionalInterface
247     public static interface XmlConsumer {
accept(XMLStreamWriter xml)248         void accept(XMLStreamWriter xml) throws IOException, XMLStreamException;
249     }
250 
createXml(Path dstFile, XmlConsumer xmlConsumer)251     public static void createXml(Path dstFile, XmlConsumer xmlConsumer) throws
252             IOException {
253         XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
254         Files.createDirectories(dstFile.getParent());
255         try (Writer w = Files.newBufferedWriter(dstFile)) {
256             // Wrap with pretty print proxy
257             XMLStreamWriter xml = (XMLStreamWriter) Proxy.newProxyInstance(
258                     XMLStreamWriter.class.getClassLoader(), new Class<?>[]{
259                 XMLStreamWriter.class}, new PrettyPrintHandler(
260                     xmlFactory.createXMLStreamWriter(w)));
261 
262             xml.writeStartDocument();
263             xmlConsumer.accept(xml);
264             xml.writeEndDocument();
265             xml.flush();
266             xml.close();
267         } catch (XMLStreamException ex) {
268             throw new IOException(ex);
269         } catch (IOException ex) {
270             throw ex;
271         }
272     }
273 
274     private static class PrettyPrintHandler implements InvocationHandler {
275 
PrettyPrintHandler(XMLStreamWriter target)276         PrettyPrintHandler(XMLStreamWriter target) {
277             this.target = target;
278         }
279 
280         @Override
invoke(Object proxy, Method method, Object[] args)281         public Object invoke(Object proxy, Method method, Object[] args) throws
282                 Throwable {
283             switch (method.getName()) {
284                 case "writeStartElement":
285                     // update state of parent node
286                     if (depth > 0) {
287                         hasChildElement.put(depth - 1, true);
288                     }
289                     // reset state of current node
290                     hasChildElement.put(depth, false);
291                     // indent for current depth
292                     target.writeCharacters(EOL);
293                     target.writeCharacters(repeat(depth, INDENT));
294                     depth++;
295                     break;
296                 case "writeEndElement":
297                     depth--;
298                     if (hasChildElement.get(depth) == true) {
299                         target.writeCharacters(EOL);
300                         target.writeCharacters(repeat(depth, INDENT));
301                     }
302                     break;
303                 case "writeProcessingInstruction":
304                 case "writeEmptyElement":
305                     // update state of parent node
306                     if (depth > 0) {
307                         hasChildElement.put(depth - 1, true);
308                     }
309                     // indent for current depth
310                     target.writeCharacters(EOL);
311                     target.writeCharacters(repeat(depth, INDENT));
312                     break;
313                 default:
314                     break;
315             }
316             method.invoke(target, args);
317             return null;
318         }
319 
repeat(int d, String s)320         private static String repeat(int d, String s) {
321             StringBuilder sb = new StringBuilder();
322             while (d-- > 0) {
323                 sb.append(s);
324             }
325             return sb.toString();
326         }
327 
328         private final XMLStreamWriter target;
329         private int depth = 0;
330         private final Map<Integer, Boolean> hasChildElement = new HashMap<>();
331         private static final String INDENT = "  ";
332         private static final String EOL = "\n";
333     }
334 }
335