/**********************************************************************
* Copyright (c) 2005-2009 ant4eclipse project team.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nils Hartmann, Daniel Kasmeroglu, Gerd Wuetherich
**********************************************************************/
package org.ant4eclipse.lib.core.util;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.ant4eclipse.lib.core.Assure;
import org.ant4eclipse.lib.core.CoreExceptionCode;
import org.ant4eclipse.lib.core.exception.Ant4EclipseException;
import org.ant4eclipse.lib.core.logging.A4ELogging;
import org.ant4eclipse.lib.core.nls.NLS;
import org.ant4eclipse.lib.core.nls.NLSMessage;
/**
* <p>
* Collection of utility functions that aren't specific to A4E.
* <p>
*
* @author Gerd Wütherich (gerd@gerd-wuetherich.de)
* @author Daniel Kasmeroglu (daniel.kasmeroglu@kasisoft.net)
*/
public class Utilities {
/** - */
private static final String OPEN = "${";
/** - */
private static final String CLOSE = "}";
@NLSMessage("Exporting a resource is only supported for root based pathes !")
public static String MSG_INVALIDRESOURCEPATH;
@NLSMessage("Failed to delete '%s' !")
public static String MSG_FAILEDTODELETE;
/** - */
public static final String PROP_A4ETEMPDIR = "ant4eclipse.temp";
/** - */
public static final String NL = System.getProperty("line.separator");
/** - */
public static final String ENCODING = System.getProperty("file.encoding");
/** - */
private static final String OS = System.getProperty("os.name");
static {
NLS.initialize(Utilities.class);
}
/**
* Returns a canonical representation of the supplied file.
*
* @param file
* The file which canonical representation is desired. Not <code>null</code>.
*
* @return The canonical file. Not <code>null</code>.
*/
public static final File getCanonicalFile(File file) {
Assure.notNull("file", file);
try {
return file.getCanonicalFile();
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.CANONICAL_FILE, file);
}
}
/**
* Reads the complete content of a text into a StringBuffer. Newlines will be transformed into the system specific
* newlines (@see {@link #NL} unless requested otherwise.
*
* @param resource
* The resource providing the text content. Must be located on the classpath.
* @param encoding
* The encoding to be used for the file. Neither <code>null</code> nor empty.
* @param includenewlines
* <code>true</code> <=> Allow newlines or remove them otherwise.
*
* @return The buffer containing the file content. Not <code>null</code>.
*/
public static final StringBuffer readTextContent(String resource, String encoding, boolean includenewlines) {
URL url = Utilities.class.getResource(resource);
if (url == null) {
throw new Ant4EclipseException(CoreExceptionCode.RESOURCE_NOT_ON_THE_CLASSPATH, resource);
}
return readTextContent(url, encoding, includenewlines);
}
/**
* Reads the complete content of a text into a StringBuffer. Newlines will be transformed into the system specific
* newlines (@see {@link #NL} unless requested otherwise.
*
* @param input
* The file providing the text content. Must be a valid file.
* @param encoding
* The encoding to be used for the file. Neither <code>null</code> nor empty.
* @param includenewlines
* <code>true</code> <=> Allow newlines or remove them otherwise.
*
* @return The buffer containing the file content. Not <code>null</code>.
*/
public static final StringBuffer readTextContent(File input, String encoding, boolean includenewlines) {
InputStream instream = null;
try {
instream = new FileInputStream(input);
return readTextContent(instream, encoding, includenewlines);
} catch (Ant4EclipseException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.FILEIO_FAILURE, input);
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.FILEIO_FAILURE, input);
} finally {
close(instream);
}
}
/**
* Reads the complete content of a text into a StringBuffer. Newlines will be transformed into the system specific
* newlines (@see {@link #NL} unless requested otherwise.
*
* @param input
* The resource providing the text content. Must be a valid resource.
* @param encoding
* The encoding to be used for the file. Maybe <code>null</code>.
* @param includenewlines
* <code>true</code> <=> Allow newlines or remove them otherwise.
*
* @return The buffer containing the file content. Not <code>null</code>.
*/
public static final StringBuffer readTextContent(URL input, String encoding, boolean includenewlines) {
InputStream instream = null;
try {
instream = input.openStream();
return readTextContent(instream, encoding, includenewlines);
} catch (Ant4EclipseException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.RESOURCEIO_FAILURE, input.toExternalForm());
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.RESOURCEIO_FAILURE, input.toExternalForm());
} finally {
close(instream);
}
}
/**
* Reads the complete content of a text into a StringBuffer. Newlines will be transformed into the system specific
* newlines (@see {@link #NL} unless requested otherwise.
*
* @param input
* The stream providing the text content. Not <code>null</code>.
* @param encoding
* The encoding to be used for the file. Neither <code>null</code> nor empty.
* @param includenewlines
* <code>true</code> <=> Allow newlines or remove them otherwise.
*
* @return The buffer containing the file content. Not <code>null</code>.
*/
public static final StringBuffer readTextContent(InputStream input, String encoding, boolean includenewlines) {
try {
StringBuffer result = new StringBuffer();
OutputCopier copier = new OutputCopier(input, result, encoding);
copier.start();
copier.join();
if (!includenewlines) {
int pos = result.indexOf(NL);
while (pos != -1) {
result.delete(pos, pos + NL.length());
pos = result.indexOf(NL);
}
}
return result;
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.IO_FAILURE);
} catch (InterruptedException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.FILEIO_FAILURE, input);
}
}
/**
* Closes the supplied Closeable if it's available.
*
* @param closeable
* The closeable that has to be closed. Maybe <code>null</code>.
*/
public static final void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException ex) {
// generally not interesting so a warning is apropriate here
A4ELogging.warn(ex.getMessage());
}
}
}
/**
* @see #close(Closeable)
*/
@Deprecated
public static final void close(InputStream instream) {
close((Closeable) instream);
}
/**
* @see #close(Closeable)
*/
@Deprecated
public static final void close(OutputStream outstream) {
close((Closeable) outstream);
}
/**
* @see #close(Closeable)
*/
@Deprecated
public static final void close(Reader reader) {
close((Closeable) reader);
}
/**
* @see #close(Closeable)
*/
@Deprecated
public static final void close(Writer writer) {
close((Closeable) writer);
}
public static final URL toURL(File file) {
Assure.notNull("file", file);
URI uri = file.toURI();
try {
return uri.toURL();
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
/**
* Returns true if the supplied path could be deleted completely. In case of a directory the complete tree will be
* deleted.
*
* @param file
* The path which shall be removed.
*
* @return true <=> The path could be deleted with success.
*/
public static final boolean delete(File file) {
Assure.notNull("file", file);
if (!file.exists()) {
return true;
}
boolean result = true;
if (file.isDirectory()) {
// delete the children
File[] children = file.listFiles();
if (children != null) {
for (File element : children) {
result = delete(element) && result;
}
}
}
// try to delete the file multiple times,
// since the deletion query may fail (f.e.
// if another process locked the file)
int tries = 5;
while ((!file.delete()) && (tries > 0)) {
try {
System.gc();
Thread.sleep(10);
System.gc();
} catch (InterruptedException ex) {
// do nothing here
}
tries--;
}
if (file.exists()) {
A4ELogging.warn(MSG_FAILEDTODELETE, file.getPath());
result = false;
}
return result;
}
/**
* Returns a list of all files located within the supplied directory/file.
*
* @param file
* The resource which files should be listed. If it's a file it will be the only child in the returned list.
*
* @return The list containing all children. Not <code>null</code>.
*/
public static final List<File> getAllChildren(File file) {
Assure.notNull("file", file);
List<File> result = new LinkedList<File>();
if (file.isDirectory()) {
// add the children
File[] children = file.listFiles();
if (children != null) {
for (File element : children) {
if (element.isFile()) {
result.add(element);
} else {
result.addAll(getAllChildren(element));
}
}
}
} else {
result.add(file);
}
return result;
}
public static final boolean hasChild(File directory, final String childName) {
return getChild(directory, childName) != null;
}
public static final File getChild(File directory, final String childName) {
File[] children = directory.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.equals(childName);
}
});
if (children.length < 1) {
return null;
}
return children[0];
}
/**
* Simple search replace method.
*
* @param input
* The text which should be altered. Not <code>null</code>.
* @param search
* The text that has to be removed. Not <code>null</code>.
* @param replacement
* The text which has to be used as a replacement. Not <code>null</code>.
*
* @return The modified text. Not <code>null</code>.
*/
public static final String replace(String input, String search, String replacement) {
Assure.notNull("input", input);
Assure.notNull("search", search);
Assure.notNull("replacement", replacement);
int idx = input.indexOf(search);
if (idx == -1) {
return input;
}
StringBuffer buffer = new StringBuffer(input);
while (idx != -1) {
buffer.delete(idx, idx + search.length());
buffer.insert(idx, replacement);
idx = buffer.indexOf(search, idx + replacement.length());
}
return buffer.toString();
}
/**
* Replaces a character with a specified string.
*
* @param input
* The string which will be modified. Not <code>null</code>.
* @param ch
* The character that shall be replaced.
* @param replacement
* The replacing string. Not <code>null</code>.
*
* @return A string with replaced characters. Not <code>null</code>.
*/
public static final String replace(String input, char ch, String replacement) {
return replace(input, String.valueOf(ch), replacement);
}
/**
* Calculates a relative path for the supplied files.
*
* @param fromfile
* Starting point within a file system.
* @param tofile
* Ending point within a file system.
*
* @return The file which indicates the relative path. null in case the relative path could not be calculated.
*/
public static final String calcRelative(File fromfile, File tofile) {
Assure.notNull("fromfile", fromfile);
Assure.notNull("tofile", tofile);
String frompath = null;
String topath = null;
try {
frompath = fromfile.getCanonicalPath().replace('\\', '/');
} catch (IOException ex) {
return null;
}
try {
topath = tofile.getCanonicalPath().replace('\\', '/');
} catch (IOException ex) {
return null;
}
if (frompath.equals("/")) {
// special treatment for unix filesystems since split would result in an empty list
if (topath.startsWith("/")) {
return replace(topath.substring(1), "/", File.separator);
} else {
// the other path is invalid
return null;
}
}
String[] fromstr = frompath.split("/");
String[] tostr = topath.split("/");
if (!fromstr[0].equals(tostr[0])) {
// we're not working on the same device
/**
* @todo [26-Feb-2006:KASI] Can this be omitted under UNIX ?
*/
return null;
}
int same = 1;
for (; same < Math.min(fromstr.length, tostr.length); same++) {
if (!fromstr[same].equals(tostr[same])) {
break;
}
}
StringBuffer buffer = new StringBuffer();
for (int i = same; i < fromstr.length; i++) {
buffer.append(File.separator);
buffer.append("..");
}
for (int i = same; i < tostr.length; i++) {
buffer.append(File.separator);
buffer.append(tostr[i]);
}
if (buffer.length() > 0) {
buffer.delete(0, File.separator.length());
}
return buffer.toString();
}
/**
* removes trailing / or \\ from the given path
*
* @param path
* @return the path without a trailing path separator
*/
public static final String removeTrailingPathSeparator(String path) {
if ((path == null) || (path.length() < 2)) {
return path;
}
if (path.endsWith("/") || path.endsWith("\\")) {
return path.substring(0, path.length() - 1);
}
return path;
}
/**
* Reverses the order of the elements in the specified array.
*
* @param array
* the array whose elements are to be reversed.
*/
public static <T> void reverse(T[] array) {
if (array != null) {
final List<T> list = Arrays.asList(array);
Collections.reverse(list);
}
}
/**
* <p>
* Check if a String has text. More specifically, returns <code>true</code> if the string not
* <code>null<code>, it's <code>length is > 0</code>, and it has at least one non-whitespace character.
* </p>
* <p>
*
* <pre>
* Utilities.hasText(null) = false
* Utilities.hasText("") = false
* Utilities.hasText(" ") = false
* Utilities.hasText("12345") = true
* Utilities.hasText(" 12345 ") = true
* </pre>
*
* </p>
*
* @param str
* the String to check, may be <code>null</code>
* @return <code>true</code> if the String is not null, length > 0, and not whitespace only
* @see java.lang.Character#isWhitespace
*/
public static final boolean hasText(String str) {
return cleanup(str) != null;
}
/**
* Alters the supplied String to make sure that it has a value.
*
* @param input
* The String that might be altered. Maybe <code>null</code>.
*
* @return <code>null</code> in case there's no value or a String which contains at least one valuable character.
*/
public static final String cleanup(String input) {
String result = input;
if (result != null) {
result = result.trim();
if (result.length() == 0) {
result = null;
}
}
return result;
}
/**
* Similar to {@link #cleanup(String)} the input is altered if necessary, so the returned list only consists of
* non-empty Strings. There's at least one String or the result is <code>null</code>.
*
* @param input
* The input array which might be altered. Maybe <code>null</code>.
*
* @return A list of the input values which is either <code>null</code> or contains at least one non empty value.
*/
public static final String[] cleanup(String[] input) {
List<String> result = new ArrayList<String>();
if (input != null) {
for (String element2 : input) {
String element = cleanup(element2);
if (element != null) {
result.add(element);
}
}
if (result.isEmpty()) {
return null;
}
}
return result.toArray(new String[result.size()]);
}
/**
* Generates a textual representation for the supplied list of values.
*
* @param objects
* A list of values. Not <code>null</code>.
* @param delimiter
* The delimiter to be used. If <code>null</code> the default value <code>,</code> is used.
*
* @return A textual representation for the supplied list of values. Not <code>null</code>.
*/
public static final String listToString(Object[] objects, String delimiter) {
Assure.notNull("objects", objects);
if (delimiter == null) {
delimiter = ",";
}
StringBuffer buffer = new StringBuffer();
if (objects.length > 0) {
buffer.append(String.valueOf(objects[0]));
for (int i = 1; i < objects.length; i++) {
buffer.append(delimiter);
buffer.append(String.valueOf(objects[i]));
}
}
return buffer.toString();
}
/**
* Generates a textual representation of the supplied Property map.
*
* @param properties
* The map which shall be translated into a text.
*
* @return The text representing the content of the supplied map.
*/
public static final String toString(Properties properties) {
return toString(null, properties);
}
/**
* Generates a textual representation of the supplied Property map.
*
* @param title
* A textual information printed above the property map.
* @param properties
* The map which shall be translated into a text. Not <code>null</code>.
*
* @return The text representing the content of the supplied map.
*/
public static final String toString(String title, Properties properties) {
Assure.notNull("properties", properties);
StringBuilder buffer = new StringBuilder();
if (title != null) {
buffer.append(title);
buffer.append(NL);
}
Iterator<Entry<Object, Object>> it = properties.entrySet().iterator();
while (it.hasNext()) {
Entry<Object, Object> entry = it.next();
buffer.append("'").append(entry.getKey());
buffer.append("' -> '");
buffer.append(entry.getValue());
buffer.append("'");
buffer.append(NL);
}
return buffer.toString();
}
/**
* Creates the given directory (including all of its missing parent directories) if it does not exists yet.
*
* @param directory
*/
public static final void mkdirs(File directory) {
Assure.notNull("directory", directory);
if (directory.isDirectory()) {
return;
}
if (directory.isFile()) {
throw new Ant4EclipseException(CoreExceptionCode.PATH_MUST_NOT_BE_A_FILE, directory);
}
if (!directory.mkdirs()) {
if (!directory.isDirectory()) { // if another one created the directory in between
throw new Ant4EclipseException(CoreExceptionCode.DIRECTORY_COULD_NOT_BE_CREATED, directory);
}
}
}
/**
* Creates a new instance of the class with the given name.
*
* <p>
* The class must be loadable via Class.forName() and must have a default constructor
*
* @param className
* @return
*/
@SuppressWarnings("unchecked")
public static final <T> T newInstance(String className) {
Assure.notNull("className", className);
Class<?> clazz = null;
try {
clazz = Class.forName(className);
} catch (Exception ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.COULD_NOT_LOAD_CLASS, className, ex.toString());
}
// try to instantiate using default cstr...
T object = null;
try {
object = (T) clazz.newInstance();
} catch (Exception ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.COULD_NOT_INSTANTIATE_CLASS, className, ex.toString());
}
// return the constructed object
return object;
}
@SuppressWarnings("unchecked")
public static final <T> T newInstance(String className, Class<?>[] types, Object[] args) {
Assure.notNull("className", className);
Class<?> clazz = null;
try {
clazz = Class.forName(className);
} catch (Exception ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.COULD_NOT_LOAD_CLASS, className, ex.toString());
}
// try to instantiate...
T object = null;
try {
Constructor<?> constructor = clazz.getDeclaredConstructor(types);
constructor.setAccessible(true);
constructor.newInstance(args);
object = (T) constructor.newInstance(args);
} catch (Exception ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.COULD_NOT_INSTANTIATE_CLASS, className, ex.toString());
}
// return the constructed object
return object;
}
/**
* <p>
* The class must be loadable via Class.forName() and must have a constructor with a single String parameter.
* </p>
*
* @param className
* The name of the class. Neither <code>null</code> nor empty.
* @param arg
* The argument used to instantiate the class. Maybe <code>null</code>.
*
* @return The newly instantiated type. Not <code>null</code>.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static final <T> T newInstance(String className, String arg) {
Assure.notNull("className", className);
Class<?> clazz = null;
// Try to load class...
try {
clazz = Class.forName(className);
} catch (Exception ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.COULD_NOT_LOAD_CLASS, className, ex.toString());
}
Constructor constructor = null;
try {
constructor = clazz.getConstructor(String.class);
} catch (NoSuchMethodException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.COULD_NOT_INSTANTIATE_CLASS, className, ex.toString());
}
// try to instantiate using default cstr...
T object = null;
try {
object = (T) constructor.newInstance(arg);
} catch (Exception ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.COULD_NOT_INSTANTIATE_CLASS, className, ex.toString());
}
// return the constructed object
return object;
}
/**
* Checks whether a specific literal is part of a list of allowed values.
*
* @param candidate
* The literal which has to be tested. Not <code>null</code>.
* @param allowed
* A list of allowed values. Not <code>null</code>.
*
* @return <code>true</code> <=> The supplied literal is part of the allowed values.
*/
public static final boolean contains(String candidate, String... allowed) {
Assure.notNull("candidate", candidate);
Assure.notNull("allowed", allowed);
for (String part : allowed) {
if (candidate.equals(part)) {
return true;
}
}
return false;
}
/**
* Filters a list according to a specified type. The order is preserved.
*
* @param input
* The list which will be filtered. Not <code>null</code>.
* @param clazz
* The type which must be matched by the list elements. Not <code>null</code>.
*
* @return A list with the input elements that do match the specified type. Not <code>null</code>.
*/
public static final List<Object> filter(List<Object> input, Class<?> clazz) {
Assure.notNull("input", input);
Assure.notNull("clazz", clazz);
List<Object> result = new ArrayList<Object>();
for (int i = 0; i < input.size(); i++) {
if (clazz.isAssignableFrom(input.get(i).getClass())) {
result.add(input.get(i));
}
}
return result;
}
/**
* This function copies the content of a resource into a file. This function will cause an exception in case of a
* failure.
*
* @param source
* The URL pointing to the resource. Not <code>null</code>.
* @param dest
* The destination file where the copy shall be created. Not <code>null</code>.
*/
public static final void copy(URL source, File dest) {
Assure.notNull("source", source);
Assure.notNull("dest", dest);
InputStream instream = null;
OutputStream outstream = null;
try {
instream = source.openStream();
outstream = new FileOutputStream(dest);
byte[] buffer = new byte[16384];
copy(instream, outstream, buffer);
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.COULD_NOT_EXPORT_RESOURCE, source.toExternalForm(), dest);
}
}
/**
* Unpacks the content from the supplied zip file into the supplied destination directory.
*
* @todo [11-Dec-2009:KASI] This should be merged with {@link #expandJarFile(JarFile, File)}
*
* @param zipfile
* The zip file which has to be unpacked. Not <code>null</code> and must be a file.
* @param destdir
* The directory where the content shall be written to. Not <code>null</code>.
*/
public static final void unpack(File zipfile, File destdir) {
Assure.notNull("zipfile", zipfile);
Assure.notNull("destdir", destdir);
byte[] buffer = new byte[16384];
try {
if (!destdir.isAbsolute()) {
destdir = destdir.getAbsoluteFile();
}
ZipFile zip = new ZipFile(zipfile);
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry zentry = entries.nextElement();
if (zentry.isDirectory()) {
mkdirs(new File(destdir, zentry.getName()));
} else {
File destfile = new File(destdir, zentry.getName());
mkdirs(destfile.getParentFile());
copy(zip.getInputStream(zentry), new FileOutputStream(destfile), buffer);
}
}
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.UNPACKING_FAILED, zipfile);
}
}
/**
* Copies one file to another location.
*
* @param source
* The original File which has to be copied. Not <code>null</code>.
* @param to
* The destination File which has to be written. Not <code>null</code>.
*/
public static final void copy(File source, File to) {
Assure.isFile("source", source);
Assure.notNull("to", to);
FileInputStream instream = null;
FileOutputStream outstream = null;
FileChannel readchannel = null;
FileChannel writechannel = null;
try {
instream = new FileInputStream(source);
outstream = new FileOutputStream(to);
readchannel = instream.getChannel();
writechannel = outstream.getChannel();
readchannel.transferTo(0, readchannel.size(), writechannel);
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.COPY_FAILURE, source.getAbsolutePath(), to.getAbsolutePath());
} finally {
close(readchannel);
close(writechannel);
close(instream);
close(outstream);
}
}
/**
* Copies the complete content from an InputStream into an OutputStream using a specified buffer. Both streams will be
* closed after completion or in case an exception comes up.
*
* @param instream
* The InputStream providing the content. Not <code>null</code>.
* @param outstream
* The OutputStream used to write the content to. Not <code>null</code>.
* @param buffer
* The buffer used for the copying process. Not <code>null</code>.
*
* @throws IOException
* Copying failed for some reason.
*/
public static final void copy(InputStream instream, OutputStream outstream, byte[] buffer) throws IOException {
Assure.notNull("instream", instream);
Assure.notNull("outstream", outstream);
Assure.nonEmpty("buffer", buffer);
try {
int read = instream.read(buffer);
while (read != -1) {
if (read > 0) {
outstream.write(buffer, 0, read);
}
read = instream.read(buffer);
}
} finally {
close(outstream);
close(instream);
}
}
/**
* Exports a resource which resides on the classpath into a temporarily generated file. This file will be deleted when
* the vm will be exited. Therefore the file is just used temporarily. The resource itself must be accessible through
* the ClassLoader used to load this class. The suffix will be derived from the resource. If this resource doesn't
* contain a suffix the default value <i>.tmp</i> is used.
*
* @param resource
* The path for the resource. Must start with a <i>/</i> since each path is based on the root. Neither
* <code>null</code> nor empty.
*
* @return The file keeping the exported content.
*/
public static final File exportResource(String resource) {
Assure.nonEmpty("resource", resource);
Assure.assertTrue(resource.startsWith("/"), MSG_INVALIDRESOURCEPATH);
String suffix = ".tmp";
int lidx = resource.lastIndexOf('.');
if (lidx != -1) {
suffix = resource.substring(lidx);
}
return exportResource(resource, suffix);
}
/**
* Exports a resource which resides on the classpath into a temporarily generated file. This file will be deleted when
* the vm will be exited. Therefore the file is just used temporarily. The resource itself must be accessible through
* the ClassLoader used to load this class.
*
* @param resource
* The path for the resource. Must start with a <i>/</i> since each path is based on the root. Neither
* <code>null</code> nor empty.
* @param suffix
* The suffix used for the exported file. Neither <code>null</code> nor empty.
*
* @return The file keeping the exported content.
*/
public static final File exportResource(String resource, String suffix) {
Assure.nonEmpty("resource", resource);
Assure.assertTrue(resource.startsWith("/"), MSG_INVALIDRESOURCEPATH);
URL url = Utilities.class.getResource(resource);
if (url == null) {
throw new Ant4EclipseException(CoreExceptionCode.RESOURCE_NOT_ON_THE_CLASSPATH, resource);
}
try {
File result = createTempFile("", suffix, ENCODING);
copy(url, result);
return result.getCanonicalFile();
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.IO_FAILURE);
}
}
/**
* Creates a directory which can be used for temporary data.
*
* @return A directory which can be used for temporary data. Not <code>null</code> and is a directory.
*/
public static final File createTempDir() {
try {
File result = null;
String tempdir = cleanup(System.getProperty(PROP_A4ETEMPDIR));
if (tempdir != null) {
File dir = new File(tempdir);
mkdirs(dir);
result = File.createTempFile("a4e", "dir", dir);
} else {
result = File.createTempFile("a4e", "dir");
}
if (!delete(result)) {
throw new Ant4EclipseException(CoreExceptionCode.IO_FAILURE);
}
if (!result.mkdirs()) {
throw new Ant4EclipseException(CoreExceptionCode.IO_FAILURE);
}
return result;
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.IO_FAILURE);
}
}
/**
* Writes some content into a temporary File and gives access to it.
*
* @param content
* The content which has to be written. Neither <code>null</code> nor empty.
* @param suffix
* The suffix used for the returned File. Neither <code>null</code> nor empty.
* @param encoding
* The encoding that will be used to write the content. Neither <code>null</code> nor empty.
*
* @return A temporary used file containing the supplied content. Not <code>null</code>.
*/
public static final File createTempFile(String content, String suffix, String encoding) {
Assure.notNull("content", content);
Assure.nonEmpty("encoding", encoding);
try {
File result = File.createTempFile("a4e", suffix);
writeFile(result, content, encoding);
result.deleteOnExit();
return result.getCanonicalFile();
} catch (IOException ex) {
A4ELogging.debug("Temp dir: %s", System.getProperty("java.io.tmpdir"));
A4ELogging.debug("CanWrite: %s", new File(System.getProperty("java.io.tmpdir")).canWrite());
throw new Ant4EclipseException(ex, CoreExceptionCode.IO_FAILURE);
}
}
/**
* This function stores a file under a specified location using a chosen encoding.
*
* @param destination
* The destination where the file has to be written to. Not <code>null</code>.
* @param content
* The content that has to be written. Not <code>null</code>.
* @param encoding
* The encoding that will be used to write the content. Neither <code>null</code> nor empty.
*/
public static final void writeFile(File destination, String content, String encoding) {
Assure.notNull("destination", destination);
Assure.notNull("content", content);
Assure.nonEmpty("encoding", encoding);
OutputStream output = null;
Writer writer = null;
try {
output = new FileOutputStream(destination);
writer = new OutputStreamWriter(output, encoding);
writer.write(content);
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.IO_FAILURE);
} finally {
close(writer);
close(output);
}
}
/**
* This function stores a file under a specified location using the supplied data.
*
* @param destination
* The destination where the file has to be written to. Not <code>null</code>.
* @param content
* The content that has to be written. Not <code>null</code>.
*/
public static final void writeFile(File destination, byte[] content) {
Assure.notNull("destination", destination);
Assure.notNull("content", content);
OutputStream output = null;
try {
output = new FileOutputStream(destination);
output.write(content);
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.FILEIO_FAILURE, destination);
} finally {
close(output);
}
}
public static synchronized void appendFile(File destination, byte[] content) {
Assure.notNull("destination", destination);
Assure.notNull("content", content);
OutputStream output = null;
try {
output = new FileOutputStream(destination, true);
output.write(content);
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.FILEIO_FAILURE, destination);
} finally {
close(output);
}
}
/**
* Removes a suffix from a name if it has one.
*
* @param name
* The name which suffix has to be removed. Not <code>null</code>.
*
* @return The name without the suffix.
*/
public static final String stripSuffix(String name) {
Assure.notNull("name", name);
int lidx = name.lastIndexOf('.');
if (lidx != -1) {
return name.substring(0, lidx);
} else {
return name;
}
}
/**
* Executes a single command. The output stream can be captured within a buffer if desired. This method does
* <b>NOT</b> support to provide some input. Failures will cause exceptions.
*
* @param exe
* The location of the executable. Not <code>null</code>.
* @param output
* A buffer for the output stream. Maybe <code>null</code>.
* @param args
* The arguments for the execution. Maybe <code>null</code>.
*/
public static final void execute(File exe, StringBuffer output, String... args) {
execute(exe, output, null, args);
}
/**
* <p>
* Expands the specified jar file to the expansion directory.
* </p>
*
* @param jarFile
* the jar file to expand
* @param expansionDirectory
* the expansion directory
*/
public static synchronized final void expandJarFile(JarFile jarFile, File expansionDirectory) {
Assure.notNull("jarFile", jarFile);
Assure.notNull("expansionDirectory", expansionDirectory);
if (expansionDirectory.exists()) {
A4ELogging.debug("%s|Already expanded '%s' to '%s'", Thread.currentThread().getId(), jarFile, expansionDirectory);
return;
}
A4ELogging.debug("%s|Expanding '%s' to '%s'", Thread.currentThread().getId(), jarFile, expansionDirectory);
mkdirs(expansionDirectory);
// this way we make sure that calls to File#getParentFile always return non-null values
expansionDirectory = expansionDirectory.getAbsoluteFile();
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
File destFile = new File(expansionDirectory, zipEntry.getName());
if (destFile.exists()) {
// a directory might already have been created
continue;
}
if (zipEntry.isDirectory()) {
mkdirs(destFile);
} else {
mkdirs(destFile.getParentFile());
InputStream inputStream = null;
try {
inputStream = jarFile.getInputStream(zipEntry);
writeFile(inputStream, destFile);
} catch (IOException ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.IO_FAILURE);
} finally {
close(inputStream);
}
}
}
}
private static void writeFile(InputStream inputStream, File file) {
Assure.notNull("inputStream", inputStream);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
byte buffer[] = new byte[1024];
int count;
while ((count = inputStream.read(buffer, 0, buffer.length)) > 0) {
fos.write(buffer, 0, count);
}
} catch (IOException e) {
/**
* @todo [28-Jun-2009:KASI] The original code didn't take care of this, since it only closed the streams (now done
* within the finally sequence). Nevertheless the resource might not have been copied completely, so there's
* need to be a valid treatment.
*/
} finally {
// close open streams
close(fos);
close(inputStream);
}
}
/**
* Executes a single command. The output and the error stream can be captured within buffers if desired. This method
* does <b>NOT</b> support to provide some input. Failures will cause exceptions.
*
* @param exe
* The location of the executable. Not <code>null</code>.
* @param output
* A buffer for the output stream. Maybe <code>null</code>.
* @param error
* A buffer for the error stream. Maybe <code>null</code>.
* @param args
* The arguments for the execution. Maybe <code>null</code>.
*/
public static final void execute(File exe, StringBuffer output, StringBuffer error, String... args) {
try {
if (output == null) {
output = new StringBuffer();
}
if (error == null) {
error = new StringBuffer();
}
String[] cmdarray = null;
if (args == null) {
cmdarray = new String[] { exe.getAbsolutePath() };
} else {
cmdarray = new String[args.length + 1];
cmdarray[0] = exe.getAbsolutePath();
System.arraycopy(args, 0, cmdarray, 1, args.length);
}
Process process = Runtime.getRuntime().exec(cmdarray);
OutputCopier outcopier = new OutputCopier(process.getInputStream(), output, ENCODING);
OutputCopier errcopier = new OutputCopier(process.getErrorStream(), error, ENCODING);
outcopier.start();
errcopier.start();
int result = process.waitFor();
if (result != 0) {
A4ELogging.error(CoreExceptionCode.LAUNCHING_FAILURE.getMessage(), exe, Integer.valueOf(result), output, error);
throw new Ant4EclipseException(CoreExceptionCode.LAUNCHING_FAILURE, exe, Integer.valueOf(result), output, error);
}
} catch (Exception ex) {
throw new Ant4EclipseException(ex, CoreExceptionCode.EXECUTION_FAILURE, exe);
}
}
/**
* Returns <code>true</code> if the supplied objects are equal. This function is capable to deal with
* <code>null</code> values.
*
* @param <T>
* The type of parameters that will be used.
* @param o1
* One object. Maybe <code>null</code>.
* @param o2
* Another object. Maybe <code>null</code>.
*
* @return <code>true</code> <=> Both objects are equal.
*/
public static final <T> boolean equals(T o1, T o2) {
if (o1 == null) {
return o2 == null;
} else if (o1 == o2) {
// not necessary but efficient
return true;
} else {
return o1.equals(o2);
}
}
/**
* <p>
* Performs a textual replacement for variables. Variables must be enclosed within {@link #OPEN} and {@link #CLOSE}.
* </p>
*
* @param template
* The template containing variable references. Not <code>null</code>.
* @param replacements
* The replacement for the variables. Not <code>null</code>.
*
* @return The evaluated result. Not <code>null</code>.
*/
public static final String replaceTokens(String template, Map<String, String> replacements) {
return replaceTokens(template, replacements, OPEN, CLOSE);
}
/**
* <p>
* Performs a textual replacement for variables.
* </p>
*
* @param template
* The template containing variable references. Not <code>null</code>.
* @param replacements
* The replacement for the variables. Not <code>null</code>.
* @param openlit
* The opening literal for a variable reference. Neither <code>null</code> nor empty.
* @param closelit
* The closing literal for a variable reference. Neither <code>null</code> nor empty.
*
* @return The evaluated result. Not <code>null</code>.
*/
public static final String replaceTokens(String template, Map<String, String> replacements, String openlit,
String closelit) {
Assure.notNull("template", template);
Assure.notNull("replacements", replacements);
Assure.notNull("openlit", openlit);
Assure.notNull("closelit", closelit);
StringBuffer buffer = new StringBuffer(template);
int index = buffer.indexOf(openlit);
while (index != -1) {
int next = buffer.indexOf(openlit, index + openlit.length());
int close = buffer.indexOf(closelit, index + openlit.length());
if (close == -1) {
// no closing anymore available, so there's no replacement operation to be done anymore
break;
}
if ((next != -1) && (next < close)) {
// no close for the current literal, so continue with the next candidate
index = next;
continue;
}
String key = buffer.substring(index + openlit.length(), close);
String value = replacements.get(key);
if (value != null) {
// remove the existing content
buffer.delete(index, close + closelit.length());
buffer.insert(index, value);
// this is advisable as the value might contain the key, so we're preventing a loop here
index += value.length();
} else {
// no replacement value found, so go on with the next candidate
index = close + closelit.length();
}
index = buffer.indexOf(openlit, index);
}
return buffer.toString();
}
/**
* Returns <code>true</<code> if we're currently running under windows.
*
* @return <code>true</code> <=> We're currently running under windows.
*/
public static final boolean isWindows() {
return OS.toLowerCase().startsWith("windows");
}
/**
* Splits the supplied text into a list of lines. Only lines with content will be delivered. The returned list is
* allowed to be altered.
*
* @param text
* The text which has to be splitted. Not <code>null</code>.
*
* @return The list of lines provided by the supplied text.
*/
public static final List<String> splitText(String text) {
return splitText(text, false);
}
/**
* Splits the supplied text into a list of lines. Only lines with content will be delivered. The returned list is
* allowed to be altered.
*
* @param text
* The text which has to be splitted. Not <code>null</code>.
* @param incall
* <code>true</code> <=> Include all lines.
*
* @return The list of lines provided by the supplied text.
*/
public static final List<String> splitText(String text, boolean incall) {
List<String> result = new ArrayList<String>();
BufferedReader reader = new BufferedReader(new StringReader(text));
try {
String line = reader.readLine();
while (line != null) {
if (incall || (Utilities.cleanup(line) != null)) {
result.add(line);
}
line = reader.readLine();
}
} catch (IOException ex) {
// as we're only working within memory this shouldn't happen
throw new Ant4EclipseException(CoreExceptionCode.IO_FAILURE);
}
return result;
}
/**
* Simple Thread extension that copies content from an InputStream into StringBuffer.
*
* @author Daniel Kasmeroglu
*/
private static final class OutputCopier extends Thread {
private BufferedReader _source;
private StringBuffer _receiver;
/**
* Initalises this copiying process.
*
* @param instream
* The stream which provides the content. Not <code>null</code>.
* @param dest
* The destination buffer used to get the output. Maybe <code>null</code>.
* @param encoding
* The encoding to be used while accessing the strem. Not <code>null</code>.
*/
public OutputCopier(InputStream instream, StringBuffer dest, String encoding) throws UnsupportedEncodingException {
if (encoding == null) {
encoding = ENCODING;
}
this._source = new BufferedReader(new InputStreamReader(instream, encoding));
this._receiver = dest;
}
/**
* {@inheritDoc}
*/
@Override
public void run() {
try {
String line = this._source.readLine();
while (line != null) {
this._receiver.append(line);
this._receiver.append(NL);
line = this._source.readLine();
}
} catch (IOException ex) {
/** @todo [06-Aug-2009:KASI] We might need something more precise here. */
throw new Ant4EclipseException(ex, CoreExceptionCode.IO_FAILURE);
}
}
} /* ENDCLASS */
} /* ENDCLASS */