package org.rr.commons.mufs; import static org.rr.commons.utils.StringUtil.EMPTY; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; import org.apache.commons.io.IOUtils; import org.rr.commons.collection.VolatileHashMap; import org.rr.commons.utils.ListUtils; import org.rr.commons.utils.StringUtil; public class ResourceHandlerFactory { /** * cache a limited number of {@link IResourceHandler} instances. */ private static final VolatileHashMap<String, IResourceHandler> resourceHandlerCache = new VolatileHashMap<String, IResourceHandler>(100,100); private static IResourceHandler userHome = null; private static final ArrayList<IResourceHandler> temporaryResourceLoader = new ArrayList<>(); private static final Thread shutdownThread = new Thread(new Runnable() { @Override public void run() { //stores all loaded which could not be deleted. final ArrayList<IResourceHandler> newTemporaryResourceLoader = new ArrayList<>(); IResourceHandler resourceHandler = null; for (int i = 0; i < temporaryResourceLoader.size(); i++) { try { resourceHandler = temporaryResourceLoader.get(i); //asking for existence throws an exception during shutdown under windows. resourceHandler.delete(); } catch (IOException e) { //do not abort System.err.println("could not delete temporary resource " + String.valueOf(resourceHandler)); newTemporaryResourceLoader.add(resourceHandler); } } temporaryResourceLoader.clear(); temporaryResourceLoader.addAll(newTemporaryResourceLoader); } }); static { Runtime.getRuntime().addShutdownHook(shutdownThread); } /** * Creates a {@link IResourceHandler} file which points to a temporary file which is automatically deleted * at application end. * * @param extension The file extension of the temporary file without the dot. */ public static IResourceHandler getTemporaryResource(String extension) { try { File tmp = File.createTempFile(UUID.randomUUID().toString(), extension != null ? "." + extension : ".tmp"); IResourceHandler resourceLoader = ResourceHandlerFactory.getResourceHandler(tmp); temporaryResourceLoader.add(resourceLoader); return resourceLoader; } catch (IOException e) { throw new RuntimeException(e); } } /** * Deletes all temporary resources previously created with the {@link #getTemporaryResource()} * method. */ static void removeTemporaryResource(IResourceHandler handler) { temporaryResourceLoader.remove(handler); } /** * reference instances. */ private static final IResourceHandler[] resourceLoader = new IResourceHandler[] { new FileResourceHandler(), new FTPResourceHandler(), new URLResourceHandler() }; /** * Creates a virtual resource handler which handles only the given childs. * @param name The name of the virtual resource * @param childs The childs to be provided by the virtual resource loader. * @return The desired virtual resource handler instance. */ public static IResourceHandler getVirtualResourceHandler(String name, final IResourceHandler[] childs) { return new VirtualStaticResourceHandler(name, childs); } /** * Creates a virtual resource handler which handles only the given childs. * @param name The name of the virtual resource * @param childs The childs to be provided by the virtual resource loader. * @return The desired virtual resource handler instance. */ public static IResourceHandler getVirtualResourceHandler(String name, final byte[] content) { return new VirtualStaticResourceHandler(name, new VirtualStaticResourceDataLoader() { @Override public InputStream getContentInputStream() { return new ByteArrayInputStream(content); } @Override public long length() { return content.length; } }); } /** * Creates a virtual resource handler which handles only the given childs. * @param name The name of the virtual resource * @param childs The childs to be provided by the virtual resource loader. * @return The desired virtual resource handler instance. */ public static IResourceHandler getVirtualResourceHandler(String name, final VirtualStaticResourceDataLoader content) { return new VirtualStaticResourceHandler(name, content); } /** * Gets a resource loader for the given {@link InputStream}. This is helpful if there is only * an {@link InputStream} available which contains data to be shown to components working with {@link IResourceHandler} * objects. * * @param inputStream The resource handled by the {@link IResourceHandler} * @return The desired {@link IResourceHandler} instance. */ public static IResourceHandler getResourceHandler(InputStream inputStream) { if(inputStream == null) { throw new NullPointerException("could not load null resource"); } return new InputStreamResourceHandler(inputStream); } /** * Get a new {@link IResourceHandler} with the given {@link IResourceHandler} as parent and the * <code>file</code> as child. * @param parent The parent {@link IResourceHandler} instance. * @param file The child attached to the parent. * @return The desired {@link IResourceHandler}. Please note that not all kind of {@link IResourceHandler} shall support this. */ public static IResourceHandler getResourceHandler(IResourceHandler parent, String file) { String parentResourceString = parent.getResourceString(); if(!parentResourceString.endsWith("/") && !parentResourceString.endsWith("\\") && !parentResourceString.endsWith(File.separator)) { parentResourceString += File.separator; } return getResourceHandler(parentResourceString + file); } /** * Gets a resource loader for the given {@link File}. * * @param file The resource handled by the {@link IResourceHandler} * @return The desired {@link IResourceHandler} instance. */ public static IResourceHandler getResourceHandler(final File file) { if(file == null) { throw new NullPointerException("could not load null resource"); } return new FileResourceHandler(file); } /** * Gets a resource loader for the given resource. * * @param url The URL handled by the {@link IResourceHandler} * @return The desired {@link IResourceHandler} instance or <code>null</code> if no {@link IResourceHandler} * available for the given resource string. */ public static IResourceHandler getResourceHandler(URL url) { return getResourceHandler(url.toString()); } /** * Get a {@link IResourceHandler} instance with the given, additional extension. The * result {@link IResourceHandler} will not exists. If the sibling with the extension already * exists a number will be added at the end of the result {@link IResourceHandler}. * * @param sibling the sibling path. * @param extension the extension for the sibling {@link IResourceHandler}. * @return The sibling {@link IResourceHandler}. */ public static IResourceHandler getTemporaryResourceLoader(String sibling, final String extension) { IResourceHandler resourceLoader = getResourceHandler(sibling); return getUniqueResourceHandler(resourceLoader, extension); } public static IResourceHandler getUniqueResourceHandler(final IResourceHandler sibling, String extension) { return getUniqueResourceHandler(sibling, null, extension); } /** * Get a {@link IResourceHandler} instance with the given, additional extension. If the sibling * with the desired extension already exists a number will be attached at the end of the filename. * * @param sibling the sibling {@link IResourceHandler}. * @param extension the extension for the sibling {@link IResourceHandler} without the dot separator. * @return The sibling {@link IResourceHandler}. */ public static IResourceHandler getUniqueResourceHandler(final IResourceHandler sibling, String addition, String extension) { if(extension == null) { extension = sibling.getFileExtension(); } String siblingString = sibling.toString(); if(siblingString.lastIndexOf('.') != -1) { siblingString = siblingString.substring(0, siblingString.lastIndexOf('.')); } //remove existing counter to avoid chained counter extensions. siblingString = siblingString.replaceAll("_\\d*$", EMPTY); int extensionNum = 0; IResourceHandler result = null; while( (result = getResourceHandler(siblingString + (addition != null ? "_" + addition : EMPTY) + (extensionNum != 0 ? "_" + extensionNum : EMPTY) + "." + extension)).exists() ) { extensionNum ++; } return result; } /** * Get those {@link IResourceHandler} which are previously created with the {@link #getUniqueResourceHandler(IResourceHandler, String)} * method. * * @param sibling the sibling {@link IResourceHandler}. * @param extension the extension for the sibling {@link IResourceHandler}. * @return The {@link IResourceHandler} found. Never returns null. */ public static List<IResourceHandler> getExistingUniqueResourceHandler(IResourceHandler sibling, final String extension) { String siblingString = sibling.toString(); if(siblingString.lastIndexOf('.') != -1) { siblingString = siblingString.substring(0, siblingString.lastIndexOf('.')); } int extensionNum = 0; List<IResourceHandler> resultList = new ArrayList<>(); IResourceHandler result = null; while( (result = getResourceHandler(siblingString + (extensionNum != 0 ? "_" + extensionNum : EMPTY) + "." + extension)).exists() ) { resultList.add(result); extensionNum ++; } return resultList; } @SuppressWarnings("unchecked") public static List<IResourceHandler> getResourceHandler(final Transferable t) throws IOException, ClassNotFoundException { List<Object> transferedFiles = Collections.emptyList(); if(t.isDataFlavorSupported(DataFlavor.stringFlavor)) { String data; try { data = (String) t.getTransferData(DataFlavor.stringFlavor); transferedFiles = getFileList(data, 100); } catch (UnsupportedFlavorException e) { } } else if(t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { try { transferedFiles = (List<Object>) t.getTransferData(DataFlavor.javaFileListFlavor); } catch (UnsupportedFlavorException e) { } } else if(t.isDataFlavorSupported(new DataFlavor("text/uri-list"))) { try { ByteArrayInputStream in = (ByteArrayInputStream) t.getTransferData(new DataFlavor("text/uri-list")); String data = IOUtils.toString(in); String[] uriList = data.split("\n"); transferedFiles = new ArrayList<>(uriList.length - 1); for(int i = 0; i < uriList.length; i++) { if(uriList[i].equals("copy") || uriList[i].equals("move")) { continue; } transferedFiles.add(uriList[i]); } } catch (UnsupportedFlavorException e) { } } ArrayList<IResourceHandler> result = new ArrayList<>(); for(Object file : transferedFiles) { if(file instanceof File) { result.add(getResourceHandler((File) file)); } else if(file instanceof String) { result.add(getResourceHandler((String) file)); } } return result; } /** * Tests if there is someting in the given {@link Transferable} that could be * fetched as {@link IResourceHandler}. * @return <code>true</code> if there is something that could be handled by an {@link IResourceHandler} instance * and <code>false</code> otherwise. */ public static boolean hasResourceHandler(final Transferable contents) { if(contents.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { return true; } else if(contents.isDataFlavorSupported(DataFlavor.stringFlavor)) { try { String data = (String) contents.getTransferData(DataFlavor.stringFlavor); List<Object> fileList = getFileList(data, 10); return !fileList.isEmpty(); } catch (Exception e) { return false; } } else try { if(contents.isDataFlavorSupported(new DataFlavor("text/uri-list"))) { return true; } } catch (Exception e) { } return false; } /** * Gets a resource loader for the given resource. * * @param resource The resource handled by the {@link IResourceHandler} * @return The desired {@link IResourceHandler} instance or <code>null</code> if no {@link IResourceHandler} * available for the given resource string. */ public static IResourceHandler getResourceHandler(final String resource) { if(resource == null || resource.isEmpty()) { return null; } synchronized(resourceHandlerCache) { IResourceHandler resourceHandler = resourceHandlerCache.get(resource); if(resourceHandler!=null) { return resourceHandler; } for (int i = 0; i < resourceLoader.length; i++) { if(resourceLoader[i].isValidResource(resource)) { try { IResourceHandler createdResourcehandlerInstance = resourceLoader[i].createInstance(resource); resourceHandlerCache.put(resource, createdResourcehandlerInstance); return createdResourcehandlerInstance; } catch (Exception e) { return null; } } } } return null; } /** * Tests if a loader is available for the given resource. * @param resource The resource string to be tested. * @return <code>true</code> if a resourceloader could be created from the given string and <code>false</code> otherwise. */ public static boolean hasResourceHandler(final String resource) { IResourceHandler loader = getResourceHandler(resource); return loader!=null; } public static void clearCache() { resourceHandlerCache.clear(); } /** * The given {@link IResourceHandler} instance will be deleted on application exit. * @param resourceHandler The {@link IResourceHandler} that should be deleted on application shutdown. */ public static void deleteOnExit(IResourceHandler resourceHandler) { temporaryResourceLoader.add(resourceHandler); } /** * Gets the {@link IResourceHandler} instance for the users home directory. * @return The desired user home {@link IResourceHandler} */ public static IResourceHandler getUserHomeResourceLoader() { if(userHome == null) { String userHomeProp = System.getProperty("user.name"); userHome = getResourceHandler(userHomeProp); } return userHome; } /** * Splits the given Data string into a list of files. * @param data The data to be splitted into a file list. * @param invalid The number of non existing file entries before the list creation gets aborted. * @return The list of files. Never returns <code>null</code>. */ private static List<Object> getFileList(String data, int invalid) { int invalidCount = 0; ArrayList<Object> result = new ArrayList<>(); data = data.replace("\r", EMPTY); List<String> splitData = ListUtils.split(data, '\n'); for (String splitDataItem : splitData) { if (!StringUtil.toString(splitDataItem).trim().isEmpty()) { try { File file; if(splitDataItem.indexOf(":") != -1) { file = new File(new URI(splitDataItem)); } else { file = new File(splitDataItem); } if (file.isFile()) { result.add(file); } else if(!file.isDirectory()) { invalidCount ++; if(invalidCount > invalid) { break; } } } catch (URISyntaxException e) { // LoggerFactory.getLogger().log(Level.INFO, "No valid file " + splitDataItem); } } } return result; } }