| /* |
| * 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 ' character is quoted as |
| * &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("&"); |
| lastIndex = i + 1; |
| break; |
| case '>': |
| builder.append(c, lastIndex, i - lastIndex); |
| builder.append(">"); |
| lastIndex = i + 1; |
| break; |
| case '<': |
| builder.append(c, lastIndex, i - lastIndex); |
| builder.append("<"); |
| lastIndex = i + 1; |
| break; |
| case '\"': |
| builder.append(c, lastIndex, i - lastIndex); |
| builder.append("""); |
| lastIndex = i + 1; |
| break; |
| case '\'': |
| if (quoteApostrophe) { |
| builder.append(c, lastIndex, i - lastIndex); |
| builder.append("'"); |
| 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() { |
| } |
| |
| } |