All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.plumelib.util.FilesPlume Maven / Gradle / Ivy

There is a newer version: 1.10.0
Show newest version
// If you edit this file, you must also edit its tests.

package org.plumelib.util;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.CREATE;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.checkerframework.checker.index.qual.Positive;
import org.checkerframework.checker.mustcall.qual.Owning;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.value.qual.IntVal;
import org.checkerframework.dataflow.qual.Pure;

/** Utility methods that create and manipulate files, directories, streams, readers, and writers. */
public final class FilesPlume {

  /** This class is a collection of methods; it does not represent anything. */
  private FilesPlume() {
    throw new Error("do not instantiate");
  }

  /** The system-specific line separator string. */
  private static final String lineSep = System.lineSeparator();

  ///////////////////////////////////////////////////////////////////////////
  /// File readers
  ///

  /**
   * Returns an InputStream for the file, accounting for the possibility that the file is
   * compressed. (A file whose name ends with ".gz" is treated as compressed.)
   *
   * 

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param path the possibly-compressed file to read * @return an InputStream for file * @throws IOException if there is trouble reading the file */ public static @Owning InputStream newFileInputStream(Path path) throws IOException { FileInputStream fis = new FileInputStream(path.toFile()); InputStream in; if (path.toString().endsWith(".gz")) { try { in = new GZIPInputStream(fis); } catch (IOException e) { fis.close(); throw new IOException("Problem while reading " + path, e); } } else { in = fis; } return in; } /** * Returns an InputStream for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param file the possibly-compressed file to read * @return an InputStream for file * @throws IOException if there is trouble reading the file */ public static @Owning InputStream newFileInputStream(File file) throws IOException { return newFileInputStream(file.toPath()); } /** * Returns a Reader for the file, accounting for the possibility that the file is compressed. (A * file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param filename the possibly-compressed file to read * @return an InputStream for filename * @throws IOException if there is trouble reading the file * @throws FileNotFoundException if the file is not found */ public static @Owning InputStreamReader newFileReader(String filename) throws FileNotFoundException, IOException { return newFileReader(new File(filename), null); } /** * Returns a Reader for the file, accounting for the possibility that the file is compressed. (A * file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param path the possibly-compressed file to read * @return an InputStreamReader for file * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning InputStreamReader newFileReader(Path path) throws FileNotFoundException, IOException { return newFileReader(path.toFile(), null); } /** * Returns a Reader for the file, accounting for the possibility that the file is compressed. (A * file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param path the possibly-compressed file to read * @param charsetName the name of a Charset to use when reading the file, or null to use UTF-8 * @return an InputStreamReader for file * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning InputStreamReader newFileReader(Path path, @Nullable String charsetName) throws FileNotFoundException, IOException { InputStream in = newFileInputStream(path.toFile()); InputStreamReader fileReader; if (charsetName == null) { fileReader = new InputStreamReader(in, UTF_8); } else { fileReader = new InputStreamReader(in, charsetName); } return fileReader; } /** * Returns a Reader for the file, accounting for the possibility that the file is compressed. (A * file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param file the possibly-compressed file to read * @return an InputStreamReader for file * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning InputStreamReader newFileReader(File file) throws FileNotFoundException, IOException { return newFileReader(file, null); } /** * Returns a Reader for the file, accounting for the possibility that the file is compressed. (A * file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param file the possibly-compressed file to read * @param charsetName the name of a Charset to use when reading the file, or null to use UTF-8 * @return an InputStreamReader for file * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning InputStreamReader newFileReader(File file, @Nullable String charsetName) throws FileNotFoundException, IOException { return newFileReader(file.toPath(), charsetName); } /////////////////////////////////////////////////////////////////////////// /// Buffered file readers and line number readers /// /** * Returns a BufferedReader for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param filename the possibly-compressed file to read * @return a BufferedReader for file * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning BufferedReader newBufferedFileReader(String filename) throws FileNotFoundException, IOException { return newBufferedFileReader(filename, null); } /** * Returns a BufferedReader for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param file the possibility-compressed file to read * @return a BufferedReader for file * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning BufferedReader newBufferedFileReader(File file) throws FileNotFoundException, IOException { return newBufferedFileReader(file, null); } /** * Returns a BufferedReader for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param filename the possibly-compressed file to read * @param charsetName the character set to use when reading the file, or null to use UTF-8 * @return a BufferedReader for filename * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning BufferedReader newBufferedFileReader( String filename, @Nullable String charsetName) throws FileNotFoundException, IOException { return newBufferedFileReader(new File(filename), charsetName); } /** * Returns a BufferedReader for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param file the possibly-compressed file to read * @param charsetName the character set to use when reading the file, or null to use UTF-8 * @return a BufferedReader for file * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning BufferedReader newBufferedFileReader( File file, @Nullable String charsetName) throws FileNotFoundException, IOException { Reader fileReader = newFileReader(file, charsetName); return new BufferedReader(fileReader); } /** * Returns a LineNumberReader for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param filename the possibly-compressed file to read * @return a LineNumberReader for filename * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning LineNumberReader newLineNumberFileReader(String filename) throws FileNotFoundException, IOException { return newLineNumberFileReader(new File(filename)); } /** * Returns a LineNumberReader for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param file the possibly-compressed file to read * @return a LineNumberReader for file * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning LineNumberReader newLineNumberFileReader(File file) throws FileNotFoundException, IOException { Reader fileReader = newFileReader(file, null); return new LineNumberReader(fileReader); } /////////////////////////////////////////////////////////////////////////// /// File writers /// /** * Returns an OutputStream for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param path the possibly-compressed file to read * @return an OutputStream for file * @throws IOException if there is trouble reading the file */ public static @Owning OutputStream newFileOutputStream(Path path) throws IOException { return newFileOutputStream(path, false); } /** * Returns an OutputStream for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param path the possibly-compressed file to read * @param append if true, then bytes will be written to the end of the file rather than the * beginning * @return an OutputStream for file * @throws IOException if there is trouble reading the file */ public static @Owning OutputStream newFileOutputStream(Path path, boolean append) throws IOException { FileOutputStream fis = new FileOutputStream(path.toFile(), append); OutputStream in; if (path.toString().endsWith(".gz")) { try { in = new GZIPOutputStream(fis); } catch (IOException e) { fis.close(); throw new IOException("Problem while reading " + path, e); } } else { in = fis; } return in; } /** * Returns an OutputStream for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param file the possibly-compressed file to read * @return an OutputStream for file * @throws IOException if there is trouble reading the file */ public static @Owning OutputStream newFileOutputStream(File file) throws IOException { return newFileOutputStream(file.toPath()); } /** * Returns a Writer for the file, accounting for the possibility that the file is compressed. (A * file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param filename the possibly-compressed file to read * @return an OutputStream for filename * @throws IOException if there is trouble reading the file * @throws FileNotFoundException if the file is not found */ public static @Owning OutputStreamWriter newFileWriter(String filename) throws FileNotFoundException, IOException { return newFileWriter(new File(filename), null); } /** * Returns a Writer for the file, accounting for the possibility that the file is compressed. (A * file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param path the possibly-compressed file to read * @return an OutputStreamWriter for file * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning OutputStreamWriter newFileWriter(Path path) throws FileNotFoundException, IOException { return newFileWriter(path.toFile(), null); } /** * Returns a Writer for the file, accounting for the possibility that the file is compressed. (A * file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param path the possibly-compressed file to read * @param charsetName the name of a Charset to use when reading the file, or null to use UTF-8 * @return an OutputStreamWriter for file * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning OutputStreamWriter newFileWriter(Path path, @Nullable String charsetName) throws FileNotFoundException, IOException { OutputStream in = newFileOutputStream(path.toFile()); OutputStreamWriter fileWriter; if (charsetName == null) { fileWriter = new OutputStreamWriter(in, UTF_8); } else { fileWriter = new OutputStreamWriter(in, charsetName); } return fileWriter; } /** * Returns a Writer for the file, accounting for the possibility that the file is compressed. (A * file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param file the possibly-compressed file to read * @return an OutputStreamWriter for file * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning OutputStreamWriter newFileWriter(File file) throws FileNotFoundException, IOException { return newFileWriter(file, null); } /** * Returns a Writer for the file, accounting for the possibility that the file is compressed. (A * file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param file the possibly-compressed file to read * @param charsetName the name of a Charset to use when reading the file, or null to use UTF-8 * @return an OutputStreamWriter for file * @throws FileNotFoundException if the file cannot be found * @throws IOException if there is trouble reading the file */ public static @Owning OutputStreamWriter newFileWriter(File file, @Nullable String charsetName) throws FileNotFoundException, IOException { return newFileWriter(file.toPath(), charsetName); } /////////////////////////////////////////////////////////////////////////// /// Buffered file writers /// /** * Returns a BufferedWriter for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param filename the possibly-compressed file to write * @return a BufferedWriter for filename * @throws IOException if there is trouble writing the file */ public static @Owning BufferedWriter newBufferedFileWriter(String filename) throws IOException { return newBufferedFileWriter(filename, false); } /** * Returns a BufferedWriter for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param filename the possibly-compressed file to write * @param append if true, the resulting BufferedWriter appends to the end of the file instead of * the beginning * @return a BufferedWriter for filename * @throws IOException if there is trouble writing the file */ // Question: should this be rewritten as a wrapper around newBufferedFileOutputStream? public static @Owning BufferedWriter newBufferedFileWriter(String filename, boolean append) throws IOException { if (filename.endsWith(".gz")) { return new BufferedWriter( new OutputStreamWriter(newFileOutputStream(Paths.get(filename), append), UTF_8)); } else { return Files.newBufferedWriter( Paths.get(filename), UTF_8, append ? new StandardOpenOption[] {CREATE, APPEND} : new StandardOpenOption[] {CREATE}); } } /** * Returns a BufferedOutputStream for the file, accounting for the possibility that the file is * compressed. (A file whose name ends with ".gz" is treated as compressed.) * *

Warning: The "gzip" program writes and reads files containing concatenated gzip files. As of * Java 1.4, Java reads just the first one: it silently discards all characters (including gzipped * files) after the first gzipped file. * * @param filename the possibly-compressed file to write * @param append if true, the resulting BufferedOutputStream appends to the end of the file * instead of the beginning * @return a BufferedOutputStream for filename * @throws IOException if there is trouble writing the file */ public static @Owning BufferedOutputStream newBufferedFileOutputStream( String filename, boolean append) throws IOException { OutputStream os = newFileOutputStream(new File(filename).toPath(), append); return new BufferedOutputStream(os); } /////////////////////////////////////////////////////////////////////////// /// File /// /** * Count the number of lines in the specified file. * * @param filename file whose size to count * @return number of lines in filename * @throws IOException if there is trouble reading the file */ public static long countLines(String filename) throws IOException { long count = 0; try (LineNumberReader reader = newLineNumberFileReader(filename)) { while (reader.readLine() != null) { count++; } } return count; } /** * Tries to infer the line separator used in a file. * * @param filename the file to infer a line separator from * @return the inferred line separator used in filename * @throws IOException if there is trouble reading the file */ public static String inferLineSeparator(String filename) throws IOException { return inferLineSeparator(new File(filename)); } /** * Tries to infer the line separator used in a file. * * @param file the file to infer a line separator from * @return the inferred line separator used in filename * @throws IOException if there is trouble reading the file */ public static String inferLineSeparator(File file) throws IOException { try (BufferedReader r = newBufferedFileReader(file)) { int unix = 0; int dos = 0; int mac = 0; while (true) { String s = r.readLine(); if (s == null) { break; } if (s.endsWith("\r\n")) { dos++; } else if (s.endsWith("\r")) { mac++; } else if (s.endsWith("\n")) { unix++; } else { // This can happen only if the last line is not terminated. } } if ((dos > mac && dos > unix) || (lineSep.equals("\r\n") && dos >= unix && dos >= mac)) { return "\r\n"; } if ((mac > dos && mac > unix) || (lineSep.equals("\r") && mac >= dos && mac >= unix)) { return "\r"; } if ((unix > dos && unix > mac) || (lineSep.equals("\n") && unix >= dos && unix >= mac)) { return "\n"; } // The two non-preferred line endings are tied and have more votes than // the preferred line ending. Give up and return the line separator // for the system on which Java is currently running. return lineSep; } } /** * Returns true iff files have the same contents. * * @param file1 first file to compare * @param file2 second file to compare * @return true iff the files have the same contents */ @Pure public static boolean equalFiles(String file1, String file2) { return equalFiles(file1, file2, false); } /** * Returns true iff the files have the same contents. * * @param file1 first file to compare * @param file2 second file to compare * @param trimLines if true, call String.trim on each line before comparing * @return true iff the files have the same contents */ @SuppressWarnings({"allcheckers:purity", "lock"}) // reads files, side effects local state @Pure public static boolean equalFiles(String file1, String file2, boolean trimLines) { try (LineNumberReader reader1 = newLineNumberFileReader(file1); LineNumberReader reader2 = newLineNumberFileReader(file2); ) { String line1 = reader1.readLine(); String line2 = reader2.readLine(); while (line1 != null && line2 != null) { if (trimLines) { line1 = line1.trim(); line2 = line2.trim(); } if (!line1.equals(line2)) { return false; } line1 = reader1.readLine(); line2 = reader2.readLine(); } if (line1 == null && line2 == null) { return true; } return false; } catch (IOException e) { throw new RuntimeException(e); } } /** * Returns true if the file exists and is writable, or if the file can be created. * * @param file the file to create and write * @return true iff the file can be created and written */ public static boolean canCreateAndWrite(File file) { if (file.exists()) { return file.canWrite(); } else { File directory = file.getParentFile(); if (directory == null) { directory = new File("."); } // Does this test need "directory.canRead()" also? return directory.canWrite(); } /// Old implementation; is this equivalent to the new one, above?? // try { // if (file.exists()) { // return file.canWrite(); // } else { // file.createNewFile(); // file.delete(); // return true; // } // } catch (IOException e) { // return false; // } } /** * Creates a new empty file in the default temporary-file directory, using the given prefix and * suffix strings to generate its name. This is like {@link File#createTempFile}, but uses * sequential file names. * * @param prefix the prefix string to be used in generating the file's name; may be null * @param suffix the suffix string to be used in generating the file's name; may be null, in which * case ".tmp" is used * @param attrs an optional list of file attributes to set atomically when creating the file * @return the path to the newly created file that did not exist before this method was invoked * @throws IOException if there is trouble creating the file */ public static Path createTempFile(String prefix, String suffix, FileAttribute... attrs) throws IOException { return createTempFile(Paths.get(System.getProperty("java.io.tmpdir")), prefix, suffix, attrs); } /** * Creates a new empty file in the specified directory, using the given prefix and suffix strings * to generate its name. This is like {@link File#createTempFile}, but uses sequential file names. * * @param dir the path to directory in which to create the file * @param prefix the prefix string to be used in generating the file's name; may be null * @param suffix the suffix string to be used in generating the file's name; may be null, in which * case ".tmp" is used * @param attrs an optional list of file attributes to set atomically when creating the file * @return the path to the newly created file that did not exist before this method was invoked * @throws IOException if there is trouble creating the file */ public static Path createTempFile( Path dir, String prefix, String suffix, FileAttribute... attrs) throws IOException { Path createdDir = Files.createDirectories(dir, attrs); for (int i = 1; i < Integer.MAX_VALUE; i++) { File candidate = new File(createdDir.toFile(), prefix + i + suffix); if (!candidate.exists()) { System.out.println("Created " + candidate); return candidate.toPath(); } } throw new Error("every file exists"); } /// /// Directories /// // TODO: Document how this differs from Files.createTempDirectory, or deprecate this. /** * Creates an empty directory in the default temporary-file directory, using the given prefix and * suffix to generate its name. For example, calling {@code createTempDir("myPrefix", "mySuffix")} * will create the following directory: {@code * temporaryFileDirectory/myUserName/myPrefix_}someString{@code _suffix}. * someString is internally generated to ensure no temporary files of the same name are * generated. * * @param prefix the prefix string to be used in generating the file's name; must be at least * three characters long * @param suffix the suffix string to be used in generating the file's name; may be null, in which * case the suffix ".tmp" will be used Returns: An abstract pathname denoting a newly-created * empty file * @return a File representing the newly-created temporary directory * @throws IllegalArgumentException If the prefix argument contains fewer than three characters * @throws IOException If a file could not be created * @throws SecurityException If a security manager exists and its * SecurityManager.checkWrite(java.lang.String) method does not allow a file to be created * @see java.io.File#createTempFile(String, String, File) */ public static File createTempDir(String prefix, String suffix) throws IOException { String fs = File.separator; String path = System.getProperty("java.io.tmpdir") + fs + System.getProperty("user.name") + fs; File pathFile = new File(path); if (!pathFile.isDirectory()) { if (!pathFile.mkdirs()) { throw new IOException("Could not create directory: " + pathFile); } } // Call Java runtime to create a file with a unique name File tmpfile = File.createTempFile(prefix + "_", "_", pathFile); String tmpDirPath = tmpfile.getPath() + suffix; File tmpDir = new File(tmpDirPath); if (!tmpDir.mkdirs()) { throw new IOException("Could not create directory: " + tmpDir); } // Now that we have created our directory, we should get rid // of the intermediate TempFile we created. tmpfile.delete(); return tmpDir; } /** * Deletes the directory at dirName and all its files. Also works on regular files. * * @param dirName the directory to delete * @return true if and only if the file or directory is successfully deleted; false otherwise */ public static boolean deleteDir(String dirName) { return deleteDir(new File(dirName)); } /** * Deletes the directory at dir and all its files. Also works on regular files. * * @param dir the directory to delete * @return true if and only if the file or directory is successfully deleted; false otherwise */ public static boolean deleteDir(File dir) { File[] children = dir.listFiles(); if (children != null) { // null means not a directory, or I/O error occurred. for (File child : children) { deleteDir(child); } } return dir.delete(); } /// /// File names (aka filenames) /// // Someone must have already written this. Right? // There is Apache Commons IO WildcardFileFilter or, using standard Java utilities, // https://stackoverflow.com/a/31685610/173852 . /** * A FilenameFilter that accepts files whose name matches the given wildcard. The wildcard must * contain exactly one "*". */ public static final class WildcardFilter implements FilenameFilter { /** The text before the wildcard. */ String prefix; /** The text after the wildcard. */ String suffix; /** * Create a filter that accepts files whose name matches the given wildcard. * * @param wildcard a string that must contain exactly one "*" */ public WildcardFilter(String wildcard) { int astloc = wildcard.indexOf('*'); if (astloc == -1) { throw new Error("No asterisk in wildcard argument: " + wildcard); } prefix = wildcard.substring(0, astloc); suffix = wildcard.substring(astloc + 1); if (wildcard.indexOf('*') != -1) { throw new Error("Multiple asterisks in wildcard argument: " + wildcard); } } @Override public boolean accept(File dir, String name) { // TODO: This is incorrect. For example, the wildcard "ax*xb" would match the string "axb". return name.startsWith(prefix) && name.endsWith(suffix); } } /** The user's home directory. */ static final String userHome = System.getProperty("user.home"); /** * Does tilde expansion on a file name (to the user's home directory). * * @param name file whose name to expand * @return file with expanded file */ public static File expandFilename(File name) { String path = name.getPath(); String newname = expandFilename(path); @SuppressWarnings({"interning", "ReferenceEquality"}) boolean changed = newname != path; if (changed) { return new File(newname); } else { return name; } } /** * Does tilde expansion on a file name (to the user's home directory). * * @param name filename to expand * @return expanded filename */ public static String expandFilename(String name) { if (name.contains("~")) { return name.replace("~", userHome); } else { return name; } } /** * Returns a string version of the filename that can be used in Java source. On Windows, the file * will return a backslash-separated string. Since backslash is an escape character, it must be * quoted itself inside the string. * *

The current implementation presumes that backslashes don't appear in filenames except as * windows path separators. That seems like a reasonable assumption. * * @param name file whose name to quote * @return a string version of the name that can be used in Java source */ public static String javaSource(File name) { return name.getPath().replace("\\", "\\\\"); } /// /// Reading and writing /// /** * Writes an Object to a File. * * @param o the object to write * @param file the file to which to write the object * @throws IOException if there is trouble writing the file */ public static void writeObject(Object o, File file) throws IOException { try (OutputStream bytes = newBufferedFileOutputStream(file.toString(), false); ObjectOutputStream objs = new ObjectOutputStream(bytes)) { objs.writeObject(o); } } /** * Reads an Object from a File. This is a wrapper around {@link ObjectInputStream#readObject}, but * it takes a {@link File} as an argument. Note that use of that method can lead to security * vulnerabilities. * * @param file the file from which to read * @return the object read from the file * @throws IOException if there is trouble reading the file * @throws ClassNotFoundException if the object's class cannot be found */ @SuppressWarnings("BanSerializableRead") // wrapper around dangerous API public static Object readObject(File file) throws IOException, ClassNotFoundException { try (InputStream fis = newFileInputStream(file); // 8192 is the buffer size in BufferedReader InputStream istream = new BufferedInputStream(fis, 8192); ObjectInputStream objs = new ObjectInputStream(istream)) { return objs.readObject(); } } /** * Reads the entire contents of the reader and returns it as a string. Any IOException encountered * will be turned into an Error. * * @param r the Reader to read; this method exhausts it and closes it * @return the entire contents of the reader, as a string */ public static String readerContents(Reader r) { try { StringBuilder contents = new StringBuilder(); int ch; while ((ch = r.read()) != -1) { contents.append((char) ch); } r.close(); return contents.toString(); } catch (Exception e) { throw new Error("Unexpected error in readerContents(" + r + ")", e); } } /** * Reads the entire contents of the file and returns it as a string. Any IOException encountered * will be turned into an Error. * *

You could use {@code new String(Files.readAllBytes(...))}, but it requires a Path rather * than a File, and it can throw IOException which has to be caught. * * @param file the file to read * @return the entire contents of the reader, as a string * @deprecated use {@link #readString} */ // @InlineMe(replacement = "FilesPlume.fileContents(file)", imports = // "org.plumelib.util.FilesPlume") @Deprecated // 2023-03-02 public static String readFile(File file) { return fileContents(file); } /** * Reads the entire contents of the file and returns it as a string. * *

The point of this method is that it does not throw any checked exception: any IOException * encountered will be turned into an Error. * * @param path the path to the file * @return a String containing the content read from the file */ public static String readString(Path path) { // In Java 11: // try { // return Files.readString(path, UTF_8); // } catch (IOException e) { // throw new Error(e); // } try (BufferedReader reader = newBufferedFileReader(path.toFile())) { StringBuilder contents = new StringBuilder(); String line = reader.readLine(); while (line != null) { contents.append(line); // Note that this converts line terminators! contents.append(lineSep); line = reader.readLine(); } return contents.toString(); } catch (Exception e) { throw new Error("Unexpected error in readString(" + path + ")", e); } } /** * Read the entire contents of the file and return it as a list of lines. Each line ends with a * line separator (except perhaps the last line). * * @param path the path to the file * @return the lines of the file */ public static List readLinesRetainingSeparators(Path path) { return StringsPlume.splitLinesRetainSeparators(readString(path)); } /** * Reads the entire contents of the file and returns it as a string. * *

The point of this method is that it does not throw any checked exception: any IOException * encountered will be turned into an Error. * *

You could use {@code new String(Files.readAllBytes(...))}, but it requires a Path rather * than a File, and it can throw IOException which has to be caught. * * @param file the file to read * @return the entire contents of the reader, as a string * @deprecated use {@link #readString} */ @Deprecated // 2024-04-14 public static String fileContents(File file) { return readString(file.toPath()); } /** * Creates a file with the given name and writes the specified string to it. If the file currently * exists (and is writable) it is overwritten. * *

The point of this method is that it does not throw any checked exception: any IOException * encountered will be turned into an Error. * * @param file the file to write to * @param contents the text to put in the file * @deprecated use {@link #writeString(File, String)} */ @Deprecated // 2024-04-16 public static void writeFile(File file, String contents) { writeString(file.toPath(), contents); } /** * Creates a file with the given name and writes the specified string to it. If the file currently * exists (and is writable) it is overwritten. * *

The point of this method is that it does not throw any checked exception: any IOException * encountered will be turned into an Error. * * @param file the file to write to * @param contents the text to put in the file */ public static void writeString(File file, String contents) { writeString(file.toPath(), contents); } /** * Creates a file with the given name and writes the specified string to it. If the file currently * exists (and is writable) it is overwritten Any IOException encountered will be turned into an * Error. * *

The point of this method is that it does not throw any checked exception: any IOException * encountered will be turned into an Error. * * @param path the path to write to * @param contents the text to put in the file */ public static void writeString(Path path, String contents) { // In Java 11: // try { // Files.writeString(path, contents, StandardCharsets.UTF_8); // } catch (Exception e) { // throw new Error("Unexpected error in writeFile(" + path + ")", e); // } try (Writer writer = Files.newBufferedWriter(path, UTF_8)) { writer.write(contents, 0, contents.length()); } catch (Exception e) { throw new Error("Unexpected error in writeString(" + path + ")", e); } } /////////////////////////////////////////////////////////////////////////// /// Stream /// /** * Copy the contents of the input stream to the output stream. * * @param from input stream * @param to output stream */ public static void streamCopy(InputStream from, OutputStream to) { byte[] buffer = new byte[1024]; int bytes; try { while (true) { bytes = from.read(buffer); if (bytes == -1) { return; } to.write(buffer, 0, bytes); } } catch (IOException e) { e.printStackTrace(); throw new Error(e); } } /** * Returns a String containing all the characters from the input stream. * * @param is input stream to read * @return a String containing all the characters from the input stream */ public static String streamString(InputStream is) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); streamCopy(is, baos); // In Java 11: String result = baos.toString(UTF_8); String result; try { result = baos.toString("UTF-8"); } catch (UnsupportedEncodingException e) { throw new Error(e); } return result; } /** * Reads all lines from the stream and returns them in a {@code List}. * * @param stream the stream to read from * @return the list of lines read from the stream * @throws IOException if there is an error reading from the stream */ public static List streamLines(InputStream stream) throws IOException { List outputLines = new ArrayList<>(); try (BufferedReader rdr = new BufferedReader(new InputStreamReader(stream, UTF_8))) { String line; while ((line = rdr.readLine()) != null) { outputLines.add(line); } } return outputLines; } /** * Calls {@code InputStream.available()}, but returns null instead of throwing an IOException. * * @param is an input stream * @return {@code is.available()}, or null if that throws an exception */ public static @Nullable Integer available(InputStream is) { try { return is.available(); } catch (IOException e) { return null; } } /** * Returns true if the first {@code readLimit} bytes of the input stream consist only of * whitespace. * * @param is an input stream * @param readLimit how many bytes to look ahead in the input stream * @return null if {@code !is.markSupported()}; otherwise, true if the first {@code readLimit} * characters of the input stream consist only of whitespace */ public static @Nullable Boolean isWhitespaceOnly(InputStream is, @Positive int readLimit) { if (!is.markSupported()) { return null; } try { is.mark(readLimit * 4); // each character is at most 4 bytes, usually much less for (int bytesRead = 0; bytesRead < readLimit; bytesRead++) { int nextCodePoint = readCodePoint(is); if (nextCodePoint == -1) { return true; } else if (Character.isWhitespace(nextCodePoint)) { // do nothing, continue loop } else { return false; } } return true; } finally { try { is.reset(); } catch (IOException e) { // Do nothing. } } } // From https://stackoverflow.com/a/54513347 . /** * Reads a Unicode code point from an input stream. * * @param is an input stream * @return the Unicode code point for the next character in the input stream */ public static int readCodePoint(InputStream is) { try { int nextByte = is.read(); if (nextByte == -1) { return -1; } byte firstByte = (byte) nextByte; int byteCount = getByteCount(firstByte); if (byteCount == 1) { return nextByte; } byte[] utf8Bytes = new byte[byteCount]; utf8Bytes[0] = (byte) nextByte; for (int i = 1; i < byteCount; i++) { // Get any subsequent bytes for this UTF-8 character. nextByte = is.read(); utf8Bytes[i] = (byte) nextByte; } int codePoint = new String(utf8Bytes, StandardCharsets.UTF_8).codePointAt(0); return codePoint; } catch (IOException e) { throw new Error("input stream = " + is, e); } } // From https://stackoverflow.com/a/54513347 . /** * Returns the number of bytes in a UTF-8 character based on the bit pattern of the supplied byte. * The only valid values are 1, 2 3 or 4. If the byte has an invalid bit pattern an * IllegalArgumentException is thrown. * * @param b The first byte of a UTF-8 character. * @return The number of bytes for this UTF-* character. * @throws IllegalArgumentException if the bit pattern is invalid. */ private static @IntVal({1, 2, 3, 4}) int getByteCount(byte b) throws IllegalArgumentException { if ((b >= 0)) return 1; // Pattern is 0xxxxxxx. if ((b >= (byte) 0b11000000) && (b <= (byte) 0b11011111)) return 2; // Pattern is 110xxxxx. if ((b >= (byte) 0b11100000) && (b <= (byte) 0b11101111)) return 3; // Pattern is 1110xxxx. if ((b >= (byte) 0b11110000) && (b <= (byte) 0b11110111)) return 4; // Pattern is 11110xxx. throw new IllegalArgumentException(); // Invalid first byte for UTF-8 character. } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy