package org.gambi.tapestry5.cli.services.internal.impl;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.beanutils.ConstructorUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.tapestry5.ValidationException;
import org.apache.tapestry5.ioc.services.TypeCoercer;
import org.apache.tapestry5.ioc.util.UnknownValueException;
import org.gambi.tapestry5.cli.data.ApplicationConfiguration;
import org.gambi.tapestry5.cli.data.CLIOption;
import org.gambi.tapestry5.cli.services.internal.ApplicationConfigurationSource;
import org.slf4j.Logger;
public class ApplicationConfigurationSourceImpl implements
ApplicationConfigurationSource {
private Logger logger;
// Since TranslatoSource is not there we need to used typeCoercer
private TypeCoercer typeCoercer;
// The properties to be validate... all of them have the validation
// annotations
private Map<String, Object> contributions;
public ApplicationConfigurationSourceImpl(Logger logger,
TypeCoercer typeCoercer, Map<String, Object> contributions) {
this.logger = logger;
this.typeCoercer = typeCoercer;
this.contributions = contributions;
}
// TODO This should be improved !
private String escapePropertyName(String propertyName) {
String _pName = propertyName.trim().replaceAll("--", "-");
StringBuilder sb = new StringBuilder();
boolean capitalizeNext = false;
for (char c : _pName.toCharArray()) {
if (c == '-') {
capitalizeNext = true;
} else {
if (capitalizeNext) {
sb.append(Character.toUpperCase(c));
capitalizeNext = false;
} else {
sb.append(c);
}
}
}
return sb.toString();
}
/**
* This is the method that encodes the Naming Convention to match options'
* name and bean properties
*
* @param parsedOptions
* @param propertyName
* @return
*/
private CLIOption findOption(Collection<CLIOption> parsedOptions,
String propertyName) {
for (CLIOption option : parsedOptions) {
String optionName = escapePropertyName(option.getLongOpt());
if (optionName.equals(propertyName)) {
return option;
}
}
return null;
}
private int findInput(List<String> parsedInputs, String propertyName) {
int inputPosition = -1;
// Extract the position from propertyName input_<pos>. pos must be an
// integer from 0 to n;
if (propertyName.startsWith("input_")) {
try {
inputPosition = Integer.parseInt(propertyName.split("_")[1]);
} catch (Throwable e) {
e.printStackTrace();
return -1;
}
}
if (parsedInputs.size() < inputPosition) {
return inputPosition;
} else {
return -1;
}
}
private boolean optionPresent(Collection<CLIOption> parsedOptions,
String propertyName) {
return findOption(parsedOptions, propertyName) != null;
}
private boolean inputPresent(List<String> parsedInputs, String propertyName) {
return findInput(parsedInputs, propertyName) != -1;
}
private void assignOption(Collection<CLIOption> parsedOptions,
String propertyName, Object bean) throws IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
CLIOption option = findOption(parsedOptions, propertyName);
if (option != null) {
assignOption(option, propertyName, bean);
}
}
private void assignOption(CLIOption option, String propertyName, Object bean)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
if (option == null) {
return;
} else if (option.getValue() == null && option.getValues() == null) {
return;
}
PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(
bean, propertyName);
// Boolean options must be treated differently
Object value = null;
if (option.getnArgs() == 0) {
// logger.debug("Flag Option");
// This is a boolean option that is present
value = typeCoercer.coerce(new Boolean(true),
descriptor.getPropertyType());
} else if (option.getnArgs() > 1) {
// logger.debug("Multiple Arguments Options");
logger.debug("Assign values " + Arrays.toString(option.getValues())
+ " to " + propertyName + " for bean " + bean.getClass());
char[][] values = new char[option.getnArgs()][];
// We use this format to avoid failing in parsing the ","
for (int i = 0; i < option.getnArgs(); i++) {
values[i] = option.getValues()[i].toCharArray();
}
// Transform back to String. This is necessary because the
// target property may not be a String[]
logger.debug("\n\nCoercing " + values + " to "
+ descriptor.getPropertyType().getCanonicalName());
value = typeCoercer.coerce(values, descriptor.getPropertyType());
} else {
// logger.debug("Single Argument option");
logger.debug("Assign value " + option.getValue() + " to "
+ propertyName + " for bean " + bean.getClass());
value = typeCoercer.coerce(option.getValue(),
descriptor.getPropertyType());
}
PropertyUtils.setProperty(bean, propertyName, value);
logger.debug(propertyName + " "
+ PropertyUtils.getProperty(bean, propertyName));
}
private void assignInput(List<String> parsedInputs, String propertyName,
Object bean) throws IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
int inputPosition = findInput(parsedInputs, propertyName);
if (inputPosition != -1) {
assignInput(parsedInputs.get(inputPosition), propertyName, bean);
}
}
private void assignInput(String input, String propertyName, Object bean)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
if (input == null) {
return;
}
PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(
bean, propertyName);
logger.debug("Assign values " + input + " to " + propertyName
+ " for bean " + bean.getClass());
logger.debug("\n\nCoercing " + input + " to "
+ descriptor.getPropertyType().getCanonicalName());
Object value = typeCoercer.coerce(input, descriptor.getPropertyType());
PropertyUtils.setProperty(bean, propertyName, value);
logger.debug(propertyName + " "
+ PropertyUtils.getProperty(bean, propertyName));
}
// This is a recursive call !
private void evaluateTheBean(Collection<CLIOption> parsedOptions,
List<String> parsedInputs, Object bean)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException, ValidationException {
Map properties = PropertyUtils.describe(bean);
// Remove Objects Properties
properties.remove("class");
properties.remove("methods");
for (Object _propName : properties.keySet()) {
String propertyName = (String) _propName;
// logger.debug("propertyName " + propertyName);
// Check if this is contained in the parsed Options
if (optionPresent(parsedOptions, propertyName)) {
// logger.debug("The bean property (" + propertyName
// + ") was specified as input");
assignOption(parsedOptions, propertyName, bean);
} else if (inputPresent(parsedInputs, propertyName)) {
// logger.debug("The bean property (" + propertyName
// + ") was specified as input");
assignInput(parsedInputs, propertyName, bean);
}
else {
logger.debug("The property (" + propertyName
+ ") was not specified on the command line.");
// Access the property
PropertyDescriptor innerBean = PropertyUtils
.getPropertyDescriptor(bean, propertyName);
// Does it have a TypeCoercer from String to its' type ?
try {
typeCoercer.getCoercion(String.class,
innerBean.getPropertyType());
// If the object can be build from a string then it must be
// a property
logger.debug(propertyName
+ " is actually a real property that was SKIP by the user !");
continue;
} catch (UnknownValueException uve) {
// Check if that is actually a bean or something else
// Try to go one step inside this and retry
Object newInnerBeanInstance = null;
Object originalInnerBeanInstance = PropertyUtils
.getProperty(bean, propertyName);
if (originalInnerBeanInstance != null) {
newInnerBeanInstance = originalInnerBeanInstance;
} else {
try {
newInnerBeanInstance = ConstructorUtils
.invokeConstructor(
innerBean.getPropertyType(),
new Object[0]);
} catch (NoSuchMethodException e) {
// If does not provide a no args constructor by
// definition its not a bean
logger.debug(propertyName + " is not a bean SKIP! ");
continue;
} catch (Exception e) {
logger.error("Generic error", e);
throw new RuntimeException(e);
}
}
// logger.debug("Evaluating the inner bean " +
// propertyName);
// Here we are almost sure that this is a bean, therefore we
// make a recursive call and try to evaluate the inner bean
evaluateTheBean(parsedOptions, parsedInputs,
newInnerBeanInstance);
// logger.debug("Setting the inner bean "
// + innerBean.getPropertyType());
// If everything went fine then this should be ok
PropertyUtils.setProperty(bean, propertyName,
newInnerBeanInstance);
} catch (Throwable e) {
// Temporary patch
logger.error("Exception ", e);
throw new RuntimeException(e);
}
}
}
}
public ApplicationConfiguration get(Collection<CLIOption> parsedOptions,
List<String> parsedInputs) {
Collection<Object> properties = new ArrayList<Object>();
for (Entry<String, Object> entry : contributions.entrySet()) {
Object newBeanInstance = entry.getValue();
// try {
// // Create new instance of the contributed bean
// newBeanInstance = ConstructorUtils.invokeConstructor(
// originalBeanInstance.getClass(), new Object[0]);
// } catch (Exception e) {
// logger.error(" Error while instantiating bean "
// + originalBeanInstance.getClass(), e);
// throw new RuntimeException(e);
// }
try {
// Set the value of the properties that we found inside CLI
evaluateTheBean(parsedOptions, parsedInputs, newBeanInstance);
} catch (Exception e) {
logger.error(" Error while setting bean properties", e);
throw new RuntimeException(e);
}
// Add to the returned object
properties.add(newBeanInstance);
}
return new ApplicationConfiguration(properties);
}
}