001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.common.collection;
017
018import java.util.Collection;
019import java.util.Collections;
020import java.util.Iterator;
021import java.util.List;
022import java.util.ListIterator;
023import java.util.NoSuchElementException;
024import org.modeshape.common.annotation.Immutable;
025import org.modeshape.common.util.CheckArg;
026
027/**
028 * An immutable {@link List} that consists of a single element appended to another existing {@link List}. The result is a list
029 * that contains all of the elements in the parent list as well as the last appended element, but while reusing the existing
030 * parent list and without having to create a copy of the parent list.
031 * 
032 * @param <T> the type of element
033 */
034@Immutable
035public class ImmutableAppendedList<T> implements List<T> {
036
037    private final List<T> parent;
038    private final T element;
039    private final int size;
040    private transient int hc;
041
042    /**
043     * Create an instance using the supplied parent list and an element to be virtually appended to the parent. Note that the
044     * parent must be immutable (though this is not checked).
045     * 
046     * @param parent the parent list
047     * @param element the child element (may be null)
048     * @throws IllegalArgumentException if the reference to the parent list is null
049     */
050    public ImmutableAppendedList( List<T> parent,
051                                  T element ) {
052        CheckArg.isNotNull(parent, "parent");
053        this.parent = parent;
054        this.element = element;
055        this.size = parent.size() + 1;
056    }
057
058    @Override
059    public boolean contains( Object o ) {
060        return element == o || (element != null && element.equals(o)) || parent.contains(o);
061    }
062
063    @Override
064    public boolean containsAll( Collection<?> c ) {
065        Iterator<?> e = c.iterator();
066        while (e.hasNext()) {
067            if (!contains(e.next())) return false;
068        }
069        return true;
070    }
071
072    @Override
073    public T get( int index ) {
074        if (index == (size - 1)) return element;
075        return parent.get(index);
076    }
077
078    @Override
079    public int indexOf( Object o ) {
080        int index = parent.indexOf(o);
081        if (index == -1) {
082            return (element == o || (element != null && element.equals(o))) ? (size - 1) : -1;
083        }
084        return -1;
085    }
086
087    @Override
088    public boolean isEmpty() {
089        return false;
090    }
091
092    @Override
093    @SuppressWarnings( "synthetic-access" )
094    public Iterator<T> iterator() {
095        final Iterator<T> parentIterator = parent.iterator();
096        return new Iterator<T>() {
097            boolean finished = false;
098
099            @Override
100            public boolean hasNext() {
101                return parentIterator.hasNext() || !finished;
102            }
103
104            @Override
105            public T next() {
106                if (parentIterator.hasNext()) return parentIterator.next();
107                if (finished) throw new NoSuchElementException();
108                finished = true;
109                return element;
110            }
111
112            @Override
113            public void remove() {
114                throw new UnsupportedOperationException();
115            }
116        };
117    }
118
119    @Override
120    public int lastIndexOf( Object o ) {
121        if (element == o || (element != null && element.equals(o))) return size - 1;
122        return parent.lastIndexOf(o);
123    }
124
125    @Override
126    public ListIterator<T> listIterator() {
127        return listIterator(0);
128    }
129
130    @Override
131    @SuppressWarnings( "synthetic-access" )
132    public ListIterator<T> listIterator( final int index ) {
133        return new ListIterator<T>() {
134            int cursor = index;
135
136            @Override
137            public boolean hasNext() {
138                return cursor < size;
139            }
140
141            @Override
142            public T next() {
143                try {
144                    T next = get(cursor);
145                    cursor++;
146                    return next;
147                } catch (IndexOutOfBoundsException e) {
148                    throw new NoSuchElementException();
149                }
150            }
151
152            @Override
153            public boolean hasPrevious() {
154                return cursor != 0;
155            }
156
157            @Override
158            public int nextIndex() {
159                return cursor;
160            }
161
162            @Override
163            public T previous() {
164                try {
165                    int i = cursor - 1;
166                    T previous = get(i);
167                    cursor = i;
168                    return previous;
169                } catch (IndexOutOfBoundsException e) {
170                    throw new NoSuchElementException();
171                }
172            }
173
174            @Override
175            public int previousIndex() {
176                return cursor - 1;
177            }
178
179            @Override
180            public void set( T o ) {
181                throw new UnsupportedOperationException();
182            }
183
184            @Override
185            public void remove() {
186                throw new UnsupportedOperationException();
187            }
188
189            @Override
190            public void add( T o ) {
191                throw new UnsupportedOperationException();
192            }
193
194        };
195    }
196
197    @Override
198    public int size() {
199        return size;
200    }
201
202    @Override
203    public List<T> subList( int fromIndex,
204                            int toIndex ) {
205        if (fromIndex == 0 && toIndex == size) {
206            // The bounds are the same as this list, so just return this list ...
207            return this;
208        }
209        if (toIndex == size && fromIndex == (size - 1)) {
210            // The only list is the last element ...
211            return Collections.singletonList(element);
212        }
213        if (toIndex < size) {
214            // It is all within the range of the parent's list, so simply delegate
215            return parent.subList(fromIndex, toIndex);
216        }
217        // Otherwise, the sublist starts within the parent list and ends with the last element.
218        // So, create a sublist starting at the 'fromIndex' until the end of the parent list ...
219        List<T> sublist = parent.subList(fromIndex, toIndex - 1); // will catch out-of-bounds errors
220        // And wrap with another immutable appended list to add the last element ...
221        return new ImmutableAppendedList<T>(sublist, element);
222    }
223
224    @Override
225    public Object[] toArray() {
226        Object[] result = new Object[size];
227        int i = 0;
228        for (T e : parent) {
229            result[i++] = e;
230        }
231        result[i] = element;
232        return result;
233    }
234
235    @Override
236    @SuppressWarnings( "unchecked" )
237    public <X> X[] toArray( X[] a ) {
238        if (a.length < size) a = (X[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
239        a = parent.toArray(a);
240        a[size - 1] = (X)element;
241        return a;
242    }
243
244    @Override
245    public int hashCode() {
246        if (hc == 0) {
247            int hashCode = 1;
248            for (T element : this) {
249                hashCode = 31 * hashCode + (element == null ? 0 : element.hashCode());
250            }
251            hc = hashCode;
252        }
253        return hc;
254    }
255
256    @Override
257    public boolean equals( Object obj ) {
258        if (obj == this) return true;
259        if (obj instanceof List<?>) {
260            List<?> that = (List<?>)obj;
261            if (this.size() != that.size()) return false;
262            Iterator<?> thisIter = this.iterator();
263            Iterator<?> thatIter = that.iterator();
264            while (thisIter.hasNext()) {
265                Object thisValue = thisIter.next();
266                Object thatValue = thatIter.next();
267                if (thisValue == null) {
268                    if (thatValue != null) return false;
269                    // assert thatValue == null;
270                } else {
271                    if (!thisValue.equals(thatValue)) return false;
272                }
273            }
274            return true;
275        }
276        return super.equals(obj);
277    }
278
279    @Override
280    public String toString() {
281        StringBuilder sb = new StringBuilder();
282        sb.append("[");
283
284        Iterator<T> i = iterator();
285        boolean hasNext = i.hasNext();
286        while (hasNext) {
287            T o = i.next();
288            sb.append(o == this ? "(this Collection)" : String.valueOf(o));
289            hasNext = i.hasNext();
290            if (hasNext) sb.append(", ");
291        }
292
293        sb.append("]");
294        return sb.toString();
295    }
296
297    // ----------------------------------------------------------------------------------------------------------------
298    // Methods that modify are not supported
299    // ----------------------------------------------------------------------------------------------------------------
300
301    @Override
302    public void add( int index,
303                     T element ) {
304        throw new UnsupportedOperationException();
305    }
306
307    @Override
308    public boolean add( T o ) {
309        throw new UnsupportedOperationException();
310    }
311
312    @Override
313    public boolean addAll( Collection<? extends T> c ) {
314        throw new UnsupportedOperationException();
315    }
316
317    @Override
318    public boolean addAll( int index,
319                           Collection<? extends T> c ) {
320        throw new UnsupportedOperationException();
321    }
322
323    @Override
324    public void clear() {
325        throw new UnsupportedOperationException();
326    }
327
328    @Override
329    public boolean remove( Object o ) {
330        throw new UnsupportedOperationException();
331    }
332
333    @Override
334    public T remove( int index ) {
335        throw new UnsupportedOperationException();
336    }
337
338    @Override
339    public boolean removeAll( Collection<?> c ) {
340        throw new UnsupportedOperationException();
341    }
342
343    @Override
344    public boolean retainAll( Collection<?> c ) {
345        throw new UnsupportedOperationException();
346    }
347
348    @Override
349    public T set( int index,
350                  T element ) {
351        throw new UnsupportedOperationException();
352    }
353
354}