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