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}