/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Common Public License (CPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/cpl1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.classloader;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import org.jikesrvm.runtime.VM_Reflection;
import org.jikesrvm.runtime.VM_Runtime;
import org.jikesrvm.runtime.VM_Statics;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Offset;
// TODO - deal with annotations from subarch
/**
* Internal representation of an annotation. We synthetically create
* actual annotations {@link VM_Class}.
*/
public final class VM_Annotation {
/**
* The type of the annotation. This is an interface name that the
* annotation value will implement
*/
private final VM_Atom type;
/**
* Members of this annotation
*/
private final AnnotationMember[] elementValuePairs;
/**
* The class loader that loaded this annotation
*/
private final ClassLoader classLoader;
/**
* A reference to the constructor of the base annotation
*/
private static final VM_MethodReference baseAnnotationInitMethod;
/**
* The concrete annotation represented by this VM_Annotation
*/
private Annotation value;
/**
* Class constructor
*/
static {
baseAnnotationInitMethod =
VM_MemberReference.findOrCreate(VM_TypeReference.VM_BaseAnnotation,
VM_Atom.findOrCreateAsciiAtom("<init>"),
VM_Atom.findOrCreateAsciiAtom("(Lorg/jikesrvm/classloader/VM_Annotation;)V")).asMethodReference();
if (baseAnnotationInitMethod == null) {
throw new Error("Error creating reference to base annotation");
}
}
/**
* Construct a read annotation
* @param type the name of the type this annotation's value will
* implement
* @param elementValuePairs values for the fields in the annotation
* that override the defaults
* @param classLoader the class loader being used to load this annotation
*/
private VM_Annotation(VM_Atom type, AnnotationMember[] elementValuePairs, ClassLoader classLoader) {
this.type = type;
this.elementValuePairs = elementValuePairs;
this.classLoader = classLoader;
}
/**
* Read an annotation attribute from the class file
*
* @param constantPool from constant pool being loaded
* @param input the data being rea
*/
static VM_Annotation readAnnotation(int[] constantPool, DataInputStream input, ClassLoader classLoader)
throws IOException, ClassNotFoundException {
VM_Atom type;
// Read type
int typeIndex = input.readUnsignedShort();
type = VM_Class.getUtf(constantPool, typeIndex);
// Read values
int numAnnotationMembers = input.readUnsignedShort();
AnnotationMember[] elementValuePairs = new AnnotationMember[numAnnotationMembers];
for (int i = 0; i < numAnnotationMembers; i++) {
elementValuePairs[i] = AnnotationMember.readAnnotationMember(constantPool, input, classLoader);
}
// Arrays.sort(elementValuePairs);
return new VM_Annotation(type, elementValuePairs, classLoader);
}
/**
* Return the annotation represented by this VM_Annotation. If this
* is the first time this annotation has been accessed the subclass
* of annotation this class represents needs creating.
* @return the annotation represented
*/
Annotation getValue() {
if (value == null) {
value = createValue();
}
return value;
}
/**
* Create an instance of this type of annotation with the values
* given in the members
*
* @return the created annotation
*/
private Annotation createValue() {
// Find the annotation then find its implementing class
VM_Class annotationInterface = VM_TypeReference.findOrCreate(classLoader, type).resolve(false).asClass();
if (!annotationInterface.isResolved(false)) {
annotationInterface.resolve(false);
}
VM_Class annotationClass = annotationInterface.getAnnotationClass();
if (!annotationClass.isResolved(false)) {
annotationClass.resolve(false);
}
if (!annotationClass.isInitialized(false)) {
VM_Runtime.initializeClassForDynamicLink(annotationClass);
}
// Construct an instance with default values
Annotation annotationInstance = (Annotation) VM_Runtime.resolvedNewScalar(annotationClass);
VM_Method defaultConstructor = annotationClass.getConstructorMethods()[0];
VM_Reflection.invoke(defaultConstructor, annotationInstance, new VM_Annotation[]{this});
// Override default values with those given in the element value pairs
VM_Field[] annotationClassFields = annotationClass.getDeclaredFields();
for (AnnotationMember evp : elementValuePairs) {
VM_Atom evpFieldName = evp.getNameAsFieldName();
for (VM_Field field : annotationClassFields) {
if (field.getName() == evpFieldName) {
evp.setValueToField(field, annotationInstance);
}
}
}
return annotationInstance;
}
/**
* Return a string representation of the annotation of the form
* "@type(name1=val1, ...nameN=valN)"
*/
public String toString() {
String result = type.toString();
result = "@" + result.substring(1, result.length() - 1) + "(";
if (elementValuePairs != null) {
for (int i = 0; i < elementValuePairs.length; i++) {
result += elementValuePairs[i];
if (i < (elementValuePairs.length - 1)) {
result += ", ";
}
}
}
result += ")";
return result;
}
/**
* Read the element_value field of an annotation
*
* @param constantPool the constant pool for the class being read
* @param input stream to read from
* @return object representing the value read
*/
static Object readValue(int[] constantPool, DataInputStream input, ClassLoader classLoader)
throws IOException, ClassNotFoundException {
// Read element value's tag and decode
byte elementValue_tag = input.readByte();
Object value;
switch (elementValue_tag) {
case'B': {
Offset offset = VM_Class.getLiteralOffset(constantPool, input.readUnsignedShort());
value = (byte) VM_Statics.getSlotContentsAsInt(offset);
break;
}
case'C': {
Offset offset = VM_Class.getLiteralOffset(constantPool, input.readUnsignedShort());
value = (char) VM_Statics.getSlotContentsAsInt(offset);
break;
}
case'D': {
Offset offset = VM_Class.getLiteralOffset(constantPool, input.readUnsignedShort());
long longValue = VM_Statics.getSlotContentsAsLong(offset);
value = Double.longBitsToDouble(longValue);
break;
}
case'F': {
Offset offset = VM_Class.getLiteralOffset(constantPool, input.readUnsignedShort());
int intValue = VM_Statics.getSlotContentsAsInt(offset);
value = Float.intBitsToFloat(intValue);
break;
}
case'I': {
Offset offset = VM_Class.getLiteralOffset(constantPool, input.readUnsignedShort());
value = VM_Statics.getSlotContentsAsInt(offset);
break;
}
case'J': {
Offset offset = VM_Class.getLiteralOffset(constantPool, input.readUnsignedShort());
value = VM_Statics.getSlotContentsAsLong(offset);
break;
}
case'S': {
Offset offset = VM_Class.getLiteralOffset(constantPool, input.readUnsignedShort());
value = (short) VM_Statics.getSlotContentsAsInt(offset);
break;
}
case'Z': {
Offset offset = VM_Class.getLiteralOffset(constantPool, input.readUnsignedShort());
value = VM_Statics.getSlotContentsAsInt(offset) == 1;
break;
}
case's': {
value = VM_Class.getUtf(constantPool, input.readUnsignedShort()).toString();
break;
}
case'e': {
int typeNameIndex = input.readUnsignedShort();
@SuppressWarnings("unchecked") Class enumType =
VM_TypeReference.findOrCreate(classLoader,
VM_Class.getUtf(constantPool, typeNameIndex)).resolve(false).getClassForType();
int constNameIndex = input.readUnsignedShort();
//noinspection unchecked
value = Enum.valueOf(enumType, VM_Class.getUtf(constantPool, constNameIndex).toString());
break;
}
case'c': {
int classInfoIndex = input.readUnsignedShort();
value = Class.forName(VM_Class.getUtf(constantPool, classInfoIndex).toString());
break;
}
case'@':
value = VM_Annotation.readAnnotation(constantPool, input, classLoader);
break;
case'[': {
int numValues = input.readUnsignedShort();
Object[] array = new Object[numValues];
for (int i = 0; i < numValues; i++) {
array[i] = readValue(constantPool, input, classLoader);
}
value = array;
break;
}
default:
throw new ClassFormatError("Unknown element_value tag '" + (char) elementValue_tag + "'");
}
return value;
}
/**
* Return the VM_TypeReference of the declared annotation, ie an
* interface and not the class object of this instance
*
* @return VM_TypeReferernce of interface annotation object implements
*/
VM_TypeReference annotationType() {
return VM_TypeReference.findOrCreate(classLoader, type);
}
@Uninterruptible
VM_Atom getType() { return type; }
@Uninterruptible
ClassLoader getClassLoader() { return classLoader; }
/**
* Are two annotations logically equivalent?
*
* todo: for performance reasons if we dynamically generated the
* bytecode for this method, rather than using reflection, the
* performance should be better.
*/
static boolean equals(BaseAnnotation a, VM_Annotation vmA, BaseAnnotation b, VM_Annotation vmB) {
if (vmA.type != vmB.type) {
return false;
} else {
VM_Class annotationInterface = VM_TypeReference.findOrCreate(vmA.classLoader, vmA.type).resolve(false).asClass();
VM_Class annotationClass = annotationInterface.getAnnotationClass();
VM_Field[] annotationClassFields = annotationClass.getDeclaredFields();
for (VM_Field annotationClassField : annotationClassFields) {
Object objA = annotationClassField.getObjectUnchecked(a);
Object objB = annotationClassField.getObjectUnchecked(b);
if (!objA.getClass().isArray()) {
if (!objA.equals(objB)) {
return false;
}
} else {
return Arrays.equals((Object[]) objA, (Object[]) objB);
}
}
return true;
}
}
/**
* Compute the hashCode for an instance of an annotation
*
* todo: for performance reasons if we dynamically generated the
* bytecode for this method, rather than using reflection, the
* performance should be better.
*/
public int hashCode(BaseAnnotation a) {
VM_Class annotationInterface = VM_TypeReference.findOrCreate(classLoader, type).resolve(false).asClass();
VM_Class annotationClass = annotationInterface.getAnnotationClass();
VM_Field[] annotationClassFields = annotationClass.getDeclaredFields();
String typeString = type.toString();
int result = typeString.substring(1, typeString.length() - 1).hashCode();
for (VM_Field field : annotationClassFields) {
String name = field.getName().toString();
name = name.substring(0, name.length() - 6); // remove "_field" from name
Object value = field.getObjectUnchecked(a);
int part_result = name.hashCode() * 127;
if (value.getClass().isArray()) {
part_result ^= Arrays.hashCode((Object[]) value);
} else {
part_result ^= value.hashCode();
}
result += part_result;
}
return result;
}
/**
* @return member reference to init method of BaseAnnotation
*/
static VM_MethodReference getBaseAnnotationInitMemberReference() {
if (baseAnnotationInitMethod == null) {
throw new Error("Error creating reference to base annotation");
}
return baseAnnotationInitMethod;
}
/**
* The superclass for all annotation instances
*/
abstract static class BaseAnnotation implements Annotation {
/**
* The VM_Annotation that this annotation is an instance of
*/
private final VM_Annotation vmAnnotation;
/**
* Constructor, called via VM_Annotation.createValue
*/
BaseAnnotation(VM_Annotation vmAnnotation) {
this.vmAnnotation = vmAnnotation;
}
/**
* Return a string representation of the annotation of the form
* "@type(name1=val1, ...nameN=valN)"
*/
public String toString() {
return vmAnnotation.toString();
}
/**
* Return the Class object of the declared annotation, ie an
* interface and not the class object of this instance
*
* @return Class object of interface annotation object implements
*/
@SuppressWarnings("unchecked")
// We intentionally break type-safety
public Class<? extends Annotation> annotationType() {
return (Class<? extends Annotation>) vmAnnotation.annotationType().resolve(false).getClassForType();
}
/**
* Are two annotations logically equivalent?
*/
public boolean equals(Object o) {
if (o instanceof BaseAnnotation) {
if (o == this) {
return true;
} else {
BaseAnnotation b = (BaseAnnotation) o;
return VM_Annotation.equals(this, this.vmAnnotation, b, b.vmAnnotation);
}
} else {
return false;
}
}
/**
* Compute the hash code of an annotation using the standard
* algorithm {@link java.lang.annotation.Annotation#hashCode()}
*/
public int hashCode() {
return vmAnnotation.hashCode(this);
}
}
/**
* A class to decode and hold the name and its associated value for
* an annotation member
*/
private static final class AnnotationMember implements Comparable<AnnotationMember> {
/**
* Name of element
*/
private final VM_Atom name;
/**
* Elements value, decoded from its tag
*/
private final Object value;
/**
* Construct a read value pair
*/
private AnnotationMember(VM_Atom name, Object value) {
this.name = name;
this.value = value;
}
/**
* Read the pair from the input stream and create object
* @param constantPool the constant pool for the class being read
* @param input stream to read from
* @param classLoader the class loader being used to load this annotation
* @return a newly created annotation member
*/
static AnnotationMember readAnnotationMember(int[] constantPool, DataInputStream input, ClassLoader classLoader)
throws IOException, ClassNotFoundException {
// Read name of pair
int elemNameIndex = input.readUnsignedShort();
VM_Atom name = VM_Class.getUtf(constantPool, elemNameIndex);
Object value = VM_Annotation.readValue(constantPool, input, classLoader);
return new AnnotationMember(name, value);
}
/**
* Return name as it would appear in a class implementing this
* annotation
*/
VM_Atom getNameAsFieldName() {
return VM_Atom.findAsciiAtom(name.toString() + "_field");
}
/**
* Set the value to the given field of the given annotation
*/
void setValueToField(VM_Field field, Annotation annotation) {
if (value instanceof Boolean) {
field.setBooleanValueUnchecked(annotation, (Boolean) value);
} else if (value instanceof Integer) {
field.setIntValueUnchecked(annotation, (Integer) value);
} else if (value instanceof Long) {
field.setLongValueUnchecked(annotation, (Long) value);
} else if (value instanceof Byte) {
field.setByteValueUnchecked(annotation, (Byte) value);
} else if (value instanceof Character) {
field.setCharValueUnchecked(annotation, (Character) value);
} else if (value instanceof Short) {
field.setShortValueUnchecked(annotation, (Short) value);
} else if (value instanceof Float) {
field.setFloatValueUnchecked(annotation, (Float) value);
} else if (value instanceof Double) {
field.setDoubleValueUnchecked(annotation, (Double) value);
} else {
field.setObjectValueUnchecked(annotation, value);
}
}
/**
* String representation of the value pair of the form
* "name=value"
*/
public String toString() {
String result = name.toString() + "=";
if (value instanceof Object[]) {
result += "{";
Object[] a = (Object[]) value;
for (int i = 0; i < a.length; i++) {
result += a[i].toString();
if (i < (a.length - 1)) {
result += ", ";
}
result += "}";
}
} else {
result += value.toString();
}
return result;
}
/**
* Ordering for sorted annotation members
*/
public int compareTo(AnnotationMember am) {
if (am.name != this.name) {
return am.name.toString().compareTo(this.name.toString());
} else {
if (value.getClass().isArray()) {
return Arrays.hashCode((Object[]) value) - Arrays.hashCode((Object[]) am.value);
} else {
@SuppressWarnings("unchecked") // True generic programming, we can't type check it in Java
Comparable<Object> cValue = (Comparable) value;
return cValue.compareTo(am.value);
}
}
}
}
}