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     */
051    public class Objects {
052    
053      private static final Objects INSTANCE = new Objects();
054    
055      /**
056       * Returns the singleton instance of this class based on {@link StandardComparisonStrategy}.
057       * @return the singleton instance of this class based on {@link StandardComparisonStrategy}.
058       */
059      public static Objects instance() {
060        return INSTANCE;
061      }
062    
063      @VisibleForTesting
064      Failures failures = Failures.instance();
065      
066      @VisibleForTesting
067      PropertySupport propertySupport = PropertySupport.instance();
068    
069      private final ComparisonStrategy comparisonStrategy;
070      
071      @VisibleForTesting
072      Objects() {
073        this(StandardComparisonStrategy.instance());
074      }
075    
076      public Objects(ComparisonStrategy comparisonStrategy) {
077        this.comparisonStrategy = comparisonStrategy;
078      }
079      
080      @VisibleForTesting
081      public Comparator<?> getComparator() {
082        if (comparisonStrategy instanceof ComparatorBasedComparisonStrategy) {
083          return ((ComparatorBasedComparisonStrategy)comparisonStrategy).getComparator();
084        }
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, Object 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, Object 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(Object actual, Iterable<? extends A> values) {
307        for (Object 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 void assertIsLenientEqualsToByIgnoringNullFields(AssertionInfo info, Object actual, Object 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(), other, field.getType());
330                            if(otherFieldValue != null){
331                                    Object actualFieldValue = propertySupport.propertyValue(field.getName(), actual, field.getType());
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 by ignoring null fields value on other object.
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 void assertIsLenientEqualsToByAcceptingFields(AssertionInfo info, Object actual, Object other, String... fields){
360            assertIsInstanceOf(info, other, actual.getClass());
361            List<String> fieldsNames = new LinkedList<String>();
362            List<Object> values = new LinkedList<Object>();
363            for (String fieldName : fields) {
364                    Object actualFieldValue = propertySupport.propertyValue(fieldName, actual, Object.class);
365                    Object otherFieldValue = propertySupport.propertyValue(fieldName, other, Object.class);
366                    if(!(actualFieldValue == otherFieldValue || (actualFieldValue != null && actualFieldValue.equals(otherFieldValue)))){
367                            fieldsNames.add(fieldName);
368                            values.add(otherFieldValue);                            
369                    }
370            }
371            if(fieldsNames.isEmpty()) return;
372            throw failures.failure(info, shouldBeLenientEqualByAccepting(actual, fieldsNames, values, 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 ignore fields
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 void assertIsLenientEqualsToByIgnoringFields(AssertionInfo info, Object actual, Object other, String... fields) {
387                    assertIsInstanceOf(info, other, actual.getClass());
388                    List<String> fieldsNames = new LinkedList<String>();
389                    List<Object> values = 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                                            Object otherFieldValue = propertySupport.propertyValue(field.getName(), other, field.getType());
395                                            if(otherFieldValue != null){
396                                                    Object actualFieldValue = propertySupport.propertyValue(field.getName(), actual, field.getType());
397                                                    if(!otherFieldValue.equals(actualFieldValue)){
398                                                            fieldsNames.add(field.getName());
399                                                            values.add(otherFieldValue);
400                                                    }
401                                            }
402                                    }       
403                            } catch (IntrospectionError e) {
404                                    // Not readeable field, skip.
405                            }
406                    }               
407                    if(fieldsNames.isEmpty()) return;
408                    throw failures.failure(info,shouldBeLenientEqualByIgnoring(actual, fieldsNames, values, list(fields)));                 
409            }  
410    }