001    /*
002     * Created on Mar 29, 2009
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 @2009-2011 the original author or authors.
014     */
015    package org.fest.assertions;
016    
017    import static java.util.Collections.emptyList;
018    import static org.fest.assertions.Formatting.format;
019    import static org.fest.util.Collections.list;
020    import static org.fest.util.Objects.areEqual;
021    
022    import java.util.*;
023    
024    import org.fest.util.IntrospectionError;
025    
026    /**
027     * Assertions for <code>{@link List}</code>s.
028     * <p>
029     * To create a new instance of this class invoke <code>{@link Assertions#assertThat(List)}</code>.
030     * </p>
031     *
032     * @author Alex Ruiz
033     * @author Yvonne Wang
034     *
035     * @since 1.1
036     */
037    public class ListAssert extends ObjectGroupAssert<ListAssert, List<?>> {
038    
039      /**
040       * Creates a new </code>{@link ListAssert}</code>.
041       * @param actual the target to verify.
042       */
043      protected ListAssert(List<?> actual) {
044        super(ListAssert.class, actual);
045      }
046    
047      /**
048       * Verifies that the actual <code>{@link List}</code> contains the given object at the given index.
049       * @param o the object to look for.
050       * @param index the index where the object should be stored in the actual {@code List}.
051       * @return this assertion object.
052       * @throws NullPointerException if the given <code>Index</code> is {@code null}.
053       * @throws IndexOutOfBoundsException if the value of the given <code>Index</code> is negative, or equal to or greater
054       * than the size of the actual {@code List}.
055       * @throws AssertionError if the given {@code List} does not contain the given object at the given index.
056       */
057      public ListAssert contains(Object o, Index index) {
058        if (index == null) throw new NullPointerException(formattedErrorMessage("The given index should not be null"));
059        isNotNull().isNotEmpty();
060        int indexValue = index.value();
061        int listSize = actualGroupSize();
062        if (indexValue < 0 || indexValue >= listSize) failIndexOutOfBounds(indexValue);
063        Object actualElement = actual.get(indexValue);
064        if (!areEqual(actualElement, o)) failElementNotFound(o, actualElement, indexValue);
065        return this;
066      }
067    
068      private void failElementNotFound(Object e, Object a, int index) {
069        failIfCustomMessageIsSet();
070        fail(format("expecting <%s> at index <%s> but found <%s>", e, index, a));
071      }
072    
073      private void failIndexOutOfBounds(int index) {
074        throw new IndexOutOfBoundsException(formattedErrorMessage(
075            format("The index <%s> should be greater than or equal to zero and less than %s", index, actualGroupSize())));
076      }
077    
078      /**
079       * Verifies that the actual <code>{@link List}</code> contains the given sequence of objects, without any other
080       * objects between them.
081       * @param sequence the sequence of objects to look for.
082       * @return this assertion object.
083       * @throws AssertionError if the actual {@code List} is {@code null}.
084       * @throws AssertionError if the given array is {@code null}.
085       * @throws AssertionError if the actual {@code List} does not contain the given sequence of objects.
086       */
087      public ListAssert containsSequence(Object... sequence) {
088        isNotNull();
089        validateIsNotNull(sequence);
090        int sequenceSize = sequence.length;
091        if (sequenceSize == 0) return this;
092        int indexOfFirst = actual.indexOf(sequence[0]);
093        if (indexOfFirst == -1) failIfSequenceNotFound(sequence);
094        int listSize = actualGroupSize();
095        for (int i = 0; i < sequenceSize; i++) {
096          int actualIndex = indexOfFirst + i;
097          if (actualIndex > listSize - 1) failIfSequenceNotFound(sequence);
098          if (!areEqual(sequence[i], actual.get(actualIndex))) failIfSequenceNotFound(sequence);
099        }
100        return this;
101      }
102    
103      private void failIfSequenceNotFound(Object[] notFound) {
104        failIfCustomMessageIsSet();
105        fail(format("list:<%s> does not contain the sequence:<%s>", actual, notFound));
106      }
107    
108      /**
109       * Verifies that the actual <code>{@link List}</code> starts with the given sequence of objects, without any other
110       * objects between them. Same as <code>{@link #containsSequence}</code>, but verifies also that first given object is
111       * also first element of {@code List}.
112       * @param sequence the sequence of objects to look for.
113       * @return this assertion object.
114       * @throws AssertionError if the actual {@code List} is {@code null}.
115       * @throws AssertionError if the given array is {@code null}.
116       * @throws AssertionError if the actual {@code List} is not empty and with the given sequence of objects is
117       * empty.
118       * @throws AssertionError if the actual {@code List} does not start with the given sequence of objects.
119       */
120      public ListAssert startsWith(Object... sequence) {
121        isNotNull();
122        validateIsNotNull(sequence);
123        int sequenceSize = sequence.length;
124        int listSize = actualGroupSize();
125        if (sequenceSize == 0 && listSize == 0) return this;
126        if (sequenceSize == 0 && listSize != 0) failIfNotStartingWithSequence(sequence);
127        if (listSize < sequenceSize) failIfNotStartingWithSequence(sequence);
128        for (int i = 0; i < sequenceSize; i++)
129          if (!areEqual(sequence[i], actual.get(i))) failIfNotStartingWithSequence(sequence);
130        return this;
131      }
132    
133      private void failIfNotStartingWithSequence(Object[] notFound) {
134        failIfCustomMessageIsSet();
135        fail(format("list:<%s> does not start with the sequence:<%s>", actual, notFound));
136      }
137    
138      /**
139       * Verifies that the actual <code>{@link List}</code> ends with the given sequence of objects, without any other
140       * objects between them. Same as <code>{@link #containsSequence}</code>, but verifies also that last given object is
141       * also last element of {@code List}.
142       * @param sequence the sequence of objects to look for.
143       * @return this assertion object.
144       * @throws AssertionError if the actual {@code List} is {@code null}.
145       * @throws AssertionError if the given array is {@code null}.
146       * @throws AssertionError if the actual {@code List} is not empty and with the given sequence of objects is
147       * empty.
148       * @throws AssertionError if the actual {@code List} does not end with the given sequence of objects.
149       */
150      public ListAssert endsWith(Object... sequence) {
151        isNotNull();
152        validateIsNotNull(sequence);
153        int sequenceSize = sequence.length;
154        int listSize = actualGroupSize();
155        if (sequenceSize == 0 && listSize == 0) return this;
156        if (sequenceSize == 0 && listSize != 0) failIfNotEndingWithSequence(sequence);
157        if (listSize < sequenceSize) failIfNotEndingWithSequence(sequence);
158        for (int i = 0; i < sequenceSize; i++) {
159          int sequenceIndex = sequenceSize - 1 - i;
160          int listIndex = listSize - 1 - i;
161          if (!areEqual(sequence[sequenceIndex], actual.get(listIndex))) failIfNotEndingWithSequence(sequence);
162        }
163        return this;
164      }
165    
166      private void failIfNotEndingWithSequence(Object[] notFound) {
167        failIfCustomMessageIsSet();
168        fail(format("list:<%s> does not end with the sequence:<%s>", actual, notFound));
169      }
170    
171      /**
172       * Returns the number of elements in the actual <code>{@link List}</code>.
173       * @return the number of elements in the actual {@code List}.
174       * @throws AssertionError if the actual {@code List} is {@code null}.
175       */
176      @Override protected int actualGroupSize() {
177        isNotNull();
178        return actual.size();
179      }
180    
181      /**
182       * Verifies that the actual <code>{@link List}</code> contains the given objects, in the same order. This method works
183       * just like <code>{@link #isEqualTo(List)}</code>, with the difference that internally the given array is converted
184       * to a {@code List}.
185       * @param objects the objects to look for.
186       * @return this assertion object.
187       * @throws AssertionError if the actual {@code List} is {@code null}.
188       * @throws NullPointerException if the given array is {@code null}.
189       * @throws AssertionError if the actual {@code List} does not contain the given objects.
190       */
191      public ListAssert containsExactly(Object... objects) {
192        validateIsNotNull(objects);
193        return isNotNull().isEqualTo(list(objects));
194      }
195    
196      /**
197       * Creates a new instance of <code>{@link ListAssert}</code> whose target list contains the values of the given
198       * property name from the elements of this {@code ListAssert}'s list. Property access works with both simple
199       * properties like {@code Person.age} and nested properties {@code Person.father.age}.
200       * </p>
201       * <p>
202       * For example, let's say we have a list of {@code Person} objects and you want to verify their age:
203       * <pre>
204       * assertThat(persons).onProperty("age").containsOnly(25, 16, 44, 37); // simple property
205       * assertThat(persons).onProperty("father.age").containsOnly(55, 46, 74, 62); // nested property
206       * </p>
207       * @param propertyName the name of the property to extract values from the actual list to build a new
208       * {@code ListAssert}.
209       * @return a new {@code ListAssert} containing the values of the given property name from the elements of this
210       * {@code ListAssert}'s list.
211       * @throws AssertionError if the actual list is {@code null}.
212       * @throws IntrospectionError if an element in the given list does not have a matching property.
213       * @since 1.3
214       */
215      @Override public ListAssert onProperty(String propertyName) {
216        isNotNull();
217        if (actual.isEmpty()) return new ListAssert(emptyList());
218        return new ListAssert(PropertySupport.instance().propertyValues(propertyName, actual));
219      }
220    
221      /** {@inheritDoc} */
222      @Override protected Set<Object> actualAsSet() {
223        return new LinkedHashSet<Object>(actual);
224      }
225    
226      /** {@inheritDoc} */
227      @Override protected List<Object> actualAsList() {
228        return new ArrayList<Object>(actual);
229      }
230    }