Classfile.java
/*
* Copyright (c) 2001-2024, Jean Tessier
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Jean Tessier nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jeantessier.classreader.impl;
import com.jeantessier.classreader.*;
import org.apache.logging.log4j.*;
import java.io.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
public class Classfile implements com.jeantessier.classreader.Classfile {
private static final int ACC_PUBLIC = 0x0001;
private static final int ACC_FINAL = 0x0010;
private static final int ACC_SUPER = 0x0020;
private static final int ACC_INTERFACE = 0x0200;
private static final int ACC_ABSTRACT = 0x0400;
private static final int ACC_SYNTHETIC = 0x1000;
private static final int ACC_ANNOTATION = 0x2000;
private static final int ACC_ENUM = 0x4000;
private static final int ACC_MODULE = 0x8000;
private final ClassfileLoader loader;
private final int magicNumber;
private final int minorVersion;
private final int majorVersion;
private final ConstantPool constantPool;
private final int accessFlags;
private final int classIndex;
private final int superclassIndex;
private final Collection<Class_info> interfaces = new LinkedList<>();
private final Collection<Field_info> fields = new LinkedList<>();
private final Collection<Method_info> methods = new LinkedList<>();
private final Collection<Attribute_info> attributes = new LinkedList<>();
/**
* Parses the input stream and extracts the class description.
* You should only call this constructor from a ClassfileLoader.
*/
public Classfile(ClassfileLoader loader, DataInput in, AttributeFactory attributeFactory) throws IOException {
this.loader = loader;
magicNumber = in.readInt();
LogManager.getLogger(getClass()).debug("magic number = 0x{}", () -> Integer.toHexString(magicNumber).toUpperCase());
if (magicNumber != 0xCAFEBABE) {
throw new IOException("Bad magic number");
}
// Reading the file format's version number
minorVersion = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("minor version = {}", minorVersion);
majorVersion = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("major version = {}", majorVersion);
// Reading the constant pool
LogManager.getLogger(getClass()).debug("Reading the constant pool ...");
constantPool = new ConstantPool(this, in);
LogManager.getLogger(getClass()).debug(constantPool);
// Skipping the access flags
accessFlags = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("accessFlags = {}", accessFlags);
// Retrieving this class's name
classIndex = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("thisClass = {} ({})", classIndex, getClassName());
// Retrieving this class's superclass
superclassIndex = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("superclass = {} ({})", superclassIndex, getSuperclassName());
// Retrieving the interfaces
int interfaceCount = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("Reading {} interface(s)", interfaceCount);
IntStream.range(0, interfaceCount).forEach(i -> {
try {
Class_info interfaceInfo = (Class_info) constantPool.get(in.readUnsignedShort());
LogManager.getLogger(getClass()).debug(" {}", interfaceInfo.getName());
interfaces.add(interfaceInfo);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
// Retrieving the fields
int fieldCount = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("Reading {} field(s)", fieldCount);
IntStream.range(0, fieldCount).forEach(i -> {
try {
LogManager.getLogger(getClass()).debug("Field {}:", i);
fields.add(new Field_info(this, in, attributeFactory));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
// Retrieving the methods
int methodCount = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("Reading {} method(s)", methodCount);
IntStream.range(0, methodCount).forEach(i -> {
try {
LogManager.getLogger(getClass()).debug("Method {}:", i);
methods.add(new Method_info(this, in, attributeFactory));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
// Retrieving the attributes
int attributeCount = in.readUnsignedShort();
LogManager.getLogger(getClass()).debug("Reading {} class attribute(s)", attributeCount);
IntStream.range(0, attributeCount).forEach(i -> {
try {
LogManager.getLogger(getClass()).debug("Attribute {}:", i);
attributes.add(attributeFactory.create(constantPool, this, in));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
/**
* For testing only
*/
Classfile(ClassfileLoader loader, ConstantPool constantPool, int accessFlags, int classIndex, int superclassIndex, Collection<Class_info> interfaces, Collection<Field_info> fields, Collection<Method_info> methods, Collection<Attribute_info> attributes) {
this.loader = loader;
this.constantPool = constantPool;
this.magicNumber = 0xCAFEBABE;
this.minorVersion = Integer.MIN_VALUE;
this.majorVersion = Integer.MAX_VALUE;
this.accessFlags = accessFlags;
this.classIndex = classIndex;
this.superclassIndex = superclassIndex;
this.interfaces.addAll(interfaces);
this.fields.addAll(fields);
this.methods.addAll(methods);
this.attributes.addAll(attributes);
}
public ClassfileLoader getLoader() {
return loader;
}
public int getMagicNumber() {
return magicNumber;
}
public int getMinorVersion() {
return minorVersion;
}
public int getMajorVersion() {
return majorVersion;
}
public ConstantPool getConstantPool() {
return constantPool;
}
public int getAccessFlags() {
return accessFlags;
}
public int getClassIndex() {
return classIndex;
}
public Class_info getRawClass() {
return (Class_info) getConstantPool().get(getClassIndex());
}
public String getClassName() {
return getRawClass().getName();
}
public String getPackageName() {
return getRawClass().getPackageName();
}
public String getSimpleName() {
return getRawClass().getSimpleName();
}
public int getSuperclassIndex() {
return superclassIndex;
}
public boolean hasSuperclass() {
return getSuperclassIndex() != 0;
}
public Class_info getRawSuperclass() {
return (Class_info) getConstantPool().get(getSuperclassIndex());
}
public String getSuperclassName() {
if (hasSuperclass()) {
return getRawSuperclass().getName();
}
return "";
}
public Class_info getInterface(String name) {
return interfaces.parallelStream()
.filter(interfaceInfo -> interfaceInfo.getName().equals(name))
.findAny()
.orElse(null);
}
public Collection<Class_info> getAllInterfaces() {
return interfaces;
}
public Collection<Field_info> getAllFields() {
return fields;
}
public com.jeantessier.classreader.Field_info getField(Predicate<com.jeantessier.classreader.Field_info> filter) {
return fields.parallelStream()
.filter(filter)
.findAny()
.orElse(null);
}
public com.jeantessier.classreader.Field_info locateField(Predicate<com.jeantessier.classreader.Field_info> filter) {
var localField = getField(filter);
if (localField != null) {
return localField;
}
var superclass = getLoader().getClassfile(getSuperclassName());
if (superclass != null) {
var inheritedField = superclass.locateField(filter);
if (inheritedField != null && (inheritedField.isPublic() || inheritedField.isProtected() || (inheritedField.isPackage() && inheritedField.getClassfile().getPackageName().equals(superclass.getPackageName())))) {
return inheritedField;
}
}
for (var interfaceInfo : getAllInterfaces()) {
var interfaceClassfile = getLoader().getClassfile(interfaceInfo.getName());
if (interfaceClassfile != null) {
var interfaceField = interfaceClassfile.locateField(filter);
if (interfaceField != null && (interfaceField.isPublic() || interfaceField.isProtected())) {
return interfaceField;
}
}
}
return null;
}
public Collection<Method_info> getAllMethods() {
return methods;
}
public com.jeantessier.classreader.Method_info getMethod(Predicate<com.jeantessier.classreader.Method_info> filter) {
return methods.parallelStream()
.filter(filter)
.findAny()
.orElse(null);
}
public com.jeantessier.classreader.Method_info locateMethod(Predicate<com.jeantessier.classreader.Method_info> filter) {
var localMethod = getMethod(filter);
if (localMethod != null) {
return localMethod;
}
var superclass = getLoader().getClassfile(getSuperclassName());
if (superclass != null) {
var inheritedMethod = superclass.locateMethod(filter);
if (inheritedMethod != null && (inheritedMethod.isPublic() || inheritedMethod.isProtected() || (inheritedMethod.isPackage() && inheritedMethod.getClassfile().getPackageName().equals(superclass.getPackageName())))) {
return inheritedMethod;
}
}
for (var interfaceInfo : getAllInterfaces()) {
var interfaceClassfile = getLoader().getClassfile(interfaceInfo.getName());
if (interfaceClassfile != null) {
var interfaceMethod = interfaceClassfile.locateMethod(filter);
if (interfaceMethod != null && (interfaceMethod.isPublic() || interfaceMethod.isProtected())) {
return interfaceMethod;
}
}
}
return null;
}
public Collection<? extends com.jeantessier.classreader.Method_info> locateMethodDeclarations(Predicate<com.jeantessier.classreader.Method_info> filter) {
var declarations = Stream.concat(
getAllInterfaces().stream().map(com.jeantessier.classreader.Class_info::getName),
Stream.of(getSuperclassName())
)
.map(className -> getLoader().getClassfile(className))
.filter(Objects::nonNull)
.flatMap(classfile -> classfile.locateMethodDeclarations(filter).stream())
.toList();
if (!declarations.isEmpty()) {
return declarations;
}
return getAllMethods().stream()
.filter(filter)
.toList();
}
public Collection<Attribute_info> getAttributes() {
return attributes;
}
public boolean isPublic() {
return (getAccessFlags() & ACC_PUBLIC) != 0;
}
public boolean isPackage() {
return (getAccessFlags() & ACC_PUBLIC) == 0;
}
public boolean isFinal() {
return (getAccessFlags() & ACC_FINAL) != 0;
}
public boolean isSuper() {
return (getAccessFlags() & ACC_SUPER) != 0;
}
public boolean isInterface() {
return (getAccessFlags() & ACC_INTERFACE) != 0;
}
public boolean isAbstract() {
return (getAccessFlags() & ACC_ABSTRACT) != 0;
}
public boolean isSynthetic() {
return isSyntheticFromAccessFlags() || isSyntheticFromAttribute();
}
public boolean isAnnotation() {
return (getAccessFlags() & ACC_ANNOTATION) != 0;
}
public boolean isEnum() {
return (getAccessFlags() & ACC_ENUM) != 0;
}
public boolean isModule() {
return (getAccessFlags() & ACC_MODULE) != 0;
}
private boolean isSyntheticFromAccessFlags() {
return (getAccessFlags() & ACC_SYNTHETIC) != 0;
}
private boolean isSyntheticFromAttribute() {
return getAttributes().parallelStream().anyMatch(attribute -> attribute instanceof Synthetic_attribute);
}
public boolean isDeprecated() {
return getAttributes().parallelStream().anyMatch(attribute -> attribute instanceof Deprecated_attribute);
}
public boolean isGeneric() {
return getAttributes().parallelStream().anyMatch(attribute -> attribute instanceof Signature_attribute);
}
public String getDeclaration() {
var result = new StringBuilder();
if (isPublic()) result.append("public ");
if (isFinal()) result.append("final ");
if (isInterface()) {
result.append("interface ").append(getClassName());
if (!getAllInterfaces().isEmpty()) {
result.append(" extends ");
result.append(getAllInterfaces().stream().map(Class_info::toString).collect(joining(", ")));
}
} else {
if (isAbstract()) result.append("abstract ");
result.append("class ").append(getClassName());
if (hasSuperclass()) {
result.append(" extends ").append(getSuperclassName());
}
if (!getAllInterfaces().isEmpty()) {
result.append(" implements ");
result.append(getAllInterfaces().stream().map(Class_info::toString).collect(joining(", ")));
}
}
return result.toString();
}
public void accept(Visitor visitor) {
visitor.visitClassfile(this);
}
public String toString() {
return getClassName();
}
public boolean isInnerClass() {
return getMatchingInnerClass() != null;
}
public boolean isMemberClass() {
var innerClass = getMatchingInnerClass();
if (innerClass != null) {
return innerClass.isMemberClass();
}
return false;
}
public boolean isLocalClass() {
var innerClass = getMatchingInnerClass();
var enclosingMethod = getEnclosingMethod();
if (innerClass != null && enclosingMethod != null) {
return !innerClass.isAnonymousClass();
}
return false;
}
public boolean isAnonymousClass() {
var innerClass = getMatchingInnerClass();
if (innerClass != null) {
return innerClass.isAnonymousClass();
}
return false;
}
private InnerClass getMatchingInnerClass() {
for (var attribute : getAttributes()) {
if (attribute instanceof InnerClasses_attribute innerClasses_attribute) {
for (var innerClass : innerClasses_attribute.getInnerClasses()) {
if (innerClass.getInnerClassInfo().equals(getClassName())) {
return innerClass;
}
}
}
}
return null;
}
private EnclosingMethod_attribute getEnclosingMethod() {
for (var attribute : getAttributes()) {
if (attribute instanceof EnclosingMethod_attribute enclosingMethod_attribute) {
return enclosingMethod_attribute;
}
}
return null;
}
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
return compareTo((Classfile) object) == 0;
}
public int hashCode() {
return getClassName().hashCode();
}
public int compareTo(com.jeantessier.classreader.Classfile other) {
if (this == other) {
return 0;
}
if (other == null) {
throw new ClassCastException("compareTo: expected a " + getClass().getName() + " but got null");
}
return getClassName().compareTo(other.getClassName());
}
}