/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.util.io;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import de.fhg.igd.slf4jplus.ALogger;
import de.fhg.igd.slf4jplus.ALoggerFactory;
/**
* Helper class for IO
*
* @author Kai Schwierczek
*/
public final class IOUtils {
private static final ALogger log = ALoggerFactory.getLogger(IOUtils.class);
/**
* Static class, constructor private.
*/
private IOUtils() {
}
/**
* Extract a ZIP archive.
*
* @param baseDir the base directory to extract to
* @param in the input stream of the ZIP archive, which is closed after
* extraction
* @return the collection of extracted files
* @throws IOException if an error occurs
*/
public static Collection<File> extract(File baseDir, InputStream in) throws IOException {
final Path basePath = baseDir.getAbsoluteFile().toPath();
Collection<File> collect = new ArrayList<>();
try (ZipInputStream zis = new ZipInputStream(in)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (!entry.isDirectory()) {
final Path file = basePath.resolve(entry.getName()).normalize();
if (!file.startsWith(basePath)) {
// not inside target directory
log.warn(
"Skipped extraction of file {} as it is not in the target directory",
file);
continue;
}
File fileObj = file.toFile();
Files.createParentDirs(fileObj);
try (OutputStream out = new BufferedOutputStream(
new FileOutputStream(fileObj))) {
ByteStreams.copy(zis, out);
}
collect.add(fileObj);
}
}
}
return collect;
}
/**
* ZIP a directory with sub-folders and write it to the given output stream.
*
* @param zipDir the directory to ZIP
* @param zos the ZIP output stream
* @throws IOException if reading the directory or writing the ZIP stream
* fails
*/
public static void zipDirectory(File zipDir, ZipOutputStream zos) throws IOException {
zipDirectory(zipDir, zos, "");
}
private static void zipDirectory(File zipDir, ZipOutputStream zos, String parentFolder)
throws IOException {
String[] dirList = zipDir.list();
byte[] readBuffer = new byte[2156];
int bytesIn = 0;
for (int i = 0; i < dirList.length; i++) {
File f = new File(zipDir, dirList[i]);
if (f.isDirectory()) {
if (parentFolder.isEmpty())
zipDirectory(f, zos, f.getName());
else
zipDirectory(f, zos, parentFolder + "/" + f.getName());
continue;
}
FileInputStream fis = new FileInputStream(f);
ZipEntry anEntry;
if (parentFolder.isEmpty())
anEntry = new ZipEntry(f.getName());
else
anEntry = new ZipEntry(parentFolder + "/" + f.getName());
zos.putNextEntry(anEntry);
while ((bytesIn = fis.read(readBuffer)) != -1) {
zos.write(readBuffer, 0, bytesIn);
}
fis.close();
}
}
/**
* Returns a relative path between basePath and targetPath if possible.
*
* Source: http://stackoverflow.com/a/1288584
*
* @param targetURI the target path
* @param baseURI the base path
* @return a relative path from basePath to targetPath or the targetPath if
* a relative path is not possible
*/
public static URI getRelativePath(URI targetURI, URI baseURI) {
// nothing to do if one path is opaque or not absolute
if (!targetURI.isAbsolute() || !baseURI.isAbsolute() || targetURI.isOpaque()
|| baseURI.isOpaque())
return targetURI;
// check scheme
// XXX also check the other stuff (authority, host, port)?
if (!targetURI.getScheme().equals(baseURI.getScheme()))
return targetURI;
// Only use path, strip leading '/'
String basePath = baseURI.getPath().substring(1);
String targetPath = targetURI.getPath().substring(1);
// We need the -1 argument to split to make sure we get a trailing
// "" token if the base ends in the path separator and is therefore
// a directory. We require directory paths to end in the path
// separator -- otherwise they are indistinguishable from files.
String[] base = basePath.split("/", -1);
String[] target = targetPath.split("/", 0);
// First get all the common elements. Store them as a string,
// and also count how many of them there are.
StringBuffer buf = new StringBuffer();
int commonIndex = 0;
for (int i = 0; i < target.length && i < base.length; i++) {
if (target[i].equals(base[i])) {
buf.append(target[i]).append("/");
commonIndex++;
}
else
break;
}
String common = buf.toString();
if (commonIndex == 0) {
// Whoops -- not even a single common path element. This most
// likely indicates differing drive letters, like C: and D:.
// These paths cannot be relativized. Return the target path.
return targetURI;
// This should never happen when all absolute paths
// begin with / as in *nix.
}
String relative = "";
if (base.length == commonIndex) {
// Comment this out if you prefer that a relative path not start
// with ./
// relative = "./";
}
else {
int numDirsUp = base.length - commonIndex - 1;
// The number of directories we have to backtrack is the length of
// the base path MINUS the number of common path elements, minus
// one because the last element in the path isn't a directory.
for (int i = 1; i <= (numDirsUp); i++) {
relative += "../";
}
}
relative += targetPath.substring(common.length());
try {
return new URI(null, null, relative, targetURI.getQuery(), targetURI.getFragment());
} catch (URISyntaxException e) {
return targetURI;
}
}
/**
* Returns a URI for the given file.
*
* In contrast to {@link File#toURI()} it does not resolve a relative file,
* but instead returns a relative URI.
*
* @param file the file to transform
* @return a (relative) URI for the given file
*/
public static URI relativeFileToURI(File file) {
if (file.isAbsolute())
return file.toURI();
else {
String path = file.getPath();
if (File.separatorChar != '/')
path = path.replace(File.separatorChar, '/');
return URI.create(path);
}
}
/**
* Get the human readable notation of a size in bytes.
*
* {@link "http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java"}
*
* @param bytes the number of bytes
* @param si if the SI or binary unit should be used
* @return the human readable string representation of the number of bytes
*/
public static String humanReadableByteCount(long bytes, boolean si) {
int unit = si ? 1000 : 1024;
if (bytes < unit)
return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(unit));
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}
}