//
// Copyright (C) 2006 United States Government as represented by the
// Administrator of the National Aeronautics and Space Administration
// (NASA). All Rights Reserved.
//
// This software is distributed under the NASA Open Source Agreement
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
// directory tree for the complete NOSA document.
//
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
//
package gov.nasa.jpf.vm;
import java.util.HashMap;
import gov.nasa.jpf.JPFException;
/**
* the JPF internal representation for Java Annotations
*
* AnnotationInfos represent a separate type system. While we could have used normal ClassInfos
* (Java annotations are just restricted interfaces with some syntactic sugar), we keep this separate because
* ClassInfos would be overkill. Besides, our runtime behavior differs in that we synchronously load
* annotation class files when we encounter them during normal ClassInfo construction (i.e. we parse recursively),
* whereas a normal JVM only loads them once they are referenced. The reason why we deviate is that
* annotations are used more often in tools than via reflection within the SUT, i.e. they most likely will
* be read either by JPF or by listeners, so we want them as soon as possible to avoid additional class state.
* This also means we do not faithfully model ClassNotFoundExceptions on annotations due to reflection
* calls within the SUT, but that seems less important than having them available during ClassInfo construction.
* This mostly matters because of default values and inherited class annotations.
*
* Note - AnnotationInfos loaded by the same ClassLoader that do not have explicitly set values are shared
* between annotated objects
*/
public class AnnotationInfo implements Cloneable {
// NOTE - never modify an Entry object since it might be shared between
// different instances of the same annotation type
public static class Entry implements Cloneable {
String key;
Object value;
public String getKey() {
return key;
}
public Object getValue() {
return value;
}
public Entry (String key, Object value){
this.key = key;
this.value = value;
}
public Entry clone(){
try {
return (Entry) super.clone();
} catch (CloneNotSupportedException cnsx){
throw new JPFException("AnnotationInfo.Entry clone() failed");
}
}
}
public static class EnumValue {
String eClassName;
String eConst;
EnumValue (String clsName, String constName){
eClassName = clsName;
eConst = constName;
}
public String getEnumClassName(){
return eClassName;
}
public String getEnumConstName(){
return eConst;
}
public String toString(){
return eClassName + '.' + eConst;
}
}
public static class ClassValue {
String name;
ClassValue (String cn){
name = cn;
}
public String getName(){
return name;
}
public String toString(){
return name;
}
}
static final Entry[] NONE = new Entry[0];
// we have to jump through a lot of hoops to handle default annotation parameter values
// this is not ideal, since it causes the classfile to be re-read if the SUT
// uses annotation reflection (which creates a ClassInfo), but this is rather
// exotic, so we save some time by not creating a ClassInfo (which would hold
// the default vals as method annotations) and directly store the default values here
static HashMap<String, AnnotationAttribute> annotationAttributes = new HashMap<String, AnnotationAttribute>();
public static class AnnotationAttribute {
Entry[] defaultEntries;
boolean isInherited;
AnnotationAttribute (Entry[] defaultEntries, boolean isInherited) {
this.defaultEntries = defaultEntries;
this.isInherited = isInherited;
}
}
public static Object getEnumValue(String eType, String eConst){
return new EnumValue( Types.getClassNameFromTypeName(eType), eConst);
}
public static Object getClassValue(String type){
return new ClassValue( Types.getClassNameFromTypeName(type));
}
protected String name;
protected Entry[] entries;
protected boolean isInherited = false;
/**
* this records if the associated class file has been loaded. If it isn't resolved yet,
* we don't know about default values, hence we need to check before retrieving field values
* that have not been explicitly set. Note this is search global and hence does not need to
* be state managed since we only check for default values, i.e. there are no side effects.
* Loading has to happen with the right ClassLoader though
*/
protected ClassLoaderInfo classLoader; // set once it is resolved (i.e. the corresponding classfile is read)
public AnnotationInfo (String name, ClassLoaderInfo classLoader, AnnotationParser parser) throws ClassParseException {
this.name = name;
this.classLoader = classLoader;
parser.parse(this);
}
//--- the init API used by AnnotationParsers
public void setName (String name) throws ClassParseException {
if (!this.name.equals(name)){
throw new ClassParseException("wrong annotation name in classfile, expected " + this.name + ", found " + name);
}
}
public void setEntries (Entry[] entries){
this.entries = entries;
}
public void setInherited (boolean isInherited){
this.isInherited = isInherited;
}
public AnnotationInfo (String name, Entry[] entries, boolean isInherited){
this.name = name;
this.entries = entries;
this.isInherited = isInherited;
}
public boolean isInherited (){
return this.isInherited;
}
public ClassLoaderInfo getClassLoaderInfo(){
return classLoader;
}
public String getName() {
return name;
}
protected AnnotationInfo cloneFor (ClassLoaderInfo cl){
try {
AnnotationInfo ai = (AnnotationInfo) clone();
// <2do> once we support class/enum values we have to clone these too
ai.classLoader = cl;
return ai;
} catch (CloneNotSupportedException cnsx){
throw new JPFException("AnnotationInfo cloneFor() failed");
}
}
/**
* this returns a clone that can be used to explicitly set values.
* NOTE - Entry instances are still shared, i.e. to change values we have to create and set
* new Entry instances
*/
public AnnotationInfo cloneForOverriddenValues(){
try {
AnnotationInfo ai = (AnnotationInfo) clone();
ai.entries = entries.clone();
return ai;
} catch (CloneNotSupportedException cnsx){
throw new JPFException("AnnotationInfo cloneFor() failed");
}
}
public void setClonedEntryValue (String key, Object newValue){
for (int i=0; i<entries.length; i++){
if (entries[i].getKey().equals(key)){
entries[i] = new Entry( key, newValue);
return;
}
}
}
public Entry[] getEntries() {
return entries;
}
/**
* this is the common getter that should trigger parsing the corresponding class file
*/
public Object getValue (String key){
for (int i=0; i<entries.length; i++){
if (entries[i].getKey().equals(key)){
return entries[i].getValue();
}
}
return null;
}
// convenience method for single-attribute annotations
public Object value() {
return getValue("value");
}
public String valueAsString(){
Object v = value();
return (v != null) ? v.toString() : null;
}
public String getValueAsString (String key){
Object v = getValue(key);
return (v != null) ? v.toString() : null;
}
public String[] getValueAsStringArray() {
String a[] = null;
Object v = value();
if (v instanceof Object[]) {
Object[] va = (Object[])v;
a = new String[va.length];
for (int i=0; i<a.length; i++) {
if (va[i] != null) {
a[i] = va[i].toString();
}
}
}
return a;
}
public String[] getValueAsStringArray (String key) {
// <2do> not very efficient
String a[] = null;
Object v = getValue(key);
if (v instanceof Object[]) {
Object[] va = (Object[])v;
a = new String[va.length];
for (int i=0; i<a.length; i++) {
if (va[i] != null) {
a[i] = va[i].toString();
}
}
}
return a;
}
@SuppressWarnings("unchecked")
public <T> T getValue (String key, Class<T> type){
Object v = getValue(key);
if (type.isInstance(v)){
return (T)v;
} else {
return null;
}
}
public boolean getValueAsBoolean (String key){
Object v = getValue(key);
if (v instanceof Boolean){
return ((Boolean)v).booleanValue();
} else {
throw new JPFException("annotation element @" + name + '.' + key + "() not a boolean: " + v);
}
}
public int getValueAsInt (String key){
Object v = getValue(key);
if (v instanceof Integer){
return ((Integer)v).intValue();
} else {
throw new JPFException("annotation element @" + name + '.' + key + "() not an int: " + v);
}
}
public long getValueAsLong (String key){
Object v = getValue(key);
if (v instanceof Long){
return ((Long)v).longValue();
} else {
throw new JPFException("annotation element @" + name + '.' + key + "() not a long: " + v);
}
}
public float getValueAsFloat (String key){
Object v = getValue(key);
if (v instanceof Float){
return ((Float)v).floatValue();
} else {
throw new JPFException("annotation element @" + name + '.' + key + "() not a float: " + v);
}
}
public double getValueAsDouble (String key){
Object v = getValue(key);
if (v instanceof Double){
return ((Double)v).doubleValue();
} else {
throw new JPFException("annotation element @" + name + '.' + key + "() not a double: " + v);
}
}
public String asString() {
StringBuilder sb = new StringBuilder();
sb.append('@');
sb.append(name);
sb.append('[');
for (int i=0; i<entries.length; i++){
if (i > 0){
sb.append(',');
}
sb.append(entries[i].getKey());
sb.append('=');
sb.append(entries[i].getValue());
}
sb.append(']');
return sb.toString();
}
}