InvocationParameter.java
/*
* Copyright 2022 the original author or authors.
*
* Licensed 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
*
* http://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.bremersee.apiclient.webflux;
import static java.util.Objects.nonNull;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
import static org.springframework.util.ObjectUtils.isArray;
import static org.springframework.util.ObjectUtils.isEmpty;
import static org.springframework.util.ObjectUtils.toObjectArray;
import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* The invocation parameter.
*
* @author Christian Bremer
*/
@SuppressWarnings("SameNameButDifferent")
@Getter
@EqualsAndHashCode(callSuper = true)
public class InvocationParameter extends Invocation {
private final Parameter parameter;
private final Object value;
private final int index;
/**
* Instantiates a new invocation parameter.
*
* @param invocation the invocation
* @param parameter the parameter
* @param value the value
* @param index the index
*/
public InvocationParameter(Invocation invocation, Parameter parameter, Object value, int index) {
super(invocation.getTargetClass(), invocation.getMethod(), invocation.getArgs());
Assert.notNull(parameter, "Parameter must be present.");
Assert.isTrue(
index >= 0 && index < invocation.getMethod().getParameters().length,
String.format("Illegal index [%s].", index));
this.parameter = parameter;
this.value = value;
this.index = index;
}
/**
* Gets parameter name.
*
* @return the parameter name
*/
public String getParameterName() {
try {
String[] names = new DefaultParameterNameDiscoverer().getParameterNames(getMethod());
if (nonNull(names) && index >= 0 && index < names.length && !isEmpty(names[index])) {
return names[index];
}
} catch (Exception ignored) {
// ignored
}
try {
String[] names = new LocalVariableTableParameterNameDiscoverer()
.getParameterNames(getMethod());
if (nonNull(names) && index >= 0 && index < names.length && !isEmpty(names[index])) {
return names[index];
}
} catch (Exception ignored) {
// ignored
}
String name = parameter.getName();
return isEmpty(name) ? "arg" + index : name;
}
/**
* Has none parameter annotation.
*
* @param annotationTypes the annotation types
* @return the boolean
*/
public boolean hasNoneParameterAnnotation(Set<Class<? extends Annotation>> annotationTypes) {
if (isEmpty(annotationTypes)) {
return true;
}
return annotationTypes.stream().noneMatch(this::hasParameterAnnotation);
}
/**
* Has parameter annotation.
*
* @param annotationType the annotation type
* @return the boolean
*/
public boolean hasParameterAnnotation(Class<? extends Annotation> annotationType) {
return findParameterAnnotation(annotationType).isPresent();
}
/**
* Find parameter annotation.
*
* @param <A> the type parameter
* @param annotationType the annotation type
* @return the optional
*/
public <A extends Annotation> Optional<A> findParameterAnnotation(Class<A> annotationType) {
return Optional.ofNullable(findAnnotation(parameter, annotationType));
}
private <A extends Annotation> String getKey(
A annotation,
Function<A, String> keyExtractor) {
return Optional.ofNullable(annotation)
.map(keyExtractor)
.filter(name -> !name.isBlank())
.orElseGet(this::getParameterName);
}
/**
* To multi value map.
*
* @param <E> the type parameter
* @param <A> the type parameter
* @param annotationType the annotation type
* @param keyExtractor the key extractor
* @param valueMapper the value mapper
* @return the multi value map
*/
public <E, A extends Annotation> MultiValueMap<String, E> toMultiValueMap(
Class<A> annotationType,
Function<A, String> keyExtractor,
Function<Object, E> valueMapper) {
return findParameterAnnotation(annotationType)
.map(annotation -> {
MultiValueMap<String, E> map = new LinkedMultiValueMap<>();
Object value = getValue();
if (value instanceof Map<?, ?>) {
map.putAll(toMultiValueMap((Map<?, ?>) value, valueMapper));
} else {
String key = getKey(
annotation,
keyExtractor);
map.put(key, toList(value, valueMapper));
}
return map;
})
.orElseGet(LinkedMultiValueMap::new);
}
private <E> MultiValueMap<String, E> toMultiValueMap(
Map<?, ?> map, Function<Object, E> valueMapper) {
MultiValueMap<String, E> multiValueMap = new LinkedMultiValueMap<>();
if (!isEmpty(map)) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
String key = String.valueOf(entry.getKey());
List<E> value = toList(entry.getValue(), valueMapper);
if (!isEmpty(value)) {
multiValueMap.addAll(key, value);
}
}
}
return multiValueMap;
}
private <E> List<E> toList(Object value, Function<Object, E> valueMapper) {
List<E> list = new ArrayList<>();
if (isEmpty(value)) {
return list;
}
if (isArray(value)) {
return Arrays.stream(toObjectArray(value))
.filter(Objects::nonNull)
.map(valueMapper)
.collect(Collectors.toList());
}
if (value instanceof Collection<?>) {
return ((Collection<?>) value).stream()
.filter(Objects::nonNull)
.map(valueMapper)
.collect(Collectors.toList());
}
list.add(valueMapper.apply(value));
return list;
}
@Override
public String toString() {
return "InvocationParameter{"
+ "targetClass=" + getTargetClass().getName()
+ ", method=" + getMethod().getName()
+ ", parameter=" + getParameterName()
+ ", value=" + value
+ ", index=" + index
+ '}';
}
}