View Javadoc
1   package com.github.mygreen.supercsv.builder;
2   
3   import java.lang.annotation.Annotation;
4   import java.lang.reflect.Field;
5   import java.util.ArrayList;
6   import java.util.Comparator;
7   import java.util.List;
8   import java.util.Objects;
9   import java.util.Optional;
10  import java.util.stream.Collectors;
11  
12  import org.supercsv.exception.SuperCsvReflectionException;
13  
14  import com.github.mygreen.supercsv.annotation.DefaultGroup;
15  import com.github.mygreen.supercsv.util.Utils;
16  
17  /**
18   * フィールドに統一的にアクセスするためのクラス。
19   *
20   * @since 2.0
21   * @author T.TSUCHIE
22   *
23   */
24  public class FieldAccessor {
25      
26      /**
27       * フィールドの実体
28       */
29      private final Field field;
30      
31      /**
32       * フィールドの名称
33       */
34      private final String name;
35      
36      /**
37       * フィールドのタイプ
38       */
39      private final Class<?> type;
40      
41      /**
42       * アノテーションの一覧
43       */
44      private final List<ExpandedAnnotation> expandedAnnos = new ArrayList<>();
45      
46      /**
47       * フィールド情報を指定するコンストラクタ。
48       * @param field フィールド情報
49       * @param comparator アノテーションの順序を比較するためのコンパレータ。
50       * @throws NullPointerException {@literal field or comparator == null.}
51       */
52      public FieldAccessor(final Field field, final Comparator<Annotation> comparator) {
53          Objects.requireNonNull(field);
54          Objects.requireNonNull(comparator);
55          
56          field.setAccessible(true);
57          
58          this.field = field;
59          this.type = field.getType();
60          this.name = field.getName();
61          
62          final AnnotationExpanderotationExpander.html#AnnotationExpander">AnnotationExpander expander = new AnnotationExpander(comparator);
63          this.expandedAnnos.addAll(expander.expand(field.getAnnotations()));
64      }
65      
66      /**
67       * アノテーションのタイプを指定してアノテーションを取得します。
68       * <p>繰り返しのアノテーションの場合、初めに見つかったものを返します。</p>
69       * 
70       * @param <A> 取得対象のアノテーションのタイプ
71       * @param annoClass 取得対象のアノテーションのタイプ。
72       * @return 指定したアノテーションが見つからない場合は、空を返します。
73       * @throws NullPointerException {@literal annoClass is null.}
74       */
75      public <A extends Annotation> Optional<A> getAnnotation(final Class<A> annoClass) {
76          Objects.requireNonNull(annoClass, "annoClass should not be null.");
77          
78          return getAnnotationsByType(expandedAnnos, annoClass).stream()
79                  .findFirst();
80          
81      }
82      
83      /**
84       * アノテーションのタイプを指定してアノテーション一覧を取得します。
85       * <p>繰り返しのアノテーションの場合、初めに見つかったものを返します。</p>
86       * 
87       * @param <A> 取得対象のアノテーションのタイプ
88       * @param annoClass 取得対象のアノテーションのタイプ。
89       * @return 指定したアノテーションが見つからない場合は、空のリスト返します。
90       * @throws NullPointerException {@literal annoClass is null.}
91       */
92      public <A extends Annotation> List<A> getAnnotations(final Class<A> annoClass) {
93          Objects.requireNonNull(annoClass, "annoClass should not be null.");
94          
95          return getAnnotationsByType(expandedAnnos, annoClass);
96      }
97      
98      @SuppressWarnings({"unchecked"})
99      private static <A extends Annotation> List<A> getAnnotationsByType(
100             final List<ExpandedAnnotation> expanedAnnos, final Class<A> annoClass) {
101         
102         final List<A> list = new ArrayList<>();
103         
104         for(ExpandedAnnotation anno : expanedAnnos) {
105             
106             if(anno.isAnnotationType(annoClass)) {
107                 list.add((A)anno.getOriginal());
108             
109             } else if(anno.isComposed()) {
110                 
111                 list.addAll(getAnnotationsByType(anno.getChilds(), annoClass));
112                 
113             }
114             
115             
116         }
117         
118         return list;
119         
120     }
121     
122     /**
123      * 指定したアノテーションと持つかどうか。
124      * <p>繰り返し可能なアノテーションの場合、初めに見つかったものを返します。</p>
125      * 
126      * @param <A> 取得対象のアノテーションのタイプ
127      * @param annoClass 取得対象のアノテーションのタイプ。
128      * @return {@literal true}の場合、アノテーションを持ちます。
129      * @throws NullPointerException {@literal annoClass is null.}
130      */
131     public <A extends Annotation> boolean hasAnnotation(final Class<A> annoClass) {
132         return getAnnotation(annoClass).isPresent();
133     }
134     
135     /**
136      * アノテーションのタイプとグループを指定してアノテーションを取得します。
137      * 
138      * @param <A> 取得対象のアノテーションのタイプ
139      * @param annoClass 取得対象のアノテーションのタイプ。
140      * @param groups グループ(クラスタイプ)による絞り込み。属性groupsが存在する場合に、絞り込みます。
141      * @return 指定したアノテーションが見つからない場合は、サイズ0のリストを返します。
142      * @throws NullPointerException {@literal annoClass is null.}
143      */
144     public <A extends Annotation> List<A> getAnnotationsByGroup(final Class<A> annoClass, final Class<?>... groups) {
145         Objects.requireNonNull(annoClass, "annoClass should not be null.");
146         
147         return getAnnotations(annoClass).stream()
148                 .filter(anno -> hasGroups(anno, groups))
149                 .collect(Collectors.toList());
150         
151     }
152     
153     /**
154      * グループを指定して指定したアノテーションを持つかどうか判定します。
155      * 
156      * @param <A> 取得対象のアノテーションのタイプ
157      * @param annoClass 判定対象のアノテーションのグループ
158      * @param groups グループ(クラスタイプ)による絞り込み。属性groupsが存在する場合に、絞り込みます。
159      * @return 指定したアノテーションが見つからない場合は、サイズ0のリストを返します。
160      */
161     public <A extends Annotation> boolean hasAnnotationByGroup(final Class<A> annoClass, final Class<?>... groups) {
162         
163         return getAnnotationsByGroup(annoClass, groups).size() > 0;
164         
165     }
166     
167     
168     /**
169      * 付与されているアノテーションの一覧を取得する。
170      * 
171      * @param groups グループ(クラスタイプ)による絞り込み。属性groupsが存在する場合に、絞り込みます。
172      * @return 指定したアノテーションが見つからない場合は、サイズ0のリストを返します。
173      */
174     public List<Annotation> getAnnotationsByGroup(final Class<?>... groups) {
175         
176         return getAnnotations(expandedAnnos).stream()
177                 .filter(anno -> hasGroups(anno, groups))
178                 .collect(Collectors.toList());
179         
180     }
181     
182     @SuppressWarnings({"unchecked"})
183     private static <A extends Annotation> List<A> getAnnotations(final List<ExpandedAnnotation> expanedAnnos) {
184         
185         final List<A> list = new ArrayList<>();
186         
187         for(ExpandedAnnotation anno : expanedAnnos) {
188             if(anno.isComposed()) {
189                 list.addAll(getAnnotations(anno.getChilds()));
190                 
191             } else {
192                 list.add((A)anno.getOriginal());
193                 
194             }
195         }
196         
197         return list;
198         
199     }
200     
201     /**
202      * アノテーションの属性{@literal groups} が指定したグループと一致するか比較します。
203      * <p>groups属性を持たない場合は、必ずfalseを返します。</p>
204      * @param anno 検証対象のアノテーション。
205      * @param groups 比較対象のグループ情報。
206      * @return {@literal true}の場合、指定したグループを持ちます。
207      */
208     @SuppressWarnings("rawtypes")
209     private boolean hasGroups(final Annotation anno, final Class<?>... groups) {
210         
211         final Optional<Class[]> targetGroups = Utils.getAnnotationAttribute(anno, "groups", Class[].class);
212         
213         if(!targetGroups.isPresent()) {
214             // groups属性を持たない場合
215             return false;
216             
217         }
218         
219         if(groups.length == 0) {
220             if(targetGroups.get().length == 0) {
221                 // グループの指定がない場合は、デフォルトグループとして処理。
222                 return true;
223                 
224             } else {
225                 for(Class<?> targetGroup : targetGroups.get()) {
226                     if(targetGroup.equals(DefaultGroup.class)) {
227                         // デフォルトを直接指定している場合に、グループと一致。
228                         return true;
229                     }
230                 }
231             }
232             
233         } else {
234             // グループの指定がある場合
235             for(Class<?> group : groups) {
236                 
237                 if(group.equals(DefaultGroup.class) && targetGroups.get().length == 0) {
238                     // フィールド側にグループの指定がない場合は、デフォルトグループとして処理する。
239                     return true;
240                 }
241                 
242                 for(Class<?> targetGroup : targetGroups.get()) {
243                     // 一致するグループを持つか判定する。
244                     if(targetGroup.equals(group)) {
245                         return true;
246                     }
247                 }
248                 
249             }
250             
251         }
252         
253         return false;
254         
255     }
256     
257     /**
258      * フィールドの名称を取得する。
259      * @return フィールド名
260      */
261     public String getName() {
262         return name;
263     }
264     
265     /**
266      * クラス名付きのフィールド名称を取得する。
267      * @return {@literal <クラス名#フィールド名>}の形式
268      */
269     public String getNameWithClass() {
270         return getDeclaredClass().getName() + "#" + getName();
271     }
272     
273     /**
274      * フィールドのタイプを取得する。
275      * @return フィールドのクラスタイプ。
276      */
277     public Class<?> getType() {
278         return type;
279     }
280     
281     /**
282      * フィールドのタイプのクラス名称を取得する。
283      * @return パッケージ名付きのFQDNの形式。
284      */
285     public String getTypeName() {
286         return getType().getName();
287     }
288     
289     /**
290      * フィールドが定義されているクラス情報を取得する。
291      * 
292      * @see Field#getDeclaringClass()
293      * @return フィールドが定義されているクラス上方。
294      */
295     public Class<?> getDeclaredClass() {
296         return field.getDeclaringClass();
297     }
298     
299     /**
300      * フィールドのタイプが指定してたタイプかどうか。
301      * <p>{@link Class#isAssignableFrom(Class)}により比較を行う。
302      * @param clazz 比較対象のクラスタイプ。
303      * @return タイプが一致する場合、{@literal true}を返す。
304      */
305     public boolean isTypeOf(final Class<?> clazz) {
306         return clazz.isAssignableFrom(getType());
307     }
308     
309     /**
310      * フィールドの値を取得する。
311      * @param record レコードオブジェクト。
312      * @return フィールドの値。
313      * @throws IllegalArgumentException レコードのインスタンスがフィールドが定義されているクラスと異なる場合。
314      * @throws SuperCsvReflectionException フィールドの値の取得に失敗した場合。
315      */
316     public Object getValue(final Object record) {
317         Objects.requireNonNull(record);
318         
319         if(!getDeclaredClass().equals(record.getClass())) {
320             throw new IllegalArgumentException(String.format("not match record class type. expected=%s. actual=%s, ",
321                     type.getName(), record.getClass().getName()));
322         }
323         
324         try {
325             return field.get(record);
326         } catch (IllegalArgumentException | IllegalAccessException e) {
327             throw new SuperCsvReflectionException("fail get field value.", e);
328         }
329         
330     }
331 }