001    package edu.rice.cs.cunit.subAnnot;
002    
003    import edu.rice.cs.cunit.classFile.ClassFile;
004    import edu.rice.cs.cunit.classFile.ClassFileTools;
005    import edu.rice.cs.cunit.classFile.MethodInfo;
006    import edu.rice.cs.cunit.classFile.attributes.AAnnotationsAttributeInfo;
007    import edu.rice.cs.cunit.classFile.attributes.RuntimeVisibleAnnotationsAttributeInfo;
008    import edu.rice.cs.cunit.classFile.attributes.RuntimeVisibleLocalVariableAnnotationsAttributeInfo;
009    import edu.rice.cs.cunit.classFile.attributes.RuntimeVisibleParameterAnnotationsAttributeInfo;
010    
011    import java.io.File;
012    import java.io.IOException;
013    import java.lang.annotation.Annotation;
014    import java.lang.annotation.Inherited;
015    import java.lang.reflect.AnnotatedElement;
016    import java.lang.reflect.Member;
017    import java.lang.reflect.Proxy;
018    import java.util.ArrayList;
019    import java.util.Arrays;
020    import java.util.List;
021    
022    /**
023     * Abstract class for annotated elements supporting annotations with subtyping.
024     * 
025     * @author Mathias Ricken
026     */
027    public abstract class AAnnotatedElementEx implements AnnotatedElementEx {
028        /**
029         * Annotations attribute.
030         */
031        protected RuntimeVisibleAnnotationsAttributeInfo[] _ai;
032        
033        /**
034         * True if the class file was not found and only the information given by Java's Class class
035         * is available.
036         */
037        protected boolean _classFileNotFound = false;
038        
039        /**
040         * Class loader to use, or null to use the bootstrap class loader.
041         */
042        protected ClassLoader _classLoader = null;
043    
044        /**
045         * Constructor leaving the _classLoader field empty.
046         */
047        protected AAnnotatedElementEx() { }
048    
049        /**
050         * Constructor setting the _classLoader field.
051         * @param cl class loader
052         */
053        protected AAnnotatedElementEx(ClassLoader cl) {
054            _classLoader = cl;
055        }
056    
057        /**
058         * List of class path entries.
059         */
060        protected static ArrayList<String> _classPath = new ArrayList<String>();
061        static {
062            _classPath.addAll(Arrays.asList(System.getProperty("sun.boot.class.path").split(File.pathSeparator)));
063            _classPath.addAll(Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator)));
064        }
065        
066        /**
067         * Returns the class loader used.
068         * @return class loader or null if the bootstrap class loader is used.
069         */
070        public ClassLoader getClassLoader() { return _classLoader; }
071        
072        /**
073         * Returns true if the class file was found and additional information is available beyond what
074         * Java's Class class might return.
075         * @return true if class file was found.
076         */
077        public boolean isAvailable() { return !_classFileNotFound; }
078    
079        /**
080         * Set the state of the _classFileNotFound flag.
081         * @param b new state of the _classFileNotFound flag.
082         */
083        protected void setClassFileNotFound(boolean b) {
084            // System.err.println("Class file for "+getAnnotatedElement()+" not found!");
085            _classFileNotFound = b;
086        }
087        
088        /**
089         * Returns true if an annotation for the specified type or one of its subtypes is present on this element,
090         * else false. This method is designed primarily for convenient access to marker annotations.
091         * @param c class
092         * @return true if present
093         */
094        public boolean isAnnotationPresent(ClassEx<? extends Annotation> c) {
095            return (getAnnotations(c).size()>0);
096        }
097    
098        /**
099         * Returns true if an annotation for the specified type or one of its subtypes is present on this element,
100         * else false. This method is designed primarily for convenient access to marker annotations.
101         * @param c class
102         * @return true if present
103         */
104        public boolean isAnnotationPresent(Class<? extends Annotation> c) {
105            return (getAnnotations(c).size()>0);
106        }
107    
108        /**
109         * Return an array of the annotations attached to the element that are subclasses of the specified class.
110         * @param c class
111         * @return array of annotations
112         */
113        public <A extends Annotation> List<A> getAnnotations(ClassEx<A> c) {
114            return getAnnotations(c.java);
115        }
116    
117        /**
118         * Return an array of the annotations attached to the element that are subclasses of the specified class.
119         * @param c class
120         * @return array of annotations
121         */
122        @SuppressWarnings("unchecked")
123        public <A extends Annotation> List<A> getAnnotations(Class<A> c) {
124            ArrayList<A> as = new ArrayList<A>();
125            for(Annotation a: getAnnotations()) {
126                if (c.isAssignableFrom(a.annotationType())) { as.add((A)a); }
127            }
128            return as;
129        }
130    
131        /**
132         * Return an array of all annotations attached to the element.
133         * @return array of annotations
134         */
135        @SuppressWarnings("unchecked")
136        public Annotation[] getAnnotations() {
137            if (!isAvailable()) {
138                return getAnnotatedElement().getAnnotations();
139            }
140            if ((_ai==null) || (_ai.length<1)) { return new Annotation[0]; }
141            ArrayList<Annotation> as = new ArrayList<Annotation>();
142            boolean first = true;
143            for (RuntimeVisibleAnnotationsAttributeInfo ai : _ai) {
144                if (ai!=null) {
145                    for (AAnnotationsAttributeInfo.Annotation aia : ai.getAnnotations()) {
146                        String name = aia.getType();
147                        try {
148                            Class<? extends Annotation> aClass;
149                            if (_classLoader!=null) {
150                                // System.out.println("ClassLoader!=null: "+_classLoader);
151                                aClass = (Class<? extends Annotation>)ClassFileTools.getClassFromType(name, true, _classLoader);
152                            }
153                            else {
154                                // System.out.println("ClassLoader==null");
155                                aClass = (Class<? extends Annotation>)ClassFileTools.getClassFromType(name);
156                            }
157                            if ((first) || (aClass.isAnnotationPresent(Inherited.class))) {
158                                // add annotation if in class itself or if annotation is marked as @Inherited
159                                as.add((Annotation) Proxy.newProxyInstance(
160                                        aClass.getClassLoader(),
161                                        new Class[]{aClass},
162                                        new AnnotationDynamicProxyHandler(aClass, aia)));
163                            }
164                        }
165                        catch (ClassNotFoundException cnfe) {
166                            cnfe.printStackTrace();
167                            throw new ClassFormatError("Could not load annotation class " + ClassFileTools.getClassNameFromType(name));
168                        }
169                    }
170                }
171                first = false;
172            }
173            return as.toArray(new Annotation[as.size()]);
174        }
175    
176        /**
177         * Returns true if an annotation for the specified type or one of its subtypes is present on this element,
178         * else false. This method is designed primarily for convenient access to marker annotations.
179         * Contrary to isAnnotationPresent, this does not consider annotations inherited from superclasses.
180         * @param c class
181         * @return true if present
182         */
183        public boolean isDeclaredAnnotationPresent(ClassEx<? extends Annotation> c) {
184            return (getDeclaredAnnotations(c).size()>0);
185        }
186    
187        /**
188         * Returns true if an annotation for the specified type or one of its subtypes is present on this element,
189         * else false. This method is designed primarily for convenient access to marker annotations.
190         * Contrary to isAnnotationPresent, this does not consider annotations inherited from superclasses.
191         * @param c class
192         * @return true if present
193         */
194        public boolean isDeclaredAnnotationPresent(Class<? extends Annotation> c) {
195            return (getDeclaredAnnotations(c).size()>0);
196        }
197    
198        /**
199         * Return an array of the annotations attached to the element that are subclasses of the specified class.
200         * Contrary to getAnnotations, this does not consider annotations inherited from superclasses.
201         * @param c class
202         * @return array of annotations
203         */
204        public <A extends Annotation> List<A> getDeclaredAnnotations(ClassEx<A> c) {
205            return getDeclaredAnnotations(c.java);
206        }
207    
208        /**
209         * Return an array of the annotations attached to the element that are subclasses of the specified class.
210         * Contrary to isAnnotationPresent, this does not consider annotations inherited from superclasses.
211         * @param c class
212         * @return array of annotations
213         */
214        @SuppressWarnings("unchecked")
215        public <A extends Annotation> List<A> getDeclaredAnnotations(Class<A> c) {
216            ArrayList<A> as = new ArrayList<A>();
217            for(Annotation a: getDeclaredAnnotations()) {
218                if (c.isAssignableFrom(a.getClass())) { as.add((A)a); }
219            }
220            return as;
221        }
222    
223        /**
224         * Return an array of all annotations attached directly to the element.
225         * Contrary to getAnnotations(), this does not consider annotations inherited from superclasses.
226         * @return array of annotations
227         */
228        @SuppressWarnings("unchecked")
229        public Annotation[] getDeclaredAnnotations() {
230            if (!isAvailable()) {
231                return getAnnotatedElement().getAnnotations();
232            }
233            if ((_ai == null) || (_ai.length < 1) || (_ai[0]==null)) {
234                return new Annotation[0];
235            }
236            ArrayList<Annotation> as = new ArrayList<Annotation>();
237            for (AAnnotationsAttributeInfo.Annotation aia : _ai[0].getAnnotations()) {
238                String name = aia.getType();
239                try {
240                    Class<? extends Annotation> aClass = (Class<? extends Annotation>)ClassFileTools.getClassFromType(name);
241                    as.add((Annotation) Proxy.newProxyInstance(
242                            aClass.getClassLoader(),
243                            new Class[]{aClass},
244                            new AnnotationDynamicProxyHandler(aClass, aia)));
245                }
246                catch (ClassNotFoundException cnfe) {
247                    throw new ClassFormatError("Could not load annotation class " + name);
248                }
249            }
250            return as.toArray(new Annotation[as.size()]);
251        }
252    
253        /**
254         * Return the annotated element.
255         * @return annotated element
256         */
257        protected abstract AnnotatedElement getAnnotatedElement();
258    
259        /**
260         * Find the annotations attribute and assign it to the _ai field.
261         * @param member member whose annotations attribute should be found
262         * @param name member name
263         * @param types parameter types
264         */
265        protected void findMethodAnnotationsAttributeInfo(Member member, String name, Class<?>[] types) {
266            if (member.getDeclaringClass().isPrimitive()) { _ai = null; return; }
267            ClassFileTools.ClassLocation cl = ClassFileTools.findClassFile(member.getDeclaringClass().getName(), _classPath);
268            if (cl==null) {
269                setClassFileNotFound(true);
270                return;
271            }
272            ClassFile cf = cl.getClassFile();
273            try {
274                cl.close();
275            }
276            catch(IOException e) { /* ignore */ }
277            for(MethodInfo mi: cf.getMethods()) {
278                if (mi.getName().toString().equals(name)) {
279                    boolean match = true;
280                    // descriptor is "(...)...", remove "(" and ")..."
281                    String desc = mi.getDescriptor().toString();
282                    List<String> paramTypeList = ClassFileTools.getSignatures(desc.substring(1, desc.lastIndexOf(')')));
283                    if (paramTypeList.size()==types.length) {
284                        for(int i=0; i<paramTypeList.size(); ++i) {
285                            String pt = paramTypeList.get(i);
286                            String cpt = types[i].getName();
287                            if ((pt.length()>0) && (pt.charAt(0)=='[') &&
288                                (cpt.length()>0) && (cpt.charAt(0)=='[')) {
289                                // if both type strings are arrays, cut off matching '[' prefixes
290                                do {
291                                    pt = pt.substring(1);
292                                    cpt = cpt.substring(1);
293                                } while ((pt.length()>0) && (pt.charAt(0)=='[') &&
294                                         (cpt.length()>0) && (cpt.charAt(0)=='['));
295                                // if this is an object array, the class name will be "Lxxx;"
296                                // cut off the 'L' and ';'
297                                if ((cpt.charAt(0)=='L') && (cpt.charAt(cpt.length()-1)==';')) {
298                                    cpt = cpt.substring(1,cpt.length()-1);
299                                }
300                            }
301                            if (!ClassFileTools.getTypeString(pt,"").equals(cpt+" ")) {
302                                match = false;
303                                break;
304                            }
305                        }
306                        if (match) {
307                   _ai = new RuntimeVisibleAnnotationsAttributeInfo[1];
308                            _ai[0] = (RuntimeVisibleAnnotationsAttributeInfo)
309                                mi.getAttribute(RuntimeVisibleAnnotationsAttributeInfo.getAttributeName());
310                            return;
311                        }
312                    }
313                }
314            }
315            throw new ClassFormatError("Could not find member "+member+" in class files");
316        }
317    
318        /**
319         * Return the parameter annotations attribute.
320         * @param member member whose parameter annotations attribute should be found
321         * @param name member name
322         * @param types parameter types
323         * @return parameter annotations attribute
324         */
325        protected RuntimeVisibleParameterAnnotationsAttributeInfo getParameterAnnotationsAttributeInfo(Member member, String name, Class<?>[] types) {
326            if (member.getDeclaringClass().isPrimitive()) { return null; }
327            ClassFileTools.ClassLocation cl = ClassFileTools.findClassFile(member.getDeclaringClass().getName(), _classPath);
328            if (cl==null) {
329                setClassFileNotFound(true);
330                return null;
331            }
332            ClassFile cf = cl.getClassFile();
333            try {
334                cl.close();
335            }
336            catch(IOException e) { /* ignore */ }
337            for(MethodInfo mi: cf.getMethods()) {
338                if (mi.getName().toString().equals(name)) {
339                    boolean match = true;
340                    // descriptor is "(...)...", remove "(" and ")..."
341                    String desc = mi.getDescriptor().toString();
342                    List<String> paramTypeList = ClassFileTools.getSignatures(desc.substring(1, desc.lastIndexOf(')')));
343                    if (paramTypeList.size()== types.length) {
344                        for(int i=0; i<paramTypeList.size(); ++i) {
345                            String pt = paramTypeList.get(i);
346                            String cpt = types[i].getName();
347                            if ((pt.length()>0) && (pt.charAt(0)=='[') &&
348                                (cpt.length()>0) && (cpt.charAt(0)=='[')) {
349                                // if both type strings are arrays, cut off matching '[' prefixes
350                                do {
351                                    pt = pt.substring(1);
352                                    cpt = cpt.substring(1);
353                                } while ((pt.length()>0) && (pt.charAt(0)=='[') &&
354                                         (cpt.length()>0) && (cpt.charAt(0)=='['));
355                                // if this is an object array, the class name will be "Lxxx;"
356                                // cut off the 'L' and ';'
357                                if ((cpt.charAt(0)=='L') && (cpt.charAt(cpt.length()-1)==';')) {
358                                    cpt = cpt.substring(1,cpt.length()-1);
359                                }
360                            }
361                            if (!ClassFileTools.getTypeString(pt,"").equals(cpt+" ")) {
362                                match = false;
363                                break;
364                            }
365                        }
366                        if (match) {
367                            return (RuntimeVisibleParameterAnnotationsAttributeInfo)
368                                mi.getAttribute(RuntimeVisibleParameterAnnotationsAttributeInfo.getAttributeName());
369                        }
370                    }
371                }
372            }
373            throw new ClassFormatError("Could not find member "+member+" in class files");
374        }
375    
376        /**
377         * Return the local variable annotations attribute.
378         * @param member member whose parameter annotations attribute should be found
379         * @param name member name
380         * @param types parameter types
381         * @return parameter annotations attribute
382         */
383        protected RuntimeVisibleLocalVariableAnnotationsAttributeInfo getLocalVarAnnotationsAttributeInfo(Member member, String name, Class<?>[] types) {
384            if (member.getDeclaringClass().isPrimitive()) { return null; }
385            ClassFileTools.ClassLocation cl = ClassFileTools.findClassFile(member.getDeclaringClass().getName(), _classPath);
386            if (cl==null) {
387                setClassFileNotFound(true);
388                return null;
389            }
390            ClassFile cf = cl.getClassFile();
391            try {
392                cl.close();
393            }
394            catch(IOException e) { /* ignore */ }
395            for(MethodInfo mi: cf.getMethods()) {
396                if (mi.getName().toString().equals(name)) {
397                    boolean match = true;
398                    // descriptor is "(...)...", remove "(" and ")..."
399                    String desc = mi.getDescriptor().toString();
400                    List<String> paramTypeList = ClassFileTools.getSignatures(desc.substring(1, desc.lastIndexOf(')')));
401                    if (paramTypeList.size()== types.length) {
402                        for(int i=0; i<paramTypeList.size(); ++i) {
403                            String pt = paramTypeList.get(i);
404                            String cpt = types[i].getName();
405                            if ((pt.length()>0) && (pt.charAt(0)=='[') &&
406                                (cpt.length()>0) && (cpt.charAt(0)=='[')) {
407                                // if both type strings are arrays, cut off matching '[' prefixes
408                                do {
409                                    pt = pt.substring(1);
410                                    cpt = cpt.substring(1);
411                                } while ((pt.length()>0) && (pt.charAt(0)=='[') &&
412                                         (cpt.length()>0) && (cpt.charAt(0)=='['));
413                                // if this is an object array, the class name will be "Lxxx;"
414                                // cut off the 'L' and ';'
415                                if ((cpt.charAt(0)=='L') && (cpt.charAt(cpt.length()-1)==';')) {
416                                    cpt = cpt.substring(1,cpt.length()-1);
417                                }
418                            }
419                            if (!ClassFileTools.getTypeString(pt,"").equals(cpt+" ")) {
420                                match = false;
421                                break;
422                            }
423                        }
424                        if (match) {
425                            return (RuntimeVisibleLocalVariableAnnotationsAttributeInfo)
426                                mi.getAttribute(RuntimeVisibleLocalVariableAnnotationsAttributeInfo.getAttributeName());
427                        }
428                    }
429                }
430            }
431            throw new ClassFormatError("Could not find member "+member+" in class files");
432        }
433    }