View Javadoc

1   package org.apache.velocity.tools.generic;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.    
20   */
21  
22  import java.lang.annotation.Annotation;
23  import java.lang.reflect.AnnotatedElement;
24  import java.lang.reflect.Constructor;
25  import java.lang.reflect.Field;
26  import java.lang.reflect.Method;
27  import java.lang.reflect.Modifier;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.List;
32  import java.util.HashSet;
33  import java.util.Set;
34  import org.apache.velocity.runtime.log.Log;
35  import org.apache.velocity.tools.ClassUtils;
36  import org.apache.velocity.tools.config.DefaultKey;
37  
38  /**
39   * <p>
40   * This tool is meant to simplify reflective lookup of information about
41   * a {@link Class} and its {@link Field}s, {@link Method}s, and {@link Constructor}s.
42   * This is ideally aimed at those wishing to generate documentation, demo code, or
43   * other content based on runtime reflection of a specified Class or Classes. It was not
44   * designed with reflective execution of code in mind and thus provides no facilities
45   * for code execution, nor direct access to the actual methods, constructors or fields
46   * of the class being inspected.
47   * </p>
48   *
49   * <p>
50   * <pre>
51   * Example tools.xml config:
52   * &lt;tools&gt;
53   *   &lt;toolbox scope="application"&gt;
54   *     &lt;tool class="org.apache.velocity.tools.generic.ClassTool"
55   *              inspect="com.org.Foo"/&gt;
56   *   &lt;/toolbox&gt;
57   * &lt;/tools&gt;
58   * </pre></p>
59   * <p>
60   * If no Class to be inspected is specified, the default is java.lang.Object.
61   * </p>
62   *
63   * @author Nathan Bubna
64   * @since VelocityTools 2.0
65   * @version $Id: ClassTool.java 463298 2006-10-12 16:10:32Z henning $
66   */
67  @DefaultKey("class")
68  public class ClassTool extends SafeConfig
69  {
70      public static final String INSPECT_KEY = "inspect";
71      public static final String SHOW_DEPRECATED_KEY = "showDeprecated";
72  
73      protected Log log;
74      protected Class type;
75      protected List<MethodSub> methods;
76      protected List<ConstructorSub> constructors;
77      protected List<FieldSub> fields;
78  
79      private boolean showDeprecated = false;
80  
81      /**
82       * Creates an instance with target type of {@link Object}.
83       */
84      public ClassTool()
85      {
86          setType(Object.class);
87      }
88  
89      /**
90       * Creates a new instance that inspects the specified type
91       * and otherwise shares the configuration values of the specified "parent"
92       * ClassTool instance.
93       */
94      protected ClassTool(ClassTool tool, Class type)
95      {
96          setType(type);
97          if (tool == null)
98          {
99              throw new IllegalArgumentException("parent tool must not be null");
100         }
101 
102         // manually duplicate configuration of the parent tool
103         this.log = tool.log;
104         this.showDeprecated = tool.showDeprecated;
105         setSafeMode(tool.isSafeMode());
106         setLockConfig(tool.isConfigLocked());
107     }
108 
109     protected void configure(ValueParser values)
110     {
111         this.log = (Log)values.getValue("log");
112         this.showDeprecated =
113             values.getBoolean(SHOW_DEPRECATED_KEY, showDeprecated);
114 
115         String classname = values.getString(INSPECT_KEY);
116         if (classname != null)
117         {
118             setType(toClass(classname));
119         }
120     }
121 
122     private Class toClass(String name)
123     {
124         try
125         {
126             return ClassUtils.getClass(name);
127         }
128         catch (Exception e)
129         {
130             if (this.log != null)
131             {
132                 this.log.error("Could not load Class for "+name);
133             }
134             return null;
135         }
136     }
137 
138     protected void setType(Class type)
139     {
140         if (type == null)
141         {
142             throw new IllegalArgumentException("target type is null or invalid");
143         }
144         this.type = type;
145     }
146 
147     protected static boolean isDeprecated(AnnotatedElement element)
148     {
149         return (element.getAnnotation(Deprecated.class) != null);
150     }
151 
152     /**
153      * Returns the current showDeprecated setting.
154      */
155     public boolean getShowDeprecated()
156     {
157         return this.showDeprecated;
158     }
159 
160     /**
161      * Returns the {@link Class} being inspected by this instance.
162      */
163     public Class getType()
164     {
165         return this.type;
166     }
167 
168     /**
169      * Returns a new ClassTool instance that is inspecting the
170      * Class with the specified name.  If the specified Class cannot
171      * be found, then this will return {@code null}. All other
172      * configuration settings will be copied to the new instance.
173      */
174     public ClassTool inspect(String name)
175     {
176         if (name == null)
177         {
178             return null;
179         }
180         return inspect(toClass(name));
181     }
182 
183     /**
184      * Returns a new ClassTool instance that is inspecting the
185      * Class of the specified {@link Object}.  If the specified object
186      * is null, then this will return {@code null}. All other
187      * configuration settings will be copied to the new instance.
188      */
189     public ClassTool inspect(Object obj)
190     {
191         if (obj == null)
192         {
193             return null;
194         }
195         return inspect(obj.getClass());
196     }
197 
198     /**
199      * Returns a new ClassTool instance that is inspecting the
200      * superclass of the Class being inspected by this instance.
201      * If the current inspectee has no super class,
202      * then this will return {@code null}. All other
203      * configuration settings will be copied to the new instance.
204      */
205     public ClassTool getSuper()
206     {
207         Class sup = getType().getSuperclass();
208         if (sup == null)
209         {
210             return null;
211         }
212         return inspect(sup);
213     }
214 
215     /**
216      * Returns a new ClassTool instance that is inspecting the
217      * the specified {@link Class}.  If the specified class
218      * is null, then this will return {@code null}. All other
219      * configuration settings will be copied to the new instance.
220      * If {@link #isSafeMode()} is {@code true} and the specified Class
221      * is not declared {@code public}, then this will return
222      * {@code null}.
223      */
224     public ClassTool inspect(Class type)
225     {
226         if (type == null)
227         {
228             return null;
229         }
230         // create the new tool, but only return it if
231         // it is public or isSafeMode() is off
232         ClassTool tool = new ClassTool(this, type);
233         if (isSafeMode() && !tool.isPublic())
234         {
235             return null;
236         }
237         return tool;
238     }
239 
240     /**
241      * Returns the name of the package to which the inspected Class belongs.
242      */
243     public String getPackage()
244     {
245         return getType().getPackage().getName();
246     }
247 
248     /**
249      * Returns the simple name (i.e. full name with package name removed) of
250      * the inspected Class.
251      */
252     public String getName()
253     {
254         return getType().getSimpleName();
255     }
256 
257     /**
258      * Returns the fully-qualified name for the inspected Class.
259      */
260     public String getFullName()
261     {
262         return getType().getName();
263     }
264 
265     /**
266      * Returns true if a call to newInstance() on the Class being
267      * inspected is successful; otherwise returns false.  Unlike calling
268      * newInstance() directly from a template, this will not throw an
269      * Exception if it fails, as all Exceptions are caught.
270      */
271     public boolean supportsNewInstance()
272     {
273         try
274         {
275             type.newInstance();
276             return true;
277         }
278         catch (Exception e)
279         {
280             return false;
281         }
282     }
283 
284     /**
285      * Returns true if the inspected Class has been deprecated.
286      */
287     public boolean isDeprecated()
288     {
289         return isDeprecated(getType());
290     }
291 
292     /**
293      * Returns true if the inspected Class is declared public.
294      */
295     public boolean isPublic()
296     {
297         return Modifier.isPublic(getType().getModifiers());
298     }
299 
300     /**
301      * Returns true if the inspected Class is declared protected.
302      */
303     public boolean isProtected()
304     {
305         return Modifier.isProtected(getType().getModifiers());
306     }
307 
308     /**
309      * Returns true if the inspected Class is declared private.
310      */
311     public boolean isPrivate()
312     {
313         return Modifier.isPrivate(getType().getModifiers());
314     }
315 
316     /**
317      * Returns true if the inspected Class is an inner class
318      * that has been declared static or is a standard outer class..
319      */
320     public boolean isStatic()
321     {
322         return Modifier.isStatic(getType().getModifiers());
323     }
324 
325     /**
326      * Returns true if the inspected Class is declared final.
327      */
328     public boolean isFinal()
329     {
330         return Modifier.isFinal(getType().getModifiers());
331     }
332 
333     /**
334      * Returns true if the inspected Class is an interface.
335      */
336     public boolean isInterface()
337     {
338         return Modifier.isInterface(getType().getModifiers());
339     }
340 
341     /**
342      * Returns true if the inspected Class is declared strictfp
343      * (uses strict floating point math).
344      */
345     public boolean isStrict()
346     {
347         return Modifier.isStrict(getType().getModifiers());
348     }
349 
350     /**
351      * Returns true if the inspected Class is declared abstract.
352      */
353     public boolean isAbstract()
354     {
355         return Modifier.isAbstract(getType().getModifiers());
356     }
357 
358     /**
359      * Returns a {@link List} of {@link MethodSub}s for each
360      * method declared method in the inspected class. However,
361      * in safe mode (which *is* the default), this will only return
362      * the public methods.  You must configure safe mode to be off
363      * to receive a list of all methods.
364      */
365     public List<MethodSub> getMethods()
366     {
367         if (methods == null)
368         {
369             Method[] declared = getType().getDeclaredMethods();
370             List<MethodSub> subs = new ArrayList<MethodSub>(declared.length);
371             for (Method method : declared)
372             {
373                 MethodSub sub = new MethodSub(method);
374                 if ((!isSafeMode() || sub.isPublic()) &&
375                     (showDeprecated || !sub.isDeprecated()))
376                 {
377                     subs.add(sub);
378                 }
379             }
380             Collections.sort(subs);
381             methods = Collections.unmodifiableList(subs);
382         }
383         return methods;
384     }
385 
386     /**
387      * Returns a {@link List} of {@link ConstructorSub}s for each
388      * constructor declared constructor in the inspected class. However,
389      * in safe mode (which *is* the default), this will only return
390      * the public constructors.  You must configure safe mode to be off
391      * to receive a list of all constructors.
392      */
393     public List<ConstructorSub> getConstructors()
394     {
395         if (constructors == null)
396         {
397             Constructor[] declared = getType().getDeclaredConstructors();
398             List<ConstructorSub> subs = new ArrayList<ConstructorSub>(declared.length);
399             for (Constructor constructor : declared)
400             {
401                 ConstructorSub sub = new ConstructorSub(constructor);
402                 if ((!isSafeMode() || sub.isPublic()) &&
403                     (showDeprecated || !sub.isDeprecated()))
404                 {
405                     subs.add(sub);
406                 }
407             }
408             Collections.sort(subs);
409             constructors = Collections.unmodifiableList(subs);
410         }
411         return constructors;
412     }
413 
414     /**
415      * Returns a {@link List} of {@link FieldSub}s for each
416      * field declared field in the inspected class. However,
417      * in safe mode (which *is* the default), this will only return
418      * the public fields.  You must configure safe mode to be off
419      * to receive a list of all fields.
420      */
421     public List<FieldSub> getFields()
422     {
423         if (fields == null)
424         {
425             Field[] declared = getType().getDeclaredFields();
426             List<FieldSub> subs = new ArrayList<FieldSub>(declared.length);
427             for (Field field : declared)
428             {
429                 FieldSub sub = new FieldSub(field);
430                 if ((!isSafeMode() || sub.isPublic()) &&
431                     (showDeprecated || !sub.isDeprecated()))
432                 {
433                     subs.add(sub);
434                 }
435             }
436             Collections.sort(subs);
437             fields = Collections.unmodifiableList(subs);
438         }
439         return fields;
440     }
441 
442     /**
443      * Returns a {@link Set} of all {@link Class}es that are
444      * part of the signatures (i.e. parameters or return types)
445      * of the inspected Class's methods, constructors and fields.
446      */
447     public Set<Class> getTypes()
448     {
449         Set<Class> types = new HashSet<Class>();
450         for (MethodSub method : getMethods())
451         {
452             if (!isSafeMode() || method.isPublic())
453             {
454                 if (!method.isVoid())
455                 {
456                     addType(types, method.getReturns());
457                 }
458                 for (Class type : method.getParameters())
459                 {
460                     addType(types, type);
461                 }
462             }
463         }
464         for (ConstructorSub constructor : getConstructors())
465         {
466             if (!isSafeMode() || constructor.isPublic())
467             {
468                 for (Class type : constructor.getParameters())
469                 {
470                     addType(types, type);
471                 }
472             }
473         }
474         for (FieldSub field : getFields())
475         {
476             if (!isSafeMode() || field.isPublic())
477             {
478                 addType(types, field.getType());
479             }
480         }
481         return types;
482     }
483 
484     private void addType(Set<Class> types, Class type)
485     {
486         if (type.isArray())
487         {
488             type = type.getComponentType();
489         }
490         if (!type.isPrimitive())
491         {
492             types.add(type);
493         }
494     }
495 
496     /**
497      * Returns the {@link Annotation}s of the Class being inspected.
498      */
499     public List<Annotation> getAnnotations()
500     {
501         return Arrays.asList(getType().getAnnotations());
502     }
503 
504     public String toString()
505     {
506         return getType().toString();
507     }
508 
509 
510 
511     /**
512      * A simplified wrapping interface for inspecting features
513      * of a {@link Field} in an inspected Class.
514      */
515     public static class FieldSub extends Sub<FieldSub>
516     {
517         protected Field field;
518 
519         public FieldSub(Field field)
520         {
521             this.field = field;
522         }
523 
524         protected AnnotatedElement getElement()
525         {
526             return field;
527         }
528 
529         public String getName()
530         {
531             return field.getName();
532         }
533 
534         /**
535          * Simply returns the name of the field, since field names
536          * cannot be overloaded.
537          */
538         public String getUniqueName()
539         {
540             // field names can't be overloaded
541             return field.getName();
542         }
543 
544         /**
545          * Simply returns the name of the field.
546          */
547         public String getJavadocRef()
548         {
549             return field.getName();
550         }
551 
552         public Class getType()
553         {
554             return field.getType();
555         }
556 
557         /**
558          * Returns the value of the field if and only if
559          * it is a static field that has no access restrictions
560          * set by the security manager.
561          */
562         public Object getStaticValue()
563         {
564             if (isStatic())
565             {
566                 try
567                 {
568                     return field.get(null);
569                 }
570                 catch(IllegalAccessException iae)
571                 {
572                     //ignore
573                 }
574             }
575             return null;
576         }
577 
578         protected int getModifiers()
579         {
580             return field.getModifiers();
581         }
582 
583         protected String getSubType()
584         {
585             return "field";
586         }
587     }
588 
589     /**
590      * A simplified wrapping interface for inspecting features
591      * of a {@link Constructor} in an inspected Class.
592      */
593     public static class ConstructorSub extends CallableSub<ConstructorSub>
594     {
595         protected Constructor constructor;
596 
597         public ConstructorSub(Constructor constructor)
598         {
599             this.constructor = constructor;
600         }
601 
602         protected AnnotatedElement getElement()
603         {
604             return constructor;
605         }
606 
607         public String getName()
608         {
609             return constructor.getDeclaringClass().getSimpleName();
610         }
611 
612         public Class[] getParameters()
613         {
614             return constructor.getParameterTypes();
615         }
616 
617         /**
618          * Returns true if the final parameter for the constructor was declared
619          * as a vararg.
620          */
621         public boolean isVarArgs()
622         {
623             return constructor.isVarArgs();
624         }
625 
626         protected int getModifiers()
627         {
628             return constructor.getModifiers();
629         }
630 
631         protected String getSubType()
632         {
633             return "constructor";
634         }
635     }
636 
637     /**
638      * A simplified wrapping interface for inspecting features
639      * of a {@link Method} in an inspected Class.
640      */
641     public static class MethodSub extends CallableSub<MethodSub>
642     {
643         protected Method method;
644 
645         public MethodSub(Method method)
646         {
647             this.method = method;
648         }
649 
650         protected AnnotatedElement getElement()
651         {
652             return method;
653         }
654 
655         public String getName()
656         {
657             return method.getName();
658         }
659 
660         /**
661          * If this method can be treated as a bean property in Velocity
662          * (which does not exactly follow the javabean spec for such things)
663          * then it will return the "bean property" equivalent of the method name.
664          * (e.g. for getFoo(), isFoo() or setFoo(foo) it will return "foo")
665          */
666         public String getPropertyName()
667         {
668             String name = getName();
669             switch (getParameterCount())
670             {
671                 case 0:
672                     if (name.startsWith("get") && name.length() > 3)
673                     {
674                         return uncapitalize(name.substring(3, name.length()));
675                     }
676                     else if (name.startsWith("is") && name.length() > 2)
677                     {
678                         return uncapitalize(name.substring(2, name.length()));
679                     }
680                     break;
681                 case 1:
682                     if (name.startsWith("set") && name.length() > 3)
683                     {
684                         return uncapitalize(name.substring(3, name.length()));
685                     }
686                 default:
687             }
688             return null;
689         }
690 
691         private String uncapitalize(String string)
692         {
693             if (string.length() > 1)
694             {
695                 StringBuilder out = new StringBuilder(string.length());
696                 out.append(string.substring(0,1).toLowerCase());
697                 out.append(string.substring(1, string.length()));
698                 return out.toString();
699             }
700             else
701             {
702                 return string.toLowerCase();
703             }
704         }
705 
706         /**
707          * Returns true if the final parameter for the method was declared
708          * as a vararg.
709          */
710         public boolean isVarArgs()
711         {
712             return method.isVarArgs();
713         }
714 
715         /**
716          * Returns true if the return type of this method is void.
717          */
718         public boolean isVoid()
719         {
720             return (getReturns() == Void.TYPE);
721         }
722 
723         public Class getReturns()
724         {
725             return method.getReturnType();
726         }
727 
728         public Class[] getParameters()
729         {
730             return method.getParameterTypes();
731         }
732 
733         protected int getModifiers()
734         {
735             return method.getModifiers();
736         }
737 
738         protected String getSubType()
739         {
740             return "method";
741         }
742     }
743 
744     public abstract static class CallableSub<T extends CallableSub> extends Sub<T>
745     {
746         protected String uniqueName;
747         protected String javadocRef;
748         protected String signature;
749 
750         public abstract Class[] getParameters();
751         public abstract boolean isVarArgs();
752 
753         public boolean takesParameters()
754         {
755             return (getParameterCount() > 0);
756         }
757 
758         /**
759          * Returns the number of expected parameters. If this method or
760          * constructor is declared with varargs, the vararg only counts as one.
761          */
762         public int getParameterCount()
763         {
764             return getParameters().length;
765         }
766 
767         /**
768          * Build a unique method/ctor name by appending the simple names of
769          * the expected parameter types, thereby distinguishing constructors
770          * and overloaded methods with a useful name that would still be a
771          * valid method name.  This is particularly useful for generating
772          * JUnit test method names.
773          */
774         public String getUniqueName()
775         {
776             if (uniqueName == null)
777             {
778                 Class[] params = getParameters();
779                 if (params.length == 0)
780                 {
781                     uniqueName = getName();
782                 }
783                 else
784                 {
785                     StringBuilder out = new StringBuilder(30);
786                     out.append(getName());
787                     out.append('_');
788                     for (int i=0; i < params.length; i++)
789                     {
790                         Class param = params[i];
791                         if (param.isArray())
792                         {
793                             out.append(param.getComponentType().getSimpleName());
794                             // check for vararg on last param
795                             if (i == params.length - 1 && isVarArgs())
796                             {
797                                 out.append("VarArgs");
798                             }
799                             else
800                             {
801                                 out.append("Array");
802                             }
803                         }
804                         else
805                         {
806                             out.append(param.getSimpleName());
807                         }
808                     }
809                     uniqueName = out.toString();
810                 }
811             }
812             return uniqueName;
813         }
814 
815         public String getSignature()
816         {
817             if (signature == null)
818             {
819                 signature = signature(false);
820             }
821             return signature;
822         }
823 
824         public String getJavadocRef()
825         {
826             if (javadocRef == null)
827             {
828                 javadocRef = signature(true);
829             }
830             return javadocRef;
831         }
832 
833         protected String signature(boolean fullNames)
834         {
835             Class[] params = getParameters();
836             if (params.length == 0)
837             {
838                 return getName() + "()";
839             }
840             else
841             {
842                 StringBuilder out = new StringBuilder(30);
843                 out.append(getName());
844                 out.append('(');
845                 boolean first = true;
846                 for (int i=0; i < params.length; i++)
847                 {
848                     Class param = params[i];
849                     if (first)
850                     {
851                         first = false;
852                     }
853                     else
854                     {
855                         out.append(',');
856                     }
857                     if (param.isArray())
858                     {
859                         if (fullNames)
860                         {
861                             out.append(param.getComponentType().getName());
862                         }
863                         else
864                         {
865                             out.append(param.getComponentType().getSimpleName());
866                         }
867                         if (i == params.length - 1 && isVarArgs())
868                         {
869                             out.append("...");
870                         }
871                         else
872                         {
873                             out.append("[]");
874                         }
875                     }
876                     else
877                     {
878                         if (fullNames)
879                         {
880                             out.append(param.getName());
881                         }
882                         else
883                         {
884                             out.append(param.getSimpleName());
885                         }
886                     }
887                 }
888                 out.append(')');
889                 return out.toString();
890             }
891         }
892     }
893 
894     public abstract static class Sub<T extends Sub> implements Comparable<T>
895     {
896         protected abstract AnnotatedElement getElement();
897 
898         protected abstract int getModifiers();
899 
900         protected abstract String getSubType();
901 
902         public abstract String getName();
903 
904         public abstract String getUniqueName();
905 
906         public abstract String getJavadocRef();
907 
908         /**
909          * Returns the {@link Annotation}s of the element being inspected.
910          */
911         public List<Annotation> getAnnotations()
912         {
913             return Arrays.asList(getElement().getAnnotations());
914         }
915 
916         public boolean isDeprecated()
917         {
918             return ClassTool.isDeprecated(getElement());
919         }
920 
921         public boolean isPublic()
922         {
923             return Modifier.isPublic(getModifiers());
924         }
925 
926         public boolean isProtected()
927         {
928             return Modifier.isProtected(getModifiers());
929         }
930 
931         public boolean isPrivate()
932         {
933             return Modifier.isPrivate(getModifiers());
934         }
935 
936         public boolean isStatic()
937         {
938             return Modifier.isStatic(getModifiers());
939         }
940 
941         public boolean isFinal()
942         {
943             return Modifier.isFinal(getModifiers());
944         }
945 
946         public boolean isInterface()
947         {
948             return Modifier.isInterface(getModifiers());
949         }
950 
951         public boolean isNative()
952         {
953             return Modifier.isNative(getModifiers());
954         }
955 
956         public boolean isStrict()
957         {
958             return Modifier.isStrict(getModifiers());
959         }
960 
961         public boolean isSynchronized()
962         {
963             return Modifier.isSynchronized(getModifiers());
964         }
965 
966         public boolean isTransient()
967         {
968             return Modifier.isTransient(getModifiers());
969         }
970 
971         public boolean isVolatile()
972         {
973             return Modifier.isVolatile(getModifiers());
974         }
975 
976         public boolean isAbstract()
977         {
978             return Modifier.isAbstract(getModifiers());
979         }
980 
981         public int compareTo(T that)
982         {
983             return this.getUniqueName().compareTo(that.getUniqueName());
984         }
985 
986         public int hashCode()
987         {
988             return this.getUniqueName().hashCode();
989         }
990 
991         public boolean equals(Object obj)
992         {
993             if (obj instanceof Sub)
994             {
995                 Sub that = (Sub)obj;
996                 return this.getUniqueName().equals(that.getUniqueName());
997             }
998             return false;
999         }
1000 
1001         public String toString()
1002         {
1003             return getSubType() + ' ' + getJavadocRef();
1004         }
1005     }
1006 
1007 }