/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.vm.classmgr;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.jnode.vm.objects.VmSystemObject;
import sun.reflect.annotation.AnnotationParser;
/**
* VM representation of a single annotation.
*
* @author Ewout Prangsma (epr@users.sourceforge.net)
*/
public final class VmAnnotation extends VmSystemObject {
/**
* An empty array of annotations
*/
static final VmAnnotation[] EMPTY_ARR = new VmAnnotation[0];
/**
* The descriptor of the annotation type
*/
private final String typeDescr;
/**
* The element values
*/
private ElementValue[] values;
/**
* The type of this annotation
*/
private transient VmType<? extends Annotation> annType;
/**
* The type implementing this annotation
*/
private transient Annotation value;
/**
* @param typeDescr the annotation type descriptor
* @param values annotation parameter name-value pairs
*/
public VmAnnotation(String typeDescr, ElementValue[] values) {
this.typeDescr = typeDescr;
this.values = values;
}
/**
* Gets the type of this annotation.
*/
@SuppressWarnings("unchecked")
final VmType<? extends Annotation> annotationType(VmClassLoader loader) {
if (annType == null) {
try {
annType = new Signature(typeDescr, loader).getType();
} catch (ClassNotFoundException ex) {
throw new NoClassDefFoundError(ex.getMessage());
}
}
return annType;
}
/**
* Gets the annotation value.
*
* @param loader
* @return
* @throws ClassNotFoundException
*/
@SuppressWarnings("unchecked")
final Annotation getValue(VmClassLoader loader)
throws ClassNotFoundException {
if (value == null) {
final VmType<? extends Annotation> annType = annotationType(loader);
//todo will be obsolate when annotation paring is migrated to openjdk
if (!annType.getName().equals(org.jnode.annotation.AllowedPackages.class.getName())) {
int dmc = annType.getNoDeclaredMethods();
Map vmap = new HashMap();
for (int i = 0; i < dmc; i++) {
VmMethod met = annType.getDeclaredMethod(i);
Object o = met.getAnnotationDefault();
if (o != null) {
String name = met.getName();
vmap.put(name, o);
}
}
for (ElementValue value1 : values) {
vmap.put(value1.name, value1.value);
}
Set<Map.Entry> set = vmap.entrySet();
values = new ElementValue[set.size()];
int i = 0;
for (Map.Entry e : set) {
values[i++] = new ElementValue((String) e.getKey(), e.getValue());
}
for (Map.Entry e : set) {
Object o = e.getValue();
if (o != null && o.getClass().isArray()) {
Object[] arr = (Object[]) o;
if (arr.length > 0) {
Object el = arr[0];
Class elc = el.getClass();
Object[] ar2 = (Object[]) Array.newInstance(elc, arr.length);
for (int v = 0; v < arr.length; v++) {
ar2[v] = elc.cast(arr[v]);
}
e.setValue(ar2);
}
}
}
//try {
/*value =*/
return AnnotationParser.annotationForMap(loader.asClassLoader().loadClass(annType.getName()), vmap);
} else {
final VmType<? extends ImplBase> implType = getImplClass(annType);
final ImplBase value;
try {
value = implType.asClass().newInstance();
} catch (InstantiationException ex) {
throw new RuntimeException(ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
}
value.initialize(annType, values, loader);
this.value = value;
}
}
return value;
}
/**
* Gets the annotation implementation class.
*/
@SuppressWarnings("unchecked")
private VmNormalClass<? extends ImplBase> getImplClass(VmType<?> annType)
throws ClassNotFoundException {
final String clsName = annType.getName() + "$Impl";
try {
return (VmNormalClass<? extends ImplBase>) annType.getLoader()
.loadClass(clsName, true);
} catch (ClassNotFoundException ex) {
return createImplClass(annType, clsName);
}
}
/**
* Create a class that implements this annotation.
*
* @param loader
* @return
* @throws ClassNotFoundException
*/
@SuppressWarnings("unchecked")
private VmNormalClass<? extends ImplBase> createImplClass(
VmType<?> annType, String clsName) throws ClassNotFoundException {
// Create the implementation class
final VmClassLoader loader = annType.getLoader();
final String superClassName = ImplBase.class.getName();
final int accFlags = Modifier.ACC_FINAL | Modifier.ACC_PUBLIC;
final VmNormalClass<? extends ImplBase> implType = new VmNormalClass(
clsName, superClassName, loader, accFlags, annType
.getProtectionDomain());
// Create the constant pool
final VmCP cp = new VmCP(2);
final VmConstClass superClassRef = new VmConstClass(superClassName);
// Set field ref values[] at index 0
cp.setConstFieldRef(0, new VmConstFieldRef(superClassRef, "values",
"[Ljava/lang/Object;"));
// Set <init> ref at index 1
cp.setConstMethodRef(1, new VmConstMethodRef(superClassRef, "<init>",
"()V"));
implType.setCp(cp);
// Create method table
final int mcnt = annType.getNoDeclaredMethods();
final VmMethod[] methods = new VmMethod[mcnt + 1];
// Add default constructor
final int caccFlags = Modifier.ACC_FINAL | Modifier.ACC_PUBLIC;
final VmSpecialMethod cons = new VmSpecialMethod("<init>", "()V",
caccFlags, implType);
methods[0] = cons;
final byte[] cbytes = new byte[5];
cbytes[0] = (byte) 0x2a; // aload_0
cbytes[1] = (byte) 0xB7; // invokespecial <init>
cbytes[2] = (byte) 0x00;
cbytes[3] = (byte) 0x01;
cbytes[4] = (byte) 0xb1; // return
cons.setBytecode(new VmByteCode(cons, ByteBuffer.wrap(cbytes), 1,
2, null, null, null));
// Add the methods
final int maccFlags = Modifier.ACC_FINAL | Modifier.ACC_PUBLIC;
for (int i = 0; i < mcnt; i++) {
final VmMethod imethod = annType.getDeclaredMethod(i);
final VmInstanceMethod m = new VmInstanceMethod(imethod.getName(),
imethod.getSignature(), maccFlags, implType);
final int noLocals = 1; // this == local0
final int maxStack = 3; // TODO fix me
// Create bytecode
final byte[] bytes = new byte[8];
bytes[0] = (byte) 0x2a; // aload_0
bytes[1] = (byte) 0xB4; // getfield <values>
bytes[2] = (byte) 0x00;
bytes[3] = (byte) 0x00;
bytes[4] = (byte) 0x10; // bipush <element_value index>
bytes[5] = (byte) indexOfElementValue(imethod.getName());
bytes[6] = (byte) 0x32; // aaload
bytes[7] = (byte) 0xB0; // areturn
m.setBytecode(new VmByteCode(m, ByteBuffer.wrap(bytes), noLocals,
maxStack, null, null, null));
methods[i + 1] = m;
}
implType.setMethodTable(methods);
// Add the implemented interfaces
final VmImplementedInterface[] itable = new VmImplementedInterface[1];
itable[0] = new VmImplementedInterface(annType);
implType.setInterfaceTable(itable);
return (VmNormalClass<? extends ImplBase>) loader
.defineClass(implType);
}
/**
* Gets the index in the values array of the element with the given name.
*/
private final int indexOfElementValue(String name) {
for (int i = 0;; i++) {
if (values[i].getName().equals(name)) {
return i;
}
}
}
/**
* Class holding a single element_value structure.
*
* @author Ewout Prangsma (epr@users.sourceforge.net)
*/
static final class ElementValue extends VmSystemObject {
static final ElementValue[] EMPTY_ARR = new ElementValue[0];
private final String name;
private final Object value;
/**
* @param name
* @param value
*/
public ElementValue(String name, Object value) {
this.name = name;
this.value = value;
}
/**
* @return Returns the name.
*/
final String getName() {
return name;
}
/**
* @return Returns the value.
* @throws ClassNotFoundException
*/
final Object getValue(VmClassLoader loader, VmType<?> valueType)
throws ClassNotFoundException {
return resolve(value, loader, valueType);
}
private final Object resolve(Object value, VmClassLoader loader, VmType<?> valueType)
throws ClassNotFoundException {
if (value instanceof EnumValue) {
return ((EnumValue) value).getValue(loader);
} else if (value instanceof ClassInfo) {
return ((ClassInfo) value).getValue(loader);
} else if (value instanceof VmAnnotation) {
return ((VmAnnotation) value).getValue(loader);
} else if (value instanceof Object[]) {
final Object[] arr = (Object[]) value;
final int max = arr.length;
final Class<?> compType = ((VmArrayClass<?>) valueType).getComponentType().asClass();
final Object result = Array.newInstance(compType, max);
for (int i = 0; i < max; i++) {
final Object v = resolve(arr[i], loader, VmType.getObjectClass());
if (v != null) {
Array.set(result, i, v);
}
}
return result;
} else {
return value;
}
}
}
/**
* Class representing an enum_const_value structure.
*
* @author Ewout Prangsma (epr@users.sourceforge.net)
*/
static final class EnumValue {
private final String typeDescr;
private final String constName;
private transient Object value;
/**
* @param typeDescr
* @param constName
*/
public EnumValue(String typeDescr, String constName) {
this.typeDescr = typeDescr;
this.constName = constName;
}
/**
* @return Returns the constName.
*/
final String getConstName() {
return constName;
}
/**
* @return Returns the typeDescr.
*/
final String getTypeDescriptor() {
return typeDescr;
}
/**
* @return Returns the value.
* @throws ClassNotFoundException
*/
@SuppressWarnings("unchecked")
final Object getValue(VmClassLoader loader)
throws ClassNotFoundException {
if (value == null) {
// Load the enum value
final VmType enumType = new Signature(typeDescr, loader)
.getType();
value = Enum.valueOf(enumType.asClass(), constName);
}
return value;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return typeDescr + '#' + constName;
}
}
/**
* Class representing a single class_info structure.
*
* @author Ewout Prangsma (epr@users.sourceforge.net)
*/
static final class ClassInfo extends VmSystemObject {
private final String classDescr;
private transient Class value;
/**
* @param classDescr
*/
public ClassInfo(String classDescr) {
this.classDescr = classDescr;
}
/**
* @return Returns the value.
* @throws ClassNotFoundException
*/
@SuppressWarnings("unchecked")
final Object getValue(VmClassLoader loader)
throws ClassNotFoundException {
if (value == null) {
value = new Signature(classDescr, loader).getType().asClass();
}
return value;
}
}
/**
* Base class used to implement annotations
*
* @author Ewout Prangsma (epr@users.sourceforge.net)
*/
public static final class ImplBase implements Annotation {
/**
* The annotation type
*/
private VmType<? extends Annotation> annotationType;
/**
* The actual values
*/
protected Object[] values;
/**
* The loaded name+value pairs
*/
private ElementValue[] elemValues;
/**
* Should this annotation be inherited
*/
private boolean inheritable;
/**
* Initialize this annotation implementation.
*
* @param type
* @param values
* @param loader
* @throws ClassNotFoundException
*/
final void initialize(VmType<? extends Annotation> type,
ElementValue[] values, VmClassLoader loader)
throws ClassNotFoundException {
this.annotationType = type;
this.elemValues = values;
this.values = new Object[values.length];
int i = 0;
for (ElementValue v : values) {
this.values[i++] = v.getValue(loader, getReturnType(type, v.getName()));
}
this.inheritable = type.isAnnotationPresent(Inherited.class);
}
private final VmType getReturnType(VmType<?> type, String elemName) {
for (int i = 0;; i++) {
final VmMethod m = type.getDeclaredMethod(i);
if (m.getName().equals(elemName)) {
return m.getReturnType();
}
}
}
/**
* Should this annotation be inherited.
*/
public final boolean isInheritable() {
return inheritable;
}
/**
* @see java.lang.annotation.Annotation#annotationType()
*/
public Class<? extends Annotation> annotationType() {
return annotationType.asClass();
}
/**
* Converts to a string representation.
*/
public final String toString() {
final StringBuilder sb = new StringBuilder();
sb.append('@');
sb.append(annotationType.getName());
sb.append('(');
final int max = values.length;
for (int i = 0; i < max; i++) {
if (i > 0) {
sb.append(", ");
}
if (max > 1) {
sb.append(elemValues[i].getName());
sb.append('=');
}
final Object value = values[i];
if ((value != null) && (value.getClass().isArray())) {
sb.append('[');
final int arrLen = Array.getLength(value);
for (int j = 0; j < arrLen; j++) {
if (j > 0) {
sb.append(", ");
}
sb.append(Array.get(value, j));
}
sb.append(']');
} else {
sb.append(value);
}
}
sb.append(')');
return sb.toString();
}
}
/**
* @return Returns the typeDescr.
*/
final String getTypeDescriptor() {
return typeDescr;
}
}