Path: blob/master/src/java.desktop/macosx/classes/com/apple/laf/AquaFileView.java
41155 views
/*1* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.apple.laf;2627import java.io.*;28import java.util.*;29import java.util.Map.Entry;3031import javax.swing.Icon;32import javax.swing.filechooser.FileView;3334import com.apple.laf.AquaUtils.RecyclableSingleton;3536@SuppressWarnings({"removal","serial"}) // JDK implementation class37class AquaFileView extends FileView {38private static final boolean DEBUG = false;3940private static final int UNINITALIZED_LS_INFO = -1;4142// Constants from LaunchServices.h43static final int kLSItemInfoIsPlainFile = 0x00000001; /* Not a directory, volume, or symlink*/44static final int kLSItemInfoIsPackage = 0x00000002; /* Packaged directory*/45static final int kLSItemInfoIsApplication = 0x00000004; /* Single-file or packaged application*/46static final int kLSItemInfoIsContainer = 0x00000008; /* Directory (includes packages) or volume*/47static final int kLSItemInfoIsAliasFile = 0x00000010; /* Alias file (includes sym links)*/48static final int kLSItemInfoIsSymlink = 0x00000020; /* UNIX sym link*/49static final int kLSItemInfoIsInvisible = 0x00000040; /* Invisible by any known mechanism*/50static final int kLSItemInfoIsNativeApp = 0x00000080; /* Carbon or Cocoa native app*/51static final int kLSItemInfoIsClassicApp = 0x00000100; /* CFM/68K Classic app*/52static final int kLSItemInfoAppPrefersNative = 0x00000200; /* Carbon app that prefers to be launched natively*/53static final int kLSItemInfoAppPrefersClassic = 0x00000400; /* Carbon app that prefers to be launched in Classic*/54static final int kLSItemInfoAppIsScriptable = 0x00000800; /* App can be scripted*/55static final int kLSItemInfoIsVolume = 0x00001000; /* Item is a volume*/56static final int kLSItemInfoExtensionIsHidden = 0x00100000; /* Item has a hidden extension*/5758static {59java.security.AccessController.doPrivileged(60new java.security.PrivilegedAction<Void>() {61public Void run() {62System.loadLibrary("osxui");63return null;64}65});66}6768// TODO: Un-comment this out when the native version exists69//private static native String getNativePathToRunningJDKBundle();70private static native String getNativePathToSharedJDKBundle();7172private static native String getNativeMachineName();73private static native String getNativeDisplayName(final byte[] pathBytes, final boolean isDirectory);74private static native int getNativeLSInfo(final byte[] pathBytes, final boolean isDirectory);75private static native String getNativePathForResolvedAlias(final byte[] absolutePath, final boolean isDirectory);7677private static final RecyclableSingleton<String> machineName = new RecyclableSingleton<String>() {78@Override79protected String getInstance() {80return getNativeMachineName();81}82};83private static String getMachineName() {84return machineName.get();85}8687protected static String getPathToRunningJDKBundle() {88// TODO: Return empty string for now89return "";//getNativePathToRunningJDKBundle();90}9192protected static String getPathToSharedJDKBundle() {93return getNativePathToSharedJDKBundle();94}9596static class FileInfo {97final boolean isDirectory;98final String absolutePath;99byte[] pathBytes;100101String displayName;102Icon icon;103int launchServicesInfo = UNINITALIZED_LS_INFO;104105FileInfo(final File file){106isDirectory = file.isDirectory();107absolutePath = file.getAbsolutePath();108try {109pathBytes = absolutePath.getBytes("UTF-8");110} catch (final UnsupportedEncodingException e) {111pathBytes = new byte[0];112}113}114}115116final int MAX_CACHED_ENTRIES = 256;117protected final Map<File, FileInfo> cache = new LinkedHashMap<File, FileInfo>(){118protected boolean removeEldestEntry(final Entry<File, FileInfo> eldest) {119return size() > MAX_CACHED_ENTRIES;120}121};122123FileInfo getFileInfoFor(final File file) {124final FileInfo info = cache.get(file);125if (info != null) return info;126final FileInfo newInfo = new FileInfo(file);127cache.put(file, newInfo);128return newInfo;129}130131132final AquaFileChooserUI fFileChooserUI;133public AquaFileView(final AquaFileChooserUI fileChooserUI) {134fFileChooserUI = fileChooserUI;135}136137String _directoryDescriptionText() {138return fFileChooserUI.directoryDescriptionText;139}140141String _fileDescriptionText() {142return fFileChooserUI.fileDescriptionText;143}144145boolean _packageIsTraversable() {146return fFileChooserUI.fPackageIsTraversable == AquaFileChooserUI.kOpenAlways;147}148149boolean _applicationIsTraversable() {150return fFileChooserUI.fApplicationIsTraversable == AquaFileChooserUI.kOpenAlways;151}152153public String getName(final File f) {154final FileInfo info = getFileInfoFor(f);155if (info.displayName != null) return info.displayName;156157final String nativeDisplayName = getNativeDisplayName(info.pathBytes, info.isDirectory);158if (nativeDisplayName != null) {159info.displayName = nativeDisplayName;160return nativeDisplayName;161}162163final String displayName = f.getName();164if (f.isDirectory() && fFileChooserUI.getFileChooser().getFileSystemView().isRoot(f)) {165final String localMachineName = getMachineName();166info.displayName = localMachineName;167return localMachineName;168}169170info.displayName = displayName;171return displayName;172}173174public String getDescription(final File f) {175return f.getName();176}177178public String getTypeDescription(final File f) {179if (f.isDirectory()) return _directoryDescriptionText();180return _fileDescriptionText();181}182183public Icon getIcon(final File f) {184final FileInfo info = getFileInfoFor(f);185if (info.icon != null) return info.icon;186187if (f == null) {188info.icon = AquaIcon.SystemIcon.getDocumentIconUIResource();189} else {190// Look for the document's icon191final AquaIcon.FileIcon fileIcon = new AquaIcon.FileIcon(f);192info.icon = fileIcon;193if (!fileIcon.hasIconRef()) {194// Fall back on the default icons195if (f.isDirectory()) {196if (fFileChooserUI.getFileChooser().getFileSystemView().isRoot(f)) {197info.icon = AquaIcon.SystemIcon.getComputerIconUIResource();198} else if (f.getParent() == null || f.getParent().equals("/")) {199info.icon = AquaIcon.SystemIcon.getHardDriveIconUIResource();200} else {201info.icon = AquaIcon.SystemIcon.getFolderIconUIResource();202}203} else {204info.icon = AquaIcon.SystemIcon.getDocumentIconUIResource();205}206}207}208209return info.icon;210}211212// aliases are traversable though they aren't directories213public Boolean isTraversable(final File f) {214if (f.isDirectory()) {215// Doesn't matter if it's a package or app, because they're traversable216if (_packageIsTraversable() && _applicationIsTraversable()) {217return Boolean.TRUE;218} else if (!_packageIsTraversable() && !_applicationIsTraversable()) {219if (isPackage(f) || isApplication(f)) return Boolean.FALSE;220} else if (!_applicationIsTraversable()) {221if (isApplication(f)) return Boolean.FALSE;222} else if (!_packageIsTraversable()) {223// [3101730] All applications are packages, but not all packages are applications.224if (isPackage(f) && !isApplication(f)) return Boolean.FALSE;225}226227// We're allowed to traverse it228return Boolean.TRUE;229}230231if (isAlias(f)) {232final File realFile = resolveAlias(f);233return realFile.isDirectory() ? Boolean.TRUE : Boolean.FALSE;234}235236return Boolean.FALSE;237}238239int getLSInfoFor(final File f) {240final FileInfo info = getFileInfoFor(f);241242if (info.launchServicesInfo == UNINITALIZED_LS_INFO) {243info.launchServicesInfo = getNativeLSInfo(info.pathBytes, info.isDirectory);244}245246return info.launchServicesInfo;247}248249boolean isAlias(final File f) {250final int lsInfo = getLSInfoFor(f);251return ((lsInfo & kLSItemInfoIsAliasFile) != 0) && ((lsInfo & kLSItemInfoIsSymlink) == 0);252}253254boolean isApplication(final File f) {255return (getLSInfoFor(f) & kLSItemInfoIsApplication) != 0;256}257258boolean isPackage(final File f) {259return (getLSInfoFor(f) & kLSItemInfoIsPackage) != 0;260}261262/**263* Things that need to be handled:264* -Change getFSRef to use CFURLRef instead of FSPathMakeRef265* -Use the HFS-style path from CFURLRef in resolveAlias() to avoid266* path length limitations267* -In resolveAlias(), simply resolve immediately if this is an alias268*/269270/**271* Returns the actual file represented by this object. This will272* resolve any aliases in the path, including this file if it is an273* alias. No alias resolution requiring user interaction (e.g.274* mounting servers) will occur. Note that aliases to servers may275* take a significant amount of time to resolve. This method276* currently does not have any provisions for a more fine-grained277* timeout for alias resolution beyond that used by the system.278*279* In the event of a path that does not contain any aliases, or if the file280* does not exist, this method will return the file that was passed in.281* @return The canonical path to the file282* @throws IOException If an I/O error occurs while attempting to283* construct the path284*/285File resolveAlias(final File mFile) {286// If the file exists and is not an alias, there aren't287// any aliases along its path, so the standard version288// of getCanonicalPath() will work.289if (mFile.exists() && !isAlias(mFile)) {290if (DEBUG) System.out.println("not an alias");291return mFile;292}293294// If it doesn't exist, either there's an alias in the295// path or this is an alias. Traverse the path and296// resolve all aliases in it.297final LinkedList<String> components = getPathComponents(mFile);298if (components == null) {299if (DEBUG) System.out.println("getPathComponents is null ");300return mFile;301}302303File file = new File("/");304for (final String nextComponent : components) {305file = new File(file, nextComponent);306final FileInfo info = getFileInfoFor(file);307308// If any point along the way doesn't exist,309// just return the file.310if (!file.exists()) { return mFile; }311312if (isAlias(file)) {313// Resolve it!314final String path = getNativePathForResolvedAlias(info.pathBytes, info.isDirectory);315316// <rdar://problem/3582601> If the alias doesn't resolve (on a non-existent volume, for example)317// just return the file.318if (path == null) return mFile;319320file = new File(path);321}322}323324return file;325}326327/**328* Returns a linked list of Strings consisting of the components of329* the path of this file, in order, including the filename as the330* last element. The first element in the list will be the first331* directory in the path, or "".332* @return A linked list of the components of this file's path333*/334private static LinkedList<String> getPathComponents(final File mFile) {335final LinkedList<String> componentList = new LinkedList<String>();336String parent;337338File file = new File(mFile.getAbsolutePath());339componentList.add(0, file.getName());340while ((parent = file.getParent()) != null) {341file = new File(parent);342componentList.add(0, file.getName());343}344return componentList;345}346}347348349