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