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, "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 }