ClassPath.java revision 10967:e336cbd8b15e
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
26package sun.tools.java;
27
28import java.io.File;
29import java.io.IOException;
30import java.io.UncheckedIOException;
31import java.util.zip.*;
32import java.util.Enumeration;
33import java.util.Map;
34import java.util.HashMap;
35import java.util.Hashtable;
36import java.util.Set;
37import java.util.LinkedHashSet;
38import java.net.URI;
39import java.nio.file.DirectoryStream;
40import java.nio.file.Files;
41import java.nio.file.FileSystem;
42import java.nio.file.FileSystems;
43import java.nio.file.Path;
44import 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 */
54public
55class 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 */
273abstract 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
280final 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
353final 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
384final 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}
484