0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.openide.filesystems;
0043:
0044: import java.io.File;
0045: import java.io.FileFilter;
0046: import java.io.FilenameFilter;
0047: import java.io.IOException;
0048: import java.io.InputStream;
0049: import java.io.OutputStream;
0050: import java.io.SyncFailedException;
0051: import java.net.MalformedURLException;
0052: import java.net.URI;
0053: import java.net.URL;
0054: import java.net.URLStreamHandler;
0055: import java.util.Arrays;
0056: import java.util.Collection;
0057: import java.util.Dictionary;
0058: import java.util.Enumeration;
0059: import java.util.HashMap;
0060: import java.util.HashSet;
0061: import java.util.Hashtable;
0062: import java.util.Iterator;
0063: import java.util.List;
0064: import java.util.Map;
0065: import java.util.Set;
0066: import java.util.Stack;
0067: import java.util.StringTokenizer;
0068: import java.util.WeakHashMap;
0069: import java.util.jar.JarEntry;
0070: import java.util.jar.JarInputStream;
0071: import java.util.logging.Level;
0072: import java.util.logging.Logger;
0073: import javax.swing.Icon;
0074: import javax.swing.JFileChooser;
0075: import javax.swing.SwingUtilities;
0076: import javax.swing.filechooser.FileSystemView;
0077: import org.openide.filesystems.FileSystem.AtomicAction;
0078: import org.openide.util.Exceptions;
0079: import org.openide.util.Lookup;
0080: import org.openide.util.NbBundle;
0081: import org.openide.util.Utilities;
0082: import org.openide.util.WeakListeners;
0083:
0084: /** Common utilities for handling files.
0085: * This is a dummy class; all methods are static.
0086: */
0087: public final class FileUtil extends Object {
0088:
0089: /** Normal header for ZIP files. */
0090: private static byte[] ZIP_HEADER_1 = { 0x50, 0x4b, 0x03, 0x04 };
0091: /** Also seems to be used at least in apisupport/project/test/unit/data/example-external-projects/suite3/nbplatform/random/modules/ext/stuff.jar; not known why */
0092: private static byte[] ZIP_HEADER_2 = { 0x50, 0x4b, 0x05, 0x06 };
0093:
0094: /** transient attributes which should not be copied
0095: * of type Set<String>
0096: */
0097: static final Set<String> transientAttributes = new HashSet<String>();
0098:
0099: static {
0100: transientAttributes.add("templateWizardURL"); // NOI18N
0101: transientAttributes.add("templateWizardIterator"); // NOI18N
0102: transientAttributes.add("templateWizardDescResource"); // NOI18N
0103: transientAttributes.add("templateCategory"); // NOI18N
0104: transientAttributes.add("instantiatingIterator"); // NOI18N
0105: transientAttributes.add("instantiatingWizardURL"); // NOI18N
0106: transientAttributes.add("SystemFileSystem.localizingBundle"); // NOI18N
0107: transientAttributes.add("SystemFileSystem.icon"); // NOI18N
0108: transientAttributes.add("SystemFileSystem.icon32"); // NOI18N
0109: transientAttributes.add("position"); // NOI18N
0110: }
0111:
0112: /* mapping of file extensions to content-types */
0113: private static Dictionary<String, String> map = new Hashtable<String, String>();
0114:
0115: static {
0116: // Set up at least this one statically, because it is so basic;
0117: // we do not want to rely on Lookup, MIMEResolver, declarative resolvers,
0118: // XML layers, etc. just to find this.
0119: setMIMEType("xml", "text/xml"); // NOI18N
0120: }
0121:
0122: /** Cache for {@link #isArchiveFile(FileObject)}. */
0123: private static final Map<FileObject, Boolean> archiveFileCache = new WeakHashMap<FileObject, Boolean>();
0124: private static FileSystem diskFileSystem;
0125:
0126: private static FileSystem getDiskFileSystemFor(File... files) {
0127: FileSystem fs = getDiskFileSystem();
0128: if (fs == null) {
0129: for (File file : files) {
0130: FileObject fo = toFileObject(file);
0131: fs = getDiskFileSystem();
0132: if (fs != null) {
0133: break;
0134: }
0135: }
0136: }
0137: return fs;
0138: }
0139:
0140: private FileUtil() {
0141: }
0142:
0143: /**
0144: * Refreshes all necessary filesystems. Not all instances of <code>FileObject</code> are refreshed
0145: * but just those that represent passed <code>files</code> and their children recursively.
0146: * @param files
0147: * @since 7.6
0148: */
0149: public static void refreshFor(File... files) {
0150: FileSystem fs = getDiskFileSystemFor(files);
0151: if (fs != null) {
0152: try {
0153: fs
0154: .getRoot()
0155: .setAttribute(
0156: "request_for_refreshing_files_be_aware_this_is_not_public_api",
0157: files);
0158: } catch (IOException ex) {
0159: Exceptions.printStackTrace(ex);
0160: }
0161: }
0162: }
0163:
0164: /**
0165: * Refreshes all <code>FileObject</code> that represent files <code>File.listRoots()</code>
0166: * and their children recursively.
0167: * @since 7.7
0168: */
0169: public static void refreshAll() {
0170: refreshFor(File.listRoots());
0171: }
0172:
0173: /**
0174: * Registers <code>listener</code> so that it will receive
0175: * <code>FileEvent</code>s from <code>FileSystem</code>s providing instances
0176: * of <code>FileObject</code> convertible to <code>java.io.File</code>.
0177: * @param fcl
0178: * @see #toFileObject
0179: * @since 7.7
0180: */
0181: public static void addFileChangeListener(FileChangeListener fcl) {
0182: FileSystem fs = getDiskFileSystem();
0183: if (fs == null) {
0184: fs = getDiskFileSystemFor(File.listRoots());
0185: }
0186: if (fs != null) {
0187: fs.addFileChangeListener(fcl);
0188: }
0189: }
0190:
0191: /**
0192: * Unregisters <code>listener</code> so that it will no longer receive
0193: * <code>FileEvent</code>s from <code>FileSystem</code>s providing instances
0194: * of <code>FileObject</code> convertible to <code>java.io.File</code>
0195: * @param fcl
0196: * @see #toFileObject
0197: * @since 7.7
0198: */
0199: public static void removeFileChangeListener(FileChangeListener fcl) {
0200: FileSystem fs = getDiskFileSystem();
0201: if (fs == null) {
0202: fs = getDiskFileSystemFor(File.listRoots());
0203: }
0204: if (fs != null) {
0205: fs.addFileChangeListener(fcl);
0206: }
0207: }
0208:
0209: /**
0210: * Executes atomic action. For more info see {@link FileSystem#runAtomicAction}.
0211: * <p>
0212: * All events about filesystem changes (related to events on all affected instances of <code>FileSystem</code>)
0213: * are postponed after the whole <code>atomicCode</code>
0214: * is executed.
0215: * </p>
0216: * @param atomicCode code that is supposed to be run as atomic action. See {@link FileSystem#runAtomicAction}
0217: * @throws java.io.IOException
0218: * @since 7.5
0219: */
0220: public static final void runAtomicAction(
0221: final AtomicAction atomicCode) throws IOException {
0222: Repository.getDefault().getDefaultFileSystem().runAtomicAction(
0223: atomicCode);
0224: }
0225:
0226: /**
0227: * Executes atomic action. For more info see {@link FileSystem#runAtomicAction}.
0228: * <p>
0229: * All events about filesystem changes (related to events on all affected instances of <code>FileSystem</code>)
0230: * are postponed after the whole <code>atomicCode</code>
0231: * is executed.
0232: * </p>
0233: * @param atomicCode code that is supposed to be run as atomic action. See {@link FileSystem#runAtomicAction}
0234: * @since 7.5
0235: */
0236: public static final void runAtomicAction(final Runnable atomicCode) {
0237: final AtomicAction action = new FileSystem.AtomicAction() {
0238: public void run() throws IOException {
0239: atomicCode.run();
0240: }
0241: };
0242: try {
0243: FileUtil.runAtomicAction(action);
0244: } catch (IOException ex) {
0245: Exceptions.printStackTrace(ex);
0246: }
0247: }
0248:
0249: /**
0250: * Returns FileObject for a folder.
0251: * If such a folder does not exist then it is created, including any necessary but nonexistent parent
0252: * folders. Note that if this operation fails it may have succeeded in creating some of the necessary
0253: * parent folders.
0254: * @param folder folder to be created
0255: * @return FileObject for a folder
0256: * @throws java.io.IOException if the creation fails
0257: * @since 7.0
0258: */
0259: public static FileObject createFolder(final File folder)
0260: throws IOException {
0261: File existingFolder = folder;
0262: while (existingFolder != null && !existingFolder.isDirectory()) {
0263: existingFolder = existingFolder.getParentFile();
0264: }
0265: if (existingFolder == null) {
0266: throw new IOException(folder.getAbsolutePath());
0267: }
0268:
0269: FileObject retval = null;
0270: FileObject folderFo = FileUtil.toFileObject(existingFolder);
0271: assert folderFo != null : existingFolder.getAbsolutePath();
0272: final String relativePath = getRelativePath(existingFolder,
0273: folder);
0274: try {
0275: retval = FileUtil.createFolder(folderFo, relativePath);
0276: } catch (IOException ex) {
0277: //thus retval = null;
0278: }
0279: //if refresh needed because of external changes
0280: if (retval == null || !retval.isValid()) {
0281: folderFo.getFileSystem().refresh(false);
0282: retval = FileUtil.createFolder(folderFo, relativePath);
0283: }
0284: assert retval != null;
0285: return retval;
0286: }
0287:
0288: /**Returns FileObject for a data file.
0289: * If such a data file does not exist then it is created, including any necessary but nonexistent parent
0290: * folders. Note that if this operation fails it may have succeeded in creating some of the necessary
0291: * parent folders.
0292: * @param data data file to be created
0293: * @return FileObject for a data file
0294: * @throws java.io.IOException if the creation fails
0295: * @since 7.0
0296: */
0297: public static FileObject createData(final File data)
0298: throws IOException {
0299: File folder = data;
0300: while (folder != null && !folder.isDirectory()) {
0301: folder = folder.getParentFile();
0302: }
0303: if (folder == null) {
0304: throw new IOException(data.getAbsolutePath());
0305: }
0306:
0307: FileObject retval = null;
0308: FileObject folderFo = FileUtil.toFileObject(folder);
0309: assert folderFo != null : folder.getAbsolutePath();
0310: final String relativePath = getRelativePath(folder, data);
0311: try {
0312: retval = FileUtil.createData(folderFo, relativePath);
0313: } catch (IOException ex) {
0314: //thus retval = null;
0315: }
0316: //if refresh needed because of external changes
0317: if (retval == null || !retval.isValid()) {
0318: folderFo.getFileSystem().refresh(false);
0319: retval = FileUtil.createData(folderFo, relativePath);
0320: }
0321: assert retval != null;
0322: return retval;
0323: }
0324:
0325: private static File getRoot(final File dir) {
0326: File retval = dir;
0327: for (; retval.getParentFile() != null; retval = retval
0328: .getParentFile())
0329: ;
0330: assert retval != null;
0331: return retval;
0332: }
0333:
0334: private static String getRelativePath(final File dir,
0335: final File file) {
0336: Stack<String> stack = new Stack<String>();
0337: File tempFile = file;
0338: while (tempFile != null && !tempFile.equals(dir)) {
0339: stack.push(tempFile.getName());
0340: tempFile = tempFile.getParentFile();
0341: }
0342: assert tempFile != null : file.getAbsolutePath()
0343: + "not found in " + dir.getAbsolutePath();//NOI18N
0344: StringBuilder retval = new StringBuilder();
0345: while (!stack.isEmpty()) {
0346: retval.append(stack.pop());
0347: if (!stack.isEmpty()) {
0348: retval.append('/');//NOI18N
0349: }
0350: }
0351: return retval.toString();
0352: }
0353:
0354: /** Copies stream of files.
0355: * <P>
0356: * Please be aware, that this method doesn't close any of passed streams.
0357: * @param is input stream
0358: * @param os output stream
0359: */
0360: public static void copy(InputStream is, OutputStream os)
0361: throws IOException {
0362: final byte[] BUFFER = new byte[4096];
0363: int len;
0364:
0365: for (;;) {
0366: len = is.read(BUFFER);
0367:
0368: if (len == -1) {
0369: return;
0370: }
0371:
0372: os.write(BUFFER, 0, len);
0373: }
0374: }
0375:
0376: /** Copies file to the selected folder.
0377: * This implementation simply copies the file by stream content.
0378: * @param source source file object
0379: * @param destFolder destination folder
0380: * @param newName file name (without extension) of destination file
0381: * @param newExt extension of destination file
0382: * @return the created file object in the destination folder
0383: * @exception IOException if <code>destFolder</code> is not a folder or does not exist; the destination file already exists; or
0384: * another critical error occurs during copying
0385: */
0386: static FileObject copyFileImpl(FileObject source,
0387: FileObject destFolder, String newName, String newExt)
0388: throws IOException {
0389: FileObject dest = destFolder.createData(newName, newExt);
0390:
0391: FileLock lock = null;
0392: InputStream bufIn = null;
0393: OutputStream bufOut = null;
0394:
0395: try {
0396: lock = dest.lock();
0397: bufIn = source.getInputStream();
0398:
0399: if (dest instanceof AbstractFileObject) {
0400: /** prevents from firing fileChange*/
0401: bufOut = ((AbstractFileObject) dest).getOutputStream(
0402: lock, false);
0403: } else {
0404: bufOut = dest.getOutputStream(lock);
0405: }
0406:
0407: copy(bufIn, bufOut);
0408: copyAttributes(source, dest);
0409: } finally {
0410: if (bufIn != null) {
0411: bufIn.close();
0412: }
0413:
0414: if (bufOut != null) {
0415: bufOut.close();
0416: }
0417:
0418: if (lock != null) {
0419: lock.releaseLock();
0420: }
0421: }
0422:
0423: return dest;
0424: }
0425:
0426: //
0427: // public methods
0428: //
0429:
0430: /** Factory method that creates an empty implementation of a filesystem that
0431: * completely resides in a memory.
0432: * @return a blank writable filesystem
0433: * @since 4.43
0434: */
0435: public static FileSystem createMemoryFileSystem() {
0436: return new MemoryFileSystem();
0437: }
0438:
0439: /** Copies file to the selected folder.
0440: * This implementation simply copies the file by stream content.
0441: * @param source source file object
0442: * @param destFolder destination folder
0443: * @param newName file name (without extension) of destination file
0444: * @param newExt extension of destination file
0445: * @return the created file object in the destination folder
0446: * @exception IOException if <code>destFolder</code> is not a folder or does not exist; the destination file already exists; or
0447: * another critical error occurs during copying
0448: */
0449: public static FileObject copyFile(FileObject source,
0450: FileObject destFolder, String newName, String newExt)
0451: throws IOException {
0452: return source.copy(destFolder, newName, newExt);
0453: }
0454:
0455: /** Copies file to the selected folder.
0456: * This implementation simply copies the file by stream content.
0457: * Uses the extension of the source file.
0458: * @param source source file object
0459: * @param destFolder destination folder
0460: * @param newName file name (without extension) of destination file
0461: * @return the created file object in the destination folder
0462: * @exception IOException if <code>destFolder</code> is not a folder or does not exist; the destination file already exists; or
0463: * another critical error occurs during copying
0464: */
0465: public static FileObject copyFile(FileObject source,
0466: FileObject destFolder, String newName) throws IOException {
0467: return copyFile(source, destFolder, newName, source.getExt());
0468: }
0469:
0470: /** Moves file to the selected folder.
0471: * This implementation uses a copy-and-delete mechanism, and automatically uses the necessary lock.
0472: * @param source source file object
0473: * @param destFolder destination folder
0474: * @param newName file name (without extension) of destination file
0475: * @return new file object
0476: * @exception IOException if either the {@link #copyFile copy} or {@link FileObject#delete delete} failed
0477: */
0478: public static FileObject moveFile(FileObject source,
0479: FileObject destFolder, String newName) throws IOException {
0480: FileLock lock = null;
0481:
0482: try {
0483: lock = source.lock();
0484:
0485: return source.move(lock, destFolder, newName, source
0486: .getExt());
0487: } finally {
0488: if (lock != null) {
0489: lock.releaseLock();
0490: }
0491: }
0492: }
0493:
0494: /** Returns a folder on given filesystem if such a folder exists.
0495: * If not then a folder is created, including any necessary but nonexistent parent
0496: * folders. Note that if this operation fails it may have succeeded in creating some of the necessary
0497: * parent folders.
0498: * The name of the new folder can be
0499: * specified as a multi-component pathname whose components are separated
0500: * by File.separatorChar or "/" (forward slash).
0501: *
0502: * @param folder where the new folder will be placed in
0503: * @param name name of the new folder
0504: * @return the new folder
0505: * @exception IOException if the creation fails
0506: */
0507: public static FileObject createFolder(FileObject folder, String name)
0508: throws IOException {
0509: String separators;
0510:
0511: if (File.separatorChar != '/') {
0512: separators = "/" + File.separatorChar; // NOI18N
0513: } else {
0514: separators = "/"; // NOI18N
0515: }
0516:
0517: StringTokenizer st = new StringTokenizer(name, separators);
0518:
0519: while (st.hasMoreElements()) {
0520: name = st.nextToken();
0521:
0522: if (name.length() > 0) {
0523: FileObject f = folder.getFileObject(name);
0524:
0525: if (f == null) {
0526: try {
0527: f = folder.createFolder(name);
0528: } catch (SyncFailedException ex) {
0529: // there might be unconsistency between the cache
0530: // and the disk, that is why
0531: folder.refresh();
0532:
0533: // and try again
0534: f = folder.getFileObject(name);
0535:
0536: if (f == null) {
0537: // if still not found than we have to report the
0538: // exception
0539: throw ex;
0540: }
0541: }
0542: }
0543:
0544: folder = f;
0545: }
0546: }
0547:
0548: return folder;
0549: }
0550:
0551: /** Returns a data file on given filesystem if such a data file exists.
0552: * If not then a data file is created, including any necessary but nonexistent parent
0553: * folders. Note that if this operation fails it may have succeeded in creating some of the necessary
0554: * parent folders. The name of
0555: * data file can be composed as resource name (e. g. org/netbeans/myfolder/mydata ).
0556: *
0557: * @param folder to begin with creation at
0558: * @param name name of data file as a resource
0559: * @return the data file for given name
0560: * @exception IOException if the creation fails
0561: */
0562: public static FileObject createData(FileObject folder, String name)
0563: throws IOException {
0564: if (folder == null) {
0565: throw new IllegalArgumentException("Null folder"); // NOI18N
0566: }
0567:
0568: if (name == null) {
0569: throw new IllegalArgumentException("Null name"); // NOI18N
0570: }
0571:
0572: String foldername;
0573: String dataname;
0574: String fname;
0575: String ext;
0576: int index = name.lastIndexOf('/');
0577: FileObject data;
0578:
0579: // names with '/' on the end are not valid
0580: if (index >= name.length()) {
0581: throw new IOException("Wrong file name."); // NOI18N
0582: }
0583:
0584: // if name contains '/', create necessary folder first
0585: if (index != -1) {
0586: foldername = name.substring(0, index);
0587: dataname = name.substring(index + 1);
0588: folder = createFolder(folder, foldername);
0589: assert folder != null;
0590: } else {
0591: dataname = name;
0592: }
0593:
0594: // create data
0595: index = dataname.lastIndexOf('.');
0596:
0597: if (index != -1) {
0598: fname = dataname.substring(0, index);
0599: ext = dataname.substring(index + 1);
0600: } else {
0601: fname = dataname;
0602: ext = ""; // NOI18N
0603: }
0604:
0605: data = folder.getFileObject(fname, ext);
0606:
0607: if (data == null) {
0608: try {
0609: data = folder.createData(fname, ext);
0610: assert data != null : "FileObject.createData cannot return null; called on "
0611: + folder + " + " + fname + " + " + ext; // #50802
0612: } catch (SyncFailedException ex) {
0613: // there might be unconsistency between the cache
0614: // and the disk, that is why
0615: folder.refresh();
0616:
0617: // and try again
0618: data = folder.getFileObject(fname, ext);
0619:
0620: if (data == null) {
0621: // if still not found than we have to report the
0622: // exception
0623: throw ex;
0624: }
0625: }
0626: }
0627:
0628: return data;
0629: }
0630:
0631: /** Finds appropriate java.io.File to FileObject if possible.
0632: * If not possible then null is returned.
0633: * This is the inverse operation of {@link #toFileObject}.
0634: * @param fo FileObject whose corresponding File will be looked for
0635: * @return java.io.File or null if no corresponding File exists.
0636: * @since 1.29
0637: */
0638: public static File toFile(FileObject fo) {
0639: File retVal = (File) fo.getAttribute("java.io.File"); // NOI18N;
0640:
0641: if (retVal == null) {
0642: URL fileURL = null;
0643: int[] types = new int[] { URLMapper.INTERNAL,
0644: URLMapper.EXTERNAL };
0645:
0646: for (int i = 0; ((fileURL == null) || "file".equals(fileURL
0647: .getProtocol()))
0648: && (i < types.length); i++) { // NOI18N
0649: fileURL = URLMapper.findURL(fo, types[i]);
0650: }
0651:
0652: if ((fileURL != null)
0653: && "file".equals(fileURL.getProtocol())) {
0654: retVal = new File(URI.create(fileURL.toExternalForm()));
0655: }
0656: }
0657:
0658: return (retVal != null) ? normalizeFile(retVal) : null;
0659: }
0660:
0661: /**
0662: * Converts a disk file to a matching file object.
0663: * This is the inverse operation of {@link #toFile}.
0664: * <p class="nonnormative">
0665: * If you are running with the MasterFS module enabled, that will guarantee
0666: * that this method never returns null for a file which exists on disk.
0667: * </p>
0668: * @param file a disk file (may or may not exist). This file
0669: * must be {@link #normalizeFile normalized}.
0670: * @return a corresponding file object, or null if the file does not exist
0671: * or there is no {@link URLMapper} available to convert it
0672: * @since 4.29
0673: */
0674: public static FileObject toFileObject(File file) {
0675: boolean asserts = false;
0676: assert asserts = true;
0677: if (asserts) {
0678: File normFile = normalizeFile(file);
0679: if (!file.equals(normFile)) {
0680: Logger.getLogger(FileUtil.class.getName()).log(
0681: Level.WARNING,
0682: null,
0683: new IllegalArgumentException(
0684: "Parameter file was not "
0685: + // NOI18N
0686: "normalized. Was " + file
0687: + " instead of " + normFile));
0688: }
0689: file = normFile;
0690: }
0691:
0692: FileObject retVal = null;
0693:
0694: try {
0695: URL url = fileToURL(file);
0696:
0697: if ((url.getAuthority() != null)
0698: && (Utilities.isWindows() || (Utilities
0699: .getOperatingSystem() == Utilities.OS_OS2))) {
0700: return null;
0701: }
0702:
0703: retVal = URLMapper.findFileObject(url);
0704:
0705: /*probably temporary piece of code to catch the cause of #46630*/
0706: } catch (MalformedURLException e) {
0707: retVal = null;
0708: }
0709:
0710: if (retVal != null) {
0711: if (getDiskFileSystem() == null) {
0712: try {
0713: FileSystem fs = retVal.getFileSystem();
0714: setDiskFileSystem(fs);
0715: } catch (FileStateInvalidException ex) {
0716: Exceptions.printStackTrace(ex);
0717: }
0718: }
0719: }
0720: return retVal;
0721: }
0722:
0723: static URL fileToURL(File file) throws MalformedURLException {
0724: URL retVal = null;
0725:
0726: if (!Utilities.isWindows() || canBeCanonicalizedOnWindows(file)) {
0727: retVal = file.toURI().toURL();
0728: } else {
0729: if (Utilities.isWindows() && file.getParentFile() == null) {
0730: retVal = new URL("file:/"
0731: + file.getAbsolutePath().toUpperCase()); //NOI18N
0732: } else {
0733: retVal = new URL("file:/" + file.getAbsolutePath()); //NOI18N
0734: }
0735: }
0736:
0737: return retVal;
0738: }
0739:
0740: /** Finds appropriate FileObjects to java.io.File if possible.
0741: * If not possible then empty array is returned. More FileObjects may
0742: * correspond to one java.io.File that`s why array is returned.
0743: * @param file File whose corresponding FileObjects will be looked for.
0744: * The file has to be "normalized" otherwise IllegalArgumentException is thrown.
0745: * See {@link #normalizeFile} for how to do that.
0746: * @return corresponding FileObjects or empty array if no
0747: * corresponding FileObject exists.
0748: * @since 1.29
0749: * @deprecated Use {@link #toFileObject} instead.
0750: */
0751: @Deprecated
0752: public static FileObject[] fromFile(File file) {
0753: FileObject[] retVal;
0754:
0755: if (!file.equals(normalizeFile(file))) {
0756: throw new IllegalArgumentException(
0757: "Parameter file was not "
0758: + // NOI18N
0759: "normalized. Was " + file + " instead of "
0760: + normalizeFile(file)); // NOI18N
0761: }
0762:
0763: try {
0764: URL url = (file.toURI().toURL());
0765:
0766: if ((url.getAuthority() != null)
0767: && (Utilities.isWindows() || (Utilities
0768: .getOperatingSystem() == Utilities.OS_OS2))) {
0769: return null;
0770: }
0771:
0772: retVal = URLMapper.findFileObjects(url);
0773: } catch (MalformedURLException e) {
0774: retVal = null;
0775: }
0776:
0777: return retVal;
0778: }
0779:
0780: /** Copies attributes from one file to another.
0781: * Note: several special attributes will not be copied, as they should
0782: * semantically be transient. These include attributes used by the
0783: * template wizard (but not the template attribute itself).
0784: * @param source source file object
0785: * @param dest destination file object
0786: * @exception IOException if the copying failed
0787: */
0788: public static void copyAttributes(FileObject source, FileObject dest)
0789: throws IOException {
0790: Enumeration<String> attrKeys = source.getAttributes();
0791:
0792: while (attrKeys.hasMoreElements()) {
0793: String key = attrKeys.nextElement();
0794:
0795: if (transientAttributes.contains(key)) {
0796: continue;
0797: }
0798:
0799: if (isTransient(source, key)) {
0800: continue;
0801: }
0802:
0803: Object value = source.getAttribute(key);
0804:
0805: if (value != null) {
0806: dest.setAttribute(key, value);
0807: }
0808: }
0809: }
0810:
0811: static boolean isTransient(FileObject fo, String attrName) {
0812: return XMLMapAttr.ModifiedAttribute.isTransient(fo, attrName);
0813: }
0814:
0815: /** Extract jar file into folder represented by file object. If the JAR contains
0816: * files with name filesystem.attributes, it is assumed that these files
0817: * has been created by DefaultAttributes implementation and the content
0818: * of these files is treated as attributes and added to extracted files.
0819: * <p><code>META-INF/</code> directories are skipped over.
0820: *
0821: * @param fo file object of destination folder
0822: * @param is input stream of jar file
0823: * @exception IOException if the extraction fails
0824: * @deprecated Use of XML filesystem layers generally obsoletes this method.
0825: */
0826: @Deprecated
0827: public static void extractJar(final FileObject fo,
0828: final InputStream is) throws IOException {
0829: FileSystem fs = fo.getFileSystem();
0830:
0831: fs.runAtomicAction(new FileSystem.AtomicAction() {
0832: public void run() throws IOException {
0833: extractJarImpl(fo, is);
0834: }
0835: });
0836: }
0837:
0838: /** Does the actual extraction of the Jar file.
0839: */
0840: private static void extractJarImpl(FileObject fo, InputStream is)
0841: throws IOException {
0842: JarInputStream jis;
0843: JarEntry je;
0844:
0845: // files with extended attributes (name, DefaultAttributes.Table)
0846: HashMap<String, DefaultAttributes.Table> attributes = new HashMap<String, DefaultAttributes.Table>(
0847: 7);
0848:
0849: jis = new JarInputStream(is);
0850:
0851: while ((je = jis.getNextJarEntry()) != null) {
0852: String name = je.getName();
0853:
0854: if (name.toLowerCase().startsWith("meta-inf/")) {
0855: continue; // NOI18N
0856: }
0857:
0858: if (je.isDirectory()) {
0859: createFolder(fo, name);
0860:
0861: continue;
0862: }
0863:
0864: if (DefaultAttributes.acceptName(name)) {
0865: // file with extended attributes
0866: DefaultAttributes.Table table = DefaultAttributes
0867: .loadTable(jis, name);
0868: attributes.put(name, table);
0869: } else {
0870: // copy the file
0871: FileObject fd = createData(fo, name);
0872: FileLock lock = fd.lock();
0873:
0874: try {
0875: OutputStream os = fd.getOutputStream(lock);
0876:
0877: try {
0878: copy(jis, os);
0879: } finally {
0880: os.close();
0881: }
0882: } finally {
0883: lock.releaseLock();
0884: }
0885: }
0886: }
0887:
0888: //
0889: // apply all extended attributes
0890: //
0891: Iterator it = attributes.entrySet().iterator();
0892:
0893: while (it.hasNext()) {
0894: Map.Entry entry = (Map.Entry) it.next();
0895:
0896: String fileName = (String) entry.getKey();
0897: int last = fileName.lastIndexOf('/');
0898: String dirName;
0899:
0900: if (last != -1) {
0901: dirName = fileName.substring(0, last + 1);
0902: } else {
0903: dirName = ""; // NOI18N
0904: }
0905:
0906: String prefix = fo.isRoot() ? dirName
0907: : (fo.getPath() + '/' + dirName);
0908:
0909: DefaultAttributes.Table t = (DefaultAttributes.Table) entry
0910: .getValue();
0911: Iterator files = t.keySet().iterator();
0912:
0913: while (files.hasNext()) {
0914: String orig = (String) files.next();
0915: String fn = prefix + orig;
0916: FileObject obj = fo.getFileSystem().findResource(fn);
0917:
0918: if (obj == null) {
0919: continue;
0920: }
0921:
0922: Enumeration<String> attrEnum = t.attrs(orig);
0923:
0924: while (attrEnum.hasMoreElements()) {
0925: // iterate thru all arguments
0926: String attrName = attrEnum.nextElement();
0927:
0928: // Note: even transient attributes set here!
0929: Object value = t.getAttr(orig, attrName);
0930:
0931: if (value != null) {
0932: obj.setAttribute(attrName, value);
0933: }
0934: }
0935: }
0936: }
0937: }
0938:
0939: // extractJar
0940:
0941: /** Gets the extension of a specified file name. The extension is
0942: * everything after the last dot.
0943: *
0944: * @param fileName name of the file
0945: * @return extension of the file (or <code>""</code> if it had none)
0946: */
0947: public static String getExtension(String fileName) {
0948: int index = fileName.lastIndexOf("."); // NOI18N
0949:
0950: if (index == -1) {
0951: return ""; // NOI18N
0952: } else {
0953: return fileName.substring(index + 1);
0954: }
0955: }
0956:
0957: /** Finds an unused file name similar to that requested in the same folder.
0958: * The specified file name is used if that does not yet exist or is
0959: * {@link FileObject#isVirtual isVirtual}.
0960: * Otherwise, the first available name of the form <code>basename_nnn.ext</code> (counting from one) is used.
0961: *
0962: * <p><em>Caution:</em> this method does not lock the parent folder
0963: * to prevent race conditions: i.e. it is possible (though unlikely)
0964: * that the resulting name will have been created by another thread
0965: * just as you were about to create the file yourself (if you are,
0966: * in fact, intending to create it just after this call). Since you
0967: * cannot currently lock a folder against child creation actions,
0968: * the safe approach is to use a loop in which a free name is
0969: * retrieved; an attempt is made to {@link FileObject#createData create}
0970: * that file; and upon an <code>IOException</code> during
0971: * creation, retry the loop up to a few times before giving up.
0972: *
0973: * @param folder parent folder
0974: * @param name preferred base name of file
0975: * @param ext extension to use
0976: * @return a free file name <strong>(without the extension)</strong>
0977: */
0978: public static String findFreeFileName(FileObject folder,
0979: String name, String ext) {
0980: if (checkFreeName(folder, name, ext)) {
0981: return name;
0982: }
0983:
0984: for (int i = 1;; i++) {
0985: String destName = name + "_" + i; // NOI18N
0986:
0987: if (checkFreeName(folder, destName, ext)) {
0988: return destName;
0989: }
0990: }
0991: }
0992:
0993: /** Finds an unused folder name similar to that requested in the same parent folder.
0994: * <p>See caveat for <code>findFreeFileName</code>.
0995: * @see #findFreeFileName findFreeFileName
0996: * @param folder parent folder
0997: * @param name preferred folder name
0998: * @return a free folder name
0999: */
1000: public static String findFreeFolderName(FileObject folder,
1001: String name) {
1002: if (checkFreeName(folder, name, null)) {
1003: return name;
1004: }
1005:
1006: for (int i = 1;; i++) {
1007: String destName = name + "_" + i; // NOI18N
1008:
1009: if (checkFreeName(folder, destName, null)) {
1010: return destName;
1011: }
1012: }
1013: }
1014:
1015: /**
1016: * Gets a relative resource path between folder and fo.
1017: * @param folder root of filesystem or any other folder in folders hierarchy
1018: * @param fo arbitrary FileObject in folder's tree (including folder itself)
1019: * @return relative path between folder and fo. The returned path never
1020: * starts with a '/'. It never ends with a '/'. Specifically, if
1021: * folder==fo, returns "". Returns <code>null</code> if fo is not in
1022: * folder's tree.
1023: * @see #isParentOf
1024: * @since 4.16
1025: */
1026: public static String getRelativePath(FileObject folder,
1027: FileObject fo) {
1028: if (!isParentOf(folder, fo) && (folder != fo)) {
1029: return null;
1030: }
1031:
1032: String result = fo.getPath().substring(
1033: folder.getPath().length());
1034:
1035: if (result.startsWith("/")) {
1036: result = result.substring(1);
1037: }
1038:
1039: return result;
1040: }
1041:
1042: /** Test if given name is free in given folder.
1043: * @param fo folder to check in
1044: * @param name name of the file or folder to check
1045: * @param ext extension of the file (null for folders)
1046: * @return true, if such name does not exists
1047: */
1048: private static boolean checkFreeName(FileObject fo, String name,
1049: String ext) {
1050: if ((Utilities.isWindows() || (Utilities.getOperatingSystem() == Utilities.OS_OS2))
1051: || Utilities.isMac()) {
1052: // case-insensitive, do some special check
1053: Enumeration<? extends FileObject> en = fo
1054: .getChildren(false);
1055:
1056: while (en.hasMoreElements()) {
1057: fo = en.nextElement();
1058:
1059: String n = fo.getName();
1060: String e = fo.getExt();
1061:
1062: // different names => check others
1063: if (!n.equalsIgnoreCase(name)) {
1064: continue;
1065: }
1066:
1067: // same name + without extension => no
1068: if (((ext == null) || (ext.trim().length() == 0))
1069: && ((e == null) || (e.trim().length() == 0))) {
1070: return fo.isVirtual();
1071: }
1072:
1073: // one of there is witout extension => check next
1074: if ((ext == null) || (e == null)) {
1075: continue;
1076: }
1077:
1078: if (ext.equalsIgnoreCase(e)) {
1079: // same name + same extension => no
1080: return fo.isVirtual();
1081: }
1082: }
1083:
1084: // no of the files has similar name and extension
1085: return true;
1086: } else {
1087: if (ext == null) {
1088: fo = fo.getFileObject(name);
1089:
1090: if (fo == null) {
1091: return true;
1092: }
1093:
1094: return fo.isVirtual();
1095: } else {
1096: fo = fo.getFileObject(name, ext);
1097:
1098: if (fo == null) {
1099: return true;
1100: }
1101:
1102: return fo.isVirtual();
1103: }
1104: }
1105: }
1106:
1107: // note: "sister" is preferred in English, please don't ask me why --jglick // NOI18N
1108:
1109: /** Finds brother file with same base name but different extension.
1110: * @param fo the file to find the brother for or <CODE>null</CODE>
1111: * @param ext extension for the brother file
1112: * @return a brother file (with the requested extension and the same parent folder as the original) or
1113: * <CODE>null</CODE> if the brother file does not exist or the original file was <CODE>null</CODE>
1114: */
1115: public static FileObject findBrother(FileObject fo, String ext) {
1116: if (fo == null) {
1117: return null;
1118: }
1119:
1120: FileObject parent = fo.getParent();
1121:
1122: if (parent == null) {
1123: return null;
1124: }
1125:
1126: return parent.getFileObject(fo.getName(), ext);
1127: }
1128:
1129: /** Obtain MIME type for a well-known extension.
1130: * If there is a case-sensitive match, that is used, else will fall back
1131: * to a case-insensitive match.
1132: * @param ext the extension: <code>"jar"</code>, <code>"zip"</code>, etc.
1133: * @return the MIME type for the extension, or <code>null</code> if the extension is unrecognized
1134: * @deprecated use {@link #getMIMEType(FileObject) getMIMEType(FileObject)} as MIME cannot
1135: * be generally detected by file object extension.
1136: */
1137: @Deprecated
1138: public static String getMIMEType(String ext) {
1139: String s = map.get(ext);
1140:
1141: if (s != null) {
1142: return s;
1143: } else {
1144: return map.get(ext.toLowerCase());
1145: }
1146: }
1147:
1148: /** Resolves MIME type. Registered resolvers are invoked and used to achieve this goal.
1149: * Resolvers must subclass MIMEResolver. If resolvers don`t recognize MIME type then
1150: * MIME type is obtained for a well-known extension.
1151: * @param fo whose MIME type should be recognized
1152: * @return the MIME type for the FileObject, or <code>null</code> if the FileObject is unrecognized
1153: */
1154: public static String getMIMEType(FileObject fo) {
1155: String retVal = MIMESupport.findMIMEType(fo, null);
1156:
1157: if (retVal == null) {
1158: retVal = getMIMEType(fo.getExt());
1159: }
1160:
1161: return retVal;
1162: }
1163:
1164: /** Finds mime type by calling getMIMEType, but
1165: * instead of returning null it fallbacks to default type
1166: * either text/plain or content/unknown (even for folders)
1167: */
1168: static String getMIMETypeOrDefault(FileObject fo) {
1169: String def = getMIMEType(fo.getExt());
1170: String t = MIMESupport.findMIMEType(fo, def);
1171:
1172: if (t == null) {
1173: // #42965: never allowed
1174: t = "content/unknown"; // NOI18N
1175: }
1176:
1177: return t;
1178: }
1179:
1180: /**
1181: * Register MIME type for a new extension.
1182: * Note that you may register a case-sensitive extension if that is
1183: * relevant (for example <samp>*.C</samp> for C++) but if you register
1184: * a lowercase extension it will by default apply to uppercase extensions
1185: * too (for use on Windows or generally for situations where filenames
1186: * become accidentally uppercase).
1187: * @param ext the file extension (should be lowercase unless you specifically care about case)
1188: * @param mimeType the new MIME type
1189: * @throws IllegalArgumentException if this extension was already registered with a <em>different</em> MIME type
1190: * @see #getMIMEType
1191: * @deprecated You should instead use the more general {@link MIMEResolver} system.
1192: */
1193: @Deprecated
1194: public static void setMIMEType(String ext, String mimeType) {
1195: synchronized (map) {
1196: String old = map.get(ext);
1197:
1198: if (old == null) {
1199: map.put(ext, mimeType);
1200: } else {
1201: if (!old.equals(mimeType)) {
1202: throw new IllegalArgumentException(
1203: "Cannot overwrite existing MIME type mapping for extension `"
1204: + // NOI18N
1205: ext + "' with " + mimeType
1206: + " (was " + old + ")"); // NOI18N
1207: }
1208:
1209: // else do nothing
1210: }
1211: }
1212: }
1213:
1214: /**
1215: * Construct a stream handler that handles the <code>nbfs</code> URL protocol
1216: * used for accessing file objects directly.
1217: * This method is not intended for module use; only the core
1218: * should need to call it.
1219: * Modules probably need only use {@link URLMapper} to create and decode such
1220: * URLs.
1221: * @since 3.17
1222: */
1223: public static URLStreamHandler nbfsURLStreamHandler() {
1224: return FileURL.HANDLER;
1225: }
1226:
1227: /** Recursively checks whether the file is underneath the folder. It checks whether
1228: * the file and folder are located on the same filesystem, in such case it checks the
1229: * parent <code>FileObject</code> of the file recursively until the folder is found
1230: * or the root of the filesystem is reached.
1231: * <p><strong>Warning:</strong> this method will return false in the case that
1232: * <code>folder == fo</code>.
1233: * @param folder the root of folders hierarchy to search in
1234: * @param fo the file to search for
1235: * @return <code>true</code>, if <code>fo</code> lies somewhere underneath the <code>folder</code>,
1236: * <code>false</code> otherwise
1237: * @since 3.16
1238: */
1239: public static boolean isParentOf(FileObject folder, FileObject fo) {
1240: if (folder == null) {
1241: throw new IllegalArgumentException(
1242: "Tried to pass null folder arg"); // NOI18N
1243: }
1244:
1245: if (fo == null) {
1246: throw new IllegalArgumentException(
1247: "Tried to pass null fo arg"); // NOI18N
1248: }
1249:
1250: if (folder.isData()) {
1251: return false;
1252: }
1253:
1254: try {
1255: if (folder.getFileSystem() != fo.getFileSystem()) {
1256: return false;
1257: }
1258: } catch (FileStateInvalidException e) {
1259: return false;
1260: }
1261:
1262: FileObject parent = fo.getParent();
1263:
1264: while (parent != null) {
1265: if (parent == folder) {
1266: return true;
1267: }
1268:
1269: parent = parent.getParent();
1270: }
1271:
1272: return false;
1273: }
1274:
1275: /** Creates a weak implementation of FileChangeListener.
1276: *
1277: * @param l the listener to delegate to
1278: * @param source the source that the listener should detach from when
1279: * listener <CODE>l</CODE> is freed, can be <CODE>null</CODE>
1280: * @return a FileChangeListener delegating to <CODE>l</CODE>.
1281: * @since 4.10
1282: */
1283: public static FileChangeListener weakFileChangeListener(
1284: FileChangeListener l, Object source) {
1285: return WeakListeners
1286: .create(FileChangeListener.class, l, source);
1287: }
1288:
1289: /** Creates a weak implementation of FileStatusListener.
1290: *
1291: * @param l the listener to delegate to
1292: * @param source the source that the listener should detach from when
1293: * listener <CODE>l</CODE> is freed, can be <CODE>null</CODE>
1294: * @return a FileChangeListener delegating to <CODE>l</CODE>.
1295: * @since 4.10
1296: */
1297: public static FileStatusListener weakFileStatusListener(
1298: FileStatusListener l, Object source) {
1299: return WeakListeners
1300: .create(FileStatusListener.class, l, source);
1301: }
1302:
1303: /**
1304: * Get an appropriate display name for a file object.
1305: * If the file corresponds to a path on disk, this will be the disk path.
1306: * Otherwise the name will mention the filesystem name or archive name in case
1307: * the file comes from archive and relative path. Relative path will be mentioned
1308: * just in case that passed <code>FileObject</code> isn't root {@link FileObject#isRoot}.
1309: *
1310: * @param fo a file object
1311: * @return a display name indicating where the file is
1312: * @since 4.39
1313: */
1314: public static String getFileDisplayName(FileObject fo) {
1315: String displayName = null;
1316: File f = FileUtil.toFile(fo);
1317:
1318: if (f != null) {
1319: displayName = f.getAbsolutePath();
1320: } else {
1321: FileObject archiveFile = FileUtil.getArchiveFile(fo);
1322:
1323: if (archiveFile != null) {
1324: displayName = getArchiveDisplayName(fo, archiveFile);
1325: }
1326: }
1327:
1328: if (displayName == null) {
1329: try {
1330: if (fo.isRoot()) {
1331: displayName = fo.getFileSystem().getDisplayName();
1332: } else {
1333: displayName = NbBundle.getMessage(FileUtil.class,
1334: "LBL_file_in_filesystem", fo.getPath(), fo
1335: .getFileSystem().getDisplayName());
1336: }
1337: } catch (FileStateInvalidException e) {
1338: // Not relevant now, just use the simple path.
1339: displayName = fo.getPath();
1340: }
1341: }
1342:
1343: return displayName;
1344: }
1345:
1346: private static String getArchiveDisplayName(FileObject fo,
1347: FileObject archiveFile) {
1348: String displayName = null;
1349:
1350: File f = FileUtil.toFile(archiveFile);
1351:
1352: if (f != null) {
1353: String archivDisplayName = f.getAbsolutePath();
1354:
1355: if (fo.isRoot()) {
1356: displayName = archivDisplayName;
1357: } else {
1358: String entryPath = fo.getPath();
1359: displayName = NbBundle.getMessage(FileUtil.class,
1360: "LBL_file_in_filesystem", entryPath,
1361: archivDisplayName);
1362: }
1363: }
1364:
1365: return displayName;
1366: }
1367:
1368: /**
1369: * Normalize a file path to a clean form.
1370: * This method may for example make sure that the returned file uses
1371: * the natural case on Windows; that old Windows 8.3 filenames are changed to the long form;
1372: * that relative paths are changed to be
1373: * absolute; that <code>.</code> and <code>..</code> sequences are removed; etc.
1374: * Unlike {@link File#getCanonicalFile} this method will not traverse symbolic links on Unix.
1375: * <p>This method involves some overhead and should not be called frivolously.
1376: * Generally it should be called on <em>incoming</em> pathnames that are gotten from user input
1377: * (including filechoosers), configuration files, Ant properties, etc. <em>Internal</em>
1378: * calculations should not need to renormalize paths since {@link File#listFiles},
1379: * {@link File#getParentFile}, etc. will not produce abnormal variants.
1380: * @param file file to normalize
1381: * @return normalized file
1382: * @since 4.48
1383: */
1384: public static File normalizeFile(final File file) {
1385: File retFile;
1386:
1387: if ((Utilities.isWindows() || (Utilities.getOperatingSystem() == Utilities.OS_OS2))) {
1388: retFile = normalizeFileOnWindows(file);
1389: } else if (Utilities.isMac()) {
1390: retFile = normalizeFileOnMac(file);
1391: } else {
1392: retFile = normalizeFileOnUnixAlike(file);
1393: }
1394:
1395: return (file.getPath().equals(retFile.getPath())) ? file
1396: : retFile;
1397: }
1398:
1399: private static File normalizeFileOnUnixAlike(File file) {
1400: // On Unix, do not want to traverse symlinks.
1401: if (file.getAbsolutePath().equals("/..")) { // NOI18N
1402:
1403: // Special treatment.
1404: file = new File("/"); // NOI18N
1405: } else {
1406: // URI.normalize removes ../ and ./ sequences nicely.
1407: file = new File(file.toURI().normalize()).getAbsoluteFile();
1408: }
1409:
1410: return file;
1411: }
1412:
1413: private static File normalizeFileOnMac(final File file) {
1414: File retVal = file;
1415:
1416: try {
1417: // URI.normalize removes ../ and ./ sequences nicely.
1418: File absoluteFile = new File(file.toURI().normalize());
1419: File canonicalFile = file.getCanonicalFile();
1420: boolean isSymLink = !canonicalFile.getAbsolutePath()
1421: .equalsIgnoreCase(absoluteFile.getAbsolutePath());
1422:
1423: if (isSymLink) {
1424: retVal = normalizeSymLinkOnMac(absoluteFile);
1425: } else {
1426: retVal = canonicalFile;
1427: }
1428: } catch (IOException ioe) {
1429: Logger.getAnonymousLogger()
1430: .severe(
1431: "Normalization failed on file " + file
1432: + ": " + ioe);
1433:
1434: // OK, so at least try to absolutize the path
1435: retVal = file.getAbsoluteFile();
1436: }
1437:
1438: return retVal;
1439: }
1440:
1441: /**
1442: * @param file is expected to be already absolute with removed ../ and ./
1443: */
1444: private static File normalizeSymLinkOnMac(final File file)
1445: throws IOException {
1446: File retVal = File.listRoots()[0];
1447: File pureCanonicalFile = retVal;
1448:
1449: final String pattern = File.separator + ".." + File.separator; //NOI18N
1450: final String fileName;
1451:
1452: { // strips insufficient non-<tt>".."</tt> segments preceding them
1453:
1454: String tmpFileName = file.getAbsolutePath();
1455: int index = tmpFileName.lastIndexOf(pattern);
1456:
1457: if (index > -1) {
1458: tmpFileName = tmpFileName.substring(index
1459: + pattern.length()); //Remove starting {/../}*
1460: }
1461:
1462: fileName = tmpFileName;
1463: }
1464:
1465: /*normalized step after step*/
1466: StringTokenizer fileSegments = new StringTokenizer(fileName,
1467: File.separator);
1468:
1469: while (fileSegments.hasMoreTokens()) {
1470: File absolutelyEndingFile = new File(pureCanonicalFile,
1471: fileSegments.nextToken());
1472: pureCanonicalFile = absolutelyEndingFile.getCanonicalFile();
1473:
1474: boolean isSymLink = !pureCanonicalFile.getAbsolutePath()
1475: .equalsIgnoreCase(
1476: absolutelyEndingFile.getAbsolutePath());
1477:
1478: if (isSymLink) {
1479: retVal = new File(retVal, absolutelyEndingFile
1480: .getName());
1481: } else {
1482: retVal = new File(retVal, pureCanonicalFile.getName());
1483: }
1484: }
1485:
1486: return retVal;
1487: }
1488:
1489: private static File normalizeFileOnWindows(final File file) {
1490: File retVal = null;
1491:
1492: if (canBeCanonicalizedOnWindows(file)) {
1493: try {
1494: retVal = file.getCanonicalFile();
1495: } catch (IOException e) {
1496: Logger.getAnonymousLogger().severe(
1497: "getCanonicalFile() on file " + file
1498: + " failed. " + e.toString()); // NOI18N
1499: }
1500: }
1501:
1502: return (retVal != null) ? retVal : file.getAbsoluteFile();
1503: }
1504:
1505: private static FileSystemView fileSystemView;
1506: private static float javaSpecVersion;
1507:
1508: private static boolean canBeCanonicalizedOnWindows(final File file) {
1509: /*#4089199, #95031 - Flopy and empty CD-drives can't be canonicalized*/
1510: boolean canBeCanonizalized = true;
1511: if (file.getParent() == null && Utilities.isWindows()) {//NOI18N
1512: FileSystemView fsv = getFileSystemView();
1513: canBeCanonizalized = (fsv != null) ? !fsv
1514: .isFloppyDrive(file)
1515: && file.exists() : false;
1516: }
1517:
1518: return canBeCanonizalized;
1519: }
1520:
1521: private static boolean is4089199() {
1522: return /*98388*/Utilities.isWindows()
1523: && getJavaSpecVersion() < 1.6;
1524: }
1525:
1526: private static float getJavaSpecVersion() {
1527: synchronized (FileUtil.class) {
1528: if (javaSpecVersion == 0) {
1529: javaSpecVersion = Float.valueOf(System
1530: .getProperty("java.specification.version"));//NOI18N
1531: }
1532: }
1533: return javaSpecVersion;
1534: }
1535:
1536: private static FileSystemView getFileSystemView() {
1537: boolean init = false;
1538: final FileSystemView[] fsv = { fileSystemView };
1539:
1540: synchronized (FileUtil.class) {
1541: init = is4089199() && fsv[0] == null;
1542: }
1543:
1544: if (init) {
1545: if (SwingUtilities.isEventDispatchThread()) {
1546: fsv[0] = javax.swing.filechooser.FileSystemView
1547: .getFileSystemView();
1548: synchronized (FileUtil.class) {
1549: fileSystemView = fsv[0];
1550: }
1551: } else {
1552: SwingUtilities.invokeLater(new java.lang.Runnable() {
1553: public void run() {
1554: fsv[0] = javax.swing.filechooser.FileSystemView
1555: .getFileSystemView();
1556: synchronized (FileUtil.class) {
1557: fileSystemView = fsv[0];
1558: }
1559: }
1560: });
1561: }
1562: }
1563: return fileSystemView;
1564: }
1565:
1566: /**
1567: * Returns a FileObject representing the root folder of an archive.
1568: * Clients may need to first call {@link #isArchiveFile(FileObject)} to determine
1569: * if the file object refers to an archive file.
1570: * @param fo a ZIP- (or JAR-) format archive file
1571: * @return a virtual archive root folder, or null if the file is not actually an archive
1572: * @since 4.48
1573: */
1574: public static FileObject getArchiveRoot(FileObject fo) {
1575: URL archiveURL = URLMapper.findURL(fo, URLMapper.EXTERNAL);
1576:
1577: if (archiveURL == null) {
1578: return null;
1579: }
1580:
1581: return URLMapper.findFileObject(getArchiveRoot(archiveURL));
1582: }
1583:
1584: /**
1585: * Returns a URL representing the root of an archive.
1586: * Clients may need to first call {@link #isArchiveFile(URL)} to determine if the URL
1587: * refers to an archive file.
1588: * @param url of a ZIP- (or JAR-) format archive file
1589: * @return the <code>jar</code>-protocol URL of the root of the archive
1590: * @since 4.48
1591: */
1592: public static URL getArchiveRoot(URL url) {
1593: try {
1594: // XXX TBD whether the url should ever be escaped...
1595: return new URL("jar:" + url + "!/"); // NOI18N
1596: } catch (MalformedURLException e) {
1597: throw new AssertionError(e);
1598: }
1599: }
1600:
1601: /**
1602: * Returns a FileObject representing an archive file containing the
1603: * FileObject given by the parameter.
1604: * <strong>Remember</strong> that any path within the archive is discarded
1605: * so you may need to check for non-root entries.
1606: * @param fo a file in a JAR filesystem
1607: * @return the file corresponding to the archive itself,
1608: * or null if <code>fo</code> is not an archive entry
1609: * @since 4.48
1610: */
1611: public static FileObject getArchiveFile(FileObject fo) {
1612: try {
1613: FileSystem fs = fo.getFileSystem();
1614:
1615: if (fs instanceof JarFileSystem) {
1616: File jarFile = ((JarFileSystem) fs).getJarFile();
1617:
1618: return toFileObject(jarFile);
1619: }
1620: } catch (FileStateInvalidException e) {
1621: Exceptions.printStackTrace(e);
1622: }
1623:
1624: return null;
1625: }
1626:
1627: /**
1628: * Returns the URL of the archive file containing the file
1629: * referred to by a <code>jar</code>-protocol URL.
1630: * <strong>Remember</strong> that any path within the archive is discarded
1631: * so you may need to check for non-root entries.
1632: * @param url a URL
1633: * @return the embedded archive URL, or null if the URL is not a
1634: * <code>jar</code>-protocol URL containing <code>!/</code>
1635: * @since 4.48
1636: */
1637: public static URL getArchiveFile(URL url) {
1638: String protocol = url.getProtocol();
1639:
1640: if ("jar".equals(protocol)) { //NOI18N
1641:
1642: String path = url.getPath();
1643: int index = path.indexOf("!/"); //NOI18N
1644:
1645: if (index >= 0) {
1646: try {
1647: return new URL(path.substring(0, index));
1648: } catch (MalformedURLException mue) {
1649: Exceptions.printStackTrace(mue);
1650: }
1651: }
1652: }
1653:
1654: return null;
1655: }
1656:
1657: /**
1658: * Tests if a file represents a JAR or ZIP archive.
1659: * @param fo the file to be tested
1660: * @return true if the file looks like a ZIP-format archive
1661: * @since 4.48
1662: */
1663: public static boolean isArchiveFile(FileObject fo) {
1664: if (fo == null) {
1665: throw new IllegalArgumentException(
1666: "Cannot pass null to FileUtil.isArchiveFile"); // NOI18N
1667: }
1668:
1669: if (!fo.isValid()) {
1670: return false;
1671: }
1672: // XXX Special handling of virtual file objects: try to determine it using its name, but don't cache the
1673: // result; when the file is checked out the more correct method can be used
1674: if (fo.isVirtual()) {
1675: String path = fo.getPath();
1676: int index = path.lastIndexOf('.');
1677:
1678: return (index != -1) && (index > path.lastIndexOf('/') + 1);
1679: }
1680:
1681: if (fo.isFolder()) {
1682: return false;
1683: }
1684:
1685: // First check the cache.
1686: Boolean b = archiveFileCache.get(fo);
1687:
1688: if (b == null) {
1689: // Need to check it.
1690: try {
1691: InputStream in = fo.getInputStream();
1692:
1693: try {
1694: byte[] buffer = new byte[4];
1695: int len = in.read(buffer, 0, 4);
1696:
1697: if (len == 4) {
1698: // Got a header, see if it is a ZIP file.
1699: b = Boolean.valueOf(Arrays.equals(ZIP_HEADER_1,
1700: buffer)
1701: || Arrays.equals(ZIP_HEADER_2, buffer));
1702: } else {
1703: //If the length is less than 4, it can be either
1704: //broken (empty) archive file or other empty file.
1705: //Return false and don't cache it, when the archive
1706: //file will be written and closed its length will change
1707: return false;
1708: }
1709: } finally {
1710: in.close();
1711: }
1712: } catch (IOException ioe) {
1713: Logger.getLogger(FileUtil.class.getName()).log(
1714: Level.INFO, null, ioe);
1715: }
1716:
1717: if (b == null) {
1718: String path = fo.getPath();
1719: int index = path.lastIndexOf('.');
1720: b = ((index != -1) && (index > path.lastIndexOf('/') + 1)) ? Boolean.TRUE
1721: : Boolean.FALSE;
1722: }
1723:
1724: archiveFileCache.put(fo, b);
1725: }
1726:
1727: return b.booleanValue();
1728: }
1729:
1730: /**
1731: * Tests if a URL represents a JAR or ZIP archive.
1732: * If there is no such file object, the test is done by heuristic: any URL with an extension is
1733: * treated as an archive.
1734: * @param url a URL to a file
1735: * @return true if the URL seems to represent a ZIP-format archive
1736: * @since 4.48
1737: */
1738: public static boolean isArchiveFile(URL url) {
1739: if (url == null) {
1740: throw new NullPointerException(
1741: "Cannot pass null URL to FileUtil.isArchiveFile"); // NOI18N
1742: }
1743:
1744: if ("jar".equals(url.getProtocol())) { //NOI18N
1745:
1746: //Already inside archive, return false
1747: return false;
1748: }
1749:
1750: FileObject fo = URLMapper.findFileObject(url);
1751:
1752: if ((fo != null) && !fo.isVirtual()) {
1753: return isArchiveFile(fo);
1754: } else {
1755: String urlPath = url.getPath();
1756: int index = urlPath.lastIndexOf('.');
1757:
1758: return (index != -1)
1759: && (index > urlPath.lastIndexOf('/') + 1);
1760: }
1761: }
1762:
1763: /**
1764: * Make sure that a JFileChooser does not traverse symlinks on Unix.
1765: * @param chooser a file chooser
1766: * @param currentDirectory if not null, a file to set as the current directory
1767: * using {@link JFileChooser#setCurrentDirectory} without canonicalizing
1768: * @see <a href="http://www.netbeans.org/issues/show_bug.cgi?id=46459">Issue #46459</a>
1769: * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4906607">JRE bug #4906607</a>
1770: * @since org.openide/1 4.42
1771: */
1772: public static void preventFileChooserSymlinkTraversal(
1773: JFileChooser chooser, File currentDirectory) {
1774: if (!(Utilities.isWindows() || (Utilities.getOperatingSystem() == Utilities.OS_OS2))
1775: && System.getProperty("java.specification.version")
1776: .startsWith("1.5")) { // NOI18N
1777: chooser
1778: .setCurrentDirectory(wrapFileNoCanonicalize(currentDirectory));
1779: chooser
1780: .setFileSystemView(new NonCanonicalizingFileSystemView());
1781: } else {
1782: chooser.setCurrentDirectory(currentDirectory);
1783: }
1784: }
1785:
1786: /**
1787: * Sorts some sibling file objects.
1788: * <p>Normally this is done by looking for numeric file attributes named <code>position</code>
1789: * on the children; children with a lower position number are placed first.
1790: * Now-deprecated relative ordering attributes of the form <code>earlier/later</code> may
1791: * also be used; if the above attribute has a boolean value of <code>true</code>,
1792: * then the file named <code>earlier</code> will be sorted somewhere (not necessarily directly)
1793: * before the file named <code>later</code>. Numeric and relative attributes may also be mixed.</p>
1794: * <p>The sort is <em>stable</em> at least to the extent that if there is no ordering information
1795: * whatsoever, the returned list will be in the same order as the incoming collection.</p>
1796: * @param children zero or more files (or folders); must all have the same {@link FileObject#getParent}
1797: * @param logWarnings true to log warnings about relative ordering attributes or other semantic problems, false to keep quiet
1798: * @return a sorted list of the same children
1799: * @throws IllegalArgumentException in case there are duplicates, or nulls, or the files do not have a common parent
1800: * @since org.openide.filesystems 7.2
1801: * @see #setOrder
1802: * @see <a href="http://wiki.netbeans.org/wiki/view/FolderOrdering103187">Specification</a>
1803: */
1804: public static List<FileObject> getOrder(
1805: Collection<FileObject> children, boolean logWarnings)
1806: throws IllegalArgumentException {
1807: return Ordering.getOrder(children, logWarnings);
1808: }
1809:
1810: /**
1811: * Imposes an order on some sibling file objects.
1812: * After this call, if no other changes have intervened,
1813: * {@link #getOrder} on these files should return a list in the same order.
1814: * Beyond the fact that this call may manipulate the <code>position</code> attributes
1815: * of files in the folder, and may delete deprecated relative ordering attributes on the folder,
1816: * the exact means of setting the order is unspecified.
1817: * @param children a list of zero or more files (or folders); must all have the same {@link FileObject#getParent}
1818: * @throws IllegalArgumentException in case there are duplicates, or nulls, or the files do not have a common parent
1819: * @throws IOException if new file attributes to order the children cannot be written out
1820: * @since org.openide.filesystems 7.2
1821: */
1822: public static void setOrder(List<FileObject> children)
1823: throws IllegalArgumentException, IOException {
1824: Ordering.setOrder(children);
1825: }
1826:
1827: /**
1828: * Checks whether a change in a given file attribute would affect the result of {@link #getOrder}.
1829: * @param event an attribute change event
1830: * @return true if the attribute in question might affect the order of some folder
1831: * @since org.openide.filesystems 7.2
1832: */
1833: public static boolean affectsOrder(FileAttributeEvent event) {
1834: return Ordering.affectsOrder(event);
1835: }
1836:
1837: static boolean assertDeprecatedMethod() {
1838: Thread.dumpStack();
1839:
1840: return true;
1841: }
1842:
1843: private static File wrapFileNoCanonicalize(File f) {
1844: if (f instanceof NonCanonicalizingFile) {
1845: return f;
1846: } else if (f != null) {
1847: return new NonCanonicalizingFile(f);
1848: } else {
1849: return null;
1850: }
1851: }
1852:
1853: private static File[] wrapFilesNoCanonicalize(File[] fs) {
1854: if (fs != null) {
1855: for (int i = 0; i < fs.length; i++) {
1856: fs[i] = wrapFileNoCanonicalize(fs[i]);
1857: }
1858: }
1859:
1860: return fs;
1861: }
1862:
1863: private static final class NonCanonicalizingFile extends File {
1864: public NonCanonicalizingFile(File orig) {
1865: this (orig.getPath());
1866: }
1867:
1868: private NonCanonicalizingFile(String path) {
1869: super (path);
1870: }
1871:
1872: private NonCanonicalizingFile(URI uri) {
1873: super (uri);
1874: }
1875:
1876: public File getCanonicalFile() throws IOException {
1877: return wrapFileNoCanonicalize(normalizeFile(super
1878: .getAbsoluteFile()));
1879: }
1880:
1881: public String getCanonicalPath() throws IOException {
1882: return normalizeFile(super .getAbsoluteFile())
1883: .getAbsolutePath();
1884: }
1885:
1886: public File getParentFile() {
1887: return wrapFileNoCanonicalize(super .getParentFile());
1888: }
1889:
1890: public File getAbsoluteFile() {
1891: return wrapFileNoCanonicalize(super .getAbsoluteFile());
1892: }
1893:
1894: public File[] listFiles() {
1895: return wrapFilesNoCanonicalize(super .listFiles());
1896: }
1897:
1898: public File[] listFiles(FileFilter filter) {
1899: return wrapFilesNoCanonicalize(super .listFiles(filter));
1900: }
1901:
1902: public File[] listFiles(FilenameFilter filter) {
1903: return wrapFilesNoCanonicalize(super .listFiles(filter));
1904: }
1905: }
1906:
1907: private static final class NonCanonicalizingFileSystemView extends
1908: FileSystemView {
1909: private final FileSystemView delegate = FileSystemView
1910: .getFileSystemView();
1911:
1912: public NonCanonicalizingFileSystemView() {
1913: }
1914:
1915: public boolean isFloppyDrive(File dir) {
1916: return delegate.isFloppyDrive(dir);
1917: }
1918:
1919: public boolean isComputerNode(File dir) {
1920: return delegate.isComputerNode(dir);
1921: }
1922:
1923: public File createNewFolder(File containingDir)
1924: throws IOException {
1925: return wrapFileNoCanonicalize(delegate
1926: .createNewFolder(containingDir));
1927: }
1928:
1929: public boolean isDrive(File dir) {
1930: return delegate.isDrive(dir);
1931: }
1932:
1933: public boolean isFileSystemRoot(File dir) {
1934: return delegate.isFileSystemRoot(dir);
1935: }
1936:
1937: public File getHomeDirectory() {
1938: return wrapFileNoCanonicalize(delegate.getHomeDirectory());
1939: }
1940:
1941: public File createFileObject(File dir, String filename) {
1942: return wrapFileNoCanonicalize(delegate.createFileObject(
1943: dir, filename));
1944: }
1945:
1946: public Boolean isTraversable(File f) {
1947: return delegate.isTraversable(f);
1948: }
1949:
1950: public boolean isFileSystem(File f) {
1951: return delegate.isFileSystem(f);
1952: }
1953:
1954: /*
1955: protected File createFileSystemRoot(File f) {
1956: return translate(delegate.createFileSystemRoot(f));
1957: }
1958: */
1959: public File getChild(File parent, String fileName) {
1960: return wrapFileNoCanonicalize(delegate.getChild(parent,
1961: fileName));
1962: }
1963:
1964: public File getParentDirectory(File dir) {
1965: return wrapFileNoCanonicalize(delegate
1966: .getParentDirectory(dir));
1967: }
1968:
1969: public Icon getSystemIcon(File f) {
1970: return delegate.getSystemIcon(f);
1971: }
1972:
1973: public boolean isParent(File folder, File file) {
1974: return delegate.isParent(folder, file);
1975: }
1976:
1977: public String getSystemTypeDescription(File f) {
1978: return delegate.getSystemTypeDescription(f);
1979: }
1980:
1981: public File getDefaultDirectory() {
1982: return wrapFileNoCanonicalize(delegate
1983: .getDefaultDirectory());
1984: }
1985:
1986: public String getSystemDisplayName(File f) {
1987: return delegate.getSystemDisplayName(f);
1988: }
1989:
1990: public File[] getRoots() {
1991: return wrapFilesNoCanonicalize(delegate.getRoots());
1992: }
1993:
1994: public boolean isHiddenFile(File f) {
1995: return delegate.isHiddenFile(f);
1996: }
1997:
1998: public File[] getFiles(File dir, boolean useFileHiding) {
1999: return wrapFilesNoCanonicalize(delegate.getFiles(dir,
2000: useFileHiding));
2001: }
2002:
2003: public boolean isRoot(File f) {
2004: return delegate.isRoot(f);
2005: }
2006:
2007: public File createFileObject(String path) {
2008: return wrapFileNoCanonicalize(delegate
2009: .createFileObject(path));
2010: }
2011: }
2012:
2013: private static FileSystem getDiskFileSystem() {
2014: synchronized (FileUtil.class) {
2015: return diskFileSystem;
2016: }
2017: }
2018:
2019: private static void setDiskFileSystem(FileSystem fs) {
2020: Object o = fs.getRoot().getAttribute(
2021: "SupportsRefreshForNoPublicAPI");
2022: if (o instanceof Boolean && ((Boolean) o).booleanValue()) {
2023: synchronized (FileUtil.class) {
2024: diskFileSystem = fs;
2025: }
2026: }
2027: }
2028: }
|