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