1 /* 2 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.apple.laf; 27 28 import java.io.*; 29 import java.util.*; 30 import java.util.Map.Entry; 31 32 import javax.swing.Icon; 33 import javax.swing.filechooser.FileView; 34 35 import com.apple.laf.AquaUtils.RecyclableSingleton; 36 37 @SuppressWarnings("serial") // JDK implementation class 38 class AquaFileView extends FileView { 39 private static final boolean DEBUG = false; 40 41 private static final int UNINITALIZED_LS_INFO = -1; 42 43 // Constants from LaunchServices.h 44 static final int kLSItemInfoIsPlainFile = 0x00000001; /* Not a directory, volume, or symlink*/ 45 static final int kLSItemInfoIsPackage = 0x00000002; /* Packaged directory*/ 46 static final int kLSItemInfoIsApplication = 0x00000004; /* Single-file or packaged application*/ 47 static final int kLSItemInfoIsContainer = 0x00000008; /* Directory (includes packages) or volume*/ 48 static final int kLSItemInfoIsAliasFile = 0x00000010; /* Alias file (includes sym links)*/ 49 static final int kLSItemInfoIsSymlink = 0x00000020; /* UNIX sym link*/ 50 static final int kLSItemInfoIsInvisible = 0x00000040; /* Invisible by any known mechanism*/ 51 static final int kLSItemInfoIsNativeApp = 0x00000080; /* Carbon or Cocoa native app*/ 52 static final int kLSItemInfoIsClassicApp = 0x00000100; /* CFM/68K Classic app*/ 53 static final int kLSItemInfoAppPrefersNative = 0x00000200; /* Carbon app that prefers to be launched natively*/ 54 static final int kLSItemInfoAppPrefersClassic = 0x00000400; /* Carbon app that prefers to be launched in Classic*/ 55 static final int kLSItemInfoAppIsScriptable = 0x00000800; /* App can be scripted*/ 56 static final int kLSItemInfoIsVolume = 0x00001000; /* Item is a volume*/ 57 static final int kLSItemInfoExtensionIsHidden = 0x00100000; /* Item has a hidden extension*/ 58 59 static { 60 java.security.AccessController.doPrivileged( 61 new java.security.PrivilegedAction<Void>() { 62 public Void run() { 63 System.loadLibrary("osxui"); 64 return null; 65 } 66 }); 67 } 68 69 // TODO: Un-comment this out when the native version exists 70 //private static native String getNativePathToRunningJDKBundle(); 71 private static native String getNativePathToSharedJDKBundle(); 72 73 private static native String getNativeMachineName(); 74 private static native String getNativeDisplayName(final byte[] pathBytes, final boolean isDirectory); 75 private static native int getNativeLSInfo(final byte[] pathBytes, final boolean isDirectory); 76 private static native String getNativePathForResolvedAlias(final byte[] absolutePath, final boolean isDirectory); 77 78 static final RecyclableSingleton<String> machineName = new RecyclableSingleton<String>() { 79 @Override 80 protected String getInstance() { 81 return getNativeMachineName(); 82 } 83 }; 84 private static String getMachineName() { 85 return machineName.get(); 86 } 87 88 protected static String getPathToRunningJDKBundle() { 89 // TODO: Return empty string for now 90 return "";//getNativePathToRunningJDKBundle(); 91 } 92 93 protected static String getPathToSharedJDKBundle() { 94 return getNativePathToSharedJDKBundle(); 95 } 96 97 static class FileInfo { 98 final boolean isDirectory; 99 final String absolutePath; 100 byte[] pathBytes; 101 102 String displayName; 103 Icon icon; 104 int launchServicesInfo = UNINITALIZED_LS_INFO; 105 106 FileInfo(final File file){ 107 isDirectory = file.isDirectory(); 108 absolutePath = file.getAbsolutePath(); 109 try { 110 pathBytes = absolutePath.getBytes("UTF-8"); 111 } catch (final UnsupportedEncodingException e) { 112 pathBytes = new byte[0]; 113 } 114 } 115 } 116 117 final int MAX_CACHED_ENTRIES = 256; 118 protected final Map<File, FileInfo> cache = new LinkedHashMap<File, FileInfo>(){ 119 protected boolean removeEldestEntry(final Entry<File, FileInfo> eldest) { 120 return size() > MAX_CACHED_ENTRIES; 121 } 122 }; 123 124 FileInfo getFileInfoFor(final File file) { 125 final FileInfo info = cache.get(file); 126 if (info != null) return info; 127 final FileInfo newInfo = new FileInfo(file); 128 cache.put(file, newInfo); 129 return newInfo; 130 } 131 132 133 final AquaFileChooserUI fFileChooserUI; 134 public AquaFileView(final AquaFileChooserUI fileChooserUI) { 135 fFileChooserUI = fileChooserUI; 136 } 137 138 String _directoryDescriptionText() { 139 return fFileChooserUI.directoryDescriptionText; 140 } 141 142 String _fileDescriptionText() { 143 return fFileChooserUI.fileDescriptionText; 144 } 145 146 boolean _packageIsTraversable() { 147 return fFileChooserUI.fPackageIsTraversable == AquaFileChooserUI.kOpenAlways; 148 } 149 150 boolean _applicationIsTraversable() { 151 return fFileChooserUI.fApplicationIsTraversable == AquaFileChooserUI.kOpenAlways; 152 } 153 154 public String getName(final File f) { 155 final FileInfo info = getFileInfoFor(f); 156 if (info.displayName != null) return info.displayName; 157 158 final String nativeDisplayName = getNativeDisplayName(info.pathBytes, info.isDirectory); 159 if (nativeDisplayName != null) { 160 info.displayName = nativeDisplayName; 161 return nativeDisplayName; 162 } 163 164 final String displayName = f.getName(); 165 if (f.isDirectory() && fFileChooserUI.getFileChooser().getFileSystemView().isRoot(f)) { 166 final String localMachineName = getMachineName(); 167 info.displayName = localMachineName; 168 return localMachineName; 169 } 170 171 info.displayName = displayName; 172 return displayName; 173 } 174 175 public String getDescription(final File f) { 176 return f.getName(); 177 } 178 179 public String getTypeDescription(final File f) { 180 if (f.isDirectory()) return _directoryDescriptionText(); 181 return _fileDescriptionText(); 182 } 183 184 public Icon getIcon(final File f) { 185 final FileInfo info = getFileInfoFor(f); 186 if (info.icon != null) return info.icon; 187 188 if (f == null) { 189 info.icon = AquaIcon.SystemIcon.getDocumentIconUIResource(); 190 } else { 191 // Look for the document's icon 192 final AquaIcon.FileIcon fileIcon = new AquaIcon.FileIcon(f); 193 info.icon = fileIcon; 194 if (!fileIcon.hasIconRef()) { 195 // Fall back on the default icons 196 if (f.isDirectory()) { 197 if (fFileChooserUI.getFileChooser().getFileSystemView().isRoot(f)) { 198 info.icon = AquaIcon.SystemIcon.getComputerIconUIResource(); 199 } else if (f.getParent() == null || f.getParent().equals("/")) { 200 info.icon = AquaIcon.SystemIcon.getHardDriveIconUIResource(); 201 } else { 202 info.icon = AquaIcon.SystemIcon.getFolderIconUIResource(); 203 } 204 } else { 205 info.icon = AquaIcon.SystemIcon.getDocumentIconUIResource(); 206 } 207 } 208 } 209 210 return info.icon; 211 } 212 213 // aliases are traversable though they aren't directories 214 public Boolean isTraversable(final File f) { 215 if (f.isDirectory()) { 216 // Doesn't matter if it's a package or app, because they're traversable 217 if (_packageIsTraversable() && _applicationIsTraversable()) { 218 return Boolean.TRUE; 219 } else if (!_packageIsTraversable() && !_applicationIsTraversable()) { 220 if (isPackage(f) || isApplication(f)) return Boolean.FALSE; 221 } else if (!_applicationIsTraversable()) { 222 if (isApplication(f)) return Boolean.FALSE; 223 } else if (!_packageIsTraversable()) { 224 // [3101730] All applications are packages, but not all packages are applications. 225 if (isPackage(f) && !isApplication(f)) return Boolean.FALSE; 226 } 227 228 // We're allowed to traverse it 229 return Boolean.TRUE; 230 } 231 232 if (isAlias(f)) { 233 final File realFile = resolveAlias(f); 234 return realFile.isDirectory() ? Boolean.TRUE : Boolean.FALSE; 235 } 236 237 return Boolean.FALSE; 238 } 239 240 int getLSInfoFor(final File f) { 241 final FileInfo info = getFileInfoFor(f); 242 243 if (info.launchServicesInfo == UNINITALIZED_LS_INFO) { 244 info.launchServicesInfo = getNativeLSInfo(info.pathBytes, info.isDirectory); 245 } 246 247 return info.launchServicesInfo; 248 } 249 250 boolean isAlias(final File f) { 251 final int lsInfo = getLSInfoFor(f); 252 return ((lsInfo & kLSItemInfoIsAliasFile) != 0) && ((lsInfo & kLSItemInfoIsSymlink) == 0); 253 } 254 255 boolean isApplication(final File f) { 256 return (getLSInfoFor(f) & kLSItemInfoIsApplication) != 0; 257 } 258 259 boolean isPackage(final File f) { 260 return (getLSInfoFor(f) & kLSItemInfoIsPackage) != 0; 261 } 262 263 /** 264 * Things that need to be handled: 265 * -Change getFSRef to use CFURLRef instead of FSPathMakeRef 266 * -Use the HFS-style path from CFURLRef in resolveAlias() to avoid 267 * path length limitations 268 * -In resolveAlias(), simply resolve immediately if this is an alias 269 */ 270 271 /** 272 * Returns the actual file represented by this object. This will 273 * resolve any aliases in the path, including this file if it is an 274 * alias. No alias resolution requiring user interaction (e.g. 275 * mounting servers) will occur. Note that aliases to servers may 276 * take a significant amount of time to resolve. This method 277 * currently does not have any provisions for a more fine-grained 278 * timeout for alias resolution beyond that used by the system. 279 * 280 * In the event of a path that does not contain any aliases, or if the file 281 * does not exist, this method will return the file that was passed in. 282 * @return The canonical path to the file 283 * @throws IOException If an I/O error occurs while attempting to 284 * construct the path 285 */ 286 File resolveAlias(final File mFile) { 287 // If the file exists and is not an alias, there aren't 288 // any aliases along its path, so the standard version 289 // of getCanonicalPath() will work. 290 if (mFile.exists() && !isAlias(mFile)) { 291 if (DEBUG) System.out.println("not an alias"); 292 return mFile; 293 } 294 295 // If it doesn't exist, either there's an alias in the 296 // path or this is an alias. Traverse the path and 297 // resolve all aliases in it. 298 final LinkedList<String> components = getPathComponents(mFile); 299 if (components == null) { 300 if (DEBUG) System.out.println("getPathComponents is null "); 301 return mFile; 302 } 303 304 File file = new File("/"); 305 for (final String nextComponent : components) { 306 file = new File(file, nextComponent); 307 final FileInfo info = getFileInfoFor(file); 308 309 // If any point along the way doesn't exist, 310 // just return the file. 311 if (!file.exists()) { return mFile; } 312 313 if (isAlias(file)) { 314 // Resolve it! 315 final String path = getNativePathForResolvedAlias(info.pathBytes, info.isDirectory); 316 317 // <rdar://problem/3582601> If the alias doesn't resolve (on a non-existent volume, for example) 318 // just return the file. 319 if (path == null) return mFile; 320 321 file = new File(path); 322 } 323 } 324 325 return file; 326 } 327 328 /** 329 * Returns a linked list of Strings consisting of the components of 330 * the path of this file, in order, including the filename as the 331 * last element. The first element in the list will be the first 332 * directory in the path, or "". 333 * @return A linked list of the components of this file's path 334 */ 335 private static LinkedList<String> getPathComponents(final File mFile) { 336 final LinkedList<String> componentList = new LinkedList<String>(); 337 String parent; 338 339 File file = new File(mFile.getAbsolutePath()); 340 componentList.add(0, file.getName()); 341 while ((parent = file.getParent()) != null) { 342 file = new File(parent); 343 componentList.add(0, file.getName()); 344 } 345 return componentList; 346 } 347 }