View Javadoc
1   /*
2    * Copyright 2022 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.apiclient.webflux;
18  
19  import static java.util.Objects.nonNull;
20  import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
21  import static org.springframework.util.ObjectUtils.isArray;
22  import static org.springframework.util.ObjectUtils.isEmpty;
23  import static org.springframework.util.ObjectUtils.toObjectArray;
24  
25  import java.lang.annotation.Annotation;
26  import java.lang.reflect.Parameter;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collection;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Objects;
33  import java.util.Optional;
34  import java.util.Set;
35  import java.util.function.Function;
36  import java.util.stream.Collectors;
37  import lombok.EqualsAndHashCode;
38  import lombok.Getter;
39  import org.springframework.core.DefaultParameterNameDiscoverer;
40  import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
41  import org.springframework.util.Assert;
42  import org.springframework.util.LinkedMultiValueMap;
43  import org.springframework.util.MultiValueMap;
44  
45  /**
46   * The invocation parameter.
47   *
48   * @author Christian Bremer
49   */
50  @SuppressWarnings("SameNameButDifferent")
51  @Getter
52  @EqualsAndHashCode(callSuper = true)
53  public class InvocationParameter extends Invocation {
54  
55    private final Parameter parameter;
56  
57    private final Object value;
58  
59    private final int index;
60  
61    /**
62     * Instantiates a new invocation parameter.
63     *
64     * @param invocation the invocation
65     * @param parameter the parameter
66     * @param value the value
67     * @param index the index
68     */
69    public InvocationParameter(Invocation invocation, Parameter parameter, Object value, int index) {
70      super(invocation.getTargetClass(), invocation.getMethod(), invocation.getArgs());
71      Assert.notNull(parameter, "Parameter must be present.");
72      Assert.isTrue(
73          index >= 0 && index < invocation.getMethod().getParameters().length,
74          String.format("Illegal index [%s].", index));
75      this.parameter = parameter;
76      this.value = value;
77      this.index = index;
78    }
79  
80    /**
81     * Gets parameter name.
82     *
83     * @return the parameter name
84     */
85    public String getParameterName() {
86      try {
87        String[] names = new DefaultParameterNameDiscoverer().getParameterNames(getMethod());
88        if (nonNull(names) && index >= 0 && index < names.length && !isEmpty(names[index])) {
89          return names[index];
90        }
91      } catch (Exception ignored) {
92        // ignored
93      }
94      try {
95        String[] names = new LocalVariableTableParameterNameDiscoverer()
96            .getParameterNames(getMethod());
97        if (nonNull(names) && index >= 0 && index < names.length && !isEmpty(names[index])) {
98          return names[index];
99        }
100     } catch (Exception ignored) {
101       // ignored
102     }
103     String name = parameter.getName();
104     return isEmpty(name) ? "arg" + index : name;
105   }
106 
107   /**
108    * Has none parameter annotation.
109    *
110    * @param annotationTypes the annotation types
111    * @return the boolean
112    */
113   public boolean hasNoneParameterAnnotation(Set<Class<? extends Annotation>> annotationTypes) {
114     if (isEmpty(annotationTypes)) {
115       return true;
116     }
117     return annotationTypes.stream().noneMatch(this::hasParameterAnnotation);
118   }
119 
120   /**
121    * Has parameter annotation.
122    *
123    * @param annotationType the annotation type
124    * @return the boolean
125    */
126   public boolean hasParameterAnnotation(Class<? extends Annotation> annotationType) {
127     return findParameterAnnotation(annotationType).isPresent();
128   }
129 
130   /**
131    * Find parameter annotation.
132    *
133    * @param <A> the type parameter
134    * @param annotationType the annotation type
135    * @return the optional
136    */
137   public <A extends Annotation> Optional<A> findParameterAnnotation(Class<A> annotationType) {
138     return Optional.ofNullable(findAnnotation(parameter, annotationType));
139   }
140 
141   private <A extends Annotation> String getKey(
142       A annotation,
143       Function<A, String> keyExtractor) {
144 
145     return Optional.ofNullable(annotation)
146         .map(keyExtractor)
147         .filter(name -> !name.isBlank())
148         .orElseGet(this::getParameterName);
149   }
150 
151   /**
152    * To multi value map.
153    *
154    * @param <E> the type parameter
155    * @param <A> the type parameter
156    * @param annotationType the annotation type
157    * @param keyExtractor the key extractor
158    * @param valueMapper the value mapper
159    * @return the multi value map
160    */
161   public <E, A extends Annotation> MultiValueMap<String, E> toMultiValueMap(
162       Class<A> annotationType,
163       Function<A, String> keyExtractor,
164       Function<Object, E> valueMapper) {
165 
166     return findParameterAnnotation(annotationType)
167         .map(annotation -> {
168           MultiValueMap<String, E> map = new LinkedMultiValueMap<>();
169           Object value = getValue();
170           if (value instanceof Map<?, ?>) {
171             map.putAll(toMultiValueMap((Map<?, ?>) value, valueMapper));
172           } else {
173             String key = getKey(
174                 annotation,
175                 keyExtractor);
176             map.put(key, toList(value, valueMapper));
177           }
178           return map;
179         })
180         .orElseGet(LinkedMultiValueMap::new);
181   }
182 
183   private <E> MultiValueMap<String, E> toMultiValueMap(
184       Map<?, ?> map, Function<Object, E> valueMapper) {
185     MultiValueMap<String, E> multiValueMap = new LinkedMultiValueMap<>();
186     if (!isEmpty(map)) {
187       for (Map.Entry<?, ?> entry : map.entrySet()) {
188         String key = String.valueOf(entry.getKey());
189         List<E> value = toList(entry.getValue(), valueMapper);
190         if (!isEmpty(value)) {
191           multiValueMap.addAll(key, value);
192         }
193       }
194     }
195     return multiValueMap;
196   }
197 
198   private <E> List<E> toList(Object value, Function<Object, E> valueMapper) {
199     List<E> list = new ArrayList<>();
200     if (isEmpty(value)) {
201       return list;
202     }
203     if (isArray(value)) {
204       return Arrays.stream(toObjectArray(value))
205           .filter(Objects::nonNull)
206           .map(valueMapper)
207           .collect(Collectors.toList());
208     }
209     if (value instanceof Collection<?>) {
210       return ((Collection<?>) value).stream()
211           .filter(Objects::nonNull)
212           .map(valueMapper)
213           .collect(Collectors.toList());
214     }
215     list.add(valueMapper.apply(value));
216     return list;
217   }
218 
219   @Override
220   public String toString() {
221     return "InvocationParameter{"
222         + "targetClass=" + getTargetClass().getName()
223         + ", method=" + getMethod().getName()
224         + ", parameter=" + getParameterName()
225         + ", value=" + value
226         + ", index=" + index
227         + '}';
228   }
229 }