1 package com.github.mygreen.supercsv.builder;
2
3 import java.lang.annotation.Annotation;
4 import java.lang.annotation.Repeatable;
5 import java.lang.reflect.InvocationHandler;
6 import java.lang.reflect.InvocationTargetException;
7 import java.lang.reflect.Method;
8 import java.lang.reflect.Proxy;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.Collections;
12 import java.util.Comparator;
13 import java.util.HashMap;
14 import java.util.HashSet;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Objects;
18 import java.util.Optional;
19 import java.util.Set;
20 import java.util.stream.Collectors;
21
22 import org.supercsv.exception.SuperCsvReflectionException;
23
24 import com.github.mygreen.supercsv.annotation.CsvComposition;
25 import com.github.mygreen.supercsv.annotation.CsvOverridesAttribute;
26 import com.github.mygreen.supercsv.annotation.constraint.CsvConstraint;
27 import com.github.mygreen.supercsv.annotation.constraint.CsvRequire;
28 import com.github.mygreen.supercsv.annotation.conversion.CsvConversion;
29 import com.github.mygreen.supercsv.exception.SuperCsvInvalidAnnotationException;
30 import com.github.mygreen.supercsv.localization.MessageBuilder;
31 import com.github.mygreen.supercsv.util.Utils;
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public class AnnotationExpander {
49
50 private final Comparator<ExpandedAnnotation> comparator;
51
52
53
54
55
56
57
58 public AnnotationExpander(final Comparator<Annotation> annotationComparator) {
59 Objects.requireNonNull(annotationComparator);
60
61 this.comparator = new Comparator<ExpandedAnnotation>() {
62
63 @Override
64 public int compare(final ExpandedAnnotationr/ExpandedAnnotation.html#ExpandedAnnotation">ExpandedAnnotation o1, final ExpandedAnnotation o2) {
65 return annotationComparator.compare(o1.getOriginal(), o2.getOriginal());
66 }
67
68 };
69 }
70
71
72
73
74
75
76
77 public List<ExpandedAnnotation> expand(final Annotation[] targetAnnos) {
78 Objects.requireNonNull(targetAnnos);
79
80 final List<ExpandedAnnotation> expanedList = new ArrayList<>();
81 for(Annotation targetAnno : targetAnnos) {
82 expanedList.addAll(expand(targetAnno));
83 }
84
85 Collections.sort(expanedList, comparator);
86
87 return expanedList;
88
89 }
90
91
92
93
94
95
96
97 public List<ExpandedAnnotation> expand(final Annotation targetAnno) {
98 Objects.requireNonNull(targetAnno);
99
100 final List<ExpandedAnnotation> expandedList = new ArrayList<>();
101
102 if(isRepeated(targetAnno)) {
103
104 try {
105 final Method method = targetAnno.getClass().getMethod("value");
106 final Annotation[] annos = (Annotation[]) method.invoke(targetAnno);
107
108 int index = 0;
109 for(Annotation anno : annos) {
110 final List<ExpandedAnnotation> repeatedAnnos = expand(anno);
111 for(ExpandedAnnotation repeatedAnno : repeatedAnnos) {
112 repeatedAnno.setIndex(index);
113 }
114
115 expandedList.addAll(repeatedAnnos);
116 index++;
117 }
118
119 } catch (Exception e) {
120 throw new RuntimeException("fail get repeated value attribute.", e);
121 }
122
123 } else if(isComposed(targetAnno)) {
124 final ExpandedAnnotationdAnnotation.html#ExpandedAnnotation">ExpandedAnnotation composedAnno = new ExpandedAnnotation(targetAnno, true);
125
126
127 final List<Annotation> childAnnos = Arrays.asList(targetAnno.annotationType().getAnnotations());
128 for(Annotation anno : childAnnos) {
129
130 final List<ExpandedAnnotation> nestedAnnos = expand(anno).stream()
131 .map(nestedAnno -> overrideAttribute(targetAnno, nestedAnno))
132 .collect(Collectors.toList());
133
134 composedAnno.addChilds(nestedAnnos);
135
136 }
137
138 Collections.sort(composedAnno.getChilds(), comparator);
139 expandedList.add(composedAnno);
140
141 } else {
142
143 expandedList.add(new ExpandedAnnotation(targetAnno, false));
144
145 }
146
147 Collections.sort(expandedList, comparator);
148 return expandedList;
149
150 }
151
152
153
154
155
156
157
158
159 private boolean isRepeated(final Annotation targetAnno) {
160
161 try {
162 final Method method = targetAnno.getClass().getMethod("value");
163
164
165 final Class<?> returnType = method.getReturnType();
166 if(!(returnType.isArray() && Annotation.class.isAssignableFrom(returnType.getComponentType()))) {
167 return false;
168 }
169
170 final Annotation[] annos = (Annotation[]) method.invoke(targetAnno);
171 if(annos.length == 0) {
172 return false;
173 }
174
175
176 if(annos[0].annotationType().getAnnotation(Repeatable.class) != null) {
177 return true;
178 }
179
180 } catch (Exception e) {
181
182 }
183
184 return false;
185
186 }
187
188
189
190
191
192
193
194 private boolean isComposed(final Annotation targetAnno) {
195
196 return targetAnno.annotationType().getAnnotation(CsvComposition.class) != null;
197
198 }
199
200
201
202
203
204
205
206 private ExpandedAnnotationExpandedAnnotatione(final Annotation compositionAnno, final ExpandedAnnotation nestedAnno) {
207
208 final Annotation originalAnno = nestedAnno.getOriginal();
209 if(!isOverridableAnnotation(originalAnno)) {
210 return nestedAnno;
211 }
212
213
214 final Map<String, Object> overrideAttrs = buildOverrideAttribute(compositionAnno, nestedAnno);
215 if(overrideAttrs.isEmpty()) {
216 return nestedAnno;
217 }
218
219
220 final Class<?> annotationClass = originalAnno.annotationType();
221 final Map<String, Object> defaultValues = new HashMap<>();
222 for(Method method : annotationClass.getMethods()) {
223 try {
224 method.setAccessible(true);
225 if(method.getParameterCount() == 0) {
226 final Object value = method.invoke(originalAnno);
227 defaultValues.put(method.getName(), value);
228
229 } else {
230 final Object value = method.getDefaultValue();
231 if(value != null) {
232 defaultValues.put(method.getName(), value);
233 }
234
235 }
236
237 } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
238 throw new RuntimeException(String.format("fail get annotation attribute %s#%s.", annotationClass.getName(), method.getName()), e);
239 }
240 }
241
242
243 final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
244 final Object annoObj = Proxy.newProxyInstance(classLoader, new Class[]{annotationClass},
245 new InvocationHandler() {
246
247 @Override
248 public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
249 final String name = method.getName();
250 if (name.equals("annotationType")) {
251 return annotationClass;
252
253 } else if(overrideAttrs.containsKey(name)){
254 return overrideAttrs.get(name);
255
256 } else {
257 return defaultValues.get(name);
258 }
259 }
260 });
261
262
263 final ExpandedAnnotationnnotation.html#ExpandedAnnotation">ExpandedAnnotation propagatedAnno = new ExpandedAnnotation((Annotation)annoObj, nestedAnno.isComposed());
264 propagatedAnno.setIndex(nestedAnno.getIndex());
265 propagatedAnno.addChilds(nestedAnno.getChilds());
266
267 return propagatedAnno;
268 }
269
270
271
272
273
274
275
276
277
278
279
280
281
282 private boolean isOverridableAnnotation(final Annotation targetAnno) {
283
284 final Class<?> annoType = targetAnno.annotationType();
285 if(annoType.getAnnotation(CsvConstraint.class) != null) {
286 return true;
287 }
288
289 if(annoType.getAnnotation(CsvConversion.class) != null) {
290 return true;
291 }
292
293 if(annoType.getTypeName().startsWith("com.github.mygreen.supercsv.annotation.format")) {
294 return true;
295 }
296
297 return false;
298
299 }
300
301
302
303
304
305
306
307 @SuppressWarnings("rawtypes")
308 private Map<String, Object> buildOverrideAttribute(final Annotation compositionAnno, final ExpandedAnnotation targetAnno) {
309
310 final Annotation originalAnno = targetAnno.getOriginal();
311
312 final Map<String, Object> overrideAttrs = new HashMap<>();
313
314
315 final Set<String> overrideMethodNames = new HashSet<>();
316
317
318 for(Method compositionMethod : compositionAnno.annotationType().getMethods()) {
319
320 final List<CsvOverridesAttribute> annoList = new ArrayList<>();
321 final CsvOverridesAttribute overrideAttrAnno = compositionMethod.getAnnotation(CsvOverridesAttribute.class);
322 if(overrideAttrAnno != null) {
323 annoList.add(overrideAttrAnno);
324 }
325
326
327 final CsvOverridesAttribute.List overrideAttrAnnoList = compositionMethod.getAnnotation(CsvOverridesAttribute.List.class);
328 if(overrideAttrAnnoList != null) {
329 annoList.addAll(Arrays.asList(overrideAttrAnnoList.value()));
330 }
331
332 if(annoList.isEmpty()) {
333
334 continue;
335 }
336
337 overrideMethodNames.add(compositionMethod.getName());
338
339 for(CsvOverridesAttribute anno : annoList) {
340
341 if(!anno.annotation().equals(originalAnno.annotationType())) {
342 continue;
343 }
344
345
346 if(anno.index() >= 0 && anno.index() != targetAnno.getIndex()) {
347 continue;
348 }
349
350 final String attrName = anno.name().isEmpty() ? compositionMethod.getName() : anno.name();
351
352 if(!Utils.hasAnnotationAttribute(originalAnno, attrName, compositionMethod.getReturnType())) {
353
354 throw new SuperCsvInvalidAnnotationException(originalAnno, MessageBuilder.create("anno.CsvOverridesAnnotation.notFoundAttr")
355 .varWithAnno("compositionAnno", compositionAnno.annotationType())
356 .varWithAnno("overrideAnno", originalAnno.annotationType())
357 .varWithClass("attrType", compositionMethod.getReturnType())
358 .var("attrName", attrName)
359 .format());
360
361 }
362
363
364 try {
365 Object attrValue = compositionMethod.invoke(compositionAnno);
366 overrideAttrs.put(attrName, attrValue);
367
368 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
369 throw new SuperCsvReflectionException(MessageBuilder.create("anno.CsvOverridesAnnotation.failGetAttr")
370 .varWithAnno("compositionAnno", compositionAnno.annotationType())
371 .var("attrName", attrName)
372 .format(),
373 e);
374 }
375
376 }
377
378 }
379
380
381
382 if(!overrideAttrs.containsKey("message") && !overrideMethodNames.contains("message")
383 && Utils.hasAnnotationAttribute(originalAnno, "message", String.class)) {
384
385 final Optional<String> messageAttr = Utils.getAnnotationAttribute(compositionAnno, "message", String.class);
386 if(messageAttr.isPresent() && Utils.isNotEmpty(messageAttr.get())) {
387 overrideAttrs.put("message", messageAttr.get());
388 }
389 }
390
391
392
393 if(!overrideAttrs.containsKey("groups") && !overrideMethodNames.contains("groups")
394 && Utils.hasAnnotationAttribute(originalAnno, "groups", Class[].class)) {
395
396 final Optional<Class[]> groupsAttr = Utils.getAnnotationAttribute(compositionAnno, "groups", Class[].class);
397 if(groupsAttr.isPresent() && Utils.isNotEmpty(groupsAttr.get())) {
398 overrideAttrs.put("groups", groupsAttr.get());
399 }
400 }
401
402
403
404 if(!overrideAttrs.containsKey("cases") && !overrideMethodNames.contains("cases")
405 && Utils.hasAnnotationAttribute(originalAnno, "cases", BuildCase[].class)) {
406
407 final Optional<BuildCase[]> casesAttr = Utils.getAnnotationAttribute(compositionAnno, "cases", BuildCase[].class);
408 if(casesAttr.isPresent() && Utils.isNotEmpty(casesAttr.get())) {
409 overrideAttrs.put("cases", casesAttr.get());
410 }
411 }
412
413 return overrideAttrs;
414 }
415
416 }