blob: 7d49bc432f16928747287ce79d060f4830b7ebbd [file] [log] [blame]
/* Copyright (c) 2000 The Apache Software Foundation */
package org.apache.tools.ant;
import java.beans.*;
import java.lang.reflect.*;
import java.util.*;
/**
* This class stores info about a bean's properties so that
* the actual bean can be instantiated at a later time. This data
* is used to store info about a task, since the actual
* task class might not be loaded until after parsing is completed.
*
* @see TaskProxy
*
* @author <a href="mailto:mpfoemme@thoughtworks.com">Matthew Foemmel</a>
*/
public class TaskData {
private TaskProxy proxy;
private String location;
private String text;
private Map properties;
/**
* Constructs a new TaskData under the specified task.
*/
public TaskData(TaskProxy proxy) {
this.proxy = proxy;
this.location = null;
this.properties = new HashMap();
}
/**
* Returns the task proxy that this data is associated with.
*/
public TaskProxy getTaskProxy() {
return proxy;
}
/**
* Returns the location in the build fiole where this data was defined.
*/
public String getLocation() {
return location;
}
/**
* Returns the location in the build fiole where this data was defined.
*/
public void setLocation(String location) {
this.location = location;
}
/**
* Sets the text for this bean data, for cases where the bean is a simple
* type like String or int.
*/
public void setText(String text) {
this.text = text;
}
/**
* Sets the value of a property on the bean. Multiple properties can be
* added with the same name only if the property on the bean is an array.
*/
public TaskData addProperty(String name) {
TaskData data = new TaskData(proxy);
getProperties(name).add(data);
return data;
}
/**
* Returns the list of property values for the specified name.
*/
private List getProperties(String name) {
List result = (List) properties.get(name);
if (result == null) {
result = new ArrayList();
properties.put(name, result);
}
return result;
}
/**
* Creates a new bean instance and initializes its properties.
*/
public Object createBean(Class type) throws BuildException {
Object bean = null;
// See if an editor exists for this type
PropertyEditor editor = PropertyEditorManager.findEditor(type);
if (editor == null) {
// We don't know how to handle text for types without editors
if (text != null) {
throw new BuildException("Unexpected text \"" + text + "\"", location);
}
try {
bean = type.newInstance();
}
catch(InstantiationException exc) {
throw new AntException("Unable to instantiate " + type.getName(), exc);
}
catch(IllegalAccessException exc) {
throw new AntException("Unable to access constructor for " + type.getName(), exc);
}
}
else {
try {
// Let the editor parse the text
editor.setAsText(parseVariables(text));
}
catch(NumberFormatException exc) {
throw new BuildException("\"" + text + "\" is not a valid number", location);
}
bean = editor.getValue();
}
// Update the fields on the bean
updateProperties(bean);
return bean;
}
/**
* Sets all of the property values on the bean.
*/
private void updateProperties(Object bean) throws BuildException {
// Call setProperty for each property that's been defined
Iterator itr = properties.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry entry = (Map.Entry) itr.next();
String name = (String) entry.getKey();
List values = (List) entry.getValue();
setProperty(bean, name, values);
}
}
/**
* Finds the PropertyDescriptor for the specifed property and sets it.
*/
private void setProperty(Object bean, String name, List value) throws BuildException {
PropertyDescriptor[] descriptors = getPropertyDescriptors(bean.getClass());
// Search for the property with the matching name
for (int i = 0; i < descriptors.length; i++) {
if (descriptors[i].getName().equals(name)) {
setProperty(bean, descriptors[i], value);
return;
}
}
throw new BuildException("Unexpected attribute \"" + name + "\"", location);
}
/**
* Sets a single property on a bean.
*/
private static void setProperty(Object obj, PropertyDescriptor descriptor, List values) throws BuildException {
Object value = null;
Class type = descriptor.getPropertyType();
if (type.isArray()) {
value = createBeans(type.getComponentType(), values);
}
else if (values.size() == 1) {
TaskData data = (TaskData) values.get(0);
value = data.createBean(type);
}
try {
descriptor.getWriteMethod().invoke(obj, new Object[] { value });
}
catch(IllegalAccessException exc) {
throw new AntException("Unable to access write method for \"" + descriptor.getName() + "\"", exc);
}
catch(InvocationTargetException exc) {
throw new AntException("Unable to set property \"" + descriptor.getName() + "\"", exc.getTargetException());
}
}
/**
* Creates a number of beans with the same type using the list of TaskData's
*/
private static Object[] createBeans(Class type, List values) throws BuildException {
Object[] beans = (Object[]) Array.newInstance(type, values.size());
int i = 0;
Iterator itr = values.iterator();
while (itr.hasNext()) {
TaskData data = (TaskData) itr.next();
beans[i++] = data.createBean(type);
}
return beans;
}
/**
* Uses the Introspector class to lookup the property descriptors for the class.
*/
private static PropertyDescriptor[] getPropertyDescriptors(Class type) {
try {
return Introspector.getBeanInfo(type, Object.class).getPropertyDescriptors();
}
catch(IntrospectionException exc) {
throw new AntException("Unable to get bean info for " + type.getName());
}
}
/**
* Replaces any variables in the input string with their values.
*/
private String parseVariables(String input) throws BuildException {
StringBuffer output = new StringBuffer();
int start = 0;
int end = 0;
while ((start = input.indexOf('{', end)) != -1) {
output.append(input.substring(end,start));
end = input.indexOf('}', start);
if (end != -1) {
String name = input.substring(++start, end++);
String value = proxy.getTarget().getProject().getVariable(name);
if (value == null) {
throw new BuildException("The variable \"" + name + "\" has not been defined");
}
output.append(value);
}
}
output.append(input.substring(end));
return output.toString();
}
}