001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.lang3.builder; 018 019import java.io.Serializable; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.List; 023import java.util.Objects; 024import java.util.function.Supplier; 025 026import org.apache.commons.lang3.ArrayUtils; 027import org.apache.commons.lang3.ObjectUtils; 028 029/** 030 * Assists in implementing {@link Diffable#diff(Object)} methods. 031 * 032 * <p> 033 * To use this class, write code as follows: 034 * </p> 035 * 036 * <pre>{@code 037 * public class Person implements Diffable<Person> { 038 * String name; 039 * int age; 040 * boolean smoker; 041 * 042 * ... 043 * 044 * public DiffResult<Person> diff(Person obj) { 045 * // No need for null check, as NullPointerException correct if obj is null 046 * return DiffBuilder.<Person>builder() 047 * .setLeft(this) 048 * .setRight(obj) 049 * .setStyle(ToStringStyle.SHORT_PREFIX_STYLE) 050 * .build() 051 * .append("name", this.name, obj.name) 052 * .append("age", this.age, obj.age) 053 * .append("smoker", this.smoker, obj.smoker) 054 * .build(); 055 * } 056 * } 057 * }</pre> 058 * 059 * <p> 060 * The {@link ToStringStyle} passed to the constructor is embedded in the returned {@link DiffResult} and influences the style of the 061 * {@code DiffResult.toString()} method. This style choice can be overridden by calling {@link DiffResult#toString(ToStringStyle)}. 062 * </p> 063 * <p> 064 * See {@link ReflectionDiffBuilder} for a reflection based version of this class. 065 * </p> 066 * 067 * @param <T> type of the left and right object. 068 * @see Diffable 069 * @see Diff 070 * @see DiffResult 071 * @see ToStringStyle 072 * @see ReflectionDiffBuilder 073 * @since 3.3 074 */ 075public class DiffBuilder<T> implements Builder<DiffResult<T>> { 076 077 /** 078 * Constructs a new instance. 079 * 080 * @param <T> type of the left and right object. 081 * @since 3.15.0 082 */ 083 public static final class Builder<T> { 084 085 private T left; 086 private T right; 087 private ToStringStyle style; 088 private boolean testObjectsEquals = true; 089 private String toStringFormat = TO_STRING_FORMAT; 090 091 /** 092 * Constructs a new instance. 093 */ 094 public Builder() { 095 // empty 096 } 097 098 /** 099 * 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}