/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.filesystems; import java.io.*; import java.net.URLStreamHandler; import java.util.*; import java.util.jar.*; import org.openide.util.Utilities; /** Common utilities for handling files. * This is a dummy class; all methods are static. * * @author Petr Hamernik */ public final class FileUtil extends Object { private FileUtil() {} /** Copies stream of files. * <P> * Please be aware, that this method doesn't close any of passed streams. * @param is input stream * @param os output stream */ public static void copy (InputStream is, OutputStream os) throws IOException { final byte[] BUFFER = new byte[4096]; int len; for (;;) { len = is.read (BUFFER); if (len == -1) return; os.write (BUFFER, 0, len); } } /** Copies file to the selected folder. * This implementation simply copies the file by stream content. * @param source source file object * @param destFolder destination folder * @param newName file name (without extension) of destination file * @param newExt extension of destination file * @return the created file object in the destination folder * @exception IOException if <code>destFolder</code> is not a folder or does not exist; the destination file already exists; or * another critical error occurs during copying */ static FileObject copyFileImpl ( FileObject source, FileObject destFolder, String newName, String newExt ) throws IOException { FileObject dest = destFolder.createData(newName, newExt); FileLock lock = null; InputStream bufIn = null; OutputStream bufOut = null; try { lock = dest.lock(); bufIn = source.getInputStream(); if (dest instanceof AbstractFileObject) /** prevents from firing fileChange*/ bufOut = ((AbstractFileObject)dest).getOutputStream(lock, false); else bufOut = dest.getOutputStream(lock); copy (bufIn, bufOut); copyAttributes (source, dest); } finally { if (bufIn != null) bufIn.close(); if (bufOut != null) bufOut.close(); if (lock != null) lock.releaseLock(); } return dest; } // // public methods // /** Copies file to the selected folder. * This implementation simply copies the file by stream content. * @param source source file object * @param destFolder destination folder * @param newName file name (without extension) of destination file * @param newExt extension of destination file * @return the created file object in the destination folder * @exception IOException if <code>destFolder</code> is not a folder or does not exist; the destination file already exists; or * another critical error occurs during copying */ public static FileObject copyFile(FileObject source, FileObject destFolder, String newName, String newExt) throws IOException { return source.copy (destFolder, newName, newExt); } /** Copies file to the selected folder. * This implementation simply copies the file by stream content. * Uses the extension of the source file. * @param source source file object * @param destFolder destination folder * @param newName file name (without extension) of destination file * @return the created file object in the destination folder * @exception IOException if <code>destFolder</code> is not a folder or does not exist; the destination file already exists; or * another critical error occurs during copying */ public static FileObject copyFile(FileObject source, FileObject destFolder, String newName) throws IOException { return copyFile(source, destFolder, newName, source.getExt()); } /** Moves file to the selected folder. * This implementation uses a copy-and-delete mechanism, and automatically uses the necessary lock. * @param source source file object * @param destFolder destination folder * @param newName file name (without extension) of destination file * @return new file object * @exception IOException if either the {@link #copyFile copy} or {@link FileObject#delete delete} failed */ public static FileObject moveFile(FileObject source, FileObject destFolder, String newName) throws IOException { FileLock lock = null; try { lock = source.lock(); return source.move (lock, destFolder, newName, source.getExt ()); } finally { if (lock != null) lock.releaseLock(); } } /** * Creates a folder on given filesystem. The name of the new folder can be * specified as a multi-component pathname whose components are separated * by File.separatorChar or "/" (forward slash). * * @param folder where the new folder will be placed in * @param name name of the new folder * @return the new folder * @exception IOException if the creation fails */ public static FileObject createFolder (FileObject folder, String name) throws IOException { String separators; if (File.separatorChar != '/') separators = "/" + File.separatorChar; // NOI18N else separators = "/"; // NOI18N StringTokenizer st = new StringTokenizer (name, separators); while (st.hasMoreElements ()) { name = st.nextToken (); if (name.length () > 0) { FileObject f = folder.getFileObject (name); if (f == null) { try { f = folder.createFolder (name); } catch (SyncFailedException ex) { // there might be unconsistency between the cache // and the disk, that is why folder.refresh(); // and try again f = folder.getFileObject (name); if (f == null) { // if still not found than we have to report the // exception throw ex; } } } folder = f; } } return folder; } /** Creates a data file on given filesystem. The name of * data file can be composed as resource name (e. g. org/netbeans/myfolder/mydata ) * and the method scans which of folders has already been created * and which not. * * @param folder to begin with creation at * @param name name of data file as a resource * @return the data file for given name * @exception IOException if the creation fails */ public static FileObject createData (FileObject folder, String name) throws IOException { String foldername, dataname, fname, ext; int index = name.lastIndexOf('/'); FileObject data; // names with '/' on the end are not valid if (index >= name.length()) throw new IOException("Wrong file name."); // NOI18N // if name contains '/', create necessary folder first if (index != -1) { foldername = name.substring(0, index); dataname = name.substring(index + 1); folder = createFolder(folder, foldername); } else { dataname = name; } // create data index = dataname.lastIndexOf('.'); if (index != -1) { fname = dataname.substring(0, index); ext = dataname.substring(index + 1); } else { fname = dataname; ext = ""; // NOI18N } data = folder.getFileObject (fname, ext); if (data == null) { try { data = folder.createData(fname, ext); } catch (SyncFailedException ex) { // there might be unconsistency between the cache // and the disk, that is why folder.refresh(); // and try again data = folder.getFileObject (fname, ext); if (data == null) { // if still not found than we have to report the // exception throw ex; } } } return data; } /** Finds appropriate java.io.File to FileObject if possible. * If not possible then null is returned. * @param fo FileObject whose coresponding File will be looked for * @return java.io.File or null if no corresponding File exists. * @since 1.29 */ public static java.io.File toFile (FileObject fo) { return (java.io.File)fo.getAttribute ("java.io.File");// NOI18N } /** Finds appropriate FileObjects to java.io.File if possible. * If not possible then empty array is returned. More FileObjects may * correspond to one java.io.File that`s why array is returned. * @param file File whose coresponding FileObjects will be looked for * @return corresponding FileObjects or empty array if no * corresponding FileObject exists. * @since 1.29 */ public static FileObject[] fromFile (File file) { Enumeration en = Repository.getDefault().getFileSystems(); ArrayList list = new ArrayList (); String fileName = null; try { file = file.getCanonicalFile(); fileName = file.getCanonicalPath(); } catch (IOException iex) { return new FileObject[] {}; } while (en.hasMoreElements()) { FileSystem fs = (FileSystem)en.nextElement(); try { String rootName = null; FileObject fsRoot = fs.getRoot (); File root = toFile (fsRoot); if (root == null) { Object rootPath = fsRoot.getAttribute("FileSystem.rootPath");//NOI18N if (rootPath != null && (rootPath instanceof String)) rootName = (String)rootPath; else continue; } if (rootName == null) rootName = root.getCanonicalPath(); /**root is parent of file*/ if (fileName.indexOf(rootName) == 0) { String res = fileName.substring(rootName.length()).replace(File.separatorChar, '/'); FileObject fo = fs.findResource(res); File file2Fo = (fo != null)? toFile(fo) : null; if (fo != null && file2Fo != null && file.equals(file2Fo.getCanonicalFile())) list.add(fo); } } catch (IOException iexc) { // catch for getCanonical.. continue; } } FileObject[] results = new FileObject[list.size()]; list.toArray(results); return results; } /** transient attributes which should not be copied * of type Set<String> */ static final Set transientAttributes = new HashSet (); static { transientAttributes.add ("templateWizardURL"); // NOI18N transientAttributes.add ("templateWizardIterator"); // NOI18N transientAttributes.add ("templateWizardDescResource"); // NOI18N transientAttributes.add ("SystemFileSystem.localizingBundle"); // NOI18N transientAttributes.add ("SystemFileSystem.icon"); // NOI18N transientAttributes.add ("SystemFileSystem.icon32"); // NOI18N } /** Copies attributes from one file to another. * Note: several special attributes will not be copied, as they should * semantically be transient. These include attributes used by the * template wizard (but not the template atttribute itself). * @param source source file object * @param dest destination file object * @exception IOException if the copying failed */ public static void copyAttributes (FileObject source, FileObject dest) throws IOException { Enumeration attrKeys = source.getAttributes(); while (attrKeys.hasMoreElements()) { String key = (String) attrKeys.nextElement(); if (transientAttributes.contains (key)) continue; if (isTransient (source, key)) continue; Object value = source.getAttribute(key); if (value != null) { dest.setAttribute(key, value); } } } static boolean isTransient (FileObject fo, String attrName) { return XMLMapAttr.ModifiedAttribute.isTransient (fo, attrName); } /** Extract jar file into folder represented by file object. If the JAR contains * files with name filesystem.attributes, it is assumed that these files * has been created by DefaultAttributes implementation and the content * of these files is treated as attributes and added to extracted files. * <p><code>META-INF/</code> directories are skipped over. * * @param fo file object of destination folder * @param is input stream of jar file * @exception IOException if the extraction fails * @deprecated Use of XML filesystem layers generally obsoletes this method. */ public static void extractJar (final FileObject fo, final InputStream is) throws IOException { FileSystem fs = fo.getFileSystem(); fs.runAtomicAction (new FileSystem.AtomicAction () { public void run () throws IOException { extractJarImpl (fo, is); } }); } /** Does the actual extraction of the Jar file. */ private static void extractJarImpl (FileObject fo, InputStream is) throws IOException { JarInputStream jis; JarEntry je; // files with extended attributes (name, DefaultAttributes.Table) HashMap attributes = new HashMap (7); jis = new JarInputStream(is); while ((je = jis.getNextJarEntry()) != null) { String name = je.getName(); if (name.toLowerCase ().startsWith ("meta-inf/")) continue; // NOI18N if (je.isDirectory ()) { createFolder (fo, name); continue; } if (DefaultAttributes.acceptName (name)) { // file with extended attributes DefaultAttributes.Table table = DefaultAttributes.loadTable (jis,name); attributes.put (name, table); } else { // copy the file FileObject fd = createData(fo, name); FileLock lock = fd.lock (); try { OutputStream os = fd.getOutputStream (lock); try { copy (jis, os); } finally { os.close (); } } finally { lock.releaseLock (); } } } // // apply all extended attributes // Iterator it = attributes.entrySet ().iterator (); while (it.hasNext ()) { Map.Entry entry = (Map.Entry)it.next (); String fileName = (String)entry.getKey (); int last = fileName.lastIndexOf ('/'); String dirName; if (last != -1) dirName = fileName.substring (0, last + 1); else dirName = ""; // NOI18N String prefix = fo.isRoot () ? dirName : fo.getPath() + '/' + dirName; DefaultAttributes.Table t = (DefaultAttributes.Table)entry.getValue (); Iterator files = t.keySet ().iterator (); while (files.hasNext ()) { String orig = (String)files.next (); String fn = prefix + orig; FileObject obj = fo.getFileSystem ().findResource (fn); if (obj == null) { continue; } Enumeration attrEnum = t.attrs (orig); while (attrEnum.hasMoreElements ()) { // iterate thru all arguments String attrName = (String)attrEnum.nextElement (); // Note: even transient attributes set here! Object value = t.getAttr (orig, attrName); if (value != null) { obj.setAttribute (attrName, value); } } } } } // extractJar /** Gets the extension of a specified file name. The extension is * everything after the last dot. * * @param fileName name of the file * @return extension of the file (or <code>""</code> if it had none) */ public static String getExtension(String fileName) { int index = fileName.lastIndexOf("."); // NOI18N if (index == -1) return ""; // NOI18N else return fileName.substring(index + 1); } /** Finds an unused file name similar to that requested in the same folder. * The specified file name is used if that does not yet exist. * Otherwise, the first available name of the form <code>basename_nnn.ext</code> (counting from one) is used. * * <p><em>Caution:</em> this method does not lock the parent folder * to prevent race conditions: i.e. it is possible (though unlikely) * that the resulting name will have been created by another thread * just as you were about to create the file yourself (if you are, * in fact, intending to create it just after this call). Since you * cannot currently lock a folder against child creation actions, * the safe approach is to use a loop in which a free name is * retrieved; an attempt is made to {@link FileObject#createData create} * that file; and upon an <code>IOException</code> during * creation, retry the loop up to a few times before giving up. * * @param folder parent folder * @param name preferred base name of file * @param ext extension to use * @return a free file name */ public static String findFreeFileName ( FileObject folder, String name, String ext ) { if (checkFreeName (folder, name, ext)) { return name; } for (int i = 1;;i++) { String destName = name + "_"+i; // NOI18N if (checkFreeName (folder, destName, ext)) { return destName; } } } /** Finds an unused folder name similar to that requested in the same parent folder. * <p>See caveat for <code>findFreeFileName</code>. * @see #findFreeFileName findFreeFileName * @param folder parent folder * @param name preferred folder name * @return a free folder name */ public static String findFreeFolderName ( FileObject folder, String name ) { if (checkFreeName (folder, name, null)) { return name; } for (int i = 1;;i++) { String destName = name + "_"+i; // NOI18N if (checkFreeName (folder, destName, null)) { return destName; } } } /** Test if given name is free in given folder. * @param fo folder to check in * @param name name of the file or folder to check * @param ext extension of the file (null for folders) * @return true, if such name does not exists */ private static boolean checkFreeName ( FileObject fo, String name, String ext ) { if (Utilities.isWindows()) { // case-insensitive, do some special check Enumeration en = fo.getChildren(false); while (en.hasMoreElements()) { fo = (FileObject)en.nextElement(); String n = fo.getName (); String e = fo.getExt(); // different names => check others if (!n.equalsIgnoreCase (name)) continue; // same name + without extension => no if ((ext == null || ext.trim().length() == 0) && (e == null || e.trim().length() == 0)) return false; // one of there is witout extension => check next if (ext == null || e == null) continue; if (ext.equalsIgnoreCase (e)) { // same name + same extension => no return false; } } // no of the files has similar name and extension return true; } else { if (ext == null) { return fo.getFileObject(name) == null; } else { return fo.getFileObject(name, ext) == null; } } } // note: "sister" is preferred in English, please don't ask me why --jglick // NOI18N /** Finds brother file with same base name but different extension. * @param fo the file to find the brother for or <CODE>null</CODE> * @param ext extension for the brother file * @return a brother file (with the requested extension and the same parent folder as the original) or * <CODE>null</CODE> if the brother file does not exist or the original file was <CODE>null</CODE> */ public static FileObject findBrother (FileObject fo, String ext) { if (fo == null) return null; FileObject parent = fo.getParent (); if (parent == null) return null; return parent.getFileObject (fo.getName (), ext); } /** Obtain MIME type for a well-known extension. * If there is a case-sensitive match, that is used, else will fall back * to a case-insensitive match. * @param ext the extension: <code>"jar"</code>, <code>"zip"</code>, etc. * @return the MIME type for the extension, or <code>null</code> if the extension is unrecognized * @deprecated in favour of {@link #getMIMEType(FileObject) getMIMEType(FileObject)} as MIME cannot * be generaly detected by a file object extension. */ public static String getMIMEType (String ext) { String s = (String) map.get (ext); if (s != null) return s; else return (String) map.get (ext.toLowerCase ()); } /** Resolves MIME type. Registered resolvers are invoked and used to achieve this goal. * Resolvers must subclass MIMEResolver. If resolvers don`t recognize MIME type then * MIME type is obtained for a well-known extension. * @param fo whose MIME type should be recognized * @return the MIME type for the FileObject, or <code>null</code> if the FileObject is unrecognized */ public static String getMIMEType (FileObject fo) { String retVal = MIMESupport.findMIMEType(fo); if (retVal == null) retVal = getMIMEType (fo.getExt ()); return retVal; } /* mapping of file extensions to content-types */ private static java.util.Dictionary map = new java.util.Hashtable(); /** * Register MIME type for a new extension. * Note that you may register a case-sensitive extension if that is * relevant (for example <samp>*.C</samp> for C++) but if you register * a lowercase extension it will by default apply to uppercase extensions * too (for use on Windows or generally for situations where filenames * become accidentally upcased). * @param ext the file extension (should be lowercase unless you specifically care about case) * @param mimeType the new MIME type * @throws IllegalArgumentException if this extension was already registered with a <em>different</em> MIME type * @see #getMIMEType * @deprecated You should instead use the more general {@link MIMEResolver} system. */ public static void setMIMEType(String ext, String mimeType) { synchronized (map) { String old=(String)map.get(ext); if (old == null) { map.put(ext, mimeType); } else { if (!old.equals(mimeType)) throw new IllegalArgumentException ("Cannot overwrite existing MIME type mapping for extension `" + // NOI18N ext + "' with " + mimeType + " (was " + old + ")"); // NOI18N // else do nothing } } } static { setMIMEType("", "content/unknown"); // NOI18N setMIMEType("uu", "application/octet-stream"); // NOI18N setMIMEType("exe", "application/octet-stream"); // NOI18N setMIMEType("ps", "application/postscript"); // NOI18N setMIMEType("zip", "application/zip"); // NOI18N setMIMEType("class", "application/octet-stream"); // Sun uses application/java-vm // NOI18N setMIMEType("jar", "application/x-jar"); // NOI18N setMIMEType("sh", "application/x-shar"); // NOI18N setMIMEType("tar", "application/x-tar"); // NOI18N setMIMEType("snd", "audio/basic"); // NOI18N setMIMEType("au", "audio/basic"); // NOI18N setMIMEType("wav", "audio/x-wav"); // NOI18N setMIMEType("gif", "image/gif"); // NOI18N setMIMEType("jpg", "image/jpeg"); // NOI18N setMIMEType("jpeg", "image/jpeg"); // NOI18N setMIMEType("htm", "text/html"); // NOI18N setMIMEType("html", "text/html"); // NOI18N setMIMEType("xml", "text/xml"); // NOI18N setMIMEType("xsl", "text/xml"); // NOI18N setMIMEType("xsd", "text/xml"); // NOI18N setMIMEType("dtd", "text/x-dtd"); // NOI18N setMIMEType("css", "text/css"); // NOI18N setMIMEType("text", "text/plain"); // NOI18N setMIMEType("pl", "text/plain"); // NOI18N setMIMEType("txt", "text/plain"); // NOI18N setMIMEType("properties", "text/plain"); // NOI18N setMIMEType("java", "text/x-java"); // NOI18N // mime types from Jetty web server setMIMEType("ra", "audio/x-pn-realaudio"); // NOI18N setMIMEType("ram", "audio/x-pn-realaudio"); // NOI18N setMIMEType("rm", "audio/x-pn-realaudio"); // NOI18N setMIMEType("rpm", "audio/x-pn-realaudio"); // NOI18N setMIMEType("mov", "video/quicktime"); // NOI18N setMIMEType("jsp", "text/plain"); // NOI18N } /** * Construct a stream handler that handles the <code>nbfs</code> URL protocol * used for accessing file objects directly. * This method is not intended for module use; only the core * should need to call it. * Modules probably need only use {@link URLMapper} to create and decode such * URLs. * @since 3.17 */ public static URLStreamHandler nbfsURLStreamHandler() { return FileURL.HANDLER; } /** Recursively checks whether the file is underneath the folder. It checks whether * the file and folder are located on the same filesystem, in such case it checks the * parent <code>FileObject</code> of the file recursively untill the folder is found * or the root of the filesystem is reached. * * @param folder the root of folders hierarchy to search in * @param fo the file to search for * @return <code>true</code>, if <code>fo</code> lies somewhere underneath the <code>folder</code>, * <code>false</code> otherwise * @since 3.16 */ public static boolean isParentOf (FileObject folder, FileObject fo) { if (folder.isData ()) { return false; } try { if (folder.getFileSystem () != fo.getFileSystem ()) { return false; } } catch (FileStateInvalidException e) { return false; } FileObject parent = fo.getParent (); while (parent != null) { if (parent == folder) { return true; } parent = parent.getParent (); } return false; } }