001    /*
002     * Created on Aug 4, 2010
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005     * the License. You may obtain a copy of the License at
006     * 
007     * http://www.apache.org/licenses/LICENSE-2.0
008     * 
009     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010     * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011     * specific language governing permissions and limitations under the License.
012     * 
013     * Copyright @2010-2011 the original author or authors.
014     */
015    package org.fest.assertions.internal;
016    
017    import static org.fest.assertions.error.ShouldBeEqual.shouldBeEqual;
018    import static org.fest.assertions.error.ShouldBeIn.shouldBeIn;
019    import static org.fest.assertions.error.ShouldBeInstance.shouldBeInstance;
020    import static org.fest.assertions.error.ShouldBeInstanceOfAny.shouldBeInstanceOfAny;
021    import static org.fest.assertions.error.ShouldBeLenientEqualByAccepting.shouldBeLenientEqualByAccepting;
022    import static org.fest.assertions.error.ShouldBeLenientEqualByIgnoring.shouldBeLenientEqualByIgnoring;
023    import static org.fest.assertions.error.ShouldBeSame.shouldBeSame;
024    import static org.fest.assertions.error.ShouldNotBeEqual.shouldNotBeEqual;
025    import static org.fest.assertions.error.ShouldNotBeIn.shouldNotBeIn;
026    import static org.fest.assertions.error.ShouldNotBeNull.shouldNotBeNull;
027    import static org.fest.assertions.error.ShouldNotBeSame.shouldNotBeSame;
028    import static org.fest.util.Collections.*;
029    import static org.fest.util.ToString.toStringOf;
030    
031    import java.lang.reflect.Field;
032    import java.util.Comparator;
033    import java.util.LinkedList;
034    import java.util.List;
035    import java.util.Set;
036    
037    import org.fest.assertions.core.AssertionInfo;
038    import org.fest.util.ComparatorBasedComparisonStrategy;
039    import org.fest.util.ComparisonStrategy;
040    import org.fest.util.IntrospectionError;
041    import org.fest.util.StandardComparisonStrategy;
042    import org.fest.util.VisibleForTesting;
043    
044    /**
045     * Reusable assertions for {@code Object}s.
046     * 
047     * @author Yvonne Wang
048     * @author Alex Ruiz
049     * @author Nicolas François
050     * @author Mikhail Mazursky
051     */
052    public class Objects {
053    
054      private static final Objects INSTANCE = new Objects();
055    
056      /**
057       * Returns the singleton instance of this class based on {@link StandardComparisonStrategy}.
058       * @return the singleton instance of this class based on {@link StandardComparisonStrategy}.
059       */
060      public static Objects instance() {
061        return INSTANCE;
062      }
063    
064      @VisibleForTesting
065      Failures failures = Failures.instance();
066    
067      @VisibleForTesting
068      PropertySupport propertySupport = PropertySupport.instance();
069    
070      private final ComparisonStrategy comparisonStrategy;
071    
072      @VisibleForTesting
073      Objects() {
074        this(StandardComparisonStrategy.instance());
075      }
076    
077      public Objects(ComparisonStrategy comparisonStrategy) {
078        this.comparisonStrategy = comparisonStrategy;
079      }
080    
081      @VisibleForTesting
082      public Comparator<?> getComparator() {
083        if (comparisonStrategy instanceof ComparatorBasedComparisonStrategy) { return ((ComparatorBasedComparisonStrategy) comparisonStrategy)
084            .getComparator(); }
085        return null;
086      }
087    
088      /**
089       * Verifies that the given object is an instance of the given type.
090       * @param info contains information about the assertion.
091       * @param actual the given object.
092       * @param type the type to check the given object against.
093       * @throws NullPointerException if the given type is {@code null}.
094       * @throws AssertionError if the given object is {@code null}.
095       * @throws AssertionError if the given object is not an instance of the given type.
096       */
097      public void assertIsInstanceOf(AssertionInfo info, Object actual, Class<?> type) {
098        if (type == null) throw new NullPointerException("The given type should not be null");
099        assertNotNull(info, actual);
100        if (type.isInstance(actual)) return;
101        throw failures.failure(info, shouldBeInstance(actual, type));
102      }
103    
104      /**
105       * Verifies that the given object is an instance of any of the given types.
106       * @param info contains information about the assertion.
107       * @param actual the given object.
108       * @param types the types to check the given object against.
109       * @throws NullPointerException if the given array is {@code null}.
110       * @throws IllegalArgumentException if the given array is empty.
111       * @throws NullPointerException if the given array has {@code null} elements.
112       * @throws AssertionError if the given object is {@code null}.
113       * @throws AssertionError if the given object is not an instance of any of the given types.
114       */
115      public void assertIsInstanceOfAny(AssertionInfo info, Object actual, Class<?>[] types) {
116        checkIsNotNullAndIsNotEmpty(types);
117        assertNotNull(info, actual);
118        boolean found = false;
119        for (Class<?> type : types) {
120          if (type == null) {
121            String format = "The given array of types:<%s> should not have null elements";
122            throw new NullPointerException(String.format(format, toStringOf(types)));
123          }
124          if (type.isInstance(actual)) {
125            found = true;
126            break;
127          }
128        }
129        if (found) return;
130        throw failures.failure(info, shouldBeInstanceOfAny(actual, types));
131      }
132    
133      private void checkIsNotNullAndIsNotEmpty(Class<?>[] types) {
134        if (types == null) throw new NullPointerException("The given array of types should not be null");
135        if (types.length == 0) throw new IllegalArgumentException("The given array of types should not be empty");
136      }
137    
138      /**
139       * Asserts that two objects are equal.
140       * @param info contains information about the assertion.
141       * @param actual the "actual" object.
142       * @param expected the "expected" object.
143       * @throws AssertionError if {@code actual} is not equal to {@code expected}. This method will throw a
144       *           {@code org.junit.ComparisonFailure} instead if JUnit is in the classpath and the given objects are not
145       *           equal.
146       */
147      public void assertEqual(AssertionInfo info, Object actual, Object expected) {
148        if (areEqual(actual, expected)) return;
149        throw failures.failure(info, shouldBeEqual(actual, expected, comparisonStrategy));
150      }
151    
152      /**
153       * Asserts that two objects are not equal.
154       * @param info contains information about the assertion.
155       * @param actual the given object.
156       * @param other the object to compare {@code actual} to.
157       * @throws AssertionError if {@code actual} is equal to {@code other}.
158       */
159      public void assertNotEqual(AssertionInfo info, Object actual, Object other) {
160        if (!areEqual(actual, other)) return;
161        throw failures.failure(info, shouldNotBeEqual(actual, other, comparisonStrategy));
162      }
163    
164      /**
165       * Compares actual and other with standard strategy (null safe equals check).
166       * @param actual the object to compare to other
167       * @param other the object to compare to actual
168       * @return true if actual and other are equal (null safe equals check), false otherwise.
169       */
170      private boolean areEqual(Object actual, Object other) {
171        return comparisonStrategy.areEqual(other, actual);
172      }
173    
174      /**
175       * Asserts that the given object is {@code null}.
176       * @param info contains information about the assertion.
177       * @param actual the given object.
178       * @throws AssertionError if the given object is not {@code null}.
179       */
180      public void assertNull(AssertionInfo info, Object actual) {
181        if (actual == null) return;
182        throw failures.failure(info, shouldBeEqual(actual, null, comparisonStrategy));
183      }
184    
185      /**
186       * Asserts that the given object is not {@code null}.
187       * @param info contains information about the assertion.
188       * @param actual the given object.
189       * @throws AssertionError if the given object is {@code null}.
190       */
191      public void assertNotNull(AssertionInfo info, Object actual) {
192        if (actual != null) return;
193        throw failures.failure(info, shouldNotBeNull());
194      }
195    
196      /**
197       * Asserts that two objects refer to the same object.
198       * @param info contains information about the assertion.
199       * @param actual the given object.
200       * @param expected the expected object.
201       * @throws AssertionError if the given objects do not refer to the same object.
202       */
203      public void assertSame(AssertionInfo info, Object actual, Object expected) {
204        if (actual == expected) return;
205        throw failures.failure(info, shouldBeSame(actual, expected));
206      }
207    
208      /**
209       * Asserts that two objects do not refer to the same object.
210       * @param info contains information about the assertion.
211       * @param actual the given object.
212       * @param other the object to compare {@code actual} to.
213       * @throws AssertionError if the given objects refer to the same object.
214       */
215      public void assertNotSame(AssertionInfo info, Object actual, Object other) {
216        if (actual != other) return;
217        throw failures.failure(info, shouldNotBeSame(actual));
218      }
219    
220      /**
221       * Asserts that the given object is present in the given array.
222       * @param info contains information about the assertion.
223       * @param actual the given object.
224       * @param values the given array.
225       * @throws NullPointerException if the given array is {@code null}.
226       * @throws IllegalArgumentException if the given array is empty.
227       * @throws AssertionError if the given object is not present in the given array.
228       */
229      public void assertIsIn(AssertionInfo info, Object actual, Object[] values) {
230        checkIsNotNullAndNotEmpty(values);
231        assertNotNull(info, actual);
232        if (isItemInArray(actual, values)) return;
233        throw failures.failure(info, shouldBeIn(actual, values, comparisonStrategy));
234      }
235    
236      /**
237       * Asserts that the given object is not present in the given array.
238       * @param info contains information about the assertion.
239       * @param actual the given object.
240       * @param values the given array.
241       * @throws NullPointerException if the given array is {@code null}.
242       * @throws IllegalArgumentException if the given array is empty.
243       * @throws AssertionError if the given object is present in the given array.
244       */
245      public void assertIsNotIn(AssertionInfo info, Object actual, Object[] values) {
246        checkIsNotNullAndNotEmpty(values);
247        assertNotNull(info, actual);
248        if (!isItemInArray(actual, values)) return;
249        throw failures.failure(info, shouldNotBeIn(actual, values, comparisonStrategy));
250      }
251    
252      private void checkIsNotNullAndNotEmpty(Object[] values) {
253        if (values == null) throw new NullPointerException("The given array should not be null");
254        if (values.length == 0) throw new IllegalArgumentException("The given array should not be empty");
255      }
256    
257      /**
258       * Returns <code>true</code> if given item is in given array, <code>false</code> otherwise.
259       * @param item the object to look for in arrayOfValues
260       * @param arrayOfValues the array of values
261       * @return <code>true</code> if given item is in given array, <code>false</code> otherwise.
262       */
263      private boolean isItemInArray(Object item, Object[] arrayOfValues) {
264        for (Object value : arrayOfValues)
265          if (areEqual(value, item)) return true;
266        return false;
267      }
268    
269      /**
270       * Asserts that the given object is present in the given collection.
271       * @param info contains information about the assertion.
272       * @param actual the given object.
273       * @param values the given iterable.
274       * @throws NullPointerException if the given collection is {@code null}.
275       * @throws IllegalArgumentException if the given collection is empty.
276       * @throws AssertionError if the given object is not present in the given collection.
277       */
278      public <A> void assertIsIn(AssertionInfo info, A actual, Iterable<? extends A> values) {
279        checkIsNotNullAndNotEmpty(values);
280        assertNotNull(info, actual);
281        if (isActualIn(actual, values)) return;
282        throw failures.failure(info, shouldBeIn(actual, values, comparisonStrategy));
283      }
284    
285      /**
286       * Asserts that the given object is not present in the given collection.
287       * @param info contains information about the assertion.
288       * @param actual the given object.
289       * @param values the given collection.
290       * @throws NullPointerException if the given iterable is {@code null}.
291       * @throws IllegalArgumentException if the given collection is empty.
292       * @throws AssertionError if the given object is present in the given collection.
293       */
294      public <A> void assertIsNotIn(AssertionInfo info, A actual, Iterable<? extends A> values) {
295        checkIsNotNullAndNotEmpty(values);
296        assertNotNull(info, actual);
297        if (!isActualIn(actual, values)) return;
298        throw failures.failure(info, shouldNotBeIn(actual, values, comparisonStrategy));
299      }
300    
301      private void checkIsNotNullAndNotEmpty(Iterable<?> values) {
302        if (values == null) throw new NullPointerException("The given iterable should not be null");
303        if (!values.iterator().hasNext()) throw new IllegalArgumentException("The given iterable should not be empty");
304      }
305    
306      private <A> boolean isActualIn(A actual, Iterable<? extends A> values) {
307        for (A value : values)
308          if (areEqual(value, actual)) return true;
309        return false;
310      }
311    
312      /**
313       * Assert that the given object is lenient equals by ignoring null fields value on other object.
314       * @param info contains information about the assertion.
315       * @param actual the given object.
316       * @param other the object to compare {@code actual} to.
317       * @throws NullPointerException if the actual type is {@code null}.
318       * @throws NullPointerException if the other type is {@code null}.
319       * @throws AssertionError if the actual and the given object are not lenient equals.
320       * @throws AssertionError if the other object is not an instance of the actual type.
321       */
322      public <A> void assertIsLenientEqualsToByIgnoringNullFields(AssertionInfo info, A actual, A other) {
323        assertIsInstanceOf(info, other, actual.getClass());
324        List<String> fieldsNames = new LinkedList<String>();
325        List<Object> values = new LinkedList<Object>();
326        List<String> nullFields = new LinkedList<String>();
327        for (Field field : actual.getClass().getDeclaredFields()) {
328          try {
329            Object otherFieldValue = propertySupport.propertyValue(field.getName(), field.getType(), other);
330            if (otherFieldValue != null) {
331              Object actualFieldValue = propertySupport.propertyValue(field.getName(), field.getType(), actual);
332              if (!otherFieldValue.equals(actualFieldValue)) {
333                fieldsNames.add(field.getName());
334                values.add(otherFieldValue);
335              }
336            } else {
337              nullFields.add(field.getName());
338            }
339          } catch (IntrospectionError e) {
340            // Not readeable field, skip.
341          }
342        }
343        if (fieldsNames.isEmpty()) return;
344        throw failures.failure(info, shouldBeLenientEqualByIgnoring(actual, fieldsNames, values, nullFields));
345      }
346    
347      /**
348       * Assert that the given object is lenient equals to other object by comparing given fields value only.
349       * @param info contains information about the assertion.
350       * @param actual the given object.
351       * @param other the object to compare {@code actual} to.
352       * @param fields accepted fields
353       * @throws NullPointerException if the actual type is {@code null}.
354       * @throws NullPointerException if the other type is {@code null}.
355       * @throws AssertionError if the actual and the given object are not lenient equals.
356       * @throws AssertionError if the other object is not an instance of the actual type.
357       * @throws IntrospectionError if a field does not exist in actual.
358       */
359      public <A> void assertIsLenientEqualsToByAcceptingFields(AssertionInfo info, A actual, A other, String... fields) {
360        assertIsInstanceOf(info, other, actual.getClass());
361        List<String> rejectedFieldsNames = new LinkedList<String>();
362        List<Object> expectedValues = new LinkedList<Object>();
363        for (String fieldName : fields) {
364          Object actualFieldValue = propertySupport.propertyValue(fieldName, Object.class, actual);
365          Object otherFieldValue = propertySupport.propertyValue(fieldName, Object.class, other);
366          if (!(actualFieldValue == otherFieldValue || (actualFieldValue != null && actualFieldValue.equals(otherFieldValue)))) {
367            rejectedFieldsNames.add(fieldName);
368            expectedValues.add(otherFieldValue);
369          }
370        }
371        if (rejectedFieldsNames.isEmpty()) return;
372        throw failures.failure(info, shouldBeLenientEqualByAccepting(actual, rejectedFieldsNames, expectedValues, list(fields)));
373      }
374    
375      /**
376       * Assert that the given object is lenient equals by ignoring fields.
377       * @param info contains information about the assertion.
378       * @param actual the given object.
379       * @param other the object to compare {@code actual} to.
380       * @param fields the fields to ignore in comparison
381       * @throws NullPointerException if the actual type is {@code null}.
382       * @throws NullPointerException if the other type is {@code null}.
383       * @throws AssertionError if the actual and the given object are not lenient equals.
384       * @throws AssertionError if the other object is not an instance of the actual type.
385       */
386      public <A> void assertIsLenientEqualsToByIgnoringFields(AssertionInfo info, A actual, A other, String... fields) {
387        assertIsInstanceOf(info, other, actual.getClass());
388        List<String> fieldsNames = new LinkedList<String>();
389        List<Object> expectedValues = new LinkedList<Object>();
390        Set<String> ignoredFields = set(fields);
391        for (Field field : actual.getClass().getDeclaredFields()) {
392          try {
393            if (!ignoredFields.contains(field.getName())) {
394              String fieldName = field.getName();
395              Object actualFieldValue = propertySupport.propertyValue(fieldName, Object.class, actual);
396              Object otherFieldValue = propertySupport.propertyValue(fieldName, Object.class, other);
397              if (!org.fest.util.Objects.areEqual(actualFieldValue, otherFieldValue)) {
398                fieldsNames.add(fieldName);
399                expectedValues.add(otherFieldValue);
400              }
401            }
402          } catch (IntrospectionError e) {
403            // Not readeable field, skip.
404          }
405        }
406        if (fieldsNames.isEmpty()) return;
407        throw failures.failure(info, shouldBeLenientEqualByIgnoring(actual, fieldsNames, expectedValues, list(fields)));
408      }
409    }