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) {
084          return ((ComparatorBasedComparisonStrategy)comparisonStrategy).getComparator();
085        }
086        return null;
087      }
088    
089      /**
090       * Verifies that the given object is an instance of the given type.
091       * @param info contains information about the assertion.
092       * @param actual the given object.
093       * @param type the type to check the given object against.
094       * @throws NullPointerException if the given type is {@code null}.
095       * @throws AssertionError if the given object is {@code null}.
096       * @throws AssertionError if the given object is not an instance of the given type.
097       */
098      public void assertIsInstanceOf(AssertionInfo info, Object actual, Class<?> type) {
099        if (type == null) throw new NullPointerException("The given type should not be null");
100        assertNotNull(info, actual);
101        if (type.isInstance(actual)) return;
102        throw failures.failure(info, shouldBeInstance(actual, type));
103      }
104    
105      /**
106       * Verifies that the given object is an instance of any of the given types.
107       * @param info contains information about the assertion.
108       * @param actual the given object.
109       * @param types the types to check the given object against.
110       * @throws NullPointerException if the given array is {@code null}.
111       * @throws IllegalArgumentException if the given array is empty.
112       * @throws NullPointerException if the given array has {@code null} elements.
113       * @throws AssertionError if the given object is {@code null}.
114       * @throws AssertionError if the given object is not an instance of any of the given types.
115       */
116      public void assertIsInstanceOfAny(AssertionInfo info, Object actual, Class<?>[] types) {
117        checkIsNotNullAndIsNotEmpty(types);
118        assertNotNull(info, actual);
119        boolean found = false;
120        for (Class<?> type : types) {
121          if (type == null) {
122            String format = "The given array of types:<%s> should not have null elements";
123            throw new NullPointerException(String.format(format, toStringOf(types)));
124          }
125          if (type.isInstance(actual)) {
126            found = true;
127            break;
128          }
129        }
130        if (found) return;
131        throw failures.failure(info, shouldBeInstanceOfAny(actual, types));
132      }
133    
134      private void checkIsNotNullAndIsNotEmpty(Class<?>[] types) {
135        if (types == null) throw new NullPointerException("The given array of types should not be null");
136        if (types.length == 0) throw new IllegalArgumentException("The given array of types should not be empty");
137      }
138    
139      /**
140       * Asserts that two objects are equal.
141       * @param info contains information about the assertion.
142       * @param actual the "actual" object.
143       * @param expected the "expected" object.
144       * @throws AssertionError if {@code actual} is not equal to {@code expected}. This method will throw a
145       *           {@code org.junit.ComparisonFailure} instead if JUnit is in the classpath and the given objects are not
146       *           equal.
147       */
148      public void assertEqual(AssertionInfo info, Object actual, Object expected) {
149        if (areEqual(actual, expected)) return;
150        throw failures.failure(info, shouldBeEqual(actual, expected, comparisonStrategy));
151      }
152    
153      /**
154       * Asserts that two objects are not equal.
155       * @param info contains information about the assertion.
156       * @param actual the given object.
157       * @param other the object to compare {@code actual} to.
158       * @throws AssertionError if {@code actual} is equal to {@code other}.
159       */
160      public void assertNotEqual(AssertionInfo info, Object actual, Object other) {
161        if (!areEqual(actual, other)) return;
162        throw failures.failure(info, shouldNotBeEqual(actual, other, comparisonStrategy));
163      }
164    
165      /**
166       * Compares actual and other with standard strategy (null safe equals check).
167       * @param actual the object to compare to other
168       * @param other the object to compare to actual
169       * @return true if actual and other are equal (null safe equals check), false otherwise.
170       */
171      private boolean areEqual(Object actual, Object other) {
172        return comparisonStrategy.areEqual(other, actual);
173      }
174    
175      /**
176       * Asserts that the given object is {@code null}.
177       * @param info contains information about the assertion.
178       * @param actual the given object.
179       * @throws AssertionError if the given object is not {@code null}.
180       */
181      public void assertNull(AssertionInfo info, Object actual) {
182        if (actual == null) return;
183        throw failures.failure(info, shouldBeEqual(actual, null, comparisonStrategy));
184      }
185    
186      /**
187       * Asserts that the given object is not {@code null}.
188       * @param info contains information about the assertion.
189       * @param actual the given object.
190       * @throws AssertionError if the given object is {@code null}.
191       */
192      public void assertNotNull(AssertionInfo info, Object actual) {
193        if (actual != null) return;
194        throw failures.failure(info, shouldNotBeNull());
195      }
196    
197      /**
198       * Asserts that two objects refer to the same object.
199       * @param info contains information about the assertion.
200       * @param actual the given object.
201       * @param expected the expected object.
202       * @throws AssertionError if the given objects do not refer to the same object.
203       */
204      public void assertSame(AssertionInfo info, Object actual, Object expected) {
205        if (actual == expected) return;
206        throw failures.failure(info, shouldBeSame(actual, expected));
207      }
208    
209      /**
210       * Asserts that two objects do not refer to the same object.
211       * @param info contains information about the assertion.
212       * @param actual the given object.
213       * @param other the object to compare {@code actual} to.
214       * @throws AssertionError if the given objects refer to the same object.
215       */
216      public void assertNotSame(AssertionInfo info, Object actual, Object other) {
217        if (actual != other) return;
218        throw failures.failure(info, shouldNotBeSame(actual));
219      }
220    
221      /**
222       * Asserts that the given object is present in the given array.
223       * @param info contains information about the assertion.
224       * @param actual the given object.
225       * @param values the given array.
226       * @throws NullPointerException if the given array is {@code null}.
227       * @throws IllegalArgumentException if the given array is empty.
228       * @throws AssertionError if the given object is not present in the given array.
229       */
230      public void assertIsIn(AssertionInfo info, Object actual, Object[] values) {
231        checkIsNotNullAndNotEmpty(values);
232        assertNotNull(info, actual);
233        if (isItemInArray(actual, values)) return;
234        throw failures.failure(info, shouldBeIn(actual, values, comparisonStrategy));
235      }
236    
237      /**
238       * Asserts that the given object is not present in the given array.
239       * @param info contains information about the assertion.
240       * @param actual the given object.
241       * @param values the given array.
242       * @throws NullPointerException if the given array is {@code null}.
243       * @throws IllegalArgumentException if the given array is empty.
244       * @throws AssertionError if the given object is present in the given array.
245       */
246      public void assertIsNotIn(AssertionInfo info, Object actual, Object[] values) {
247        checkIsNotNullAndNotEmpty(values);
248        assertNotNull(info, actual);
249        if (!isItemInArray(actual, values)) return;
250        throw failures.failure(info, shouldNotBeIn(actual, values, comparisonStrategy));
251      }
252    
253      private void checkIsNotNullAndNotEmpty(Object[] values) {
254        if (values == null) throw new NullPointerException("The given array should not be null");
255        if (values.length == 0) throw new IllegalArgumentException("The given array should not be empty");
256      }
257    
258      /**
259       * Returns <code>true</code> if given item is in given array, <code>false</code> otherwise. 
260       * @param item the object to look for in arrayOfValues 
261       * @param arrayOfValues the array of values
262       * @return <code>true</code> if given item is in given array, <code>false</code> otherwise.
263       */
264      private boolean isItemInArray(Object item, Object[] arrayOfValues) {
265        for (Object value : arrayOfValues)
266          if (areEqual(value, item)) return true;
267        return false;
268      }
269    
270      /**
271       * Asserts that the given object is present in the given collection.
272       * @param info contains information about the assertion.
273       * @param actual the given object.
274       * @param values the given iterable.
275       * @throws NullPointerException if the given collection is {@code null}.
276       * @throws IllegalArgumentException if the given collection is empty.
277       * @throws AssertionError if the given object is not present in the given collection.
278       */
279      public <A> void assertIsIn(AssertionInfo info, A actual, Iterable<? extends A> values) {
280        checkIsNotNullAndNotEmpty(values);
281        assertNotNull(info, actual);
282        if (isActualIn(actual, values)) return;
283        throw failures.failure(info, shouldBeIn(actual, values, comparisonStrategy));
284      }
285    
286      /**
287       * Asserts that the given object is not present in the given collection.
288       * @param info contains information about the assertion.
289       * @param actual the given object.
290       * @param values the given collection.
291       * @throws NullPointerException if the given iterable is {@code null}.
292       * @throws IllegalArgumentException if the given collection is empty.
293       * @throws AssertionError if the given object is present in the given collection.
294       */
295      public <A> void assertIsNotIn(AssertionInfo info, A actual, Iterable<? extends A> values) {
296        checkIsNotNullAndNotEmpty(values);
297        assertNotNull(info, actual);
298        if (!isActualIn(actual, values)) return;
299        throw failures.failure(info, shouldNotBeIn(actual, values, comparisonStrategy));
300      }
301    
302      private void checkIsNotNullAndNotEmpty(Iterable<?> values) {
303        if (values == null) throw new NullPointerException("The given iterable should not be null");
304        if (!values.iterator().hasNext()) throw new IllegalArgumentException("The given iterable should not be empty");
305      }
306    
307      private <A> boolean isActualIn(A actual, Iterable<? extends A> values) {
308        for (A value : values)
309          if (areEqual(value, actual)) return true;
310        return false;
311      }
312      
313      /**
314       * Assert that the given object is lenient equals by ignoring null fields value on other object.
315       * @param info contains information about the assertion.
316       * @param actual the given object.
317       * @param other the object to compare {@code actual} to.
318       * @throws NullPointerException if the actual type is {@code null}.
319       * @throws NullPointerException if the other type is {@code null}.
320       * @throws AssertionError if the actual and the given object are not lenient equals.
321       * @throws AssertionError if the other object is not an instance of the actual type.
322       */
323      public <A> void assertIsLenientEqualsToByIgnoringNullFields(AssertionInfo info, A actual, A other){
324            assertIsInstanceOf(info, other, actual.getClass());
325            List<String> fieldsNames = new LinkedList<String>();
326            List<Object> values = new LinkedList<Object>();
327            List<String> nullFields = new LinkedList<String>();
328            for (Field field : actual.getClass().getDeclaredFields()) {
329                    try {
330                            Object otherFieldValue = propertySupport.propertyValue(field.getName(), field.getType(), other);
331                            if (otherFieldValue != null) {
332                                    Object actualFieldValue = propertySupport.propertyValue(field.getName(), field.getType(), actual);
333                                    if (!otherFieldValue.equals(actualFieldValue)){
334                                            fieldsNames.add(field.getName());
335                                            values.add(otherFieldValue);
336                                    }
337                            } else {
338                                    nullFields.add(field.getName());
339                            }
340                    } catch (IntrospectionError e) {
341                            // Not readeable field, skip.
342                    }
343            }
344            if (fieldsNames.isEmpty()) return;
345            throw failures.failure(info, shouldBeLenientEqualByIgnoring (actual, fieldsNames, values, nullFields));   
346      }
347      
348      /**
349       * Assert that the given object is lenient equals by ignoring null fields value on other object.
350       * @param info contains information about the assertion.
351       * @param actual the given object.
352       * @param other the object to compare {@code actual} to.
353       * @param fields accepted fields
354       * @throws NullPointerException if the actual type is {@code null}.
355       * @throws NullPointerException if the other type is {@code null}.
356       * @throws AssertionError if the actual and the given object are not lenient equals.
357       * @throws AssertionError if the other object is not an instance of the actual type.
358       * @throws IntrospectionError if a field does not exist in actual.
359       */
360      public <A> void assertIsLenientEqualsToByAcceptingFields(AssertionInfo info, A actual, A other, String... fields){
361            assertIsInstanceOf(info, other, actual.getClass());
362            List<String> fieldsNames = new LinkedList<String>();
363            List<Object> values = new LinkedList<Object>();
364            for (String fieldName : fields) {
365                    Object actualFieldValue = propertySupport.propertyValue(fieldName, Object.class, actual);
366                    Object otherFieldValue = propertySupport.propertyValue(fieldName, Object.class, other);
367                    if (!(actualFieldValue == otherFieldValue || (actualFieldValue != null && actualFieldValue.equals(otherFieldValue)))) {
368                            fieldsNames.add(fieldName);
369                            values.add(otherFieldValue);                            
370                    }
371            }
372            if (fieldsNames.isEmpty()) return;
373            throw failures.failure(info, shouldBeLenientEqualByAccepting(actual, fieldsNames, values, list(fields)));         
374      }
375    
376      /**
377       * Assert that the given object is lenient equals by ignoring fields.
378       * @param info contains information about the assertion.
379       * @param actual the given object.
380       * @param other the object to compare {@code actual} to.
381       * @param fields ignore fields
382       * @throws NullPointerException if the actual type is {@code null}.
383       * @throws NullPointerException if the other type is {@code null}.
384       * @throws AssertionError if the actual and the given object are not lenient equals.
385       * @throws AssertionError if the other object is not an instance of the actual type.
386       */
387            public <A> void assertIsLenientEqualsToByIgnoringFields(AssertionInfo info, A actual, A other, String... fields) {
388                    assertIsInstanceOf(info, other, actual.getClass());
389                    List<String> fieldsNames = new LinkedList<String>();
390                    List<Object> values = new LinkedList<Object>();
391                    Set<String> ignoredFields = set(fields);
392                    for (Field field : actual.getClass().getDeclaredFields()) {
393                            try {
394                                    if (!ignoredFields.contains(field.getName())) {
395                                            Object otherFieldValue = propertySupport.propertyValue(field.getName(), field.getType(), other);
396                                            if (otherFieldValue != null) {
397                                                    Object actualFieldValue = propertySupport.propertyValue(field.getName(), field.getType(), actual);
398                                                    if (!otherFieldValue.equals(actualFieldValue)) {
399                                                            fieldsNames.add(field.getName());
400                                                            values.add(otherFieldValue);
401                                                    }
402                                            }
403                                    }
404                            } catch (IntrospectionError e) {
405                                    // Not readeable field, skip.
406                            }
407                    }
408                    if (fieldsNames.isEmpty()) return;
409                    throw failures.failure(info,shouldBeLenientEqualByIgnoring(actual, fieldsNames, values, list(fields)));                 
410            }
411    }