001    /*
002     * Created on Nov 19, 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.ShouldBeSorted.*;
018    import static org.fest.assertions.error.ShouldContainAtIndex.shouldContainAtIndex;
019    import static org.fest.assertions.error.ShouldNotContainAtIndex.shouldNotContainAtIndex;
020    import static org.fest.assertions.internal.CommonValidations.checkIndexValueIsValid;
021    
022    import java.util.ArrayList;
023    import java.util.Comparator;
024    import java.util.List;
025    
026    import org.fest.assertions.core.AssertionInfo;
027    import org.fest.assertions.data.Index;
028    import org.fest.util.ComparatorBasedComparisonStrategy;
029    import org.fest.util.ComparisonStrategy;
030    import org.fest.util.StandardComparisonStrategy;
031    import org.fest.util.VisibleForTesting;
032    
033    /**
034     * Reusable assertions for <code>{@link List}</code>s.
035     * 
036     * @author Alex Ruiz
037     * @author Yvonne Wang
038     * @author Joel Costigliola
039     */
040    // TODO inherits from Collections to avoid repeating comparisonStrategy ?
041    public class Lists {
042    
043      private static final Lists INSTANCE = new Lists();
044    
045      /**
046       * Returns the singleton instance of this class.
047       * @return the singleton instance of this class.
048       */
049      public static Lists instance() {
050        return INSTANCE;
051      }
052    
053      private ComparisonStrategy comparisonStrategy;
054    
055      @VisibleForTesting
056      Failures failures = Failures.instance();
057    
058      @VisibleForTesting
059      Lists() {
060        this(StandardComparisonStrategy.instance());
061      }
062    
063      public Lists(ComparisonStrategy comparisonStrategy) {
064        this.comparisonStrategy = comparisonStrategy;
065      }
066    
067      @VisibleForTesting
068      public Comparator<?> getComparator() {
069        if (comparisonStrategy instanceof ComparatorBasedComparisonStrategy) {
070          return ((ComparatorBasedComparisonStrategy)comparisonStrategy).getComparator();
071        }
072        return null;
073      }
074    
075      /**
076       * Verifies that the given {@code List} contains the given object at the given index.
077       * @param info contains information about the assertion.
078       * @param actual the given {@code List}.
079       * @param value the object to look for.
080       * @param index the index where the object should be stored in the given {@code List}.
081       * @throws AssertionError if the given {@code List} is {@code null} or empty.
082       * @throws NullPointerException if the given {@code Index} is {@code null}.
083       * @throws IndexOutOfBoundsException if the value of the given {@code Index} is equal to or greater than the size of
084       *           the given {@code List}.
085       * @throws AssertionError if the given {@code List} does not contain the given object at the given index.
086       */
087      public void assertContains(AssertionInfo info, List<?> actual, Object value, Index index) {
088        assertNotNull(info, actual);
089        Iterables.instance().assertNotEmpty(info, actual);
090        checkIndexValueIsValid(index, actual.size() - 1);
091        Object actualElement = actual.get(index.value);
092        if (areEqual(actualElement, value)) return;
093        throw failures.failure(info,
094            shouldContainAtIndex(actual, value, index, actual.get(index.value), comparisonStrategy));
095      }
096    
097      /**
098       * Verifies that the given {@code List} does not contain the given object at the given index.
099       * @param info contains information about the assertion.
100       * @param actual the given {@code List}.
101       * @param value the object to look for.
102       * @param index the index where the object should be stored in the given {@code List}.
103       * @throws AssertionError if the given {@code List} is {@code null}.
104       * @throws NullPointerException if the given {@code Index} is {@code null}.
105       * @throws AssertionError if the given {@code List} contains the given object at the given index.
106       */
107      public void assertDoesNotContain(AssertionInfo info, List<?> actual, Object value, Index index) {
108        assertNotNull(info, actual);
109        checkIndexValueIsValid(index, Integer.MAX_VALUE);
110        int indexValue = index.value;
111        if (indexValue >= actual.size()) return;
112        Object actualElement = actual.get(index.value);
113        if (!areEqual(actualElement, value)) return;
114        throw failures.failure(info, shouldNotContainAtIndex(actual, value, index, comparisonStrategy));
115      }
116    
117      /**
118       * Verifies that the actual list is sorted into ascending order according to the natural ordering of its elements.
119       * <p>
120       * All list elements must implement the {@link Comparable} interface and must be mutually comparable (that is,
121       * e1.compareTo(e2) must not throw a ClassCastException for any elements e1 and e2 in the list), examples :
122       * <ul>
123       * <li>a list composed of {"a1", "a2", "a3"} is ok because the element type (String) is Comparable</li>
124       * <li>a list composed of Rectangle {r1, r2, r3} is <b>NOT ok</b> because Rectangle is not Comparable</li>
125       * <li>a list composed of {True, "abc", False} is <b>NOT ok</b> because elements are not mutually comparable</li>
126       * </ul>
127       * Empty lists are considered sorted.</br> Unique element lists are considered sorted unless the element type is not
128       * Comparable.
129       * 
130       * @param info contains information about the assertion.
131       * @param actual the given {@code List}.
132       * 
133       * @throws AssertionError if the actual list is not sorted into ascending order according to the natural ordering of
134       *           its elements.
135       * @throws AssertionError if the actual list is <code>null</code>.
136       * @throws AssertionError if the actual list element type does not implement {@link Comparable}.
137       * @throws AssertionError if the actual list elements are not mutually {@link Comparable}.
138       */
139      public void assertIsSorted(AssertionInfo info, List<?> actual) {
140        assertNotNull(info, actual);
141        if (comparisonStrategy instanceof ComparatorBasedComparisonStrategy) {
142          // instead of comparing elements with their natural comparator, use the one set by client.
143          Comparator<?> comparator = ((ComparatorBasedComparisonStrategy)comparisonStrategy).getComparator();
144          assertIsSortedAccordingToComparator(info, actual, comparator);
145          return;
146        }
147        try {
148          // sorted assertion is only relevant if elements are Comparable, we assume they are
149          List<Comparable<Object>> comparableList = listOfComparableElements(actual);
150          // array with 0 or 1 element are considered sorted.
151          if (comparableList.size() <= 1) return;
152          for (int i = 0; i < comparableList.size() - 1; i++) {
153            // array is sorted in ascending order iif element i is less or equal than element i+1
154            if (comparableList.get(i).compareTo(comparableList.get(i + 1)) > 0)
155              throw failures.failure(info, shouldBeSorted(i, actual));
156          }
157        } catch (ClassCastException e) {
158          // elements are either not Comparable or not mutually Comparable (e.g. List<Object> containing String and Integer)
159          throw failures.failure(info, shouldHaveMutuallyComparableElements(actual));
160        }
161      }
162    
163      /**
164       * Verifies that the actual list is sorted according to the given comparator.</br> Empty lists are considered sorted
165       * whatever the comparator is.</br> One element lists are considered sorted if element is compatible with comparator.
166       * 
167       * @param info contains information about the assertion.
168       * @param actual the given {@code List}.
169       * @param comparator the {@link Comparator} used to compare list elements
170       * 
171       * @throws AssertionError if the actual list is not sorted according to the given comparator.
172       * @throws AssertionError if the actual list is <code>null</code>.
173       * @throws NullPointerException if the given comparator is <code>null</code>.
174       * @throws AssertionError if the actual list elements are not mutually comparabe according to given Comparator.
175       */
176      @SuppressWarnings({ "rawtypes", "unchecked" })
177      public void assertIsSortedAccordingToComparator(AssertionInfo info, List<?> actual,
178          Comparator<? extends Object> comparator) {
179        assertNotNull(info, actual);
180        if (comparator == null) throw new NullPointerException("The given comparator should not be null");
181        try {
182          // Empty collections are considered sorted even if comparator can't be applied to their element type
183          // We can't verify that point because of erasure type at runtime.
184          if (actual.size() == 0) return;
185          Comparator rawComparator = comparator;
186          if (actual.size() == 1) {
187            // Compare unique element with itself to verify thta it is compatible with comparator (a ClassCastException is
188            // thrown if not). We have to use a raw comparator to compare the unique element of actual ... :(
189            rawComparator.compare(actual.get(0), actual.get(0));
190            return;
191          }
192          for (int i = 0; i < actual.size() - 1; i++) {
193            // List is sorted in comparator defined order iif current element is less or equal than next element
194            if (rawComparator.compare(actual.get(i), actual.get(i + 1)) > 0)
195              throw failures.failure(info, shouldBeSortedAccordingToGivenComparator(i, actual, comparator));
196          }
197        } catch (ClassCastException e) {
198          throw failures.failure(info, shouldHaveComparableElementsAccordingToGivenComparator(actual, comparator));
199        }
200      }
201    
202      @SuppressWarnings("unchecked")
203      private static List<Comparable<Object>> listOfComparableElements(List<?> collection) {
204        List<Comparable<Object>> listOfComparableElements = new ArrayList<Comparable<Object>>();
205        for (Object object : collection) {
206          listOfComparableElements.add((Comparable<Object>) object);
207        }
208        return listOfComparableElements;
209      }
210    
211      private void assertNotNull(AssertionInfo info, List<?> actual) {
212        Objects.instance().assertNotNull(info, actual);
213      }
214    
215      /**
216       * Delegates to {@link ComparisonStrategy#areEqual(Object, Object)}
217       */
218      private boolean areEqual(Object actual, Object other) {
219        return comparisonStrategy.areEqual(actual, other);
220      }
221    
222    }