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}