/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.pepsoft.util; import java.awt.*; import java.io.*; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.List; import org.jetbrains.annotations.NonNls; import javax.swing.*; import javax.swing.filechooser.FileFilter; /** * * @author pepijn */ public class FileUtils { private FileUtils() { // Prevent instantiation } public static Checksum getMD5(File file) throws IOException { try { MessageDigest md5Digest = MessageDigest.getInstance("MD5"); byte[] buffer = new byte[BUFFER_SIZE]; try (FileInputStream in = new FileInputStream(file)) { int read; while ((read = in.read(buffer)) != -1) { md5Digest.update(buffer, 0, read); } } return new Checksum(md5Digest.digest()); } catch (NoSuchAlgorithmException e) { // MD5 is among the minimally required algorithms to be supported by // a Java VM throw new InternalError("MD5 message digest not supported by Java runtime"); } } public static String load(File file, Charset charset) throws IOException { long length = file.length(); if (length > Integer.MAX_VALUE) { throw new UnsupportedOperationException("File too large (" + length + " bytes)"); } StringBuilder sb = new StringBuilder((int) length); try (InputStreamReader in = new InputStreamReader(new BufferedInputStream(new FileInputStream(file)), charset)) { char[] buffer = new char[BUFFER_SIZE]; int read; while ((read = in.read(buffer)) != -1) { sb.append(buffer, 0, read); } } return sb.toString(); } public static String load(InputStream inputStream, Charset charset) throws IOException { StringBuilder sb = new StringBuilder(); try (InputStreamReader in = new InputStreamReader(new BufferedInputStream(inputStream), charset)) { char[] buffer = new char[BUFFER_SIZE]; int read; while ((read = in.read(buffer)) != -1) { sb.append(buffer, 0, read); } } return sb.toString(); } /** * Recursively copy a directory including all contents. * * @param dir The directory to copy. Must exist. * @param destDir The directory into which to copy the contents of * <code>dir</code>. Must not exist yet and will be created. * @throws IOException If there is an I/O error while performing the copy. */ public static void copyDir(File dir, File destDir) throws IOException { if (! dir.isDirectory()) { throw new IllegalArgumentException("Source directory " + dir + " does not exist or is not a directory"); } if (destDir.isDirectory()) { throw new IllegalStateException("Destination directory " + destDir + " already exists"); } if (! destDir.mkdirs()) { throw new IOException("Could not create " + destDir); } File[] files = dir.listFiles(); //noinspection ConstantConditions // Guaranteed by precondition check at start for (File file: files) { if (file.isDirectory()) { copyDir(file, new File(destDir, file.getName())); } else if (file.isFile()) { copyFileToDir(file, destDir); } else { logger.warn("Not copying " + file + "; not a regular file or directory"); } } destDir.setLastModified(dir.lastModified()); } /** * Copy a file to another file. * * @param file The file to copy. * @param destFile The file to copy the file to. If <code>overwrite</code> * is <code>false</code> it must not exist yet. In either * case it may not be an existing directory. * @param overwrite Whether <code>destFile</code> should be overwritten if * it already exists. If this is false and the file does * already exist an {@link IllegalStateException} will be * thrown. * @throws IOException If there is an I/O error while performing the copy. * @throws IllegalStateException If <code>overwrite</code> was * <code>false</code> and <code>destFile</code> already existed. */ public static void copyFileToFile(File file, File destFile, boolean overwrite) throws IOException { if ((! overwrite) && destFile.isFile()) { throw new IllegalStateException("Destination file " + destFile + " already exists"); } if (destFile.isDirectory()) { throw new IllegalStateException("Destination file is an existing directory"); } try (FileInputStream in = new FileInputStream(file); FileOutputStream out = new FileOutputStream(destFile)) { int bytesRead; byte[] buffer = new byte[BUFFER_SIZE]; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } } destFile.setLastModified(file.lastModified()); } /** * Copy a file to another directory. * * @param file The file to copy. * @param destDir The directory to copy the file into, using the same name * as the source file. * @throws IOException If there is an I/O error while performing the copy. */ public static void copyFileToDir(File file, File destDir) throws IOException { try { copyFileToDir(file, destDir, null); } catch (ProgressReceiver.OperationCancelled e) { throw new InternalError(); } } /** * Copy a file to another directory with optional progress reporting. * * @param file The file to copy. * @param destDir The directory to copy the file into, using the same name * as the source file. * @param progressReceiver The progress receiver to report copying progress * to. May be <code>null</code>. * @throws IOException If there is an I/O error while performing the copy. */ public static void copyFileToDir(File file, File destDir, ProgressReceiver progressReceiver) throws IOException, ProgressReceiver.OperationCancelled { File destFile = new File(destDir, file.getName()); if (destFile.isFile()) { throw new IllegalStateException("Destination file " + destFile + " already exists"); } long fileSize = file.length(); long bytesCopied = 0; try (FileInputStream in = new FileInputStream(file); FileOutputStream out = new FileOutputStream(destFile)) { int bytesRead; byte[] buffer = new byte[BUFFER_SIZE]; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); bytesCopied += bytesRead; if ((progressReceiver != null) && (fileSize > 0)) { progressReceiver.setProgress((float) ((double) bytesCopied / fileSize)); } } } destFile.setLastModified(file.lastModified()); } /** * Recursively delete a directory and all its contents. * * @param dir The directory to delete. * @return <code>true</code> if and only if the directory is successfully deleted; <code>false</code> otherwise */ public static boolean deleteDir(File dir) { if (! dir.isDirectory()) { throw new IllegalArgumentException(dir + " does not exist or is not a directory"); } File[] contents = dir.listFiles(); //noinspection ConstantConditions // Guaranteed by precondition check at start for (File file: contents) { if (file.isDirectory()) { deleteDir(file); } else { file.delete(); } } return dir.delete(); } /** * Sanitises a filename by replacing characters which are illegal for * Windows, Linux or Mac OS filenames with underscores and enforcing other * rules. * * @param filename The filename to sanitise. * @return The sanitised filename. */ public static String sanitiseName(@NonNls String filename) { StringBuilder sb = new StringBuilder(filename.length()); // Replace illegal characters for Windows, Linux or Mac OS with // underscores for (char c: filename.toCharArray()) { if ((c < 32) || (ILLEGAL_CHARS.indexOf(c) != -1)) { sb.append(REPLACEMENT_CHAR); } else { sb.append(c); } } // Windows can't cope with filenames which end with spaces or periods if ((sb.charAt(sb.length() - 1) == '.') || (sb.charAt(sb.length() - 1) == ' ')) { sb.setCharAt(sb.length() - 1, REPLACEMENT_CHAR); } // Make sure the name doesn't start with a Windows reserved name, by // changing the third character to an underscore if necessary String uppercaseVersion = sb.toString().toUpperCase(); for (String reservedName: RESERVED_NAMES) { if (uppercaseVersion.startsWith(reservedName) && ((uppercaseVersion.length() == reservedName.length()) || (uppercaseVersion.charAt(reservedName.length()) == '.'))) { sb.setCharAt(2, REPLACEMENT_CHAR); break; } } return sb.toString(); } /** * Select a single existing file for loading. * * @param parent The window relative to which the modal file dialog should * be displayed. * @param title The text for the title bar of the file dialog. * @param fileOrDir A file or directory to preselect. * @param fileFilter A filter limiting which files and/or directories can be * selected. * @return The selected file, or <code>null</code> if the user cancelled the * dialog. */ public static File selectFileForOpen(Window parent, String title, File fileOrDir, final FileFilter fileFilter) { if (SystemUtils.isMac()) { // On Macs the AWT file dialog looks much closer to native than the // Swing one, so use it FileDialog fileDialog; if (parent instanceof Frame) { fileDialog = new FileDialog((Frame) parent, title, FileDialog.LOAD); } else { fileDialog = new FileDialog((Dialog) parent, title, FileDialog.LOAD); } if (fileOrDir != null) { if (fileOrDir.isDirectory()) { fileDialog.setDirectory(fileOrDir.getPath()); } else { fileDialog.setDirectory(fileOrDir.getParent()); fileDialog.setDirectory(fileOrDir.getName()); } } if (fileFilter != null) { fileDialog.setFilenameFilter((file, s) -> fileFilter.accept(new File(file, s))); } fileDialog.setVisible(true); File[] files = fileDialog.getFiles(); if (files.length == 1) { return files[0]; } else { return null; } } else { JFileChooser fileChooser; if (fileOrDir != null) { if (fileOrDir.isDirectory()) { fileChooser = new JFileChooser(fileOrDir); } else { fileChooser = new JFileChooser(fileOrDir.getParentFile()); fileChooser.setSelectedFile(fileOrDir); } } else { fileChooser = new JFileChooser(); } fileChooser.setDialogTitle(title); fileChooser.setFileFilter(fileFilter); fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { return fileChooser.getSelectedFile(); } else { return null; } } } /** * Select one or more existing files for loading. * * @param parent The window relative to which the modal file dialog should * be displayed. * @param title The text for the title bar of the file dialog. * @param fileOrDir A file or directory to preselect. * @param fileFilter A filter limiting which files and/or directories can be * selected. * @return The selected file(s), or <code>null</code> if the user cancelled * the dialog. */ public static File[] selectFilesForOpen(Window parent, String title, File fileOrDir, final FileFilter fileFilter) { if (SystemUtils.isMac()) { // On Macs the AWT file dialog looks much closer to native than the // Swing one, so use it FileDialog fileDialog; if (parent instanceof Frame) { fileDialog = new FileDialog((Frame) parent, title, FileDialog.LOAD); } else { fileDialog = new FileDialog((Dialog) parent, title, FileDialog.LOAD); } fileDialog.setMultipleMode(true); if (fileOrDir != null) { if (fileOrDir.isDirectory()) { fileDialog.setDirectory(fileOrDir.getPath()); } else { fileDialog.setDirectory(fileOrDir.getParent()); fileDialog.setDirectory(fileOrDir.getName()); } } fileDialog.setFilenameFilter((file, s) -> fileFilter.accept(new File(file, s))); fileDialog.setVisible(true); File[] files = fileDialog.getFiles(); if (files.length > 0) { return files; } else { return null; } } else { JFileChooser fileChooser; if (fileOrDir != null) { if (fileOrDir.isDirectory()) { fileChooser = new JFileChooser(fileOrDir); } else { fileChooser = new JFileChooser(fileOrDir.getParentFile()); fileChooser.setSelectedFile(fileOrDir); } } else { fileChooser = new JFileChooser(); } fileChooser.setMultiSelectionEnabled(true); fileChooser.setDialogTitle(title); fileChooser.setFileFilter(fileFilter); fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { return fileChooser.getSelectedFiles(); } else { return null; } } } /** * Select a single filename for saving. May be the name of an existing file, * or a non-existent file. * * @param parent The window relative to which the modal file dialog should * be displayed. * @param title The text for the title bar of the file dialog. * @param fileOrDir An existing file or directory to preselect. * @param fileFilter A filter limiting which files and/or directories can be * selected. * @return The selected file, or <code>null</code> if the user cancelled the * dialog. */ public static File selectFileForSave(Window parent, String title, File fileOrDir, final FileFilter fileFilter) { if (SystemUtils.isMac()) { // On Macs the AWT file dialog looks much closer to native than the // Swing one, so use it FileDialog fileDialog; if (parent instanceof Frame) { fileDialog = new FileDialog((Frame) parent, title, FileDialog.SAVE); } else { fileDialog = new FileDialog((Dialog) parent, title, FileDialog.SAVE); } if (fileOrDir != null) { if (fileOrDir.isDirectory()) { fileDialog.setDirectory(fileOrDir.getPath()); } else { fileDialog.setDirectory(fileOrDir.getParent()); fileDialog.setFile(fileOrDir.getName()); } } fileDialog.setFilenameFilter((file, s) -> fileFilter.accept(new File(file, s))); fileDialog.setVisible(true); File[] files = fileDialog.getFiles(); if (files.length == 1) { return files[0]; } else { return null; } } else { JFileChooser fileChooser; if (fileOrDir != null) { if (fileOrDir.isDirectory()) { fileChooser = new JFileChooser(fileOrDir); } else { fileChooser = new JFileChooser(fileOrDir.getParentFile()); fileChooser.setSelectedFile(fileOrDir); } } else { fileChooser = new JFileChooser(); } fileChooser.setDialogTitle(title); fileChooser.setFileFilter(fileFilter); fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); if (fileChooser.showSaveDialog(parent) == JFileChooser.APPROVE_OPTION) { return fileChooser.getSelectedFile(); } else { return null; } } } /** * Checks if a <code>File</code> is really a <code>java.io.File</code>, and * if not converts it to one using {@link File#getAbsolutePath()}. * * @param file The file to absolutise. May be <code>null</code>. * @return A file with the same absolute path as the input and guaranteed to * be of class <code>java.io.File</code>, or <code>null</code> if the input was * <code>null</code>. */ public static File absolutise(File file) { return ((file != null) && (file.getClass() != File.class)) ? new File(file.getAbsolutePath()) : file; } /** * Ensures that a collection of {@link File}s only contains instances of * <code>java.io.File</code> and not subclasses, by converting subclasses * using {@link #absolutise(File)}. The collection is transformed in-place * if possible; otherwise a new collection with the same basic * characteristics is created. * * @param collection The collection to transform. * @param <T> The type of collection. * @return Either the same collection, transformed in-place, or a new * collection of the same basic type as the input containing the * transformed results. */ @SuppressWarnings("unchecked") // Guaranteed by code public static <T extends Collection<File>> T absolutise(T collection) { if (collection == null) { return null; } else if (collection instanceof List) { for (ListIterator<File> i = ((List<File>) collection).listIterator(); i.hasNext(); ) { Object object = i.next(); try { i.set(absolutise((File) object)); } catch (UnsupportedOperationException e) { Collection<File> newCollection; if (collection instanceof RandomAccess) { newCollection = new ArrayList<>(collection.size()); } else { newCollection = new LinkedList<>(); } for (Object object2: collection) { newCollection.add(absolutise((File) object2)); } return (T) newCollection; } } return collection; } else { Collection<File> newCollection; if (collection instanceof SortedSet) { newCollection = new TreeSet<>(); } else if (collection instanceof Set) { newCollection = new HashSet<>(collection.size()); } else { newCollection = new ArrayList<>(collection.size()); } for (Object object: collection) { newCollection.add(absolutise((File) object)); } return (T) newCollection; } } /** * Ensures that a map with {@link File}s as keys and/or values only contains * instances of <code>java.io.File</code> and not subclasses, by converting * subclasses using {@link #absolutise(File)}. The map is transformed * in-place if possible; otherwise a new map with the same basic * characteristics is created. * * @param map The map to transform. * @param <T> The type of map. * @return Either the same map, transformed in-place, or a new map of the * same basic type as the input containing the transformed results. */ @SuppressWarnings("unchecked") // Guaranteed by code public static <T extends Map<?, ?>> T absolutise(T map) { if (map == null) { return null; } for (Map.Entry<Object, Object> entry: ((Map<Object, Object>) map).entrySet()) { if ((entry.getKey() instanceof File) && (entry.getKey() != File.class)) { // There is a non-File File key in the map; start over and // create a new map, since we can't replace keys Map<Object, Object> newMap; if (map instanceof SortedMap) { newMap = new TreeMap<>(); } else { newMap = new HashMap<>(); } for (Map.Entry<?, ?> entry2: map.entrySet()) { Object key = entry2.getKey(); Object value = entry2.getValue(); if (key instanceof File) { key = absolutise((File) key); } if (value instanceof File) { value = absolutise((File) value); } newMap.put(key, value); } return (T) newMap; } Object value = entry.getValue(); if (value instanceof File) { entry.setValue(absolutise((File) value)); } } return map; } public static void main(String[] args) throws IOException { Checksum md5 = getMD5(new File(args[0])); System.out.print('{'); byte[] bytes = md5.getBytes(); for (int i = 0; i < bytes.length; i++) { if (i > 0) { System.out.print(", "); } System.out.print("(byte) "); System.out.print(bytes[i]); } System.out.println('}'); } private static final int BUFFER_SIZE = 32768; private static final String ILLEGAL_CHARS = "<>:\"/\\|?*\t\r\n\b\f"; private static final Set<String> RESERVED_NAMES = new HashSet<>( Arrays.asList("CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9")); private static final char REPLACEMENT_CHAR = '_'; private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(FileUtils.class); }