IsEmptyBuilder.java
package com.gh.mygreen.xlsmapper.util;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 値が全て空かどかチェックするためのクラス。
* <p>アノテーション{@link com.gh.mygreen.xlsmapper.annotation.XlsIgnorable}を付与したメソッドの実装に利用します。</p>
*
* <p>リフレクションを利用して判定する場合は、位置情報のフィールドpositions、ラベル情報のフィールドlabelsを除外します。</p>
* <pre class="highlight"><code class="java">
* // リフレクションを使用する場合
* {@literal @XlsIgnoable}
* public boolean isEmpty() {
* return IsEmptyBuilder.reflectionIsEmpty(this, "positions", "labels");
* }
* </code></pre>
*
* <p>フィールドを1つずつ判定する場合は、{@code append(...)}メソッドを利用します。</p>
* メソッド{@link #compare(IsEmptyComparator)}を利用することで、独自の実装も可能で、その際にLambda式を利用することもできます。
*
* <pre class="highlight"><code class="java">
* // 独自に組み立てる場合
* {@literal @XlsIgnoable}
* public boolean isEmpty() {
* return new IsEmptyBuilder()
* .append(name)
* .append(age)
* .compare(() {@literal ->} StringUtils.isBlank(address))
* .isEmpty();
* }
* </code></pre>
*
* <p>オプションを指定して処理する場合、{@link IsEmptyConfig}を利用します。
* <pre class="highlight"><code class="java">
* // オプションを指定する場合
* {@literal @XlsIgnoable}
* public boolean isEmpty() {
* return IsEmptyBuilder.reflectionIsEmpty(this
* , IsEmptyConfig.create().withTestArrayElement(false).withTestCollectionElement(false)
* , "positions", "labels");
* }
* </code></pre>
*
*
* @since 0.5
* @author T.TSUCHIE
*
*/
public class IsEmptyBuilder {
/**
* 現在までの判定結果を保持する。
* true: 値が空かどうか。
*/
private final AtomicBoolean result;
/**
* 数値の場合、0を空として扱うか。
*/
private boolean zeroAsEmpty;
/**
* 配列の場合、値も検証対象とするかどうか。
*/
private boolean testArrayElement;
/**
* Collectionの場合、値も検証対象とするかどうか。
*/
private boolean testCollectionElement;
/**
* Mapの場合、値も対象とするかどうか。
*/
private boolean testMapValue;
/**
* transientが付与されたフィールドも対象とするかどうか。
*/
private boolean testTransient;
/**
* コンストラクタ。
*/
public IsEmptyBuilder() {
this(IsEmptyConfig.create());
}
/**
* {@link IsEmptyConfig}を指定するコンストラクタ。
* @param config 設定用クラス。
* @throws IllegalArgumentException config is null.
*/
public IsEmptyBuilder(final IsEmptyConfig config) {
ArgUtils.notNull(config, "config");
this.result = new AtomicBoolean(true);
this.zeroAsEmpty = config.isZeroAsEmpty();
this.testArrayElement = config.isTestArrayElement();
this.testCollectionElement = config.isTestCollectionElement();
this.testMapValue = config.isTestMapValue();
this.testTransient = config.isTestTransient();
}
/**
* リフレクションを使用しフィールドの値を取得し判定する。
* <p>static修飾子を付与しているフィールドは、除外されます。
* <p>transient修飾子を付与しているフィールドは、除外されます。
* @param obj 判定対象のオブジェクト。
* @param excludedFields 除外対処のフィールド名。
* @return 引数で指定したobjがnullの場合、trueを返す。
*/
public static boolean reflectionIsEmpty(final Object obj, final String... excludedFields) {
return reflectionIsEmpty(obj, IsEmptyConfig.create(), Arrays.asList(excludedFields));
}
/**
* リフレクションを使用しフィールドの値を取得し判定する。
* @since 1.0
* @param obj 判定対象のオブジェクト。
* @param config 判定用の設定クラス。
* @param excludedFields 除外対処のフィールド名。
* @return 引数で指定したobjがnullの場合、trueを返す。
*/
public static boolean reflectionIsEmpty(final Object obj, final IsEmptyConfig config, final String... excludedFields) {
return reflectionIsEmpty(obj, config, Arrays.asList(excludedFields));
}
/**
* リフレクションを使用しフィールドの値を取得し判定する。
* @since 1.0
* @param obj 判定対象のオブジェクト。
* @param config 判定用の設定クラス。
* @param excludedFields 除外対処のフィールド名。
* @return 引数で指定したobjがnullの場合、trueを返す。
*/
public static boolean reflectionIsEmpty(final Object obj, final IsEmptyConfig config, final Collection<String> excludedFields) {
if(obj == null) {
return true;
}
final IsEmptyBuilder builder = new IsEmptyBuilder(config);
final Field[] fields = obj.getClass().getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
for(Field field : fields) {
// static フィールドかどうか
if(Modifier.isStatic(field.getModifiers())) {
continue;
}
// transientのフィールドかどうか。
if(!builder.testTransient && Modifier.isTransient(field.getModifiers())) {
continue;
}
// 除外対象のフィールド名かどうか。
if(excludedFields != null && excludedFields.contains(field.getName())) {
continue;
}
try {
final Object value = field.get(obj);
builder.append(value);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new InternalError("Unexpected IllegalAccessException");
}
}
return builder.isEmpty();
}
/**
* 値が空でないことを設定する。
*/
private void setNotEmpty() {
this.result.set(false);
}
/**
* String型の値を追加する。
* @param value nullまたは空文字の場合、空と判断する。
* @return this.
*/
public IsEmptyBuilder append(final String value) {
return append(value, false);
}
/**
* String型の値を追加する。
* @param value nullまたは空文字の場合、空と判断する。
* @param trim 引数valueをトリムした後空文字と判定するかどうか。
* @return this.
*/
public IsEmptyBuilder append(final String value, final boolean trim) {
if(isNotEmpty()) {
return this;
}
if(trim) {
if(value != null && !value.trim().isEmpty()) {
setNotEmpty();
}
} else {
if(value != null && !value.isEmpty()) {
setNotEmpty();
}
}
return this;
}
/**
* char型の値を追加する。
* @param value 空文字の場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final char value) {
if(isNotEmpty()) {
return this;
}
final String str = (value == '\u0000' ? "" : String.valueOf(value));
return append(str);
}
/**
* char型の値を追加する。
* @param value 空文字の場合、空と判断する。
* @param trim 引数valueをトリムした後空文字と判定するかどうか。
* @return this
*/
public IsEmptyBuilder append(final char value, final boolean trim) {
if(isNotEmpty()) {
return this;
}
final String str = (value == '\u0000' ? "" : String.valueOf(value));
return append(str, trim);
}
/**
* boolean型の値を追加する。
* @param value false場合、空と判断する。
* @return this.
*/
public IsEmptyBuilder append(final boolean value) {
if(isNotEmpty()) {
return this;
}
if(value) {
setNotEmpty();
}
return this;
}
/**
* byte型の値を追加する。
* @param value {@link #isZeroAsEmpty()}がtrueの場合、0の値を空として扱う。
* @return this
*/
public IsEmptyBuilder append(final byte value) {
if(isNotEmpty()) {
return this;
}
if(!(isZeroAsEmpty() && value == 0)) {
setNotEmpty();
}
return this;
}
/**
* short型の値を追加する。
* @param value {@link #isZeroAsEmpty()}がtrueの場合、0の値を空として扱う。
* @return this
*/
public IsEmptyBuilder append(final short value) {
if(isNotEmpty()) {
return this;
}
if(!(isZeroAsEmpty() && value == 0)) {
setNotEmpty();
}
return this;
}
/**
* int型の値を追加する。
* @param value {@link #isZeroAsEmpty()}がtrueの場合、0の値を空として扱う。
* @return this
*/
public IsEmptyBuilder append(final int value) {
if(isNotEmpty()) {
return this;
}
if(!(isZeroAsEmpty() && value == 0)) {
setNotEmpty();
}
return this;
}
/**
* long型の値を追加する。
* @param value {@link #isZeroAsEmpty()}がtrueの場合、0の値を空として扱う。
* @return this
*/
public IsEmptyBuilder append(final long value) {
if(isNotEmpty()) {
return this;
}
if(!(isZeroAsEmpty() && value == 0L)) {
setNotEmpty();
}
return this;
}
/**
* float型の値を追加する。
* @param value {@link #isZeroAsEmpty()}がtrueの場合、0.0の値を空として扱う。
* @return this
*/
public IsEmptyBuilder append(final float value) {
if(isNotEmpty()) {
return this;
}
if(!(isZeroAsEmpty() && value == 0.0)) {
setNotEmpty();
}
return this;
}
/**
* double型の値を追加する。
* @param value {@link #isZeroAsEmpty()}がtrueの場合、0の値を空として扱う。
* @return this
*/
public IsEmptyBuilder append(final double value) {
if(isNotEmpty()) {
return this;
}
if(!(isZeroAsEmpty() && value == 0.0)) {
setNotEmpty();
}
return this;
}
/**
* Object型の値を追加する。
* @param value nullの場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final Object value) {
if(isNotEmpty()) {
return this;
}
if(value == null) {
return this;
}
final Class<?> clazz = value.getClass();
if(clazz.isPrimitive()) {
if(clazz.equals(Boolean.TYPE)) {
return append((boolean) value);
} else if(clazz.equals(Byte.TYPE)) {
return append((byte) value);
} else if(clazz.equals(Character.TYPE)) {
return append((char) value);
} else if(clazz.equals(Short.TYPE)) {
return append((short) value);
} else if(clazz.equals(Integer.TYPE)) {
return append((int) value);
} else if(clazz.equals(Long.TYPE)) {
return append((long) value);
} else if(clazz.equals(Float.TYPE)) {
return append((float) value);
} else if(clazz.equals(Double.TYPE)) {
return append((double) value);
}
} else if(clazz.isArray()) {
if(value instanceof boolean[]) {
return append((boolean[]) value);
} else if(value instanceof char[]) {
return append((char[]) value);
} else if(value instanceof byte[]) {
return append((byte[]) value);
} else if(value instanceof short[]) {
return append((short[]) value);
} else if(value instanceof int[]) {
return append((int[]) value);
} else if(value instanceof long[]) {
return append((long[]) value);
} else if(value instanceof float[]) {
return append((float[]) value);
} else if(value instanceof double[]) {
return append((double[]) value);
} else {
return append((Object[]) value);
}
} else if(value instanceof String) {
return append((String) value);
} else if(value instanceof Boolean) {
return append((boolean) value);
} else if(value instanceof Byte) {
return append((byte) value);
} else if(value instanceof Character) {
return append((char) value);
} else if(value instanceof Short) {
return append((short) value);
} else if(value instanceof Integer) {
return append((int) value);
} else if(value instanceof Long) {
return append((long) value);
} else if(value instanceof Float) {
return append((float) value);
} else if(value instanceof Double) {
return append((double) value);
} else if(value instanceof Collection) {
return append((Collection<?>) value);
} else if(value instanceof Map) {
return append((Map<?, ?>) value);
} else {
setNotEmpty();
}
return this;
}
/**
* 配列型の値を追加する。
* @param value nullの場合、サイズが0の場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final Object[] value) {
if(isNotEmpty()) {
return this;
}
if(value != null && isTestArrayElement()) {
for(Object o : value) {
if(isNotEmpty()) {
return this;
}
append(o);
}
} else if(value != null && value.length != 0) {
setNotEmpty();
}
return this;
}
/**
* booleanの配列型の値を追加する。
* @param value nullの場合、サイズが0の場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final boolean[] value) {
if(isNotEmpty()) {
return this;
}
if(value != null && isTestArrayElement()) {
for(boolean o : value) {
if(isNotEmpty()) {
return this;
}
append(o);
}
} else if(value != null && value.length != 0) {
setNotEmpty();
}
return this;
}
/**
* charの配列型の値を追加する。
* @param value nullの場合、サイズが0の場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final char[] value) {
if(isNotEmpty()) {
return this;
}
if(value != null && isTestArrayElement()) {
for(char o : value) {
if(isNotEmpty()) {
return this;
}
append(o);
}
} else if(value != null && value.length != 0) {
setNotEmpty();
}
return this;
}
/**
* byteの配列型の値を追加する。
* @param value nullの場合、サイズが0の場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final byte[] value) {
if(isNotEmpty()) {
return this;
}
if(value != null && isTestArrayElement()) {
for(byte o : value) {
if(isNotEmpty()) {
return this;
}
append(o);
}
} else if(value != null && value.length != 0) {
setNotEmpty();
}
return this;
}
/**
* shortの配列型の値を追加する。
* @param value nullの場合、サイズが0の場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final short[] value) {
if(isNotEmpty()) {
return this;
}
if(value != null && isTestArrayElement()) {
for(short o : value) {
if(isNotEmpty()) {
return this;
}
append(o);
}
} else if(value != null && value.length != 0) {
setNotEmpty();
}
return this;
}
/**
* intの配列型の値を追加する。
* @param value nullの場合、サイズが0の場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final int[] value) {
if(isNotEmpty()) {
return this;
}
if(value != null && isTestArrayElement()) {
for(int o : value) {
if(isNotEmpty()) {
return this;
}
append(o);
}
} else if(value != null && value.length != 0) {
setNotEmpty();
}
return this;
}
/**
* longの配列型の値を追加する。
* @param value nullの場合、サイズが0の場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final long[] value) {
if(isNotEmpty()) {
return this;
}
if(value != null && isTestArrayElement()) {
for(long o : value) {
if(isNotEmpty()) {
return this;
}
append(o);
}
} else if(value != null && value.length != 0) {
setNotEmpty();
}
return this;
}
/**
* floatの配列型の値を追加する。
* @param value nullの場合、サイズが0の場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final float[] value) {
if(isNotEmpty()) {
return this;
}
if(value != null && isTestArrayElement()) {
for(float o : value) {
if(isNotEmpty()) {
return this;
}
append(o);
}
} else if(value != null && value.length != 0) {
setNotEmpty();
}
return this;
}
/**
* doubleの配列型の値を追加する。
* @param value nullの場合、サイズが0の場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final double[] value) {
if(isNotEmpty()) {
return this;
}
if(value != null && isTestArrayElement()) {
for(double o : value) {
if(isNotEmpty()) {
return this;
}
append(o);
}
} else if(value != null && value.length != 0) {
setNotEmpty();
}
return this;
}
/**
* Collection型の値を追加する。
* @param value nullの場合、サイズが0の場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final Collection<?> value) {
if(isNotEmpty()) {
return this;
}
if(value != null && isTestCollectionElement()) {
// コレクションの値も検証する。
for(Object o : value) {
if(isNotEmpty()) {
return this;
}
append(o);
}
} else if(value != null && !value.isEmpty()) {
setNotEmpty();
}
return this;
}
/**
* Map型の値を追加する。
* @param value nullの場合、サイズが0の場合、空と判断する。
* @return this
*/
public IsEmptyBuilder append(final Map<?, ?> value) {
if(isNotEmpty()) {
return this;
}
if(value != null && isTestMapValue()) {
// コレクションの値も検証する。
for(Object o : value.values()) {
if(isNotEmpty()) {
return this;
}
append(o);
}
} else if(value != null && !value.isEmpty()) {
setNotEmpty();
}
return this;
}
/**
* 独自の実装で値を空かどうか判定する。
* <p>Java8のLambda式を利用すると簡潔に書ける。
* <pre class="highlight"><code class="java">
* public boolean isEmpty() {
* return new IsEmptyBuilder()
* .append(age)
* .compare(() {@literal ->} StringUtils.isBlank(address))
* .isEmpty();
* }
* </code></pre>
*
* @param compare {@link IsEmptyComparator}のインスタンス。
* @return this.
*/
public <T> IsEmptyBuilder compare(final IsEmptyComparator compare) {
if(isNotEmpty()) {
return this;
}
if(!compare.isEmpty()) {
setNotEmpty();
}
return this;
}
/**
* 判定結果が空かどうか。
* <p>{@code append(XXXX)}メソッドで何も追加されない場合、trueを返します。
* @return true:値が空。
*/
public boolean isEmpty() {
return result.get();
}
/**
* 判定結果がが空でないかどうか。
* @return true:値が空でない。
*/
public boolean isNotEmpty() {
return !isEmpty();
}
/**
* 数値の0を空として扱うかどうか。
* @return true:0を空として扱う。
*/
private boolean isZeroAsEmpty() {
return zeroAsEmpty;
}
/**
* Collectionの値も検証するかどうか。
* @return true:Collectionの値も検証する。
*/
private boolean isTestArrayElement() {
return testArrayElement;
}
/**
* Collectionの値も検証するかどうか。
* @return true:Collectionの値も検証する。
*/
private boolean isTestCollectionElement() {
return testCollectionElement;
}
/**
* Mapの値も検証するかどうか。
* @return true:Mapの値も検証する。
*/
private boolean isTestMapValue() {
return testMapValue;
}
}