    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.lang3.builder;
    018
    019import java.util.ArrayList;
    020import java.util.Arrays;
    021import java.util.List;
    022import java.util.Objects;
    023import java.util.function.Supplier;
    024
    025import org.apache.commons.lang3.ArrayUtils;
    026import org.apache.commons.lang3.ObjectUtils;
    027
    028/**
    029 * Assists in implementing {@link Diffable#diff(Object)} methods.
    030 *
    031 * <p>
    032 * To use this class, write code as follows:
    033 * </p>
    034 *
    035 * <pre>{@code
    036 * public class Person implements Diffable<Person> {
    037 *   String name;
    038 *   int age;
    039 *   boolean smoker;
    040 *
    041 *   ...
    042 *
    043 *   public DiffResult diff(Person obj) {
    044 *     // No need for null check, as NullPointerException correct if obj is null
    045 *     return new DiffBuilder.<Person>builder()
    046 *         .setLeft(this)
    047 *         .setRight(obj)
    048 *         .setStyle(ToStringStyle.SHORT_PREFIX_STYLE))
    049 *         .build()
    050 *       .append("name", this.name, obj.name)
    051 *       .append("age", this.age, obj.age)
    052 *       .append("smoker", this.smoker, obj.smoker)
    053 *       .build();
    054 *   }
    055 * }
    056 * }</pre>
    057 *
    058 * <p>
    059 * The {@link ToStringStyle} passed to the constructor is embedded in the returned {@link DiffResult} and influences the style of the
    060 * {@code DiffResult.toString()} method. This style choice can be overridden by calling {@link DiffResult#toString(ToStringStyle)}.
    061 * </p>
    062 * <p>
    063 * See {@link ReflectionDiffBuilder} for a reflection based version of this class.
    064 * </p>
    065 *
    066 * @param <T> type of the left and right object.
    067 * @see Diffable
    068 * @see Diff
    069 * @see DiffResult
    070 * @see ToStringStyle
    071 * @see ReflectionDiffBuilder
    072 * @since 3.3
    073 */
    074public class DiffBuilder<T> implements Builder<DiffResult<T>> {
    075
    076    /**
    077     * Constructs a new instance.
    078     *
    079     * @param <T> type of the left and right object.
    080     * @since 3.15.0
    081     */
    082    public static final class Builder<T> {
    083
    084        private T left;
    085        private T right;
    086        private ToStringStyle style;
    087        private boolean testObjectsEquals = true;
    088        private String toStringFormat = TO_STRING_FORMAT;
    089
    090        /**
    091         * Constructs a new instance.
    092         */
    093        public Builder() {
    094            // empty
    095        }
    096
    097        /**
    098         * Builds a new configured {@link DiffBuilder}.
    099         *
    100         * @return a new configured {@link DiffBuilder}.
    101         */
    102        public DiffBuilder<T> build() {
    103            return new DiffBuilder<>(left, right, style, testObjectsEquals, toStringFormat);
    104        }
    105
    106        /**
    107         * Sets the left object.
    108         *
    109         * @param left the left object.
    110         * @return {@code this} instance.
    111         */
    112        public Builder<T> setLeft(final T left) {
    113            this.left = left;
    114            return this;
    115        }
    116
    117        /**
    118         * Sets the right object.
    119         *
    120         * @param right the left object.
    121         * @return {@code this} instance.
    122         */
    123        public Builder<T> setRight(final T right) {
    124            this.right = right;
    125            return this;
    126        }
    127
    128        /**
    129         * Sets the style will to use when outputting the objects, {@code null} uses the default.
    130         *
    131         * @param style the style to use when outputting the objects, {@code null} uses the default.
    132         * @return {@code this} instance.
    133         */
    134        public Builder<T> setStyle(final ToStringStyle style) {
    135            this.style = style != null ? style : ToStringStyle.DEFAULT_STYLE;
    136            return this;
    137        }
    138
    139        /**
    140         * Sets whether to test if left and right are the same or equal. All of the append(fieldName, left, right) methods will abort without creating a field
    141         * {@link Diff} if the trivially equal test is enabled and returns true. The result of this test is never changed throughout the life of this
    142         * {@link DiffBuilder}.
    143         *
    144         * @param testObjectsEquals If true, this will test if lhs and rhs are the same or equal. All of the append(fieldName, left, right) methods will abort
    145         *                          without creating a field {@link Diff} if the trivially equal test is enabled and returns true. The result of this test is
    146         *                          never changed throughout the life of this {@link DiffBuilder}.
    147         * @return {@code this} instance.
    148         */
    149        public Builder<T> setTestObjectsEquals(final boolean testObjectsEquals) {
    150            this.testObjectsEquals = testObjectsEquals;
    151            return this;
    152        }
    153
    154        /**
    155         * Sets the two-argument format string for {@link String#format(String, Object...)}, for example {@code "%s differs from %s"}.
    156         *
    157         * @param toStringFormat {@code null} uses the default.
    158         * @return {@code this} instance.
    159         */
    160        public Builder<T> setToStringFormat(final String toStringFormat) {
    161            this.toStringFormat = toStringFormat != null ? toStringFormat : TO_STRING_FORMAT;
    162            return this;
    163        }
    164    }
    165
    166    private static final class SDiff<T> extends Diff<T> {
    167
    168        private static final long serialVersionUID = 1L;
    169        private final transient Supplier<T> leftSupplier;
    170        private final transient Supplier<T> rightSupplier;
    171
    172        private SDiff(final String fieldName, final Supplier<T> leftSupplier, final Supplier<T> rightSupplier, final Class<T> type) {
    173            super(fieldName, type);
    174            this.leftSupplier = Objects.requireNonNull(leftSupplier);
    175            this.rightSupplier = Objects.requireNonNull(rightSupplier);
    176        }
    177
    178        @Override
    179        public T getLeft() {
    180            return leftSupplier.get();
    181        }
    182
    183        @Override
    184        public T getRight() {
    185            return rightSupplier.get();
    186        }
    187
    188    }
    189
    190    static final String TO_STRING_FORMAT = "%s differs from %s";
    191
    192    /**
    193     * Constructs a new {@link Builder}.
    194     *
    195     * @param <T> type of the left and right object.
    196     * @return a new {@link Builder}.
    197     * @since 3.15.0
    198     */
    199    public static <T> Builder<T> builder() {
    200        return new Builder<>();
    201    }
    202
    203    private final List<Diff<?>> diffs;
    204    private final boolean equals;
    205    private final T left;
    206    private final T right;
    207    private final ToStringStyle style;
    208    private final String toStringFormat;
    209
    210    /**
    211     * Constructs a builder for the specified objects with the specified style.
    212     *
    213     * <p>
    214     * If {@code lhs == rhs} or {@code lhs.equals(rhs)} then the builder will not evaluate any calls to {@code append(...)} and will return an empty
    215     * {@link DiffResult} when {@link #build()} is executed.
    216     * </p>
    217     *
    218     * <p>
    219     * This delegates to {@link #DiffBuilder(Object, Object, ToStringStyle, boolean)} with the testTriviallyEqual flag enabled.
    220     * </p>
    221     *
    222     * @param left  {@code this} object
    223     * @param right the object to diff against
    224     * @param style the style to use when outputting the objects, {@code null} uses the default
    225     * @throws NullPointerException if {@code lhs} or {@code rhs} is {@code null}
    226     * @deprecated Use {@link Builder}.
    227     */
    228    @Deprecated
    229    public DiffBuilder(final T left, final T right, final ToStringStyle style) {
    230        this(left, right, style, true);
    231    }
    232
    233    /**
    234     * Constructs a builder for the specified objects with the specified style.
    235     *
    236     * <p>
    237     * If {@code lhs == rhs} or {@code lhs.equals(rhs)} then the builder will not evaluate any calls to {@code append(...)} and will return an empty
    238     * {@link DiffResult} when {@link #build()} is executed.
    239     * </p>
    240     *
    241     * @param left              {@code this} object
    242     * @param right             the object to diff against
    243     * @param style             the style to use when outputting the objects, {@code null} uses the default
    244     * @param testObjectsEquals If true, this will test if lhs and rhs are the same or equal. All of the append(fieldName, lhs, rhs) methods will abort without
    245     *                          creating a field {@link Diff} if the trivially equal test is enabled and returns true. The result of this test is never changed
    246     *                          throughout the life of this {@link DiffBuilder}.
    247     * @throws NullPointerException if {@code lhs} or {@code rhs} is {@code null}
    248     * @since 3.4
    249     * @deprecated Use {@link Builder}.
    250     */
    251    @Deprecated
    252    public DiffBuilder(final T left, final T right, final ToStringStyle style, final boolean testObjectsEquals) {
    253        this(left, right, style, testObjectsEquals, TO_STRING_FORMAT);
    254    }
    255
    256    private DiffBuilder(final T left, final T right, final ToStringStyle style, final boolean testObjectsEquals, final String toStringFormat) {
    257        this.left = Objects.requireNonNull(left, "left");
    258        this.right = Objects.requireNonNull(right, "right");
    259        this.diffs = new ArrayList<>();
    260        this.toStringFormat = toStringFormat;
    261        this.style = style != null ? style : ToStringStyle.DEFAULT_STYLE;
    262        // Don't compare any fields if objects equal
    263        this.equals = testObjectsEquals && Objects.equals(left, right);
    264    }
    265
    266    private <F> DiffBuilder<T> add(final String fieldName, final Supplier<F> left, final Supplier<F> right, final Class<F> type) {
    267        diffs.add(new SDiff<>(fieldName, left, right, type));
    268        return this;
    269    }
    270
    271    /**
    272     * Tests if two {@code boolean}s are equal.
    273     *
    274     * @param fieldName the field name
    275     * @param lhs       the left-hand side {@code boolean}
    276     * @param rhs       the right-hand side {@code boolean}
    277     * @return {@code this} instance.
    278     * @throws NullPointerException if field name is {@code null}
    279     */
    280    public DiffBuilder<T> append(final String fieldName, final boolean lhs, final boolean rhs) {
    281        return equals || lhs == rhs ? this : add(fieldName, () -> Boolean.valueOf(lhs), () -> Boolean.valueOf(rhs), Boolean.class);
    282    }
    283
    284    /**
    285     * Tests if two {@code boolean[]}s are equal.
    286     *
    287     * @param fieldName the field name
    288     * @param lhs       the left-hand side {@code boolean[]}
    289     * @param rhs       the right-hand side {@code boolean[]}
    290     * @return {@code this} instance.
    291     * @throws NullPointerException if field name is {@code null}
    292     */
    293    public DiffBuilder<T> append(final String fieldName, final boolean[] lhs, final boolean[] rhs) {
    294        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Boolean[].class);
    295    }
    296
    297    /**
    298     * Tests if two {@code byte}s are equal.
    299     *
    300     * @param fieldName the field name
    301     * @param lhs       the left-hand side {@code byte}
    302     * @param rhs       the right-hand side {@code byte}
    303     * @return {@code this} instance.
    304     * @throws NullPointerException if field name is {@code null}
    305     */
    306    public DiffBuilder<T> append(final String fieldName, final byte lhs, final byte rhs) {
    307        return equals || lhs == rhs ? this : add(fieldName, () -> Byte.valueOf(lhs), () -> Byte.valueOf(rhs), Byte.class);
    308    }
    309
    310    /**
    311     * Tests if two {@code byte[]}s are equal.
    312     *
    313     * @param fieldName the field name
    314     * @param lhs       the left-hand side {@code byte[]}
    315     * @param rhs       the right-hand side {@code byte[]}
    316     * @return {@code this} instance.
    317     * @throws NullPointerException if field name is {@code null}
    318     */
    319    public DiffBuilder<T> append(final String fieldName, final byte[] lhs, final byte[] rhs) {
    320        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Byte[].class);
    321    }
    322
    323    /**
    324     * Tests if two {@code char}s are equal.
    325     *
    326     * @param fieldName the field name
    327     * @param lhs       the left-hand side {@code char}
    328     * @param rhs       the right-hand side {@code char}
    329     * @return {@code this} instance.
    330     * @throws NullPointerException if field name is {@code null}
    331     */
    332    public DiffBuilder<T> append(final String fieldName, final char lhs, final char rhs) {
    333        return equals || lhs == rhs ? this : add(fieldName, () -> Character.valueOf(lhs), () -> Character.valueOf(rhs), Character.class);
    334    }
    335
    336    /**
    337     * Tests if two {@code char[]}s are equal.
    338     *
    339     * @param fieldName the field name
    340     * @param lhs       the left-hand side {@code char[]}
    341     * @param rhs       the right-hand side {@code char[]}
    342     * @return {@code this} instance.
    343     * @throws NullPointerException if field name is {@code null}
    344     */
    345    public DiffBuilder<T> append(final String fieldName, final char[] lhs, final char[] rhs) {
    346        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Character[].class);
    347    }
    348
    349    /**
    350     * Appends diffs from another {@link DiffResult}.
    351     *
    352     * <p>
    353     * Useful this method to compare properties which are themselves Diffable and would like to know which specific part of it is different.
    354     * </p>
    355     *
    356     * <pre>{@code
    357     * public class Person implements Diffable<Person> {
    358     *   String name;
    359     *   Address address; // implements Diffable<Address>
    360     *
    361     *   ...
    362     *
    363     *   public DiffResult diff(Person obj) {
    364     *     return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
    365     *       .append("name", this.name, obj.name)
    366     *       .append("address", this.address.diff(obj.address))
    367     *       .build();
    368     *   }
    369     * }
    370     * }
    371     * </pre>
    372     *
    373     * @param fieldName  the field name
    374     * @param diffResult the {@link DiffResult} to append
    375     * @return {@code this} instance.
    376     * @throws NullPointerException if field name is {@code null} or diffResult is {@code null}
    377     * @since 3.5
    378     */
    379    public DiffBuilder<T> append(final String fieldName, final DiffResult<?> diffResult) {
    380        Objects.requireNonNull(diffResult, "diffResult");
    381        if (equals) {
    382            return this;
    383        }
    384        diffResult.getDiffs().forEach(diff -> append(fieldName + "." + diff.getFieldName(), diff.getLeft(), diff.getRight()));
    385        return this;
    386    }
    387
    388    /**
    389     * Tests if two {@code double}s are equal.
    390     *
    391     * @param fieldName the field name
    392     * @param lhs       the left-hand side {@code double}
    393     * @param rhs       the right-hand side {@code double}
    394     * @return {@code this} instance.
    395     * @throws NullPointerException if field name is {@code null}
    396     */
    397    public DiffBuilder<T> append(final String fieldName, final double lhs, final double rhs) {
    398        return equals || Double.doubleToLongBits(lhs) == Double.doubleToLongBits(rhs) ? this
    399                : add(fieldName, () -> Double.valueOf(lhs), () -> Double.valueOf(rhs), Double.class);
    400    }
    401
    402    /**
    403     * Tests if two {@code double[]}s are equal.
    404     *
    405     * @param fieldName the field name
    406     * @param lhs       the left-hand side {@code double[]}
    407     * @param rhs       the right-hand side {@code double[]}
    408     * @return {@code this} instance.
    409     * @throws NullPointerException if field name is {@code null}
    410     */
    411    public DiffBuilder<T> append(final String fieldName, final double[] lhs, final double[] rhs) {
    412        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Double[].class);
    413    }
    414
    415    /**
    416     * Test if two {@code float}s are equal.
    417     *
    418     * @param fieldName the field name
    419     * @param lhs       the left-hand side {@code float}
    420     * @param rhs       the right-hand side {@code float}
    421     * @return {@code this} instance.
    422     * @throws NullPointerException if field name is {@code null}
    423     */
    424    public DiffBuilder<T> append(final String fieldName, final float lhs, final float rhs) {
    425        return equals || Float.floatToIntBits(lhs) == Float.floatToIntBits(rhs) ? this
    426                : add(fieldName, () -> Float.valueOf(lhs), () -> Float.valueOf(rhs), Float.class);
    427    }
    428
    429    /**
    430     * Tests if two {@code float[]}s are equal.
    431     *
    432     * @param fieldName the field name
    433     * @param lhs       the left-hand side {@code float[]}
    434     * @param rhs       the right-hand side {@code float[]}
    435     * @return {@code this} instance.
    436     * @throws NullPointerException if field name is {@code null}
    437     */
    438    public DiffBuilder<T> append(final String fieldName, final float[] lhs, final float[] rhs) {
    439        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Float[].class);
    440    }
    441
    442    /**
    443     * Tests if two {@code int}s are equal.
    444     *
    445     * @param fieldName the field name
    446     * @param lhs       the left-hand side {@code int}
    447     * @param rhs       the right-hand side {@code int}
    448     * @return {@code this} instance.
    449     * @throws NullPointerException if field name is {@code null}
    450     */
    451    public DiffBuilder<T> append(final String fieldName, final int lhs, final int rhs) {
    452        return equals || lhs == rhs ? this : add(fieldName, () -> Integer.valueOf(lhs), () -> Integer.valueOf(rhs), Integer.class);
    453    }
    454
    455    /**
    456     * Tests if two {@code int[]}s are equal.
    457     *
    458     * @param fieldName the field name
    459     * @param lhs       the left-hand side {@code int[]}
    460     * @param rhs       the right-hand side {@code int[]}
    461     * @return {@code this} instance.
    462     * @throws NullPointerException if field name is {@code null}
    463     */
    464    public DiffBuilder<T> append(final String fieldName, final int[] lhs, final int[] rhs) {
    465        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Integer[].class);
    466    }
    467
    468    /**
    469     * Tests if two {@code long}s are equal.
    470     *
    471     * @param fieldName the field name
    472     * @param lhs       the left-hand side {@code long}
    473     * @param rhs       the right-hand side {@code long}
    474     * @return {@code this} instance.
    475     * @throws NullPointerException if field name is {@code null}
    476     */
    477    public DiffBuilder<T> append(final String fieldName, final long lhs, final long rhs) {
    478        return equals || lhs == rhs ? this : add(fieldName, () -> Long.valueOf(lhs), () -> Long.valueOf(rhs), Long.class);
    479    }
    480
    481    /**
    482     * Tests if two {@code long[]}s are equal.
    483     *
    484     * @param fieldName the field name
    485     * @param lhs       the left-hand side {@code long[]}
    486     * @param rhs       the right-hand side {@code long[]}
    487     * @return {@code this} instance.
    488     * @throws NullPointerException if field name is {@code null}
    489     */
    490    public DiffBuilder<T> append(final String fieldName, final long[] lhs, final long[] rhs) {
    491        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Long[].class);
    492    }
    493
    494    /**
    495     * Tests if two {@link Objects}s are equal.
    496     *
    497     * @param fieldName the field name
    498     * @param lhs       the left-hand side {@link Object}
    499     * @param rhs       the right-hand side {@link Object}
    500     * @return {@code this} instance.
    501     * @throws NullPointerException if field name is {@code null}
    502     */
    503    public DiffBuilder<T> append(final String fieldName, final Object lhs, final Object rhs) {
    504        if (equals || lhs == rhs) {
    505            return this;
    506        }
    507        // rhs cannot be null, as lhs != rhs
    508        final Object test = lhs != null ? lhs : rhs;
    509        if (ObjectUtils.isArray(test)) {
    510            if (test instanceof boolean[]) {
    511                return append(fieldName, (boolean[]) lhs, (boolean[]) rhs);
    512            }
    513            if (test instanceof byte[]) {
    514                return append(fieldName, (byte[]) lhs, (byte[]) rhs);
    515            }
    516            if (test instanceof char[]) {
    517                return append(fieldName, (char[]) lhs, (char[]) rhs);
    518            }
    519            if (test instanceof double[]) {
    520                return append(fieldName, (double[]) lhs, (double[]) rhs);
    521            }
    522            if (test instanceof float[]) {
    523                return append(fieldName, (float[]) lhs, (float[]) rhs);
    524            }
    525            if (test instanceof int[]) {
    526                return append(fieldName, (int[]) lhs, (int[]) rhs);
    527            }
    528            if (test instanceof long[]) {
    529                return append(fieldName, (long[]) lhs, (long[]) rhs);
    530            }
    531            if (test instanceof short[]) {
    532                return append(fieldName, (short[]) lhs, (short[]) rhs);
    533            }
    534            return append(fieldName, (Object[]) lhs, (Object[]) rhs);
    535        }
    536        // Not array type
    537        return Objects.equals(lhs, rhs) ? this : add(fieldName, () -> lhs, () -> rhs, Object.class);
    538    }
    539
    540    /**
    541     * Tests if two {@code Object[]}s are equal.
    542     *
    543     * @param fieldName the field name
    544     * @param lhs       the left-hand side {@code Object[]}
    545     * @param rhs       the right-hand side {@code Object[]}
    546     * @return {@code this} instance.
    547     * @throws NullPointerException if field name is {@code null}
    548     */
    549    public DiffBuilder<T> append(final String fieldName, final Object[] lhs, final Object[] rhs) {
    550        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> lhs, () -> rhs, Object[].class);
    551    }
    552
    553    /**
    554     * Tests if two {@code short}s are equal.
    555     *
    556     * @param fieldName the field name
    557     * @param lhs       the left-hand side {@code short}
    558     * @param rhs       the right-hand side {@code short}
    559     * @return {@code this} instance.
    560     * @throws NullPointerException if field name is {@code null}
    561     */
    562    public DiffBuilder<T> append(final String fieldName, final short lhs, final short rhs) {
    563        return equals || lhs == rhs ? this : add(fieldName, () -> Short.valueOf(lhs), () -> Short.valueOf(rhs), Short.class);
    564    }
    565
    566    /**
    567     * Tests if two {@code short[]}s are equal.
    568     *
    569     * @param fieldName the field name
    570     * @param lhs       the left-hand side {@code short[]}
    571     * @param rhs       the right-hand side {@code short[]}
    572     * @return {@code this} instance.
    573     * @throws NullPointerException if field name is {@code null}
    574     */
    575    public DiffBuilder<T> append(final String fieldName, final short[] lhs, final short[] rhs) {
    576        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Short[].class);
    577    }
    578
    579    /**
    580     * Builds a {@link DiffResult} based on the differences appended to this builder.
    581     *
    582     * @return a {@link DiffResult} containing the differences between the two objects.
    583     */
    584    @Override
    585    public DiffResult<T> build() {
    586        return new DiffResult<>(left, right, diffs, style, toStringFormat);
    587    }
    588
    589    /**
    590     * Gets the left object.
    591     *
    592     * @return the left object.
    593     */
    594    T getLeft() {
    595        return left;
    596    }
    597
    598    /**
    599     * Gets the right object.
    600     *
    601     * @return the right object.
    602     */
    603    T getRight() {
    604        return right;
    605    }
    606
    607}