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 }