@@ -21,6 +21,8 @@ import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
@@ -29,6 +31,11 @@ import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.function.Supplier;
import org.apache.tools.ant.taskdefs.PreSetDef;
import org.apache.tools.ant.types.EnumeratedAttribute;
@@ -1031,19 +1038,71 @@ public final class IntrospectionHelper {
private AttributeSetter createAttributeSetter(final Method m,
final Class<?> arg,
final String attrName) {
if (Optional.class.equals(arg)) {
Type gpt = m.getGenericParameterTypes()[0];
Class<?> payload = Object.class;
if (gpt instanceof ParameterizedType ) {
Type ata = ((ParameterizedType) gpt).getActualTypeArguments()[0];
if (ata instanceof Class<?>) {
payload = (Class<?>) ata;
} else if (ata instanceof ParameterizedType) {
payload = (Class<?>) ((ParameterizedType) ata).getRawType();
}
}
final AttributeSetter wrapped = createAttributeSetter(m, payload, attrName);
return new AttributeSetter(m, arg, Optional::empty) {
@Override
Optional<?> toTargetType(Project project, String value)
throws BuildException {
return Optional.ofNullable(wrapped.toTargetType(project, value));
}
};
}
if (OptionalInt.class.equals(arg)) {
final AttributeSetter wrapped = createAttributeSetter(m, Integer.class, attrName);
return new AttributeSetter(m, arg, OptionalInt::empty) {
@Override
OptionalInt toTargetType(Project project, String value)
throws BuildException {
return Optional.ofNullable((Integer) wrapped.toTargetType(project, value))
.map(OptionalInt::of).orElseGet(OptionalInt::empty);
}
};
}
if (OptionalLong.class.equals(arg)) {
final AttributeSetter wrapped = createAttributeSetter(m, Long.class, attrName);
return new AttributeSetter(m, arg, OptionalLong::empty) {
@Override
OptionalLong toTargetType(Project project, String value)
throws BuildException {
return Optional.ofNullable((Long) wrapped.toTargetType(project, value))
.map(OptionalLong::of).orElseGet(OptionalLong::empty);
}
};
}
if (OptionalDouble.class.equals(arg)) {
final AttributeSetter wrapped = createAttributeSetter(m, Double.class, attrName);
return new AttributeSetter(m, arg, OptionalDouble::empty) {
@Override
Object toTargetType(Project project, String value)
throws BuildException {
return Optional.ofNullable((Double) wrapped.toTargetType(project, value))
.map(OptionalDouble::of).orElseGet(OptionalDouble::empty);
}
};
}
// use wrappers for primitive classes, e.g. int and
// Integer are treated identically
final Class<?> reflectedArg = PRIMITIVE_TYPE_MAP.getOrDefault(arg, arg);
// Object.class - it gets handled differently by AttributeSetter
if (java.lang.Object.class == reflectedArg) {
if (Object.class == reflectedArg) {
return new AttributeSetter(m, arg) {
@Override
public void set(final Project p, final Object parent, final String value)
throws InvocationTargetException,
IllegalAccessException {
Object toTargetType(Project project, String value)
throws BuildException {
throw new BuildException(
"Internal ant problem - this should not get called");
"Internal ant problem - this should not get called");
}
};
}
@@ -1051,58 +1110,53 @@ public final class IntrospectionHelper {
if (String.class.equals(reflectedArg)) {
return new AttributeSetter(m, arg) {
@Override
public void set(final Project p, final Object parent, final String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent, (Object[]) new String[] {value});
public String toTargetType(Project project, String t) {
return t;
}
};
}
// char and Character get special treatment - take the first character
if (java.lang. Character.class.equals(reflectedArg)) {
if (Character.class.equals(reflectedArg)) {
return new AttributeSetter(m, arg) {
@Override
public void set(final Project p, final Object parent, final String value)
throws InvocationTargetException, IllegalAccessException {
public Character toTargetType(Project project, String value) {
if (value.isEmpty()) {
throw new BuildException("The value \"\" is not a "
+ "legal value for attribute \"" + attrName + "\"");
}
m.invoke(parent, (Object[]) new Character[] {value.charAt(0)} );
return Character.valueOf(value.charAt(0) );
}
};
}
// boolean and Boolean get special treatment because we have a nice method in Project
if (java.lang. Boolean.class.equals(reflectedArg)) {
if (Boolean.class.equals(reflectedArg)) {
return new AttributeSetter(m, arg) {
@Override
public void set(final Project p, final Object parent, final String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent, (Object[]) new Boolean[] {
Project.toBoolean(value) ? Boolean.TRUE : Boolean.FALSE });
public Boolean toTargetType(Project project, String value) {
return Boolean.valueOf(Project.toBoolean(value));
}
};
}
// Class doesn't have a String constructor but a decent factory method
if (java.lang. Class.class.equals(reflectedArg)) {
if (Class.class.equals(reflectedArg)) {
return new AttributeSetter(m, arg) {
@Override
public void set(final Project p, final Object parent, final String value)
throws InvocationTargetException, IllegalAccessException, BuildException {
public Class<?> toTargetType(Project project, String value) {
try {
m.invoke(parent, Class.forName(value) );
} catch (final ClassNotFoundException c e) {
throw new BuildException(c e);
return Class.forName(value );
} catch (ClassNotFoundException e) {
throw new BuildException(e);
}
}
};
}
// resolve relative paths through Project
if (java.io. File.class.equals(reflectedArg)) {
if (File.class.equals(reflectedArg)) {
return new AttributeSetter(m, arg) {
@Override
public void set(final Project p, final Object parent, final String value)
throws InvocationTargetException, IllegalAccess Exception {
m.invoke(parent, p.resolveFile(value) );
Object toTargetType(Project project, String value)
throws Build Exception {
return project.resolveFile(value );
}
};
}
@@ -1110,20 +1164,19 @@ public final class IntrospectionHelper {
if (java.nio.file.Path.class.equals(reflectedArg)) {
return new AttributeSetter(m, arg) {
@Override
public void set(final Project p, final Object parent, final String value)
throws InvocationTargetException, IllegalAccess Exception {
m.invoke(parent, p.resolveFile(value).toPath() );
Object toTargetType(Project project, String value)
throws Build Exception {
return project.resolveFile(value).toPath( );
}
};
}
// resolve Resources/FileProviders as FileResources relative to Project:
if (Resource.class.equals(reflectedArg) || FileProvider.class.equals(reflectedArg)) {
return new AttributeSetter(m, arg) {
@Override
void set(final Project p, final Object parent, final String value)
throws InvocationTargetException, IllegalAccessException, BuildException {
m.invoke(parent, new FileResource(p, p.resolveFile(value) ));
Object toTargetType(Project project, String value)
throws BuildException {
return new FileResource(project.resolveFile(value ));
}
};
}
@@ -1131,38 +1184,34 @@ public final class IntrospectionHelper {
if (EnumeratedAttribute.class.isAssignableFrom(reflectedArg)) {
return new AttributeSetter(m, arg) {
@Override
public void set(final Project p, final Object parent, final String value)
throws InvocationTargetException, IllegalAccessException, BuildException {
public EnumeratedAttribute toTargetType(Project project, String value) {
EnumeratedAttribute ea;
try {
final EnumeratedAttribute ea =
(EnumeratedAttribute) reflectedArg.getDeclaredConstructor().newInstance();
ea.setValue(value);
m.invoke(parent, ea);
} catch (final InstantiationException | NoSuchMethodException ie) {
throw new BuildException(ie);
ea = (EnumeratedAttribute) reflectedArg.getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
throw BuildException.of(e);
}
ea.setValue(value);
return ea;
}
};
}
final AttributeSetter setter = getEnumSetter(reflectedArg, m, arg);
if (setter != null) {
return setter;
}
if (java.lang.Long.class.equals(reflectedArg)) {
if (Long.class.equals(reflectedArg)) {
return new AttributeSetter(m, arg) {
@Override
public void set(final Project p, final Object parent, final String value)
throws InvocationTargetException, IllegalAccessException, BuildException {
public Long toTargetType(Project project, String value) {
try {
m.invoke(parent, StringUtils.parseHumanSizes(value));
return Long.valueOf( StringUtils.parseHumanSizes(value));
} catch (final NumberFormatException e) {
throw new BuildException("Can't assign non-numeric"
+ " value '" + value + "' to"
+ " attribute " + attrName);
} catch (final InvocationTargetException | IllegalAccessException e) {
throw e;
throw new BuildException(
String.format("Can't assign non-numeric value '%s' to attribute %s",
value, attrName));
} catch (final Exception e) {
throw new BuildException(e);
}
@@ -1194,30 +1243,32 @@ public final class IntrospectionHelper {
return new AttributeSetter(m, arg) {
@Override
public void set(final Project p, final Object parent, final String value)
throws InvocationTargetException, IllegalAccessException, BuildException {
public Object toTargetType(Project project, String value) {
try {
final Object[] args = finalIncludeProject
? new Object[] {p, value} : new Object[] {value};
? new Object[] {project , value} : new Object[] {value};
final Object attribute = finalConstructor.newInstance(args);
if (p != null) {
p.setProjectReference(attribute);
if (project != null) {
project .setProjectReference(attribute);
}
m.invoke(parent, attribute);
} catch (final InvocationTargetException e) {
final Throwable cause = e.getCause();
if (cause instanceof IllegalArgumentException) {
throw new BuildException("Can't assign value '" + value
+ "' to attribute " + attrName
+ ", reason: "
+ cause.getClass()
+ " with message '"
+ cause.getMessage() + "'");
return attribute;
} catch (final Exception e) {
Throwable thw = e;
while (true) {
if (thw instanceof IllegalArgumentException) {
throw new BuildException(String.format(
"Can't convert value '%s' to type %s, reason: %s with message '%s'",
value, reflectedArg, thw.getClass(), thw.getMessage()));
}
final Throwable _thw = thw;
Optional<Throwable> next = Optional.of(thw).map(Throwable::getCause).filter(t -> t != _thw);
if (!next.isPresent()) {
break;
}
thw = next.get();
}
throw e;
} catch (final InstantiationException ie) {
throw new BuildException(ie);
throw BuildException.of(e);
}
}
};
@@ -1228,22 +1279,18 @@ public final class IntrospectionHelper {
if (reflectedArg.isEnum()) {
return new AttributeSetter(m, arg) {
@Override
public void set(final Project p, final Object parent, final String value)
throws InvocationTargetException, IllegalAccessException,
BuildException {
Enum<?> setValue;
public Enum<?> toTargetType(Project project, String value) {
try {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Enum<?> enumValue = Enum.valueOf((Class<? extends Enum>) reflectedArg,
value);
setValue = enumValue ;
final Enum<?> result =
Enum.valueOf((Class<? extends Enum>) reflectedArg, value);
return result ;
} catch (final IllegalArgumentException e) {
// there is a specific logic here for the value
// being out of the allowed set of enumerations.
throw new BuildException("'" + value + "' is not a permitted value for "
+ reflectedArg.getName());
}
m.invoke(parent, setValue);
}
};
}
@@ -1480,32 +1527,44 @@ public final class IntrospectionHelper {
private abstract static class AttributeSetter {
private final Method method; // the method called to set the attribute
private final Class<?> type;
private final Supplier<?> supplyWhenNull;
protected AttributeSetter(final Method m, final Class<?> type) {
method = m;
this(m, type, () -> null);
}
protected AttributeSetter(final Method method, final Class<?> type,
final Supplier<?> supplyWhenNull) {
this.method = method;
this.type = type;
this.supplyWhenNull = supplyWhenNull;
}
void setObject(final Project p, final Object parent, final Object value)
final void setObject(final Project p, final Object parent, Object value)
throws InvocationTargetException, IllegalAccessException, BuildException {
if (type != null) {
Class<?> useType = type;
if (type.isPrimitive()) {
if (value == null) {
throw new BuildException(
"Attempt to set primitive "
+ getPropertyName(method.getName(), "set")
+ " to null on " + parent);
throw new BuildException("Attempt to set primitive %s to null on %s",
getPropertyName(method.getName(), "set"), parent);
}
useType = PRIMITIVE_TYPE_MAP.get(type);
}
if (value == null ) {
value = supplyWhenNull.get();
}
if (value == null || useType.isInstance(value)) {
method.invoke(parent, value);
return;
}
}
set(p, parent, value.toString());
method.invoke(parent, toTargetType(p, value.toString()));
}
Object toTargetType(Project project, String value) {
throw new UnsupportedOperationException();
}
abstract void set(Project p, Object parent, String value)
throws InvocationTargetException, IllegalAccessException, BuildException;
}
/**