View Javadoc
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   * <p>並び順は、コンストラクタで指定されたものに並び変えられる。</p>
36   * 
37   * <p>繰り返しのアノテーション{@link Repeatable}が付与されたアノテーションの場合、
38   *    取得する際には複数のアノテーションがまとめたアノテーションとして別に取得されるため分解する。</p>
39   * 
40   * <p>合成のアノテーション{@link CsvComposition}が付与されたアノテーションの場合、
41   *    付与されているアノテーションに分解する。
42   *    その際に、定義されている属性を元に、付与されているアノテーションの属性を上書きする。</p>
43   *
44   * @since 2.0
45   * @author T.TSUCHIE
46   *
47   */
48  public class AnnotationExpander {
49      
50      private final Comparator<ExpandedAnnotation> comparator;
51      
52      /**
53       * アノテーションの並び順を指定するコンストラクタ。
54       * 
55       * @param annotationComparator アノテーションの並び順を指定するためのComparator。
56       * @throws NullPointerException {@literal annotationComparator == null.}
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       * @param targetAnnos  展開対象のアノテーション
74       * @return 展開されたアノテーション
75       * @throws NullPointerException {@literal targetAnnos == null.}
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       * @param targetAnno 展開対象のアノテーション
94       * @return 展開されたアノテーション
95       * @throws NullPointerException {@literal targetAnno == null.}
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      * <p>属性「value」に、繰り返しのアノテーション{@link Repeatable}が付与されている
155      *    アノテーションの配列を保持しているかどうかで判定する。</p>
156      * @param targetAnno
157      * @return
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             // @Repetableアノテーションが付与されているかどうか
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      * <p>メタアノテーション{@link CsvComposition}が付与されているかどうかで判定する。</p>
191      * @param targetAnno
192      * @return
193      */
194     private boolean isComposed(final Annotation targetAnno) {
195         
196         return targetAnno.annotationType().getAnnotation(CsvComposition.class) != null;
197         
198     }
199     
200     /**
201      * 合成したアノテーションの属性を、構成されるアノテーションに反映する。
202      * @param compositionAnno 
203      * @param nestedAnno
204      * @return
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      * <p>ただし、実際には合成のアノテーションに属性がなければ上書きはされないので、あくまで上書き可能かの判定しか行わない。</p>
273      * <p>条件は以下の通り。</p>
274      * <ul>
275      *  <li>メタアノテーション{@link CsvConstraint}、{@link CsvConversion}が付与されているアノテーションである。</li>
276      *  <li>アノテーション{@link CsvRequire}である。</li>
277      *  <li>フォーマット用のアノテーションである。パッケージ名から判定する。</li>
278      * </ul>
279      * @param targetAnno 判定対象のアノテーション
280      * @return trueの場合、上書き対象。
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      * @param compositionAnno 合成のアノテーション
304      * @param targetAnno 上書き対象のアノテーション
305      * @return
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         // @CsvOvrerideAttributeが付与されたメソッド名
315         final Set<String> overrideMethodNames = new HashSet<>();
316         
317         // @CsvOverridesAttributeが付与された属性の組み立て
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                 // @CsvOverridesAttributeが付与されていない場合
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         // message 属性の取得。
381         // 既に取得していたり、@CsvOverridesAttributeが付与されている場合はスキップする。
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         // groups 属性の取得。
392         // 既に取得していたり、@CsvOverridesAttributeが付与されている場合はスキップする。
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         // cases 属性の取得。
403         // 既に取得していたり、@CsvOverridesAttributeが付与されている場合はスキップする。
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 }