ClassFileReader.java revision 3419:d71cfeef72eb
1/*
2 * Copyright (c) 2012, 2014, 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 com.sun.tools.jdeps;
27
28import com.sun.tools.classfile.ClassFile;
29import com.sun.tools.classfile.ConstantPoolException;
30import com.sun.tools.classfile.Dependencies.ClassFileError;
31
32import java.io.Closeable;
33import java.io.File;
34import java.io.FileNotFoundException;
35import java.io.IOException;
36import java.io.InputStream;
37import java.io.UncheckedIOException;
38import java.nio.file.FileSystem;
39import java.nio.file.FileSystems;
40import java.nio.file.Files;
41import java.nio.file.Path;
42import java.util.ArrayList;
43import java.util.Collections;
44import java.util.Enumeration;
45import java.util.Iterator;
46import java.util.List;
47import java.util.NoSuchElementException;
48import java.util.Set;
49import java.util.jar.JarEntry;
50import java.util.jar.JarFile;
51import java.util.stream.Collectors;
52import java.util.stream.Stream;
53
54/**
55 * ClassFileReader reads ClassFile(s) of a given path that can be
56 * a .class file, a directory, or a JAR file.
57 */
58public class ClassFileReader implements Closeable {
59    /**
60     * Returns a ClassFileReader instance of a given path.
61     */
62    public static ClassFileReader newInstance(Path path) throws IOException {
63        if (Files.notExists(path)) {
64            throw new FileNotFoundException(path.toString());
65        }
66
67        if (Files.isDirectory(path)) {
68            return new DirectoryReader(path);
69        } else if (path.getFileName().toString().endsWith(".jar")) {
70            return new JarFileReader(path);
71        } else {
72            return new ClassFileReader(path);
73        }
74    }
75
76    /**
77     * Returns a ClassFileReader instance of a given JarFile.
78     */
79    public static ClassFileReader newInstance(Path path, JarFile jf) throws IOException {
80        return new JarFileReader(path, jf);
81    }
82
83    /**
84     * Returns a ClassFileReader instance of a given FileSystem and path.
85     *
86     * This method is used for reading classes from jrtfs.
87     */
88    public static ClassFileReader newInstance(FileSystem fs, Path path) throws IOException {
89        return new DirectoryReader(fs, path);
90    }
91
92    protected final Path path;
93    protected final String baseFileName;
94    protected Set<String> entries; // binary names
95
96    protected final List<String> skippedEntries = new ArrayList<>();
97    protected ClassFileReader(Path path) {
98        this.path = path;
99        this.baseFileName = path.getFileName() != null
100                                ? path.getFileName().toString()
101                                : path.toString();
102    }
103
104    public String getFileName() {
105        return baseFileName;
106    }
107
108    public List<String> skippedEntries() {
109        return skippedEntries;
110    }
111
112    /**
113     * Returns all entries in this archive.
114     */
115    public Set<String> entries() {
116        Set<String> es = this.entries;
117        if (es == null) {
118            // lazily scan the entries
119            this.entries = scan();
120        }
121        return this.entries;
122    }
123
124    /**
125     * Returns the ClassFile matching the given binary name
126     * or a fully-qualified class name.
127     */
128    public ClassFile getClassFile(String name) throws IOException {
129        if (name.indexOf('.') > 0) {
130            int i = name.lastIndexOf('.');
131            String pathname = name.replace('.', File.separatorChar) + ".class";
132            if (baseFileName.equals(pathname) ||
133                    baseFileName.equals(pathname.substring(0, i) + "$" +
134                                        pathname.substring(i+1, pathname.length()))) {
135                return readClassFile(path);
136            }
137        } else {
138            if (baseFileName.equals(name.replace('/', File.separatorChar) + ".class")) {
139                return readClassFile(path);
140            }
141        }
142        return null;
143    }
144
145    public Iterable<ClassFile> getClassFiles() throws IOException {
146        return new Iterable<ClassFile>() {
147            public Iterator<ClassFile> iterator() {
148                return new FileIterator();
149            }
150        };
151    }
152
153    protected ClassFile readClassFile(Path p) throws IOException {
154        InputStream is = null;
155        try {
156            is = Files.newInputStream(p);
157            return ClassFile.read(is);
158        } catch (ConstantPoolException e) {
159            throw new ClassFileError(e);
160        } finally {
161            if (is != null) {
162                is.close();
163            }
164        }
165    }
166
167    protected Set<String> scan() {
168        try {
169            ClassFile cf = ClassFile.read(path);
170            return Collections.singleton(cf.getName());
171        } catch (ConstantPoolException|IOException e) {
172            throw new ClassFileError(e);
173        }
174    }
175
176    static boolean isClass(Path file) {
177        String fn = file.getFileName().toString();
178        return fn.endsWith(".class");
179    }
180
181    @Override
182    public void close() throws IOException {
183    }
184
185    class FileIterator implements Iterator<ClassFile> {
186        int count;
187        FileIterator() {
188            this.count = 0;
189        }
190        public boolean hasNext() {
191            return count == 0 && baseFileName.endsWith(".class");
192        }
193
194        public ClassFile next() {
195            if (!hasNext()) {
196                throw new NoSuchElementException();
197            }
198            try {
199                ClassFile cf = readClassFile(path);
200                count++;
201                return cf;
202            } catch (IOException e) {
203                throw new ClassFileError(e);
204            }
205        }
206
207        public void remove() {
208            throw new UnsupportedOperationException("Not supported yet.");
209        }
210    }
211
212    public String toString() {
213        return path.toString();
214    }
215
216    private static class DirectoryReader extends ClassFileReader {
217        protected final String fsSep;
218        DirectoryReader(Path path) throws IOException {
219            this(FileSystems.getDefault(), path);
220        }
221        DirectoryReader(FileSystem fs, Path path) throws IOException {
222            super(path);
223            this.fsSep = fs.getSeparator();
224        }
225
226        protected Set<String> scan() {
227            try (Stream<Path> stream = Files.walk(path, Integer.MAX_VALUE)) {
228                return stream.filter(ClassFileReader::isClass)
229                             .map(f -> path.relativize(f))
230                             .map(Path::toString)
231                             .map(p -> p.replace(File.separatorChar, '/'))
232                             .collect(Collectors.toSet());
233            } catch (IOException e) {
234                throw new UncheckedIOException(e);
235            }
236        }
237
238        public ClassFile getClassFile(String name) throws IOException {
239            if (name.indexOf('.') > 0) {
240                int i = name.lastIndexOf('.');
241                String pathname = name.replace(".", fsSep) + ".class";
242                Path p = path.resolve(pathname);
243                if (Files.notExists(p)) {
244                    p = path.resolve(pathname.substring(0, i) + "$" +
245                            pathname.substring(i+1, pathname.length()));
246                }
247                if (Files.exists(p)) {
248                    return readClassFile(p);
249                }
250            } else {
251                Path p = path.resolve(name + ".class");
252                if (Files.exists(p)) {
253                    return readClassFile(p);
254                }
255            }
256            return null;
257        }
258
259        public Iterable<ClassFile> getClassFiles() throws IOException {
260            final Iterator<ClassFile> iter = new DirectoryIterator();
261            return new Iterable<ClassFile>() {
262                public Iterator<ClassFile> iterator() {
263                    return iter;
264                }
265            };
266        }
267
268        class DirectoryIterator implements Iterator<ClassFile> {
269            private final List<Path> entries;
270            private int index = 0;
271            DirectoryIterator() throws IOException {
272                List<Path> paths = null;
273                try (Stream<Path> stream = Files.walk(path, Integer.MAX_VALUE)) {
274                    paths = stream.filter(ClassFileReader::isClass)
275                                  .collect(Collectors.toList());
276                }
277                this.entries = paths;
278                this.index = 0;
279            }
280
281            public boolean hasNext() {
282                return index != entries.size();
283            }
284
285            public ClassFile next() {
286                if (!hasNext()) {
287                    throw new NoSuchElementException();
288                }
289                Path path = entries.get(index++);
290                try {
291                    return readClassFile(path);
292                } catch (IOException e) {
293                    throw new ClassFileError(e);
294                }
295            }
296
297            public void remove() {
298                throw new UnsupportedOperationException("Not supported yet.");
299            }
300        }
301    }
302
303    static class JarFileReader extends ClassFileReader {
304        private final JarFile jarfile;
305        JarFileReader(Path path) throws IOException {
306            this(path, new JarFile(path.toFile(), false));
307        }
308
309        JarFileReader(Path path, JarFile jf) throws IOException {
310            super(path);
311            this.jarfile = jf;
312        }
313
314        @Override
315        public void close() throws IOException {
316            jarfile.close();
317        }
318
319        protected Set<String> scan() {
320            try (JarFile jf = new JarFile(path.toFile())) {
321                return jf.stream().map(JarEntry::getName)
322                         .filter(n -> n.endsWith(".class"))
323                         .collect(Collectors.toSet());
324            } catch (IOException e) {
325                throw new UncheckedIOException(e);
326            }
327        }
328
329        public ClassFile getClassFile(String name) throws IOException {
330            if (name.indexOf('.') > 0) {
331                int i = name.lastIndexOf('.');
332                String entryName = name.replace('.', '/') + ".class";
333                JarEntry e = jarfile.getJarEntry(entryName);
334                if (e == null) {
335                    e = jarfile.getJarEntry(entryName.substring(0, i) + "$"
336                            + entryName.substring(i + 1, entryName.length()));
337                }
338                if (e != null) {
339                    return readClassFile(jarfile, e);
340                }
341            } else {
342                JarEntry e = jarfile.getJarEntry(name + ".class");
343                if (e != null) {
344                    return readClassFile(jarfile, e);
345                }
346            }
347            return null;
348        }
349
350        protected ClassFile readClassFile(JarFile jarfile, JarEntry e) throws IOException {
351            InputStream is = null;
352            try {
353                is = jarfile.getInputStream(e);
354                return ClassFile.read(is);
355            } catch (ConstantPoolException ex) {
356                throw new ClassFileError(ex);
357            } finally {
358                if (is != null)
359                    is.close();
360            }
361        }
362
363        public Iterable<ClassFile> getClassFiles() throws IOException {
364            final Iterator<ClassFile> iter = new JarFileIterator(this, jarfile);
365            return new Iterable<ClassFile>() {
366                public Iterator<ClassFile> iterator() {
367                    return iter;
368                }
369            };
370        }
371    }
372
373    class JarFileIterator implements Iterator<ClassFile> {
374        protected final JarFileReader reader;
375        protected Enumeration<JarEntry> entries;
376        protected JarFile jf;
377        protected JarEntry nextEntry;
378        protected ClassFile cf;
379        JarFileIterator(JarFileReader reader) {
380            this(reader, null);
381        }
382        JarFileIterator(JarFileReader reader, JarFile jarfile) {
383            this.reader = reader;
384            setJarFile(jarfile);
385        }
386
387        void setJarFile(JarFile jarfile) {
388            if (jarfile == null) return;
389
390            this.jf = jarfile;
391            this.entries = jf.entries();
392            this.nextEntry = nextEntry();
393        }
394
395        public boolean hasNext() {
396            if (nextEntry != null && cf != null) {
397                return true;
398            }
399            while (nextEntry != null) {
400                try {
401                    cf = reader.readClassFile(jf, nextEntry);
402                    return true;
403                } catch (ClassFileError | IOException ex) {
404                    skippedEntries.add(nextEntry.getName());
405                }
406                nextEntry = nextEntry();
407            }
408            return false;
409        }
410
411        public ClassFile next() {
412            if (!hasNext()) {
413                throw new NoSuchElementException();
414            }
415            ClassFile classFile = cf;
416            cf = null;
417            nextEntry = nextEntry();
418            return classFile;
419        }
420
421        protected JarEntry nextEntry() {
422            while (entries.hasMoreElements()) {
423                JarEntry e = entries.nextElement();
424                String name = e.getName();
425                if (name.endsWith(".class")) {
426                    return e;
427                }
428            }
429            return null;
430        }
431
432        public void remove() {
433            throw new UnsupportedOperationException("Not supported yet.");
434        }
435    }
436    private static final String MODULE_INFO = "module-info.class";
437}
438