423 lines
13 KiB
Java
Raw Normal View History

2018-03-20 20:30:57 +01:00
/*
Copyright 2011 - 2022 Volker Berlin (i-net software)
2018-03-20 20:30:57 +01:00
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package de.inetsoftware.classparser;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
2020-01-19 15:15:01 +01:00
import java.util.ArrayList;
import java.util.Arrays;
2020-01-02 13:12:48 +01:00
import java.util.Collections;
import java.util.Map;
2018-03-20 20:30:57 +01:00
2019-05-07 21:16:30 +02:00
import javax.annotation.Nullable;
2018-03-20 20:30:57 +01:00
import de.inetsoftware.classparser.Attributes.AttributeInfo;
import de.inetsoftware.jwebassembly.JWebAssembly;
2018-03-20 20:30:57 +01:00
/**
* http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
* http://docs.oracle.com/javase/specs/jvms/se5.0/html/ClassFile.doc.html
*
* @author Volker Berlin
*/
public class ClassFile {
private final int minorVersion;
private final int majorVersion;
private final ConstantPool constantPool;
private final int accessFlags;
private final ConstantClass thisClass;
private final ConstantClass superClass;
private final ConstantClass[] interfaces;
2020-01-19 15:15:01 +01:00
private FieldInfo[] fields;
2018-03-20 20:30:57 +01:00
2020-01-19 15:15:01 +01:00
private MethodInfo[] methods;
2018-03-20 20:30:57 +01:00
private final Attributes attributes;
private String thisSignature;
private String superSignature;
2020-01-02 13:12:48 +01:00
private Map<String,Map<String,Object>> annotations;
2020-01-25 21:17:42 +01:00
private BootstrapMethod[] bootstrapMethods;
2018-03-20 20:30:57 +01:00
/**
* Load a class file and create a model of the class.
*
* @param stream
* The InputStream of the class file. Will be closed if finish.
* @throws IOException
* if this input stream reaches the end before reading the class file.
*/
public ClassFile( InputStream stream ) throws IOException {
2020-01-03 19:51:58 +01:00
DataInputStream input = new DataInputStream( stream );
2018-03-20 20:30:57 +01:00
int magic = input.readInt();
if( magic != 0xCAFEBABE ) {
throw new IOException( "Invalid class magic: " + Integer.toHexString( magic ) );
}
minorVersion = input.readUnsignedShort();
majorVersion = input.readUnsignedShort();
constantPool = new ConstantPool( input );
accessFlags = input.readUnsignedShort();
thisClass = (ConstantClass)constantPool.get( input.readUnsignedShort() );
superClass = (ConstantClass)constantPool.get( input.readUnsignedShort() );
interfaces = new ConstantClass[input.readUnsignedShort()];
for( int i = 0; i < interfaces.length; i++ ) {
interfaces[i] = (ConstantClass)constantPool.get( input.readUnsignedShort() );
}
2020-01-03 19:51:58 +01:00
fields = readFields( input );
methods = readMethods( input );
2018-03-20 20:30:57 +01:00
attributes = new Attributes( input, constantPool );
stream.close();
AttributeInfo info = attributes.get( "Signature" );
if( info != null ) {
int idx = info.getDataInputStream().readShort();
String signature = (String)constantPool.get( idx );
int count = 0;
for( int i = 0; i < signature.length(); i++ ) {
char ch = signature.charAt( i );
switch( ch ) {
case '<':
count++;
continue;
case '>':
count--;
continue;
}
if( count == 0 ) {
thisSignature = signature.substring( 0, i );
superSignature = signature.substring( i );
break;
}
}
}
}
2020-01-03 19:51:58 +01:00
/**
* Create a replaced instance.
*
* @param className
* the class name that should be replaced
* @param classFile
* the replacing class file data
*/
public ClassFile( String className, ClassFile classFile ) {
minorVersion = classFile.minorVersion;
majorVersion = classFile.majorVersion;
constantPool = classFile.constantPool;
accessFlags = classFile.accessFlags;
thisClass = new ConstantClass( className );
superClass = classFile.superClass;
interfaces = classFile.interfaces;
fields = classFile.fields;
methods = classFile.methods;
attributes = classFile.attributes;
String origClassName = classFile.thisClass.getName();
2020-01-19 15:15:01 +01:00
patchConstantPool( classFile.thisClass.getName(), thisClass );
2020-03-14 23:00:51 +01:00
for( MethodInfo m : methods ) {
m.setDeclaringClassFile( origClassName, this );
2020-03-14 23:00:51 +01:00
}
2020-01-19 15:15:01 +01:00
}
/**
* Replace the reference to the Class in the the constant pool.
*
* @param origClassName
* the class name that should be replaced.
* @param thisClass
* the reference of the class that should be used.
*/
private void patchConstantPool( String origClassName, ConstantClass thisClass ) {
String origSignature = 'L' + origClassName + ';';
String thisSignature = 'L' + thisClass.getName() + ';';
2020-01-03 19:51:58 +01:00
// patch constant pool
for( int i = 0; i < constantPool.size(); i++ ) {
Object obj = constantPool.get( i );
if( obj instanceof ConstantClass ) {
if( ((ConstantClass)obj).getName().equals( origClassName ) ) {
constantPool.set( i, thisClass );
}
2020-01-19 15:15:01 +01:00
} else if( obj instanceof ConstantRef ) {
ConstantRef ref = (ConstantRef)obj;
2020-01-03 19:51:58 +01:00
if( ref.getClassName().equals( origClassName ) ) {
String type = ref.getType().replace( origSignature, thisSignature );
ConstantNameAndType nameAndType = new ConstantNameAndType( ref.getName(), type );
2020-01-03 19:51:58 +01:00
constantPool.set( i, new ConstantFieldRef( thisClass, nameAndType ) );
}
} else if( obj instanceof String ) {
String str = ((String)obj).replace( origSignature, thisSignature );
constantPool.set( i, str );
2020-01-03 19:51:58 +01:00
}
}
}
2018-03-20 20:30:57 +01:00
/**
* Get value of SourceFile if available.
*
* @return the source file name or null.
* @throws IOException
* if an I/O error occurs.
*/
public String getSourceFile() throws IOException {
return attributes.getSourceFile();
}
2020-01-02 13:12:48 +01:00
/**
* Get a single annotation or null
*
* @param annotation
* the class name of the annotation
* @return the value or null if not exists
* @throws IOException
* if any I/O error occur
*/
@Nullable
public Map<String, Object> getAnnotation( String annotation ) throws IOException {
if( annotations == null ) {
AttributeInfo data = attributes.get( "RuntimeInvisibleAnnotations" );
if( data != null ) {
annotations = Annotations.read( data.getDataInputStream(), constantPool );
} else {
annotations = Collections.emptyMap();
}
}
return annotations.get( annotation );
}
2020-01-25 21:17:42 +01:00
/**
* Get the x-the BootstrapMethod. Bootstrap methods are used for creating an lambda object.
*
* @param methodIdx
* the index of the method
* @return the method
* @throws IOException
* if any error occur
*/
public BootstrapMethod getBootstrapMethod( int methodIdx ) throws IOException {
if( bootstrapMethods == null ) {
AttributeInfo data = attributes.get( "BootstrapMethods" );
if( data != null ) {
DataInputStream input = data.getDataInputStream();
int count = input.readUnsignedShort();
bootstrapMethods = new BootstrapMethod[count];
for( int i = 0; i < count; i++ ) {
bootstrapMethods[i] = new BootstrapMethod( input, constantPool );
}
}
}
return bootstrapMethods[methodIdx];
}
2020-01-26 12:39:00 +01:00
/**
* Get the constant pool of the the current class.
*
* @return the constant pool
*/
2018-03-20 20:30:57 +01:00
public ConstantPool getConstantPool() {
return constantPool;
}
public ConstantClass getThisClass() {
return thisClass;
}
public ConstantClass getSuperClass() {
return superClass;
}
public ConstantClass[] getInterfaces(){
return interfaces;
}
public MethodInfo[] getMethods() {
return methods;
}
/**
* Find a method via name and signature.
*
* @param name
* the name
* @param signature
* the signature
* @return the method or null if not found
*/
public MethodInfo getMethod( String name, String signature ) {
2018-03-20 20:30:57 +01:00
for( MethodInfo method : methods ) {
if( name.equals( method.getName() ) && signature.equals( method.getType() ) ) {
return method;
2018-03-20 20:30:57 +01:00
}
}
return null;
2018-03-20 20:30:57 +01:00
}
public FieldInfo getField( String name ) {
for( FieldInfo field : fields ) {
if( name.equals( field.getName() ) ) {
return field;
}
}
return null;
}
2019-01-01 11:43:49 +01:00
/**
* Get the fields of the class.
*
* @return the fields
*/
public FieldInfo[] getFields() {
return fields;
}
2018-03-20 20:30:57 +01:00
/**
* The access flags of the class.
* http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1-200-E
* http://docs.oracle.com/javase/specs/jvms/se5.0/html/ClassFile.doc.html#23242
2018-06-10 21:00:51 +02:00
* @see java.lang.Class#isInterface()
2021-03-20 21:00:15 +01:00
* @return the flag
2018-03-20 20:30:57 +01:00
*/
public int getAccessFlags() {
return accessFlags;
}
2020-05-10 15:35:26 +02:00
/**
* If the class is abstract
*
* @return true, if abstract class
*/
public boolean isAbstract() {
return (accessFlags & 0x0400) > 0;
}
2021-05-30 11:30:43 +02:00
/**
* If the class is an Enum
*
* @return true, if Enum class
*/
public boolean isEnum() {
return (accessFlags & 0x4000) > 0;
}
2020-01-03 19:51:58 +01:00
private FieldInfo[] readFields( DataInputStream input ) throws IOException {
2018-03-20 20:30:57 +01:00
FieldInfo[] fields = new FieldInfo[input.readUnsignedShort()];
for( int i = 0; i < fields.length; i++ ) {
fields[i] = new FieldInfo( input, constantPool );
}
return fields;
}
2020-01-03 19:51:58 +01:00
private MethodInfo[] readMethods( DataInputStream input ) throws IOException {
2018-03-20 20:30:57 +01:00
MethodInfo[] methods = new MethodInfo[input.readUnsignedShort()];
for( int i = 0; i < methods.length; i++ ) {
methods[i] = new MethodInfo( input, constantPool, this );
}
return methods;
}
/**
* Get the signature of the class with generic types.
2021-03-20 21:00:15 +01:00
* @return the signature
2018-03-20 20:30:57 +01:00
*/
public String getThisSignature() {
return thisSignature;
}
/**
* Get the signature of the super class with generic types.
2021-03-20 21:00:15 +01:00
* @return the signature
2018-03-20 20:30:57 +01:00
*/
public String getSuperSignature() {
return superSignature;
}
/**
* Get the type of class.
2020-01-12 16:59:02 +01:00
*
* @return the type of the class
2018-03-20 20:30:57 +01:00
*/
public Type getType() {
if( (accessFlags & 0x0200) > 0 ) {
return Type.Interface;
}
2020-01-12 16:59:02 +01:00
if( superClass != null && superClass.getName().equals( "java/lang/Enum" ) ) {
2018-03-20 20:30:57 +01:00
return Type.Enum;
}
return Type.Class;
}
public static enum Type {
Class, Interface, Enum;
}
2020-01-19 15:15:01 +01:00
/**
* Extends this class with the methods and fields of the partial class.
*
* @param partialClassFile
* extension of the class
* @throws IOException
* If any I/O error occur
2020-01-19 15:15:01 +01:00
*/
public void partial( ClassFile partialClassFile ) throws IOException {
String origClassName = partialClassFile.thisClass.getName();
2020-01-19 15:15:01 +01:00
ArrayList<MethodInfo> allMethods = new ArrayList<>( Arrays.asList( methods ) );
for( MethodInfo m : partialClassFile.methods ) {
if( m.isNative() && m.getAnnotation( JWebAssembly.IMPORT_ANNOTATION ) == null && m.getAnnotation( JWebAssembly.TEXTCODE_ANNOTATION ) == null ) {
// only a placeholder, use the original
continue;
}
if( "<clinit>".equals( m.getName() ) ) {
// can be fatal to override the static constructor
continue;
}
m.setDeclaringClassFile( origClassName, this );
MethodInfo origMethod = getMethod( m.getName(), m.getType() );
if( origMethod == null ) {
2020-01-19 15:15:01 +01:00
allMethods.add( m );
} else {
allMethods.set( allMethods.indexOf( origMethod ), m );
2020-01-19 15:15:01 +01:00
}
}
methods = allMethods.toArray( methods );
ArrayList<FieldInfo> allFields = new ArrayList<>( Arrays.asList( fields ) );
for( FieldInfo field : partialClassFile.fields ) {
if( getField( field.getName() ) == null ) {
allFields.add( field );
}
}
fields = allFields.toArray( fields );
partialClassFile.patchConstantPool( origClassName, thisClass );
2020-01-19 15:15:01 +01:00
}
2018-03-20 20:30:57 +01:00
}