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 * http://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.util.ArrayList; 020import java.util.Arrays; 021import java.util.List; 022import java.util.Objects; 023import java.util.function.Supplier; 024 025import org.apache.commons.lang3.ArrayUtils; 026import org.apache.commons.lang3.ObjectUtils; 027 028/** 029 * Assists in implementing {@link Diffable#diff(Object)} methods. 030 * 031 * <p> 032 * To use this class, write code as follows: 033 * </p> 034 * 035 * <pre>{@code 036 * public class Person implements Diffable<Person> { 037 * String name; 038 * int age; 039 * boolean smoker; 040 * 041 * ... 042 * 043 * public DiffResult diff(Person obj) { 044 * // No need for null check, as NullPointerException correct if obj is null 045 * return new DiffBuilder.<Person>builder() 046 * .setLeft(this) 047 * .setRight(obj) 048 * .setStyle(ToStringStyle.SHORT_PREFIX_STYLE)) 049 * .build() 050 * .append("name", this.name, obj.name) 051 * .append("age", this.age, obj.age) 052 * .append("smoker", this.smoker, obj.smoker) 053 * .build(); 054 * } 055 * } 056 * }</pre> 057 * 058 * <p> 059 * The {@link ToStringStyle} passed to the constructor is embedded in the returned {@link DiffResult} and influences the style of the 060 * {@code DiffResult.toString()} method. This style choice can be overridden by calling {@link DiffResult#toString(ToStringStyle)}. 061 * </p> 062 * <p> 063 * See {@link ReflectionDiffBuilder} for a reflection based version of this class. 064 * </p> 065 * 066 * @param <T> type of the left and right object. 067 * @see Diffable 068 * @see Diff 069 * @see DiffResult 070 * @see ToStringStyle 071 * @see ReflectionDiffBuilder 072 * @since 3.3 073 */ 074public class DiffBuilder<T> implements Builder<DiffResult<T>> { 075 076 /** 077 * Constructs a new instance. 078 * 079 * @param <T> type of the left and right object. 080 * @since 3.15.0 081 */ 082 public static final class Builder<T> { 083 084 private T left; 085 private T right; 086 private ToStringStyle style; 087 private boolean testObjectsEquals = true; 088 private String toStringFormat = TO_STRING_FORMAT; 089 090 /** 091 * Builds a new configured {@link DiffBuilder}. 092 * 093 * @return a new configured {@link DiffBuilder}. 094 */ 095 public DiffBuilder<T> build() { 096 return new DiffBuilder<>(left, right, style, testObjectsEquals, toStringFormat); 097 } 098 099 /** 100 * Sets the left object. 101 * 102 * @param left the left object. 103 * @return {@code this} instance. 104 */ 105 public Builder<T> setLeft(final T left) { 106 this.left = left; 107 return this; 108 } 109 110 /** 111 * Sets the right object. 112 * 113 * @param right the left object. 114 * @return {@code this} instance. 115 */ 116 public Builder<T> setRight(final T right) { 117 this.right = right; 118 return this; 119 } 120 121 /** 122 * Sets the style will to use when outputting the objects, {@code null} uses the default. 123 * 124 * @param style the style to use when outputting the objects, {@code null} uses the default. 125 * @return {@code this} instance. 126 */ 127 public Builder<T> setStyle(final ToStringStyle style) { 128 this.style = style != null ? style : ToStringStyle.DEFAULT_STYLE; 129 return this; 130 } 131 132 /** 133 * 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 134 * {@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 135 * {@link DiffBuilder}. 136 * 137 * @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 138 * without creating a field {@link Diff} if the trivially equal test is enabled and returns true. The result of this test is 139 * never changed throughout the life of this {@link DiffBuilder}. 140 * @return {@code this} instance. 141 */ 142 public Builder<T> setTestObjectsEquals(final boolean testObjectsEquals) { 143 this.testObjectsEquals = testObjectsEquals; 144 return this; 145 } 146 147 /** 148 * Sets the two-argument format string for {@link String#format(String, Object...)}, for example {@code "%s differs from %s"}. 149 * 150 * @param toStringFormat {@code null} uses the default. 151 * @return {@code this} instance. 152 */ 153 public Builder<T> setToStringFormat(final String toStringFormat) { 154 this.toStringFormat = toStringFormat != null ? toStringFormat : TO_STRING_FORMAT; 155 return this; 156 } 157 } 158 159 private static final class SDiff<T> extends Diff<T> { 160 161 private static final long serialVersionUID = 1L; 162 private final transient Supplier<T> leftSupplier; 163 private final transient Supplier<T> rightSupplier; 164 165 private SDiff(final String fieldName, final Supplier<T> leftSupplier, final Supplier<T> rightSupplier, final Class<T> type) { 166 super(fieldName, type); 167 this.leftSupplier = Objects.requireNonNull(leftSupplier); 168 this.rightSupplier = Objects.requireNonNull(rightSupplier); 169 } 170 171 @Override 172 public T getLeft() { 173 return leftSupplier.get(); 174 } 175 176 @Override 177 public T getRight() { 178 return rightSupplier.get(); 179 } 180 181 } 182 183 static final String TO_STRING_FORMAT = "%s differs from %s"; 184 185 /** 186 * Constructs a new {@link Builder}. 187 * 188 * @param <T> type of the left and right object. 189 * @return a new {@link Builder}. 190 * @since 3.15.0 191 */ 192 public static <T> Builder<T> builder() { 193 return new Builder<>(); 194 } 195 196 private final List<Diff<?>> diffs; 197 private final boolean equals; 198 private final T left; 199 private final T right; 200 private final ToStringStyle style; 201 private final String toStringFormat; 202 203 /** 204 * Constructs a builder for the specified objects with the specified style. 205 * 206 * <p> 207 * 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 208 * {@link DiffResult} when {@link #build()} is executed. 209 * </p> 210 * 211 * <p> 212 * This delegates to {@link #DiffBuilder(Object, Object, ToStringStyle, boolean)} with the testTriviallyEqual flag enabled. 213 * </p> 214 * 215 * @param left {@code this} object 216 * @param right the object to diff against 217 * @param style the style to use when outputting the objects, {@code null} uses the default 218 * @throws NullPointerException if {@code lhs} or {@code rhs} is {@code null} 219 * @deprecated Use {@link Builder}. 220 */ 221 @Deprecated 222 public DiffBuilder(final T left, final T right, final ToStringStyle style) { 223 this(left, right, style, true); 224 } 225 226 /** 227 * Constructs a builder for the specified objects with the specified style. 228 * 229 * <p> 230 * 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 231 * {@link DiffResult} when {@link #build()} is executed. 232 * </p> 233 * 234 * @param left {@code this} object 235 * @param right the object to diff against 236 * @param style the style to use when outputting the objects, {@code null} uses the default 237 * @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 238 * creating a field {@link Diff} if the trivially equal test is enabled and returns true. The result of this test is never changed 239 * throughout the life of this {@link DiffBuilder}. 240 * @throws NullPointerException if {@code lhs} or {@code rhs} is {@code null} 241 * @since 3.4 242 * @deprecated Use {@link Builder}. 243 */ 244 @Deprecated 245 public DiffBuilder(final T left, final T right, final ToStringStyle style, final boolean testObjectsEquals) { 246 this(left, right, style, testObjectsEquals, TO_STRING_FORMAT); 247 } 248 249 private DiffBuilder(final T left, final T right, final ToStringStyle style, final boolean testObjectsEquals, final String toStringFormat) { 250 this.left = Objects.requireNonNull(left, "left"); 251 this.right = Objects.requireNonNull(right, "right"); 252 this.diffs = new ArrayList<>(); 253 this.toStringFormat = toStringFormat; 254 this.style = style != null ? style : ToStringStyle.DEFAULT_STYLE; 255 // Don't compare any fields if objects equal 256 this.equals = testObjectsEquals && Objects.equals(left, right); 257 } 258 259 private <F> DiffBuilder<T> add(final String fieldName, final Supplier<F> left, final Supplier<F> right, final Class<F> type) { 260 diffs.add(new SDiff<>(fieldName, left, right, type)); 261 return this; 262 } 263 264 /** 265 * Tests if two {@code boolean}s are equal. 266 * 267 * @param fieldName the field name 268 * @param lhs the left-hand side {@code boolean} 269 * @param rhs the right-hand side {@code boolean} 270 * @return {@code this} instance. 271 * @throws NullPointerException if field name is {@code null} 272 */ 273 public DiffBuilder<T> append(final String fieldName, final boolean lhs, final boolean rhs) { 274 return equals || lhs == rhs ? this : add(fieldName, () -> Boolean.valueOf(lhs), () -> Boolean.valueOf(rhs), Boolean.class); 275 } 276 277 /** 278 * Tests if two {@code boolean[]}s are equal. 279 * 280 * @param fieldName the field name 281 * @param lhs the left-hand side {@code boolean[]} 282 * @param rhs the right-hand side {@code boolean[]} 283 * @return {@code this} instance. 284 * @throws NullPointerException if field name is {@code null} 285 */ 286 public DiffBuilder<T> append(final String fieldName, final boolean[] lhs, final boolean[] rhs) { 287 return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Boolean[].class); 288 } 289 290 /** 291 * Tests if two {@code byte}s are equal. 292 * 293 * @param fieldName the field name 294 * @param lhs the left-hand side {@code byte} 295 * @param rhs the right-hand side {@code byte} 296 * @return {@code this} instance. 297 * @throws NullPointerException if field name is {@code null} 298 */ 299 public DiffBuilder<T> append(final String fieldName, final byte lhs, final byte rhs) { 300 return equals || lhs == rhs ? this : add(fieldName, () -> Byte.valueOf(lhs), () -> Byte.valueOf(rhs), Byte.class); 301 } 302 303 /** 304 * Tests if two {@code byte[]}s are equal. 305 * 306 * @param fieldName the field name 307 * @param lhs the left-hand side {@code byte[]} 308 * @param rhs the right-hand side {@code byte[]} 309 * @return {@code this} instance. 310 * @throws NullPointerException if field name is {@code null} 311 */ 312 public DiffBuilder<T> append(final String fieldName, final byte[] lhs, final byte[] rhs) { 313 return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Byte[].class); 314 } 315 316 /** 317 * Tests if two {@code char}s are equal. 318 * 319 * @param fieldName the field name 320 * @param lhs the left-hand side {@code char} 321 * @param rhs the right-hand side {@code char} 322 * @return {@code this} instance. 323 * @throws NullPointerException if field name is {@code null} 324 */ 325 public DiffBuilder<T> append(final String fieldName, final char lhs, final char rhs) { 326 return equals || lhs == rhs ? this : add(fieldName, () -> Character.valueOf(lhs), () -> Character.valueOf(rhs), Character.class); 327 } 328 329 /** 330 * Tests if two {@code char[]}s are equal. 331 * 332 * @param fieldName the field name 333 * @param lhs the left-hand side {@code char[]} 334 * @param rhs the right-hand side {@code char[]} 335 * @return {@code this} instance. 336 * @throws NullPointerException if field name is {@code null} 337 */ 338 public DiffBuilder<T> append(final String fieldName, final char[] lhs, final char[] rhs) { 339 return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Character[].class); 340 } 341 342 /** 343 * Appends diffs from another {@link DiffResult}. 344 * 345 * <p> 346 * Useful this method to compare properties which are themselves Diffable and would like to know which specific part of it is different. 347 * </p> 348 * 349 * <pre> 350 * public class Person implements Diffable<Person> { 351 * String name; 352 * Address address; // implements Diffable<Address> 353 * 354 * ... 355 * 356 * public DiffResult diff(Person obj) { 357 * return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE) 358 * .append("name", this.name, obj.name) 359 * .append("address", this.address.diff(obj.address)) 360 * .build(); 361 * } 362 * } 363 * </pre> 364 * 365 * @param fieldName the field name 366 * @param diffResult the {@link DiffResult} to append 367 * @return {@code this} instance. 368 * @throws NullPointerException if field name is {@code null} or diffResult is {@code null} 369 * @since 3.5 370 */ 371 public DiffBuilder<T> append(final String fieldName, final DiffResult<?> diffResult) { 372 Objects.requireNonNull(diffResult, "diffResult"); 373 if (equals) { 374 return this; 375 } 376 diffResult.getDiffs().forEach(diff -> append(fieldName + "." + diff.getFieldName(), diff.getLeft(), diff.getRight())); 377 return this; 378 } 379 380 /** 381 * Tests if two {@code double}s are equal. 382 * 383 * @param fieldName the field name 384 * @param lhs the left-hand side {@code double} 385 * @param rhs the right-hand side {@code double} 386 * @return {@code this} instance. 387 * @throws NullPointerException if field name is {@code null} 388 */ 389 public DiffBuilder<T> append(final String fieldName, final double lhs, final double rhs) { 390 return equals || Double.doubleToLongBits(lhs) == Double.doubleToLongBits(rhs) ? this 391 : add(fieldName, () -> Double.valueOf(lhs), () -> Double.valueOf(rhs), Double.class); 392 } 393 394 /** 395 * Tests if two {@code double[]}s are equal. 396 * 397 * @param fieldName the field name 398 * @param lhs the left-hand side {@code double[]} 399 * @param rhs the right-hand side {@code double[]} 400 * @return {@code this} instance. 401 * @throws NullPointerException if field name is {@code null} 402 */ 403 public DiffBuilder<T> append(final String fieldName, final double[] lhs, final double[] rhs) { 404 return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Double[].class); 405 } 406 407 /** 408 * Test if two {@code float}s are equal. 409 * 410 * @param fieldName the field name 411 * @param lhs the left-hand side {@code float} 412 * @param rhs the right-hand side {@code float} 413 * @return {@code this} instance. 414 * @throws NullPointerException if field name is {@code null} 415 */ 416 public DiffBuilder<T> append(final String fieldName, final float lhs, final float rhs) { 417 return equals || Float.floatToIntBits(lhs) == Float.floatToIntBits(rhs) ? this 418 : add(fieldName, () -> Float.valueOf(lhs), () -> Float.valueOf(rhs), Float.class); 419 } 420 421 /** 422 * Tests if two {@code float[]}s are equal. 423 * 424 * @param fieldName the field name 425 * @param lhs the left-hand side {@code float[]} 426 * @param rhs the right-hand side {@code float[]} 427 * @return {@code this} instance. 428 * @throws NullPointerException if field name is {@code null} 429 */ 430 public DiffBuilder<T> append(final String fieldName, final float[] lhs, final float[] rhs) { 431 return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Float[].class); 432 } 433 434 /** 435 * Tests if two {@code int}s are equal. 436 * 437 * @param fieldName the field name 438 * @param lhs the left-hand side {@code int} 439 * @param rhs the right-hand side {@code int} 440 * @return {@code this} instance. 441 * @throws NullPointerException if field name is {@code null} 442 */ 443 public DiffBuilder<T> append(final String fieldName, final int lhs, final int rhs) { 444 return equals || lhs == rhs ? this : add(fieldName, () -> Integer.valueOf(lhs), () -> Integer.valueOf(rhs), Integer.class); 445 } 446 447 /** 448 * Tests if two {@code int[]}s are equal. 449 * 450 * @param fieldName the field name 451 * @param lhs the left-hand side {@code int[]} 452 * @param rhs the right-hand side {@code int[]} 453 * @return {@code this} instance. 454 * @throws NullPointerException if field name is {@code null} 455 */ 456 public DiffBuilder<T> append(final String fieldName, final int[] lhs, final int[] rhs) { 457 return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Integer[].class); 458 } 459 460 /** 461 * Tests if two {@code long}s are equal. 462 * 463 * @param fieldName the field name 464 * @param lhs the left-hand side {@code long} 465 * @param rhs the right-hand side {@code long} 466 * @return {@code this} instance. 467 * @throws NullPointerException if field name is {@code null} 468 */ 469 public DiffBuilder<T> append(final String fieldName, final long lhs, final long rhs) { 470 return equals || lhs == rhs ? this : add(fieldName, () -> Long.valueOf(lhs), () -> Long.valueOf(rhs), Long.class); 471 } 472 473 /** 474 * Tests if two {@code long[]}s are equal. 475 * 476 * @param fieldName the field name 477 * @param lhs the left-hand side {@code long[]} 478 * @param rhs the right-hand side {@code long[]} 479 * @return {@code this} instance. 480 * @throws NullPointerException if field name is {@code null} 481 */ 482 public DiffBuilder<T> append(final String fieldName, final long[] lhs, final long[] rhs) { 483 return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Long[].class); 484 } 485 486 /** 487 * Tests if two {@link Objects}s are equal. 488 * 489 * @param fieldName the field name 490 * @param lhs the left-hand side {@link Object} 491 * @param rhs the right-hand side {@link Object} 492 * @return {@code this} instance. 493 * @throws NullPointerException if field name is {@code null} 494 */ 495 public DiffBuilder<T> append(final String fieldName, final Object lhs, final Object rhs) { 496 if (equals || lhs == rhs) { 497 return this; 498 } 499 // rhs cannot be null, as lhs != rhs 500 final Object test = lhs != null ? lhs : rhs; 501 if (ObjectUtils.isArray(test)) { 502 if (test instanceof boolean[]) { 503 return append(fieldName, (boolean[]) lhs, (boolean[]) rhs); 504 } 505 if (test instanceof byte[]) { 506 return append(fieldName, (byte[]) lhs, (byte[]) rhs); 507 } 508 if (test instanceof char[]) { 509 return append(fieldName, (char[]) lhs, (char[]) rhs); 510 } 511 if (test instanceof double[]) { 512 return append(fieldName, (double[]) lhs, (double[]) rhs); 513 } 514 if (test instanceof float[]) { 515 return append(fieldName, (float[]) lhs, (float[]) rhs); 516 } 517 if (test instanceof int[]) { 518 return append(fieldName, (int[]) lhs, (int[]) rhs); 519 } 520 if (test instanceof long[]) { 521 return append(fieldName, (long[]) lhs, (long[]) rhs); 522 } 523 if (test instanceof short[]) { 524 return append(fieldName, (short[]) lhs, (short[]) rhs); 525 } 526 return append(fieldName, (Object[]) lhs, (Object[]) rhs); 527 } 528 // Not array type 529 return Objects.equals(lhs, rhs) ? this : add(fieldName, () -> lhs, () -> rhs, Object.class); 530 } 531 532 /** 533 * Tests if two {@code Object[]}s are equal. 534 * 535 * @param fieldName the field name 536 * @param lhs the left-hand side {@code Object[]} 537 * @param rhs the right-hand side {@code Object[]} 538 * @return {@code this} instance. 539 * @throws NullPointerException if field name is {@code null} 540 */ 541 public DiffBuilder<T> append(final String fieldName, final Object[] lhs, final Object[] rhs) { 542 return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> lhs, () -> rhs, Object[].class); 543 } 544 545 /** 546 * Tests if two {@code short}s are equal. 547 * 548 * @param fieldName the field name 549 * @param lhs the left-hand side {@code short} 550 * @param rhs the right-hand side {@code short} 551 * @return {@code this} instance. 552 * @throws NullPointerException if field name is {@code null} 553 */ 554 public DiffBuilder<T> append(final String fieldName, final short lhs, final short rhs) { 555 return equals || lhs == rhs ? this : add(fieldName, () -> Short.valueOf(lhs), () -> Short.valueOf(rhs), Short.class); 556 } 557 558 /** 559 * Tests if two {@code short[]}s are equal. 560 * 561 * @param fieldName the field name 562 * @param lhs the left-hand side {@code short[]} 563 * @param rhs the right-hand side {@code short[]} 564 * @return {@code this} instance. 565 * @throws NullPointerException if field name is {@code null} 566 */ 567 public DiffBuilder<T> append(final String fieldName, final short[] lhs, final short[] rhs) { 568 return equals || Arrays.equals(lhs, rhs) ? this : add(fieldName, () -> ArrayUtils.toObject(lhs), () -> ArrayUtils.toObject(rhs), Short[].class); 569 } 570 571 /** 572 * Builds a {@link DiffResult} based on the differences appended to this builder. 573 * 574 * @return a {@link DiffResult} containing the differences between the two objects. 575 */ 576 @Override 577 public DiffResult<T> build() { 578 return new DiffResult<>(left, right, diffs, style, toStringFormat); 579 } 580 581 /** 582 * Gets the left object. 583 * 584 * @return the left object. 585 */ 586 T getLeft() { 587 return left; 588 } 589 590 /** 591 * Gets the right object. 592 * 593 * @return the right object. 594 */ 595 T getRight() { 596 return right; 597 } 598 599}