package com.laytonsmith.PureUtilities.Common; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; import org.apache.commons.io.FileUtils; import org.mozilla.intl.chardet.nsDetector; import org.mozilla.intl.chardet.nsICharsetDetectionObserver; import org.mozilla.intl.chardet.nsPSMDetector; /** * */ public class FileUtil { private FileUtil() { } public static final int OVERWRITE = 0; public static final int APPEND = 1; private static final Map<String, Object> fileLocks = new HashMap<>(); private static final Map<String, Integer> fileLockCounter = new HashMap<>(); /** * A more complicated mechanism is required to ensure global access across the JVM * is synchronized, so file system accesses do not throw OverlappingFileLockExceptions. * Though process safe, file locks are not thread safe -.- * @param file * @return * @throws IOException */ private static synchronized Object getLock(File file) throws IOException{ String canonical = file.getAbsoluteFile().getCanonicalPath(); if(!fileLocks.containsKey(canonical)){ fileLocks.put(canonical, new Object()); fileLockCounter.put(canonical, 0); } fileLockCounter.put(canonical, fileLockCounter.get(canonical) + 1); return fileLocks.get(canonical); } private static synchronized void freeLock(File file) throws IOException{ String canonical = file.getAbsoluteFile().getCanonicalPath(); fileLockCounter.put(canonical, fileLockCounter.get(canonical) - 1); if(fileLockCounter.get(canonical) == 0){ fileLockCounter.remove(canonical); fileLocks.remove(canonical); } } public static String read(File f) throws IOException { return org.apache.commons.io.FileUtils.readFileToString(f, "UTF-8"); // try { // return read(f, "UTF-8"); // } catch (UnsupportedEncodingException ex) { // throw new Error(ex); // } } public static String read(File file, String charset) throws IOException{ return StreamUtils.GetString(readAsStream(file), charset); } /** * Returns the contents of this file as a string * * @param file The file to read * @return a string with the contents of the file * @throws FileNotFoundException */ public static InputStream readAsStream(File file) throws IOException { try { byte[] bytes = org.apache.commons.io.FileUtils.readFileToByteArray(file); return new ByteArrayInputStream(bytes); } catch(IOException ex){ //Apache IO has an interesting feature/bug where the error message "Unexpected readed size" is thrown. //If this is the case, we're going to try using a normal java file connection. Other IOExceptions //are just going be rethrown. if(ex.getMessage().startsWith("Unexpected readed size.")){ FileInputStream fis = new FileInputStream(file); try{ byte [] bytes = StreamUtils.GetBytes(fis); return new ByteArrayInputStream(bytes); } finally { //JVM bug with files fis.close(); fis = null; System.gc(); } } else { throw ex; } } // try{ // synchronized (getLock(file)) { // RandomAccessFile raf = new RandomAccessFile(file, "rw"); // FileLock lock = null; // try { // lock = raf.getChannel().lock(); // ByteBuffer buffer = ByteBuffer.allocate((int) raf.length()); // raf.getChannel().read(buffer); // return new ByteArrayInputStream(buffer.array()); // } finally { // if (lock != null) { // lock.release(); // } // raf.close(); // } // } // } finally { // freeLock(file); // } // FileInputStream fis = new FileInputStream(f); // try{ // return StreamUtils.GetString(fis, charset); // } finally { // fis.close(); // fis = null; // System.gc(); // } } /** * Works the same as write(String, File, int, false). * @param data * @param file * @param mode * @throws IOException */ public static void write(String data, File file, int mode) throws IOException{ write(data, file, mode, false); } public static void write(String data, File file, int mode, boolean create) throws IOException{ write(data.getBytes("UTF-8"), file, mode, create); } /** * Writes out a String to the given file, either appending or * overwriting, depending on the selected mode. If create is true, * will attempt to create the file and parent directories if need be. * * @param data The string to write to the file * @param file The File to write to * @param mode Either OVERWRITE or APPEND * @throws IOException If the File f cannot be written to */ public static void write(byte[] data, File file, int mode, boolean create) throws IOException { boolean append; if (mode == OVERWRITE) { append = false; } else { append = true; } if(create && !file.exists()){ if(file.getAbsoluteFile().getParentFile() != null){ file.getAbsoluteFile().getParentFile().mkdirs(); } file.getAbsoluteFile().createNewFile(); } FileUtils.writeByteArrayToFile(file, data, append); // try{ // synchronized (getLock(file)) { // int sleepTime = 0; // int sleepTimes = 0; // loop: while(true){ // try { // Thread.sleep(sleepTime); // sleepTime += 10; // sleepTimes++; // } catch (InterruptedException ex) { // // // } // RandomAccessFile raf = new RandomAccessFile(file, "rw"); // FileLock lock = null; // try { // lock = raf.getChannel().lock(); // //Clear out the file // if (!append) { // raf.getChannel().truncate(0); // } else { // raf.seek(raf.length()); // } // //Write out the data // MappedByteBuffer buf = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, data.length); // buf.put(data); // buf.force(); // //We assume it worked at this point, so let's break; // break loop; // //raf.getChannel().write(ByteBuffer.wrap(data)); // } catch(IOException e){ // //If we get this dumb message, we're on windows. We'll try again here shortly, // //but we don't want to bother the user with this exception if we can help it. // //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6354433 // if(!"The requested operation cannot be performed on a file with a user-mapped section open" // .equals(e.getMessage())){ // throw e; // } // if(sleepTimes > 10){ // //Eh. Gotta give up some time. // throw e; // } // } finally { // if (lock != null) { // lock.release(); // } // raf.close(); // raf = null; // System.gc(); // } // } // } // } finally { // freeLock(file); // } // FileWriter fw = new FileWriter(f, append); // fw.write(s); // fw.close(); } /** * This function writes out a String to a file, overwriting it if it * already exists * * @param s The string to write to the file * @param f The File to write to * @throws IOException If the File f cannot be written to */ public static void write(String s, File f) throws IOException { write(s, f, OVERWRITE); } /** * Shorthand for write(s, f, OVERWRITE, create) */ public static void write(String s, File f, boolean create) throws IOException{ write(s, f, OVERWRITE, create); } /** * Copies a file from one location to another. If overwrite is null, * prompts the user on the console if the file already exists. If * overwrite is false, the operation throws an exception if the file * already exists. If overwrite is true, the file is overwritten without * prompting if it already exists. * * @param fromFile * @param toFile * @param overwrite * @throws IOException */ public static void copy(File fromFile, File toFile, Boolean overwrite) throws IOException { if (!fromFile.exists()) { throw new IOException("FileCopy: " + "no such source file: " + fromFile.getName()); } if (!fromFile.isFile()) { throw new IOException("FileCopy: " + "can't copy directory: " + fromFile.getName()); } if (!fromFile.canRead()) { throw new IOException("FileCopy: " + "source file is unreadable: " + fromFile.getName()); } if (toFile.isDirectory()) { toFile = new File(toFile, fromFile.getName()); } if (toFile.exists()) { if (!toFile.canWrite()) { throw new IOException("FileCopy: " + "destination file is unwriteable: " + toFile.getName()); } String response = null; if (overwrite == null) { System.out.print("Overwrite existing file " + toFile.getName() + "? (Y/N): "); System.out.flush(); BufferedReader in = new BufferedReader(new InputStreamReader( System.in)); response = in.readLine(); } if ((overwrite != null && overwrite == false) || (!response.equals("Y") && !response.equals("y"))) { throw new IOException("FileCopy: " + "existing file was not overwritten."); } //overwrite being true falls through } else { String parent = toFile.getParent(); if (parent == null) { parent = System.getProperty("user.dir"); } File dir = new File(parent); if (!dir.exists()) { throw new IOException("FileCopy: " + "destination directory doesn't exist: " + parent); } if (dir.isFile()) { throw new IOException("FileCopy: " + "destination is not a directory: " + parent); } if (!dir.canWrite()) { throw new IOException("FileCopy: " + "destination directory is unwriteable: " + parent); } } FileUtils.copyFile(fromFile, toFile); // FileInputStream from = null; // FileOutputStream to = null; // try { // from = new FileInputStream(fromFile); // to = new FileOutputStream(toFile); // byte[] buffer = new byte[4096]; // int bytesRead; // // while ((bytesRead = from.read(buffer)) != -1) { // to.write(buffer, 0, bytesRead); // write // } // } finally { // if (from != null) { // try { // from.close(); // } catch (IOException e) { // ; // } // } // if (to != null) { // try { // to.close(); // } catch (IOException e) { // ; // } // } // } } /** * Moves a file from one location to another. Assuming no exception is * thrown, always returns true. * * @param from * @param to */ public static boolean move(File from, File to) throws IOException { FileUtils.moveFile(from, to); return true; // try{ // synchronized(getLock(to)){ // return from.renameTo(to); // } // } finally{ // freeLock(to); // } } /** * Recursively deletes a file/folder structure. True is returned if ALL * files were deleted. If it returns false, some or none of the files * may have been deleted. * * @param file * @return */ public static boolean recursiveDelete(File file) { //Hopefully this works around JVM bugs. //It seems that on windows machines, until garbage //collection happens, the system will still //have file locks on the file, even if the Streams //were properly closed. System.gc(); if (file.isDirectory()) { boolean ret = true; for (File f : file.listFiles()) { if (!recursiveDelete(f)) { ret = false; } } if (!file.delete()) { ret = false; } return ret; } else { return file.delete(); } } /** * Returns the most likely character encoding for this file. The default is * "ASCII" and is probably the most common. * @param file * @return * @throws IOException */ public static String getFileCharset(File file) throws IOException { int lang = nsPSMDetector.ALL; nsDetector det = new nsDetector(lang); final MutableObject result = new MutableObject("ASCII"); det.Init(new nsICharsetDetectionObserver() { @Override public void Notify(String charset) { result.setObject(charset); } }); BufferedInputStream imp = null; try{ imp = new BufferedInputStream(new FileInputStream(file)); byte[] buf = new byte[1024]; int len; boolean done = false; boolean isAscii = true; while((len=imp.read(buf, 0, buf.length)) != -1){ if(isAscii){ isAscii = det.isAscii(buf, len); } if(!isAscii && !done){ done = det.DoIt(buf, len, false); } } det.DataEnd(); return (String)result.getObject(); } finally { if(imp != null){ imp.close(); } } } }