View Javadoc
1   /*
2    *  Copyright 2012 Chris Pheby
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.jadira.scanner.classpath.types;
17  
18  import java.io.File;
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Set;
24  
25  import javassist.bytecode.AnnotationsAttribute;
26  import javassist.bytecode.AttributeInfo;
27  import javassist.bytecode.ClassFile;
28  import javassist.bytecode.FieldInfo;
29  import javassist.bytecode.InnerClassesAttribute;
30  import javassist.bytecode.MethodInfo;
31  import javassist.bytecode.annotation.Annotation;
32  
33  import org.apache.commons.lang3.builder.EqualsBuilder;
34  import org.apache.commons.lang3.builder.HashCodeBuilder;
35  import org.jadira.scanner.classpath.ClasspathResolver;
36  import org.jadira.scanner.classpath.filter.JElementTypeFilter;
37  import org.jadira.scanner.classpath.filter.JTypeSubTypeOfFilter;
38  import org.jadira.scanner.classpath.projector.ClasspathProjector;
39  import org.jadira.scanner.classpath.visitor.IntrospectionVisitor;
40  import org.jadira.scanner.core.api.Projector;
41  import org.jadira.scanner.core.exception.ClasspathAccessException;
42  
43  public class JClass extends JType {
44  
45      private static final Projector<File> CLASSPATH_PROJECTOR = ClasspathProjector.SINGLETON;
46      
47      protected JClass(String name, ClasspathResolver resolver) throws ClasspathAccessException {
48      	this(findClassFile(name, resolver), resolver);
49      }
50  
51      protected JClass(Class<?> clazz, ClasspathResolver resolver) throws ClasspathAccessException {
52          this(findClassFile(clazz.getName(), resolver), resolver);
53      }
54  
55      protected JClass(ClassFile classFile, ClasspathResolver resolver) {
56          super(classFile, resolver);
57      }
58  
59      public static JClass getJClass(String name, ClasspathResolver resolver) throws ClasspathAccessException {
60          return new JClass(name, resolver);
61      }
62  
63      public static JClass getJClass(ClassFile classFile, ClasspathResolver resolver) {
64          return new JClass(classFile, resolver);
65      }
66  
67      public static JClass getJClass(Class<?> clazz, ClasspathResolver resolver) throws ClasspathAccessException {
68          return new JClass(clazz, resolver);
69      }
70  
71      public JClass getSuperType() throws ClasspathAccessException {
72  
73          final String superClassFile = getClassFile().getSuperclass();
74          return JClass.getJClass(superClassFile, getResolver());
75      }
76      
77      public List<JInterface> getImplementedInterfaces() throws ClasspathAccessException {
78  
79          final List<JInterface> retVal = new ArrayList<JInterface>();
80          final String[] interfaces = getClassFile().getInterfaces();
81  
82          for (String next : interfaces) {
83              retVal.add(JInterface.getJInterface(next, getResolver()));
84          }
85          return retVal;
86      }
87  
88      @Override
89      public Class<?> getActualClass() throws ClasspathAccessException {
90  
91          return getResolver().loadClass(getClassFile().getName());
92      }
93  
94      public Set<JClass> getSubClasses() {
95          
96          Set<JClass> retVal = new HashSet<JClass>();
97          List<? extends ClassFile> classes = getResolver().getClassFileResolver().resolveAll(null, CLASSPATH_PROJECTOR, new JTypeSubTypeOfFilter(this.getActualClass()), new JElementTypeFilter(JClass.class)); 
98          for (ClassFile classFile : classes) {
99              retVal.add(JClass.getJClass(classFile, getResolver()));
100         }
101         return retVal;
102     }
103 
104     public List<JInnerClass> getEnclosedClasses() throws ClasspathAccessException {
105 
106         final List<JInnerClass> retVal = new ArrayList<JInnerClass>();
107         @SuppressWarnings("unchecked")
108         List<AttributeInfo> attrs = (List<AttributeInfo>) getClassFile().getAttributes();
109         for (AttributeInfo next : attrs) {
110             if (next instanceof InnerClassesAttribute) {
111                 int innerClassCount = ((InnerClassesAttribute) next).tableLength();
112                 for (int i = 0; i < innerClassCount; i++) {
113                     String innerName = ((InnerClassesAttribute) next).innerClass(i);
114                     // Skip anonymous classes - these are returned via method introspection instead
115                     if (innerName != null && innerName.startsWith(this.getClassFile().getName())) {
116                     	
117                     	ClassFile innerClass = findClassFile(innerName, getResolver());
118 
119                     	// One or two inner classes can be introspected easily - filter these
120                     	if (innerClass != null) { 
121                     		retVal.add(JInnerClass.getJInnerClass(this.getClassFile(), innerClass, getResolver()));
122                     	}
123                     }
124                 }
125             }
126         }
127         return retVal;
128     }
129 
130     public List<JField> getFields() {
131 
132     	boolean isJavaLangThrowable = "java.lang.Throwable".equals(this.getName());
133     	boolean isJavaLangSystem = "java.lang.System".equals(this.getName());
134     	
135         final List<JField> retVal = new ArrayList<JField>();
136         @SuppressWarnings("unchecked")
137         final List<FieldInfo> fields = (List<FieldInfo>) getClassFile().getFields();
138 
139         for (FieldInfo next : fields) {
140         	if (isJavaLangThrowable && ("backtrace".equals(next.getName()))) {
141         		// See http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=d7621f5189c86f127fe5737490903?bug_id=4496456
142         		continue;
143         	}
144         	if (isJavaLangSystem && ("security".equals(next.getName()))) {
145                 continue;
146             }
147             retVal.add(JField.getJField(next, this, getResolver()));
148         }
149         return retVal;
150     }
151 
152     public List<JConstructor> getConstructors() throws ClasspathAccessException {
153 
154         final List<JConstructor> retVal = new ArrayList<JConstructor>();
155         @SuppressWarnings("unchecked")
156         final List<MethodInfo> methods = (List<MethodInfo>) getClassFile().getMethods();
157 
158         for (MethodInfo next : methods) {
159             if (next.isConstructor()) {
160                 retVal.add(JConstructor.getJConstructor(next, this, getResolver()));
161             }
162         }
163         
164         if (retVal.isEmpty()) {
165         	retVal.add(JDefaultConstructor.getJConstructor((MethodInfo)null, (JClass)this, getResolver()));
166         }
167         	
168         return retVal;
169     }
170 
171     @Override
172     public Set<JAnnotation<?>> getAnnotations() {
173 
174         AnnotationsAttribute visible = (AnnotationsAttribute) getClassFile().getAttribute(AnnotationsAttribute.visibleTag);
175         AnnotationsAttribute invisible = (AnnotationsAttribute) getClassFile().getAttribute(AnnotationsAttribute.invisibleTag);
176 
177         Set<JAnnotation<?>> annotations = new HashSet<JAnnotation<?>>();
178 
179         List<Annotation> annotationsList = new ArrayList<Annotation>();
180         if (visible != null) {
181             annotationsList.addAll(Arrays.asList(visible.getAnnotations()));
182         }
183         if (invisible != null) {
184             annotationsList.addAll(Arrays.asList(invisible.getAnnotations()));
185         }
186 
187         for (Annotation nextAnnotation : annotationsList) {
188             annotations.add(JAnnotation.getJAnnotation(nextAnnotation, this, getResolver()));
189         }
190 
191         return annotations;
192     }
193 
194     @Override
195     public JPackage getPackage() throws ClasspathAccessException {
196 
197         String fqClassName = getClassFile().getName();
198 
199         String packageName;
200         if (fqClassName.contains(".")) {
201             packageName = fqClassName.substring(0, fqClassName.lastIndexOf('.'));
202         } else {
203             packageName = "";
204         }
205 
206         return JPackage.getJPackage(packageName, getResolver());
207     }
208 
209     public List<JMethod> getMethods() {
210 
211         final List<JMethod> retVal = new ArrayList<JMethod>();
212         @SuppressWarnings("unchecked")
213         final List<MethodInfo> methods = (List<MethodInfo>) getClassFile().getMethods();
214 
215         for (MethodInfo next : methods) {
216             if (next.isMethod()) {
217                 retVal.add(JMethod.getJMethod(next, this, getResolver()));
218             }
219         }
220         return retVal;
221     }
222 
223     public List<JStaticInitializer> getStaticInitializers() {
224 
225         final List<JStaticInitializer> retVal = new ArrayList<JStaticInitializer>();
226         @SuppressWarnings("unchecked")
227         final List<MethodInfo> methods = (List<MethodInfo>) getClassFile().getMethods();
228 
229         for (MethodInfo next : methods) {
230             if (next.isStaticInitializer()) {
231                 retVal.add(JStaticInitializer.getJStaticInitializer(next, this, getResolver()));
232             }
233         }
234         return retVal;
235     }
236 
237     @Override
238     public void acceptVisitor(IntrospectionVisitor visitor) throws ClasspathAccessException {
239         visitor.visit(this);
240 
241         for (JInterface next : getImplementedInterfaces()) {
242             next.acceptVisitor(visitor);
243         }
244         for (JInnerClass next : getEnclosedClasses()) {
245             next.acceptVisitor(visitor);
246         }
247         for (JField next : getFields()) {
248         	
249         	// Since Java 8 access to this member is problematic, so we always guard for it
250         	if ("classLoader".equals(next.getName()) && "java.lang.Class".equals(next.getEnclosingType().getName())) {
251         		continue;
252         	}
253             next.acceptVisitor(visitor);
254         }
255         for (JConstructor next : getConstructors()) {
256             next.acceptVisitor(visitor);
257         }
258         for (JMethod next : getMethods()) {
259             next.acceptVisitor(visitor);
260         }
261         for (JStaticInitializer next : getStaticInitializers()) {
262             next.acceptVisitor(visitor);
263         }
264         for (JAnnotation<?> next : getAnnotations()) {
265             next.acceptVisitor(visitor);
266         }
267     }
268 
269     @Override
270     public JElement getEnclosingElement() {
271         return getPackage();
272     }
273 
274 	public boolean isPrimitive() {
275 		return false;
276 	}
277 	
278 	public boolean isArray() {
279 		return false;
280 	}
281 	
282 	public boolean isInterface() {
283 		return getClassFile().isInterface();
284 	}
285 	
286     @Override
287 	public boolean equals(Object obj) {
288 		if (obj == null) {
289 			return false;
290 		}
291 		if (obj == this) {
292 			return true;
293 		}
294 		if (obj.getClass() != getClass()) {
295 			return false;
296 		}
297 		// JClass rhs = (JClass) obj;
298 		return new EqualsBuilder()
299 			 	.appendSuper(super.equals(obj))
300 				.isEquals();
301 	}
302 
303     @Override
304 	public int hashCode() {
305 		return new HashCodeBuilder(11, 47).append(super.hashCode())
306 				.toHashCode();
307 	}
308 }