Mercurial > hg > Members > kono > jpf-core
diff src/main/gov/nasa/jpf/vm/AnnotationInfo.java @ 0:61d41facf527
initial v8 import (history reset)
author | Peter Mehlitz <Peter.C.Mehlitz@nasa.gov> |
---|---|
date | Fri, 23 Jan 2015 10:14:01 -0800 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/gov/nasa/jpf/vm/AnnotationInfo.java Fri Jan 23 10:14:01 2015 -0800 @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2014, United States Government, as represented by the + * Administrator of the National Aeronautics and Space Administration. + * All rights reserved. + * + * The Java Pathfinder core (jpf-core) platform is 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 gov.nasa.jpf.vm; + +import gov.nasa.jpf.JPFException; +import java.util.HashMap; + +/** + * 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. + * + * AnnotationInfo serves as the concrete type of declaration annotations, and as the base for + * type annotations, holding all the info that comes from the annotation class file. In the first + * case, AnnotationInfo instances can be shared if there are no explicit values. Sharing does not + * make sense for type annotations which need to store site specific target info (from the classfile). + * + * 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; + } + + @Override + 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; + } + @Override + public String toString(){ + return eClassName + '.' + eConst; + } + } + + public static class ClassValue { + String name; + + ClassValue (String cn){ + name = cn; + } + + public String getName(){ + return name; + } + @Override + 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); + } + + /** + * this is the base ctor for AbstractTypeAnnotationInfos, which add additional + * target information from the classfile + */ + protected AnnotationInfo (AnnotationInfo exemplar){ + this.name = exemplar.name; + this.classLoader = exemplar.classLoader; + this.entries = exemplar.entries; + this.isInherited = exemplar.isInherited; + } + + //--- 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 != null && 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 != null && 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 <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(); + } + +}