/*
* __ .__ .__ ._____.
* _/ |_ _______ __|__| ____ | | |__\_ |__ ______
* \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/
* | | ( <_> > <| \ \___| |_| || \_\ \\___ \
* |__| \____/__/\_ \__|\___ >____/__||___ /____ >
* \/ \/ \/ \/
*
* Copyright (c) 2006-2011 Karsten Schmidt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* http://creativecommons.org/licenses/LGPL/2.1/
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
package spimedb.util;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* A collection of file handling utilities.
*/
public class FileUtils {
/**
* {@link FileDialog} constant
*/
public static final int LOAD = FileDialog.LOAD;
/**
* {@link FileDialog} constant
*/
public static final int SAVE = FileDialog.SAVE;
/**
* Attempts to create the full path of directories as specified by the given
* target path. The path is assumed to be a directory, NOT a file in a
* directory. For the latter, use {@link #createDirectoriesForFile(File)}.
*
* @param path
* @return true, if the operation succeeded
*/
static public boolean createDirectories(File path) {
try {
if (!path.exists()) {
path.mkdirs();
}
return true;
} catch (SecurityException se) {
System.err.println("No permissions to create "
+ path.getAbsolutePath());
}
return false;
}
/**
* Attempts to create the full path of directories as specified by the given
* target file.
*
* @param file
* @return true, if the operation succeeded
*/
static public boolean createDirectoriesForFile(File file) {
try {
String parentName = file.getParent();
if (parentName != null) {
File parent = new File(parentName);
if (!parent.exists()) {
parent.mkdirs();
}
}
return true;
} catch (SecurityException se) {
System.err.println("No permissions to create "
+ file.getAbsolutePath());
}
return false;
}
/**
* Creates an {@link InputStream} for the given file. If the file extension
* ends with ".gz" the stream is automatically wrapped in a
* {@link GZIPInputStream} as well.
*
* @param file
* input file
* @return input stream
* @throws IOException
*/
static public InputStream createInputStream(File file) throws IOException {
if (file == null) {
throw new IllegalArgumentException("file can't be null");
}
InputStream stream = new FileInputStream(file);
if (file.getName().toLowerCase().endsWith(".gz")) {
stream = new GZIPInputStream(stream);
}
return stream;
}
/**
* Creates an {@link OutputStream} for the given file. If the file extension
* ends with ".gz" the stream is automatically wrapped in a
* {@link GZIPOutputStream} as well. Also attempts to create any
* intermediate directories for the file using
* {@link #createDirectoriesForFile(File)}.
*
* @param file
* output file
* @return output stream
* @throws IOException
*/
static public OutputStream createOutputStream(File file) throws IOException {
if (file == null) {
throw new IllegalArgumentException("file can't be null");
}
createDirectoriesForFile(file);
OutputStream stream = new FileOutputStream(file);
if (file.getName().toLowerCase().endsWith(".gz")) {
stream = new GZIPOutputStream(stream);
}
return stream;
}
/**
* Creates a {@link BufferedReader} for the given file using UTF-8 encoding.
*
* @param file
* @return reader instance
* @throws IOException
*/
public static BufferedReader createReader(File file) throws IOException {
return createReader(createInputStream(file));
}
/**
* Creates a {@link BufferedReader} for the given {@link InputStream} using
* UTF-8 encoding.
*
* @param input
* stream
* @return reader instance
* @throws IOException
*/
public static BufferedReader createReader(InputStream input) {
return createReader(input, "UTF-8");
}
/**
* Creates a {@link BufferedReader} for the given {@link InputStream} and
* using the specified encoding.
*
* @param input
* stream
* @param encoding
* text encoding to use
* @return reader instance
* @throws IOException
*/
public static BufferedReader createReader(InputStream input, String encoding) {
InputStreamReader isr = null;
try {
isr = new InputStreamReader(input, encoding);
} catch (UnsupportedEncodingException e) {
}
return new BufferedReader(isr, 0x10000);
}
/**
* Creates a {@link BufferedWriter} for the given file using UTF-8 encoding.
*
* @param file
* @return writer instance
* @throws IOException
*/
public static BufferedWriter createWriter(File file) throws IOException {
return createWriter(createOutputStream(file));
}
/**
* Creates a {@link BufferedWriter} for the given {@link OutputStream} using
* UTF-8 encoding.
*
* @param out
* @return writer instance
* @throws IOException
*/
public static BufferedWriter createWriter(OutputStream out) {
return createWriter(out, "UTF-8");
}
/**
* Creates a {@link BufferedWriter} for the given {@link OutputStream} and
* using the specified encoding.
*
* @param out
* stream
* @param encoding
* text encoding to use
* @return writer instance
* @throws IOException
*/
public static BufferedWriter createWriter(OutputStream out, String encoding) {
OutputStreamWriter w = null;
try {
w = new OutputStreamWriter(out, encoding);
} catch (UnsupportedEncodingException e) {
}
return new BufferedWriter(w, 0x10000);
}
/**
* <p>
* Analyses the given file path for a file sequence pattern and returns a
* {@link FileSequenceDescriptor} instance for further use to handle this
* sequence. The file pattern should be in one of these formats:
* </p>
* <ul>
* <li>base_path-00001.ext</li>
* <li>base_path001.ext</li>
* </ul>
* <p>
* The sequence index should be using leading zeros, but the number of
* digits will be identified automatically.
* </p>
*
* @param path
* file path of the first file in the sequence
* @return descriptor, or null, if the path could not be analysed
*/
public static FileSequenceDescriptor getFileSequenceDescriptorFor(
String path) {
int dotIndex = path.lastIndexOf('.');
int zeroIndex = path.lastIndexOf('-') + 1;
if (zeroIndex == 0) {
zeroIndex = dotIndex - 1;
while (path.charAt(zeroIndex) >= '0'
&& path.charAt(zeroIndex) <= '9') {
zeroIndex--;
}
zeroIndex++;
}
int numDigits = dotIndex - zeroIndex;
if (dotIndex != -1 && numDigits > 0) {
String base = path.substring(0, zeroIndex);
String extension = path.substring(dotIndex);
String filePattern = base + "%0" + numDigits + 'd' + extension;
int start = Integer.parseInt(path.substring(zeroIndex, dotIndex));
return new FileSequenceDescriptor(filePattern, extension, dotIndex
- zeroIndex, start);
} else {
return null;
}
}
/**
* Loads the given {@link InputStream} into a byte array buffer.
*
* @param stream
* @return byte array
* @throws IOException
*/
static public byte[] loadBytes(InputStream stream) throws IOException {
BufferedInputStream input = new BufferedInputStream(stream);
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int c;
while ((c = input.read()) != -1) {
buffer.write(c);
}
return buffer.toByteArray();
}
/**
* Loads the given {@link BufferedReader} as text, line by line into a
* {@link String}.
*
* @param reader
* @return reader contents as string
* @throws IOException
*/
public static String loadText(BufferedReader reader) throws IOException {
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append('\n');
}
return result.toString();
}
/**
* Loads the given {@link InputStream} as text, line by line into a
* {@link String} using UTF-8 encoding.
*
* @param input
* stream
* @return stream contents as string
* @throws IOException
*/
public static String loadText(InputStream input) throws IOException {
return loadText(input, "UTF-8");
}
/**
* Loads the given {@link InputStream} as text, line by line into a
* {@link String} using the specified encoding.
*
* @param input
* stream
* @param encoding
* @return stream contents as single string
* @throws IOException
*/
public static String loadText(InputStream input, String encoding)
throws IOException {
byte[] raw = loadBytes(input);
return new String(raw, encoding);
}
/**
* Saves the given text to the specified {@link BufferedWriter} instance,
* then closes the writer afterwards.
*
* @param writer
* @param string
* @throws IOException
*/
static public void saveText(BufferedWriter writer, String string)
throws IOException {
writer.write(string);
writer.flush();
writer.close();
}
/**
* Saves the given text to the specified {@link OutputStream} instance, then
* closes the underlying writer afterwards.
*
* @param output
* @param string
* @throws IOException
*/
static public void saveText(OutputStream output, String string)
throws IOException {
saveText(createWriter(output), string);
}
/**
* Displays a standard AWT file dialog for choosing a folder (no files!).
*
* @param frame
* parent frame
* @param title
* dialog title
* @param path
* base directory (or null)
* @return path to chosen directory or null, if user has canceled
*/
public static String showDirectoryChooser(final Frame frame,
final String title, String path) {
System.setProperty("apple.awt.fileDialogForDirectories", "true");
String result = showFileDialog(frame, title, path,
(dir, name) -> new File(dir + "/" + name).isDirectory(), LOAD);
System.setProperty("apple.awt.fileDialogForDirectories", "false");
return result;
}
/**
* Displays a standard AWT file dialog for choosing a file for loading or
* saving.
*
* @param frame
* parent frame
* @param title
* dialog title
* @param path
* base directory (or null)
* @param filter
* a FilenameFilter implementation (or null)
* @param mode
* either FileUtils.LOAD or FileUtils.SAVE
* @return path to chosen file or null, if user has canceled
*/
public static String showFileDialog(final Frame frame, final String title,
String path, FilenameFilter filter, final int mode) {
String fileID = null;
FileDialog fd = new FileDialog(frame, title, mode);
if (path != null) {
fd.setDirectory(path);
}
if (filter != null) {
fd.setFilenameFilter(filter);
}
fd.setVisible(true);
if (fd.getFile() != null) {
fileID = fd.getFile();
fileID = fd.getDirectory() + fileID;
}
return fileID;
}
/**
* Displays a standard AWT file dialog for choosing a file for loading or
* saving.
*
* @param frame
* parent frame
* @param title
* dialog title
* @param path
* base directory (or null)
* @param formats
* an array of allowed file extensions (or null to allow all)
* @param mode
* either FileUtils.LOAD or FileUtils.SAVE
* @return path to chosen file or null, if user has canceled
*/
public static String showFileDialog(final Frame frame, final String title,
String path, final String[] formats, final int mode) {
String fileID = null;
FileDialog fd = new FileDialog(frame, title, mode);
if (path != null) {
fd.setDirectory(path);
}
if (formats != null) {
fd.setFilenameFilter((dir, name) -> {
boolean isAccepted = false;
for (String ext : formats) {
if (name.indexOf(ext) != -1) {
isAccepted = true;
break;
}
}
return isAccepted;
});
}
fd.setVisible(true);
if (fd.getFile() != null) {
fileID = fd.getFile();
fileID = fd.getDirectory() + fileID;
}
return fileID;
}
@Nullable
public static Path pathOrCreate(String cachePath) {
try {
return Files.createDirectories(Paths.get(cachePath));
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* A descriptor and iterator for handling file sequences.
*/
public static class FileSequenceDescriptor implements Iterable<String> {
private class SequenceIterator implements Iterator<String> {
private int curr;
private final int end;
public SequenceIterator(int start, int end) {
this.curr = start;
this.end = end;
}
public boolean hasNext() {
return curr < end;
}
public String next() {
String path = getPathForIndex(curr);
curr++;
return path;
}
public void remove() {
throw new UnsupportedOperationException("remove() not supported");
}
}
public String filePattern;
public String extension;
public int numDigits;
public int start;
public int end = -1;
/**
* Creates a new descriptor from the given sequence details.
*
* @param filePattern
* file pattern in the format: e.g. "path/basename%d04.ext"
* @param extension
* file extension (e.g. ".tga")
* @param numDigits
* number of digits used for the index
* @param start
* start index
*/
public FileSequenceDescriptor(String filePattern, String extension,
int numDigits, int start) {
this.filePattern = filePattern;
this.extension = extension;
this.numDigits = numDigits;
this.start = start;
}
/**
* Returns the base path of the sequence, i.e. the substring of the
* sequence's file pattern from the beginning until the first occurence of
* the % sign indicating the frame numbers.
*
* @return path string
*/
public String getBasePath() {
return filePattern.substring(0, filePattern.indexOf('%'));
}
/**
* Calculates sequence duration
*
* @return number of files in sequence
*/
public int getDuration() {
return getFinalIndex() - start;
}
/**
* Identifies the index of the last file of the sequence.
*
* @return final index
*/
public int getFinalIndex() {
if (end == -1) {
end = start;
while (true) {
if (!new File(getPathForIndex(end)).canRead()) {
break;
} else {
end++;
}
}
}
return end;
}
/**
* Constructs the file path for the given absolute index
*
* @param i
* index
* @return path
*/
public String getPathForIndex(int i) {
return String.format(filePattern, i);
}
/**
* Returns the index of the first file of the sequence.
*
* @return start index
*/
public int getStartIndex() {
return start;
}
/**
* Creates an iterator providing paths for each file in the sequence. The
* iterator does not support the remove() method and attempts to use it
* results in an {@link UnsupportedOperationException} being thrown.
*/
public Iterator<String> iterator() {
return new SequenceIterator(start, getFinalIndex());
}
}
}