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 }