| /* |
| * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package com.apple.laf; |
| |
| import java.io.*; |
| import java.util.*; |
| import java.util.Map.Entry; |
| |
| import javax.swing.Icon; |
| import javax.swing.filechooser.FileView; |
| |
| import com.apple.laf.AquaUtils.RecyclableSingleton; |
| |
| class AquaFileView extends FileView { |
| private static final boolean DEBUG = false; |
| |
| private static final int UNINITALIZED_LS_INFO = -1; |
| |
| // Constants from LaunchServices.h |
| static final int kLSItemInfoIsPlainFile = 0x00000001; /* Not a directory, volume, or symlink*/ |
| static final int kLSItemInfoIsPackage = 0x00000002; /* Packaged directory*/ |
| static final int kLSItemInfoIsApplication = 0x00000004; /* Single-file or packaged application*/ |
| static final int kLSItemInfoIsContainer = 0x00000008; /* Directory (includes packages) or volume*/ |
| static final int kLSItemInfoIsAliasFile = 0x00000010; /* Alias file (includes sym links)*/ |
| static final int kLSItemInfoIsSymlink = 0x00000020; /* UNIX sym link*/ |
| static final int kLSItemInfoIsInvisible = 0x00000040; /* Invisible by any known mechanism*/ |
| static final int kLSItemInfoIsNativeApp = 0x00000080; /* Carbon or Cocoa native app*/ |
| static final int kLSItemInfoIsClassicApp = 0x00000100; /* CFM/68K Classic app*/ |
| static final int kLSItemInfoAppPrefersNative = 0x00000200; /* Carbon app that prefers to be launched natively*/ |
| static final int kLSItemInfoAppPrefersClassic = 0x00000400; /* Carbon app that prefers to be launched in Classic*/ |
| static final int kLSItemInfoAppIsScriptable = 0x00000800; /* App can be scripted*/ |
| static final int kLSItemInfoIsVolume = 0x00001000; /* Item is a volume*/ |
| static final int kLSItemInfoExtensionIsHidden = 0x00100000; /* Item has a hidden extension*/ |
| |
| static { |
| java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<Void>() { |
| public Void run() { |
| System.loadLibrary("osxui"); |
| return null; |
| } |
| }); |
| } |
| |
| // TODO: Un-comment this out when the native version exists |
| //private static native String getNativePathToRunningJDKBundle(); |
| private static native String getNativePathToSharedJDKBundle(); |
| |
| private static native String getNativeMachineName(); |
| private static native String getNativeDisplayName(final byte[] pathBytes, final boolean isDirectory); |
| private static native int getNativeLSInfo(final byte[] pathBytes, final boolean isDirectory); |
| private static native String getNativePathForResolvedAlias(final byte[] absolutePath, final boolean isDirectory); |
| |
| static final RecyclableSingleton<String> machineName = new RecyclableSingleton<String>() { |
| @Override |
| protected String getInstance() { |
| return getNativeMachineName(); |
| } |
| }; |
| private static String getMachineName() { |
| return machineName.get(); |
| } |
| |
| protected static String getPathToRunningJDKBundle() { |
| // TODO: Return empty string for now |
| return "";//getNativePathToRunningJDKBundle(); |
| } |
| |
| protected static String getPathToSharedJDKBundle() { |
| return getNativePathToSharedJDKBundle(); |
| } |
| |
| static class FileInfo { |
| final boolean isDirectory; |
| final String absolutePath; |
| byte[] pathBytes; |
| |
| String displayName; |
| Icon icon; |
| int launchServicesInfo = UNINITALIZED_LS_INFO; |
| |
| FileInfo(final File file){ |
| isDirectory = file.isDirectory(); |
| absolutePath = file.getAbsolutePath(); |
| try { |
| pathBytes = absolutePath.getBytes("UTF-8"); |
| } catch (final UnsupportedEncodingException e) { |
| pathBytes = new byte[0]; |
| } |
| } |
| } |
| |
| final int MAX_CACHED_ENTRIES = 256; |
| protected final Map<File, FileInfo> cache = new LinkedHashMap<File, FileInfo>(){ |
| protected boolean removeEldestEntry(final Entry<File, FileInfo> eldest) { |
| return size() > MAX_CACHED_ENTRIES; |
| } |
| }; |
| |
| FileInfo getFileInfoFor(final File file) { |
| final FileInfo info = cache.get(file); |
| if (info != null) return info; |
| final FileInfo newInfo = new FileInfo(file); |
| cache.put(file, newInfo); |
| return newInfo; |
| } |
| |
| |
| final AquaFileChooserUI fFileChooserUI; |
| public AquaFileView(final AquaFileChooserUI fileChooserUI) { |
| fFileChooserUI = fileChooserUI; |
| } |
| |
| String _directoryDescriptionText() { |
| return fFileChooserUI.directoryDescriptionText; |
| } |
| |
| String _fileDescriptionText() { |
| return fFileChooserUI.fileDescriptionText; |
| } |
| |
| boolean _packageIsTraversable() { |
| return fFileChooserUI.fPackageIsTraversable == AquaFileChooserUI.kOpenAlways; |
| } |
| |
| boolean _applicationIsTraversable() { |
| return fFileChooserUI.fApplicationIsTraversable == AquaFileChooserUI.kOpenAlways; |
| } |
| |
| public String getName(final File f) { |
| final FileInfo info = getFileInfoFor(f); |
| if (info.displayName != null) return info.displayName; |
| |
| final String nativeDisplayName = getNativeDisplayName(info.pathBytes, info.isDirectory); |
| if (nativeDisplayName != null) { |
| info.displayName = nativeDisplayName; |
| return nativeDisplayName; |
| } |
| |
| final String displayName = f.getName(); |
| if (f.isDirectory() && fFileChooserUI.getFileChooser().getFileSystemView().isRoot(f)) { |
| final String localMachineName = getMachineName(); |
| info.displayName = localMachineName; |
| return localMachineName; |
| } |
| |
| info.displayName = displayName; |
| return displayName; |
| } |
| |
| public String getDescription(final File f) { |
| return f.getName(); |
| } |
| |
| public String getTypeDescription(final File f) { |
| if (f.isDirectory()) return _directoryDescriptionText(); |
| return _fileDescriptionText(); |
| } |
| |
| public Icon getIcon(final File f) { |
| final FileInfo info = getFileInfoFor(f); |
| if (info.icon != null) return info.icon; |
| |
| if (f == null) { |
| info.icon = AquaIcon.SystemIcon.getDocumentIconUIResource(); |
| } else { |
| // Look for the document's icon |
| final AquaIcon.FileIcon fileIcon = new AquaIcon.FileIcon(f); |
| info.icon = fileIcon; |
| if (!fileIcon.hasIconRef()) { |
| // Fall back on the default icons |
| if (f.isDirectory()) { |
| if (fFileChooserUI.getFileChooser().getFileSystemView().isRoot(f)) { |
| info.icon = AquaIcon.SystemIcon.getComputerIconUIResource(); |
| } else if (f.getParent() == null || f.getParent().equals("/")) { |
| info.icon = AquaIcon.SystemIcon.getHardDriveIconUIResource(); |
| } else { |
| info.icon = AquaIcon.SystemIcon.getFolderIconUIResource(); |
| } |
| } else { |
| info.icon = AquaIcon.SystemIcon.getDocumentIconUIResource(); |
| } |
| } |
| } |
| |
| return info.icon; |
| } |
| |
| // aliases are traversable though they aren't directories |
| public Boolean isTraversable(final File f) { |
| if (f.isDirectory()) { |
| // Doesn't matter if it's a package or app, because they're traversable |
| if (_packageIsTraversable() && _applicationIsTraversable()) { |
| return Boolean.TRUE; |
| } else if (!_packageIsTraversable() && !_applicationIsTraversable()) { |
| if (isPackage(f) || isApplication(f)) return Boolean.FALSE; |
| } else if (!_applicationIsTraversable()) { |
| if (isApplication(f)) return Boolean.FALSE; |
| } else if (!_packageIsTraversable()) { |
| // [3101730] All applications are packages, but not all packages are applications. |
| if (isPackage(f) && !isApplication(f)) return Boolean.FALSE; |
| } |
| |
| // We're allowed to traverse it |
| return Boolean.TRUE; |
| } |
| |
| if (isAlias(f)) { |
| final File realFile = resolveAlias(f); |
| return realFile.isDirectory() ? Boolean.TRUE : Boolean.FALSE; |
| } |
| |
| return Boolean.FALSE; |
| } |
| |
| int getLSInfoFor(final File f) { |
| final FileInfo info = getFileInfoFor(f); |
| |
| if (info.launchServicesInfo == UNINITALIZED_LS_INFO) { |
| info.launchServicesInfo = getNativeLSInfo(info.pathBytes, info.isDirectory); |
| } |
| |
| return info.launchServicesInfo; |
| } |
| |
| boolean isAlias(final File f) { |
| final int lsInfo = getLSInfoFor(f); |
| return ((lsInfo & kLSItemInfoIsAliasFile) != 0) && ((lsInfo & kLSItemInfoIsSymlink) == 0); |
| } |
| |
| boolean isApplication(final File f) { |
| return (getLSInfoFor(f) & kLSItemInfoIsApplication) != 0; |
| } |
| |
| boolean isPackage(final File f) { |
| return (getLSInfoFor(f) & kLSItemInfoIsPackage) != 0; |
| } |
| |
| /** |
| * Things that need to be handled: |
| * -Change getFSRef to use CFURLRef instead of FSPathMakeRef |
| * -Use the HFS-style path from CFURLRef in resolveAlias() to avoid |
| * path length limitations |
| * -In resolveAlias(), simply resolve immediately if this is an alias |
| */ |
| |
| /** |
| * Returns the actual file represented by this object. This will |
| * resolve any aliases in the path, including this file if it is an |
| * alias. No alias resolution requiring user interaction (e.g. |
| * mounting servers) will occur. Note that aliases to servers may |
| * take a significant amount of time to resolve. This method |
| * currently does not have any provisions for a more fine-grained |
| * timeout for alias resolution beyond that used by the system. |
| * |
| * In the event of a path that does not contain any aliases, or if the file |
| * does not exist, this method will return the file that was passed in. |
| * @return The canonical path to the file |
| * @throws IOException If an I/O error occurs while attempting to |
| * construct the path |
| */ |
| File resolveAlias(final File mFile) { |
| // If the file exists and is not an alias, there aren't |
| // any aliases along its path, so the standard version |
| // of getCanonicalPath() will work. |
| if (mFile.exists() && !isAlias(mFile)) { |
| if (DEBUG) System.out.println("not an alias"); |
| return mFile; |
| } |
| |
| // If it doesn't exist, either there's an alias in the |
| // path or this is an alias. Traverse the path and |
| // resolve all aliases in it. |
| final LinkedList<String> components = getPathComponents(mFile); |
| if (components == null) { |
| if (DEBUG) System.out.println("getPathComponents is null "); |
| return mFile; |
| } |
| |
| File file = new File("/"); |
| for (final String nextComponent : components) { |
| file = new File(file, nextComponent); |
| final FileInfo info = getFileInfoFor(file); |
| |
| // If any point along the way doesn't exist, |
| // just return the file. |
| if (!file.exists()) { return mFile; } |
| |
| if (isAlias(file)) { |
| // Resolve it! |
| final String path = getNativePathForResolvedAlias(info.pathBytes, info.isDirectory); |
| |
| // <rdar://problem/3582601> If the alias doesn't resolve (on a non-existent volume, for example) |
| // just return the file. |
| if (path == null) return mFile; |
| |
| file = new File(path); |
| } |
| } |
| |
| return file; |
| } |
| |
| /** |
| * Returns a linked list of Strings consisting of the components of |
| * the path of this file, in order, including the filename as the |
| * last element. The first element in the list will be the first |
| * directory in the path, or "". |
| * @return A linked list of the components of this file's path |
| */ |
| private static LinkedList<String> getPathComponents(final File mFile) { |
| final LinkedList<String> componentList = new LinkedList<String>(); |
| String parent; |
| |
| File file = new File(mFile.getAbsolutePath()); |
| componentList.add(0, file.getName()); |
| while ((parent = file.getParent()) != null) { |
| file = new File(parent); |
| componentList.add(0, file.getName()); |
| } |
| return componentList; |
| } |
| } |