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