1 /* 2 * Copyright (c) 1994, 2012, 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 sun.tools.java; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.UncheckedIOException; 31 import java.util.zip.*; 32 import java.util.Enumeration; 33 import java.util.Map; 34 import java.util.HashMap; 35 import java.util.Hashtable; 36 import java.util.Set; 37 import java.util.LinkedHashSet; 38 import java.net.URI; 39 import java.nio.file.DirectoryStream; 40 import java.nio.file.Files; 41 import java.nio.file.FileSystem; 42 import java.nio.file.FileSystems; 43 import java.nio.file.Path; 44 import java.nio.file.ProviderNotFoundException; 45 import java.nio.file.spi.FileSystemProvider; 46 47 /** 48 * This class is used to represent a class path, which can contain both 49 * directories and zip files. 50 * 51 * WARNING: The contents of this source file are not part of any 52 * supported API. Code that depends on them does so at its own risk: 53 * they are subject to change or removal without notice. 54 */ 55 public 56 class ClassPath { getJrtFileSystem()57 private FileSystem getJrtFileSystem() { 58 return FileSystems.getFileSystem(URI.create("jrt:/")); 59 } 60 61 static final char dirSeparator = File.pathSeparatorChar; 62 63 /** 64 * The original class path string 65 */ 66 String pathstr; 67 68 /** 69 * List of class path entries 70 */ 71 private ClassPathEntry[] path; 72 73 /** 74 * Build a class path from the specified path string 75 */ ClassPath(String pathstr)76 public ClassPath(String pathstr) { 77 init(pathstr); 78 } 79 80 /** 81 * Build a class path from the specified array of class path 82 * element strings. This constructor, and the corresponding 83 * "init" method, were added as part of the fix for 6473331, which 84 * adds support for Class-Path manifest entries in JAR files to 85 * rmic. It is conceivable that the value of a Class-Path 86 * manifest entry will contain a path separator, which would cause 87 * incorrect behavior if the expanded path were passed to the 88 * previous constructor as a single path-separator-delimited 89 * string; use of this constructor avoids that problem. 90 */ ClassPath(String[] patharray)91 public ClassPath(String[] patharray) { 92 init(patharray); 93 } 94 95 /** 96 * Build a default class path from the path strings specified by 97 * the properties sun.boot.class.path and env.class.path, in that 98 * order. 99 */ ClassPath()100 public ClassPath() { 101 // though this property is removed. Check for null and use only 102 // if it is not null (when bootstrap JDK is used). 103 String syscp = System.getProperty("sun.boot.class.path"); 104 String envcp = System.getProperty("env.class.path"); 105 if (envcp == null) envcp = "."; 106 107 // add syscp only if not null! 108 String cp = syscp == null? envcp : (syscp + File.pathSeparator + envcp); 109 init(cp); 110 } 111 init(String pathstr)112 private void init(String pathstr) { 113 int i, j, n; 114 // Save original class path string 115 this.pathstr = pathstr; 116 117 if (pathstr.length() == 0) { 118 this.path = new ClassPathEntry[0]; 119 } 120 121 // Count the number of path separators 122 i = n = 0; 123 while ((i = pathstr.indexOf(dirSeparator, i)) != -1) { 124 n++; i++; 125 } 126 // Build the class path 127 ClassPathEntry[] path = new ClassPathEntry[n+1]; 128 129 int len = pathstr.length(); 130 for (i = n = 0; i < len; i = j + 1) { 131 if ((j = pathstr.indexOf(dirSeparator, i)) == -1) { 132 j = len; 133 } 134 if (i == j) { 135 path[n++] = new DirClassPathEntry(new File(".")); 136 } else { 137 String filename = pathstr.substring(i, j); 138 File file = new File(filename); 139 if (file.isFile()) { 140 try { 141 ZipFile zip = new ZipFile(file); 142 path[n++] = new ZipClassPathEntry(zip); 143 } catch (ZipException e) { 144 } catch (IOException e) { 145 // Ignore exceptions, at least for now... 146 } 147 } else { 148 path[n++] = new DirClassPathEntry(file); 149 } 150 } 151 } 152 153 // add jrt file system at the end 154 try { 155 FileSystem fs = getJrtFileSystem(); 156 path[n++] = new JrtClassPathEntry(fs); 157 } catch (ProviderNotFoundException ignored) { 158 // this could happen during jdk build with earlier JDK as bootstrap 159 } 160 161 // Trim class path to exact size 162 this.path = new ClassPathEntry[n]; 163 System.arraycopy((Object)path, 0, (Object)this.path, 0, n); 164 } 165 init(String[] patharray)166 private void init(String[] patharray) { 167 // Save original class path string 168 if (patharray.length == 0) { 169 this.pathstr = ""; 170 } else { 171 StringBuilder sb = new StringBuilder(patharray[0]); 172 for (int i = 1; i < patharray.length; i++) { 173 sb.append(File.pathSeparatorChar); 174 sb.append(patharray[i]); 175 } 176 this.pathstr = sb.toString(); 177 } 178 179 // Build the class path 180 ClassPathEntry[] path = new ClassPathEntry[patharray.length + 1]; 181 int n = 0; 182 for (String name : patharray) { 183 File file = new File(name); 184 if (file.isFile()) { 185 try { 186 ZipFile zip = new ZipFile(file); 187 path[n++] = new ZipClassPathEntry(zip); 188 } catch (ZipException e) { 189 } catch (IOException e) { 190 // Ignore exceptions, at least for now... 191 } 192 } else { 193 path[n++] = new DirClassPathEntry(file); 194 } 195 } 196 197 // add jrt file system at the end 198 try { 199 FileSystem fs = getJrtFileSystem(); 200 path[n++] = new JrtClassPathEntry(fs); 201 } catch (ProviderNotFoundException ignored) { 202 // this could happen with earlier version of JDK used as bootstrap 203 } 204 205 // Trim class path to exact size 206 this.path = new ClassPathEntry[n]; 207 System.arraycopy((Object)path, 0, (Object)this.path, 0, n); 208 } 209 210 /** 211 * Find the specified directory in the class path 212 */ getDirectory(String name)213 public ClassFile getDirectory(String name) { 214 return getFile(name, true); 215 } 216 217 /** 218 * Load the specified file from the class path 219 */ getFile(String name)220 public ClassFile getFile(String name) { 221 return getFile(name, false); 222 } 223 224 private final String fileSeparatorChar = "" + File.separatorChar; 225 getFile(String name, boolean isDirectory)226 private ClassFile getFile(String name, boolean isDirectory) { 227 String subdir = name; 228 String basename = ""; 229 if (!isDirectory) { 230 int i = name.lastIndexOf(File.separatorChar); 231 subdir = name.substring(0, i + 1); 232 basename = name.substring(i + 1); 233 } else if (!subdir.isEmpty() 234 && !subdir.endsWith(fileSeparatorChar)) { 235 // zip files are picky about "foo" vs. "foo/". 236 // also, the getFiles caches are keyed with a trailing / 237 subdir = subdir + File.separatorChar; 238 name = subdir; // Note: isDirectory==true & basename=="" 239 } 240 for (int i = 0; i < path.length; i++) { 241 ClassFile cf = path[i].getFile(name, subdir, basename, isDirectory); 242 if (cf != null) { 243 return cf; 244 } 245 } 246 return null; 247 } 248 249 /** 250 * Returns list of files given a package name and extension. 251 */ getFiles(String pkg, String ext)252 public Enumeration<ClassFile> getFiles(String pkg, String ext) { 253 Hashtable<String, ClassFile> files = new Hashtable<>(); 254 for (int i = path.length; --i >= 0; ) { 255 path[i].fillFiles(pkg, ext, files); 256 } 257 return files.elements(); 258 } 259 260 /** 261 * Release resources. 262 */ close()263 public void close() throws IOException { 264 for (int i = path.length; --i >= 0; ) { 265 path[i].close(); 266 } 267 } 268 269 /** 270 * Returns original class path string 271 */ toString()272 public String toString() { 273 return pathstr; 274 } 275 } 276 277 /** 278 * A class path entry, which can either be a directory or an open zip file or an open jimage filesystem. 279 */ 280 abstract class ClassPathEntry { getFile(String name, String subdir, String basename, boolean isDirectory)281 abstract ClassFile getFile(String name, String subdir, String basename, boolean isDirectory); fillFiles(String pkg, String ext, Hashtable<String, ClassFile> files)282 abstract void fillFiles(String pkg, String ext, Hashtable<String, ClassFile> files); close()283 abstract void close() throws IOException; 284 } 285 286 // a ClassPathEntry that represents a directory 287 final class DirClassPathEntry extends ClassPathEntry { 288 private final File dir; 289 DirClassPathEntry(File dir)290 DirClassPathEntry(File dir) { 291 this.dir = dir; 292 } 293 294 private final Hashtable<String, String[]> subdirs = new Hashtable<>(29); // cache of sub-directory listings: getFiles(String subdir)295 private String[] getFiles(String subdir) { 296 String files[] = subdirs.get(subdir); 297 if (files == null) { 298 files = computeFiles(subdir); 299 subdirs.put(subdir, files); 300 } 301 return files; 302 } 303 computeFiles(String subdir)304 private String[] computeFiles(String subdir) { 305 File sd = new File(dir.getPath(), subdir); 306 String[] files = null; 307 if (sd.isDirectory()) { 308 files = sd.list(); 309 if (files == null) { 310 // should not happen, but just in case, fail silently 311 files = new String[0]; 312 } 313 if (files.length == 0) { 314 String nonEmpty[] = { "" }; 315 files = nonEmpty; 316 } 317 } else { 318 files = new String[0]; 319 } 320 return files; 321 } 322 getFile(String name, String subdir, String basename, boolean isDirectory)323 ClassFile getFile(String name, String subdir, String basename, boolean isDirectory) { 324 File file = new File(dir.getPath(), name); 325 String list[] = getFiles(subdir); 326 if (isDirectory) { 327 if (list.length > 0) { 328 return ClassFile.newClassFile(file); 329 } 330 } else { 331 for (int j = 0; j < list.length; j++) { 332 if (basename.equals(list[j])) { 333 // Don't bother checking !file.isDir, 334 // since we only look for names which 335 // cannot already be packages (foo.java, etc). 336 return ClassFile.newClassFile(file); 337 } 338 } 339 } 340 return null; 341 } 342 fillFiles(String pkg, String ext, Hashtable<String, ClassFile> files)343 void fillFiles(String pkg, String ext, Hashtable<String, ClassFile> files) { 344 String[] list = getFiles(pkg); 345 for (int j = 0; j < list.length; j++) { 346 String name = list[j]; 347 if (name.endsWith(ext)) { 348 name = pkg + File.separatorChar + name; 349 File file = new File(dir.getPath(), name); 350 files.put(name, ClassFile.newClassFile(file)); 351 } 352 } 353 } 354 close()355 void close() throws IOException { 356 } 357 } 358 359 // a ClassPathEntry that represents a .zip or a .jar file 360 final class ZipClassPathEntry extends ClassPathEntry { 361 private final ZipFile zip; 362 ZipClassPathEntry(ZipFile zip)363 ZipClassPathEntry(ZipFile zip) { 364 this.zip = zip; 365 } 366 close()367 void close() throws IOException { 368 zip.close(); 369 } 370 getFile(String name, String subdir, String basename, boolean isDirectory)371 ClassFile getFile(String name, String subdir, String basename, boolean isDirectory) { 372 String newname = name.replace(File.separatorChar, '/'); 373 ZipEntry entry = zip.getEntry(newname); 374 return entry != null? ClassFile.newClassFile(zip, entry) : null; 375 } 376 fillFiles(String pkg, String ext, Hashtable<String, ClassFile> files)377 void fillFiles(String pkg, String ext, Hashtable<String, ClassFile> files) { 378 Enumeration<? extends ZipEntry> e = zip.entries(); 379 while (e.hasMoreElements()) { 380 ZipEntry entry = (ZipEntry)e.nextElement(); 381 String name = entry.getName(); 382 name = name.replace('/', File.separatorChar); 383 if (name.startsWith(pkg) && name.endsWith(ext)) { 384 files.put(name, ClassFile.newClassFile(zip, entry)); 385 } 386 } 387 } 388 } 389 390 // a ClassPathEntry that represents jrt file system 391 final class JrtClassPathEntry extends ClassPathEntry { 392 private final FileSystem fs; 393 // package name to package directory path mapping (lazily filled) 394 private final Map<String, Path> pkgDirs; 395 JrtClassPathEntry(FileSystem fs)396 JrtClassPathEntry(FileSystem fs) { 397 this.fs = fs; 398 this.pkgDirs = new HashMap<>(); 399 } 400 close()401 void close() throws IOException { 402 } 403 404 // from pkgName (internal separator '/') to it's Path in jrtfs getPackagePath(String pkgName)405 synchronized Path getPackagePath(String pkgName) throws IOException { 406 // check the cache first 407 if (pkgDirs.containsKey(pkgName)) { 408 return pkgDirs.get(pkgName); 409 } 410 411 Path pkgLink = fs.getPath("/packages/" + pkgName.replace('/', '.')); 412 // check if /packages/$PACKAGE directory exists 413 if (Files.isDirectory(pkgLink)) { 414 try (DirectoryStream<Path> stream = Files.newDirectoryStream(pkgLink)) { 415 for (Path p : stream) { 416 // find first symbolic link to module directory 417 if (Files.isSymbolicLink(p)) { 418 Path modDir = Files.readSymbolicLink(p); 419 if (Files.isDirectory(modDir)) { 420 // get package subdirectory under /modules/$MODULE/ 421 Path pkgDir = fs.getPath(modDir.toString() + "/" + pkgName); 422 if (Files.isDirectory(pkgDir)) { 423 // it is a package directory only if contains 424 // at least one .class file 425 try (DirectoryStream<Path> pstream = 426 Files.newDirectoryStream(pkgDir)) { 427 for (Path f : pstream) { 428 if (Files.isRegularFile(f) 429 && f.toString().endsWith(".class")) { 430 pkgDirs.put(pkgName, pkgDir); 431 return pkgDir; 432 } 433 } 434 } 435 } 436 } 437 } 438 } 439 } 440 } 441 442 return null; 443 } 444 445 // fully qualified (internal) class name to it's Path in jrtfs getClassPath(String clsName)446 Path getClassPath(String clsName) throws IOException { 447 int index = clsName.lastIndexOf('/'); 448 if (index == -1) { 449 return null; 450 } 451 Path pkgPath = getPackagePath(clsName.substring(0, index)); 452 return pkgPath == null? null : fs.getPath(pkgPath + "/" + clsName.substring(index + 1)); 453 } 454 getFile(String name, String subdir, String basename, boolean isDirectory)455 ClassFile getFile(String name, String subdir, String basename, boolean isDirectory) { 456 try { 457 name = name.replace(File.separatorChar, '/'); 458 Path cp = getClassPath(name); 459 return cp == null? null : ClassFile.newClassFile(cp); 460 } catch (IOException ioExp) { 461 throw new UncheckedIOException(ioExp); 462 } 463 } 464 fillFiles(String pkg, String ext, Hashtable<String, ClassFile> files)465 void fillFiles(String pkg, String ext, Hashtable<String, ClassFile> files) { 466 Path dir; 467 try { 468 dir = getPackagePath(pkg); 469 if (dir == null) { 470 return; 471 } 472 } catch (IOException ioExp) { 473 throw new UncheckedIOException(ioExp); 474 } 475 476 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { 477 for (Path p : stream) { 478 String name = p.toString(); 479 name = name.replace('/', File.separatorChar); 480 if (name.startsWith(pkg) && name.endsWith(ext)) { 481 files.put(name, ClassFile.newClassFile(p)); 482 } 483 } 484 } catch (IOException ioExp) { 485 throw new UncheckedIOException(ioExp); 486 } 487 } 488 } 489