blob: db68725db64047d2f18297ff3de3873f7fc119d2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* https://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.apache.ant.s3;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.ClassUtils.Interfaces;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DynamicAttributeNS;
import org.apache.tools.ant.DynamicElementNS;
import org.apache.tools.ant.Project;
import software.amazon.awssdk.core.internal.http.loader.DefaultSdkHttpClientBuilder;
import software.amazon.awssdk.http.SdkHttpClient;
/**
* Support AWS SDK v2 fluent builder conventions.
*
* @param <T>
*/
public class Builder<T> extends S3DataType implements Consumer<T>, DynamicAttributeNS, DynamicElementNS {
private static class Parameter {
final Method mutator;
final Object value;
Parameter(Method mutator, Object value) {
this.mutator = mutator;
this.value = value;
}
}
private static final Map<Class<?>, Supplier<?>> SUPPLIERS;
private static final Set<BiPredicate<String, String>> NAME_COMPARISONS =
Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(String::equals, String::equalsIgnoreCase)));
static {
final Map<Class<?>, Supplier<?>> suppliers = new LinkedHashMap<>();
suppliers.put(SdkHttpClient.Builder.class, DefaultSdkHttpClientBuilder::new);
SUPPLIERS = Collections.unmodifiableMap(suppliers);
}
private static boolean isEquivalentTo(Class<?> c, Type t) {
if (ParameterizedType.class.isInstance(t)) {
t = ((ParameterizedType) t).getRawType();
}
return c.equals(t);
}
private static boolean isFluent(Method m) {
final Class<?> declaringClass = m.getDeclaringClass();
final Type genericReturnType = m.getGenericReturnType();
if (isEquivalentTo(declaringClass, genericReturnType)) {
return true;
}
if (TypeVariable.class.isInstance(genericReturnType)) {
final TypeVariable<?> var = (TypeVariable<?>) genericReturnType;
if (var.getGenericDeclaration().equals(declaringClass)) {
;
}
for (final Type bound : var.getBounds()) {
if (isEquivalentTo(declaringClass, bound)) {
return true;
}
}
}
return false;
}
private static boolean isFluentSdkMutator(Method m) {
return isFluent(m) && !Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 1;
}
private static Parameter parameter(Class<?> c, String name, String value) {
return searchMethods(c, name, m -> {
try {
return Optional.of(new Parameter(m, StringConversions.as(m.getParameterTypes()[0], value)));
} catch (IllegalArgumentException e) {
return Optional.empty();
}
});
}
private static Method element(Class<?> c, String name) {
return searchMethods(c, name, m -> {
return Optional.of(m).filter(o -> {
final Class<?> pt = m.getParameterTypes()[0];
return Consumer.class.equals(pt) || SUPPLIERS.containsKey(pt);
});
});
}
private static <R> R searchMethods(Class<?> c, String name, Function<Method, Optional<R>> fn) {
for (BiPredicate<String, String> nameComparison : NAME_COMPARISONS) {
for (Class<?> type : ClassUtils.hierarchy(c, Interfaces.INCLUDE)) {
for (Method m : type.getDeclaredMethods()) {
if (nameComparison.test(name, m.getName()) && isFluentSdkMutator(m)) {
final Optional<R> result = fn.apply(m);
if (result.isPresent()) {
return result.get();
}
}
}
}
}
throw new IllegalArgumentException(name);
}
private final Class<T> target;
private final Set<Parameter> parameters = new LinkedHashSet<>();
private final Map<Method, Builder<?>> elements = new LinkedHashMap<>();
/**
* Create a new {@link Builder} instance.
*
* @param target
* type
*/
public Builder(Class<T> target, Project project) {
super(project);
this.target = Objects.requireNonNull(target, "target");
}
/**
* {@inheritDoc}
*/
@Override
public void accept(T t) {
parameters.forEach(p -> {
try {
p.mutator.invoke(t, p.value);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
});
elements.forEach((m, b) -> {
final Class<?> pt = m.getParameterTypes()[0];
final Object arg;
if (Consumer.class.equals(pt)) {
arg = b;
} else {
arg = SUPPLIERS.get(pt).get();
@SuppressWarnings("unchecked")
final Consumer<Object> cmer = (Consumer<Object>) b;
cmer.accept(arg);
}
try {
m.invoke(t, arg);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void setDynamicAttribute(String uri, String localName, String qName, String value) throws BuildException {
parameters.add(parameter(target, localName, value));
}
/**
* {@inheritDoc}
*/
@Override
public Object createDynamicElement(String uri, String localName, String qName) throws BuildException {
final Method m = element(target, localName);
final Builder<?> result = new Builder<>(m.getParameterTypes()[0], getProject());
elements.put(m, result);
return result;
}
}