blob: 563523c20d4af448c5e560dfcd7ce94a174c2832 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Ant", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.ant.core.execution;
import java.lang.reflect.*;
import java.io.File;
import java.util.*;
/**
* Introspects a class and builds a set of objects to assist in intospecting the
* class.
*
* @author Stefan Bodewig <a href="mailto:stefan.bodewig@megabit.net">stefan.bodewig@megabit.net</a>
* @author <a href="mailto:conor@apache.org">Conor MacNeill</a>
*/
public class ClassIntrospector {
/**
* holds the types of the attributes that could be set.
*/
private Hashtable attributeTypes;
/**
* holds the attribute setter methods.
*/
private Hashtable attributeSetters;
/**
* Holds the types of nested elements that could be created.
*/
private Hashtable nestedTypes;
/**
* Holds methods to create nested elements.
*/
private Hashtable nestedCreators;
/**
* The method to add PCDATA stuff.
*/
private Method addText = null;
/**
* The Class that's been introspected.
*/
private Class bean;
/**
* returns the boolean equivalent of a string, which is considered true
* if either "on", "true", or "yes" is found, ignoring case.
*/
public static boolean toBoolean(String s) {
return (s.equalsIgnoreCase("on") ||
s.equalsIgnoreCase("true") ||
s.equalsIgnoreCase("yes"));
}
public ClassIntrospector(final Class bean, Map converters) {
attributeTypes = new Hashtable();
attributeSetters = new Hashtable();
nestedTypes = new Hashtable();
nestedCreators = new Hashtable();
this.bean = bean;
Method[] methods = bean.getMethods();
for (int i=0; i<methods.length; i++) {
final Method m = methods[i];
final String name = m.getName();
Class returnType = m.getReturnType();
Class[] args = m.getParameterTypes();
if ("addText".equals(name)
&& java.lang.Void.TYPE.equals(returnType)
&& args.length == 1
&& java.lang.String.class.equals(args[0])) {
addText = methods[i];
} else if (name.startsWith("set")
&& java.lang.Void.TYPE.equals(returnType)
&& args.length == 1
&& !args[0].isArray()) {
String propName = getPropertyName(name, "set");
AttributeSetter as = createAttributeSetter(m, args[0], converters);
if (as != null) {
attributeTypes.put(propName, args[0]);
attributeSetters.put(propName, as);
}
} else if (name.startsWith("create")
&& !returnType.isArray()
&& !returnType.isPrimitive()
&& args.length == 0) {
String propName = getPropertyName(name, "create");
nestedTypes.put(propName, returnType);
nestedCreators.put(propName, new NestedCreator() {
public Object create(Object parent)
throws InvocationTargetException,
IllegalAccessException {
return m.invoke(parent, new Object[] {});
}
});
} else if (name.startsWith("add")
&& java.lang.Void.TYPE.equals(returnType)
&& args.length == 1
&& !java.lang.String.class.equals(args[0])
&& !args[0].isArray()
&& !args[0].isPrimitive()) {
try {
final Constructor c =
args[0].getConstructor(new Class[] {});
String propName = getPropertyName(name, "add");
nestedTypes.put(propName, args[0]);
nestedCreators.put(propName, new NestedCreator() {
public Object create(Object parent)
throws InvocationTargetException, IllegalAccessException, InstantiationException {
Object o = c.newInstance(new Object[] {});
m.invoke(parent, new Object[] {o});
return o;
}
});
} catch (NoSuchMethodException nse) {
}
}
}
}
/**
* Sets the named attribute.
*/
public void setAttribute(Object element, String attributeName,
String value)
throws ClassIntrospectionException, ConversionException {
AttributeSetter as = (AttributeSetter) attributeSetters.get(attributeName);
if (as == null) {
String msg = "Class " + element.getClass().getName() +
" doesn't support the \"" + attributeName + "\" attribute";
throw new ClassIntrospectionException(msg);
}
try {
as.set(element, value);
} catch (IllegalAccessException ie) {
// impossible as getMethods should only return public methods
throw new ClassIntrospectionException(ie);
} catch (InvocationTargetException ite) {
Throwable t = ite.getTargetException();
if (t instanceof ClassIntrospectionException) {
throw (ClassIntrospectionException) t;
}
throw new ClassIntrospectionException(t);
}
}
/**
* Adds PCDATA areas.
*/
public void addText(Object element, String text)
throws ClassIntrospectionException {
if (addText == null) {
String msg = "Class " + element.getClass().getName() +
" doesn't support nested text elements";
throw new ClassIntrospectionException(msg);
}
try {
addText.invoke(element, new String[] {text});
} catch (IllegalAccessException ie) {
// impossible as getMethods should only return public methods
throw new ClassIntrospectionException(ie);
} catch (InvocationTargetException ite) {
Throwable t = ite.getTargetException();
if (t instanceof ClassIntrospectionException) {
throw (ClassIntrospectionException) t;
}
throw new ClassIntrospectionException(t);
}
}
public boolean supportsNestedElement(String elementName) {
return nestedCreators.containsKey(elementName);
}
/**
* Creates a named nested element.
*/
public Object createElement(Object element, String elementName)
throws ClassIntrospectionException {
NestedCreator nc = (NestedCreator) nestedCreators.get(elementName);
if (nc == null) {
String msg = "Class " + element.getClass().getName() +
" doesn't support the nested \"" + elementName + "\" element";
throw new ClassIntrospectionException(msg);
}
try {
return nc.create(element);
} catch (IllegalAccessException ie) {
// impossible as getMethods should only return public methods
throw new ClassIntrospectionException(ie);
} catch (InstantiationException ine) {
// impossible as getMethods should only return public methods
throw new ClassIntrospectionException(ine);
} catch (InvocationTargetException ite) {
Throwable t = ite.getTargetException();
if (t instanceof ClassIntrospectionException) {
throw (ClassIntrospectionException) t;
}
throw new ClassIntrospectionException(t);
}
}
/**
* returns the type of a named nested element.
*/
public Class getElementType(String elementName)
throws ClassIntrospectionException {
Class nt = (Class) nestedTypes.get(elementName);
if (nt == null) {
String msg = "Class " + bean.getName() +
" doesn't support the nested \"" + elementName + "\" element";
throw new ClassIntrospectionException(msg);
}
return nt;
}
/**
* returns the type of a named attribute.
*/
public Class getAttributeType(String attributeName)
throws ClassIntrospectionException {
Class at = (Class) attributeTypes.get(attributeName);
if (at == null) {
String msg = "Class " + bean.getName() +
" doesn't support the \"" + attributeName + "\" attribute";
throw new ClassIntrospectionException(msg);
}
return at;
}
/**
* Does the introspected class support PCDATA?
*/
public boolean supportsCharacters() {
return addText != null;
}
/**
* Return all attribues supported by the introspected class.
*/
public Enumeration getAttributes() {
return attributeSetters.keys();
}
/**
* Return all nested elements supported by the introspected class.
*/
public Enumeration getNestedElements() {
return nestedTypes.keys();
}
/**
* Create a proper implementation of AttributeSetter for the given
* attribute type.
*/
private AttributeSetter createAttributeSetter(final Method m,
final Class arg,
Map converters) {
if (converters != null && converters.containsKey(arg)) {
// we have a converter to use to convert the strign
// value of into something the set method expects.
final Converter converter = (Converter)converters.get(arg);
return new AttributeSetter() {
public void set(Object parent, String value)
throws InvocationTargetException, IllegalAccessException,
ClassIntrospectionException, ConversionException {
m.invoke(parent, new Object[] {converter.convert(value, arg)});
}
};
}
// simplest case - setAttribute expects String
else if (java.lang.String.class.equals(arg)) {
return new AttributeSetter() {
public void set(Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent, new String[] {value});
}
};
// now for the primitive types, use their wrappers
} else if (java.lang.Character.class.equals(arg)
|| java.lang.Character.TYPE.equals(arg)) {
return new AttributeSetter() {
public void set(Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent, new Character[] {new Character(value.charAt(0))});
}
};
} else if (java.lang.Byte.TYPE.equals(arg)) {
return new AttributeSetter() {
public void set(Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent, new Byte[] {new Byte(value)});
}
};
} else if (java.lang.Short.TYPE.equals(arg)) {
return new AttributeSetter() {
public void set(Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent, new Short[] {new Short(value)});
}
};
} else if (java.lang.Integer.TYPE.equals(arg)) {
return new AttributeSetter() {
public void set(Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent, new Integer[] {new Integer(value)});
}
};
} else if (java.lang.Long.TYPE.equals(arg)) {
return new AttributeSetter() {
public void set(Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent, new Long[] {new Long(value)});
}
};
} else if (java.lang.Float.TYPE.equals(arg)) {
return new AttributeSetter() {
public void set(Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent, new Float[] {new Float(value)});
}
};
} else if (java.lang.Double.TYPE.equals(arg)) {
return new AttributeSetter() {
public void set(Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent, new Double[] {new Double(value)});
}
};
} else if (java.lang.Boolean.class.equals(arg)
|| java.lang.Boolean.TYPE.equals(arg)) {
return new AttributeSetter() {
public void set(Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent,
new Boolean[] {new Boolean(toBoolean(value))});
}
};
// Class doesn't have a String constructor but a decent factory method
} else if (java.lang.Class.class.equals(arg)) {
return new AttributeSetter() {
public void set(Object parent, String value)
throws InvocationTargetException, IllegalAccessException, ClassIntrospectionException {
try {
m.invoke(parent, new Class[] {Class.forName(value)});
} catch (ClassNotFoundException ce) {
throw new ClassIntrospectionException(ce);
}
}
};
// worst case. look for a public String constructor and use it
} else {
try {
final Constructor c =
arg.getConstructor(new Class[] {java.lang.String.class});
return new AttributeSetter() {
public void set(Object parent,
String value)
throws InvocationTargetException, IllegalAccessException, ClassIntrospectionException {
try {
m.invoke(parent, new Object[] {c.newInstance(new String[] {value})});
} catch (InstantiationException ie) {
throw new ClassIntrospectionException(ie);
}
}
};
} catch (NoSuchMethodException nme) {
}
}
return null;
}
/**
* extract the name of a property from a method name - subtracting
* a given prefix.
*/
private String getPropertyName(String methodName, String prefix) {
int start = prefix.length();
return methodName.substring(start).toLowerCase();
}
private interface NestedCreator {
public Object create(Object parent)
throws InvocationTargetException, IllegalAccessException, InstantiationException;
}
private interface AttributeSetter {
public void set(Object parent, String value)
throws InvocationTargetException, IllegalAccessException,
ClassIntrospectionException, ConversionException;
}
}