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 * Constructs a new instance. 092 */ 093 public Builder() { 094 // empty 095 } 096 097 /** 098 * Builds a new configured {@link DiffBuilder}. 099 * 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}