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