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