View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3.builder;
18  
19  import java.io.Serializable;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.List;
23  import java.util.Objects;
24  import java.util.function.Supplier;
25  
26  import org.apache.commons.lang3.ArrayUtils;
27  import org.apache.commons.lang3.ObjectUtils;
28  
29  /**
30   * Assists in implementing {@link Diffable#diff(Object)} methods.
31   *
32   * <p>
33   * To use this class, write code as follows:
34   * </p>
35   *
36   * <pre>{@code
37   * public class Person implements Diffable<Person> {
38   *   String name;
39   *   int age;
40   *   boolean smoker;
41   *
42   *   ...
43   *
44   *   public DiffResult<Person> diff(Person obj) {
45   *     // No need for null check, as NullPointerException correct if obj is null
46   *     return DiffBuilder.<Person>builder()
47   *         .setLeft(this)
48   *         .setRight(obj)
49   *         .setStyle(ToStringStyle.SHORT_PREFIX_STYLE)
50   *         .build()
51   *       .append("name", this.name, obj.name)
52   *       .append("age", this.age, obj.age)
53   *       .append("smoker", this.smoker, obj.smoker)
54   *       .build();
55   *   }
56   * }
57   * }</pre>
58   *
59   * <p>
60   * The {@link ToStringStyle} passed to the constructor is embedded in the returned {@link DiffResult} and influences the style of the
61   * {@code DiffResult.toString()} method. This style choice can be overridden by calling {@link DiffResult#toString(ToStringStyle)}.
62   * </p>
63   * <p>
64   * See {@link ReflectionDiffBuilder} for a reflection based version of this class.
65   * </p>
66   *
67   * @param <T> type of the left and right object.
68   * @see Diffable
69   * @see Diff
70   * @see DiffResult
71   * @see ToStringStyle
72   * @see ReflectionDiffBuilder
73   * @since 3.3
74   */
75  public class DiffBuilder<T> implements Builder<DiffResult<T>> {
76  
77      /**
78       * Constructs a new instance.
79       *
80       * @param <T> type of the left and right object.
81       * @since 3.15.0
82       */
83      public static final class Builder<T> {
84  
85          private T left;
86          private T right;
87          private ToStringStyle style;
88          private boolean testObjectsEquals = true;
89          private String toStringFormat = TO_STRING_FORMAT;
90  
91          /**
92           * Constructs a new instance.
93           */
94          public Builder() {
95              // empty
96          }
97  
98          /**
99           * 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);
176             this.rightSupplier = Objects.requireNonNull(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 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 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         this.equals = testObjectsEquals && Objects.equals(left, right);
274     }
275 
276     private <F> DiffBuilder<T> add(final String fieldName, final SerializableSupplier<F> left, final SerializableSupplier<F> right, final Class<F> type) {
277         diffs.add(new SDiff<>(fieldName, left, right, type));
278         return this;
279     }
280 
281     /**
282      * Tests if two {@code boolean}s are equal.
283      *
284      * @param fieldName the field name
285      * @param lhs       the left-hand side {@code boolean}
286      * @param rhs       the right-hand side {@code boolean}
287      * @return {@code this} instance.
288      * @throws NullPointerException if field name is {@code null}
289      */
290     public DiffBuilder<T> append(final String fieldName, final boolean lhs, final boolean rhs) {
291         return equals || lhs == rhs ? this : add(fieldName, () -> Boolean.valueOf(lhs), () -> Boolean.valueOf(rhs), Boolean.class);
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 if field name is {@code null}
302      */
303     public DiffBuilder<T> append(final String fieldName, final boolean[] lhs, final boolean[] rhs) {
304         return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Boolean[].class);
305     }
306 
307     /**
308      * Tests if two {@code byte}s are equal.
309      *
310      * @param fieldName the field name
311      * @param lhs       the left-hand side {@code byte}
312      * @param rhs       the right-hand side {@code byte}
313      * @return {@code this} instance.
314      * @throws NullPointerException if field name is {@code null}
315      */
316     public DiffBuilder<T> append(final String fieldName, final byte lhs, final byte rhs) {
317         return equals || lhs == rhs ? this : add(fieldName, () -> Byte.valueOf(lhs), () -> Byte.valueOf(rhs), Byte.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 if field name is {@code null}
328      */
329     public DiffBuilder<T> append(final String fieldName, final byte[] lhs, final byte[] rhs) {
330         return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Byte[].class);
331     }
332 
333     /**
334      * Tests if two {@code char}s are equal.
335      *
336      * @param fieldName the field name
337      * @param lhs       the left-hand side {@code char}
338      * @param rhs       the right-hand side {@code char}
339      * @return {@code this} instance.
340      * @throws NullPointerException if field name is {@code null}
341      */
342     public DiffBuilder<T> append(final String fieldName, final char lhs, final char rhs) {
343         return equals || lhs == rhs ? this : add(fieldName, () -> Character.valueOf(lhs), () -> Character.valueOf(rhs), Character.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 if field name is {@code null}
354      */
355     public DiffBuilder<T> append(final String fieldName, final char[] lhs, final char[] rhs) {
356         return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Character[].class);
357     }
358 
359     /**
360      * Appends diffs from another {@link DiffResult}.
361      *
362      * <p>
363      * Useful this method to compare properties which are themselves Diffable and would like to know which specific part of it is different.
364      * </p>
365      *
366      * <pre>{@code
367      * public class Person implements Diffable<Person> {
368      *   String name;
369      *   Address address; // implements Diffable<Address>
370      *
371      *   ...
372      *
373      *   public DiffResult diff(Person obj) {
374      *     return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
375      *       .append("name", this.name, obj.name)
376      *       .append("address", this.address.diff(obj.address))
377      *       .build();
378      *   }
379      * }
380      * }
381      * </pre>
382      *
383      * @param fieldName  the field name
384      * @param diffResult the {@link DiffResult} to append
385      * @return {@code this} instance.
386      * @throws NullPointerException if field name is {@code null} or diffResult is {@code null}
387      * @since 3.5
388      */
389     public DiffBuilder<T> append(final String fieldName, final DiffResult<?> diffResult) {
390         Objects.requireNonNull(diffResult, "diffResult");
391         if (equals) {
392             return this;
393         }
394         diffResult.getDiffs().forEach(diff -> append(fieldName + "." + diff.getFieldName(), diff.getLeft(), diff.getRight()));
395         return this;
396     }
397 
398     /**
399      * Tests if two {@code double}s are equal.
400      *
401      * @param fieldName the field name
402      * @param lhs       the left-hand side {@code double}
403      * @param rhs       the right-hand side {@code double}
404      * @return {@code this} instance.
405      * @throws NullPointerException if field name is {@code null}
406      */
407     public DiffBuilder<T> append(final String fieldName, final double lhs, final double rhs) {
408         return equals || Double.doubleToLongBits(lhs) == Double.doubleToLongBits(rhs) ? this
409                 : add(fieldName, () -> Double.valueOf(lhs), () -> Double.valueOf(rhs), Double.class);
410     }
411 
412     /**
413      * Tests if two {@code double[]}s are equal.
414      *
415      * @param fieldName the field name
416      * @param lhs       the left-hand side {@code double[]}
417      * @param rhs       the right-hand side {@code double[]}
418      * @return {@code this} instance.
419      * @throws NullPointerException if field name is {@code null}
420      */
421     public DiffBuilder<T> append(final String fieldName, final double[] lhs, final double[] rhs) {
422         return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Double[].class);
423     }
424 
425     /**
426      * Test if two {@code float}s are equal.
427      *
428      * @param fieldName the field name
429      * @param lhs       the left-hand side {@code float}
430      * @param rhs       the right-hand side {@code float}
431      * @return {@code this} instance.
432      * @throws NullPointerException if field name is {@code null}
433      */
434     public DiffBuilder<T> append(final String fieldName, final float lhs, final float rhs) {
435         return equals || Float.floatToIntBits(lhs) == Float.floatToIntBits(rhs) ? this
436                 : add(fieldName, () -> Float.valueOf(lhs), () -> Float.valueOf(rhs), Float.class);
437     }
438 
439     /**
440      * Tests if two {@code float[]}s are equal.
441      *
442      * @param fieldName the field name
443      * @param lhs       the left-hand side {@code float[]}
444      * @param rhs       the right-hand side {@code float[]}
445      * @return {@code this} instance.
446      * @throws NullPointerException if field name is {@code null}
447      */
448     public DiffBuilder<T> append(final String fieldName, final float[] lhs, final float[] rhs) {
449         return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Float[].class);
450     }
451 
452     /**
453      * Tests if two {@code int}s are equal.
454      *
455      * @param fieldName the field name
456      * @param lhs       the left-hand side {@code int}
457      * @param rhs       the right-hand side {@code int}
458      * @return {@code this} instance.
459      * @throws NullPointerException if field name is {@code null}
460      */
461     public DiffBuilder<T> append(final String fieldName, final int lhs, final int rhs) {
462         return equals || lhs == rhs ? this : add(fieldName, () -> Integer.valueOf(lhs), () -> Integer.valueOf(rhs), Integer.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 if field name is {@code null}
473      */
474     public DiffBuilder<T> append(final String fieldName, final int[] lhs, final int[] rhs) {
475         return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Integer[].class);
476     }
477 
478     /**
479      * Tests if two {@code long}s are equal.
480      *
481      * @param fieldName the field name
482      * @param lhs       the left-hand side {@code long}
483      * @param rhs       the right-hand side {@code long}
484      * @return {@code this} instance.
485      * @throws NullPointerException if field name is {@code null}
486      */
487     public DiffBuilder<T> append(final String fieldName, final long lhs, final long rhs) {
488         return equals || lhs == rhs ? this : add(fieldName, () -> Long.valueOf(lhs), () -> Long.valueOf(rhs), Long.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 if field name is {@code null}
499      */
500     public DiffBuilder<T> append(final String fieldName, final long[] lhs, final long[] rhs) {
501         return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Long[].class);
502     }
503 
504     /**
505      * Tests if two {@link Objects}s are equal.
506      *
507      * @param fieldName the field name
508      * @param lhs       the left-hand side {@link Object}
509      * @param rhs       the right-hand side {@link Object}
510      * @return {@code this} instance.
511      * @throws NullPointerException if field name is {@code null}
512      */
513     public DiffBuilder<T> append(final String fieldName, final Object lhs, final Object rhs) {
514         if (equals || lhs == rhs) {
515             return this;
516         }
517         // rhs cannot be null, as lhs != rhs
518         final Object test = lhs != null ? lhs : rhs;
519         if (ObjectUtils.isArray(test)) {
520             if (test instanceof boolean[]) {
521                 return append(fieldName, (boolean[]) lhs, (boolean[]) rhs);
522             }
523             if (test instanceof byte[]) {
524                 return append(fieldName, (byte[]) lhs, (byte[]) rhs);
525             }
526             if (test instanceof char[]) {
527                 return append(fieldName, (char[]) lhs, (char[]) rhs);
528             }
529             if (test instanceof double[]) {
530                 return append(fieldName, (double[]) lhs, (double[]) rhs);
531             }
532             if (test instanceof float[]) {
533                 return append(fieldName, (float[]) lhs, (float[]) rhs);
534             }
535             if (test instanceof int[]) {
536                 return append(fieldName, (int[]) lhs, (int[]) rhs);
537             }
538             if (test instanceof long[]) {
539                 return append(fieldName, (long[]) lhs, (long[]) rhs);
540             }
541             if (test instanceof short[]) {
542                 return append(fieldName, (short[]) lhs, (short[]) rhs);
543             }
544             return append(fieldName, (Object[]) lhs, (Object[]) rhs);
545         }
546         // Not array type
547         return Objects.equals(lhs, rhs) ? this : add(fieldName, () -> lhs, () -> rhs, Object.class);
548     }
549 
550     /**
551      * Tests if two {@code Object[]}s are equal.
552      *
553      * @param fieldName the field name
554      * @param lhs       the left-hand side {@code Object[]}
555      * @param rhs       the right-hand side {@code Object[]}
556      * @return {@code this} instance.
557      * @throws NullPointerException if field name is {@code null}
558      */
559     public DiffBuilder<T> append(final String fieldName, final Object[] lhs, final Object[] rhs) {
560         return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> lhs, () -> rhs, Object[].class);
561     }
562 
563     /**
564      * Tests if two {@code short}s are equal.
565      *
566      * @param fieldName the field name
567      * @param lhs       the left-hand side {@code short}
568      * @param rhs       the right-hand side {@code short}
569      * @return {@code this} instance.
570      * @throws NullPointerException if field name is {@code null}
571      */
572     public DiffBuilder<T> append(final String fieldName, final short lhs, final short rhs) {
573         return equals || lhs == rhs ? this : add(fieldName, () -> Short.valueOf(lhs), () -> Short.valueOf(rhs), Short.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 if field name is {@code null}
584      */
585     public DiffBuilder<T> append(final String fieldName, final short[] lhs, final short[] rhs) {
586         return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Short[].class);
587     }
588 
589     /**
590      * Builds a {@link DiffResult} based on the differences appended to this builder.
591      *
592      * @return a {@link DiffResult} containing the differences between the two objects.
593      */
594     @Override
595     public DiffResult<T> build() {
596         return new DiffResult<>(left, right, diffs, style, toStringFormat);
597     }
598 
599     /**
600      * Gets the left object.
601      *
602      * @return the left object.
603      */
604     T getLeft() {
605         return left;
606     }
607 
608     /**
609      * Gets the right object.
610      *
611      * @return the right object.
612      */
613     T getRight() {
614         return right;
615     }
616 
617 }