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