package ch.inftec.ju.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Properties;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.inftec.ju.util.io.NewLineReader;
/**
* Utility class containing I/O related helper methods. Methods that depend on a charset
* or on a text Reader are not static. In this case, a new IOUtil instance must be created using either the
* default charset or an explicit charset (as specified in the constructor).
* <p>
* The IOUtil class converts line endings to a single LF character ('\n'), regardless of the actual
* line feed policy used in the source file.
* @author tgdmemae
*
*/
public final class IOUtil {
static final Logger log = LoggerFactory.getLogger(IOUtil.class);
/**
* The default charset that will be used to initialize an IOUtil instance
* if no explizit charset is specified.
*/
private static String defaultCharset = null;
private static int tempFileCounter = 0;
/**
* The Charset used by the IOUtil instance. If not submitted with the constructor, the
* defaultCharset will be used.
*/
private final String charset;
/**
* Gets the default charset used by the IOUtil classes if no explicit
* charset is submited with the call. If not changed by the used,
* this is the system's default charset as returned by
* Charset.defaultCharset()
* @return Default charset
*/
public synchronized static String getDefaultCharset() {
if (IOUtil.defaultCharset == null) {
IOUtil.defaultCharset = Charset.defaultCharset().displayName();
}
return IOUtil.defaultCharset;
}
/**
* Sets the default charset used by the IOUtil classes if no explicit
* charset is submitted with the call.
* <p>
* Note that this will only change the default charset for the IOUtil class
* and not for the whole java runtime.
* @param charset Default charset
*/
public synchronized static void setDefaultCharset(String charset) {
IOUtil.defaultCharset = charset;
}
/**
* Gets the charset that this IOUtil instance uses.
* @return Charset
*/
public String getCharset() {
return this.charset;
}
/**
* Creates a new IOUtil instance using the default charset.
*/
public IOUtil() {
this(IOUtil.getDefaultCharset());
}
/**
* Creates a new IOUtil instance with the specified charset.
* @param charset
*/
public IOUtil(String charset) {
this.charset = charset;
}
/**
* Closes the specified Reader and consumes any exception that
* might be raised.
* @param reader Reader instance
*/
public static void close(Reader reader) {
try {
log.debug("Closing Reader: " + ObjectUtils.identityToString(reader));
if (reader != null) reader.close();
} catch (IOException ex) {
log.warn("Could not close Reader instance: " + ex.getMessage());
}
}
/**
* Closes the specified InputStream and consumes any exception that
* might be raised.
* @param stream InputStream instance
*/
public static void close(InputStream stream) {
try {
log.debug("Closing InputStream: " + ObjectUtils.identityToString(stream));
if (stream != null) stream.close();
} catch (IOException ex) {
log.warn("Could not close InputStream instance: " + ex.getMessage());
}
}
/**
* Closes the specified OutputStream and consumes any exception that
* might be raised.
* @param stream OutputStream instance
*/
public static void close(OutputStream stream) {
try {
log.debug("Closing OutputStream: " + ObjectUtils.identityToString(stream));
if (stream != null) stream.close();
} catch (IOException ex) {
log.warn("Could not close OutputStream instance: " + ex.getMessage());
}
}
/**
* Generates a String for the specified reader.
* @param reader Reader instance
* @throws JuRuntimeException If the conversion fails
*/
public static String toString(Reader reader) {
try {
return IOUtils.toString(reader);
} catch (Exception ex) {
throw new JuRuntimeException("Couldn't generate String for Reader", ex);
}
}
/**
* Converts the specified String to a String containing Unix style new lines, i.e. \n
* @param s String to be converted
* @return String containing \n for line breaks
*/
public static String toNewLineUnix(String s) {
NewLineReader reader = new NewLineReader(new StringReader(s), null, IOUtils.LINE_SEPARATOR_UNIX);
return IOUtil.toString(reader);
}
/**
* Creates a new temporary file in the default temporary directory.
* <p>
* The file will be new/empty and deleted automatically when the JVM is exited.
* @return New temporary file
*/
public static synchronized Path getTemporaryFile() throws JuException {
try {
Path tempPath = Files.createTempFile(String.format("%s_%s_%s",
IOUtil.class.getName(),
IOUtil.tempFileCounter++,
System.currentTimeMillis())
, "tmp");
tempPath.toFile().deleteOnExit();
return tempPath;
} catch (Exception ex) {
throw new JuException("Couldn't create temporary file", ex);
}
}
/**
* Deletes the specified file (if it exists) and throws a runtime
* exception if deletion fails.
* @param path Path to file to delete
* @return True if file existed, false otherwise
*/
public static boolean deleteFile(Path path) {
if (Files.exists(path)) {
if (!Files.isRegularFile(path)) {
throw new JuRuntimeException("Not a regular file: " + path);
}
try {
Files.delete(path);
return true;
} catch (Exception ex) {
throw new JuRuntimeException("Couldn't delete file: " + path, ex);
}
} else {
return false;
}
}
/**
* Delets all specified files (if they exist).
* @param paths Path to files
* @return True if at least one file was deleted, false otherwise
*/
public static boolean deleteFiles(Path... paths) {
boolean oneDeleted = false;
for (Path path : paths) {
if (IOUtil.deleteFile(path)) {
oneDeleted = true;
}
}
return oneDeleted;
}
/**
* Creates a file using the specified path.
* @param file Path to file
* @param overwrite If true and the file exists, it will be truncated. Otherwise, an exception will be thrown.
* @return Path to the file
*/
public static Path createFile(Path file, boolean overwrite) throws JuException {
try {
if (Files.exists(file)) {
if (Files.isDirectory(file)) {
throw new JuException("Directory with file name exists: " + file);
} else {
if (!overwrite) {
throw new JuException("File exists: " + file);
} else {
// overwrite, i.e. recreate
Files.delete(file);
Files.createFile(file);
}
}
} else {
Files.createDirectories(file.getParent()); // Makes sure directories exist
Files.createFile(file);
}
} catch (JuException ex) {
throw ex;
} catch (Exception ex) {
throw new JuException("Couldn't create file " + file, ex);
}
return file;
}
/**
* Loads the specified URL resource into a string. This method uses the charset of the
* IOUtil instance.
* <p>
* Line breaks from the source will be converted to LF if necessary.
* @param url URL to resource
* @param replacements Optional 'key, value' strings to replace %key% tags in the resource with the specified value
* @return Loaded resource as string
* @throws JuException If the resource cannot be loaded
*/
public String loadTextFromUrl(URL url, String... replacements) throws JuException {
try {
if (url == null) {
throw new JuException("Resource not found: " + url);
}
try (Reader reader = this.createReader(url)) {
StringBuilder sb = new StringBuilder();
char[] buff = new char[1024];
int read;
while ((read = reader.read(buff)) > 0) {
sb.append(buff, 0, read);
}
return JuStringUtils.replaceAll(sb.toString(), replacements);
}
} catch (Exception ex) {
throw new JuException("Couldn't load text from URL " + url, ex);
}
}
/**
* Creates a Reader for the resource at the specified URL using the IOUtils
* charset.
* <p>
* Line breaks will be automatically converted to LF if necessary.
* <p>
* The reader will be buffered.
* <p>
* The reader needs to be closed by the client.
* @param url URL to text resource
* @return Reader instance
*/
public BufferedReader createReader(URL url) {
try {
BufferedReader reader = new BufferedReader(
new NewLineReader(
new InputStreamReader(url.openStream(), this.charset)
, null, NewLineReader.LF));
return reader;
} catch (Exception ex) {
throw new JuRuntimeException("Couldn't create reader for URL " + url, ex);
}
}
/**
* Loads properties from the specified URL.
* @param url
* @return Properties
* @throws JuException If loading fails
*/
public Properties loadPropertiesFromUrl(URL url) throws JuException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), this.charset))) {
Properties props = new Properties();
props.load(reader);
return props;
} catch (Exception ex) {
throw new JuException("Couldn't load properties from URL: " + url, ex);
}
}
/**
* Writes the specified text to a file.
* @param text Text
* @param file File to write to
* @param overwrite If true, an existing file will be overwritten. If false, it will be preserved.
* @throws JuException If the file cannot be written
*/
public void writeTextToFile(String text, Path file, boolean overwrite) throws JuException {
try {
if (Files.exists(file)) {
if (Files.isDirectory(file)) throw new JuException("Directory with file name exists: " + file);
else if (!overwrite) throw new JuException("File exists: " + file);
}
try (BufferedWriter w = Files.newBufferedWriter(file, Charset.forName(this.charset))) {
w.write(text);
}
} catch (JuException ex) {
throw ex;
} catch (Exception ex) {
throw new JuException("Couldn't write text to file: " + file, ex);
}
}
/**
* Opens a Writer to the specified file with this IOUtils character encoding.
* @param file File to write to
* @param append If true and the file exists, text will be appended. If the file doesn't exist, we create a new one.
* @param overwrite If true and the file exists, it will be overwritten. If false and the file exists, a
* JuException will be thrown. If append is true, overwrite must be false.
* @return Writer to write to the file. The caller is responsible of closing the writer
* @throws JuException If the file cannot be opened for writing using the specified options
*/
public Writer openWriter(Path file, boolean append, boolean overwrite) throws JuException {
if (append) AssertUtil.assertFalse("When appending, overwrite must be set to false", overwrite);
try {
OpenOption openOption = StandardOpenOption.WRITE;
// Handle append case
if (Files.exists(file) && append) {
openOption = StandardOpenOption.APPEND;
} else {
IOUtil.createFile(file, overwrite);
}
BufferedWriter w = Files.newBufferedWriter(file, Charset.forName(this.charset), openOption);
return w;
} catch (JuException ex) {
throw ex;
} catch (Exception ex) {
throw new JuException("Couldn't open Writer to: " + file, ex);
}
}
@Override
public String toString() {
return JuStringUtils.toString(this, "charset", this.charset);
}
}