
org.teatrove.teatools.TeaToolsUtils Maven / Gradle / Ivy
/*
* Copyright 1997-2011 teatrove.org
*
* Licensed 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.
*/
package org.teatrove.teatools;
import org.teatrove.tea.runtime.*;
import org.teatrove.tea.util.*;
import org.teatrove.trove.classfile.Modifiers;
import org.teatrove.trove.generics.GenericType;
import org.teatrove.trove.util.*;
import java.beans.*;
import java.lang.reflect.*;
import java.util.*;
import java.text.BreakIterator;
/**
* A Tea Tool's best friend. This class has several useful methods for writing
* tools that work with Tea. Many of these methods were taken from Kettle
* 3.0.x so that they could be reused in future versions and in other
* applications.
*
* This class was written with the intent that it could be used as a tea
* context class. It provides a collection of functions to make introspection
* possible from Tea.
*
* @author Mark Masse, Sean Treat
*/
public class TeaToolsUtils extends DefaultContext
implements TeaToolsConstants {
/** Zero-length array of properties */
private final static PropertyDescriptor[] NO_PROPERTIES =
new PropertyDescriptor[0];
/** Finite set of primative Class objects. Mapping of primitive
name to Class (i.e. "int" -> int.class) */
private final static Hashtable cPrimativeClasses = new Hashtable();
/** The DescriptorComparator used to sort FeatureDescriptors */
private final static DescriptorComparator DESCRIPTOR_COMPARATOR =
new DescriptorComparator();
/**
* Test program
*/
public static void main(String[] args) throws Exception {
new Tester(args);
}
static {
Class[] primativeClasses =
new Class[] {
void.class,
boolean.class,
char.class,
byte.class,
short.class,
int.class,
float.class,
double.class,
long.class
};
for (int i = 0; i < primativeClasses.length; i++) {
cPrimativeClasses.put(primativeClasses[i].getName(),
primativeClasses[i]);
}
}
/**
* Creates a new TeaToolsUtils
*/
public TeaToolsUtils() {
}
// DefaultContext method; implemented to do nothing
public void print(Object obj) throws Exception {
}
/**
* Returns a TypeDescription object to wrap and describe the
* specified type.
*/
public TypeDescription createTypeDescription(Class type) {
return new TypeDescription(type, this);
}
/**
* Returns an array of PropertyDescriptions to wrap and describe the
* specified PropertyDescriptors.
*/
public PropertyDescription[] createPropertyDescriptions(
PropertyDescriptor[] pds) {
if (pds == null) {
return null;
}
PropertyDescription[] descriptions =
new PropertyDescription[pds.length];
for (int i = 0; i < pds.length; i++) {
descriptions[i] = new PropertyDescription(pds[i], this);
}
return descriptions;
}
/**
* Returns an array of MethodDescriptions to wrap and describe the
* specified MethodDescriptors.
*/
public MethodDescription[] createMethodDescriptions(
MethodDescriptor[] mds) {
if (mds == null) {
return null;
}
MethodDescription[] descriptions =
new MethodDescription[mds.length];
for (int i = 0; i < mds.length; i++) {
descriptions[i] = new MethodDescription(mds[i], this);
}
return descriptions;
}
/**
* Returns an array of ParameterDescriptions to wrap and describe the
* parameters of the specified MethodDescriptor.
*/
public ParameterDescription[] createParameterDescriptions(
MethodDescriptor md) {
if (md == null) {
return null;
}
Method method = md.getMethod();
Class[] paramClasses = method.getParameterTypes();
int descriptionCount = paramClasses.length;
if (acceptsSubstitution(md)) {
descriptionCount--;
}
ParameterDescriptor[] pds = md.getParameterDescriptors();
ParameterDescription[] descriptions =
new ParameterDescription[descriptionCount];
for (int i = 0; i < descriptions.length; i++) {
TypeDescription type = createTypeDescription(paramClasses[i]);
ParameterDescriptor pd = null;
if (pds != null && i < pds.length) {
pd = pds[i];
}
descriptions[i] = new ParameterDescription(type, pd, this);
}
return descriptions;
}
/**
* Creates a PackageDescriptor for the named package.
*/
public PackageDescriptor createPackageDescriptor(String packageName) {
return PackageDescriptor.forName(packageName);
}
/**
* Creates a PackageDescriptor for the named package using the
* specified ClassLoader to load the PackageInfo or Package.
*/
public PackageDescriptor createPackageDescriptor(String packageName,
ClassLoader classLoader) {
return PackageDescriptor.forName(packageName, classLoader);
}
/**
* Returns a Modifiers instance that can be used to check the modifier
* int returned by the Class.getModifiers or Member.getModifiers
* method.
*/
public Modifiers getModifiers(int modifier) {
return new Modifiers(modifier);
}
/**
* Returns a Class object for a given name. Primitive classes can be
* loaded via thier normal names (i.e. "float"). Array classes can be
* loaded using either the normal Java name (i.e. int[][]) or the VM
* name (i.e. [[I).
*
* Note this method swallows all exceptions and simply returns null if
* the class could not be loaded.
*
* @param className the name of the Class
*/
public Class getClassForName(String className) {
return getClassForName(className, null);
}
/**
* Returns a Class object for a given name. Primitive classes can be
* loaded via thier normal names (i.e. "float"). Array classes can be
* loaded using either the normal Java name (i.e. int[][]) or the VM
* name (i.e. [[I).
*
* Note this method swallows all exceptions and simply returns null if
* the class could not be loaded.
*
* @param className the name of the Class
* @param classLoader the ClassLoader to use
*/
public Class getClassForName(String className, ClassLoader classLoader) {
if (className == null) {
return null;
}
int bracketIndex = className.indexOf('[');
if (bracketIndex > 0) {
// Convert foo[][] to [[Lfoo;
className = convertArrayClassName(className, bracketIndex);
}
if (className == null) {
// Array class name could not be converted (should only happen
// for void[]
return null;
}
Class clazz = (Class) cPrimativeClasses.get(className);
if (clazz != null) {
// Return the primitive class (i.e. int.class for "int")
return clazz;
}
Throwable error = null;
try {
if (classLoader == null) {
classLoader = getClass().getClassLoader();
}
if (classLoader != null) {
clazz = classLoader.loadClass(className);
}
}
catch (Throwable t) {
error = t;
}
if (clazz == null) {
// Try Class.forName
try {
clazz = Class.forName(className);
}
catch (Throwable t) {
if (error == null) {
//t.printStackTrace();
}
else {
//error.printStackTrace();
}
}
}
return clazz;
}
/**
* Returns the full class name of the specified class. This method
* provides special formatting for array and inner classes.
*/
public String getFullClassName(Class clazz) {
return getFullClassName(getArrayClassName(clazz));
}
/**
* Returns the full class name of the specified class. This method
* provides special formatting for inner classes.
*/
public String getFullClassName(String fullClassName) {
String[] parts = parseClassName(fullClassName);
String className = getInnerClassName(parts[1]);
if (parts[0] != null) {
// Return the package name plus the converted class name
return parts[0] + '.' + className;
}
else {
return className;
}
}
/**
* Returns the class name of the specified class. The class name returned
* does not include the package. This method provides special formatting
* for array and inner classes.
*/
public String getClassName(Class clazz) {
return getClassName(getArrayClassName(clazz));
}
/**
* Returns the class name of the specified class. The class name returned
* does not include the package. This method provides special formatting
* for inner classes.
*/
public String getClassName(String fullClassName) {
return getInnerClassName(parseClassName(fullClassName)[1]);
}
/**
* Returns the package name of the specified class. Returns "" if the
* class has no package.
*/
public String getClassPackage(Class clazz) {
return getClassPackage(getArrayClassName(clazz));
}
/**
* Returns the package name of the specified class. Returns null if the
* class has no package.
*/
public String getClassPackage(String fullClassName) {
return parseClassName(fullClassName)[0];
}
/**
* Returns the type of the specified class.
*
*
* - A Class returns "class"
*
- An Interface returns "interface"
*
- An array returns null
*
- A primitive returns null
*
*/
public String getClassTypeName(Class clazz) {
String type = null;
if (clazz.isInterface()) {
type = "interface";
}
else if (clazz.isPrimitive()) {
type = null;
}
else if (clazz.isArray()) {
type = null;
}
else {
type = "class";
}
return type;
}
/**
* Create a version information string based on what the build process
* provided. The string is of the form "M.m.r" or
* "M.m.r.bbbb" (i.e. 1.1.0.0004) if the build number can be retrieved.
* Returns null
if the version string cannot be retrieved.
*/
public String getPackageVersion(String packageName) {
if (packageName == null || packageName.trim().length() == 0) {
return null;
}
if (!packageName.endsWith(".")) {
packageName = packageName + ".";
}
String className = packageName + "PackageInfo";
Class packageInfoClass = getClassForName(className);
if (packageInfoClass == null) {
return null;
}
String productVersion = null;
String buildNumber = null;
try {
Method getProductVersionMethod =
packageInfoClass.getMethod("getProductVersion",
EMPTY_CLASS_ARRAY);
productVersion =
(String) getProductVersionMethod.invoke(null,
EMPTY_OBJECT_ARRAY);
Method getBuildNumberMethod =
packageInfoClass.getMethod("getBuildNumber",
EMPTY_CLASS_ARRAY);
buildNumber =
(String) getBuildNumberMethod.invoke(null,
EMPTY_OBJECT_ARRAY);
}
catch (Throwable t) {
// Just eat it
}
if (productVersion != null && productVersion.length() > 0 &&
buildNumber != null && buildNumber.length() > 0) {
productVersion += '.' + buildNumber;
}
return productVersion;
}
/**
* Splits a class name into two strings.
*
* [0] = package name (or null if the class is unpackaged)
* [1] = class name
*/
public String[] parseClassName(String fullClassName) {
int dotIndex = fullClassName.lastIndexOf(".");
String packageName = null;
String className = fullClassName;
if (dotIndex > 0) {
packageName = fullClassName.substring(0, dotIndex);
className = fullClassName.substring(dotIndex + 1);
}
return new String[] { packageName, className };
}
/**
* Formats the class name with trailing square brackets.
*/
public String getArrayClassName(Class clazz) {
if (clazz.isArray()) {
return getArrayClassName(clazz.getComponentType()) + "[]";
}
return clazz.getName();
}
/**
* Returns the array type. Returns the specified class if it is not an
* array.
*/
public Class getArrayType(Class clazz) {
if (clazz.isArray()) {
return getArrayType(clazz.getComponentType());
}
return clazz;
}
/**
* Returns the array dimensions.
* Returns 0 if the specified class is not an array.
*/
public int getArrayDimensions(Class clazz) {
if (clazz.isArray()) {
return getArrayDimensions(clazz.getComponentType()) + 1;
}
return 0;
}
/**
* Returns the array dimensions String (i.e. "[][][]").
* Returns "" (empty string) if the specified class is not an array.
*/
public String getArrayDimensionsString(Class clazz) {
return createPatternString("[]", getArrayDimensions(clazz));
}
/**
* Converts the user-friendly array class name to the VM friendly one.
* For example:
*
* java.lang.String[]
becomes
* [Ljava.lang.String;
* int[][]
becomes [[I
* byte[]
becomes [B
*
*
* @param className the name of the array class
* @param bracketIndex the index (withing className) of the first '['
* character
*/
public String convertArrayClassName(String className,
int bracketIndex) {
String dimensions = "";
String brackets = className.substring(bracketIndex).trim();
char[] chars = brackets.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (chars[i] == '[') {
dimensions = dimensions + '[';
}
}
String arrayType = className.substring(0, bracketIndex).trim();
char prefixChar = 'L';
Class clazz = (Class) cPrimativeClasses.get(arrayType);
if (clazz != null) {
arrayType = "";
if (clazz == boolean.class) {
prefixChar = 'Z';
}
else if (clazz == char.class) {
prefixChar = 'C';
}
else if (clazz == byte.class) {
prefixChar = 'B';
}
else if (clazz == short.class) {
prefixChar = 'S';
}
else if (clazz == int.class) {
prefixChar = 'I';
}
else if (clazz == float.class) {
prefixChar = 'F';
}
else if (clazz == double.class) {
prefixChar = 'D';
}
else if (clazz == long.class) {
prefixChar = 'J';
}
else if (clazz == void.class) {
return null;
}
}
else {
arrayType = arrayType + ';';
}
return dimensions + prefixChar + arrayType;
}
/**
* Returns the className with '$'s changed to '.'s
*/
public String getInnerClassName(String className) {
return className.replace('$', '.');
}
/**
* Introspects a Java bean to learn about all its properties,
* exposed methods, and events.
*
* @param beanClass the bean class to be analyzed
*/
public BeanInfo getBeanInfo(Class beanClass)
throws IntrospectionException {
return Introspector.getBeanInfo(beanClass);
}
/**
* Introspects a Java bean to learn all about its properties, exposed
* methods, below a given "stop" point.
*
* @param beanClass the bean class to be analyzed
* @param stopClass the base class at which to stop the analysis.
* Any methods/properties/events in the stopClass or in its baseclasses
* will be ignored in the analysis
*/
public BeanInfo getBeanInfo(Class beanClass, Class stopClass)
throws IntrospectionException {
return Introspector.getBeanInfo(beanClass, stopClass);
}
/**
* Retrieves the value of the named FeatureDescriptor attribute.
*
* @return The value of the attribute. May be null if the attribute
* is unknown.
*/
public Object getAttributeValue(FeatureDescriptor feature,
String attributeName) {
if (feature == null || attributeName == null) {
return null;
}
return feature.getValue(attributeName);
}
/**
* Converts a string to normal Java variable name capitalization.
* This normally means converting the first character from upper case
* to lower case, but in the (unusual) special case when there is more
* than one character and both the first and second characters are upper
* case, we leave it alone.
*
* Thus "FooBar" becomes "fooBar" and "X" becomes "x", but "URL" stays
* as "URL".
*
* @param name the string to be decapitalized
*
* @return the decapitalized version of the string
*/
public String decapitalize(String name) {
if (name == null) {
return "";
}
return Introspector.decapitalize(name);
}
/**
* Returns the first sentence of the specified paragraph. Uses
* java.text.BreakIterator.getSentenceInstance()
*/
public String getFirstSentence(String paragraph) {
if (paragraph == null || paragraph.length() == 0) {
return "";
}
BreakIterator sentenceBreaks = BreakIterator.getSentenceInstance();
sentenceBreaks.setText(paragraph);
int start = sentenceBreaks.first();
int end = sentenceBreaks.next();
String sentence = paragraph;
if (start >= 0 && end <= paragraph.length()) {
sentence = paragraph.substring(start, end);
}
return sentence.trim();
}
/**
* Creates a String with the specified pattern repeated length
* times.
*/
public String createPatternString(String pattern, int length) {
if (pattern == null) {
return null;
}
int totalLength = pattern.length() * length;
StringBuffer sb = new StringBuffer(totalLength);
for (int i = 0; i < length; i++) {
sb.append(pattern);
}
return sb.toString();
}
/**
* Creates a String of spaces with the specified length.
*/
public String createWhitespaceString(int length) {
return createPatternString(" ", length);
}
/**
* Returns the FeatureDescriptor's shortDescription or "" if the
* shortDescription is the same as the displayName.
*/
public String getDescription(FeatureDescriptor feature) {
String description = feature.getShortDescription();
if (description == null ||
description.equals(feature.getDisplayName())) {
description = "";
}
return description;
}
/**
* Returns the first sentence of the FeatureDescriptor's
* shortDescription. Returns "" if the shortDescription is the same as
* the displayName (the default for reflection-generated
* FeatureDescriptors).
*/
public String getDescriptionFirstSentence(FeatureDescriptor feature) {
return getFirstSentence(getDescription(feature));
}
/**
* Sorts an array of FeatureDescriptors based on the method name and
* if these descriptors are MethodDescriptors, by param count as well.
* To prevent damage to the original array, a clone is made, sorted,
* and returned from this method.
*/
public FeatureDescriptor[] sortDescriptors(FeatureDescriptor[] fds) {
/*
* the variable dolly is used here in reference to the sheep cloned
* a few years back by Scottish scientists. - Jonathan
*/
FeatureDescriptor[] dolly = (FeatureDescriptor[])fds.clone();
Arrays.sort(dolly, DESCRIPTOR_COMPARATOR);
return dolly;
}
/**
* Sorts an array of MethodDescriptors based on the method name and
* param count.
*/
public void sortMethodDescriptors(MethodDescriptor[] mds) {
Arrays.sort(mds, DESCRIPTOR_COMPARATOR);
}
/**
* Sorts an array of PropertyDescriptors based on name.
*/
public void sortPropertyDescriptors(PropertyDescriptor[] pds) {
Arrays.sort(pds, DESCRIPTOR_COMPARATOR);
}
//
// Tea-specific utilities
//
/**
* Merges several classes together, producing a new class that has all
* of the methods of the combined classes. All methods in the combined
* class delegate to instances of the source classes. If multiple classes
* implement the same method, the first one provided is used.
* The merged class implements all of the interfaces provided by the
* source classes or interfaces.
*
* This method uses org.teatrove.trove.util.MergedClass
*/
public Class createContextClass(ClassLoader loader,
ContextClassEntry[] contextClasses)
throws Exception {
Object[] ret = loadContextClasses(loader, contextClasses);
Class[] classes = (Class[]) ret[0];
String[] prefixNames = (String[]) ret[1];
return createContextClass(loader, classes, prefixNames);
}
/**
* Merges several classes together, producing a new class that has all
* of the methods of the combined classes. All methods in the combined
* class delegate to instances of the source classes. If multiple classes
* implement the same method, the first one provided is used.
* The merged class implements all of the interfaces provided by the
* source classes or interfaces.
*
* This method uses org.teatrove.trove.util.MergedClass
*/
public Class createContextClass(ClassLoader loader,
Class[] classes,
String[] prefixNames)
throws Exception {
ClassInjector classInjector =
new ClassInjector(loader, (java.io.File) null, null);
Constructor constructor = null;
constructor = MergedClass.getConstructor(classInjector,
classes,
prefixNames);
Class mergedContextClass = constructor.getDeclaringClass();
return mergedContextClass;
}
/**
* Loads and returns the runtime context classes specified by the
* ContextClassEntry array.
*
* @return a 2 element Object array:
*
* index [0] is the Class[] containing the context classes.
* index [1] is the String[] containing the context prefix names.
*/
public Object[] loadContextClasses(ClassLoader loader,
ContextClassEntry[] contextClasses)
throws Exception {
if (loader == null) {
throw new IllegalArgumentException("ClassLoader is null");
}
if (contextClasses == null || contextClasses.length == 0) {
throw new IllegalArgumentException("No Context Classes");
}
Vector classVector = new Vector(contextClasses.length);
String[] prefixNames = new String[contextClasses.length];
String className = null;
for (int i = 0; i < contextClasses.length; i++) {
className = contextClasses[i].getContextClassName();
Class contextClass = loader.loadClass(className);
classVector.addElement(contextClass);
String prefixName = contextClasses[i].getPrefixName();
if (prefixName != null) {
prefixNames[i] = prefixName + "$";
}
else {
prefixNames[i] = null;
}
}
Class[] classes = new Class[classVector.size()];
classVector.copyInto(classes);
return new Object[] {classes, prefixNames};
}
/**
* Returns true if it is likely that the specified class serves as
* a Tea runtime context class.
*/
public boolean isLikelyContextClass(Class clazz) {
return (OutputReceiver.class.isAssignableFrom(clazz) ||
getClassName(clazz).toLowerCase().endsWith("context"));
}
/**
* Gets the MethodDescriptors of the specified context class including
* all of the MethodDescriptors for methods declared in the class's
* superclass and interfaces
*
* @param contextClass the Tea context Class to introspect (any class will
* work fine)
*/
public MethodDescriptor[] getTeaContextMethodDescriptors(
Class contextClass) {
return getTeaContextMethodDescriptors(contextClass, false);
}
/**
* Gets the MethodDescriptors of the specified context class
*
* @param contextClass the Tea context Class to introspect (any class will
* work fine)
* @param specifiedClassOnly true indicates that this function should
* only return MethodDescriptors declared by the specified Class.
*/
public MethodDescriptor[] getTeaContextMethodDescriptors(
Class contextClass,
boolean specifiedClassOnly) {
Vector v = new Vector();
MethodDescriptor[] methodDescriptors = null;
try {
BeanInfo beanInfo = getBeanInfo(contextClass);
methodDescriptors = beanInfo.getMethodDescriptors();
}
catch (Throwable e) {
e.printStackTrace();
}
if (methodDescriptors != null) {
Method[] methods = contextClass.getMethods();
if (methods.length > methodDescriptors.length) {
methodDescriptors = addMissingContextMethodDescriptors(
methods, methodDescriptors);
}
for (int i = 0; i < methodDescriptors.length; i++) {
MethodDescriptor md = methodDescriptors[i];
Class declaringClass = md.getMethod().getDeclaringClass();
if (declaringClass != Object.class &&
!md.isHidden() &&
(!specifiedClassOnly || declaringClass == contextClass)) {
v.addElement(md);
}
}
}
methodDescriptors = new MethodDescriptor[v.size()];
v.copyInto(methodDescriptors);
sortMethodDescriptors(methodDescriptors);
return methodDescriptors;
}
/**
* Gets the complete, combined set of MethodDescriptors for the specified
* context classes.
*
* @param contextClasses the Tea context classes to introspect
*/
public MethodDescriptor[] getTeaContextMethodDescriptors(
Class[] contextClasses) {
Vector v = new Vector();
Hashtable methods = new Hashtable();
if (contextClasses != null) {
// Combine all of the MethodDescriptors
for (int i = 0; i < contextClasses.length; i++) {
Class c = contextClasses[i];
if (c == null) {
continue;
}
MethodDescriptor[] mds =
getTeaContextMethodDescriptors(c, false);
for (int j = 0; j < mds.length; j++) {
MethodDescriptor md = mds[j];
Method m = md.getMethod();
// Check uniqueness of method
if (methods.get(m) == null) {
methods.put(m, m);
v.addElement(md);
}
}
}
}
MethodDescriptor[] methodDescriptors = new MethodDescriptor[v.size()];
v.copyInto(methodDescriptors);
// Sort the combined results
sortMethodDescriptors(methodDescriptors);
return methodDescriptors;
}
/**
* A function that returns an array of all the available properties on
* a given class.
*
* NOTE: If possible, the results of this method should be cached
* by the caller.
*
* @param beanClass the bean class to introspect
*
* @return an array of all the available properties on the specified class.
*/
public PropertyDescriptor[] getTeaBeanPropertyDescriptors(
Class beanClass) {
// Code taken from KettleUtilities.getPropertyDescriptors(Class)
if (beanClass == null) {
return NO_PROPERTIES;
}
PropertyDescriptor[] properties = null;
Map allProps = null;
try {
allProps = BeanAnalyzer.getAllProperties(new GenericType(beanClass));
}
catch (Throwable t) {
return NO_PROPERTIES;
}
Collection cleanProps = new ArrayList(allProps.size());
Iterator it = allProps.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String name = (String) entry.getKey();
PropertyDescriptor desc = (PropertyDescriptor) entry.getValue();
// Discard properties that have no name or should be hidden.
if (name == null || name.length() == 0 || "class".equals(name)) {
continue;
}
if (desc instanceof KeyedPropertyDescriptor) {
KeyedPropertyDescriptor keyed = (KeyedPropertyDescriptor) desc;
Class type = keyed.getKeyedPropertyType().getRawType().getType();
try {
// Convert the KeyedPropertyDescriptor to a
// ArrayIndexPropertyDescriptor
desc = new ArrayIndexPropertyDescriptor(beanClass, type);
}
catch (Throwable t) {
continue;
}
}
else if (!beanClass.isArray() && desc.getReadMethod() == null) {
continue;
}
cleanProps.add(desc);
}
properties = (PropertyDescriptor[]) cleanProps.toArray
(new PropertyDescriptor[cleanProps.size()]);
// Sort 'em!
sortPropertyDescriptors(properties);
return properties;
}
/**
* Returns the full class name of the specified class. This method
* provides special formatting for array and inner classes. If the
* specified class is implicitly imported by Tea, then its package is
* omitted in the returned name.
*/
public String getTeaFullClassName(Class clazz) {
if (isImplicitTeaImport(clazz)) {
return getClassName(clazz);
}
return getFullClassName(clazz);
}
/**
* Returns true if the specified class is
* implicitly imported by Tea.
*
* Returns true if the specified class represents a primitive type or
* a class or interface defined in one of the IMPLICIT_TEA_IMPORTS
* packages. This method also works for array types.
*/
public boolean isImplicitTeaImport(Class clazz) {
if (getArrayType(clazz).isPrimitive()) {
return true;
}
return isImplicitTeaImport(getClassPackage(clazz));
}
/**
* Returns true if the specified class or package is
* implicitly imported by Tea.
*/
public boolean isImplicitTeaImport(String classOrPackageName) {
if (classOrPackageName == null) {
return false;
}
if (cPrimativeClasses.get(classOrPackageName) != null) {
return true;
}
for (int i = 0; i < IMPLICIT_TEA_IMPORTS.length; i++) {
String prefix = IMPLICIT_TEA_IMPORTS[i];
if (classOrPackageName.startsWith(prefix)) {
return true;
}
}
return false;
}
/**
* Returns true if the specified method accepts a
* Substitution
as its last parameter.
*/
public boolean acceptsSubstitution(MethodDescriptor md) {
return acceptsSubstitution(md.getMethod());
}
/**
* Returns true if the specified method accepts a
* Substitution
as its last parameter.
*/
public boolean acceptsSubstitution(Method m) {
Class[] paramTypes = m.getParameterTypes();
if (paramTypes.length > 0) {
// Check if last param is a Substitution
Class lastParam = paramTypes[paramTypes.length - 1];
return Substitution.class.isAssignableFrom(lastParam);
}
return false;
}
/**
* Returns true if the specifed class is compatible with Tea's
* foreach
statement. Compatibility implies that the
* class can be iterated on by the foreach
.
*/
public boolean isForeachCompatible(Class clazz) {
if (clazz == null) {
return false;
}
return (clazz.isArray() ||
Collection.class.isAssignableFrom(clazz));
}
/**
* Returns true if the specifed class is compatible with Tea's if
*
statement. Only Boolean.class and boolean.class qualify.
*/
public boolean isIfCompatible(Class clazz) {
if (clazz == null) {
return false;
}
return (clazz == boolean.class || clazz == Boolean.class);
}
//
// File name utilities
//
/**
* Removes the file extension (all text after the last '.' character)
* from the specified fileName.
*
* @param fileName the String with an (optional) extension
*
* @return the String param sans extension
*/
public String removeFileExtension(String fileName) {
int dotIndex = fileName.lastIndexOf(".");
if (dotIndex > 0) {
fileName = fileName.substring(0, dotIndex);
}
return fileName;
}
/**
* Returns true if the specifed fileName is a Tea file.
*/
public boolean isTeaFileName(String fileName) {
return compareFileExtension(fileName, TEA_FILE_EXTENSION);
}
/**
* Returns true if the specified fileName ends with the specified
* file extension.
*/
public boolean compareFileExtension(String fileName,
String extension) {
if (fileName == null || extension == null) {
return false;
}
fileName = fileName.toLowerCase().trim();
extension = extension.toLowerCase();
return (fileName.endsWith(extension));
}
/**
* Works around a bug in java.beans.Introspector, where sub-interfaces
* do not return their parent's methods, in the method descriptor array.
*/
protected MethodDescriptor[] addMissingContextMethodDescriptors(
Method[] methods, MethodDescriptor[] methodDescriptors) {
List methodNames = new ArrayList(methodDescriptors.length);
Vector mds = new Vector(methods.length);
for (int i=0; i y.
*/
public int compare(Object x, Object y) {
int nameResult = compareNames(x,y);
if (nameResult == 0 && x != null && y != null &&
x instanceof MethodDescriptor &&
y instanceof MethodDescriptor) {
MethodDescriptor mdX = (MethodDescriptor) x;
MethodDescriptor mdY = (MethodDescriptor) y;
Method mX = mdX.getMethod();
Method mY = mdY.getMethod();
return (getParamCount(mX) - getParamCount(mY));
}
return nameResult;
}
private int compareNames(Object x, Object y) {
if (x != null && y != null &&
x instanceof FeatureDescriptor &&
y instanceof FeatureDescriptor) {
FeatureDescriptor fdX = (FeatureDescriptor) x;
FeatureDescriptor fdY = (FeatureDescriptor) y;
return fdX.getName().compareToIgnoreCase(fdY.getName());
}
return 0;
}
private int getParamCount(Method method) {
int paramCount = 0;
Class[] paramClasses = method.getParameterTypes();
if (paramClasses != null) {
paramCount = paramClasses.length;
}
return paramCount;
}
}
/**
* Slight variation of Tea's KeyedPropertyDescriptor. Does essentially
* the same thing, but provides more user-friendly information.
*
* @author Mark Masse
* @version
*/
private class ArrayIndexPropertyDescriptor
extends PropertyDescriptor {
private Class mPropertyType;
public ArrayIndexPropertyDescriptor(Class beanClass,
Class propertyType)
throws IntrospectionException {
super(BeanAnalyzer.KEYED_PROPERTY_NAME, beanClass, null, null);
mPropertyType = propertyType;
String typeName = getTeaFullClassName(propertyType);
setShortDescription("An indexed property with " +
typeName + " elements");
}
public Class getPropertyType() {
return mPropertyType;
}
}
/**
* TeaToolsUtils test class
*
* @author Mark Masse
* @version
*/
private static class Tester {
Tester(String[] args) throws Exception {
TeaToolsUtils utils = new TeaToolsUtils();
if (args == null || args.length == 0) {
System.err.println("No classes specified, using defaults");
args =
new String[] {
int.class.getName(),
Object.class.getName(),
String[].class.getName(),
int[].class.getName(),
int[][].class.getName(),
boolean[].class.getName(),
Tester.class.getName() };
}
for (int i = 0; i < args.length; i++) {
String className = args[i];
System.out.println("\nTesting with class: " + className);
System.out.println("Class getClassForName(String)");
Class clazz = utils.getClassForName(className);
if (clazz == null) {
System.err.println("Failed to load class!");
continue;
}
System.out.println("BeanInfo getBeanInfo(Class)");
BeanInfo info = utils.getBeanInfo(clazz);
System.out.println("String getDescriptionFirstSentence(FeatureDescriptor)");
System.out.println(utils.getDescriptionFirstSentence(info.getBeanDescriptor()));
System.out.println("String getFirstSentence(String)");
System.out.println(utils.getFirstSentence("Hello World. Hi!"));
System.out.println("String getFullClassName(Class)");
System.out.println(utils.getFullClassName(clazz));
System.out.println("String getFullClassName(String)");
System.out.println(utils.getFullClassName(className));
System.out.println("String getClassName(Class)");
System.out.println(utils.getClassName(clazz));
System.out.println("String getClassName(String)");
System.out.println(utils.getClassName(className));
System.out.println("String getClassPackage(Class)");
System.out.println(utils.getClassPackage(clazz));
System.out.println("String getClassPackage(String)");
System.out.println(utils.getClassPackage(className));
System.out.println("String getTeaFullClassName(Class)");
System.out.println(utils.getTeaFullClassName(clazz));
System.out.println("boolean isImplicitTeaImport(Class)");
System.out.println(utils.isImplicitTeaImport(clazz));
System.out.println("MethodDescriptor[] getTeaContextMethodDescriptors(Class)");
MethodDescriptor[] mds =
utils.getTeaContextMethodDescriptors(clazz);
printMethodDescriptors(mds, utils);
System.out.println("MethodDescriptor[] getTeaContextMethodDescriptors(Class, boolean)");
mds = utils.getTeaContextMethodDescriptors(clazz, true);
printMethodDescriptors(mds, utils);
System.out.println("MethodDescriptor[] getTeaBeanPropertyDescriptors(Class)");
PropertyDescriptor[] pds =
utils.getTeaBeanPropertyDescriptors(clazz);
printPropertyDescriptors(pds, utils);
}
if (args.length > 1) {
System.out.println("");
System.out.println("");
System.out.println("MethodDescriptor[] getTeaContextMethodDescriptors(Class[])");
Class[] classes = new Class[args.length];
for (int i = 0; i < classes.length; i++) {
classes[i] = utils.getClassForName(args[i]);
}
MethodDescriptor[] mds =
utils.getTeaContextMethodDescriptors(classes);
printMethodDescriptors(mds, utils);
}
}
private void printMethodDescriptors(MethodDescriptor[] mds,
TeaToolsUtils utils) {
for (int i = 0; i < mds.length; i++) {
printMethodDescriptor(mds[i], utils);
}
}
private void printMethodDescriptor(MethodDescriptor md,
TeaToolsUtils utils) {
printlnIndented("Method: " + md.getName(), 2);
String desc = utils.getDescriptionFirstSentence(md);
if (desc.length() > 0) {
printlnIndented(desc, 8);
}
}
private void printPropertyDescriptors(PropertyDescriptor[] pds,
TeaToolsUtils utils) {
for (int i = 0; i < pds.length; i++) {
printPropertyDescriptor(pds[i], utils);
}
}
private void printPropertyDescriptor(PropertyDescriptor pd,
TeaToolsUtils utils) {
printlnIndented("Property: " + pd.getName(), 2);
String desc = utils.getDescriptionFirstSentence(pd);
if (desc.length() > 0) {
printlnIndented(desc, 8);
}
}
private void printlnIndented(Object o, int indent) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < indent; i++) {
sb.append(' ');
}
System.out.println(sb.toString() + o);
}
}
}