package com.sun.tools.jdeps;
import com.sun.tools.classfile.AccessFlags;
import com.sun.tools.classfile.ClassFile;
import com.sun.tools.classfile.ConstantPoolException;
import com.sun.tools.classfile.Dependencies.ClassFileError;
import jdk.internal.util.jar.VersionedStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipFile;
public class ClassFileReader implements Closeable {
public static ClassFileReader newInstance(Path path) throws IOException {
return newInstance(path, null);
}
public static ClassFileReader newInstance(Path path, Runtime.Version version) throws IOException {
if (Files.notExists(path)) {
throw new FileNotFoundException(path.toString());
}
if (Files.isDirectory(path)) {
return new DirectoryReader(path);
} else if (path.getFileName().toString().endsWith(".jar")) {
return new JarFileReader(path, version);
} else {
return new ClassFileReader(path);
}
}
public static ClassFileReader newInstance(FileSystem fs, Path path) throws IOException {
return new DirectoryReader(fs, path);
}
protected final Path path;
protected final String baseFileName;
protected Set<String> entries;
protected final List<String> skippedEntries = new ArrayList<>();
protected ClassFileReader(Path path) {
this.path = path;
this.baseFileName = path.getFileName() != null
? path.getFileName().toString()
: path.toString();
}
public String getFileName() {
return baseFileName;
}
public List<String> skippedEntries() {
return skippedEntries;
}
public Set<String> entries() {
Set<String> es = this.entries;
if (es == null) {
this.entries = scan();
}
return this.entries;
}
public ClassFile getClassFile(String name) throws IOException {
if (name.indexOf('.') > 0) {
int i = name.lastIndexOf('.');
String pathname = name.replace('.', File.separatorChar) + ".class";
if (baseFileName.equals(pathname) ||
baseFileName.equals(pathname.substring(0, i) + "$" +
pathname.substring(i+1, pathname.length()))) {
return readClassFile(path);
}
} else {
if (baseFileName.equals(name.replace('/', File.separatorChar) + ".class")) {
return readClassFile(path);
}
}
return null;
}
public Iterable<ClassFile> getClassFiles() throws IOException {
return FileIterator::new;
}
protected ClassFile readClassFile(Path p) throws IOException {
InputStream is = null;
try {
is = Files.newInputStream(p);
return ClassFile.read(is);
} catch (ConstantPoolException e) {
throw new ClassFileError(e);
} finally {
if (is != null) {
is.close();
}
}
}
protected Set<String> scan() {
try {
ClassFile cf = ClassFile.read(path);
String name = cf.access_flags.is(AccessFlags.ACC_MODULE)
? "module-info" : cf.getName();
return Collections.singleton(name);
} catch (ConstantPoolException|IOException e) {
throw new ClassFileError(e);
}
}
static boolean isClass(Path file) {
String fn = file.getFileName().toString();
return fn.endsWith(".class");
}
@Override
public void close() throws IOException {
}
class FileIterator implements Iterator<ClassFile> {
int count;
FileIterator() {
this.count = 0;
}
public boolean hasNext() {
return count == 0 && baseFileName.endsWith(".class");
}
public ClassFile next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
try {
ClassFile cf = readClassFile(path);
count++;
return cf;
} catch (IOException e) {
throw new ClassFileError(e);
}
}
public void remove() {
throw new UnsupportedOperationException("Not supported yet.");
}
}
public String toString() {
return path.toString();
}
private static class DirectoryReader extends ClassFileReader {
protected final String fsSep;
DirectoryReader(Path path) throws IOException {
this(FileSystems.getDefault(), path);
}
DirectoryReader(FileSystem fs, Path path) throws IOException {
super(path);
this.fsSep = fs.getSeparator();
}
protected Set<String> scan() {
try (Stream<Path> stream = Files.walk(path, Integer.MAX_VALUE)) {
return stream.filter(ClassFileReader::isClass)
.map(path::relativize)
.map(Path::toString)
.map(p -> p.replace(File.separatorChar, '/'))
.collect(Collectors.toSet());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public ClassFile getClassFile(String name) throws IOException {
if (name.indexOf('.') > 0) {
int i = name.lastIndexOf('.');
String pathname = name.replace(".", fsSep) + ".class";
Path p = path.resolve(pathname);
if (Files.notExists(p)) {
p = path.resolve(pathname.substring(0, i) + "$" +
pathname.substring(i+1, pathname.length()));
}
if (Files.exists(p)) {
return readClassFile(p);
}
} else {
Path p = path.resolve(name + ".class");
if (Files.exists(p)) {
return readClassFile(p);
}
}
return null;
}
public Iterable<ClassFile> getClassFiles() throws IOException {
final Iterator<ClassFile> iter = new DirectoryIterator();
return () -> iter;
}
class DirectoryIterator implements Iterator<ClassFile> {
private final List<Path> entries;
private int index = 0;
DirectoryIterator() throws IOException {
List<Path> paths = null;
try (Stream<Path> stream = Files.walk(path, Integer.MAX_VALUE)) {
paths = stream.filter(ClassFileReader::isClass)
.collect(Collectors.toList());
}
this.entries = paths;
this.index = 0;
}
public boolean hasNext() {
return index != entries.size();
}
public ClassFile next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Path path = entries.get(index++);
try {
return readClassFile(path);
} catch (IOException e) {
throw new ClassFileError(e);
}
}
public void remove() {
throw new UnsupportedOperationException("Not supported yet.");
}
}
}
static class JarFileReader extends ClassFileReader {
private final JarFile jarfile;
private final Runtime.Version version;
JarFileReader(Path path, Runtime.Version version) throws IOException {
this(path, openJarFile(path.toFile(), version), version);
}
JarFileReader(Path path, JarFile jf, Runtime.Version version) throws IOException {
super(path);
this.jarfile = jf;
this.version = version;
}
@Override
public void close() throws IOException {
jarfile.close();
}
private static JarFile openJarFile(File f, Runtime.Version version)
throws IOException {
JarFile jf;
if (version == null) {
jf = new JarFile(f, false);
if (jf.isMultiRelease()) {
throw new MultiReleaseException("err.multirelease.option.notfound", f.getName());
}
} else {
jf = new JarFile(f, false, ZipFile.OPEN_READ, version);
if (!jf.isMultiRelease()) {
throw new MultiReleaseException("err.multirelease.option.exists", f.getName());
}
}
return jf;
}
protected Set<String> scan() {
try (JarFile jf = openJarFile(path.toFile(), version)) {
return VersionedStream.stream(jf).map(JarEntry::getName)
.filter(n -> n.endsWith(".class"))
.collect(Collectors.toSet());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public ClassFile getClassFile(String name) throws IOException {
if (name.indexOf('.') > 0) {
int i = name.lastIndexOf('.');
String entryName = name.replace('.', '/') + ".class";
JarEntry e = jarfile.getJarEntry(entryName);
if (e == null) {
e = jarfile.getJarEntry(entryName.substring(0, i) + "$"
+ entryName.substring(i + 1, entryName.length()));
}
if (e != null) {
return readClassFile(jarfile, e);
}
} else {
JarEntry e = jarfile.getJarEntry(name + ".class");
if (e != null) {
return readClassFile(jarfile, e);
}
}
return null;
}
protected ClassFile readClassFile(JarFile jarfile, JarEntry e) throws IOException {
try (InputStream is = jarfile.getInputStream(e)) {
ClassFile cf = ClassFile.read(is);
if (jarfile.isMultiRelease()) {
VersionHelper.add(jarfile, e, cf);
}
return cf;
} catch (ConstantPoolException ex) {
throw new ClassFileError(ex);
}
}
public Iterable<ClassFile> getClassFiles() throws IOException {
final Iterator<ClassFile> iter = new JarFileIterator(this, jarfile);
return () -> iter;
}
}
Enumeration<JarEntry> versionedEntries(JarFile jf) {
Iterator<JarEntry> it = VersionedStream.stream(jf).iterator();
return new Enumeration<>() {
@Override
public boolean hasMoreElements() {
return it.hasNext();
}
@Override
public JarEntry nextElement() {
return it.next();
}
};
}
class JarFileIterator implements Iterator<ClassFile> {
protected final JarFileReader reader;
protected Enumeration<JarEntry> entries;
protected JarFile jf;
protected JarEntry nextEntry;
protected ClassFile cf;
JarFileIterator(JarFileReader reader) {
this(reader, null);
}
JarFileIterator(JarFileReader reader, JarFile jarfile) {
this.reader = reader;
setJarFile(jarfile);
}
void setJarFile(JarFile jarfile) {
if (jarfile == null) return;
this.jf = jarfile;
this.entries = versionedEntries(jf);
this.nextEntry = nextEntry();
}
public boolean hasNext() {
if (nextEntry != null && cf != null) {
return true;
}
while (nextEntry != null) {
try {
cf = reader.readClassFile(jf, nextEntry);
return true;
} catch (ClassFileError | IOException ex) {
skippedEntries.add(String.format("%s: %s (%s)",
ex.getMessage(),
nextEntry.getName(),
jf.getName()));
}
nextEntry = nextEntry();
}
return false;
}
public ClassFile next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
ClassFile classFile = cf;
cf = null;
nextEntry = nextEntry();
return classFile;
}
protected JarEntry nextEntry() {
while (entries.hasMoreElements()) {
JarEntry e = entries.nextElement();
String name = e.getName();
if (name.endsWith(".class")) {
return e;
}
}
return null;
}
public void remove() {
throw new UnsupportedOperationException("Not supported yet.");
}
}
private static final String MODULE_INFO = "module-info.class";
}