All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.velocity.tools.generic.ClassTool Maven / Gradle / Ivy

Go to download

Generic tools that can be used in any context. PLEASE NOTE: this is a temporary fork to unblock projects migrating to Jakarta, but I won't continue maintaining it in the future as the Velocity team doesn't understand the value of Jakarta. I strongly suggest you plan a switch to a more modern template engine such as Thymeleaf.

The newest version!
package org.apache.velocity.tools.generic;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.    
 */

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.HashSet;
import java.util.Set;

import org.apache.velocity.tools.ClassUtils;
import org.apache.velocity.tools.Scope;
import org.apache.velocity.tools.config.DefaultKey;
import org.apache.velocity.tools.config.ValidScope;

/**
 * 

* This tool is meant to simplify reflective lookup of information about * a {@link Class} and its {@link Field}s, {@link Method}s, and {@link Constructor}s. * This is ideally aimed at those wishing to generate documentation, demo code, or * other content based on runtime reflection of a specified Class or Classes. It was not * designed with reflective execution of code in mind and thus provides no facilities * for code execution, nor direct access to the actual methods, constructors or fields * of the class being inspected. *

*
 * Example tools.xml config:
 * <tools>
 *   <toolbox scope="application">
 *     <tool class="org.apache.velocity.tools.generic.ClassTool"
 *              inspect="com.org.Foo"/>
 *   </toolbox>
 * </tools>
 * 
*

* If no Class to be inspected is specified, the default is java.lang.Object. *

* * @author Nathan Bubna * @since VelocityTools 2.0 * @version $Id: ClassTool.java 463298 2006-10-12 16:10:32Z henning $ */ @DefaultKey("class") @ValidScope(Scope.APPLICATION) public class ClassTool extends SafeConfig { public static final String INSPECT_KEY = "inspect"; public static final String SHOW_DEPRECATED_KEY = "showDeprecated"; protected Class type; protected List methods; protected List constructors; protected List fields; private boolean showDeprecated = false; /** * Creates an instance with target type of {@link Object}. */ public ClassTool() { setType(Object.class); } /** * Creates a new instance that inspects the specified type * and otherwise shares the configuration values of the specified "parent" * ClassTool instance. * @param tool parent class tool * @param type class to inspect */ protected ClassTool(ClassTool tool, Class type) { setType(type); if (tool == null) { throw new IllegalArgumentException("parent tool must not be null"); } // manually duplicate configuration of the parent tool this.showDeprecated = tool.showDeprecated; setSafeMode(tool.isSafeMode()); setLockConfig(tool.isConfigLocked()); } /** * Configure this tool * @param values configuration values */ protected void configure(ValueParser values) { this.showDeprecated = values.getBoolean(SHOW_DEPRECATED_KEY, showDeprecated); String classname = values.getString(INSPECT_KEY); if (classname != null) { setType(toClass(classname)); } } /** * Find a class given its name * @param name class name * @return found class or null */ private Class toClass(String name) { try { return ClassUtils.getClass(name); } catch (Exception e) { getLog().error("Could not load Class for {}", name); return null; } } /** * Set the class to inspect * @param type the class to inspect */ protected void setType(Class type) { if (type == null) { throw new IllegalArgumentException("target type is null or invalid"); } this.type = type; } /** * Check if an annotation deprecates its class * @param element annotation * @return deprecation status */ protected static boolean isDeprecated(AnnotatedElement element) { return (element.getAnnotation(Deprecated.class) != null); } /** * Returns the current showDeprecated setting. * @return flag value */ public boolean getShowDeprecated() { return this.showDeprecated; } /** * Returns the {@link Class} being inspected by this instance. * @return inspected class */ public Class getType() { return this.type; } /** * Returns a new ClassTool instance that is inspecting the * Class with the specified name. If the specified Class cannot * be found, then this will return {@code null}. All other * configuration settings will be copied to the new instance. * @param name class name * @return new class tool */ public ClassTool inspect(String name) { if (name == null) { return null; } return inspect(toClass(name)); } /** * Returns a new ClassTool instance that is inspecting the * Class of the specified {@link Object}. If the specified object * is null, then this will return {@code null}. All other * configuration settings will be copied to the new instance. * @param obj object instance to inspect * @return new class tool */ public ClassTool inspect(Object obj) { if (obj == null) { return null; } return inspect(obj.getClass()); } /** * Returns a new ClassTool instance that is inspecting the * superclass of the Class being inspected by this instance. * If the current inspectee has no super class, * then this will return {@code null}. All other * configuration settings will be copied to the new instance. * @return parent class tool */ public ClassTool getSuper() { Class sup = getType().getSuperclass(); if (sup == null) { return null; } return inspect(sup); } /** * Returns a new ClassTool instance that is inspecting the * the specified {@link Class}. If the specified class * is null, then this will return {@code null}. All other * configuration settings will be copied to the new instance. * If {@link #isSafeMode()} is {@code true} and the specified Class * is not declared {@code public}, then this will return * {@code null}. * @param type class to inspect * @return new class tool */ public ClassTool inspect(Class type) { if (type == null) { return null; } // create the new tool, but only return it if // it is public or isSafeMode() is off ClassTool tool = new ClassTool(this, type); if (isSafeMode() && !tool.isPublic()) { return null; } return tool; } /** * Returns the name of the package to which the inspected Class belongs. * @return package name */ public String getPackage() { return getType().getPackage().getName(); } /** * Returns the simple name (i.e. full name with package name removed) of * the inspected Class. * @return inspected class simple name */ public String getName() { return getType().getSimpleName(); } /** * Returns the fully-qualified name for the inspected Class. * @return inspected class name */ public String getFullName() { return getType().getName(); } /** * Returns true if a call to newInstance() on the Class being * inspected is successful; otherwise returns false. Unlike calling * newInstance() directly from a template, this will not throw an * Exception if it fails, as all Exceptions are caught. * @return new instances supported or not */ public boolean supportsNewInstance() { try { type.newInstance(); return true; } catch (Exception e) { return false; } } /** * Returns true if the inspected Class has been deprecated. * @return deprecation status */ public boolean isDeprecated() { return isDeprecated(getType()); } /** * Returns true if the inspected Class is declared public. * @return whether the inspected class is public */ public boolean isPublic() { return Modifier.isPublic(getType().getModifiers()); } /** * Returns true if the inspected Class is declared protected. * @return whether the inspected class is protected */ public boolean isProtected() { return Modifier.isProtected(getType().getModifiers()); } /** * Returns true if the inspected Class is declared private. * @return whether the inspected class is private */ public boolean isPrivate() { return Modifier.isPrivate(getType().getModifiers()); } /** * Returns true if the inspected Class is an inner class * that has been declared static or is a standard outer class.. * @return whether the inspected class is static */ public boolean isStatic() { return Modifier.isStatic(getType().getModifiers()); } /** * Returns true if the inspected Class is declared final. * @return whether the inspected class is final */ public boolean isFinal() { return Modifier.isFinal(getType().getModifiers()); } /** * Returns true if the inspected Class is an interface. * @return whether the inspected class is an interface */ public boolean isInterface() { return Modifier.isInterface(getType().getModifiers()); } /** * Returns true if the inspected Class is declared strictfp * (uses strict floating point math). * @return whether the inspected class is strictfp */ public boolean isStrict() { return Modifier.isStrict(getType().getModifiers()); } /** * Returns true if the inspected Class is declared abstract. * @return whether the inspected class is abstract */ public boolean isAbstract() { return Modifier.isAbstract(getType().getModifiers()); } /** * Returns a {@link List} of {@link MethodSub}s for each * method declared method in the inspected class. However, * in safe mode (which *is* the default), this will only return * the public methods. You must configure safe mode to be off * to receive a list of all methods. * @return methods inspectors list */ public List getMethods() { if (methods == null) { Method[] declared = getType().getDeclaredMethods(); List subs = new ArrayList(declared.length); for (Method method : declared) { MethodSub sub = new MethodSub(method); if ((!isSafeMode() || sub.isPublic()) && (showDeprecated || !sub.isDeprecated())) { subs.add(sub); } } Collections.sort(subs); methods = Collections.unmodifiableList(subs); } return methods; } /** * Returns a {@link List} of {@link ConstructorSub}s for each * constructor declared constructor in the inspected class. However, * in safe mode (which *is* the default), this will only return * the public constructors. You must configure safe mode to be off * to receive a list of all constructors. * @return constructors inspectors list */ public List getConstructors() { if (constructors == null) { Constructor[] declared = getType().getDeclaredConstructors(); List subs = new ArrayList(declared.length); for (Constructor constructor : declared) { ConstructorSub sub = new ConstructorSub(constructor); if ((!isSafeMode() || sub.isPublic()) && (showDeprecated || !sub.isDeprecated())) { subs.add(sub); } } Collections.sort(subs); constructors = Collections.unmodifiableList(subs); } return constructors; } /** * Returns a {@link List} of {@link FieldSub}s for each * field declared field in the inspected class. However, * in safe mode (which *is* the default), this will only return * the public fields. You must configure safe mode to be off * to receive a list of all fields. * @return fields inspectors list */ public List getFields() { if (fields == null) { Field[] declared = getType().getDeclaredFields(); List subs = new ArrayList(declared.length); for (Field field : declared) { FieldSub sub = new FieldSub(field); if ((!isSafeMode() || sub.isPublic()) && (showDeprecated || !sub.isDeprecated())) { subs.add(sub); } } Collections.sort(subs); fields = Collections.unmodifiableList(subs); } return fields; } /** * Returns a {@link Set} of all {@link Class}es that are * part of the signatures (i.e. parameters or return types) * of the inspected Class's methods, constructors and fields. * @return referenced classes set */ public Set getTypes() { Set types = new HashSet(); for (MethodSub method : getMethods()) { if (!isSafeMode() || method.isPublic()) { if (!method.isVoid()) { addType(types, method.getReturns()); } for (Class type : method.getParameters()) { addType(types, type); } } } for (ConstructorSub constructor : getConstructors()) { if (!isSafeMode() || constructor.isPublic()) { for (Class type : constructor.getParameters()) { addType(types, type); } } } for (FieldSub field : getFields()) { if (!isSafeMode() || field.isPublic()) { addType(types, field.getType()); } } return types; } private void addType(Set types, Class type) { if (type.isArray()) { type = type.getComponentType(); } if (!type.isPrimitive()) { types.add(type); } } /** * Returns the {@link Annotation}s of the Class being inspected. * @return annotation list */ public List getAnnotations() { return Arrays.asList(getType().getAnnotations()); } /** * @return string representation of inspected class */ public String toString() { return getType().toString(); } /** * A simplified wrapping interface for inspecting features * of a {@link Field} in an inspected Class. */ public static class FieldSub extends Sub { protected Field field; /** * FieldSub constructor * @param field inspected field */ public FieldSub(Field field) { this.field = field; } /** * @return inspected element */ protected AnnotatedElement getElement() { return field; } /** * @return field name */ public String getName() { return field.getName(); } /** * Simply returns the name of the field, since field names * cannot be overloaded. * @return unique name */ public String getUniqueName() { // field names can't be overloaded return field.getName(); } /** * Simply returns the name of the field. * @return field name */ public String getJavadocRef() { return field.getName(); } /** * @return field class */ public Class getType() { return field.getType(); } /** * Returns the value of the field if and only if * it is a static field that has no access restrictions * set by the security manager. * @return value of static field */ public Object getStaticValue() { if (isStatic()) { try { return field.get(null); } catch(IllegalAccessException iae) { //ignore } } return null; } /** * @return inspected field modifiers */ protected int getModifiers() { return field.getModifiers(); } /** * @return inspected field inspector type, aka "field" */ protected String getSubType() { return "field"; } } /** * A simplified wrapping interface for inspecting features * of a {@link Constructor} in an inspected Class. */ public static class ConstructorSub extends CallableSub { protected Constructor constructor; /** * Constructor inspector constructor * @param constructor constructor to inspect */ public ConstructorSub(Constructor constructor) { this.constructor = constructor; } /** * @return inspected element */ protected AnnotatedElement getElement() { return constructor; } /** * @return inspected constructor name */ public String getName() { return constructor.getDeclaringClass().getSimpleName(); } /** * @return inspected constructor parameters */ public Class[] getParameters() { return constructor.getParameterTypes(); } /** * Returns true if the final parameter for the constructor was declared * as a vararg. * @return whether the inspected constructor is vararg */ public boolean isVarArgs() { return constructor.isVarArgs(); } /** * @return inspected constructor modifiers */ protected int getModifiers() { return constructor.getModifiers(); } /** * @return inspector type, aka "constructor" */ protected String getSubType() { return "constructor"; } } /** * A simplified wrapping interface for inspecting features * of a {@link Method} in an inspected Class. */ public static class MethodSub extends CallableSub { protected Method method; /** * Method inspector constructor * @param method methodto inspect */ public MethodSub(Method method) { this.method = method; } /** * @return inspected element */ protected AnnotatedElement getElement() { return method; } /** * @return method name */ public String getName() { return method.getName(); } /** * If this method can be treated as a bean property in Velocity * (which does not exactly follow the javabean spec for such things) * then it will return the "bean property" equivalent of the method name. * (e.g. for getFoo(), isFoo() or setFoo(foo) it will return "foo") * @return related property name, or null */ public String getPropertyName() { String name = getName(); switch (getParameterCount()) { case 0: if (name.startsWith("get") && name.length() > 3) { return uncapitalize(name.substring(3, name.length())); } else if (name.startsWith("is") && name.length() > 2) { return uncapitalize(name.substring(2, name.length())); } break; case 1: if (name.startsWith("set") && name.length() > 3) { return uncapitalize(name.substring(3, name.length())); } default: } return null; } private String uncapitalize(String string) { if (string.length() > 1) { StringBuilder out = new StringBuilder(string.length()); out.append(string.substring(0,1).toLowerCase()); out.append(string.substring(1, string.length())); return out.toString(); } else { return string.toLowerCase(); } } /** * Returns true if the final parameter for the method was declared * as a vararg. * @return vararg status */ public boolean isVarArgs() { return method.isVarArgs(); } /** * Returns true if the return type of this method is void. * @return true if the inspected method returns null */ public boolean isVoid() { return (getReturns() == Void.TYPE); } /** * @return inspected method return type */ public Class getReturns() { return method.getReturnType(); } /** * @return inspected method parameters types */ public Class[] getParameters() { return method.getParameterTypes(); } /** * @return inspected method modifiers */ protected int getModifiers() { return method.getModifiers(); } /** * @return inspector type, aka "method" */ protected String getSubType() { return "method"; } } public abstract static class CallableSub extends Sub { protected String uniqueName; protected String javadocRef; protected String signature; public abstract Class[] getParameters(); public abstract boolean isVarArgs(); /** * @return whether the inspected callable takes parameters */ public boolean takesParameters() { return (getParameterCount() > 0); } /** * Returns the number of expected parameters. If this method or * constructor is declared with varargs, the vararg only counts as one. * @return inspected callable parameters count */ public int getParameterCount() { return getParameters().length; } /** * Build a unique method/ctor name by appending the simple names of * the expected parameter types, thereby distinguishing constructors * and overloaded methods with a useful name that would still be a * valid method name. This is particularly useful for generating * JUnit test method names. * @return inspected callable unique name */ public String getUniqueName() { if (uniqueName == null) { Class[] params = getParameters(); if (params.length == 0) { uniqueName = getName(); } else { StringBuilder out = new StringBuilder(30); out.append(getName()); out.append('_'); for (int i=0; i < params.length; i++) { Class param = params[i]; if (param.isArray()) { out.append(param.getComponentType().getSimpleName()); // check for vararg on last param if (i == params.length - 1 && isVarArgs()) { out.append("VarArgs"); } else { out.append("Array"); } } else { out.append(param.getSimpleName()); } } uniqueName = out.toString(); } } return uniqueName; } /** * Get (and cache) inspected callable signature * @return inspected callable signature */ public String getSignature() { if (signature == null) { signature = signature(false); } return signature; } /** * @return inspected callable javadoc ref */ public String getJavadocRef() { if (javadocRef == null) { javadocRef = signature(true); } return javadocRef; } /** * Internal method to get the inspected callable signature. * @param fullNames whether to use full names * @return inspected callable signature */ protected String signature(boolean fullNames) { Class[] params = getParameters(); if (params.length == 0) { return getName() + "()"; } else { StringBuilder out = new StringBuilder(30); out.append(getName()); out.append('('); boolean first = true; for (int i=0; i < params.length; i++) { Class param = params[i]; if (first) { first = false; } else { out.append(','); } if (param.isArray()) { if (fullNames) { out.append(param.getComponentType().getName()); } else { out.append(param.getComponentType().getSimpleName()); } if (i == params.length - 1 && isVarArgs()) { out.append("..."); } else { out.append("[]"); } } else { if (fullNames) { out.append(param.getName()); } else { out.append(param.getSimpleName()); } } } out.append(')'); return out.toString(); } } } public abstract static class Sub implements Comparable { protected abstract AnnotatedElement getElement(); protected abstract int getModifiers(); protected abstract String getSubType(); public abstract String getName(); public abstract String getUniqueName(); public abstract String getJavadocRef(); /** * Returns the {@link Annotation}s of the element being inspected. * @return annotations list */ public List getAnnotations() { return Arrays.asList(getElement().getAnnotations()); } /** * Inspected object deprecation status * @return deprecation status */ public boolean isDeprecated() { return ClassTool.isDeprecated(getElement()); } /** * @return whether the inspected object is public */ public boolean isPublic() { return Modifier.isPublic(getModifiers()); } /** * @return whether the inspected object is protected */ public boolean isProtected() { return Modifier.isProtected(getModifiers()); } /** * @return whether the inspected object is private */ public boolean isPrivate() { return Modifier.isPrivate(getModifiers()); } /** * @return whether the inspected object is static */ public boolean isStatic() { return Modifier.isStatic(getModifiers()); } /** * @return whether the inspected object is final */ public boolean isFinal() { return Modifier.isFinal(getModifiers()); } /** * @return whether the inspected object is an interface */ public boolean isInterface() { return Modifier.isInterface(getModifiers()); } /** * @return whether the inspected object is native */ public boolean isNative() { return Modifier.isNative(getModifiers()); } /** * @return whether the inspected object is strictfp */ public boolean isStrict() { return Modifier.isStrict(getModifiers()); } /** * @return whether the inspected object is synchronized */ public boolean isSynchronized() { return Modifier.isSynchronized(getModifiers()); } /** * @return whether the inspected object is transcient */ public boolean isTransient() { return Modifier.isTransient(getModifiers()); } /** * @return whether the inspected object is volatile */ public boolean isVolatile() { return Modifier.isVolatile(getModifiers()); } /** * @return whether the inspected object is abstract */ public boolean isAbstract() { return Modifier.isAbstract(getModifiers()); } /** * Compare unique names of inspected and given objects * @param that object to compare to * @return comparison result */ public int compareTo(T that) { return this.getUniqueName().compareTo(that.getUniqueName()); } /** * @return hash code */ public int hashCode() { return this.getUniqueName().hashCode(); } /** * @param obj object to compare to * @return whether it's the same object */ public boolean equals(Object obj) { if (obj instanceof Sub) { Sub that = (Sub)obj; return this.getUniqueName().equals(that.getUniqueName()); } return false; } /** * @return string representation */ public String toString() { return getSubType() + ' ' + getJavadocRef(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy