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    *      http://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.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.List;
22  import java.util.Objects;
23  import java.util.function.Supplier;
24  
25  import org.apache.commons.lang3.ArrayUtils;
26  import org.apache.commons.lang3.ObjectUtils;
27  
28  /**
29   * Assists in implementing {@link Diffable#diff(Object)} methods.
30   *
31   * <p>
32   * To use this class, write code as follows:
33   * </p>
34   *
35   * <pre>{@code
36   * public class Person implements Diffable<Person> {
37   *   String name;
38   *   int age;
39   *   boolean smoker;
40   *
41   *   ...
42   *
43   *   public DiffResult diff(Person obj) {
44   *     // No need for null check, as NullPointerException correct if obj is null
45   *     return new DiffBuilder.<Person>builder()
46   *         .setLeft(this)
47   *         .setRight(obj)
48   *         .setStyle(ToStringStyle.SHORT_PREFIX_STYLE))
49   *         .build()
50   *       .append("name", this.name, obj.name)
51   *       .append("age", this.age, obj.age)
52   *       .append("smoker", this.smoker, obj.smoker)
53   *       .build();
54   *   }
55   * }
56   * }</pre>
57   *
58   * <p>
59   * The {@link ToStringStyle} passed to the constructor is embedded in the returned {@link DiffResult} and influences the style of the
60   * {@code DiffResult.toString()} method. This style choice can be overridden by calling {@link DiffResult#toString(ToStringStyle)}.
61   * </p>
62   * <p>
63   * See {@link ReflectionDiffBuilder} for a reflection based version of this class.
64   * </p>
65   *
66   * @param <T> type of the left and right object.
67   * @see Diffable
68   * @see Diff
69   * @see DiffResult
70   * @see ToStringStyle
71   * @see ReflectionDiffBuilder
72   * @since 3.3
73   */
74  public class DiffBuilder<T> implements Builder<DiffResult<T>> {
75  
76      /**
77       * Constructs a new instance.
78       *
79       * @param <T> type of the left and right object.
80       * @since 3.15.0
81       */
82      public static final class Builder<T> {
83  
84          private T left;
85          private T right;
86          private ToStringStyle style;
87          private boolean testObjectsEquals = true;
88          private String toStringFormat = TO_STRING_FORMAT;
89  
90          /**
91           * Constructs a new instance.
92           */
93          public Builder() {
94              // empty
95          }
96  
97          /**
98           * Builds a new configured {@link DiffBuilder}.
99           *
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 }