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;
- }
- }