/*******************************************************************************
* Copyright 2011
* Ubiquitous Knowledge Processing (UKP) Lab
* Technische Universität Darmstadt
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.dkpro.lab;
import java.io.Closeable;
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.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import org.apache.commons.io.FileExistsException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang.reflect.MethodUtils;
import org.apache.tools.ant.taskdefs.Execute;
import org.dkpro.lab.storage.StreamReader;
import org.dkpro.lab.task.Discriminable;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.util.MethodInvoker;
public class Util
{
private static Map<URL, File> urlFileCache;
static {
urlFileCache = new HashMap<URL, File>();
}
/**
* Make the given URL available as a file. A temporary file is created and
* deleted upon a regular shutdown of the JVM. If the parameter {@code
* aCache} is {@code true}, the temporary file is remembered in a cache and
* if a file is requested for the same URL at a later time, the same file is
* returned again. If the previously created file has been deleted
* meanwhile, it is recreated from the URL.
*
* @param aUrl
* the URL.
* @param aCache
* use the cache or not.
* @return a file created from the given URL.
* @throws IOException
* if the URL cannot be accessed to (re)create the file.
*/
public static synchronized File getUrlAsFile(URL aUrl, boolean aCache)
throws IOException
{
// If the URL already points to a file, there is not really much to do.
if ("file".equals(aUrl.getProtocol())) {
try {
return new File(aUrl.toURI());
}
catch (URISyntaxException e) {
throw new IOException(e);
}
}
// Lets see if we already have a file for this URL in our cache. Maybe
// the file has been deleted meanwhile, so we also check if the file
// actually still exists on disk.
File file = urlFileCache.get(aUrl);
if (!aCache || (file == null) || !file.exists()) {
// Create a temporary file and try to preserve the file extension
String suffix = ".temp";
String name = new File(aUrl.getPath()).getName();
int suffixSep = name.indexOf(".");
if (suffixSep != -1) {
suffix = name.substring(suffixSep + 1);
name = name.substring(0, suffixSep);
}
// Get a temporary file which will be deleted when the JVM shuts
// down.
file = File.createTempFile(name, suffix);
file.deleteOnExit();
// Now copy the file from the URL to the file.
shoveAndClose(aUrl.openStream(), new FileOutputStream(file));
// Remember the file
if (aCache) {
urlFileCache.put(aUrl, file);
}
}
return file;
}
/**
* Makes the given stream available as a file. The created file is temporary
* and deleted upon normal termination of the JVM. Still the file should be
* deleted as soon as possible if it is no longer required. In case the JVM
* crashes the file would not be deleted. The source stream is closed by
* this operation in all cases.
*
* @param is
* the source.
* @return the file.
* @throws IOException
* in case of read or write problems.
*/
public static File getStreamAsFile(final InputStream is)
throws IOException
{
OutputStream os = null;
try {
final File f = File.createTempFile("lab_stream", "tmp");
f.deleteOnExit();
os = new FileOutputStream(f);
shove(is, os);
return f;
}
finally {
close(os);
close(is);
}
}
/**
* Shove data from an {@link InputStream} into an {@link OutputStream}.
* Neither of the streams are closed after the operation.
*
* @param is the source.
* @param os the target.
* @throws IOException in case of read or write problems.
*/
public static void shove(final InputStream is, final OutputStream os)
throws IOException
{
byte[] buffer = new byte[65536];
int read;
while (true) {
read = is.read(buffer);
if (read == -1) {
break;
}
os.write(buffer, 0, read);
}
os.flush();
}
/**
* As {@link #shove(InputStream, OutputStream)} but the streams are closed
* at the end of the process.
*
* @param is the input stream.
* @param os the output stream.
* @throws IOException in case of read or write problems.
*/
public static
void shoveAndClose(
final InputStream is,
final OutputStream os)
throws IOException
{
try {
shove(is, os);
}
finally {
close(is);
close(os);
}
}
/**
* Close a {@link Closeable} object. This method is best used in {@code finally}
* sections.
*
* @param object the object to close.
*/
public static void close(final Closeable object)
{
if (object == null) {
return;
}
try {
object.close();
}
catch (IOException e) {
// Ignore exceptions happening while closing.
}
}
/**
* Recursively copy files and directories. The target must not exist before this operation.
*
* @param aIn the source.
* @param aOut the target.
* @throws IOException if something goes wrong.
*/
public static void copy(File aIn, File aOut)
throws IOException
{
copy(aIn, aOut, false);
}
/**
* Recursively copy files and directories. The target must not exist before this operation.
*
* @param aIn the source.
* @param aOut the target.
* @param aLink whether to create a symbolic link instead of copying
* @throws IOException if something goes wrong.
*/
public static void copy(File aIn, File aOut, boolean aLink)
throws IOException
{
if (aOut.exists()) {
throw new IOException("Target ["+aOut+"] already exists");
}
if (aIn.isDirectory()) {
aOut.mkdirs();
for (File child : aIn.listFiles()) {
copy(child, new File(aOut, child.getName()), aLink);
}
}
else {
if (aLink) {
createSymbolicLink(aIn, aOut);
}
else {
copyFile(aIn, aOut);
}
}
}
public static void copyFile(final File aIn, final File aOut)
throws IOException
{
FileUtils.copyFile(aIn, aOut);
}
public static String toString(final Object aObject)
{
if (aObject == null) {
return "null";
}
if (aObject.getClass().isArray()) {
return new ToStringBuilder(aObject, LAB_STYLE).append(aObject).toString();
}
else if (aObject instanceof Discriminable) {
return toString(((Discriminable) aObject).getDiscriminatorValue());
}
else {
return String.valueOf(aObject);
}
}
public static <T extends StreamReader> T retrieveBinary(final File aFile, final T aConsumer)
{
InputStream is = null;
try {
is = new FileInputStream(aFile);
if (aFile.getName().toLowerCase().endsWith(".gz")) {
is = new GZIPInputStream(is);
}
aConsumer.read(is);
return aConsumer;
}
catch (IOException e) {
throw new DataAccessResourceFailureException(e.getMessage(), e);
}
finally {
Util.close(is);
}
}
public static boolean isWindows()
{
return (System.getProperty("os.name").toLowerCase().indexOf("win") >= 0);
}
public static boolean isMac()
{
return (System.getProperty("os.name").toLowerCase().indexOf("mac") >= 0);
}
public static boolean isUnix()
{
String os = System.getProperty("os.name").toLowerCase();
return (os.indexOf("nix") >= 0 || os.indexOf("nux") >= 0);
}
public static boolean isSymlinkSupported()
{
return isMac() || isUnix();
}
public static void createSymbolicLink(File aSource, File aTarget) throws IOException
{
if (aTarget.exists()) {
throw new FileExistsException(aTarget);
}
File parentDir = aTarget.getAbsoluteFile().getParentFile();
if (parentDir != null && !parentDir.exists()) {
FileUtils.forceMkdir(parentDir);
}
// Try Java 7 methods
try {
Object fromPath = MethodUtils.invokeExactMethod(aSource, "toPath", new Object[0]);
Object toPath = MethodUtils.invokeExactMethod(aTarget, "toPath", new Object[0]);
Object options = Array.newInstance(Class.forName("java.nio.file.attribute.FileAttribute"), 0);
MethodInvoker inv = new MethodInvoker();
inv.setStaticMethod("java.nio.file.Files.createSymbolicLink");
inv.setArguments(new Object[] { toPath, fromPath, options });
inv.prepare();
inv.invoke();
return;
}
catch (ClassNotFoundException e) {
// Ignore
}
catch (NoSuchMethodException e) {
// Ignore
}
catch (IllegalAccessException e) {
// Ignore
}
catch (InvocationTargetException e) {
if ("java.nio.file.FileAlreadyExistsException".equals(e.getTargetException().getClass().getName())) {
throw new FileExistsException(aTarget);
}
}
// If the Java 7 stuff is not available, fall back to Runtime.exec
String[] cmdline = { "ln", "-s", aSource.getAbsolutePath(), aTarget.getAbsolutePath() };
Execute exe = new Execute();
exe.setVMLauncher(false);
exe.setCommandline(cmdline);
exe.execute();
if (exe.isFailure()) {
throw new IOException("Unable to create symlink from [" + aSource + "] to [" + aTarget
+ "]");
}
}
public static final ToStringStyle LAB_STYLE = new LabToStringStyle();
/**
* <p>Have to use a custom style here since Apache Commons Lang uses curly braces for arrays
* and we traditionally use square brackets as is used in Java Collections toString() methods.
* </p>
*/
private static final class LabToStringStyle extends ToStringStyle {
private static final long serialVersionUID = 1L;
private LabToStringStyle() {
super();
setUseClassName(false);
setUseIdentityHashCode(false);
setUseFieldNames(false);
setContentStart("");
setContentEnd("");
setArrayStart("[");
setArrayEnd("]");
setArraySeparator(", ");
}
private Object readResolve() {
return Util.LAB_STYLE;
}
}
public static String getComprehensiveMessage(Throwable aThrowable)
{
StringBuilder sb = new StringBuilder();
getComprehensiveMessage(sb, aThrowable, 0);
return sb.toString();
}
private static void getComprehensiveMessage(StringBuilder aSb, Throwable aThrowable, int aLevel)
{
aSb.append(aThrowable.getMessage());
Throwable cause = ExceptionUtils.getCause(aThrowable);
if (cause != null && cause != aThrowable) {
aSb.append('\n');
for (int i = 0; i < aLevel; i++) {
aSb.append(" ");
}
getComprehensiveMessage(aSb, cause, aLevel + 1);
}
}
}