1 /*
2  * Copyright (c) 2016, 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 com.sun.tools.jdeprscan.scan;
27 
28 import com.sun.tools.classfile.ClassFile;
29 import com.sun.tools.classfile.ConstantPoolException;
30 
31 import java.io.IOException;
32 import java.net.URI;
33 import java.nio.file.FileSystem;
34 import java.nio.file.FileSystems;
35 import java.nio.file.Files;
36 import java.nio.file.NoSuchFileException;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.Optional;
42 import java.util.jar.JarEntry;
43 import java.util.jar.JarFile;
44 import java.util.stream.Stream;
45 
46 /**
47  * A simple search path for classes.
48  */
49 public class ClassFinder {
50     final List<PathEntry> list = new ArrayList<>();
51     final boolean verbose;
52 
ClassFinder(boolean verbose)53     public ClassFinder(boolean verbose) {
54         this.verbose = verbose;
55     }
56 
57     /**
58      * Adds a directory to this finder's search path, ignoring errors.
59      *
60      * @param dirName the directory to add
61      */
addDir(String dirName)62     public void addDir(String dirName) {
63         Path dir = Paths.get(dirName);
64 
65         if (Files.isDirectory(dir)) {
66             list.add(new DirPathEntry(dir));
67         }
68     }
69 
70     /**
71      * Adds a jar file to this finder's search path, ignoring errors.
72      *
73      * @param jarName the jar file name to add
74      */
addJar(String jarName)75     public void addJar(String jarName) {
76         try {
77             list.add(new JarPathEntry(new JarFile(jarName)));
78         } catch (IOException ignore) { }
79     }
80 
81     /**
82      * Adds the JRT filesystem to this finder's search path.
83      */
addJrt()84     public void addJrt() {
85         list.add(new JrtPathEntry());
86     }
87 
88     /**
89      * Searches the class path for a class with the given name,
90      * returning a ClassFile for it. Returns null if not found.
91      *
92      * @param className the class to search for
93      * @return a ClassFile instance, or null if not found
94      */
find(String className)95     public ClassFile find(String className) {
96         for (PathEntry pe : list) {
97             ClassFile cf = pe.find(className);
98             if (cf != null) {
99                 return cf;
100             }
101         }
102         return null;
103     }
104 
105     /**
106      * An entry in this finder's class path.
107      */
108     interface PathEntry {
109         /**
110          * Returns a ClassFile instance corresponding to this name,
111          * or null if it's not present in this entry.
112          *
113          * @param className the class to search for
114          * @return a ClassFile instance, or null if not found
115          */
find(String className)116         ClassFile find(String className);
117     }
118 
119     /**
120      * An entry that represents a jar file.
121      */
122     class JarPathEntry implements PathEntry {
123         final JarFile jarFile;
124 
JarPathEntry(JarFile jf)125         JarPathEntry(JarFile jf) {
126             jarFile = jf;
127         }
128 
129         @Override
find(String className)130         public ClassFile find(String className) {
131             JarEntry entry = jarFile.getJarEntry(className + ".class");
132             if (entry == null) {
133                 return null;
134             }
135             try {
136                 return ClassFile.read(jarFile.getInputStream(entry));
137             } catch (IOException | ConstantPoolException ex) {
138                 if (verbose) {
139                     ex.printStackTrace();
140                 }
141             }
142             return null;
143         }
144     }
145 
146     /**
147      * An entry that represents a directory containing a class hierarchy.
148      */
149     class DirPathEntry implements PathEntry {
150         final Path dir;
151 
DirPathEntry(Path dir)152         DirPathEntry(Path dir) {
153             this.dir = dir;
154         }
155 
156         @Override
find(String className)157         public ClassFile find(String className) {
158             Path classFileName = dir.resolve(className + ".class");
159             try {
160                 return ClassFile.read(classFileName);
161             } catch (NoSuchFileException nsfe) {
162                 // not found, return silently
163             } catch (IOException | ConstantPoolException ex) {
164                 if (verbose) {
165                     ex.printStackTrace();
166                 }
167             }
168             return null;
169         }
170     }
171 
172     /**
173      * An entry that represents the JRT filesystem in the running image.
174      *
175      * JRT filesystem structure is:
176      *     /packages/<dotted-pkgname>/<modlink>
177      * where modlink is a symbolic link to /modules/<modname> which is
178      * the top of the usual package-class hierarchy
179      */
180     class JrtPathEntry implements PathEntry {
181         final FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
182 
183         @Override
find(String className)184         public ClassFile find(String className) {
185             int end = className.lastIndexOf('/');
186             if (end < 0) {
187                 return null;
188             }
189             String pkg = "/packages/" + className.substring(0, end)
190                                                  .replace('/', '.');
191             try (Stream<Path> mods = Files.list(fs.getPath(pkg))) {
192                 Optional<Path> opath =
193                     mods.map(path -> path.resolve(className + ".class"))
194                         .filter(Files::exists)
195                         .findFirst();
196                 if (opath.isPresent()) {
197                     return ClassFile.read(opath.get());
198                 } else {
199                     return null;
200                 }
201             } catch (NoSuchFileException nsfe) {
202                 // not found, return silently
203             } catch (IOException | ConstantPoolException ex) {
204                 if (verbose) {
205                     ex.printStackTrace();
206                 }
207             }
208             return null;
209         }
210     }
211 }
212