Path: blob/master/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ClassFileReader.java
41161 views
/*1* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.tools.jdeps;2627import com.sun.tools.classfile.AccessFlags;28import com.sun.tools.classfile.ClassFile;29import com.sun.tools.classfile.ConstantPoolException;30import com.sun.tools.classfile.Dependencies.ClassFileError;3132import 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;53import java.util.zip.ZipFile;5455/**56* ClassFileReader reads ClassFile(s) of a given path that can be57* a .class file, a directory, or a JAR file.58*/59public class ClassFileReader implements Closeable {60/**61* Returns a ClassFileReader instance of a given path.62*/63public static ClassFileReader newInstance(Path path, Runtime.Version version) throws IOException {64if (Files.notExists(path)) {65throw new FileNotFoundException(path.toString());66}6768if (Files.isDirectory(path)) {69return new DirectoryReader(path);70} else if (path.getFileName().toString().endsWith(".jar")) {71return new JarFileReader(path, version);72} else {73return new ClassFileReader(path);74}75}7677/**78* Returns a ClassFileReader instance of a given FileSystem and path.79*80* This method is used for reading classes from jrtfs.81*/82public static ClassFileReader newInstance(FileSystem fs, Path path) throws IOException {83return new DirectoryReader(fs, path);84}8586protected final Path path;87protected final String baseFileName;88protected Set<String> entries; // binary names8990protected final List<String> skippedEntries = new ArrayList<>();91protected ClassFileReader(Path path) {92this.path = path;93this.baseFileName = path.getFileName() != null94? path.getFileName().toString()95: path.toString();96}9798public String getFileName() {99return baseFileName;100}101102public List<String> skippedEntries() {103return skippedEntries;104}105106/**107* Returns all entries in this archive.108*/109public Set<String> entries() {110Set<String> es = this.entries;111if (es == null) {112// lazily scan the entries113this.entries = scan();114}115return this.entries;116}117118/**119* Returns the ClassFile matching the given binary name120* or a fully-qualified class name.121*/122public ClassFile getClassFile(String name) throws IOException {123if (name.indexOf('.') > 0) {124int i = name.lastIndexOf('.');125String pathname = name.replace('.', File.separatorChar) + ".class";126if (baseFileName.equals(pathname) ||127baseFileName.equals(pathname.substring(0, i) + "$" +128pathname.substring(i+1, pathname.length()))) {129return readClassFile(path);130}131} else {132if (baseFileName.equals(name.replace('/', File.separatorChar) + ".class")) {133return readClassFile(path);134}135}136return null;137}138139public Iterable<ClassFile> getClassFiles() throws IOException {140return FileIterator::new;141}142143protected ClassFile readClassFile(Path p) throws IOException {144InputStream is = null;145try {146is = Files.newInputStream(p);147return ClassFile.read(is);148} catch (ConstantPoolException e) {149throw new ClassFileError(e);150} finally {151if (is != null) {152is.close();153}154}155}156157protected Set<String> scan() {158try {159ClassFile cf = ClassFile.read(path);160String name = cf.access_flags.is(AccessFlags.ACC_MODULE)161? "module-info" : cf.getName();162return Collections.singleton(name);163} catch (ConstantPoolException|IOException e) {164throw new ClassFileError(e);165}166}167168static boolean isClass(Path file) {169String fn = file.getFileName().toString();170return fn.endsWith(".class");171}172173@Override174public void close() throws IOException {175}176177class FileIterator implements Iterator<ClassFile> {178int count;179FileIterator() {180this.count = 0;181}182public boolean hasNext() {183return count == 0 && baseFileName.endsWith(".class");184}185186public ClassFile next() {187if (!hasNext()) {188throw new NoSuchElementException();189}190try {191ClassFile cf = readClassFile(path);192count++;193return cf;194} catch (IOException e) {195throw new ClassFileError(e);196}197}198199public void remove() {200throw new UnsupportedOperationException("Not supported yet.");201}202}203204public String toString() {205return path.toString();206}207208private static class DirectoryReader extends ClassFileReader {209protected final String fsSep;210DirectoryReader(Path path) throws IOException {211this(FileSystems.getDefault(), path);212}213DirectoryReader(FileSystem fs, Path path) throws IOException {214super(path);215this.fsSep = fs.getSeparator();216}217218protected Set<String> scan() {219try (Stream<Path> stream = Files.walk(path, Integer.MAX_VALUE)) {220return stream.filter(ClassFileReader::isClass)221.map(path::relativize)222.map(Path::toString)223.map(p -> p.replace(File.separatorChar, '/'))224.collect(Collectors.toSet());225} catch (IOException e) {226throw new UncheckedIOException(e);227}228}229230public ClassFile getClassFile(String name) throws IOException {231if (name.indexOf('.') > 0) {232int i = name.lastIndexOf('.');233String pathname = name.replace(".", fsSep) + ".class";234Path p = path.resolve(pathname);235if (Files.notExists(p)) {236p = path.resolve(pathname.substring(0, i) + "$" +237pathname.substring(i+1, pathname.length()));238}239if (Files.exists(p)) {240return readClassFile(p);241}242} else {243Path p = path.resolve(name + ".class");244if (Files.exists(p)) {245return readClassFile(p);246}247}248return null;249}250251public Iterable<ClassFile> getClassFiles() throws IOException {252final Iterator<ClassFile> iter = new DirectoryIterator();253return () -> iter;254}255256class DirectoryIterator implements Iterator<ClassFile> {257private final List<Path> entries;258private int index = 0;259DirectoryIterator() throws IOException {260List<Path> paths = null;261try (Stream<Path> stream = Files.walk(path, Integer.MAX_VALUE)) {262paths = stream.filter(ClassFileReader::isClass).toList();263264}265this.entries = paths;266this.index = 0;267}268269public boolean hasNext() {270return index != entries.size();271}272273public ClassFile next() {274if (!hasNext()) {275throw new NoSuchElementException();276}277Path path = entries.get(index++);278try {279return readClassFile(path);280} catch (IOException e) {281throw new ClassFileError(e);282}283}284285public void remove() {286throw new UnsupportedOperationException("Not supported yet.");287}288}289}290291static class JarFileReader extends ClassFileReader {292private final JarFile jarfile;293private final Runtime.Version version;294295JarFileReader(Path path, Runtime.Version version) throws IOException {296this(path, openJarFile(path.toFile(), version), version);297}298299JarFileReader(Path path, JarFile jf, Runtime.Version version) throws IOException {300super(path);301this.jarfile = jf;302this.version = version;303}304305@Override306public void close() throws IOException {307jarfile.close();308}309310private static JarFile openJarFile(File f, Runtime.Version version)311throws IOException {312JarFile jf;313if (version == null) {314jf = new JarFile(f, false);315if (jf.isMultiRelease()) {316throw new MultiReleaseException("err.multirelease.option.notfound", f.getName());317}318} else {319jf = new JarFile(f, false, ZipFile.OPEN_READ, version);320}321return jf;322}323324protected Set<String> scan() {325try (JarFile jf = openJarFile(path.toFile(), version)) {326return jf.versionedStream().map(JarEntry::getName)327.filter(n -> n.endsWith(".class"))328.collect(Collectors.toSet());329} catch (IOException e) {330throw new UncheckedIOException(e);331}332}333334public ClassFile getClassFile(String name) throws IOException {335if (name.indexOf('.') > 0) {336int i = name.lastIndexOf('.');337String entryName = name.replace('.', '/') + ".class";338JarEntry e = jarfile.getJarEntry(entryName);339if (e == null) {340e = jarfile.getJarEntry(entryName.substring(0, i) + "$"341+ entryName.substring(i + 1, entryName.length()));342}343if (e != null) {344return readClassFile(jarfile, e);345}346} else {347JarEntry e = jarfile.getJarEntry(name + ".class");348if (e != null) {349return readClassFile(jarfile, e);350}351}352return null;353}354355protected ClassFile readClassFile(JarFile jarfile, JarEntry e) throws IOException {356try (InputStream is = jarfile.getInputStream(e)) {357ClassFile cf = ClassFile.read(is);358if (jarfile.isMultiRelease()) {359VersionHelper.add(jarfile, e, cf);360}361return cf;362} catch (ConstantPoolException ex) {363throw new ClassFileError(ex);364}365}366367public Iterable<ClassFile> getClassFiles() throws IOException {368final Iterator<ClassFile> iter = new JarFileIterator(this, jarfile);369return () -> iter;370}371}372373class JarFileIterator implements Iterator<ClassFile> {374protected final JarFileReader reader;375protected Iterator<JarEntry> entries;376protected JarFile jf;377protected JarEntry nextEntry;378protected ClassFile cf;379JarFileIterator(JarFileReader reader) {380this(reader, null);381}382JarFileIterator(JarFileReader reader, JarFile jarfile) {383this.reader = reader;384setJarFile(jarfile);385}386387void setJarFile(JarFile jarfile) {388if (jarfile == null) return;389390this.jf = jarfile;391this.entries = jarfile.versionedStream().iterator();392this.nextEntry = nextEntry();393}394395public boolean hasNext() {396if (nextEntry != null && cf != null) {397return true;398}399while (nextEntry != null) {400try {401cf = reader.readClassFile(jf, nextEntry);402return true;403} catch (ClassFileError | IOException ex) {404skippedEntries.add(String.format("%s: %s (%s)",405ex.getMessage(),406nextEntry.getName(),407jf.getName()));408}409nextEntry = nextEntry();410}411return false;412}413414public ClassFile next() {415if (!hasNext()) {416throw new NoSuchElementException();417}418ClassFile classFile = cf;419cf = null;420nextEntry = nextEntry();421return classFile;422}423424protected JarEntry nextEntry() {425while (entries.hasNext()) {426JarEntry e = entries.next();427String name = e.getName();428if (name.endsWith(".class")) {429return e;430}431}432return null;433}434435public void remove() {436throw new UnsupportedOperationException("Not supported yet.");437}438}439private static final String MODULE_INFO = "module-info.class";440}441442443