From ea47f03c4d6fba945be43d5c676075ba98b28138 Mon Sep 17 00:00:00 2001 From: Duncan Jones Date: Fri, 24 Jan 2014 22:37:50 +0000 Subject: [PATCH] LANG-637: There should be a DifferenceBuilder with a ReflectionDifferenceBuilder implementation git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1561215 13f79535-47bb-0310-9956-ffa450edef68 --- src/changes/changes.xml | 1 + .../apache/commons/lang3/builder/Diff.java | 119 +++ .../commons/lang3/builder/DiffBuilder.java | 914 ++++++++++++++++++ .../commons/lang3/builder/DiffList.java | 208 ++++ .../commons/lang3/builder/Diffable.java | 54 ++ .../lang3/builder/DiffBuilderTest.java | 415 ++++++++ .../commons/lang3/builder/DiffListTest.java | 149 +++ .../commons/lang3/builder/DiffTest.java | 72 ++ 8 files changed, 1932 insertions(+) create mode 100644 src/main/java/org/apache/commons/lang3/builder/Diff.java create mode 100644 src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java create mode 100644 src/main/java/org/apache/commons/lang3/builder/DiffList.java create mode 100644 src/main/java/org/apache/commons/lang3/builder/Diffable.java create mode 100644 src/test/java/org/apache/commons/lang3/builder/DiffBuilderTest.java create mode 100644 src/test/java/org/apache/commons/lang3/builder/DiffListTest.java create mode 100644 src/test/java/org/apache/commons/lang3/builder/DiffTest.java diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 5803e5a5a..6c6e00907 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -22,6 +22,7 @@ + There should be a DifferenceBuilder with a ReflectionDifferenceBuilder implementation uncaught PatternSyntaxException in FastDateFormat on Android Improve JavaDoc of WordUtils.wrap methods Add the Jaro-Winkler string distance algorithm to StringUtils diff --git a/src/main/java/org/apache/commons/lang3/builder/Diff.java b/src/main/java/org/apache/commons/lang3/builder/Diff.java new file mode 100644 index 000000000..6aa0fa360 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/builder/Diff.java @@ -0,0 +1,119 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.builder; + +import java.lang.reflect.Type; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.reflect.TypeUtils; +import org.apache.commons.lang3.tuple.Pair; + +/** + *

+ * A {@code Diff} contains the differences between two {@link Diffable} class + * fields. + *

+ * + *

+ * Typically, {@code Diff}s are retrieved by using a {@link DiffBuilder} to + * produce a {@link DiffList}, containing the differences between two objects. + *

+ * + * + * @param + * The type of object contained within this {@code Diff}. Differences + * between primitive objects are stored as their Object wrapper + * equivalent. + * @since 3.3 + * @version $Id$ + */ +public abstract class Diff extends Pair { + + private static final long serialVersionUID = 1L; + + private final Type type; + private final String fieldName; + + /** + *

+ * Constructs a new {@code Diff} for the given field name. + *

+ * + * @param fieldName + * the name of the field + */ + protected Diff(String fieldName) { + this.type = ObjectUtils.defaultIfNull( + TypeUtils.getTypeArguments(getClass(), Diff.class).get( + Diff.class.getTypeParameters()[0]), Object.class); + this.fieldName = fieldName; + } + + /** + *

+ * Returns the type of the field. + *

+ * + * @return the field type + */ + public final Type getType() { + return type; + } + + /** + *

+ * Returns the name of the field. + *

+ * + * @return the field name + */ + public final String getFieldName() { + return fieldName; + } + + /** + *

+ * Returns a {@code String} representation of the {@code Diff}, with the + * following format: + * + *

+     * [fieldname: left-value, right-value]
+     * 
+ * + *

+ * + * @return the string representation + */ + @Override + public final String toString() { + return String.format("[%s: %s, %s]", fieldName, getLeft(), getRight()); + } + + /** + *

+ * Throws {@code UnsupportedOperationException}. + *

+ * + * @param value + * ignored + * @return nothing + */ + @Override + public final T setValue(T value) { + throw new UnsupportedOperationException("Cannot alter Diff object."); + } +} diff --git a/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java b/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java new file mode 100644 index 000000000..8c54777b6 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java @@ -0,0 +1,914 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.builder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang3.ArrayUtils; + +/** + *

+ * Assists in implementing {@link Diffable#diff(Object)} methods. + *

+ * + *

+ * To use this class, write code as follows: + *

+ * + *
+ * public class Person implements Diffable<Person> {
+ *   String name;
+ *   int age;
+ *   boolean smoker;
+ *   
+ *   ...
+ *   
+ *   public DiffList diff(Person obj) {
+ *     // No need for null check, as NullPointerException correct if obj is null
+ *     return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
+ *       .append("name", this.name, obj.name)
+ *       .append("age", this.age, obj.age)
+ *       .append("smoker", this.smoker, obj.smoker)
+ *       .build();
+ *   }
+ * }
+ * 
+ * + *

+ * The {@code ToStringStyle} passed to the constructor is embedded in the + * returned {@code DiffList} and influences the style of the + * {@code DiffList.toString()} method. This style choice can be overridden by + * calling {@link DiffList#toString(ToStringStyle)}. + *

+ * + * @since 3.3 + * @version $Id$ + * @see Diffable + * @see Diff + * @see DiffList + * @see ToStringStyle + */ +public class DiffBuilder implements Builder { + + private final List> diffs; + private final boolean objectsTriviallyEqual; + private final Object lhs; + private final Object rhs; + private final ToStringStyle style; + + /** + *

+ * Constructs a builder for the specified objects with the specified style. + *

+ * + *

+ * 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 + * {@link DiffList} when {@link #build()} is executed. + *

+ * + * @param lhs + * {@code this} object + * @param rhs + * the object to diff against + * @param style + * the style will use when outputting the objects, {@code null} + * uses the default + * @throws IllegalArgumentException + * if {@code lhs} or {@code rhs} is {@code null} + */ + public DiffBuilder(final Object lhs, final Object rhs, + final ToStringStyle style) { + if (lhs == null) { + throw new IllegalArgumentException("lhs cannot be null"); + } + if (rhs == null) { + throw new IllegalArgumentException("rhs cannot be null"); + } + + this.diffs = new ArrayList>(); + this.lhs = lhs; + this.rhs = rhs; + this.style = style; + + // Don't compare any fields if objects equal + this.objectsTriviallyEqual = (lhs == rhs || lhs.equals(rhs)); + } + + /** + *

+ * Test if two {@code boolean}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code boolean} + * @param rhs + * the right hand {@code boolean} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final boolean lhs, + final boolean rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Boolean getLeft() { + return Boolean.valueOf(lhs); + } + + @Override + public Boolean getRight() { + return Boolean.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code boolean[]}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code boolean[]} + * @param rhs + * the right hand {@code boolean[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final boolean[] lhs, + final boolean[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Boolean[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Boolean[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code byte}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code byte} + * @param rhs + * the right hand {@code byte} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final byte lhs, + final byte rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Byte getLeft() { + return Byte.valueOf(lhs); + } + + @Override + public Byte getRight() { + return Byte.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code byte[]}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code byte[]} + * @param rhs + * the right hand {@code byte[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final byte[] lhs, + final byte[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Byte[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Byte[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code char}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code char} + * @param rhs + * the right hand {@code char} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final char lhs, + final char rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Character getLeft() { + return Character.valueOf(lhs); + } + + @Override + public Character getRight() { + return Character.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code char[]}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code char[]} + * @param rhs + * the right hand {@code char[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final char[] lhs, + final char[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Character[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Character[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code double}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code double} + * @param rhs + * the right hand {@code double} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final double lhs, + final double rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (Double.doubleToLongBits(lhs) != Double.doubleToLongBits(rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Double getLeft() { + return Double.valueOf(lhs); + } + + @Override + public Double getRight() { + return Double.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code double[]}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code double[]} + * @param rhs + * the right hand {@code double[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final double[] lhs, + final double[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Double[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Double[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code float}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code float} + * @param rhs + * the right hand {@code float} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final float lhs, + final float rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (Float.floatToIntBits(lhs) != Float.floatToIntBits(rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Float getLeft() { + return Float.valueOf(lhs); + } + + @Override + public Float getRight() { + return Float.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code float[]}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code float[]} + * @param rhs + * the right hand {@code float[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final float[] lhs, + final float[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Float[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Float[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code int}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code int} + * @param rhs + * the right hand {@code int} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final int lhs, + final int rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Integer getLeft() { + return Integer.valueOf(lhs); + } + + @Override + public Integer getRight() { + return Integer.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code int[]}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code int[]} + * @param rhs + * the right hand {@code int[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final int[] lhs, + final int[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Integer[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Integer[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code long}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code long} + * @param rhs + * the right hand {@code long} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final long lhs, + final long rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Long getLeft() { + return Long.valueOf(lhs); + } + + @Override + public Long getRight() { + return Long.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code long[]}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code long[]} + * @param rhs + * the right hand {@code long[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final long[] lhs, + final long[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Long[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Long[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code short}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code short} + * @param rhs + * the right hand {@code short} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final short lhs, + final short rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (lhs != rhs) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Short getLeft() { + return Short.valueOf(lhs); + } + + @Override + public Short getRight() { + return Short.valueOf(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code short[]}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code short[]} + * @param rhs + * the right hand {@code short[]} + * @return this + * @throws IllegalArgumentException + * if field name is {@code null} + */ + public DiffBuilder append(final String fieldName, final short[] lhs, + final short[] rhs) { + if (fieldName == null) { + throw new IllegalArgumentException("Field name cannot be null"); + } + + if (objectsTriviallyEqual) { + return this; + } + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Short[] getLeft() { + return ArrayUtils.toObject(lhs); + } + + @Override + public Short[] getRight() { + return ArrayUtils.toObject(rhs); + } + }); + } + return this; + } + + /** + *

+ * Test if two {@code Objects}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code Object} + * @param rhs + * the right hand {@code Object} + * @return this + */ + public DiffBuilder append(final String fieldName, final Object lhs, + final Object rhs) { + + if (objectsTriviallyEqual) { + return this; + } + if (lhs == rhs) { + return this; + } + + Object objectToTest; + if (lhs != null) { + objectToTest = lhs; + } else { + // rhs cannot be null, as lhs != rhs + objectToTest = rhs; + } + + if (objectToTest.getClass().isArray()) { + if (objectToTest instanceof boolean[]) { + return append(fieldName, (boolean[]) lhs, (boolean[]) rhs); + } + if (objectToTest instanceof byte[]) { + return append(fieldName, (byte[]) lhs, (byte[]) rhs); + } + if (objectToTest instanceof char[]) { + return append(fieldName, (char[]) lhs, (char[]) rhs); + } + if (objectToTest instanceof double[]) { + return append(fieldName, (double[]) lhs, (double[]) rhs); + } + if (objectToTest instanceof float[]) { + return append(fieldName, (float[]) lhs, (float[]) rhs); + } + if (objectToTest instanceof int[]) { + return append(fieldName, (int[]) lhs, (int[]) rhs); + } + if (objectToTest instanceof long[]) { + return append(fieldName, (long[]) lhs, (long[]) rhs); + } + if (objectToTest instanceof short[]) { + return append(fieldName, (short[]) lhs, (short[]) rhs); + } + + return append(fieldName, (Object[]) lhs, (Object[]) rhs); + } + + // Not array type + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Object getLeft() { + return lhs; + } + + @Override + public Object getRight() { + return rhs; + } + }); + + return this; + } + + /** + *

+ * Test if two {@code Object[]}s are equal. + *

+ * + * @param fieldName + * the field name + * @param lhs + * the left hand {@code Object[]} + * @param rhs + * the right hand {@code Object[]} + * @return this + */ + public DiffBuilder append(final String fieldName, final Object[] lhs, + final Object[] rhs) { + if (objectsTriviallyEqual) { + return this; + } + + if (!Arrays.equals(lhs, rhs)) { + diffs.add(new Diff(fieldName) { + private static final long serialVersionUID = 1L; + + @Override + public Object[] getLeft() { + return lhs; + } + + @Override + public Object[] getRight() { + return rhs; + } + }); + } + + return this; + } + + /** + *

+ * Builds a {@link DiffList} based on the differences appended to this + * builder. + *

+ * + * @return a {@code DiffList} containing the differences between the two + * objects. + */ + @Override + public DiffList build() { + return new DiffList(lhs, rhs, diffs, style); + } + +} diff --git a/src/main/java/org/apache/commons/lang3/builder/DiffList.java b/src/main/java/org/apache/commons/lang3/builder/DiffList.java new file mode 100644 index 000000000..5ca6505e7 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/builder/DiffList.java @@ -0,0 +1,208 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.builder; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + *

+ * A {@code DiffList} contains a list of the differences between two + * {@link Diffable} objects. Typically these differences are displayed using + * {@link #toString()} method, which returns a string describing the fields that + * differ between the objects. + *

+ *

+ * Use a {@link DiffBuilder} to build a {@code DiffList} comparing two objects. + *

+ * + * @since 3.3 + * @version $Id$ + */ +public class DiffList implements Iterable> { + + /** + *

+ * The {@code String} returned when the objects have no differences: + * {@value} + *

+ */ + public static final String OBJECTS_SAME_STRING = ""; + + private static final String DIFFERS_STRING = "differs from"; + + private final List> diffs; + private final Object lhs; + private final Object rhs; + private final ToStringStyle style; + + /** + *

+ * Creates a {@link DiffList} containing the differences between two + * objects. + *

+ * + * @param lhs + * the left hand object + * @param rhs + * the right hand object + * @param diffs + * the list of differences, may be empty + * @param style + * the style to use for the {@link #toString()} method. May be + * {@code null}, in which case + * {@link ToStringStyle#DEFAULT_STYLE} is used + * @throws IllegalArgumentException + * if {@code lhs}, {@code rhs} or {@code diffs} is {@code null} + */ + DiffList(final Object lhs, final Object rhs, final List> diffs, + final ToStringStyle style) { + if (lhs == null) { + throw new IllegalArgumentException( + "Left hand object cannot be null"); + } + if (rhs == null) { + throw new IllegalArgumentException( + "Right hand object cannot be null"); + } + if (diffs == null) { + throw new IllegalArgumentException( + "List of differences cannot be null"); + } + + this.diffs = diffs; + this.lhs = lhs; + this.rhs = rhs; + + if (style == null) { + this.style = ToStringStyle.DEFAULT_STYLE; + } else { + this.style = style; + } + } + + /** + *

+ * Returns an unmodifiable list of {@code Diff}s. The list may be empty if + * there were no differences between the objects. + *

+ * + * @return an unmodifiable list of {@code Diff}s + */ + public List> getDiffs() { + return Collections.unmodifiableList(diffs); + } + + /** + *

+ * Returns the number of differences between the two objects. + *

+ * + * @return the number of differences + */ + public int getNumberOfDiffs() { + return diffs.size(); + } + + /** + *

+ * Returns the style used by the {@link #toString()} method. + *

+ * + * @return the style + */ + public ToStringStyle getToStringStyle() { + return style; + } + + /** + *

+ * Builds a {@code String} description of the differences contained within + * this {@code DiffList}. A {@link ToStringBuilder} is used for each object + * and the style of the output is governed by the {@code ToStringStyle} + * passed to the constructor. + *

+ * + *

+ * If there are no differences stored in this list, the method will return + * {@link #OBJECTS_SAME_STRING}. Otherwise, using the example given in + * {@link Diffable} and {@link ToStringStyle#SHORT_PREFIX_STYLE}, an output + * might be: + *

+ * + *
+     * Person[name=John Doe,age=32] differs from Person[name=Joe Bloggs,age=26]
+     * 
+ * + *

+ * This indicates that the objects differ in name and age, but not in + * smoking status. + *

+ * + *

+ * To use a different {@code ToStringStyle} for an instance of this class, + * use {@link #toString(ToStringStyle)}. + *

+ * + * @return a {@code String} description of the differences. + */ + @Override + public String toString() { + return toString(style); + } + + /** + *

+ * Builds a {@code String} description of the differences contained within + * this {@code DiffList}, using the supplied {@code ToStringStyle}. + *

+ * + * @param style + * the {@code ToStringStyle} to use when outputting the objects + * + * @return a {@code String} description of the differences. + */ + public String toString(ToStringStyle style) { + if (diffs.size() == 0) { + return OBJECTS_SAME_STRING; + } + + ToStringBuilder lhsBuilder = new ToStringBuilder(lhs, style); + ToStringBuilder rhsBuilder = new ToStringBuilder(rhs, style); + + for (Diff diff : diffs) { + lhsBuilder.append(diff.getFieldName(), diff.getLeft()); + rhsBuilder.append(diff.getFieldName(), diff.getRight()); + } + + return String.format("%s %s %s", lhsBuilder.build(), DIFFERS_STRING, + rhsBuilder.build()); + } + + /** + *

+ * Returns an iterator over the {@code Diff} objects contained in this list. + *

+ * + * @return the iterator + */ + @Override + public Iterator> iterator() { + return diffs.iterator(); + } +} diff --git a/src/main/java/org/apache/commons/lang3/builder/Diffable.java b/src/main/java/org/apache/commons/lang3/builder/Diffable.java new file mode 100644 index 000000000..2008825da --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/builder/Diffable.java @@ -0,0 +1,54 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.builder; + +/** + *

{@code Diffable} classes can be compared with other objects + * for differences. The {@link DiffList} object retrieved can be queried + * for a list of differences or printed using the {@link DiffList#toString()}.

+ * + *

The calculation of the differences is consistent with equals if + * and only if {@code d1.equals(d2)} implies {@code d1.diff(d2) == ""}. + * It is strongly recommended that implementations are consistent with equals + * to avoid confusion. Note that {@code null} is not an instance of any class + * and {@code d1.diff(null)} should throw a {@code NullPointerException}.

+ * + *

+ * {@code Diffable} classes lend themselves well to unit testing, in which a + * easily readable description of the differences between an anticipated result and + * an actual result can be retrieved. For example: + *

+ *
+ * Assert.assertEquals(expected.diff(result), expected, result);
+ * 
+ * + * @param the type of objects that this object may be differentiated against + * @since 3.3 + * @version $Id$ + */ +public interface Diffable { + + /** + *

Retrieves a list of the differences between + * this object and the supplied object.

+ * + * @param obj the object to diff against, can be {@code null} + * @return a list of differences + * @throws NullPointerException if the specified object is {@code null} + */ + DiffList diff(T obj); +} diff --git a/src/test/java/org/apache/commons/lang3/builder/DiffBuilderTest.java b/src/test/java/org/apache/commons/lang3/builder/DiffBuilderTest.java new file mode 100644 index 000000000..4f64164c7 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/builder/DiffBuilderTest.java @@ -0,0 +1,415 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.builder; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import org.apache.commons.lang3.ArrayUtils; +import org.junit.Test; + + +/** + * Unit tests {@link DiffBuilder}. + * + * @version $Id$ + */ +public class DiffBuilderTest { + + private static final ToStringStyle SHORT_STYLE = ToStringStyle.SHORT_PREFIX_STYLE; + + private static class TypeTestClass implements Diffable { + private ToStringStyle style = SHORT_STYLE; + private boolean booleanField = true; + private boolean[] booleanArrayField = {true}; + private byte byteField = (byte) 0xFF; + private byte[] byteArrayField = {(byte) 0xFF}; + private char charField = 'a'; + private char[] charArrayField = {'a'}; + private double doubleField = 1.0; + private double[] doubleArrayField = {1.0}; + private float floatField = 1.0f; + private float[] floatArrayField = {1.0f}; + private int intField = 1; + private int[] intArrayField = {1}; + private long longField = 1L; + private long[] longArrayField = {1L}; + private short shortField = 1; + private short[] shortArrayField = {1}; + private Object objectField = null; + private Object[] objectArrayField = {null}; + + @Override + public DiffList diff(TypeTestClass obj) { + return new DiffBuilder(this, obj, style) + .append("boolean", booleanField, obj.booleanField) + .append("booleanArray", booleanArrayField, obj.booleanArrayField) + .append("byte", byteField, obj.byteField) + .append("byteArray", byteArrayField, obj.byteArrayField) + .append("char", charField, obj.charField) + .append("charArray", charArrayField, obj.charArrayField) + .append("double", doubleField, obj.doubleField) + .append("doubleArray", doubleArrayField, obj.doubleArrayField) + .append("float", floatField, obj.floatField) + .append("floatArray", floatArrayField, obj.floatArrayField) + .append("int", intField, obj.intField) + .append("intArray", intArrayField, obj.intArrayField) + .append("long", longField, obj.longField) + .append("longArray", longArrayField, obj.longArrayField) + .append("short", shortField, obj.shortField) + .append("shortArray", shortArrayField, obj.shortArrayField) + .append("objectField", objectField, obj.objectField) + .append("objectArrayField", objectArrayField, obj.objectArrayField) + .build(); + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this, false); + } + + @Override + public boolean equals(Object obj) { + return EqualsBuilder.reflectionEquals(this, obj, false); + } + } + + + @Test + public void testBoolean() { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.booleanField = false; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertEquals(Boolean.class, diff.getType()); + assertEquals(Boolean.TRUE, diff.getLeft()); + assertEquals(Boolean.FALSE, diff.getRight()); + } + + @Test + public void testBooleanArray() throws Exception { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.booleanArrayField = new boolean[] {false, false}; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertArrayEquals(ArrayUtils.toObject(class1.booleanArrayField), + (Object[]) diff.getLeft()); + assertArrayEquals(ArrayUtils.toObject(class2.booleanArrayField), + (Object[]) diff.getRight()); + } + + + @Test + public void testByte() { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.byteField = 0x01; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertEquals(Byte.valueOf(class1.byteField), diff.getLeft()); + assertEquals(Byte.valueOf(class2.byteField), diff.getRight()); + } + + @Test + public void testByteArray() throws Exception { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.byteArrayField= new byte[] {0x01, 0x02}; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertArrayEquals(ArrayUtils.toObject(class1.byteArrayField), + (Object[]) diff.getLeft()); + assertArrayEquals(ArrayUtils.toObject(class2.byteArrayField), + (Object[]) diff.getRight()); + } + + @Test + public void testChar() { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.charField = 'z'; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertEquals(Character.valueOf(class1.charField), diff.getLeft()); + assertEquals(Character.valueOf(class2.charField), diff.getRight()); + } + + + @Test + public void testCharArray() throws Exception { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.charArrayField = new char[] {'f', 'o', 'o'}; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertArrayEquals(ArrayUtils.toObject(class1.charArrayField), + (Object[]) diff.getLeft()); + assertArrayEquals(ArrayUtils.toObject(class2.charArrayField), + (Object[]) diff.getRight()); + } + + + @Test + public void testDouble() { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.doubleField = 99.99; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertEquals(Double.valueOf(class1.doubleField), diff.getLeft()); + assertEquals(Double.valueOf(class2.doubleField), diff.getRight()); + } + + + @Test + public void testDoubleArray() throws Exception { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.doubleArrayField = new double[] {3.0, 2.9, 2.8}; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertArrayEquals(ArrayUtils.toObject(class1.doubleArrayField), + (Object[]) diff.getLeft()); + assertArrayEquals(ArrayUtils.toObject(class2.doubleArrayField), + (Object[]) diff.getRight()); + } + + @Test + public void testFloat() { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.floatField = 99.99F; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertEquals(Float.valueOf(class1.floatField), diff.getLeft()); + assertEquals(Float.valueOf(class2.floatField), diff.getRight()); + } + + + @Test + public void testFloatArray() throws Exception { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.floatArrayField = new float[] {3.0F, 2.9F, 2.8F}; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertArrayEquals(ArrayUtils.toObject(class1.floatArrayField), + (Object[]) diff.getLeft()); + assertArrayEquals(ArrayUtils.toObject(class2.floatArrayField), + (Object[]) diff.getRight()); + } + + + @Test + public void testInt() { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.intField = 42; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertEquals(Integer.valueOf(class1.intField), diff.getLeft()); + assertEquals(Integer.valueOf(class2.intField), diff.getRight()); + } + + + @Test + public void testIntArray() throws Exception { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.intArrayField = new int[] {3, 2, 1}; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertArrayEquals(ArrayUtils.toObject(class1.intArrayField), + (Object[]) diff.getLeft()); + assertArrayEquals(ArrayUtils.toObject(class2.intArrayField), + (Object[]) diff.getRight()); + } + + @Test + public void testLong() { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.longField = 42L; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertEquals(Long.valueOf(class1.longField), diff.getLeft()); + assertEquals(Long.valueOf(class2.longField), diff.getRight()); + } + + + @Test + public void testLongArray() throws Exception { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.longArrayField = new long[] {3L, 2L, 1L}; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertArrayEquals(ArrayUtils.toObject(class1.longArrayField), + (Object[]) diff.getLeft()); + assertArrayEquals(ArrayUtils.toObject(class2.longArrayField), + (Object[]) diff.getRight()); + } + + @Test + public void testShort() { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.shortField = 42; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertEquals(Short.valueOf(class1.shortField), diff.getLeft()); + assertEquals(Short.valueOf(class2.shortField), diff.getRight()); + } + + + @Test + public void testShortArray() throws Exception { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.shortArrayField = new short[] {3, 2, 1}; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertArrayEquals(ArrayUtils.toObject(class1.shortArrayField), + (Object[]) diff.getLeft()); + assertArrayEquals(ArrayUtils.toObject(class2.shortArrayField), + (Object[]) diff.getRight()); + } + + @Test + public void testObject() throws Exception { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.objectField = "Some string"; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertEquals(class1.objectField, diff.getLeft()); + assertEquals(class2.objectField, diff.getRight()); + } + + @Test + public void testObjectsEqual() throws Exception { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class1.objectField = "Some string"; + class2.objectField = "Some string"; + DiffList list = class1.diff(class2); + assertEquals(0, list.getNumberOfDiffs()); + } + + + @Test + public void testObjectArray() throws Exception { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class2.objectArrayField = new Object[] {"string", 1, 2}; + DiffList list = class1.diff(class2); + assertEquals(1, list.getNumberOfDiffs()); + Diff diff = list.getDiffs().get(0); + assertArrayEquals(class1.objectArrayField, (Object[]) diff.getLeft()); + assertArrayEquals(class2.objectArrayField, (Object[]) diff.getRight()); + } + + @Test + public void testObjectArrayEqual() throws Exception { + TypeTestClass class1 = new TypeTestClass(); + TypeTestClass class2 = new TypeTestClass(); + class1.objectArrayField = new Object[] {"string", 1, 2}; + class2.objectArrayField = new Object[] {"string", 1, 2}; + DiffList list = class1.diff(class2); + assertEquals(0, list.getNumberOfDiffs()); + } + + + @Test + public void testByteArrayEqualAsObject() throws Exception { + DiffList list = new DiffBuilder("String1", "String2", SHORT_STYLE) + .append("foo", (Object) new boolean[] {false}, (Object) new boolean[] {false}) + .append("foo", (Object) new byte[] {0x01}, (Object) new byte[] {0x01}) + .append("foo", (Object) new char[] {'a'}, (Object) new char[] {'a'}) + .append("foo", (Object) new double[] {1.0}, (Object) new double[] {1.0}) + .append("foo", (Object) new float[] {1.0F}, (Object) new float[] {1.0F}) + .append("foo", (Object) new int[] {1}, (Object) new int[] {1}) + .append("foo", (Object) new long[] {1L}, (Object) new long[] {1L}) + .append("foo", (Object) new short[] {1}, (Object) new short[] {1}) + .append("foo", (Object) new Object[] {1, "two"}, (Object) new Object[] {1, "two"}) + .build(); + + assertEquals(0, list.getNumberOfDiffs()); + } + + + @Test(expected=IllegalArgumentException.class) + public void testNullLhs() { + new DiffBuilder(null, this, ToStringStyle.DEFAULT_STYLE); + } + + + @Test(expected=IllegalArgumentException.class) + public void testNullRhs() { + new DiffBuilder(this, null, ToStringStyle.DEFAULT_STYLE); + } + + @Test + public void testSameObjectIgnoresAppends() { + TypeTestClass testClass = new TypeTestClass(); + DiffList list = new DiffBuilder(testClass, testClass, SHORT_STYLE) + .append("ignored", false, true) + .build(); + assertEquals(0, list.getNumberOfDiffs()); + } + + @Test + public void testSimilarObjectIgnoresAppends() { + TypeTestClass testClass1 = new TypeTestClass(); + TypeTestClass testClass2 = new TypeTestClass(); + DiffList list = new DiffBuilder(testClass1, testClass2, SHORT_STYLE) + .append("ignored", false, true) + .build(); + assertEquals(0, list.getNumberOfDiffs()); + } + + + @Test + public void testStylePassedToDiffList() { + TypeTestClass class1 = new TypeTestClass(); + DiffList list = class1.diff(class1); + assertEquals(SHORT_STYLE, list.getToStringStyle()); + + class1.style = ToStringStyle.MULTI_LINE_STYLE; + list = class1.diff(class1); + assertEquals(ToStringStyle.MULTI_LINE_STYLE, list.getToStringStyle()); + } +} diff --git a/src/test/java/org/apache/commons/lang3/builder/DiffListTest.java b/src/test/java/org/apache/commons/lang3/builder/DiffListTest.java new file mode 100644 index 000000000..d4a1f45c6 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/builder/DiffListTest.java @@ -0,0 +1,149 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.builder; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Iterator; +import java.util.List; + +import org.junit.Test; + +/** + * Unit tests {@link DiffList}. + * + * @version $Id$ + */ +public class DiffListTest { + + private static final SimpleClass SIMPLE_FALSE = new SimpleClass(false); + private static final SimpleClass SIMPLE_TRUE = new SimpleClass(true); + private static final ToStringStyle SHORT_STYLE = ToStringStyle.SHORT_PREFIX_STYLE; + + private static class SimpleClass implements Diffable { + private boolean booleanField; + + public SimpleClass(boolean booleanField) { + this.booleanField = booleanField; + } + + public static String getFieldName() { + return "booleanField"; + } + + @Override + public DiffList diff(SimpleClass obj) { + return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE) + .append(getFieldName(), booleanField, obj.booleanField) + .build(); + } + } + + private static class EmptyClass { + } + + @Test(expected = UnsupportedOperationException.class) + public void testListIsNonModifiable() { + SimpleClass lhs = new SimpleClass(true); + SimpleClass rhs = new SimpleClass(false); + + List> diffs = lhs.diff(rhs).getDiffs(); + + DiffList list = new DiffList(lhs, rhs, diffs, SHORT_STYLE); + assertEquals(diffs, list.getDiffs()); + assertEquals(1, list.getNumberOfDiffs()); + list.getDiffs().remove(0); + } + + @Test + public void testIterator() { + SimpleClass lhs = new SimpleClass(true); + SimpleClass rhs = new SimpleClass(false); + + List> diffs = lhs.diff(rhs).getDiffs(); + Iterator> expectedIterator = diffs.iterator(); + + DiffList list = new DiffList(lhs, rhs, diffs, SHORT_STYLE); + Iterator> iterator = list.iterator(); + + while (iterator.hasNext()) { + assertTrue(expectedIterator.hasNext()); + assertEquals(expectedIterator.next(), iterator.next()); + } + } + + @Test + public void testToStringOutput() { + DiffList list = new DiffBuilder(new EmptyClass(), new EmptyClass(), + ToStringStyle.SHORT_PREFIX_STYLE).append("test", false, true) + .build(); + assertEquals( + "DiffListTest.EmptyClass[test=false] differs from DiffListTest.EmptyClass[test=true]", + list.toString()); + } + + @Test + public void testToStringSpecifyStyleOutput() { + DiffList list = SIMPLE_FALSE.diff(SIMPLE_TRUE); + assertTrue(list.getToStringStyle().equals(SHORT_STYLE)); + + String lhsString = new ToStringBuilder(SIMPLE_FALSE, + ToStringStyle.MULTI_LINE_STYLE).append( + SimpleClass.getFieldName(), SIMPLE_FALSE.booleanField).build(); + + String rhsString = new ToStringBuilder(SIMPLE_TRUE, + ToStringStyle.MULTI_LINE_STYLE).append( + SimpleClass.getFieldName(), SIMPLE_TRUE.booleanField).build(); + + String expectedOutput = String.format("%s differs from %s", lhsString, + rhsString); + assertEquals(expectedOutput, + list.toString(ToStringStyle.MULTI_LINE_STYLE)); + } + + @Test(expected = IllegalArgumentException.class) + public void testNullLhs() { + new DiffList(null, SIMPLE_FALSE, SIMPLE_TRUE.diff(SIMPLE_FALSE) + .getDiffs(), SHORT_STYLE); + } + + @Test(expected = IllegalArgumentException.class) + public void testNullRhs() { + new DiffList(SIMPLE_TRUE, null, SIMPLE_TRUE.diff(SIMPLE_FALSE) + .getDiffs(), SHORT_STYLE); + } + + @Test(expected = IllegalArgumentException.class) + public void testNullList() { + new DiffList(SIMPLE_TRUE, SIMPLE_FALSE, null, SHORT_STYLE); + } + + @Test + public void testNullStyle() { + DiffList diffList = new DiffList(SIMPLE_TRUE, SIMPLE_FALSE, SIMPLE_TRUE + .diff(SIMPLE_FALSE).getDiffs(), null); + assertEquals(ToStringStyle.DEFAULT_STYLE, diffList.getToStringStyle()); + } + + @Test + public void testNoDifferencesString() { + DiffList diffList = new DiffBuilder(SIMPLE_TRUE, SIMPLE_TRUE, + SHORT_STYLE).build(); + assertEquals(DiffList.OBJECTS_SAME_STRING, diffList.toString()); + } +} diff --git a/src/test/java/org/apache/commons/lang3/builder/DiffTest.java b/src/test/java/org/apache/commons/lang3/builder/DiffTest.java new file mode 100644 index 000000000..b6a0347f6 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/builder/DiffTest.java @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.builder; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + +/** + * Unit tests {@link Diff}. + * + * @version $Id$ + */ +public class DiffTest { + + private static final String FIELD_NAME = "field"; + private static final Diff booleanDiff = new BooleanDiff(FIELD_NAME); + + private static class BooleanDiff extends Diff { + private static final long serialVersionUID = 1L; + + protected BooleanDiff(String fieldName) { + super(fieldName); + } + + @Override + public Boolean getLeft() { + return Boolean.TRUE; + } + + @Override + public Boolean getRight() { + return Boolean.FALSE; + } + } + + @Test(expected = UnsupportedOperationException.class) + public void testCannotModify() { + booleanDiff.setValue(Boolean.FALSE); + } + + @Test + public void testGetFieldName() { + assertEquals(FIELD_NAME, booleanDiff.getFieldName()); + } + + @Test + public void testGetType() { + assertEquals(Boolean.class, booleanDiff.getType()); + } + + @Test + public void testToString() { + assertEquals(String.format("[%s: %s, %s]", FIELD_NAME, booleanDiff.getLeft(), + booleanDiff.getRight()), booleanDiff.toString()); + } +}