blob: e28e2993bb84aa83e8fcdcfb04b69985a49b908c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.directmemory.lightning.internal.util;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.directmemory.lightning.metadata.ClassDefinition;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
public final class ClassUtil
{
public static final ClassDefinition[] CLASS_DESCRIPTORS = new ClassDefinition[] {
new JavaBuildInTypeClassDefinition( boolean.class, 1 ), new JavaBuildInTypeClassDefinition( Boolean.class, 2 ),
new JavaBuildInTypeClassDefinition( byte.class, 3 ), new JavaBuildInTypeClassDefinition( Byte.class, 4 ),
new JavaBuildInTypeClassDefinition( char.class, 5 ), new JavaBuildInTypeClassDefinition( Character.class, 6 ),
new JavaBuildInTypeClassDefinition( double.class, 7 ), new JavaBuildInTypeClassDefinition( Double.class, 8 ),
new JavaBuildInTypeClassDefinition( float.class, 9 ), new JavaBuildInTypeClassDefinition( Float.class, 10 ),
new JavaBuildInTypeClassDefinition( int.class, 11 ), new JavaBuildInTypeClassDefinition( Integer.class, 12 ),
new JavaBuildInTypeClassDefinition( long.class, 13 ), new JavaBuildInTypeClassDefinition( Long.class, 14 ),
new JavaBuildInTypeClassDefinition( short.class, 15 ), new JavaBuildInTypeClassDefinition( Short.class, 16 ),
new JavaBuildInTypeClassDefinition( String.class, 17 ), new JavaBuildInTypeClassDefinition( List.class, 18 ),
new JavaBuildInTypeClassDefinition( Set.class, 19 ), new JavaBuildInTypeClassDefinition( Map.class, 20 ),
new JavaBuildInTypeClassDefinition( BigInteger.class, 21 ),
new JavaBuildInTypeClassDefinition( BigDecimal.class, 22 ) };
private static final Map<Class<?>, Long> SERIAL_VERSION_UID_CACHE = new ConcurrentHashMap<Class<?>, Long>();
private ClassUtil()
{
}
public static boolean isReferenceCapable( Class<?> type )
{
return !type.isPrimitive() && Boolean.class != type && Byte.class != type && Short.class != type
&& Integer.class != type && Long.class != type && Float.class != type && Double.class != type;
}
public static Class<?> loadClass( String canonicalName )
throws ClassNotFoundException
{
return loadClass( canonicalName, ClassUtil.class.getClassLoader() );
}
public static Class<?> loadClass( String canonicalName, ClassLoader classLoader )
throws ClassNotFoundException
{
Class<?> type = null;
try
{
type = classLoader.loadClass( canonicalName );
}
catch ( ClassNotFoundException e )
{
// Intentionally left blank
}
if ( type == null )
{
try
{
type = Class.forName( canonicalName );
}
catch ( ClassNotFoundException e )
{
// Intentionally left blank
}
}
if ( type == null )
{
try
{
ClassLoader tcl = Thread.currentThread().getContextClassLoader();
type = tcl.loadClass( canonicalName );
}
catch ( ClassNotFoundException e )
{
// Intentionally left blank
}
}
if ( type == null )
{
try
{
ClassLoader ccl = ClassUtil.class.getClassLoader();
type = ccl.loadClass( canonicalName );
}
catch ( ClassNotFoundException e )
{
// Intentionally left blank
}
}
if ( type != null )
{
return type;
}
throw new ClassNotFoundException( "Class " + canonicalName + " not found on classpath" );
}
public static long calculateSerialVersionUID( Class<?> clazz )
{
Long serialVersion = SERIAL_VERSION_UID_CACHE.get( clazz );
if ( serialVersion != null )
{
return serialVersion;
}
if ( Serializable.class.isAssignableFrom( clazz ) )
{
serialVersion = ObjectStreamClass.lookup( clazz ).getSerialVersionUID();
SERIAL_VERSION_UID_CACHE.put( clazz, serialVersion );
return serialVersion;
}
serialVersion = getSerialVersionUIDFromField( clazz );
if ( serialVersion != null )
{
SERIAL_VERSION_UID_CACHE.put( clazz, serialVersion );
return serialVersion;
}
try
{
ClassReader reader = new ClassReader( Type.getInternalName( clazz ).replace( "/", "." ) );
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream( baos );
SerialVersionClassVisitor classVisitor = new SerialVersionClassVisitor();
reader.accept( classVisitor, 0 );
// Classname
out.writeUTF( toJavaName( classVisitor.name ) );
// Modifiers
out.writeInt( clazz.getModifiers()
& ( Modifier.PUBLIC | Modifier.FINAL | Modifier.INTERFACE | Modifier.ABSTRACT ) );
// Interfaces
Collections.sort( classVisitor.interfaces );
for ( int i = 0; i < classVisitor.interfaces.size(); i++ )
{
out.writeUTF( toJavaName( classVisitor.interfaces.get( i ) ) );
}
// Fields
Field[] fields = clazz.getDeclaredFields();
Arrays.sort( fields, new Comparator<Field>()
{
@Override
public int compare( Field o1, Field o2 )
{
return o1.getName().compareTo( o2.getName() );
}
} );
for ( Field field : fields )
{
int mods = field.getModifiers();
if ( ( ( mods & Modifier.PRIVATE ) == 0 || ( mods & ( Modifier.STATIC | Modifier.TRANSIENT ) ) == 0 ) )
{
out.writeUTF( field.getName() );
out.writeInt( mods );
out.writeUTF( Type.getDescriptor( field.getType() ) );
}
}
// Static Initializer
if ( classVisitor.staticInitializerFound )
{
out.writeUTF( "<clinit>" );
out.writeInt( Modifier.STATIC );
out.writeUTF( "()V" );
}
// Constructors
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
Arrays.sort( constructors, new Comparator<Constructor<?>>()
{
@Override
public int compare( Constructor<?> o1, Constructor<?> o2 )
{
return Type.getConstructorDescriptor( o1 ).compareTo( Type.getConstructorDescriptor( o2 ) );
}
} );
for ( int i = 0; i < constructors.length; i++ )
{
Constructor<?> constructor = constructors[i];
int mods = constructor.getModifiers();
if ( ( mods & Modifier.PRIVATE ) == 0 )
{
out.writeUTF( "<init>" );
out.writeInt( mods );
out.writeUTF( toJavaName( Type.getConstructorDescriptor( constructor ) ) );
}
}
// Methods
Method[] methods = clazz.getDeclaredMethods();
Arrays.sort( methods, new Comparator<Method>()
{
@Override
public int compare( Method o1, Method o2 )
{
return Type.getMethodDescriptor( o1 ).compareTo( Type.getMethodDescriptor( o2 ) );
}
} );
for ( int i = 0; i < methods.length; i++ )
{
Method method = methods[i];
int mods = method.getModifiers();
if ( ( mods & Modifier.PRIVATE ) == 0 )
{
out.writeUTF( "<init>" );
out.writeInt( mods );
out.writeUTF( toJavaName( Type.getMethodDescriptor( method ) ) );
}
}
// Final calculation
out.flush();
MessageDigest digest = MessageDigest.getInstance( "SHA" );
byte[] checksum = digest.digest( baos.toByteArray() );
long hash = 0;
for ( int i = Math.min( checksum.length, 8 ) - 1; i >= 0; i-- )
{
hash = ( hash << 8 ) | ( checksum[i] & 0xFF );
}
SERIAL_VERSION_UID_CACHE.put( clazz, hash );
return hash;
}
catch ( IOException e )
{
}
catch ( NoSuchAlgorithmException e )
{
}
return -1L;
}
public static byte[] getClassBytes( Class<?> clazz )
{
try
{
ClassLoader classLoader = clazz.getClassLoader();
if ( classLoader == null )
{
classLoader = Thread.currentThread().getContextClassLoader();
}
String internalName = Type.getInternalName( clazz );
InputStream stream = classLoader.getResourceAsStream( internalName + ".class" );
byte[] data = new byte[stream.available()];
stream.read( data );
stream.close();
return data;
}
catch ( IOException e )
{
throw new RuntimeException( "Class bytes could not be read", e );
}
}
private static String toJavaName( String classname )
{
return classname.replace( "/", "." );
}
private static Long getSerialVersionUIDFromField( Class<?> clazz )
{
try
{
Field f = clazz.getDeclaredField( "serialVersionUID" );
int mask = Modifier.STATIC | Modifier.FINAL;
if ( ( f.getModifiers() & mask ) == mask )
{
f.setAccessible( true );
return Long.valueOf( f.getLong( null ) );
}
}
catch ( Exception ex )
{
}
return null;
}
private static class JavaBuildInTypeClassDefinition
implements ClassDefinition
{
private final long id;
private final Class<?> type;
private final String canonicalName;
private final byte[] checksum = new byte[20];
private final long serialVersionUID = -1L;
JavaBuildInTypeClassDefinition( Class<?> type, long id )
{
this.id = id;
this.type = type;
this.canonicalName = type.getCanonicalName();
}
@Override
public String getCanonicalName()
{
return canonicalName;
}
@Override
public Class<?> getType()
{
return type;
}
@Override
public byte[] getChecksum()
{
return checksum;
}
@Override
public long getId()
{
return id;
}
@Override
public long getSerialVersionUID()
{
return serialVersionUID;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ( ( canonicalName == null ) ? 0 : canonicalName.hashCode() );
result = prime * result + Arrays.hashCode( checksum );
result = prime * result + (int) ( id ^ ( id >>> 32 ) );
result = prime * result + (int) ( serialVersionUID ^ ( serialVersionUID >>> 32 ) );
return result;
}
@Override
public boolean equals( Object obj )
{
if ( this == obj )
{
return true;
}
if ( obj == null )
{
return false;
}
if ( getClass() != obj.getClass() )
{
return false;
}
JavaBuildInTypeClassDefinition other = (JavaBuildInTypeClassDefinition) obj;
if ( canonicalName == null )
{
if ( other.canonicalName != null )
{
return false;
}
}
else if ( !canonicalName.equals( other.canonicalName ) )
{
return false;
}
if ( !Arrays.equals( checksum, other.checksum ) )
{
return false;
}
if ( id != other.id )
{
return false;
}
if ( serialVersionUID != other.serialVersionUID )
{
return false;
}
return true;
}
@Override
public String toString()
{
return "JavaBuildInTypeClassDefinition [id=" + id + ", type=" + type + ", canonicalName=" + canonicalName
+ ", checksum=" + Arrays.toString( checksum ) + ", serialVersionUID=" + serialVersionUID + "]";
}
}
private static class SerialVersionClassVisitor
extends ClassVisitor
{
public SerialVersionClassVisitor()
{
super( Opcodes.ASM4 );
}
private List<String> interfaces = new ArrayList<String>();
private boolean staticInitializerFound = false;
private String name;
@Override
public void visit( int version, int access, String name, String signature, String superName, String[] interfaces )
{
this.name = name;
this.interfaces = Arrays.asList( interfaces );
}
@Override
public AnnotationVisitor visitAnnotation( String desc, boolean visible )
{
return null;
}
@Override
public void visitAttribute( Attribute attr )
{
}
@Override
public void visitEnd()
{
}
@Override
public FieldVisitor visitField( int access, String name, String desc, String signature, Object value )
{
return null;
}
@Override
public void visitInnerClass( String name, String outerName, String innerName, int access )
{
}
@Override
public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions )
{
if ( "<clinit>".equals( name ) && ( access & Opcodes.ACC_STATIC ) != 0 )
{
staticInitializerFound = true;
}
return null;
}
@Override
public void visitOuterClass( String owner, String name, String desc )
{
}
@Override
public void visitSource( String source, String debug )
{
}
}
}