    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&lt;Person&gt; {
    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         * Builds a new configured {@link DiffBuilder}.
    092         *
    093         * @return a new configured {@link DiffBuilder}.
    094         */
    095        public DiffBuilder<T> build() {
    096            return new DiffBuilder<>(left, right, style, testObjectsEquals, toStringFormat);
    097        }
    098
    099        /**
    100         * Sets the left object.
    101         *
    102         * @param left the left object.
    103         * @return {@code this} instance.
    104         */
    105        public Builder<T> setLeft(final T left) {
    106            this.left = left;
    107            return this;
    108        }
    109
    110        /**
    111         * Sets the right object.
    112         *
    113         * @param right the left object.
    114         * @return {@code this} instance.
    115         */
    116        public Builder<T> setRight(final T right) {
    117            this.right = right;
    118            return this;
    119        }
    120
    121        /**
    122         * Sets the style will to use when outputting the objects, {@code null} uses the default.
    123         *
    124         * @param style the style to use when outputting the objects, {@code null} uses the default.
    125         * @return {@code this} instance.
    126         */
    127        public Builder<T> setStyle(final ToStringStyle style) {
    128            this.style = style != null ? style : ToStringStyle.DEFAULT_STYLE;
    129            return this;
    130        }
    131
    132        /**
    133         * 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
    134         * {@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
    135         * {@link DiffBuilder}.
    136         *
    137         * @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
    138         *                          without creating a field {@link Diff} if the trivially equal test is enabled and returns true. The result of this test is
    139         *                          never changed throughout the life of this {@link DiffBuilder}.
    140         * @return {@code this} instance.
    141         */
    142        public Builder<T> setTestObjectsEquals(final boolean testObjectsEquals) {
    143            this.testObjectsEquals = testObjectsEquals;
    144            return this;
    145        }
    146
    147        /**
    148         * Sets the two-argument format string for {@link String#format(String, Object...)}, for example {@code "%s differs from %s"}.
    149         *
    150         * @param toStringFormat {@code null} uses the default.
    151         * @return {@code this} instance.
    152         */
    153        public Builder<T> setToStringFormat(final String toStringFormat) {
    154            this.toStringFormat = toStringFormat != null ? toStringFormat : TO_STRING_FORMAT;
    155            return this;
    156        }
    157    }
    158
    159    private static final class SDiff<T> extends Diff<T> {
    160
    161        private static final long serialVersionUID = 1L;
    162        private final transient Supplier<T> leftSupplier;
    163        private final transient Supplier<T> rightSupplier;
    164
    165        private SDiff(final String fieldName, final Supplier<T> leftSupplier, final Supplier<T> rightSupplier, final Class<T> type) {
    166            super(fieldName, type);
    167            this.leftSupplier = Objects.requireNonNull(leftSupplier);
    168            this.rightSupplier = Objects.requireNonNull(rightSupplier);
    169        }
    170
    171        @Override
    172        public T getLeft() {
    173            return leftSupplier.get();
    174        }
    175
    176        @Override
    177        public T getRight() {
    178            return rightSupplier.get();
    179        }
    180
    181    }
    182
    183    static final String TO_STRING_FORMAT = "%s differs from %s";
    184
    185    /**
    186     * Constructs a new {@link Builder}.
    187     *
    188     * @param <T> type of the left and right object.
    189     * @return a new {@link Builder}.
    190     * @since 3.15.0
    191     */
    192    public static <T> Builder<T> builder() {
    193        return new Builder<>();
    194    }
    195
    196    private final List<Diff<?>> diffs;
    197    private final boolean equals;
    198    private final T left;
    199    private final T right;
    200    private final ToStringStyle style;
    201    private final String toStringFormat;
    202
    203    /**
    204     * Constructs a builder for the specified objects with the specified style.
    205     *
    206     * <p>
    207     * 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
    208     * {@link DiffResult} when {@link #build()} is executed.
    209     * </p>
    210     *
    211     * <p>
    212     * This delegates to {@link #DiffBuilder(Object, Object, ToStringStyle, boolean)} with the testTriviallyEqual flag enabled.
    213     * </p>
    214     *
    215     * @param left  {@code this} object
    216     * @param right the object to diff against
    217     * @param style the style to use when outputting the objects, {@code null} uses the default
    218     * @throws NullPointerException if {@code lhs} or {@code rhs} is {@code null}
    219     * @deprecated Use {@link Builder}.
    220     */
    221    @Deprecated
    222    public DiffBuilder(final T left, final T right, final ToStringStyle style) {
    223        this(left, right, style, true);
    224    }
    225
    226    /**
    227     * Constructs a builder for the specified objects with the specified style.
    228     *
    229     * <p>
    230     * 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
    231     * {@link DiffResult} when {@link #build()} is executed.
    232     * </p>
    233     *
    234     * @param left              {@code this} object
    235     * @param right             the object to diff against
    236     * @param style             the style to use when outputting the objects, {@code null} uses the default
    237     * @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
    238     *                          creating a field {@link Diff} if the trivially equal test is enabled and returns true. The result of this test is never changed
    239     *                          throughout the life of this {@link DiffBuilder}.
    240     * @throws NullPointerException if {@code lhs} or {@code rhs} is {@code null}
    241     * @since 3.4
    242     * @deprecated Use {@link Builder}.
    243     */
    244    @Deprecated
    245    public DiffBuilder(final T left, final T right, final ToStringStyle style, final boolean testObjectsEquals) {
    246        this(left, right, style, testObjectsEquals, TO_STRING_FORMAT);
    247    }
    248
    249    private DiffBuilder(final T left, final T right, final ToStringStyle style, final boolean testObjectsEquals, final String toStringFormat) {
    250        this.left = Objects.requireNonNull(left, "left");
    251        this.right = Objects.requireNonNull(right, "right");
    252        this.diffs = new ArrayList<>();
    253        this.toStringFormat = toStringFormat;
    254        this.style = style != null ? style : ToStringStyle.DEFAULT_STYLE;
    255        // Don't compare any fields if objects equal
    256        this.equals = testObjectsEquals && Objects.equals(left, right);
    257    }
    258
    259    private <F> DiffBuilder<T> add(final String fieldName, final Supplier<F> left, final Supplier<F> right, final Class<F> type) {
    260        diffs.add(new SDiff<>(fieldName, left, right, type));
    261        return this;
    262    }
    263
    264    /**
    265     * Tests if two {@code boolean}s are equal.
    266     *
    267     * @param fieldName the field name
    268     * @param lhs       the left-hand side {@code boolean}
    269     * @param rhs       the right-hand side {@code boolean}
    270     * @return {@code this} instance.
    271     * @throws NullPointerException if field name is {@code null}
    272     */
    273    public DiffBuilder<T> append(final String fieldName, final boolean lhs, final boolean rhs) {
    274        return equals || lhs == rhs ? this : add(fieldName, () -> Boolean.valueOf(lhs), () -> Boolean.valueOf(rhs), Boolean.class);
    275    }
    276
    277    /**
    278     * Tests if two {@code boolean[]}s are equal.
    279     *
    280     * @param fieldName the field name
    281     * @param lhs       the left-hand side {@code boolean[]}
    282     * @param rhs       the right-hand side {@code boolean[]}
    283     * @return {@code this} instance.
    284     * @throws NullPointerException if field name is {@code null}
    285     */
    286    public DiffBuilder<T> append(final String fieldName, final boolean[] lhs, final boolean[] rhs) {
    287        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Boolean[].class);
    288    }
    289
    290    /**
    291     * Tests if two {@code byte}s are equal.
    292     *
    293     * @param fieldName the field name
    294     * @param lhs       the left-hand side {@code byte}
    295     * @param rhs       the right-hand side {@code byte}
    296     * @return {@code this} instance.
    297     * @throws NullPointerException if field name is {@code null}
    298     */
    299    public DiffBuilder<T> append(final String fieldName, final byte lhs, final byte rhs) {
    300        return equals || lhs == rhs ? this : add(fieldName, () -> Byte.valueOf(lhs), () -> Byte.valueOf(rhs), Byte.class);
    301    }
    302
    303    /**
    304     * Tests if two {@code byte[]}s are equal.
    305     *
    306     * @param fieldName the field name
    307     * @param lhs       the left-hand side {@code byte[]}
    308     * @param rhs       the right-hand side {@code byte[]}
    309     * @return {@code this} instance.
    310     * @throws NullPointerException if field name is {@code null}
    311     */
    312    public DiffBuilder<T> append(final String fieldName, final byte[] lhs, final byte[] rhs) {
    313        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Byte[].class);
    314    }
    315
    316    /**
    317     * Tests if two {@code char}s are equal.
    318     *
    319     * @param fieldName the field name
    320     * @param lhs       the left-hand side {@code char}
    321     * @param rhs       the right-hand side {@code char}
    322     * @return {@code this} instance.
    323     * @throws NullPointerException if field name is {@code null}
    324     */
    325    public DiffBuilder<T> append(final String fieldName, final char lhs, final char rhs) {
    326        return equals || lhs == rhs ? this : add(fieldName, () -> Character.valueOf(lhs), () -> Character.valueOf(rhs), Character.class);
    327    }
    328
    329    /**
    330     * Tests if two {@code char[]}s are equal.
    331     *
    332     * @param fieldName the field name
    333     * @param lhs       the left-hand side {@code char[]}
    334     * @param rhs       the right-hand side {@code char[]}
    335     * @return {@code this} instance.
    336     * @throws NullPointerException if field name is {@code null}
    337     */
    338    public DiffBuilder<T> append(final String fieldName, final char[] lhs, final char[] rhs) {
    339        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Character[].class);
    340    }
    341
    342    /**
    343     * Appends diffs from another {@link DiffResult}.
    344     *
    345     * <p>
    346     * Useful this method to compare properties which are themselves Diffable and would like to know which specific part of it is different.
    347     * </p>
    348     *
    349     * <pre>
    350     * public class Person implements Diffable&lt;Person&gt; {
    351     *   String name;
    352     *   Address address; // implements Diffable&lt;Address&gt;
    353     *
    354     *   ...
    355     *
    356     *   public DiffResult diff(Person obj) {
    357     *     return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
    358     *       .append("name", this.name, obj.name)
    359     *       .append("address", this.address.diff(obj.address))
    360     *       .build();
    361     *   }
    362     * }
    363     * </pre>
    364     *
    365     * @param fieldName  the field name
    366     * @param diffResult the {@link DiffResult} to append
    367     * @return {@code this} instance.
    368     * @throws NullPointerException if field name is {@code null} or diffResult is {@code null}
    369     * @since 3.5
    370     */
    371    public DiffBuilder<T> append(final String fieldName, final DiffResult<?> diffResult) {
    372        Objects.requireNonNull(diffResult, "diffResult");
    373        if (equals) {
    374            return this;
    375        }
    376        diffResult.getDiffs().forEach(diff -> append(fieldName + "." + diff.getFieldName(), diff.getLeft(), diff.getRight()));
    377        return this;
    378    }
    379
    380    /**
    381     * Tests if two {@code double}s are equal.
    382     *
    383     * @param fieldName the field name
    384     * @param lhs       the left-hand side {@code double}
    385     * @param rhs       the right-hand side {@code double}
    386     * @return {@code this} instance.
    387     * @throws NullPointerException if field name is {@code null}
    388     */
    389    public DiffBuilder<T> append(final String fieldName, final double lhs, final double rhs) {
    390        return equals || Double.doubleToLongBits(lhs) == Double.doubleToLongBits(rhs) ? this
    391                : add(fieldName, () -> Double.valueOf(lhs), () -> Double.valueOf(rhs), Double.class);
    392    }
    393
    394    /**
    395     * Tests if two {@code double[]}s are equal.
    396     *
    397     * @param fieldName the field name
    398     * @param lhs       the left-hand side {@code double[]}
    399     * @param rhs       the right-hand side {@code double[]}
    400     * @return {@code this} instance.
    401     * @throws NullPointerException if field name is {@code null}
    402     */
    403    public DiffBuilder<T> append(final String fieldName, final double[] lhs, final double[] rhs) {
    404        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Double[].class);
    405    }
    406
    407    /**
    408     * Test if two {@code float}s are equal.
    409     *
    410     * @param fieldName the field name
    411     * @param lhs       the left-hand side {@code float}
    412     * @param rhs       the right-hand side {@code float}
    413     * @return {@code this} instance.
    414     * @throws NullPointerException if field name is {@code null}
    415     */
    416    public DiffBuilder<T> append(final String fieldName, final float lhs, final float rhs) {
    417        return equals || Float.floatToIntBits(lhs) == Float.floatToIntBits(rhs) ? this
    418                : add(fieldName, () -> Float.valueOf(lhs), () -> Float.valueOf(rhs), Float.class);
    419    }
    420
    421    /**
    422     * Tests if two {@code float[]}s are equal.
    423     *
    424     * @param fieldName the field name
    425     * @param lhs       the left-hand side {@code float[]}
    426     * @param rhs       the right-hand side {@code float[]}
    427     * @return {@code this} instance.
    428     * @throws NullPointerException if field name is {@code null}
    429     */
    430    public DiffBuilder<T> append(final String fieldName, final float[] lhs, final float[] rhs) {
    431        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Float[].class);
    432    }
    433
    434    /**
    435     * Tests if two {@code int}s are equal.
    436     *
    437     * @param fieldName the field name
    438     * @param lhs       the left-hand side {@code int}
    439     * @param rhs       the right-hand side {@code int}
    440     * @return {@code this} instance.
    441     * @throws NullPointerException if field name is {@code null}
    442     */
    443    public DiffBuilder<T> append(final String fieldName, final int lhs, final int rhs) {
    444        return equals || lhs == rhs ? this : add(fieldName, () -> Integer.valueOf(lhs), () -> Integer.valueOf(rhs), Integer.class);
    445    }
    446
    447    /**
    448     * Tests if two {@code int[]}s are equal.
    449     *
    450     * @param fieldName the field name
    451     * @param lhs       the left-hand side {@code int[]}
    452     * @param rhs       the right-hand side {@code int[]}
    453     * @return {@code this} instance.
    454     * @throws NullPointerException if field name is {@code null}
    455     */
    456    public DiffBuilder<T> append(final String fieldName, final int[] lhs, final int[] rhs) {
    457        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Integer[].class);
    458    }
    459
    460    /**
    461     * Tests if two {@code long}s are equal.
    462     *
    463     * @param fieldName the field name
    464     * @param lhs       the left-hand side {@code long}
    465     * @param rhs       the right-hand side {@code long}
    466     * @return {@code this} instance.
    467     * @throws NullPointerException if field name is {@code null}
    468     */
    469    public DiffBuilder<T> append(final String fieldName, final long lhs, final long rhs) {
    470        return equals || lhs == rhs ? this : add(fieldName, () -> Long.valueOf(lhs), () -> Long.valueOf(rhs), Long.class);
    471    }
    472
    473    /**
    474     * Tests if two {@code long[]}s are equal.
    475     *
    476     * @param fieldName the field name
    477     * @param lhs       the left-hand side {@code long[]}
    478     * @param rhs       the right-hand side {@code long[]}
    479     * @return {@code this} instance.
    480     * @throws NullPointerException if field name is {@code null}
    481     */
    482    public DiffBuilder<T> append(final String fieldName, final long[] lhs, final long[] rhs) {
    483        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Long[].class);
    484    }
    485
    486    /**
    487     * Tests if two {@link Objects}s are equal.
    488     *
    489     * @param fieldName the field name
    490     * @param lhs       the left-hand side {@link Object}
    491     * @param rhs       the right-hand side {@link Object}
    492     * @return {@code this} instance.
    493     * @throws NullPointerException if field name is {@code null}
    494     */
    495    public DiffBuilder<T> append(final String fieldName, final Object lhs, final Object rhs) {
    496        if (equals || lhs == rhs) {
    497            return this;
    498        }
    499        // rhs cannot be null, as lhs != rhs
    500        final Object test = lhs != null ? lhs : rhs;
    501        if (ObjectUtils.isArray(test)) {
    502            if (test instanceof boolean[]) {
    503                return append(fieldName, (boolean[]) lhs, (boolean[]) rhs);
    504            }
    505            if (test instanceof byte[]) {
    506                return append(fieldName, (byte[]) lhs, (byte[]) rhs);
    507            }
    508            if (test instanceof char[]) {
    509                return append(fieldName, (char[]) lhs, (char[]) rhs);
    510            }
    511            if (test instanceof double[]) {
    512                return append(fieldName, (double[]) lhs, (double[]) rhs);
    513            }
    514            if (test instanceof float[]) {
    515                return append(fieldName, (float[]) lhs, (float[]) rhs);
    516            }
    517            if (test instanceof int[]) {
    518                return append(fieldName, (int[]) lhs, (int[]) rhs);
    519            }
    520            if (test instanceof long[]) {
    521                return append(fieldName, (long[]) lhs, (long[]) rhs);
    522            }
    523            if (test instanceof short[]) {
    524                return append(fieldName, (short[]) lhs, (short[]) rhs);
    525            }
    526            return append(fieldName, (Object[]) lhs, (Object[]) rhs);
    527        }
    528        // Not array type
    529        return Objects.equals(lhs, rhs) ? this : add(fieldName, () -> lhs, () -> rhs, Object.class);
    530    }
    531
    532    /**
    533     * Tests if two {@code Object[]}s are equal.
    534     *
    535     * @param fieldName the field name
    536     * @param lhs       the left-hand side {@code Object[]}
    537     * @param rhs       the right-hand side {@code Object[]}
    538     * @return {@code this} instance.
    539     * @throws NullPointerException if field name is {@code null}
    540     */
    541    public DiffBuilder<T> append(final String fieldName, final Object[] lhs, final Object[] rhs) {
    542        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> lhs, () -> rhs, Object[].class);
    543    }
    544
    545    /**
    546     * Tests if two {@code short}s are equal.
    547     *
    548     * @param fieldName the field name
    549     * @param lhs       the left-hand side {@code short}
    550     * @param rhs       the right-hand side {@code short}
    551     * @return {@code this} instance.
    552     * @throws NullPointerException if field name is {@code null}
    553     */
    554    public DiffBuilder<T> append(final String fieldName, final short lhs, final short rhs) {
    555        return equals || lhs == rhs ? this : add(fieldName, () -> Short.valueOf(lhs), () -> Short.valueOf(rhs), Short.class);
    556    }
    557
    558    /**
    559     * Tests if two {@code short[]}s are equal.
    560     *
    561     * @param fieldName the field name
    562     * @param lhs       the left-hand side {@code short[]}
    563     * @param rhs       the right-hand side {@code short[]}
    564     * @return {@code this} instance.
    565     * @throws NullPointerException if field name is {@code null}
    566     */
    567    public DiffBuilder<T> append(final String fieldName, final short[] lhs, final short[] rhs) {
    568        return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Short[].class);
    569    }
    570
    571    /**
    572     * Builds a {@link DiffResult} based on the differences appended to this builder.
    573     *
    574     * @return a {@link DiffResult} containing the differences between the two objects.
    575     */
    576    @Override
    577    public DiffResult<T> build() {
    578        return new DiffResult<>(left, right, diffs, style, toStringFormat);
    579    }
    580
    581    /**
    582     * Gets the left object.
    583     *
    584     * @return the left object.
    585     */
    586    T getLeft() {
    587        return left;
    588    }
    589
    590    /**
    591     * Gets the right object.
    592     *
    593     * @return the right object.
    594     */
    595    T getRight() {
    596        return right;
    597    }
    598
    599}