MATSIM
IOUtils.java
Go to the documentation of this file.
1 /* *********************************************************************** *
2  * project: org.matsim.*
3  * IOUtils.java
4  * *
5  * *********************************************************************** *
6  * *
7  * copyright : (C) 2007 by the members listed in the COPYING, *
8  * LICENSE and WARRANTY file. *
9  * email : info at matsim dot org *
10  * *
11  * *********************************************************************** *
12  * *
13  * This program is free software; you can redistribute it and/or modify *
14  * it under the terms of the GNU General Public License as published by *
15  * the Free Software Foundation; either version 2 of the License, or *
16  * (at your option) any later version. *
17  * See also COPYING, LICENSE and WARRANTY file *
18  * *
19  * *********************************************************************** */
20 
21 package org.matsim.core.utils.io;
22 
23 import com.github.luben.zstd.ZstdInputStream;
24 import com.github.luben.zstd.ZstdOutputStream;
25 import net.jpountz.lz4.LZ4FrameInputStream;
26 import net.jpountz.lz4.LZ4FrameOutputStream;
27 import org.apache.commons.compress.compressors.CompressorException;
28 import org.apache.commons.compress.compressors.CompressorStreamFactory;
29 import org.apache.logging.log4j.LogManager;
30 import org.apache.logging.log4j.Logger;
31 
32 import java.io.BufferedInputStream;
33 import java.io.BufferedOutputStream;
34 import java.io.BufferedReader;
35 import java.io.BufferedWriter;
36 import java.io.DataInputStream;
37 import java.io.EOFException;
38 import java.io.File;
39 import java.io.FileNotFoundException;
40 import java.io.FileOutputStream;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.io.InputStreamReader;
44 import java.io.OutputStream;
45 import java.io.OutputStreamWriter;
46 import java.io.PrintStream;
47 import java.io.UncheckedIOException;
48 import java.net.MalformedURLException;
49 import java.net.URISyntaxException;
50 import java.net.URL;
51 import java.nio.charset.Charset;
52 import java.nio.charset.StandardCharsets;
53 import java.nio.file.*;
54 import java.nio.file.attribute.BasicFileAttributes;
55 import java.security.GeneralSecurityException;
56 import java.util.Arrays;
57 import java.util.Locale;
58 import java.util.Map;
59 import java.util.TreeMap;
60 import java.util.zip.GZIPInputStream;
61 import java.util.zip.GZIPOutputStream;
62 
124 final public class IOUtils {
125 
126 /*
127 The following says something about URLs vs Strings as file"names" (by Marcel):
128 
129 Generell sollten File-Zugriffe entweder per URL oder per String erfolgen. Früher waren alles Strings, neu gibt es mit dem Config-Context auch URLs. Man sollte aber, wenn
130 man URLs verwendet, möglichst die ganze Aufruf-Kette als URL durchziehen, da es ansonsten Probleme geben kann (und eben nicht versuchen, die URL in einen String
131 umzuwandeln, wie ich in meiner Commit-Message geschrieben hatte).
132 
133 Da wird eine URL erzeugt und in einen String umgewandelt, was definitiv zu Problemen führen wird, wenn der Pfad Leerzeichen enthält.
134 
135 Zudem wird durch die Umwandlung in einen String dann im Code ein anderer Pfad aufgerufen (nämlich der für String-Pfade), welcher dann mit der verschachtelten URL für das File innerhalb eines Jars Probleme hat. In kurz:
136 
137  String urlString = "jar:file:/Users/kainagel/.m2/repository/org/matsim/matsim-examples/12.0-SNAPSHOT/matsim-examples-12.0-SNAPSHOT.jar!/test/scenarios/equil/config.xml”;
138  URL url = new URL(urlString);
139  InputStream is = new FileInputStream(new File(url.toURI()); // produces an exception, as it cannot access the file inside a Jar.
140  InputStream is = url.openStream(); // this works
141  InputStream is = new FileInputStream(new File(“file:/Users/kainagel/.m2/repository/org/matsim/matsim-examples/12.0-SNAPSHOT/matsim-examples-12.0-SNAPSHOT.jar"); // this works, as it is an actual file on the file system
142 
143 In ConfigUtils.loadConfig( String[] ) wird nun IOUtils.resolveFileOrResource(String) aufgerufen, das unter der Annahme, dass weil das Argument ein String ist, es sich um ein File im Filesystem handeln muss, versucht mittels new File() darauf zuzugreifen. Da es sich aber eigentlich um eine URL handelt, die dazu noch ein File innerhalb eines Jar spezifiziert, schlägt das fehl.
144 
145 Ich habe nun IOUtils.resolveFileOrResource(String) so angepasst, dass es versucht zu erkennen, ob das String-Argument eine URL ist, und in diesem Falle dies einfach als URL
146 zurückgibt und nicht noch all die anderen Tests durchführt, welche die Methode sonst macht.
147 
148 Damit scheint dann auch dein Test zu funktionieren.
149 
150 PR ist hier: https://github.com/matsim-org/matsim/pull/646
151 */
152 
156  private IOUtils() {
157  }
158 
159  private enum CompressionType { GZIP, LZ4, BZIP2, ZSTD }
160 
161  // Define compressions that can be used.
162  private static final Map<String, CompressionType> COMPRESSION_EXTENSIONS = new TreeMap<>();
163 
164  static {
165  COMPRESSION_EXTENSIONS.put("gz", CompressionType.GZIP);
166  COMPRESSION_EXTENSIONS.put("lz4", CompressionType.LZ4);
167  COMPRESSION_EXTENSIONS.put("bz2", CompressionType.BZIP2);
168  COMPRESSION_EXTENSIONS.put("zst", CompressionType.ZSTD);
169  }
170 
171  private static int zstdCompressionLevel = 6;
172 
173  public static void setZstdCompressionLevel(int level) {
174  if (level >= 1) {
175  zstdCompressionLevel = level;
176  } else {
177  logger.error("Invalid ZSTD compression level.");
178  }
179  }
180 
181  // Define a number of charsets that are / have been used.
182  public static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8;
183  public static final Charset CHARSET_WINDOWS_ISO88591 = StandardCharsets.ISO_8859_1;
184 
185  // We niw use Unix line endings everywhere.
186  public static final String NATIVE_NEWLINE = "\n";
187 
188  // Logger
189  private final static Logger logger = LogManager.getLogger(IOUtils.class);
190 
207  public static URL resolveFileOrResource(String filename) throws UncheckedIOException {
208  try {
209  // I) do not handle URLs
210  if (filename.startsWith("jar:file:") || filename.startsWith("file:") || filename.startsWith( "https:" ) || filename.startsWith( "http:" )) {
211  // looks like an URI
212  return new URL(filename);
213  }
214 
215  // II) Replace home identifier
216  if (filename.startsWith("~" + File.separator)) {
217  filename = System.getProperty("user.home") + filename.substring(1);
218  }
219 
220  // III.1) First, try to find the file in the file system
221  File file = new File(filename);
222 
223  if (file.exists()) {
224  logger.info(String.format("Resolved %s to %s", filename, file));
225  return file.toURI().toURL();
226  }
227 
228  // III.2) Try to find file with an additional postfix for compression
229  for (String postfix : COMPRESSION_EXTENSIONS.keySet()) {
230  file = new File(filename + "." + postfix);
231 
232  if (file.exists()) {
233  logger.info(String.format("Resolved %s to %s", filename, file));
234  return file.toURI().toURL();
235  }
236  }
237 
238  // IV.1) First, try to find the file in the class path
239  URL resource = IOUtils.class.getClassLoader().getResource(filename);
240 
241  if (resource != null) {
242  logger.info(String.format("Resolved %s to %s", filename, resource));
243  return resource;
244  }
245 
246  // IV.2) Second, try to find the resource with a compression extension
247  for (String postfix : COMPRESSION_EXTENSIONS.keySet()) {
248  resource = IOUtils.class.getClassLoader().getResource(filename + "." + postfix);
249 
250  if (resource != null) {
251  logger.info(String.format("Resolved %s to %s", filename, resource));
252  return resource;
253  }
254  }
255 
256  throw new FileNotFoundException(filename);
257  } catch (FileNotFoundException | MalformedURLException e) {
258  throw new UncheckedIOException(e);
259  }
260  }
261 
266  private static CompressionType getCompression(URL url) {
267 
268  // .enc extension is ignored
269  String[] segments = url.getPath().replace(".enc", "").split("\\.");
270  String lastExtension = segments[segments.length - 1];
271  return COMPRESSION_EXTENSIONS.get(lastExtension.toLowerCase(Locale.ROOT));
272  }
273 
281  public static InputStream getInputStream(URL url) throws UncheckedIOException {
282  try {
283  InputStream inputStream = url.openStream();
284 
285  if (url.getPath().endsWith(".enc"))
286  inputStream = CipherUtils.getDecryptedInput(inputStream);
287 
288  CompressionType compression = getCompression(url);
289  if (compression != null) {
290  switch (compression) {
291  case GZIP:
292  inputStream = new GZIPInputStream(inputStream);
293  break;
294  case LZ4:
295  inputStream = new LZ4FrameInputStream(inputStream);
296  break;
297  case BZIP2:
298  inputStream = new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.BZIP2, inputStream);
299  break;
300  case ZSTD:
301  inputStream = new ZstdInputStream(inputStream);
302  break;
303  }
304  }
305 
306  return new UnicodeInputStream(new BufferedInputStream(inputStream));
307  } catch (IOException e) {
308  throw new UncheckedIOException(e);
309  } catch (CompressorException | GeneralSecurityException e) {
310  throw new UncheckedIOException(new IOException(e));
311  }
312  }
313 
321  public static BufferedReader getBufferedReader(URL url, Charset charset) throws UncheckedIOException {
322  InputStream inputStream = getInputStream(url);
323  return new BufferedReader(new InputStreamReader(inputStream, charset));
324  }
325 
332  public static BufferedReader getBufferedReader(URL url) throws UncheckedIOException {
333  return getBufferedReader(url, CHARSET_UTF8);
334  }
335 
344  public static OutputStream getOutputStream(URL url, boolean append) throws UncheckedIOException {
345  try {
346  if (!url.getProtocol().equals("file")) {
347  throw new UncheckedIOException(new IOException("Can only write to file:// protocol URLs"));
348  }
349 
350  File file = new File(url.toURI());
351  CompressionType compression = getCompression(url);
352 
353  if ((compression != null && compression != CompressionType.ZSTD) && append && file.exists()) {
354  throw new UncheckedIOException(new IOException("Cannot append to compressed files."));
355  }
356 
357  OutputStream outputStream = new FileOutputStream(file, append);
358 
359  if (compression != null) {
360  switch (compression) {
361  case GZIP:
362  outputStream = new GZIPOutputStream(outputStream);
363  break;
364  case LZ4:
365  outputStream = new LZ4FrameOutputStream(outputStream);
366  break;
367  case BZIP2:
368  outputStream = new CompressorStreamFactory().createCompressorOutputStream(CompressorStreamFactory.BZIP2, outputStream);
369  break;
370  case ZSTD:
371  outputStream = new ZstdOutputStream(outputStream, zstdCompressionLevel);
372  break;
373  }
374  }
375 
376  return new BufferedOutputStream(outputStream);
377  } catch (IOException e) {
378  throw new UncheckedIOException(e);
379  } catch (CompressorException | URISyntaxException e) {
380  throw new UncheckedIOException(new IOException(e));
381  }
382  }
383 
390  public static BufferedWriter getBufferedWriter(URL url, Charset charset, boolean append)
391  throws UncheckedIOException {
392  OutputStream outputStream = getOutputStream(url, append);
393  return new BufferedWriter(new OutputStreamWriter(outputStream, charset));
394  }
395 
402  public static BufferedWriter getBufferedWriter(URL url) throws UncheckedIOException {
403  return getBufferedWriter(url, CHARSET_UTF8, false);
404  }
405 
412  public static PrintStream getPrintStream(URL url) throws UncheckedIOException {
413  return new PrintStream(getOutputStream(url, false));
414  }
415 
424  public static void copyStream(final InputStream fromStream, final OutputStream toStream)
425  throws UncheckedIOException {
426  try {
427  byte[] buffer = new byte[4096];
428  int bytesRead;
429 
430  while ((bytesRead = fromStream.read(buffer)) != -1) {
431  toStream.write(buffer, 0, bytesRead);
432  }
433  } catch (IOException e) {
434  throw new UncheckedIOException(e);
435  }
436  }
437 
446  public static void deleteDirectoryRecursively(Path path) throws UncheckedIOException {
447  try {
448  Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
449  @Override
450  public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
451  Files.delete(file);
452  return FileVisitResult.CONTINUE;
453  }
454 
455  @Override
456  public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
457  Files.delete(dir);
458  return FileVisitResult.CONTINUE;
459  }
460  });
461  } catch (IOException e) {
462  throw new UncheckedIOException(e);
463  }
464  }
465 
474  public static boolean isEqual(InputStream first, InputStream second) throws UncheckedIOException {
475  byte[] buf1 = new byte[64 * 1024];
476  byte[] buf2 = new byte[64 * 1024];
477  try (first; second) {
478  DataInputStream d2 = new DataInputStream(second);
479  int len;
480  while ((len = first.read(buf1)) > 0) {
481  d2.readFully(buf2,0, len);
482  if (!Arrays.equals(buf1, 0, len, buf2, 0, len)) {
483  return false;
484  }
485  }
486  return d2.read() < 0; // is the end of the second file also.
487  } catch(EOFException ioe) {
488  return false;
489  } catch (IOException e) {
490  throw new UncheckedIOException(e);
491  }
492  }
493 
501  public static URL getFileUrl(String filename) throws UncheckedIOException {
502  try {
503  return new File(filename).toURI().toURL();
504  } catch (MalformedURLException e) {
505  throw new UncheckedIOException(e);
506  }
507  }
508 
517  public static URL extendUrl(URL context, String extension) throws UncheckedIOException {
518  if (context == null) {
519  throw new IllegalArgumentException("Please use IOUtils.getFileUrl");
520  }
521 
522  try {
523  return new URL(context, extension);
524  } catch (MalformedURLException e) {
525  // We cannot construct a URL for some reason (see respective unit test)
526  return getFileUrl(extension);
527  }
528  }
529 
539  public static BufferedReader getBufferedReader(String filename) {
540  return getBufferedReader(resolveFileOrResource(filename));
541  }
542 
546  public static BufferedWriter getBufferedWriter(String filename) {
547  return getBufferedWriter(getFileUrl(filename));
548  }
549 
553  public static BufferedWriter getAppendingBufferedWriter(String filename) {
554  return getBufferedWriter(getFileUrl(filename), CHARSET_UTF8, true);
555  }
556 
562  public static void copyFile(String inputFilename, String outputFilename) throws IOException {
563  File fromFile = new File(inputFilename);
564  File toFile = new File(outputFilename);
565  Files.copy(fromFile.toPath(), toFile.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
566  }
567 }
static final Charset CHARSET_UTF8
Definition: IOUtils.java:182
static void deleteDirectoryRecursively(Path path)
Definition: IOUtils.java:446
static boolean isEqual(InputStream first, InputStream second)
Definition: IOUtils.java:474
static BufferedWriter getBufferedWriter(URL url)
Definition: IOUtils.java:402
static final String NATIVE_NEWLINE
Definition: IOUtils.java:186
static InputStream getInputStream(URL url)
Definition: IOUtils.java:281
static BufferedWriter getBufferedWriter(String filename)
Definition: IOUtils.java:546
static InputStream getDecryptedInput(InputStream is)
static void setZstdCompressionLevel(int level)
Definition: IOUtils.java:173
static BufferedReader getBufferedReader(URL url, Charset charset)
Definition: IOUtils.java:321
static URL getFileUrl(String filename)
Definition: IOUtils.java:501
static final Charset CHARSET_WINDOWS_ISO88591
Definition: IOUtils.java:183
static void copyStream(final InputStream fromStream, final OutputStream toStream)
Definition: IOUtils.java:424
static BufferedWriter getBufferedWriter(URL url, Charset charset, boolean append)
Definition: IOUtils.java:390
static final Map< String, CompressionType > COMPRESSION_EXTENSIONS
Definition: IOUtils.java:162
static OutputStream getOutputStream(URL url, boolean append)
Definition: IOUtils.java:344
static BufferedReader getBufferedReader(String filename)
Definition: IOUtils.java:539
static final Logger logger
Definition: IOUtils.java:189
static void copyFile(String inputFilename, String outputFilename)
Definition: IOUtils.java:562
static URL resolveFileOrResource(String filename)
Definition: IOUtils.java:207
static BufferedWriter getAppendingBufferedWriter(String filename)
Definition: IOUtils.java:553
static URL extendUrl(URL context, String extension)
Definition: IOUtils.java:517
static PrintStream getPrintStream(URL url)
Definition: IOUtils.java:412
static CompressionType getCompression(URL url)
Definition: IOUtils.java:266
static BufferedReader getBufferedReader(URL url)
Definition: IOUtils.java:332