/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as * published by the Free Software Foundation, and/or the GNU Lesser * General Public License, version 2.1, also as published by the Free * Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License and the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.rhq.core.util.file; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Deque; import java.util.LinkedList; import java.util.List; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import org.rhq.core.util.collection.IntHashMap; import org.rhq.core.util.stream.StreamUtil; public class FileUtil { private static IntHashMap invalidChars = null; /** * This will check to see if file1 is newer than file2. If file1's last modified date * is <strong>after</strong> file2's last modified date, then <code>true</code> is returned. * <code>false</code> is returned if file1's date is the same or older than file2's date. * <p> * <code>null</code> is returned if any of these conditions are true: * <ul> * <li>If either file is null</li> * <li>If either file does not exist</li> * <li>If either file is not a normal file (but, say, a directory)</li> * </ul> * </p> * * @param file1 * @param file2 * @return indication if file1 is newer than file2 */ public static Boolean isNewer(File file1, File file2) { if (file1 == null || file2 == null) { return null; } if (!file1.isFile() || !file2.isFile()) { return null; } long file1Date = file1.lastModified(); long file2Date = file2.lastModified(); return file1Date > file2Date; } /** * Creates a temporary directory using the same algorithm as JDK's File.createTempFile. */ public static File createTempDirectory(String prefix, String suffix, File parentDirectory) throws IOException { // Let's reuse the algorithm the JDK uses to determine a unique name: // 1) create a temp file to get a unique name using JDK createTempFile // 2) then quickly delete the file and... // 3) convert it to a directory File tmpDir = File.createTempFile(prefix, suffix, parentDirectory); // create file with unique name boolean deleteOk = tmpDir.delete(); // delete the tmp file and... boolean mkdirsOk = tmpDir.mkdirs(); // ...convert it to a directory if (!deleteOk || !mkdirsOk) { throw new IOException("Failed to create temp directory named [" + tmpDir + "]"); } return tmpDir; } /** * Given a directory, this will recursively purge all child directories and files. * If dir is actually a normal file, it will be deleted but only if deleteIt is true. * * If deleteIt is true, the directory itself will be deleted, otherwise it will remain (albeit empty). * * @param dir the directory to purge (if <code>null</code>, this method does nothing and returns normally) * @param deleteIt if <code>true</code> delete the directory itself, otherwise leave it but purge its children */ public static void purge(File dir, boolean deleteIt) { if (dir != null) { if (dir.isDirectory()) { File[] doomedFiles = dir.listFiles(); if (doomedFiles != null) { for (File doomedFile : doomedFiles) { purge(doomedFile, true); // call this method recursively } } } if (deleteIt) { dir.delete(); } } return; } /** * Copy a file from one file to another */ public static void copyFile(File inFile, File outFile) throws FileNotFoundException, IOException { BufferedInputStream is = new BufferedInputStream(new FileInputStream(inFile)); BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(outFile)); StreamUtil.copy(is, os); // TODO do we care to restore execute bit? For coping large number of files via copyDirectory, will this make things slow? // if (inFile.canExecute()) { // outFile.setExecutable(true); // } // TODO do we care to restore the last mod time on the destination file? //outFile.setLastModified(inFile.lastModified()); } public static void copyDirectory(File inDir, File outDir) throws IOException { if (inDir.exists()) { if (!inDir.isDirectory()) { throw new IOException("Source directory [" + inDir + "] is not a directory"); } } else { throw new FileNotFoundException("Source directory [" + inDir + "] does not exist"); } if (!outDir.mkdirs()) { throw new IOException("Destination directory [" + outDir + "] failed to be created"); } if (!canWrite(outDir)) { throw new IOException("Cannot write to destination directory [" + outDir + "]"); } // TODO do we care to restore the last mod time on the destination dir? //outDir.setLastModified(inDir.lastModified()); File[] files = inDir.listFiles(); if (files == null) { throw new IOException("Failed to get the list of files in source directory [" + inDir + "]"); } for (File file : files) { File copiedFile = new File(outDir, file.getName()); if (file.isDirectory()) { copyDirectory(file, copiedFile); } else { copyFile(file, copiedFile); } } files = null; // help GC return; } /** * Obtains the list of all files in the given directory and, recursively, all its subdirectories. * Note that the returns list is only regular files - directory names are NOT in the list. Also, * the names in the list are relative to the given directory. * @param directory the directory whose files are to be returned * @return list of files in the directory, not sorted in any particular order * @throws IOException if directory does not exist or is not a directory */ public static List<File> getDirectoryFiles(File directory) throws IOException { ArrayList<File> files = new ArrayList<File>(); if (!directory.isDirectory()) { throw new IOException("[" + directory + "] is not an existing directory"); } getDirectoryFilesRecursive(directory, files, null); return files; } private static void getDirectoryFilesRecursive(File directory, List<File> files, String relativeTo) throws IOException { File[] children = directory.listFiles(); if (children == null) { throw new IOException("Cannot obtain files from directory [" + directory + "]"); } for (File child : children) { if (child.isDirectory()) { getDirectoryFilesRecursive(child, files, ((relativeTo == null) ? "" : relativeTo) + child.getName() + File.separatorChar); } else { files.add(new File(relativeTo, child.getName())); } } return; } /** * Copy a stream, using a buffer. * @deprecated use {@link StreamUtil} for more methods like this - those are unit tested and used more */ @Deprecated public static void copyStream(InputStream is, OutputStream os) throws IOException { StreamUtil.copy(is, os, false); } /** * Copy a stream, using a buffer. * @deprecated use {@link StreamUtil} for more methods like this - those are unit tested and used more */ @Deprecated public static void copyStream(InputStream is, OutputStream os, byte[] buf) throws IOException { int bytesRead = 0; while (true) { bytesRead = is.read(buf); if (bytesRead == -1) { break; } os.write(buf, 0, bytesRead); } } /** * Writes the content in the input stream to the specified file. * NOTE: inputStream will be closed by this. * * @param inputStream stream containing the content to write * @param outputFile file to which the content will be written * * @throws IOException if any errors occur during the reading or writing */ public static void writeFile(InputStream inputStream, File outputFile) throws IOException { FileOutputStream fos = new FileOutputStream(outputFile); copyStream(inputStream, fos); inputStream.close(); fos.close(); } public static String findString(String fname, String toFind) throws IOException { StringBuffer result = null; BufferedReader in = new BufferedReader(new FileReader(fname)); try { char[] data = new char[8096]; int numread; int toFindIndex = 0; /* Just need to initialize this, because the compiler doesn't * realize that it can't be used before it is assigned a value */ char lastchar = 'a'; while ((numread = in.read(data, 0, 8096)) != -1) { for (int i = 0; i < numread; i++) { /* If we have found the string already or if we our current * character matches the current char in the target string then we just add the current character to * our result string and move on. */ if ((toFindIndex >= toFind.length()) || (data[i] == toFind.charAt(toFindIndex))) { if (result == null) { result = new StringBuffer(); } if (Character.isISOControl(data[i])) { return result.toString(); } result.append(data[i]); toFindIndex++; } else { /* Otherwise things can get complex. If we haven't * started to match, then just keep going. If we have started to match, then we need to move * backwards to make sure we don't miss a match. For example: looking for HI in HHI. If the * current character isn't the same as the last character, then we aren't going to match, so * null everything out and keep going. Otherwise, decrment everything by one, because we didn't * match the first character, and go through the loop on this character again. */ if (toFindIndex > 0) { if (data[i] != lastchar) { result = null; toFindIndex = 0; continue; } toFindIndex--; i--; result.deleteCharAt(result.length() - 1); continue; } } lastchar = data[i]; } } } catch (IOException e) { } finally { if (in != null) { try { in.close(); } catch (IOException e) { } } } if (result != null) { return result.toString(); } return null; } /** * The base attribute specifies what the directory base the relative path should be considered relative to. The base * must be part of the absolute path specified by the path attribute. */ public static String getRelativePath(File path, File base) { String path_abs = path.getAbsolutePath(); String base_abs = base.getAbsolutePath(); int idx = path_abs.indexOf(base_abs); if (idx == -1) { throw new IllegalArgumentException("Path (" + path_abs + ") " + "does not contain " + "base (" + base_abs + ")"); } String relativePath = "." + path_abs.substring(idx + base_abs.length()); return relativePath; } private static void initInvalidChars() { if (invalidChars != null) { return; } invalidChars = new IntHashMap(); char[] invalid = { '\\', '/', ':', '*', '?', '\'', '"', '~', '<', '>', '|', '#', '{', '}', '%', '&', ' ' }; for (int i = 0; i < invalid.length; i++) { invalidChars.put(invalid[i], Boolean.TRUE); } } /** * Escape invalid characters in a filename, replacing with "_" */ public static String escape(String name) { initInvalidChars(); int len = name.length(); StringBuffer buf = new StringBuffer(len); char[] chars = name.toCharArray(); for (int i = 0; i < len; i++) { char c = chars[i]; if (invalidChars.get(c) == Boolean.TRUE) { buf.append("_"); } else { buf.append(c); } } return buf.toString(); } /** * Test if a directory is writable java.io.File#canWrite() has problems on windows for properly detecting if a * directory is writable by the current user. For example, C:\Program Files is set to read-only, however the * Administrator user is able to write to that directory * * @throws IOException If the File is not a directory */ public static boolean canWrite(File dir) throws IOException { if (!dir.isDirectory()) { throw new IOException(dir.getPath() + " is not a directory"); } File tmp = null; try { tmp = File.createTempFile("rhq", null, dir); return true; } catch (IOException e) { return false; } finally { if (tmp != null) { tmp.delete(); } } } /** * Strips the drive letter from the given Windows path. The drive letter is returned * or <code>null</code> is returned if there was no drive letter in the path. * * @param path the path string that will be altered to have its drive letter stripped. * @return if there was a drive letter, it will be returned and normalized to upcase. If no drive letter * was in path, null is returned */ public static String stripDriveLetter(StringBuilder path) { String driveLetter = null; Pattern regex = Pattern.compile("^([a-zA-Z]):.*"); Matcher matcher = regex.matcher(path); if (matcher.matches()) { driveLetter = matcher.group(1).toUpperCase(); path.replace(0, 2, ""); // we know the pattern is one char drive letter plus one ':' char followed by the path } return driveLetter; } /** * Ensure that the path uses only forward slash. * @param path * @return forward-slashed path, or null if path is null */ public static String useForwardSlash(String path) { return (null != path) ? path.replace('\\', '/') : null; } /** * Ensure that the path uses only forward slash. * @param path * @return backward-slashed path, or null if path is null */ public static String useBackwardSlash(String path) { return (null != path) ? path.replace('/', '\\') : null; } /** * Ensure that the path uses only the proper file separator for the OS. * @param path * @return appropriate forward- or back-slashed path, or null if path is null */ public static String useNativeSlash(String path) { return ('/' == File.separatorChar) ? useForwardSlash(path) : useBackwardSlash(path); } /** * Return just the filename portion (the portion right of the last path separator string) * @param path * @param separator * @return null if path is null, otherwise the trimmed filename */ public static String getFileName(String path, String separator) { if (null == path) { return null; } int i = path.lastIndexOf(separator); return (i < 0) ? path.trim() : path.substring(++i).trim(); } /** * Performs a breadth-first scan, calling <code>visitor</code> for each file in * <code>directory</code>. Sub directories are scanned as well. Note that if * <code>visitor</code> throws a RuntimeException it will not be called again as this * method does not provide any exception handling. * * @param directory The directory over which to iterate * @param visitor The callback to invoke for each file */ public static void forEachFile(File directory, FileVisitor visitor) { Deque<File> directories = new LinkedList<File>(); directories.push(directory); while (!directories.isEmpty()) { File dir = directories.pop(); File[] files = dir.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { directories.push(file); } else { visitor.visit(file); } } } } } /** * Takes a list of filters and compiles them into a regular expression that can be used * for matching or filtering paths. The pattern syntax supports regular expressions as * well as the syntax used in Ant's file/path selectors. Here are some example patterns * that can be specified in a filter: * <p/> * <table border="1"> * <tr> * <td>Pattern</td> * <td>Description</td> * </tr> * <tr> * <td>/etc/yum.conf</td> * <td>exact match of the path</td> * </tr> * <tr> * <td>/etc/*.conf</td> * <td>match any file /etc that has a .conf suffix</td> * </tr> * <tr> * <td>deploy/myapp-?.war</td> * <td>Match any file in the deploy directory that starts with myapp- followed any one character and * ending with a suffix of .war</td> * </tr> * <tr> * <td>jboss/server/**/*.war</td> * <td>Matches all .war files under the server directory. Sub directories are included as well such * that jboss/server/default/myapp.war, jboss/server/production/myapp.war and * jboss/server/default/myapp.ear/myapp.war all match</td> * </tr> * </table> * * @param filters Compiled into a regular expression * @return A Pattern object that is a compilation of regular expressions built from * the specified path filters */ public static Pattern generateRegex(List<PathFilter> filters) { boolean first = true; StringBuilder regex = new StringBuilder(); for (PathFilter filter : filters) { if (!first) { regex.append("|"); } else { first = false; } regex.append("("); File pathFile = new File(filter.getPath()); if (isEmpty(filter.getPattern()) && pathFile.isDirectory()) { regex.append(".*"); } else if (isEmpty(filter.getPattern()) && !pathFile.isDirectory()) { buildPatternRegex(pathFile.getAbsolutePath(), regex); } else if (!isEmpty(filter.getPattern())) { // note that this case assumes path is a directory. We probably // need another if else block for when there is a pattern and // path is not a directory. // escape win separators because backslash is a regex character String pathString = pathFile.getAbsolutePath(); if (!pathString.endsWith(File.separator)) { pathString += File.separator; } pathString = pathString.replace("\\", "\\\\"); // escape parens in the path, these are valid dir chars on win and also regex characters pathString = pathString.replace("(", "\\("); pathString = pathString.replace(")", "\\)"); regex.append(pathString).append("("); buildPatternRegex(filter.getPattern(), regex); regex.append(")"); } regex.append(")"); } return Pattern.compile(regex.toString()); } private static void buildPatternRegex(String pattern, StringBuilder regex) { for (int i = 0; i < pattern.length(); i++) { char c = pattern.charAt(i); if (c == '?') { // ? match any character regex.append('.'); } else if (c == '*') { // ? match zero or more characters if (i + 1 < pattern.length()) { char c2 = pattern.charAt(i + 1); if (c2 == '*') { regex.append(".*"); i += 2; continue; } } String separator = File.separator; if ("\\".equals(separator)) { separator = "\\\\"; } regex.append("[^" + separator + "]*"); } else if (c == '.' || c == '(' || c == ')') { // escape file extensions because dot is a regex character // escape parens because they are regex characters regex.append("\\"); regex.append(c); } else if (c == '\\') { // escape windows separators because backslash is a regex character regex.append("\\\\"); } else { regex.append(c); } } } private static boolean isEmpty(String s) { return s == null || s.length() == 0; } /** * Normalizes the path of the file by removing any ".." and "." * <p/> * This method behaves very similar to Java7's {@code Path.normalize()} method with the exception of dealing with * paths jumping "above" the FS root. * <p/> * Java7's normalization will normalize a path like {@code C:\..\asdf} to {@code C:\asdf}, while this method will * return null, because it understands {@code C:\..\asdf} as an attempt to "go above" the file system root. * * @return the file with the normalized path or null if the ".."s would jump further up than the number of preceding * path elements (e.g. passing files with paths like ".." or "path/../.." will return null). On Windows the drive * letter will be upper-cased if present. */ public static File normalizePath(File file) { String path = file.getPath(); File root = null; // make sure driver letter on windows is upcased int rootLength = FileSystem.get().getPathRootLength(path); if (rootLength > 0) { StringBuilder rootPath = new StringBuilder(path.substring(0, rootLength)); String driveLetter = stripDriveLetter(rootPath); root = new File((null == driveLetter) ? rootPath.toString() : (driveLetter + ":" + rootPath.toString())); } StringTokenizer tokenizer = new StringTokenizer(path.substring(rootLength), FileSystem.get() .getSeparatorChars(), true); LinkedList<String> pathStack = new LinkedList<String>(); boolean previousWasDelimiter = false; while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (File.separator.equals(token)) { if (!previousWasDelimiter) { pathStack.push(token); previousWasDelimiter = true; } } else if ("..".equals(token)) { //yes, this is correct - ".." will jump up the stack to the next-previous delimiter, so we should //declare that we're at a delimiter position. previousWasDelimiter = true; if (pathStack.isEmpty()) { return null; } else { //pop the previous delimiter(s) pathStack.pop(); //and pop the previous path element if (pathStack.isEmpty()) { return null; } pathStack.pop(); } } else if (".".equals(token)) { previousWasDelimiter = true; } else if (token.length() > 0) { previousWasDelimiter = false; pathStack.push(token); } else { previousWasDelimiter = false; } } StringBuilder normalizedPath = new StringBuilder(); for (int i = pathStack.size(); --i >= 0;) { normalizedPath.append(pathStack.get(i)); } File ret = (root == null) ? new File(normalizedPath.toString()) : new File(root, normalizedPath.toString()); if (file.isAbsolute() != ret.isAbsolute()) { // if the normalization changed the path such that it is not absolute anymore // (or that it wasn't absolute but now is, which shouldn't ever happen), return null. // The fact that the original file was absolute and the normalized path isn't can be caused by // the normalization "climbing past" the prefix of the absolute path which is the drive letter of Windows // for example. return null; } else { return ret; } } private enum FileSystem { UNIX { @Override public int getPathRootLength(String path) { if (path != null && path.charAt(0) == '/') { return 1; } else { return 0; } } @Override public String getSeparatorChars() { return "/"; } }, WINDOWS { @Override public int getPathRootLength(String path) { if (path == null || path.length() < 3) { return 0; } // C:\asdf // C:asdf // \\host\share\asdf char c0 = path.charAt(0); char c1 = path.charAt(1); char c2 = path.charAt(2); switch (c0) { case '\\': case '/': if (isSlash(c1)) { //UNC int nextSlash = nextSlash(path, 2); if (nextSlash < 3) { throw new IllegalArgumentException("Invalid UNC path - no host specified"); } int hostSlash = nextSlash; nextSlash = nextSlash(path, nextSlash + 1); if (nextSlash <= hostSlash) { throw new IllegalArgumentException("Invalid UNC path - no share specified"); } return nextSlash; } else { return 0; } default: if (c1 == ':') { char driveLetter = Character.toLowerCase(c0); if ('a' <= driveLetter && 'z' >= driveLetter) { return c2 == '\\' ? 3 : 2; } else { return 0; } } else { return 0; } } } @Override public String getSeparatorChars() { return "\\/"; } }; private static boolean isSlash(char c) { return c == '\\' || c == '/'; } private static int nextSlash(String str, int from) { int len = str.length(); for (int i = from; i < len; ++i) { if (isSlash(str.charAt(i))) { return i; } } return -1; } public static FileSystem get() { switch (File.separatorChar) { case '/': return UNIX; case '\\': return WINDOWS; default: throw new IllegalStateException("Unsupported filesystem"); } } public abstract int getPathRootLength(String path); public abstract String getSeparatorChars(); } /** * Under certain conditions, it might be desired to consider a path that is technically a Windows relative path * to be absolute. For example, a relative path declared as "\opt" could be considered absolute if * the current working directory at runtime is C:\working\dir\here and that \opt directory is located on * the same drive (i.e. C:\opt). * * It is preferable that you do use real absolute paths including drive letter, but this method helps * work around some circumstances where that isn't possible, but yet by just implying a drive letter, you * do have an absolute path. * * Note that if the VM is not running on a Windows machine, this method is the same as File.isAbsolute(). * * @param path the path to see if it really can be considered absolute * * @return true if the path can be considered absolute if the current working directory drive letter is implied. */ public static boolean isAbsolutePath(String path) { File filepath = new File(path); if (File.separatorChar == '/') { return filepath.isAbsolute(); } if (filepath.isAbsolute()) { return true; // nothing else to check, it already is technically an absolute path } String driveLetter = stripDriveLetter(new StringBuilder(path)); if (driveLetter != null) { return false; // the path already had a drive letter in it, it really is a relative path that we can't consider absolute } char cwdDriveLetter = new File("\\").getAbsolutePath().charAt(0); // gets the current working directory's drive letter return new File(cwdDriveLetter + ":" + path).isAbsolute(); } /** * Compressed the data found in the file. The file can only be decompressed with {@link #decompressFile(File)}. * * @param originalFile * @throws IOException */ public static void compressFile(File originalFile) throws IOException { // make a copy of the original data, so we can use the original file for the compressed data File decompressedFile = new File(originalFile + ".d"); try { copyFile(originalFile, decompressedFile); try { FileOutputStream out = new FileOutputStream(originalFile); out.write(new byte[] { (byte) 0 }); // write a prefix byte so people can't easily just gunzip this GZIPOutputStream zip = new GZIPOutputStream(out); // writes the compressed data into original file StreamUtil.copy(new FileInputStream(decompressedFile), zip); } catch (IOException e) { // try to restore the original file before throwing exception try { copyFile(decompressedFile, originalFile); } catch (Throwable ignore) { } throw e; } } finally { // System.out.println("compress: original: " + decompressedFile.length() + ", " // + MessageDigestGenerator.getDigestString(decompressedFile)); // System.out.println(" compress: " + originalFile.length() + ", " // + MessageDigestGenerator.getDigestString(originalFile)); decompressedFile.delete(); } } /** * Decompresses the compressed data found in the file that was compressed with {@link #compressFile(File)}. * If the file was not previously compressed, an exception is thrown. * * @param originalFile * @throws IOException */ public static void decompressFile(File originalFile) throws IOException { // make a copy of the original data, so we can use the original file for the decompressed data File compressedFile = new File(originalFile + ".c"); try { copyFile(originalFile, compressedFile); try { FileInputStream in = new FileInputStream(compressedFile); in.read(); // read our prefix byte GZIPInputStream zip = new GZIPInputStream(in); StreamUtil.copy(zip, new FileOutputStream(originalFile)); } catch (IOException e) { // try to restore the original file before throwing exception try { copyFile(compressedFile, originalFile); } catch (Throwable ignore) { } throw e; } } finally { // System.out.println("decompre: original: " + compressedFile.length() + ", " // + MessageDigestGenerator.getDigestString(compressedFile)); // System.out.println(" decompre: " + originalFile.length() + ", " // + MessageDigestGenerator.getDigestString(originalFile)); compressedFile.delete(); } } /* * I was going to use this, but then decided to use the compress/decompress instead. * However, this might be useful in the future. We can uncomment this if we want to use something * like this. There is also a commented out test in FileUtilTest that should be uncommented if * we reintroduce these two methods. * public static void obfuscateFile(File originalFile) throws IOException { // make a copy of the original data, so we can use the original file for the compressed data File deobfuscatedFile = new File(originalFile + ".d"); try { copyFile(originalFile, deobfuscatedFile); try { byte[] unobfuscatedData; String obfuscatedData; unobfuscatedData = StreamUtil.slurp(new FileInputStream(deobfuscatedFile)); obfuscatedData = Obfuscator.encode(new String(unobfuscatedData)); unobfuscatedData = null; // help GC FileUtil.writeFile(new ByteArrayInputStream(obfuscatedData.getBytes()), originalFile); obfuscatedData = null; // help GC } catch (Exception e) { // try to restore the original file before throwing exception try { copyFile(deobfuscatedFile, originalFile); } catch (Throwable ignore) { } if (e instanceof IOException) { throw (IOException) e; } else { throw new IOException(e); } } } finally { // System.out.println("obfuscat: original: " + deobfuscatedFile.length() + ", " // + MessageDigestGenerator.getDigestString(deobfuscatedFile)); // System.out.println(" obfuscat: " + originalFile.length() + ", " // + MessageDigestGenerator.getDigestString(originalFile)); deobfuscatedFile.delete(); } } public static void deobfuscateFile(File originalFile) throws IOException { // make a copy of the original data, so we can use the original file for the decompressed data File obfuscatedFile = new File(originalFile + ".o"); try { copyFile(originalFile, obfuscatedFile); try { String unobfuscatedData; byte[] obfuscatedData = StreamUtil.slurp(new FileInputStream(obfuscatedFile)); unobfuscatedData = Obfuscator.decode(new String(obfuscatedData)); obfuscatedData = null; // help GC FileUtil.writeFile(new ByteArrayInputStream(unobfuscatedData.getBytes()), originalFile); unobfuscatedData = null; // help GC } catch (Exception e) { // try to restore the original file before throwing exception try { copyFile(obfuscatedFile, originalFile); } catch (Throwable ignore) { } if (e instanceof IOException) { throw (IOException) e; } else { throw new IOException(e); } } } finally { // System.out.println("deobfusc: original: " + obfuscatedFile.length() + ", " // + MessageDigestGenerator.getDigestString(obfuscatedFile)); // System.out.println(" deobfusc: " + originalFile.length() + ", " // + MessageDigestGenerator.getDigestString(originalFile)); obfuscatedFile.delete(); } } */ }