blob: 5a19a38e879d432ec9652291b72b6da635b22734 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* 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 com.google.gwt.dev.util;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.util.tools.Utility;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* A smattering of useful methods. Methods in this class are candidates for
* being moved to {@link com.google.gwt.util.tools.Utility} if they would be
* generally useful to tool writers, and don't involve TreeLogger.
*/
public final class Util {
public static String DEFAULT_ENCODING = "UTF-8";
public static final File[] EMPTY_ARRAY_FILE = new File[0];
public static final String[] EMPTY_ARRAY_STRING = new String[0];
/**
* The size of a {@link #threadLocalBuf}, which should be large enough for
* efficient data transfer but small enough to fit easily into the L2 cache of
* most modern processors.
*/
private static final int THREAD_LOCAL_BUF_SIZE = 16 * 1024;
/**
* Stores reusable thread local buffers for efficient data transfer.
*/
private static final ThreadLocal<byte[]> threadLocalBuf = new ThreadLocal<byte[]>();
public static byte[] append(byte[] xs, byte x) {
int n = xs.length;
byte[] t = new byte[n + 1];
System.arraycopy(xs, 0, t, 0, n);
t[n] = x;
return t;
}
@SuppressWarnings("unchecked")
public static <T> T[] append(T[] xs, T x) {
int n = xs.length;
T[] t = (T[]) Array.newInstance(xs.getClass().getComponentType(), n + 1);
System.arraycopy(xs, 0, t, 0, n);
t[n] = x;
return t;
}
@SuppressWarnings("unchecked")
public static <T> T[] append(T[] appendToThis, T[] these) {
if (appendToThis == null) {
throw new NullPointerException("attempt to append to a null array");
}
if (these == null) {
throw new NullPointerException("attempt to append a null array");
}
T[] result;
int newSize = appendToThis.length + these.length;
Class<?> componentType = appendToThis.getClass().getComponentType();
result = (T[]) Array.newInstance(componentType, newSize);
System.arraycopy(appendToThis, 0, result, 0, appendToThis.length);
System.arraycopy(these, 0, result, appendToThis.length, these.length);
return result;
}
/**
* Computes the MD5 hash for the specified byte array.
*
* @return a big fat string encoding of the MD5 for the content, suitably
* formatted for use as a file name
*/
public static String computeStrongName(byte[] content) {
return computeStrongName(new byte[][] {content});
}
/**
* Computes the MD5 hash of the specified byte arrays.
*
* @return a big fat string encoding of the MD5 for the content, suitably
* formatted for use as a file name
*/
public static String computeStrongName(byte[][] contents) {
MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Error initializing MD5", e);
}
/*
* Include the lengths of the contents components in the hash, so that the
* hashed sequence of bytes is in a one-to-one correspondence with the
* possible arguments to this method.
*/
ByteBuffer b = ByteBuffer.allocate((contents.length + 1) * 4);
b.putInt(contents.length);
for (int i = 0; i < contents.length; i++) {
b.putInt(contents[i].length);
}
b.flip();
md5.update(b);
// Now hash the actual contents of the arrays
for (int i = 0; i < contents.length; i++) {
md5.update(contents[i]);
}
return Utility.toHexString(md5.digest());
}
public static void copy(InputStream is, OutputStream os) throws IOException {
try {
copyNoClose(is, os);
} finally {
Utility.close(is);
Utility.close(os);
}
}
public static boolean copy(TreeLogger logger, File in, File out)
throws UnableToCompleteException {
try {
if (in.lastModified() > out.lastModified()) {
copy(logger, new FileInputStream(in), out);
return true;
} else {
return false;
}
} catch (FileNotFoundException e) {
logger.log(TreeLogger.ERROR, "Unable to open file '"
+ in.getAbsolutePath() + "'", e);
throw new UnableToCompleteException();
}
}
/**
* Copies an input stream out to a file. Closes the input steam.
*/
public static void copy(TreeLogger logger, InputStream is, File out)
throws UnableToCompleteException {
try {
// No need to check mkdirs result because an IOException will occur anyway
out.getParentFile().mkdirs();
copy(logger, is, new FileOutputStream(out));
} catch (FileNotFoundException e) {
logger.log(TreeLogger.ERROR, "Unable to create file '"
+ out.getAbsolutePath() + "'", e);
throw new UnableToCompleteException();
}
}
/**
* Copies an input stream out to an output stream. Closes the input steam and
* output stream.
*/
public static void copy(TreeLogger logger, InputStream is, OutputStream os)
throws UnableToCompleteException {
try {
copy(is, os);
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Error during copy", e);
throw new UnableToCompleteException();
}
}
public static boolean copy(TreeLogger logger, URL in, File out)
throws UnableToCompleteException {
try {
URLConnection conn = in.openConnection();
if (conn.getLastModified() > out.lastModified()) {
copy(logger, in.openStream(), out);
return true;
} else {
return false;
}
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Unable to open '" + in.toExternalForm()
+ "'", e);
throw new UnableToCompleteException();
}
}
/**
* Copies all of the bytes from the input stream to the output stream until
* the input stream is EOF. Does not close either stream.
*/
public static void copyNoClose(InputStream is, OutputStream os)
throws IOException {
byte[] buf = takeThreadLocalBuf();
try {
int i;
while ((i = is.read(buf)) != -1) {
os.write(buf, 0, i);
}
} finally {
releaseThreadLocalBuf(buf);
}
}
public static Reader createReader(TreeLogger logger, URL url)
throws UnableToCompleteException {
try {
return new InputStreamReader(url.openStream());
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Unable to open resource: " + url, e);
throw new UnableToCompleteException();
}
}
public static void deleteFilesInDirectory(File dir) {
File[] files = dir.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
File file = files[i];
if (file.isFile()) {
file.delete();
}
}
}
}
/**
* Deletes all files have the same base name as the specified file.
*/
public static void deleteFilesStartingWith(File dir, final String prefix) {
File[] toDelete = dir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(prefix);
}
});
if (toDelete != null) {
for (int i = 0; i < toDelete.length; i++) {
toDelete[i].delete();
}
}
}
/**
* Escapes '&', '<', '>', '"', and '\'' to their XML entity equivalents.
*/
public static String escapeXml(String unescaped) {
StringBuilder builder = new StringBuilder();
escapeXml(unescaped, 0, unescaped.length(), true, builder);
return builder.toString();
}
/**
* Escapes '&', '<', '>', '"', and optionally ''' to their XML entity
* equivalents. The portion of the input string between start (inclusive) and
* end (exclusive) is scanned. The output is appended to the given
* StringBuilder.
*
* @param code the input String
* @param start the first character position to scan.
* @param end the character position following the last character to scan.
* @param quoteApostrophe if true, the &apos; character is quoted as
* &amp;apos;
* @param builder a StringBuilder to be appended with the output.
*/
public static void escapeXml(String code, int start, int end,
boolean quoteApostrophe, StringBuilder builder) {
int lastIndex = 0;
int len = end - start;
char[] c = new char[len];
code.getChars(start, end, c, 0);
for (int i = 0; i < len; i++) {
switch (c[i]) {
case '&':
builder.append(c, lastIndex, i - lastIndex);
builder.append("&amp;");
lastIndex = i + 1;
break;
case '>':
builder.append(c, lastIndex, i - lastIndex);
builder.append("&gt;");
lastIndex = i + 1;
break;
case '<':
builder.append(c, lastIndex, i - lastIndex);
builder.append("&lt;");
lastIndex = i + 1;
break;
case '\"':
builder.append(c, lastIndex, i - lastIndex);
builder.append("&quot;");
lastIndex = i + 1;
break;
case '\'':
if (quoteApostrophe) {
builder.append(c, lastIndex, i - lastIndex);
builder.append("&apos;");
lastIndex = i + 1;
}
break;
default:
break;
}
}
builder.append(c, lastIndex, len - lastIndex);
}
public static URL findSourceInClassPath(ClassLoader cl, String sourceTypeName) {
String toTry = sourceTypeName.replace('.', '/') + ".java";
URL foundURL = cl.getResource(toTry);
if (foundURL != null) {
return foundURL;
}
int i = sourceTypeName.lastIndexOf('.');
if (i != -1) {
return findSourceInClassPath(cl, sourceTypeName.substring(0, i));
} else {
return null;
}
}
/**
* Returns a byte-array representing the default encoding for a String.
*/
public static byte[] getBytes(String s) {
try {
return s.getBytes(DEFAULT_ENCODING);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(
"The JVM does not support the compiler's default encoding.", e);
}
}
/**
* Returns an array of byte-arrays representing the default encoding for an
* array of Strings.
*/
public static byte[][] getBytes(String[] s) {
byte[][] bytes = new byte[s.length][];
for (int i = 0; i < s.length; i++) {
bytes[i] = getBytes(s[i]);
}
return bytes;
}
/**
* @param cls A class whose name you want.
* @return The base name for the specified class.
*/
public static String getClassName(Class<?> cls) {
return getClassName(cls.getName());
}
/**
* @param className A fully-qualified class name whose name you want.
* @return The base name for the specified class.
*/
public static String getClassName(String className) {
return className.substring(className.lastIndexOf('.') + 1);
}
/**
* Gets the contents of a file.
*
* @param relativePath relative path within the install directory
* @return the contents of the file, or null if an error occurred
*/
public static String getFileFromInstallPath(String relativePath) {
String installPath = Utility.getInstallPath();
File file = new File(installPath + '/' + relativePath);
return readFileAsString(file);
}
/**
* A 4-digit hex result.
*
* @deprecated use {@link Utility#hex4(char, StringBuffer)} instead.
*/
@Deprecated
public static void hex4(char c, StringBuffer sb) {
Utility.hex4(c, sb);
}
/**
* This method invokes an inaccessible method in another class.
*
* @param targetClass the class owning the method
* @param methodName the name of the method
* @param argumentTypes the types of the parameters to the method call
* @param target the receiver of the method call
* @param arguments the parameters to the method call
*/
public static void invokeInaccessableMethod(Class<?> targetClass,
String methodName, Class<?>[] argumentTypes, TypeOracle target,
Object[] arguments) {
String failedReflectErrMsg = "The definition of " + targetClass.getName()
+ "." + methodName + " has changed in an " + "incompatible way.";
try {
Method m = targetClass.getDeclaredMethod(methodName, argumentTypes);
m.setAccessible(true);
m.invoke(target, arguments);
} catch (NoSuchMethodException e) {
throw new RuntimeException(failedReflectErrMsg, e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(failedReflectErrMsg, e);
} catch (IllegalAccessException e) {
throw new RuntimeException(failedReflectErrMsg, e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getTargetException());
}
}
public static boolean isValidJavaIdent(String token) {
if (token.length() == 0) {
return false;
}
if (!Character.isJavaIdentifierStart(token.charAt(0))) {
return false;
}
for (int i = 1, n = token.length(); i < n; i++) {
if (!Character.isJavaIdentifierPart(token.charAt(i))) {
return false;
}
}
return true;
}
/**
* Attempts to make a path relative to a particular directory.
*
* @param from the directory from which 'to' should be relative
* @param to an absolute path which will be returned so that it is relative to
* 'from'
* @return the relative path, if possible; null otherwise
*/
public static File makeRelativeFile(File from, File to) {
// Keep ripping off directories from the 'from' path until the 'from' path
// is a prefix of the 'to' path.
//
String toPath = tryMakeCanonical(to).getAbsolutePath();
File currentFrom = tryMakeCanonical(from.isDirectory() ? from
: from.getParentFile());
int numberOfBackups = 0;
while (currentFrom != null) {
String currentFromPath = currentFrom.getPath();
if (toPath.startsWith(currentFromPath)) {
// Found a prefix!
//
break;
} else {
++numberOfBackups;
currentFrom = currentFrom.getParentFile();
}
}
if (currentFrom == null) {
// Cannot make it relative.
//
return null;
}
// Find everything to the right of the common prefix.
//
String trailingToPath = toPath.substring(currentFrom.getAbsolutePath().length());
if (currentFrom.getParentFile() != null && trailingToPath.length() > 0) {
trailingToPath = trailingToPath.substring(1);
}
File relativeFile = new File(trailingToPath);
for (int i = 0; i < numberOfBackups; ++i) {
relativeFile = new File("..", relativeFile.getPath());
}
return relativeFile;
}
// /**
// * Reads the file as an array of strings.
// */
// public static String[] readURLAsStrings(URL url) {
// ArrayList lines = new ArrayList();
// String contents = readURLAsString(url);
// if (contents != null) {
// StringReader sr = new StringReader(contents);
// BufferedReader br = new BufferedReader(sr);
// String line;
// while (null != (line = readNextLine(br)))
// lines.add(line);
// }
// return (String[]) lines.toArray(new String[lines.size()]);
// }
public static String makeRelativePath(File from, File to) {
File f = makeRelativeFile(from, to);
return (f != null ? f.getPath() : null);
}
public static String makeRelativePath(File from, String to) {
File f = makeRelativeFile(from, new File(to));
return (f != null ? f.getPath() : null);
}
public static byte[] readFileAsBytes(File file) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
int length = (int) file.length();
return readBytesFromInputStream(fileInputStream, length);
} catch (IOException e) {
return null;
} finally {
Utility.close(fileInputStream);
}
}
public static char[] readFileAsChars(File file) {
String string = readFileAsString(file);
if (string != null) {
return string.toCharArray();
}
return null;
}
public static <T extends Serializable> T readFileAsObject(File file,
Class<T> type) throws ClassNotFoundException, IOException {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
return readStreamAsObject(fileInputStream, type);
} finally {
Utility.close(fileInputStream);
}
}
public static String readFileAsString(File file) {
byte[] bytes = readFileAsBytes(file);
if (bytes != null) {
return toString(bytes, DEFAULT_ENCODING);
}
return null;
}
/**
* Reads the next non-empty line.
*
* @return a non-empty string that has been trimmed or null if the reader is
* exhausted
*/
public static String readNextLine(BufferedReader br) {
try {
String line = br.readLine();
while (line != null) {
line = line.trim();
if (line.length() > 0) {
break;
}
line = br.readLine();
}
return line;
} catch (IOException e) {
return null;
}
}
/**
* Reads an entire input stream as bytes. Closes the input stream.
*/
public static byte[] readStreamAsBytes(InputStream in) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
copy(in, out);
return out.toByteArray();
} catch (IOException e) {
return null;
}
}
public static <T> T readStreamAsObject(InputStream inputStream, Class<T> type)
throws ClassNotFoundException, IOException {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(inputStream);
return type.cast(objectInputStream.readObject());
} finally {
Utility.close(objectInputStream);
}
}
/**
* Reads an entire input stream as String. Closes the input stream.
*/
public static String readStreamAsString(InputStream in) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
copy(in, out);
return out.toString(DEFAULT_ENCODING);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(
"The JVM does not support the compiler's default encoding.", e);
} catch (IOException e) {
return null;
}
}
/**
* @return null if the file could not be read
*/
public static byte[] readURLAsBytes(URL url) {
try {
URLConnection conn = url.openConnection();
conn.setUseCaches(false);
return readURLConnectionAsBytes(conn);
} catch (IOException e) {
return null;
}
}
/**
* @return null if the file could not be read
*/
public static char[] readURLAsChars(URL url) {
byte[] bytes = readURLAsBytes(url);
if (bytes != null) {
return toString(bytes, DEFAULT_ENCODING).toCharArray();
}
return null;
}
/**
* @return null if the file could not be read
*/
public static String readURLAsString(URL url) {
byte[] bytes = readURLAsBytes(url);
if (bytes != null) {
return toString(bytes, DEFAULT_ENCODING);
}
return null;
}
public static byte[] readURLConnectionAsBytes(URLConnection connection) {
// ENH: add a weak cache that has an additional check against the file date
InputStream input = null;
try {
input = connection.getInputStream();
int contentLength = connection.getContentLength();
if (contentLength < 0) {
return null;
}
return readBytesFromInputStream(input, contentLength);
} catch (IOException e) {
return null;
} finally {
Utility.close(input);
}
}
/**
* Deletes a file or recursively deletes a directory.
*
* @param file the file to delete, or if this is a directory, the directory
* that serves as the root of a recursive deletion
* @param childrenOnly if <code>true</code>, only the children of a
* directory are recursively deleted but the specified directory
* itself is spared; if <code>false</code>, the specified
* directory is also deleted; ignored if <code>file</code> is not a
* directory
*/
public static void recursiveDelete(File file, boolean childrenOnly) {
recursiveDelete(file, childrenOnly, null);
}
/**
* Selectively deletes a file or recursively deletes a directory. Note that
* it is possible that files remain if file.delete() fails.
*
* @param file the file to delete, or if this is a directory, the directory
* that serves as the root of a recursive deletion
* @param childrenOnly if <code>true</code>, only the children of a
* directory are recursively deleted but the specified directory
* itself is spared; if <code>false</code>, the specified
* directory is also deleted; ignored if <code>file</code> is not a
* directory
* @param filter only files matching this filter will be deleted
*/
public static void recursiveDelete(File file, boolean childrenOnly,
FileFilter filter) {
if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null) {
for (int i = 0; i < children.length; i++) {
recursiveDelete(children[i], false, filter);
}
}
if (childrenOnly) {
// Do not delete the specified directory itself.
return;
}
}
if (filter == null || filter.accept(file)) {
file.delete();
}
}
/**
* Recursively lists a directory, returning the partial paths of the child
* files.
*
* @param parent the directory to start from
* @param includeDirs whether or not to include directories in the results
* @return all partial paths descending from the parent file
*/
public static SortedSet<String> recursiveListPartialPaths(File parent,
boolean includeDirs) {
assert parent != null;
TreeSet<String> toReturn = new TreeSet<String>();
int start = parent.getAbsolutePath().length() + 1;
List<File> q = new LinkedList<File>();
q.add(parent);
while (!q.isEmpty()) {
File f = q.remove(0);
if (f.isDirectory()) {
if (includeDirs) {
toReturn.add(f.getAbsolutePath().substring(start));
}
q.addAll(Arrays.asList(f.listFiles()));
} else {
toReturn.add(f.getAbsolutePath().substring(start));
}
}
return toReturn;
}
/**
* Release a buffer previously returned from {@link #takeThreadLocalBuf()}.
* The released buffer may then be reused.
*/
public static void releaseThreadLocalBuf(byte[] buf) {
assert buf.length == THREAD_LOCAL_BUF_SIZE;
threadLocalBuf.set(buf);
}
public static File removeExtension(File file) {
String name = file.getName();
int lastDot = name.lastIndexOf('.');
if (lastDot != -1) {
name = name.substring(0, lastDot);
}
return new File(file.getParentFile(), name);
}
@SuppressWarnings("unchecked")
public static <T> T[] removeNulls(T[] a) {
int n = a.length;
for (int i = 0; i < a.length; i++) {
if (a[i] == null) {
--n;
}
}
Class<?> componentType = a.getClass().getComponentType();
T[] t = (T[]) Array.newInstance(componentType, n);
int out = 0;
for (int in = 0; in < t.length; in++) {
if (a[in] != null) {
t[out++] = a[in];
}
}
return t;
}
/**
* @param path The path to slashify.
* @return The path with any directory separators replaced with '/'.
*/
public static String slashify(String path) {
path = path.replace(File.separatorChar, '/');
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
return path;
}
/**
* Get a large byte buffer local to this thread. Currently this is set to a
* 16k buffer, which is small enough to fit into the L2 cache on modern
* processors. The contents of the returned buffer are undefined. Calling
* {@link #releaseThreadLocalBuf(byte[])} on the returned buffer allows
* subsequent callers to reuse the buffer later, avoiding unncessary
* allocations and GC.
*/
public static byte[] takeThreadLocalBuf() {
byte[] buf = threadLocalBuf.get();
if (buf == null) {
buf = new byte[THREAD_LOCAL_BUF_SIZE];
} else {
threadLocalBuf.set(null);
}
return buf;
}
/**
* Creates an array from a collection of the specified component type and
* size. You can definitely downcast the result to T[] if T is the specified
* component type.
*
* Class<? super T> is used to allow creation of generic types, such as
* Map.Entry<K,V> since we can only pass in Map.Entry.class.
*/
@SuppressWarnings("unchecked")
public static <T> T[] toArray(Class<? super T> componentType,
Collection<? extends T> coll) {
int n = coll.size();
T[] a = (T[]) Array.newInstance(componentType, n);
return coll.toArray(a);
}
/**
* Like {@link #toArray(Class, Collection)}, but the option of having the
* array reversed.
*/
@SuppressWarnings("unchecked")
public static <T> T[] toArrayReversed(Class<? super T> componentType,
Collection<? extends T> coll) {
int n = coll.size();
T[] a = (T[]) Array.newInstance(componentType, n);
int i = n - 1;
for (Iterator<? extends T> iter = coll.iterator(); iter.hasNext(); --i) {
a[i] = iter.next();
}
return a;
}
/**
* Returns a string representation of the byte array as a series of
* hexadecimal characters.
*
* @param bytes byte array to convert
* @return a string representation of the byte array as a series of
* hexadecimal characters
* @deprecated use {@link Utility#toHexString(byte[])} instead.
*/
@Deprecated
public static String toHexString(byte[] bytes) {
return Utility.toHexString(bytes);
}
/**
* Returns a String representing the character content of the bytes; the bytes
* must be encoded using the compiler's default encoding.
*/
public static String toString(byte[] bytes) {
return toString(bytes, DEFAULT_ENCODING);
}
/**
* Creates a string array from the contents of a collection.
*/
public static String[] toStringArray(Collection<String> coll) {
return toArray(String.class, coll);
}
public static String[] toStrings(byte[][] bytes) {
String[] strings = new String[bytes.length];
for (int i = 0; i < bytes.length; i++) {
strings[i] = toString(bytes[i]);
}
return strings;
}
public static URL toURL(File f) {
try {
return f.toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException("Failed to convert a File to a URL", e);
}
}
public static String toXml(Document doc) {
Throwable caught = null;
try {
byte[] bytes = toXmlUtf8(doc);
return new String(bytes, DEFAULT_ENCODING);
} catch (UnsupportedEncodingException e) {
caught = e;
}
throw new RuntimeException("Unable to encode xml string as utf-8", caught);
}
public static byte[] toXmlUtf8(Document doc) {
Throwable caught = null;
try {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
writeDocument(pw, doc);
return sw.toString().getBytes(DEFAULT_ENCODING);
} catch (UnsupportedEncodingException e) {
caught = e;
} catch (IOException e) {
caught = e;
}
throw new RuntimeException(
"Unable to encode xml document object as a string", caught);
// THE COMMENTED-OUT CODE BELOW IS THE WAY I'D LIKE TO GENERATE XML,
// BUT IT SEEMS TO BLOW UP WHEN YOU CHANGE JRE VERSIONS AND/OR RUN
// IN TOMCAT. INSTEAD, I JUST SLAPPED TOGETHER THE MINIMAL STUFF WE
// NEEDED TO WRITE CACHE ENTRIES.
// Throwable caught = null;
// try {
// TransformerFactory transformerFactory = TransformerFactory.newInstance();
// Transformer transformer = transformerFactory.newTransformer();
// transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT,
// "yes");
// transformer.setOutputProperty(
// "{http://xml.apache.org/xslt}indent-amount", "4");
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
// OutputStreamWriter osw = new OutputStreamWriter(baos, "UTF-8");
// StreamResult result = new StreamResult(osw);
// DOMSource domSource = new DOMSource(doc);
// transformer.transform(domSource, result);
// byte[] bytes = baos.toByteArray();
// return bytes;
// } catch (TransformerConfigurationException e) {
// caught = e;
// } catch (UnsupportedEncodingException e) {
// caught = e;
// } catch (TransformerException e) {
// caught = e;
// }
// throw new RuntimeException(
// "Unable to encode xml document object as a string", caught);
}
public static File tryCombine(File parentMaybeIgnored, File childMaybeAbsolute) {
if (childMaybeAbsolute == null) {
return parentMaybeIgnored;
} else if (childMaybeAbsolute.isAbsolute()) {
return childMaybeAbsolute;
} else {
return new File(parentMaybeIgnored, childMaybeAbsolute.getPath());
}
}
public static File tryCombine(File parentMaybeIgnored,
String childMaybeAbsolute) {
return tryCombine(parentMaybeIgnored, new File(childMaybeAbsolute));
}
/**
* Attempts to find the canonical form of a file path.
*
* @return the canonical version of the file path, if it could be computed;
* otherwise, the original file is returned unmodified
*/
public static File tryMakeCanonical(File file) {
try {
return file.getCanonicalFile();
} catch (IOException e) {
return file;
}
}
public static void writeBytesToFile(TreeLogger logger, File where, byte[] what)
throws UnableToCompleteException {
writeBytesToFile(logger, where, new byte[][] {what});
}
/**
* Gathering write.
*/
public static void writeBytesToFile(TreeLogger logger, File where,
byte[][] what) throws UnableToCompleteException {
FileOutputStream f = null;
Throwable caught;
try {
// No need to check mkdirs result because an IOException will occur anyway
where.getParentFile().mkdirs();
f = new FileOutputStream(where);
for (int i = 0; i < what.length; i++) {
f.write(what[i]);
}
return;
} catch (FileNotFoundException e) {
caught = e;
} catch (IOException e) {
caught = e;
} finally {
Utility.close(f);
}
String msg = "Unable to write file '" + where + "'";
logger.log(TreeLogger.ERROR, msg, caught);
throw new UnableToCompleteException();
}
public static void writeCharsAsFile(TreeLogger logger, File file, char[] chars)
throws UnableToCompleteException {
FileOutputStream stream = null;
OutputStreamWriter writer = null;
BufferedWriter buffered = null;
try {
// No need to check mkdirs result because an IOException will occur anyway
file.getParentFile().mkdirs();
stream = new FileOutputStream(file);
writer = new OutputStreamWriter(stream, DEFAULT_ENCODING);
buffered = new BufferedWriter(writer);
buffered.write(chars);
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Unable to write file: "
+ file.getAbsolutePath(), e);
throw new UnableToCompleteException();
} finally {
Utility.close(buffered);
Utility.close(writer);
Utility.close(stream);
}
}
/**
* Serializes an object and writes it to a file.
*/
public static void writeObjectAsFile(TreeLogger logger, File file,
Object... objects) throws UnableToCompleteException {
Event writeObjectAsFileEvent = SpeedTracerLogger.start(CompilerEventType.WRITE_OBJECT_AS_FILE);
FileOutputStream stream = null;
try {
// No need to check mkdirs result because an IOException will occur anyway
file.getParentFile().mkdirs();
stream = new FileOutputStream(file);
writeObjectToStream(stream, objects);
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Unable to write file: "
+ file.getAbsolutePath(), e);
throw new UnableToCompleteException();
} finally {
Utility.close(stream);
writeObjectAsFileEvent.end();
}
}
/**
* Serializes an object and writes it to a stream.
*/
public static void writeObjectToStream(OutputStream stream, Object... objects)
throws IOException {
ObjectOutputStream objectStream = null;
objectStream = new ObjectOutputStream(stream);
for (Object object : objects) {
objectStream.writeObject(object);
}
objectStream.flush();
}
public static boolean writeStringAsFile(File file, String string) {
FileOutputStream stream = null;
OutputStreamWriter writer = null;
BufferedWriter buffered = null;
try {
// No need to check mkdirs result because an IOException will occur anyway
file.getParentFile().mkdirs();
stream = new FileOutputStream(file);
writer = new OutputStreamWriter(stream, DEFAULT_ENCODING);
buffered = new BufferedWriter(writer);
buffered.write(string);
} catch (IOException e) {
return false;
} finally {
Utility.close(buffered);
Utility.close(writer);
Utility.close(stream);
}
return true;
}
public static void writeStringAsFile(TreeLogger logger, File file,
String string) throws UnableToCompleteException {
FileOutputStream stream = null;
OutputStreamWriter writer = null;
BufferedWriter buffered = null;
try {
stream = new FileOutputStream(file);
writer = new OutputStreamWriter(stream, DEFAULT_ENCODING);
buffered = new BufferedWriter(writer);
// No need to check mkdirs result because an IOException will occur anyway
file.getParentFile().mkdirs();
buffered.write(string);
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Unable to write file: "
+ file.getAbsolutePath(), e);
throw new UnableToCompleteException();
} finally {
Utility.close(buffered);
Utility.close(writer);
Utility.close(stream);
}
}
public static void writeStringToStream(OutputStream stream, String string) throws IOException {
Writer writer = new OutputStreamWriter(stream, DEFAULT_ENCODING);
writer.write(string);
writer.close();
}
/**
* Writes the contents of a StringBuilder to an OutputStream, encoding
* each character using the UTF-* encoding. Unicode characters between
* U+0000 and U+10FFFF are supported.
*/
public static void writeUtf8(StringBuilder builder, OutputStream out)
throws IOException {
// Rolling our own converter avoids the following:
//
// o Instantiating the entire builder as a String
// o Creating CharEncoders and NIO buffer
// o Passing through an OutputStreamWriter
int buflen = 1024;
char[] inBuf = new char[buflen];
byte[] outBuf = new byte[4 * buflen];
int length = builder.length();
int start = 0;
while (start < length) {
int end = Math.min(start + buflen, length);
builder.getChars(start, end, inBuf, 0);
int index = 0;
int len = end - start;
for (int i = 0; i < len; i++) {
int c = inBuf[i] & 0xffff;
if (c < 0x80) {
outBuf[index++] = (byte) c;
} else if (c < 0x800) {
int y = c >> 8;
int x = c & 0xff;
outBuf[index++] = (byte) (0xc0 | (y << 2) | (x >> 6)); // 110yyyxx
outBuf[index++] = (byte) (0x80 | (x & 0x3f)); // 10xxxxxx
} else if (c < 0xD800 || c > 0xDFFF) {
int y = (c >> 8) & 0xff;
int x = c & 0xff;
outBuf[index++] = (byte) (0xe0 | (y >> 4)); // 1110yyyy
outBuf[index++] = (byte) (0x80 | ((y << 2) & 0x3c) | (x >> 6)); // 10yyyyxx
outBuf[index++] = (byte) (0x80 | (x & 0x3f)); // 10xxxxxx
} else {
// Ignore if no second character (which is not be legal unicode)
if (i + 1 < len) {
int hi = c & 0x3ff;
int lo = inBuf[i + 1] & 0x3ff;
int full = 0x10000 + ((hi << 10) | lo);
int z = (full >> 16) & 0xff;
int y = (full >> 8) & 0xff;
int x = full & 0xff;
outBuf[index++] = (byte) (0xf0 | (z >> 5));
outBuf[index++] = (byte) (0x80 | ((z << 4) & 0x30) | (y >> 4));
outBuf[index++] = (byte) (0x80 | ((y << 2) & 0x3c) | (x >> 6));
outBuf[index++] = (byte) (0x80 | (x & 0x3f));
i++; // char has been consumed
}
}
}
out.write(outBuf, 0, index);
start = end;
}
}
// /**
// * Write all of the supplied bytes to the file, in a way that they can be
// read
// * back by {@link #readFileAndSplit(File).
// */
// public static boolean writeStringsAsFile(TreeLogger branch,
// File makePermFilename, String[] js) {
// RandomAccessFile f = null;
// try {
// makePermFilename.delete();
// makePermFilename.getParentFile().mkdirs();
// f = new RandomAccessFile(makePermFilename, "rwd");
// f.writeInt(js.length);
// for (String s : js) {
// byte[] b = getBytes(s);
// f.writeInt(b.length);
// f.write(b);
// }
// return true;
// } catch (IOException e) {
// return false;
// } finally {
// Utility.close(f);
// }
// }
/**
* Reads the specified number of bytes from the {@link InputStream}.
*
* @param byteLength number of bytes to read
* @return byte array containing the bytes read or <code>null</code> if
* there is an {@link IOException} or if the requested number of bytes
* cannot be read from the {@link InputStream}
*/
private static byte[] readBytesFromInputStream(InputStream input,
int byteLength) {
try {
byte[] bytes = new byte[byteLength];
int byteOffset = 0;
while (byteOffset < byteLength) {
int bytesReadCount = input.read(bytes, byteOffset, byteLength
- byteOffset);
if (bytesReadCount == -1) {
return null;
}
byteOffset += bytesReadCount;
}
return bytes;
} catch (IOException e) {
// Ignored.
}
return null;
}
/**
* Creates a string from the bytes using the specified character set name.
*
* @param bytes bytes to convert
* @param charsetName the name of the character set to use
*
* @return String for the given bytes and character set or <code>null</code>
* if the character set is not supported
*/
private static String toString(byte[] bytes, String charsetName) {
try {
return new String(bytes, charsetName);
} catch (UnsupportedEncodingException e) {
// Ignored.
}
return null;
}
private static void writeAttribute(PrintWriter w, Attr attr, int depth)
throws IOException {
w.write(attr.getName());
w.write('=');
Node c = attr.getFirstChild();
while (c != null) {
w.write('"');
writeNode(w, c, depth);
w.write('"');
c = c.getNextSibling();
}
}
private static void writeDocument(PrintWriter w, Document d)
throws IOException {
w.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
Node c = d.getFirstChild();
while (c != null) {
writeNode(w, c, 0);
c = c.getNextSibling();
}
}
private static void writeElement(PrintWriter w, Element el, int depth)
throws IOException {
String tagName = el.getTagName();
writeIndent(w, depth);
w.write('<');
w.write(tagName);
NamedNodeMap attrs = el.getAttributes();
for (int i = 0, n = attrs.getLength(); i < n; ++i) {
w.write(' ');
writeNode(w, attrs.item(i), depth);
}
Node c = el.getFirstChild();
if (c != null) {
// There is at least one child.
//
w.println('>');
// Write the children.
//
while (c != null) {
writeNode(w, c, depth + 1);
w.println();
c = c.getNextSibling();
}
// Write the closing tag.
//
writeIndent(w, depth);
w.write("</");
w.write(tagName);
w.print('>');
} else {
// There are no children, so just write the short form close.
//
w.print("/>");
}
}
private static void writeIndent(PrintWriter w, int depth) {
for (int i = 0; i < depth; ++i) {
w.write('\t');
}
}
private static void writeNode(PrintWriter w, Node node, int depth)
throws IOException {
short nodeType = node.getNodeType();
switch (nodeType) {
case Node.ELEMENT_NODE:
writeElement(w, (Element) node, depth);
break;
case Node.ATTRIBUTE_NODE:
writeAttribute(w, (Attr) node, depth);
break;
case Node.DOCUMENT_NODE:
writeDocument(w, (Document) node);
break;
case Node.TEXT_NODE:
writeText(w, (Text) node);
break;
case Node.COMMENT_NODE:
case Node.CDATA_SECTION_NODE:
case Node.ENTITY_REFERENCE_NODE:
case Node.ENTITY_NODE:
case Node.PROCESSING_INSTRUCTION_NODE:
default:
throw new RuntimeException("Unsupported DOM node type: " + nodeType);
}
}
private static void writeText(PrintWriter w, Text text) throws DOMException {
String nodeValue = text.getNodeValue();
String escaped = escapeXml(nodeValue);
w.write(escaped);
}
/**
* Not instantiable.
*/
private Util() {
}
}