0001: /*
0002: * Copyright 2007 Google Inc.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0005: * use this file except in compliance with the License. You may obtain a copy of
0006: * the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0013: * License for the specific language governing permissions and limitations under
0014: * the License.
0015: */
0016: package com.google.gwt.dev.util;
0017:
0018: import com.google.gwt.core.ext.TreeLogger;
0019: import com.google.gwt.core.ext.UnableToCompleteException;
0020: import com.google.gwt.core.ext.typeinfo.TypeOracle;
0021: import com.google.gwt.util.tools.Utility;
0022:
0023: import org.w3c.dom.Attr;
0024: import org.w3c.dom.DOMException;
0025: import org.w3c.dom.Document;
0026: import org.w3c.dom.Element;
0027: import org.w3c.dom.NamedNodeMap;
0028: import org.w3c.dom.Node;
0029: import org.w3c.dom.Text;
0030:
0031: import java.io.BufferedReader;
0032: import java.io.BufferedWriter;
0033: import java.io.File;
0034: import java.io.FileInputStream;
0035: import java.io.FileNotFoundException;
0036: import java.io.FileOutputStream;
0037: import java.io.FilenameFilter;
0038: import java.io.IOException;
0039: import java.io.InputStream;
0040: import java.io.InputStreamReader;
0041: import java.io.OutputStream;
0042: import java.io.OutputStreamWriter;
0043: import java.io.PrintWriter;
0044: import java.io.RandomAccessFile;
0045: import java.io.Reader;
0046: import java.io.StringWriter;
0047: import java.io.UnsupportedEncodingException;
0048: import java.lang.reflect.Array;
0049: import java.lang.reflect.InvocationTargetException;
0050: import java.lang.reflect.Method;
0051: import java.net.MalformedURLException;
0052: import java.net.URL;
0053: import java.net.URLConnection;
0054: import java.net.URLDecoder;
0055: import java.security.MessageDigest;
0056: import java.security.NoSuchAlgorithmException;
0057: import java.util.Collection;
0058: import java.util.Iterator;
0059: import java.util.jar.JarEntry;
0060:
0061: /**
0062: * A smattering of useful methods. Methods in this class are candidates for
0063: * being moved to {@link com.google.gwt.util.tools.Utility} if they would be
0064: * generally useful to tool writers, and don't involve TreeLogger.
0065: */
0066: public final class Util {
0067:
0068: public static String DEFAULT_ENCODING = "UTF-8";
0069:
0070: public static final File[] EMPTY_ARRAY_FILE = new File[0];
0071:
0072: public static final String[] EMPTY_ARRAY_STRING = new String[0];
0073:
0074: public static char[] HEX_CHARS = new char[] { '0', '1', '2', '3',
0075: '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
0076:
0077: public static <T> void addAll(Collection<T> c, T[] a) {
0078: for (T val : a) {
0079: c.add(val);
0080: }
0081: }
0082:
0083: public static byte[] append(byte[] xs, byte x) {
0084: int n = xs.length;
0085: byte[] t = new byte[n + 1];
0086: System.arraycopy(xs, 0, t, 0, n);
0087: t[n] = x;
0088: return t;
0089: }
0090:
0091: public static <T> T[] append(T[] xs, T x) {
0092: int n = xs.length;
0093: T[] t = (T[]) Array.newInstance(xs.getClass()
0094: .getComponentType(), n + 1);
0095: System.arraycopy(xs, 0, t, 0, n);
0096: t[n] = x;
0097: return t;
0098: }
0099:
0100: public static <T> T[] append(T[] appendToThis, T[] these) {
0101: if (appendToThis == null) {
0102: throw new NullPointerException(
0103: "attempt to append to a null array");
0104: }
0105:
0106: if (these == null) {
0107: throw new NullPointerException(
0108: "attempt to append a null array");
0109: }
0110:
0111: T[] result;
0112: int newSize = appendToThis.length + these.length;
0113: Class<?> componentType = appendToThis.getClass()
0114: .getComponentType();
0115: result = (T[]) Array.newInstance(componentType, newSize);
0116: System.arraycopy(appendToThis, 0, result, 0,
0117: appendToThis.length);
0118: System.arraycopy(these, 0, result, appendToThis.length,
0119: these.length);
0120: return result;
0121: }
0122:
0123: /**
0124: * Computes the MD5 hash for the specified byte array.
0125: *
0126: * @return a big fat string encoding of the MD5 for the content, suitably
0127: * formatted for use as a file name
0128: */
0129: public static String computeStrongName(byte[] content) {
0130: MessageDigest md5;
0131: try {
0132: md5 = MessageDigest.getInstance("MD5");
0133: } catch (NoSuchAlgorithmException e) {
0134: throw new RuntimeException("Error initializing MD5", e);
0135: }
0136:
0137: md5.update(content);
0138: return toHexString(md5.digest());
0139: }
0140:
0141: public static boolean copy(TreeLogger logger, File in, File out)
0142: throws UnableToCompleteException {
0143: FileInputStream fis = null;
0144: try {
0145: if (in.lastModified() > out.lastModified()) {
0146: fis = new FileInputStream(in);
0147: copy(logger, fis, out);
0148: return true;
0149: } else {
0150: return false;
0151: }
0152: } catch (FileNotFoundException e) {
0153: logger.log(TreeLogger.ERROR, "Unable to open file '"
0154: + in.getAbsolutePath() + "'", e);
0155: throw new UnableToCompleteException();
0156: } finally {
0157: Utility.close(fis);
0158: }
0159: }
0160:
0161: public static void copy(TreeLogger logger, InputStream is, File out)
0162: throws UnableToCompleteException {
0163: FileOutputStream fos = null;
0164: try {
0165: out.getParentFile().mkdirs();
0166: fos = new FileOutputStream(out);
0167: copy(logger, is, fos);
0168: } catch (FileNotFoundException e) {
0169: logger.log(TreeLogger.ERROR, "Unable to create file '"
0170: + out.getAbsolutePath() + "'", e);
0171: throw new UnableToCompleteException();
0172: } finally {
0173: Utility.close(fos);
0174: }
0175: }
0176:
0177: public static void copy(TreeLogger logger, InputStream is,
0178: OutputStream os) throws UnableToCompleteException {
0179: try {
0180: byte[] buf = new byte[8 * 1024];
0181: int i = 0;
0182: while ((i = is.read(buf)) != -1) {
0183: os.write(buf, 0, i);
0184: }
0185: } catch (IOException e) {
0186: logger.log(TreeLogger.ERROR, "Error during copy", e);
0187: throw new UnableToCompleteException();
0188: }
0189: }
0190:
0191: public static boolean copy(TreeLogger logger, URL in, File out)
0192: throws UnableToCompleteException {
0193: InputStream is = null;
0194: try {
0195: URLConnection conn = in.openConnection();
0196: if (conn.getLastModified() > out.lastModified()) {
0197: is = in.openStream();
0198: copy(logger, is, out);
0199: return true;
0200: } else {
0201: return false;
0202: }
0203: } catch (IOException e) {
0204: logger.log(TreeLogger.ERROR, "Unable to open '"
0205: + in.toExternalForm() + "'", e);
0206: throw new UnableToCompleteException();
0207: } finally {
0208: Utility.close(is);
0209: }
0210: }
0211:
0212: public static Reader createReader(TreeLogger logger, URL url)
0213: throws UnableToCompleteException {
0214: try {
0215: return new InputStreamReader(url.openStream());
0216: } catch (IOException e) {
0217: logger.log(TreeLogger.ERROR, "Unable to open resource: "
0218: + url, e);
0219: throw new UnableToCompleteException();
0220: }
0221: }
0222:
0223: public static void deleteFilesInDirectory(File dir) {
0224: File[] files = dir.listFiles();
0225: if (files != null) {
0226: for (int i = 0; i < files.length; i++) {
0227: File file = files[i];
0228: if (file.isFile()) {
0229: file.delete();
0230: }
0231: }
0232: }
0233: }
0234:
0235: /**
0236: * Deletes all files have the same base name as the specified file.
0237: */
0238: public static void deleteFilesStartingWith(File dir,
0239: final String prefix) {
0240: File[] toDelete = dir.listFiles(new FilenameFilter() {
0241: public boolean accept(File dir, String name) {
0242: return name.startsWith(prefix);
0243: }
0244: });
0245:
0246: if (toDelete != null) {
0247: for (int i = 0; i < toDelete.length; i++) {
0248: toDelete[i].delete();
0249: }
0250: }
0251: }
0252:
0253: /**
0254: * Escapes '&', '<', '>', '"', and '\'' to their XML entity equivalents.
0255: */
0256: public static String escapeXml(String unescaped) {
0257: String escaped = unescaped.replaceAll("\\&", "&");
0258: escaped = escaped.replaceAll("\\<", "<");
0259: escaped = escaped.replaceAll("\\>", ">");
0260: escaped = escaped.replaceAll("\\\"", """);
0261: escaped = escaped.replaceAll("\\'", "'");
0262: return escaped;
0263: }
0264:
0265: /**
0266: * Converts a URL "jar:file:aaa.jar!bbb" to the filename "aaa.jar". This also
0267: * does URLDecoding if needed.
0268: *
0269: * @param location the URL pointing at a jar location
0270: * @return the path to the jar file
0271: */
0272: public static String findFileName(String location) {
0273: String newLocation = location;
0274: if ((location.startsWith("zip:file:"))
0275: || (location.startsWith("jar:file:"))) {
0276: int lastIndexOfExclam = newLocation.lastIndexOf('!');
0277: // This file lives in a jar or zipfile
0278: // we pretend the location is the jar or zip file
0279: if (lastIndexOfExclam != -1) {
0280: newLocation = newLocation.substring(0,
0281: lastIndexOfExclam);
0282: }
0283: newLocation = newLocation.substring(9);
0284: // If the file does not exist that may be because the path is
0285: // URLEncoded. Therefore, decode it.
0286: if (!new File(newLocation).exists()) {
0287: try {
0288: newLocation = URLDecoder.decode(newLocation,
0289: DEFAULT_ENCODING);
0290: } catch (UnsupportedEncodingException e) {
0291: // An unsupported encoding indicates confusion; do nothing.
0292: return location;
0293: }
0294: }
0295: }
0296: return newLocation;
0297: }
0298:
0299: public static URL findSourceInClassPath(ClassLoader cl,
0300: String sourceTypeName) {
0301: String toTry = sourceTypeName.replace('.', '/') + ".java";
0302: URL foundURL = cl.getResource(toTry);
0303: if (foundURL != null) {
0304: return foundURL;
0305: }
0306: int i = sourceTypeName.lastIndexOf('.');
0307: if (i != -1) {
0308: return findSourceInClassPath(cl, sourceTypeName.substring(
0309: 0, i));
0310: } else {
0311: return null;
0312: }
0313: }
0314:
0315: /**
0316: * @param cls A class whose name you want.
0317: * @return The base name for the specified class.
0318: */
0319: public static String getClassName(Class<?> cls) {
0320: return getClassName(cls.getName());
0321: }
0322:
0323: /**
0324: * @param className A fully-qualified class name whose name you want.
0325: * @return The base name for the specified class.
0326: */
0327: public static String getClassName(String className) {
0328: return className.substring(className.lastIndexOf('.') + 1);
0329: }
0330:
0331: /**
0332: * Gets the contents of a file.
0333: *
0334: * @param relativePath relative path within the install directory
0335: * @return the contents of the file, or null if an error occurred
0336: */
0337: public static String getFileFromInstallPath(String relativePath) {
0338: String installPath = Utility.getInstallPath();
0339: File file = new File(installPath + '/' + relativePath);
0340: return readFileAsString(file);
0341: }
0342:
0343: /**
0344: * A 4-digit hex result.
0345: */
0346: public static void hex4(char c, StringBuffer sb) {
0347: sb.append(HEX_CHARS[(c & 0xF000) >> 12]);
0348: sb.append(HEX_CHARS[(c & 0x0F00) >> 8]);
0349: sb.append(HEX_CHARS[(c & 0x00F0) >> 4]);
0350: sb.append(HEX_CHARS[c & 0x000F]);
0351: }
0352:
0353: /**
0354: * This method invokes an inaccessible method in another class.
0355: *
0356: * @param targetClass the class owning the method
0357: * @param methodName the name of the method
0358: * @param argumentTypes the types of the parameters to the method call
0359: * @param target the receiver of the method call
0360: * @param arguments the parameters to the method call
0361: */
0362: public static void invokeInaccessableMethod(Class<?> targetClass,
0363: String methodName, Class<?>[] argumentTypes,
0364: TypeOracle target, Object[] arguments) {
0365: String failedReflectErrMsg = "The definition of "
0366: + targetClass.getName() + "." + methodName
0367: + " has changed in an " + "incompatible way.";
0368: try {
0369: Method m = targetClass.getDeclaredMethod(methodName,
0370: argumentTypes);
0371: m.setAccessible(true);
0372: m.invoke(target, arguments);
0373: } catch (NoSuchMethodException e) {
0374: throw new RuntimeException(failedReflectErrMsg, e);
0375: } catch (IllegalArgumentException e) {
0376: throw new RuntimeException(failedReflectErrMsg, e);
0377: } catch (IllegalAccessException e) {
0378: throw new RuntimeException(failedReflectErrMsg, e);
0379: } catch (InvocationTargetException e) {
0380: throw new RuntimeException(e.getTargetException());
0381: }
0382: }
0383:
0384: public static boolean isCompilationUnitOnDisk(String loc) {
0385: try {
0386: if (new File(loc).exists()) {
0387: return true;
0388: }
0389:
0390: URL url = new URL(loc);
0391: String s = url.toExternalForm();
0392: if (s.startsWith("file:") || s.startsWith("jar:")) {
0393: return true;
0394: }
0395: } catch (MalformedURLException e) {
0396: // Probably not really on disk.
0397: }
0398: return false;
0399: }
0400:
0401: public static boolean isValidJavaIdent(String token) {
0402: if (token.length() == 0) {
0403: return false;
0404: }
0405:
0406: if (!Character.isJavaIdentifierStart(token.charAt(0))) {
0407: return false;
0408: }
0409:
0410: for (int i = 1, n = token.length(); i < n; i++) {
0411: if (!Character.isJavaIdentifierPart(token.charAt(i))) {
0412: return false;
0413: }
0414: }
0415:
0416: return true;
0417: }
0418:
0419: public static void logMissingTypeErrorWithHints(TreeLogger logger,
0420: String missingType) {
0421: logger = logger.branch(TreeLogger.ERROR,
0422: "Unable to find type '" + missingType + "'", null);
0423:
0424: ClassLoader cl = Thread.currentThread().getContextClassLoader();
0425:
0426: URL sourceURL = findSourceInClassPath(cl, missingType);
0427: if (sourceURL != null) {
0428: if (missingType.indexOf(".client.") != -1) {
0429: Messages.HINT_PRIOR_COMPILER_ERRORS.log(logger, null);
0430: Messages.HINT_CHECK_MODULE_INHERITANCE
0431: .log(logger, null);
0432: } else {
0433: // Give the best possible hint here.
0434: //
0435: if (findSourceInClassPath(cl, missingType) == null) {
0436: Messages.HINT_CHECK_MODULE_NONCLIENT_SOURCE_DECL
0437: .log(logger, null);
0438: } else {
0439: Messages.HINT_PRIOR_COMPILER_ERRORS.log(logger,
0440: null);
0441: }
0442: }
0443: } else if (!missingType.equals("java.lang.Object")) {
0444: Messages.HINT_CHECK_TYPENAME.log(logger, missingType, null);
0445: Messages.HINT_CHECK_CLASSPATH_SOURCE_ENTRIES.log(logger,
0446: null);
0447: }
0448:
0449: // For Object in particular, there's a special warning.
0450: //
0451: if (missingType.indexOf("java.lang.") == 0) {
0452: Messages.HINT_CHECK_INHERIT_CORE.log(logger, null);
0453: } else if (missingType.indexOf("com.google.gwt.core.") == 0) {
0454: Messages.HINT_CHECK_INHERIT_CORE.log(logger, null);
0455: } else if (missingType.indexOf("com.google.gwt.user.") == 0) {
0456: Messages.HINT_CHECK_INHERIT_USER.log(logger, null);
0457: }
0458: }
0459:
0460: // /**
0461: // * Reads the file as an array of strings.
0462: // */
0463: // public static String[] readURLAsStrings(URL url) {
0464: // ArrayList lines = new ArrayList();
0465: // String contents = readURLAsString(url);
0466: // if (contents != null) {
0467: // StringReader sr = new StringReader(contents);
0468: // BufferedReader br = new BufferedReader(sr);
0469: // String line;
0470: // while (null != (line = readNextLine(br)))
0471: // lines.add(line);
0472: // }
0473: // return (String[]) lines.toArray(new String[lines.size()]);
0474: // }
0475:
0476: /**
0477: * Attempts to make a path relative to a particular directory.
0478: *
0479: * @param from the directory from which 'to' should be relative
0480: * @param to an absolute path which will be returned so that it is relative to
0481: * 'from'
0482: * @return the relative path, if possible; null otherwise
0483: */
0484: public static File makeRelativeFile(File from, File to) {
0485:
0486: // Keep ripping off directories from the 'from' path until the 'from' path
0487: // is a prefix of the 'to' path.
0488: //
0489: String toPath = tryMakeCanonical(to).getAbsolutePath();
0490: File currentFrom = tryMakeCanonical(from.isDirectory() ? from
0491: : from.getParentFile());
0492:
0493: int numberOfBackups = 0;
0494: while (currentFrom != null) {
0495: String currentFromPath = currentFrom.getPath();
0496: if (toPath.startsWith(currentFromPath)) {
0497: // Found a prefix!
0498: //
0499: break;
0500: } else {
0501: ++numberOfBackups;
0502: currentFrom = currentFrom.getParentFile();
0503: }
0504: }
0505:
0506: if (currentFrom == null) {
0507: // Cannot make it relative.
0508: //
0509: return null;
0510: }
0511:
0512: // Find everything to the right of the common prefix.
0513: //
0514: String trailingToPath = toPath.substring(currentFrom
0515: .getAbsolutePath().length());
0516: if (currentFrom.getParentFile() != null
0517: && trailingToPath.length() > 0) {
0518: trailingToPath = trailingToPath.substring(1);
0519: }
0520:
0521: File relativeFile = new File(trailingToPath);
0522: for (int i = 0; i < numberOfBackups; ++i) {
0523: relativeFile = new File("..", relativeFile.getPath());
0524: }
0525:
0526: return relativeFile;
0527: }
0528:
0529: public static String makeRelativePath(File from, File to) {
0530: File f = makeRelativeFile(from, to);
0531: return (f != null ? f.getPath() : null);
0532: }
0533:
0534: public static String makeRelativePath(File from, String to) {
0535: File f = makeRelativeFile(from, new File(to));
0536: return (f != null ? f.getPath() : null);
0537: }
0538:
0539: /**
0540: * Give the developer a chance to see the in-memory source that failed.
0541: */
0542: public static String maybeDumpSource(TreeLogger logger,
0543: String location, char[] source, String optionalTypeName) {
0544:
0545: if (isCompilationUnitOnDisk(location)) {
0546: // Don't write another copy.
0547: //
0548: return null;
0549: }
0550:
0551: File tmpSrc;
0552: Throwable caught = null;
0553: try {
0554: String prefix = "gen";
0555: if (optionalTypeName != null) {
0556: prefix = optionalTypeName;
0557: }
0558: tmpSrc = File.createTempFile(prefix, ".java");
0559: writeCharsAsFile(logger, tmpSrc, source);
0560: String dumpPath = tmpSrc.getAbsolutePath();
0561: logger.log(TreeLogger.ERROR, "Compilation problem due to '"
0562: + location + "'; see snapshot " + dumpPath, null);
0563: return dumpPath;
0564: } catch (IOException e) {
0565: caught = e;
0566: } catch (UnableToCompleteException e) {
0567: caught = e;
0568: }
0569: logger.log(TreeLogger.ERROR,
0570: "Compilation problem due to in-memory source '"
0571: + location + "', but unable to dump to disk",
0572: caught);
0573: return null;
0574: }
0575:
0576: public static byte[] readFileAsBytes(File file) {
0577: FileInputStream fileInputStream = null;
0578: try {
0579: fileInputStream = new FileInputStream(file);
0580: int length = (int) file.length();
0581: byte[] data = new byte[length];
0582: fileInputStream.read(data);
0583: return data;
0584: } catch (IOException e) {
0585: return null;
0586: } finally {
0587: Utility.close(fileInputStream);
0588: }
0589: }
0590:
0591: public static char[] readFileAsChars(File file) {
0592: if (!file.exists()) {
0593: return null;
0594: }
0595: Reader fileReader = null;
0596: try {
0597: fileReader = new InputStreamReader(
0598: new FileInputStream(file), DEFAULT_ENCODING);
0599: int length = (int) file.length();
0600: if (length < 0) {
0601: return null;
0602: }
0603: char[] fileContents = new char[length];
0604: int charsRead = fileReader.read(fileContents);
0605: if (charsRead < fileContents.length) {
0606: /*
0607: * Calling functions expect returned char[] to be fully populated. This
0608: * happens because some UTF-8 chars take more than one byte to
0609: * represent.
0610: */
0611: char[] trimmed = new char[charsRead];
0612: System
0613: .arraycopy(fileContents, 0, trimmed, 0,
0614: charsRead);
0615: fileContents = trimmed;
0616: }
0617: return fileContents;
0618: } catch (IOException e) {
0619: return null;
0620: } finally {
0621: Utility.close(fileReader);
0622: }
0623: }
0624:
0625: public static String readFileAsString(File file) {
0626: try {
0627: URL toURL = file.toURI().toURL();
0628: char[] buf = readURLAsChars(toURL);
0629: if (buf == null) {
0630: return null;
0631: }
0632: return String.valueOf(buf);
0633: } catch (MalformedURLException e) {
0634: return null;
0635: }
0636: }
0637:
0638: /**
0639: * Reads the next non-empty line.
0640: *
0641: * @return a non-empty string that has been trimmed or null if the reader is
0642: * exhausted
0643: */
0644: public static String readNextLine(BufferedReader br) {
0645: try {
0646: String line = br.readLine();
0647: while (line != null) {
0648: line = line.trim();
0649: if (line.length() > 0) {
0650: break;
0651: }
0652: line = br.readLine();
0653: }
0654: return line;
0655: } catch (IOException e) {
0656: return null;
0657: }
0658: }
0659:
0660: /**
0661: * @return null if the file could not be read
0662: */
0663: public static byte[] readURLAsBytes(URL url) {
0664: // ENH: add a weak cache that has an additional check against the file date
0665: InputStream input = null;
0666: try {
0667: URLConnection connection = url.openConnection();
0668: connection.setUseCaches(false);
0669: input = connection.getInputStream();
0670: int contentLength = connection.getContentLength();
0671: if (contentLength < 0) {
0672: return null;
0673: }
0674: byte[] data = new byte[contentLength];
0675: input.read(data);
0676: return data;
0677: } catch (IOException e) {
0678: return null;
0679: } finally {
0680: Utility.close(input);
0681: }
0682: }
0683:
0684: /**
0685: * @return null if the file could not be read
0686: */
0687: public static char[] readURLAsChars(URL url) {
0688: // ENH: add a weak cache that has an additional check against the file date
0689: InputStreamReader reader = null;
0690: try {
0691: URLConnection connection = url.openConnection();
0692: connection.setUseCaches(false);
0693: reader = new InputStreamReader(connection.getInputStream(),
0694: DEFAULT_ENCODING);
0695: int contentLength = connection.getContentLength();
0696: if (contentLength < 0) {
0697: return null;
0698: }
0699: char[] fileContents = new char[contentLength];
0700: int charsRead = reader.read(fileContents);
0701: if (charsRead < fileContents.length) {
0702: /*
0703: * Calling functions expect returned char[] to be fully populated. This
0704: * happens because some UTF-8 chars take more than one byte to
0705: * represent.
0706: */
0707: char[] trimmed = new char[charsRead];
0708: System
0709: .arraycopy(fileContents, 0, trimmed, 0,
0710: charsRead);
0711: fileContents = trimmed;
0712: }
0713: return fileContents;
0714: } catch (IOException e) {
0715: return null;
0716: } finally {
0717: Utility.close(reader);
0718: }
0719: }
0720:
0721: /**
0722: * Deletes a file or recursively deletes a directory.
0723: *
0724: * @param file the file to delete, or if this is a directory, the directory
0725: * that serves as the root of a recursive deletion
0726: * @param childrenOnly if <code>true</code>, only the children of a
0727: * directory are recursively deleted but the specified directory
0728: * itself is spared; if <code>false</code>, the specified
0729: * directory is also deleted; ignored if <code>file</code> is not a
0730: * directory
0731: */
0732: public static void recursiveDelete(File file, boolean childrenOnly) {
0733: if (file.isDirectory()) {
0734: File[] children = file.listFiles();
0735: if (children != null) {
0736: for (int i = 0; i < children.length; i++) {
0737: recursiveDelete(children[i], false);
0738: }
0739: }
0740: if (childrenOnly) {
0741: // Do not delete the specified directory itself.
0742: //
0743: return;
0744: }
0745: }
0746: file.delete();
0747: }
0748:
0749: public static File removeExtension(File file) {
0750: String name = file.getName();
0751: int lastDot = name.lastIndexOf('.');
0752: if (lastDot != -1) {
0753: name = name.substring(0, lastDot);
0754: }
0755: return new File(file.getParentFile(), name);
0756: }
0757:
0758: public static <T> T[] removeNulls(T[] a) {
0759: int n = a.length;
0760: for (int i = 0; i < a.length; i++) {
0761: if (a[i] == null) {
0762: --n;
0763: }
0764: }
0765:
0766: Class<?> componentType = a.getClass().getComponentType();
0767: T[] t = (T[]) Array.newInstance(componentType, n);
0768: int out = 0;
0769: for (int in = 0; in < t.length; in++) {
0770: if (a[in] != null) {
0771: t[out++] = a[in];
0772: }
0773: }
0774: return t;
0775: }
0776:
0777: /**
0778: * @param path The path to slashify.
0779: * @return The path with any directory separators replaced with '/'.
0780: */
0781: public static String slashify(String path) {
0782: path = path.replace(File.separatorChar, '/');
0783: if (path.endsWith("/")) {
0784: path = path.substring(0, path.length() - 1);
0785: }
0786: return path;
0787: }
0788:
0789: /**
0790: * Creates an array from a collection of the specified component type and
0791: * size. You can definitely downcast the result to T[] if T is the specified
0792: * component type.
0793: *
0794: * Class<? super T> is used to allow creation of generic types, such as
0795: * Map.Entry<K,V> since we can only pass in Map.Entry.class.
0796: */
0797: public static <T> T[] toArray(Class<? super T> componentType,
0798: Collection<? extends T> coll) {
0799: int n = coll.size();
0800: T[] a = (T[]) Array.newInstance(componentType, n);
0801: return coll.toArray(a);
0802: }
0803:
0804: /**
0805: * Like {@link #toArray(Class, Collection)}, but the option of having the
0806: * array reversed.
0807: */
0808: public static <T> T[] toArrayReversed(
0809: Class<? super T> componentType, Collection<? extends T> coll) {
0810: int n = coll.size();
0811: T[] a = (T[]) Array.newInstance(componentType, n);
0812: int i = n - 1;
0813: for (Iterator<? extends T> iter = coll.iterator(); iter
0814: .hasNext(); --i) {
0815: a[i] = iter.next();
0816: }
0817: return a;
0818: }
0819:
0820: /**
0821: * Returns a string representation of the byte array as a series of
0822: * hexadecimal characters.
0823: *
0824: * @param bytes byte array to convert
0825: * @return a string representation of the byte array as a series of
0826: * hexadecimal characters
0827: */
0828: public static String toHexString(byte[] bytes) {
0829: char[] hexString = new char[2 * bytes.length];
0830: int j = 0;
0831: for (int i = 0; i < bytes.length; i++) {
0832: hexString[j++] = Util.HEX_CHARS[(bytes[i] & 0xF0) >> 4];
0833: hexString[j++] = Util.HEX_CHARS[bytes[i] & 0x0F];
0834: }
0835:
0836: return new String(hexString);
0837: }
0838:
0839: /**
0840: * Creates a string array from the contents of a collection.
0841: */
0842: public static String[] toStringArray(Collection<String> coll) {
0843: return toArray(String.class, coll);
0844: }
0845:
0846: public static URL toURL(File f) {
0847: try {
0848: return f.toURI().toURL();
0849: } catch (MalformedURLException e) {
0850: throw new RuntimeException(
0851: "Failed to convert a File to a URL", e);
0852: }
0853: }
0854:
0855: public static URL toURL(URL jarUrl, JarEntry jarEntry) {
0856: return toURL(jarUrl, jarEntry.toString());
0857: }
0858:
0859: public static URL toURL(URL jarUrl, String path) {
0860: try {
0861: return new URL("jar" + ":" + jarUrl.toString() + "!/"
0862: + path);
0863: } catch (MalformedURLException e) {
0864: throw new RuntimeException(
0865: "Failed to convert a jar path to a URL", e);
0866: }
0867: }
0868:
0869: public static String toXml(Document doc) {
0870: Throwable caught = null;
0871: try {
0872: byte[] bytes = toXmlUtf8(doc);
0873: return new String(bytes, DEFAULT_ENCODING);
0874: } catch (UnsupportedEncodingException e) {
0875: caught = e;
0876: }
0877: throw new RuntimeException(
0878: "Unable to encode xml string as utf-8", caught);
0879: }
0880:
0881: public static byte[] toXmlUtf8(Document doc) {
0882: Throwable caught = null;
0883: try {
0884: StringWriter sw = new StringWriter();
0885: PrintWriter pw = new PrintWriter(sw);
0886: writeDocument(pw, doc);
0887: return sw.toString().getBytes(DEFAULT_ENCODING);
0888: } catch (UnsupportedEncodingException e) {
0889: caught = e;
0890: } catch (IOException e) {
0891: caught = e;
0892: }
0893: throw new RuntimeException(
0894: "Unable to encode xml document object as a string",
0895: caught);
0896:
0897: // THE COMMENTED-OUT CODE BELOW IS THE WAY I'D LIKE TO GENERATE XML,
0898: // BUT IT SEEMS TO BLOW UP WHEN YOU CHANGE JRE VERSIONS AND/OR RUN
0899: // IN TOMCAT. INSTEAD, I JUST SLAPPED TOGETHER THE MINIMAL STUFF WE
0900: // NEEDED TO WRITE CACHE ENTRIES.
0901:
0902: // Throwable caught = null;
0903: // try {
0904: // TransformerFactory transformerFactory = TransformerFactory.newInstance();
0905: // Transformer transformer = transformerFactory.newTransformer();
0906: // transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT,
0907: // "yes");
0908: // transformer.setOutputProperty(
0909: // "{http://xml.apache.org/xslt}indent-amount", "4");
0910: // ByteArrayOutputStream baos = new ByteArrayOutputStream();
0911: // OutputStreamWriter osw = new OutputStreamWriter(baos, "UTF-8");
0912: // StreamResult result = new StreamResult(osw);
0913: // DOMSource domSource = new DOMSource(doc);
0914: // transformer.transform(domSource, result);
0915: // byte[] bytes = baos.toByteArray();
0916: // return bytes;
0917: // } catch (TransformerConfigurationException e) {
0918: // caught = e;
0919: // } catch (UnsupportedEncodingException e) {
0920: // caught = e;
0921: // } catch (TransformerException e) {
0922: // caught = e;
0923: // }
0924: // throw new RuntimeException(
0925: // "Unable to encode xml document object as a string", caught);
0926: }
0927:
0928: public static File tryCombine(File parentMaybeIgnored,
0929: File childMaybeAbsolute) {
0930: if (childMaybeAbsolute == null) {
0931: return parentMaybeIgnored;
0932: } else if (childMaybeAbsolute.isAbsolute()) {
0933: return childMaybeAbsolute;
0934: } else {
0935: return new File(parentMaybeIgnored, childMaybeAbsolute
0936: .getPath());
0937: }
0938: }
0939:
0940: public static File tryCombine(File parentMaybeIgnored,
0941: String childMaybeAbsolute) {
0942: return tryCombine(parentMaybeIgnored, new File(
0943: childMaybeAbsolute));
0944: }
0945:
0946: /**
0947: * Attempts to find the canonical form of a file path.
0948: *
0949: * @return the canonical version of the file path, if it could be computed;
0950: * otherwise, the original file is returned unmodified
0951: */
0952: public static File tryMakeCanonical(File file) {
0953: try {
0954: return file.getCanonicalFile();
0955: } catch (IOException e) {
0956: return file;
0957: }
0958: }
0959:
0960: public static void writeBytesToFile(TreeLogger logger, File where,
0961: byte[] what) throws UnableToCompleteException {
0962: writeBytesToFile(logger, where, new byte[][] { what });
0963: }
0964:
0965: /**
0966: * Gathering write.
0967: */
0968: public static void writeBytesToFile(TreeLogger logger, File where,
0969: byte[][] what) throws UnableToCompleteException {
0970: RandomAccessFile f = null;
0971: Throwable caught;
0972: try {
0973: where.getParentFile().mkdirs();
0974: f = new RandomAccessFile(where, "rwd");
0975: long newLen = 0;
0976: for (int i = 0; i < what.length; i++) {
0977: newLen += what[i].length;
0978: f.write(what[i]);
0979: }
0980: f.setLength(newLen);
0981: return;
0982: } catch (FileNotFoundException e) {
0983: caught = e;
0984: } catch (IOException e) {
0985: caught = e;
0986: } finally {
0987: Utility.close(f);
0988: }
0989: String msg = "Unable to write file '" + where + "'";
0990: logger.log(TreeLogger.ERROR, msg, caught);
0991: throw new UnableToCompleteException();
0992: }
0993:
0994: public static void writeCharsAsFile(TreeLogger logger, File file,
0995: char[] chars) throws UnableToCompleteException {
0996: FileOutputStream stream = null;
0997: OutputStreamWriter writer = null;
0998: BufferedWriter buffered = null;
0999: try {
1000: file.getParentFile().mkdirs();
1001: stream = new FileOutputStream(file);
1002: writer = new OutputStreamWriter(stream, DEFAULT_ENCODING);
1003: buffered = new BufferedWriter(writer);
1004: buffered.write(chars);
1005: } catch (IOException e) {
1006: logger.log(TreeLogger.ERROR, "Unable to write file: "
1007: + file.getAbsolutePath(), e);
1008: throw new UnableToCompleteException();
1009: } finally {
1010: Utility.close(buffered);
1011: Utility.close(writer);
1012: Utility.close(stream);
1013: }
1014: }
1015:
1016: public static boolean writeStringAsFile(File file, String string) {
1017: FileOutputStream stream = null;
1018: OutputStreamWriter writer = null;
1019: BufferedWriter buffered = null;
1020: try {
1021: stream = new FileOutputStream(file);
1022: writer = new OutputStreamWriter(stream, DEFAULT_ENCODING);
1023: buffered = new BufferedWriter(writer);
1024: file.getParentFile().mkdirs();
1025: buffered.write(string);
1026: } catch (IOException e) {
1027: return false;
1028: } finally {
1029: Utility.close(buffered);
1030: Utility.close(writer);
1031: Utility.close(stream);
1032: }
1033: return true;
1034: }
1035:
1036: private static void writeAttribute(PrintWriter w, Attr attr,
1037: int depth) throws IOException {
1038: w.write(attr.getName());
1039: w.write('=');
1040: Node c = attr.getFirstChild();
1041: while (c != null) {
1042: w.write('"');
1043: writeNode(w, c, depth);
1044: w.write('"');
1045: c = c.getNextSibling();
1046: }
1047: }
1048:
1049: private static void writeDocument(PrintWriter w, Document d)
1050: throws IOException {
1051: w.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
1052: Node c = d.getFirstChild();
1053: while (c != null) {
1054: writeNode(w, c, 0);
1055: c = c.getNextSibling();
1056: }
1057: }
1058:
1059: private static void writeElement(PrintWriter w, Element el,
1060: int depth) throws IOException {
1061: String tagName = el.getTagName();
1062:
1063: writeIndent(w, depth);
1064: w.write('<');
1065: w.write(tagName);
1066: NamedNodeMap attrs = el.getAttributes();
1067: for (int i = 0, n = attrs.getLength(); i < n; ++i) {
1068: w.write(' ');
1069: writeNode(w, attrs.item(i), depth);
1070: }
1071:
1072: Node c = el.getFirstChild();
1073: if (c != null) {
1074: // There is at least one child.
1075: //
1076: w.println('>');
1077:
1078: // Write the children.
1079: //
1080: while (c != null) {
1081: writeNode(w, c, depth + 1);
1082: w.println();
1083: c = c.getNextSibling();
1084: }
1085:
1086: // Write the closing tag.
1087: //
1088: writeIndent(w, depth);
1089: w.write("</");
1090: w.write(tagName);
1091: w.print('>');
1092: } else {
1093: // There are no children, so just write the short form close.
1094: //
1095: w.print("/>");
1096: }
1097: }
1098:
1099: private static void writeIndent(PrintWriter w, int depth) {
1100: for (int i = 0; i < depth; ++i) {
1101: w.write('\t');
1102: }
1103: }
1104:
1105: private static void writeNode(PrintWriter w, Node node, int depth)
1106: throws IOException {
1107: short nodeType = node.getNodeType();
1108: switch (nodeType) {
1109: case Node.ELEMENT_NODE:
1110: writeElement(w, (Element) node, depth);
1111: break;
1112: case Node.ATTRIBUTE_NODE:
1113: writeAttribute(w, (Attr) node, depth);
1114: break;
1115: case Node.DOCUMENT_NODE:
1116: writeDocument(w, (Document) node);
1117: break;
1118: case Node.TEXT_NODE:
1119: writeText(w, (Text) node);
1120: break;
1121:
1122: case Node.COMMENT_NODE:
1123: case Node.CDATA_SECTION_NODE:
1124: case Node.ENTITY_REFERENCE_NODE:
1125: case Node.ENTITY_NODE:
1126: case Node.PROCESSING_INSTRUCTION_NODE:
1127: default:
1128: throw new RuntimeException("Unsupported DOM node type: "
1129: + nodeType);
1130: }
1131: }
1132:
1133: private static void writeText(PrintWriter w, Text text)
1134: throws DOMException {
1135: String nodeValue = text.getNodeValue();
1136: String escaped = escapeXml(nodeValue);
1137: w.write(escaped);
1138: }
1139:
1140: /**
1141: * Not instantiable.
1142: */
1143: private Util() {
1144: }
1145:
1146: }
|