1 /* 2 * Copyright (c) 2012, 2019, 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.channels.FileChannel; 33 import java.nio.file.FileVisitResult; 34 import java.nio.file.Files; 35 import java.nio.file.Path; 36 import java.nio.file.SimpleFileVisitor; 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 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 84 public static void copyRecursive(Path src, Path dest) throws IOException { 85 copyRecursive(src, dest, List.of()); 86 } 87 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 113 public static void copyFile(File sourceFile, File destFile) 114 throws IOException { 115 destFile.getParentFile().mkdirs(); 116 117 //recreate the file as existing copy may have weird permissions 118 destFile.delete(); 119 destFile.createNewFile(); 120 121 try (FileChannel source = new FileInputStream(sourceFile).getChannel(); 122 FileChannel destination = 123 new FileOutputStream(destFile).getChannel()) { 124 125 if (destination != null && source != null) { 126 destination.transferFrom(source, 0, source.size()); 127 } 128 } 129 130 //preserve executable bit! 131 if (sourceFile.canExecute()) { 132 destFile.setExecutable(true, false); 133 } 134 if (!sourceFile.canWrite()) { 135 destFile.setReadOnly(); 136 } 137 destFile.setReadable(true, false); 138 } 139 140 // run "launcher paramfile" in the directory where paramfile is kept 141 public static void run(String launcher, File paramFile) 142 throws IOException { 143 if (paramFile != null && paramFile.exists()) { 144 ProcessBuilder pb = 145 new ProcessBuilder(launcher, paramFile.getName()); 146 pb = pb.directory(paramFile.getParentFile()); 147 exec(pb); 148 } 149 } 150 151 public static void exec(ProcessBuilder pb) 152 throws IOException { 153 exec(pb, false, null); 154 } 155 156 static void exec(ProcessBuilder pb, boolean testForPresenseOnly, 157 PrintStream consumer) throws IOException { 158 List<String> output = new ArrayList<>(); 159 Executor exec = Executor.of(pb).setOutputConsumer(lines -> { 160 lines.forEach(output::add); 161 if (consumer != null) { 162 output.forEach(consumer::println); 163 } 164 }); 165 166 if (testForPresenseOnly) { 167 exec.execute(); 168 } else { 169 exec.executeExpectSuccess(); 170 } 171 } 172 173 public static int getProcessOutput(List<String> result, String... args) 174 throws IOException, InterruptedException { 175 176 ProcessBuilder pb = new ProcessBuilder(args); 177 178 final Process p = pb.start(); 179 180 List<String> list = new ArrayList<>(); 181 182 final BufferedReader in = 183 new BufferedReader(new InputStreamReader(p.getInputStream())); 184 final BufferedReader err = 185 new BufferedReader(new InputStreamReader(p.getErrorStream())); 186 187 Thread t = new Thread(() -> { 188 try { 189 String line; 190 while ((line = in.readLine()) != null) { 191 list.add(line); 192 } 193 } catch (IOException ioe) { 194 Log.verbose(ioe); 195 } 196 197 try { 198 String line; 199 while ((line = err.readLine()) != null) { 200 Log.error(line); 201 } 202 } catch (IOException ioe) { 203 Log.verbose(ioe); 204 } 205 }); 206 t.setDaemon(true); 207 t.start(); 208 209 int ret = p.waitFor(); 210 211 result.clear(); 212 result.addAll(list); 213 214 return ret; 215 } 216 217 static void writableOutputDir(Path outdir) throws PackagerException { 218 File file = outdir.toFile(); 219 220 if (!file.isDirectory() && !file.mkdirs()) { 221 throw new PackagerException("error.cannot-create-output-dir", 222 file.getAbsolutePath()); 223 } 224 if (!file.canWrite()) { 225 throw new PackagerException("error.cannot-write-to-output-dir", 226 file.getAbsolutePath()); 227 } 228 } 229 230 public static Path replaceSuffix(Path path, String suffix) { 231 Path parent = path.getParent(); 232 String filename = path.getFileName().toString().replaceAll("\\.[^.]*$", "") 233 + Optional.ofNullable(suffix).orElse(""); 234 return parent != null ? parent.resolve(filename) : Path.of(filename); 235 } 236 237 public static Path addSuffix(Path path, String suffix) { 238 Path parent = path.getParent(); 239 String filename = path.getFileName().toString() + suffix; 240 return parent != null ? parent.resolve(filename) : Path.of(filename); 241 } 242 243 public static String getSuffix(Path path) { 244 String filename = replaceSuffix(path.getFileName(), null).toString(); 245 return path.getFileName().toString().substring(filename.length()); 246 } 247 248 @FunctionalInterface 249 public static interface XmlConsumer { 250 void accept(XMLStreamWriter xml) throws IOException, XMLStreamException; 251 } 252 253 public static void createXml(Path dstFile, XmlConsumer xmlConsumer) throws 254 IOException { 255 XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance(); 256 try (Writer w = Files.newBufferedWriter(dstFile)) { 257 // Wrap with pretty print proxy 258 XMLStreamWriter xml = (XMLStreamWriter) Proxy.newProxyInstance( 259 XMLStreamWriter.class.getClassLoader(), new Class<?>[]{ 260 XMLStreamWriter.class}, new PrettyPrintHandler( 261 xmlFactory.createXMLStreamWriter(w))); 262 263 xml.writeStartDocument(); 264 xmlConsumer.accept(xml); 265 xml.writeEndDocument(); 266 xml.flush(); 267 xml.close(); 268 } catch (XMLStreamException ex) { 269 throw new IOException(ex); 270 } catch (IOException ex) { 271 throw ex; 272 } 273 } 274 275 private static class PrettyPrintHandler implements InvocationHandler { 276 277 PrettyPrintHandler(XMLStreamWriter target) { 278 this.target = target; 279 } 280 281 @Override 282 public Object invoke(Object proxy, Method method, Object[] args) throws 283 Throwable { 284 switch (method.getName()) { 285 case "writeStartElement": 286 // update state of parent node 287 if (depth > 0) { 288 hasChildElement.put(depth - 1, true); 289 } 290 // reset state of current node 291 hasChildElement.put(depth, false); 292 // indent for current depth 293 target.writeCharacters(EOL); 294 target.writeCharacters(repeat(depth, INDENT)); 295 depth++; 296 break; 297 case "writeEndElement": 298 depth--; 299 if (hasChildElement.get(depth) == true) { 300 target.writeCharacters(EOL); 301 target.writeCharacters(repeat(depth, INDENT)); 302 } 303 break; 304 case "writeProcessingInstruction": 305 case "writeEmptyElement": 306 // update state of parent node 307 if (depth > 0) { 308 hasChildElement.put(depth - 1, true); 309 } 310 // indent for current depth 311 target.writeCharacters(EOL); 312 target.writeCharacters(repeat(depth, INDENT)); 313 break; 314 default: 315 break; 316 } 317 method.invoke(target, args); 318 return null; 319 } 320 321 private static String repeat(int d, String s) { 322 StringBuilder sb = new StringBuilder(); 323 while (d-- > 0) { 324 sb.append(s); 325 } 326 return sb.toString(); 327 } 328 329 private final XMLStreamWriter target; 330 private int depth = 0; 331 private final Map<Integer, Boolean> hasChildElement = new HashMap<>(); 332 private static final String INDENT = " "; 333 private static final String EOL = "\n"; 334 } 335 }