FieldAccessor.java
package com.github.mygreen.supercsv.builder;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.supercsv.exception.SuperCsvReflectionException;
import com.github.mygreen.supercsv.annotation.DefaultGroup;
import com.github.mygreen.supercsv.util.Utils;
/**
* フィールドに統一的にアクセスするためのクラス。
*
* @since 2.0
* @author T.TSUCHIE
*
*/
public class FieldAccessor {
/**
* フィールドの実体
*/
private final Field field;
/**
* フィールドの名称
*/
private final String name;
/**
* フィールドのタイプ
*/
private final Class<?> type;
/**
* アノテーションの一覧
*/
private final List<ExpandedAnnotation> expandedAnnos = new ArrayList<>();
/**
* フィールド情報を指定するコンストラクタ。
* @param field フィールド情報
* @param comparator アノテーションの順序を比較するためのコンパレータ。
* @throws NullPointerException {@literal field or comparator == null.}
*/
public FieldAccessor(final Field field, final Comparator<Annotation> comparator) {
Objects.requireNonNull(field);
Objects.requireNonNull(comparator);
field.setAccessible(true);
this.field = field;
this.type = field.getType();
this.name = field.getName();
final AnnotationExpander expander = new AnnotationExpander(comparator);
this.expandedAnnos.addAll(expander.expand(field.getAnnotations()));
}
/**
* アノテーションのタイプを指定してアノテーションを取得します。
* <p>繰り返しのアノテーションの場合、初めに見つかったものを返します。</p>
*
* @param <A> 取得対象のアノテーションのタイプ
* @param annoClass 取得対象のアノテーションのタイプ。
* @return 指定したアノテーションが見つからない場合は、空を返します。
* @throws NullPointerException {@literal annoClass is null.}
*/
public <A extends Annotation> Optional<A> getAnnotation(final Class<A> annoClass) {
Objects.requireNonNull(annoClass, "annoClass should not be null.");
return getAnnotationsByType(expandedAnnos, annoClass).stream()
.findFirst();
}
/**
* アノテーションのタイプを指定してアノテーション一覧を取得します。
* <p>繰り返しのアノテーションの場合、初めに見つかったものを返します。</p>
*
* @param <A> 取得対象のアノテーションのタイプ
* @param annoClass 取得対象のアノテーションのタイプ。
* @return 指定したアノテーションが見つからない場合は、空のリスト返します。
* @throws NullPointerException {@literal annoClass is null.}
*/
public <A extends Annotation> List<A> getAnnotations(final Class<A> annoClass) {
Objects.requireNonNull(annoClass, "annoClass should not be null.");
return getAnnotationsByType(expandedAnnos, annoClass);
}
@SuppressWarnings({"unchecked"})
private static <A extends Annotation> List<A> getAnnotationsByType(
final List<ExpandedAnnotation> expanedAnnos, final Class<A> annoClass) {
final List<A> list = new ArrayList<>();
for(ExpandedAnnotation anno : expanedAnnos) {
if(anno.isAnnotationType(annoClass)) {
list.add((A)anno.getOriginal());
} else if(anno.isComposed()) {
list.addAll(getAnnotationsByType(anno.getChilds(), annoClass));
}
}
return list;
}
/**
* 指定したアノテーションと持つかどうか。
* <p>繰り返し可能なアノテーションの場合、初めに見つかったものを返します。</p>
*
* @param <A> 取得対象のアノテーションのタイプ
* @param annoClass 取得対象のアノテーションのタイプ。
* @return {@literal true}の場合、アノテーションを持ちます。
* @throws NullPointerException {@literal annoClass is null.}
*/
public <A extends Annotation> boolean hasAnnotation(final Class<A> annoClass) {
return getAnnotation(annoClass).isPresent();
}
/**
* アノテーションのタイプとグループを指定してアノテーションを取得します。
*
* @param <A> 取得対象のアノテーションのタイプ
* @param annoClass 取得対象のアノテーションのタイプ。
* @param groups グループ(クラスタイプ)による絞り込み。属性groupsが存在する場合に、絞り込みます。
* @return 指定したアノテーションが見つからない場合は、サイズ0のリストを返します。
* @throws NullPointerException {@literal annoClass is null.}
*/
public <A extends Annotation> List<A> getAnnotationsByGroup(final Class<A> annoClass, final Class<?>... groups) {
Objects.requireNonNull(annoClass, "annoClass should not be null.");
return getAnnotations(annoClass).stream()
.filter(anno -> hasGroups(anno, groups))
.collect(Collectors.toList());
}
/**
* グループを指定して指定したアノテーションを持つかどうか判定します。
*
* @param <A> 取得対象のアノテーションのタイプ
* @param annoClass 判定対象のアノテーションのグループ
* @param groups グループ(クラスタイプ)による絞り込み。属性groupsが存在する場合に、絞り込みます。
* @return 指定したアノテーションが見つからない場合は、サイズ0のリストを返します。
*/
public <A extends Annotation> boolean hasAnnotationByGroup(final Class<A> annoClass, final Class<?>... groups) {
return getAnnotationsByGroup(annoClass, groups).size() > 0;
}
/**
* 付与されているアノテーションの一覧を取得する。
*
* @param groups グループ(クラスタイプ)による絞り込み。属性groupsが存在する場合に、絞り込みます。
* @return 指定したアノテーションが見つからない場合は、サイズ0のリストを返します。
*/
public List<Annotation> getAnnotationsByGroup(final Class<?>... groups) {
return getAnnotations(expandedAnnos).stream()
.filter(anno -> hasGroups(anno, groups))
.collect(Collectors.toList());
}
@SuppressWarnings({"unchecked"})
private static <A extends Annotation> List<A> getAnnotations(final List<ExpandedAnnotation> expanedAnnos) {
final List<A> list = new ArrayList<>();
for(ExpandedAnnotation anno : expanedAnnos) {
if(anno.isComposed()) {
list.addAll(getAnnotations(anno.getChilds()));
} else {
list.add((A)anno.getOriginal());
}
}
return list;
}
/**
* アノテーションの属性{@literal groups} が指定したグループと一致するか比較します。
* <p>groups属性を持たない場合は、必ずfalseを返します。</p>
* @param anno 検証対象のアノテーション。
* @param groups 比較対象のグループ情報。
* @return {@literal true}の場合、指定したグループを持ちます。
*/
@SuppressWarnings("rawtypes")
private boolean hasGroups(final Annotation anno, final Class<?>... groups) {
final Optional<Class[]> targetGroups = Utils.getAnnotationAttribute(anno, "groups", Class[].class);
if(!targetGroups.isPresent()) {
// groups属性を持たない場合
return false;
}
if(groups.length == 0) {
if(targetGroups.get().length == 0) {
// グループの指定がない場合は、デフォルトグループとして処理。
return true;
} else {
for(Class<?> targetGroup : targetGroups.get()) {
if(targetGroup.equals(DefaultGroup.class)) {
// デフォルトを直接指定している場合に、グループと一致。
return true;
}
}
}
} else {
// グループの指定がある場合
for(Class<?> group : groups) {
if(group.equals(DefaultGroup.class) && targetGroups.get().length == 0) {
// フィールド側にグループの指定がない場合は、デフォルトグループとして処理する。
return true;
}
for(Class<?> targetGroup : targetGroups.get()) {
// 一致するグループを持つか判定する。
if(targetGroup.equals(group)) {
return true;
}
}
}
}
return false;
}
/**
* フィールドの名称を取得する。
* @return フィールド名
*/
public String getName() {
return name;
}
/**
* クラス名付きのフィールド名称を取得する。
* @return {@literal <クラス名#フィールド名>}の形式
*/
public String getNameWithClass() {
return getDeclaredClass().getName() + "#" + getName();
}
/**
* フィールドのタイプを取得する。
* @return フィールドのクラスタイプ。
*/
public Class<?> getType() {
return type;
}
/**
* フィールドのタイプのクラス名称を取得する。
* @return パッケージ名付きのFQDNの形式。
*/
public String getTypeName() {
return getType().getName();
}
/**
* フィールドが定義されているクラス情報を取得する。
*
* @see Field#getDeclaringClass()
* @return フィールドが定義されているクラス上方。
*/
public Class<?> getDeclaredClass() {
return field.getDeclaringClass();
}
/**
* フィールドのタイプが指定してたタイプかどうか。
* <p>{@link Class#isAssignableFrom(Class)}により比較を行う。
* @param clazz 比較対象のクラスタイプ。
* @return タイプが一致する場合、{@literal true}を返す。
*/
public boolean isTypeOf(final Class<?> clazz) {
return clazz.isAssignableFrom(getType());
}
/**
* フィールドの値を取得する。
* @param record レコードオブジェクト。
* @return フィールドの値。
* @throws IllegalArgumentException レコードのインスタンスがフィールドが定義されているクラスと異なる場合。
* @throws SuperCsvReflectionException フィールドの値の取得に失敗した場合。
*/
public Object getValue(final Object record) {
Objects.requireNonNull(record);
if(!getDeclaredClass().equals(record.getClass())) {
throw new IllegalArgumentException(String.format("not match record class type. expected=%s. actual=%s, ",
type.getName(), record.getClass().getName()));
}
try {
return field.get(record);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new SuperCsvReflectionException("fail get field value.", e);
}
}
}