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); 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}