Fixes null captured parameters
[jpf-core.git] / src / main / gov / nasa / jpf / vm / AnnotationInfo.java
1 /*
2  * Copyright (C) 2014, United States Government, as represented by the
3  * Administrator of the National Aeronautics and Space Administration.
4  * All rights reserved.
5  *
6  * The Java Pathfinder core (jpf-core) platform is licensed under the
7  * Apache License, Version 2.0 (the "License"); you may not use this file except
8  * in compliance with the License. You may obtain a copy of the License at
9  * 
10  *        http://www.apache.org/licenses/LICENSE-2.0. 
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and 
16  * limitations under the License.
17  */
18 package gov.nasa.jpf.vm;
19
20 import gov.nasa.jpf.JPFException;
21 import java.util.HashMap;
22
23 /**
24  * the JPF internal representation for Java Annotations
25  * 
26  * AnnotationInfos represent a separate type system. While we could have used normal ClassInfos
27  * (Java annotations are just restricted interfaces with some syntactic sugar), we keep this separate because
28  * ClassInfos would be overkill. Besides, our runtime behavior differs in that we synchronously load
29  * annotation class files when we encounter them during normal ClassInfo construction (i.e. we parse recursively),
30  * whereas a normal JVM only loads them once they are referenced. The reason why we deviate is that
31  * annotations are used more often in tools than via reflection within the SUT, i.e. they most likely will
32  * be read either by JPF or by listeners, so we want them as soon as possible to avoid additional class state.
33  * This also means we do not faithfully model ClassNotFoundExceptions on annotations due to reflection
34  * calls within the SUT, but that seems less important than having them available during ClassInfo construction.
35  * This mostly matters because of default values and inherited class annotations.
36  * 
37  * AnnotationInfo serves as the concrete type of declaration annotations, and as the base for
38  * type annotations, holding all the info that comes from the annotation class file. In the first
39  * case, AnnotationInfo instances can be shared if there are no explicit values. Sharing does not
40  * make sense for type annotations which need to store site specific target info (from the classfile).
41  * 
42  * Note - AnnotationInfos loaded by the same ClassLoader that do not have explicitly set values are shared
43  * between annotated objects
44  * 
45  */
46 public class AnnotationInfo implements Cloneable {
47
48   // NOTE - never modify an Entry object since it might be shared between
49   // different instances of the same annotation type
50   public static class Entry implements Cloneable {
51     String key;
52     Object value;
53     
54     public String getKey() {
55       return key;
56     }
57
58     public Object getValue() {
59       return value;
60     }
61     
62     public Entry (String key, Object value){
63       this.key = key;
64       this.value = value;
65     }
66     
67     @Override
68         public Entry clone(){
69       try {
70         return (Entry) super.clone();
71       } catch (CloneNotSupportedException cnsx){
72         throw new JPFException("AnnotationInfo.Entry clone() failed");
73       }
74     }
75   }
76   
77   public static class EnumValue {
78     String eClassName;
79     String eConst;
80     
81     EnumValue (String clsName, String constName){
82       eClassName = clsName;
83       eConst = constName;
84     }
85     public String getEnumClassName(){
86       return eClassName;
87     }
88     public String getEnumConstName(){
89       return eConst;
90     }
91     @Override
92         public String toString(){
93       return eClassName + '.' + eConst;
94     }
95   }
96
97   public static class ClassValue {
98     String name;
99
100     ClassValue (String cn){
101       name = cn;
102     }
103
104     public String getName(){
105       return name;
106     }
107     @Override
108         public String toString(){
109       return name;
110     }
111   }
112
113   static final Entry[] NONE = new Entry[0];
114   
115   // we have to jump through a lot of hoops to handle default annotation parameter values
116   // this is not ideal, since it causes the classfile to be re-read if the SUT
117   // uses annotation reflection (which creates a ClassInfo), but this is rather
118   // exotic, so we save some time by not creating a ClassInfo (which would hold
119   // the default vals as method annotations) and directly store the default values here
120
121   static HashMap<String, AnnotationAttribute> annotationAttributes = new HashMap<String, AnnotationAttribute>();
122
123   public static class AnnotationAttribute {
124     Entry[] defaultEntries;
125     boolean isInherited;
126
127     AnnotationAttribute (Entry[] defaultEntries, boolean isInherited) {
128       this.defaultEntries = defaultEntries;
129       this.isInherited = isInherited;
130     }
131   }
132   
133   public static Object getEnumValue(String eType, String eConst){
134     return new EnumValue( Types.getClassNameFromTypeName(eType), eConst);
135   }
136
137   public static Object getClassValue(String type){
138     return new ClassValue( Types.getClassNameFromTypeName(type));
139   }  
140   
141   protected String name;
142   protected Entry[] entries;
143   protected boolean isInherited = false;
144     
145   /**
146    * this records if the associated class file has been loaded. If it isn't resolved yet,
147    * we don't know about default values, hence we need to check before retrieving field values
148    * that have not been explicitly set. Note this is search global and hence does not need to
149    * be state managed since we only check for default values, i.e. there are no side effects.
150    * Loading has to happen with the right ClassLoader though
151    */
152   protected ClassLoaderInfo classLoader; // set once it is resolved (i.e. the corresponding classfile is read)
153   
154   
155   public AnnotationInfo (String name, ClassLoaderInfo classLoader, AnnotationParser parser) throws ClassParseException {
156     this.name = name;
157     this.classLoader = classLoader;
158     
159     parser.parse(this);
160   }
161   
162   /**
163    * this is the base ctor for AbstractTypeAnnotationInfos, which add additional
164    * target information from the classfile
165    */
166   protected AnnotationInfo (AnnotationInfo exemplar){
167     this.name = exemplar.name;
168     this.classLoader = exemplar.classLoader;
169     this.entries = exemplar.entries;
170     this.isInherited = exemplar.isInherited;
171   }
172   
173   //--- the init API used by AnnotationParsers
174   public void setName (String name) throws ClassParseException {
175     if (!this.name.equals(name)){
176       throw new ClassParseException("wrong annotation name in classfile, expected " + this.name + ", found " + name);
177     }
178   }
179
180   public void setEntries (Entry[] entries){
181     this.entries = entries;
182   }
183   
184   public void setInherited (boolean isInherited){
185     this.isInherited = isInherited;
186   }
187   
188   
189   public AnnotationInfo (String name, Entry[] entries, boolean isInherited){
190     this.name = name;
191     this.entries = entries;
192     this.isInherited = isInherited;
193   }
194
195
196   public boolean isInherited (){
197     return this.isInherited;
198   }
199   
200   public ClassLoaderInfo getClassLoaderInfo(){
201     return classLoader;
202   }
203
204   public String getName() {
205     return name;
206   }
207   
208   protected AnnotationInfo cloneFor (ClassLoaderInfo cl){
209     try {
210       AnnotationInfo ai = (AnnotationInfo) clone();
211       
212       // <2do> once we support class/enum values we have to clone these too
213       
214       ai.classLoader = cl;
215       
216       return ai;
217       
218     } catch (CloneNotSupportedException cnsx){
219       throw new JPFException("AnnotationInfo cloneFor() failed");
220     }
221   }
222   
223   /**
224    * this returns a clone that can be used to explicitly set values.
225    * NOTE - Entry instances are still shared, i.e. to change values we have to create and set
226    * new Entry instances
227    */
228   public AnnotationInfo cloneForOverriddenValues(){
229     try {
230       AnnotationInfo ai = (AnnotationInfo) clone();
231       ai.entries = entries.clone();
232       return ai;
233       
234     } catch (CloneNotSupportedException cnsx){
235       throw new JPFException("AnnotationInfo cloneFor() failed");
236     }    
237   }
238   
239   public void setClonedEntryValue (String key, Object newValue){
240     for (int i=0; i<entries.length; i++){
241       if (entries[i].getKey().equals(key)){
242         entries[i] = new Entry( key, newValue);
243         return;
244       }
245     }    
246   }
247   
248   public Entry[] getEntries() {
249     return entries;
250   }
251   
252   /**
253    * this is the common getter that should trigger parsing the corresponding class file 
254    */
255   public Object getValue (String key){    
256     for (int i=0; i<entries.length; i++){
257       if (entries[i].getKey().equals(key)){
258         return entries[i].getValue();
259       }
260     }
261     return null;
262   }
263
264   
265   // convenience method for single-attribute annotations
266   public Object value() {
267     return getValue("value");
268   }
269   
270   public String valueAsString(){
271     Object v = value();
272     return (v != null) ? v.toString() : null;
273   }
274   
275   public String getValueAsString (String key){
276     Object v = getValue(key);
277     return (v != null) ? v.toString() : null;
278   }
279   
280   public String[] getValueAsStringArray() {
281     String a[] = null; 
282     Object v = value();
283     if (v != null && v instanceof Object[]) {
284       Object[] va = (Object[])v;
285       a = new String[va.length];
286       for (int i=0; i<a.length; i++) {
287         if (va[i] != null) {
288           a[i] = va[i].toString();
289         }
290       }
291     }
292     
293     return a;    
294   }
295   
296   public String[] getValueAsStringArray (String key) {
297     // <2do> not very efficient
298     String a[] = null; 
299     Object v = getValue(key);
300     if (v != null && v instanceof Object[]) {
301       Object[] va = (Object[])v;
302       a = new String[va.length];
303       for (int i=0; i<a.length; i++) {
304         if (va[i] != null) {
305           a[i] = va[i].toString();
306         }
307       }
308     }
309     
310     return a;
311   }
312   
313   public <T> T getValue (String key, Class<T> type){
314     Object v = getValue(key);
315     if (type.isInstance(v)){
316       return (T)v;
317     } else {
318       return null;
319     }
320   }
321   
322   public boolean getValueAsBoolean (String key){
323     Object v = getValue(key);
324     if (v instanceof Boolean){
325       return ((Boolean)v).booleanValue();
326     } else {
327       throw new JPFException("annotation element @" + name + '.' + key + "() not a boolean: " + v);
328     }
329   }
330   
331   public int getValueAsInt (String key){
332     Object v = getValue(key);
333     if (v instanceof Integer){
334       return ((Integer)v).intValue();
335     } else {
336       throw new JPFException("annotation element @" + name + '.' + key + "() not an int: " + v);
337     }
338   }
339
340   public long getValueAsLong (String key){
341     Object v = getValue(key);
342     if (v instanceof Long){
343       return ((Long)v).longValue();
344     } else {
345       throw new JPFException("annotation element @" + name + '.' + key + "() not a long: " + v);
346     }
347   }
348
349   public float getValueAsFloat (String key){
350     Object v = getValue(key);
351     if (v instanceof Float){
352       return ((Float)v).floatValue();
353     } else {
354       throw new JPFException("annotation element @" + name + '.' + key + "() not a float: " + v);
355     }
356   }
357   
358   public double getValueAsDouble (String key){
359     Object v = getValue(key);
360     if (v instanceof Double){
361       return ((Double)v).doubleValue();
362     } else {
363       throw new JPFException("annotation element @" + name + '.' + key + "() not a double: " + v);
364     }
365   }
366   
367   public String asString() {
368     StringBuilder sb = new StringBuilder();
369     sb.append('@');
370     sb.append(name);
371     sb.append('[');
372     for (int i=0; i<entries.length; i++){
373       if (i > 0){
374         sb.append(',');
375       }
376       sb.append(entries[i].getKey());
377       sb.append('=');
378       sb.append(entries[i].getValue());
379     }
380     sb.append(']');
381     
382     return sb.toString();
383   }
384
385 }