/*
* Copyright (c) 2011, 2014, 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;
@SuppressWarnings("serial") // JDK implementation class
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);
private 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
/**
* 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
/**
* 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;
}
}