001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *  http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.directory.shared.ldap.model.cursor;
020
021
022import java.io.IOException;
023import java.util.Collections;
024import java.util.Comparator;
025import java.util.List;
026
027import org.apache.directory.shared.i18n.I18n;
028
029
030/**
031 * A simple implementation of a Cursor on a {@link List}.  Optionally, the
032 * Cursor may be limited to a specific range within the list.
033 *
034 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
035 * @param <E> The element on which this cursor will iterate
036 */
037public class ListCursor<E> extends AbstractCursor<E>
038{
039    /** The inner List */
040    private final List<E> list;
041
042    /** The associated comparator */
043    private final Comparator<E> comparator;
044
045    /** The starting position for the cursor in the list. It can be > 0 */
046    private final int start;
047
048    /** The ending position for the cursor in the list. It can be < List.size() */
049    private final int end;
050
051    /** The current position in the list */
052    private int index = -1;
053
054
055    /**
056     * Creates a new ListCursor with lower (inclusive) and upper (exclusive)
057     * bounds.
058     *
059     * As with all Cursors, this ListCursor requires a successful return from
060     * advance operations (next() or previous()) to properly return values
061     * using the get() operation.
062     *
063     * @param comparator an optional comparator to use for ordering
064     * @param start the lower bound index
065     * @param list the list this ListCursor operates on
066     * @param end the upper bound index
067     */
068    public ListCursor( Comparator<E> comparator, int start, List<E> list, int end )
069    {
070        if ( list == null )
071        {
072            list = Collections.emptyList();
073        }
074
075        if ( ( start < 0 ) || ( start > list.size() ) )
076        {
077            throw new IllegalArgumentException( I18n.err( I18n.ERR_02005_START_INDEX_OUT_OF_RANGE, start ) );
078        }
079
080        if ( ( end < 0 ) || ( end > list.size() ) )
081        {
082            throw new IllegalArgumentException( I18n.err( I18n.ERR_02006_END_INDEX_OUT_OF_RANGE, end ) );
083        }
084
085        // check list is not empty list since the empty list is the only situation
086        // where we allow for start to equal the end: in other cases it makes no sense
087        if ( ( list.size() > 0 ) && ( start >= end ) )
088        {
089            throw new IllegalArgumentException( I18n.err( I18n.ERR_02007_START_INDEX_ABOVE_END_INDEX, start, end ) );
090        }
091
092        this.comparator = comparator;
093        this.list = list;
094        this.start = start;
095        this.end = end;
096    }
097
098
099    /**
100     * Creates a new ListCursor with lower (inclusive) and upper (exclusive)
101     * bounds.
102     *
103     * As with all Cursors, this ListCursor requires a successful return from
104     * advance operations (next() or previous()) to properly return values
105     * using the get() operation.
106     *
107     * @param start the lower bound index
108     * @param list the list this ListCursor operates on
109     * @param end the upper bound index
110     */
111    public ListCursor( int start, List<E> list, int end )
112    {
113        this( null, start, list, end );
114    }
115
116
117    /**
118     * Creates a new ListCursor with a specific upper (exclusive) bound: the
119     * lower (inclusive) bound defaults to 0.
120     *
121     * @param list the backing for this ListCursor
122     * @param end the upper bound index representing the position after the
123     * last element
124     */
125    public ListCursor( List<E> list, int end )
126    {
127        this( null, 0, list, end );
128    }
129
130
131    /**
132     * Creates a new ListCursor with a specific upper (exclusive) bound: the
133     * lower (inclusive) bound defaults to 0. We also provide a comparator.
134     *
135     * @param comparator The comparator to use for the <E> elements
136     * @param list the backing for this ListCursor
137     * @param end the upper bound index representing the position after the
138     * last element
139     */
140    public ListCursor( Comparator<E> comparator, List<E> list, int end )
141    {
142        this( comparator, 0, list, end );
143    }
144
145
146    /**
147     * Creates a new ListCursor with a lower (inclusive) bound: the upper
148     * (exclusive) bound is the size of the list.
149     *
150     * @param start the lower (inclusive) bound index: the position of the
151     * first entry
152     * @param list the backing for this ListCursor
153     */
154    public ListCursor( int start, List<E> list )
155    {
156        this( null, start, list, list.size() );
157    }
158
159
160    /**
161     * Creates a new ListCursor with a lower (inclusive) bound: the upper
162     * (exclusive) bound is the size of the list. We also provide a comparator.
163     *
164     * @param comparator The comparator to use for the <E> elements
165     * @param start the lower (inclusive) bound index: the position of the
166     * first entry
167     * @param list the backing for this ListCursor
168     */
169    public ListCursor( Comparator<E> comparator, int start, List<E> list )
170    {
171        this( comparator, start, list, list.size() );
172    }
173
174
175    /**
176     * Creates a new ListCursor without specific bounds: the bounds are
177     * acquired from the size of the list.
178     *
179     * @param list the backing for this ListCursor
180     */
181    public ListCursor( List<E> list )
182    {
183        this( null, 0, list, list.size() );
184    }
185
186
187    /**
188     * Creates a new ListCursor without specific bounds: the bounds are
189     * acquired from the size of the list. We also provide a comparator.
190     *
191     * @param comparator The comparator to use for the <E> elements
192     * @param list the backing for this ListCursor
193     */
194    public ListCursor( Comparator<E> comparator, List<E> list )
195    {
196        this( comparator, 0, list, list.size() );
197    }
198
199
200    /**
201     * Creates a new ListCursor without any elements.
202     */
203    @SuppressWarnings("unchecked")
204    public ListCursor()
205    {
206        this( null, 0, Collections.EMPTY_LIST, 0 );
207    }
208
209
210    /**
211     * Creates a new ListCursor without any elements. We also provide 
212     * a comparator.
213     * 
214     * @param comparator The comparator to use for the <E> elements
215     */
216    @SuppressWarnings("unchecked")
217    public ListCursor( Comparator<E> comparator )
218    {
219        this( comparator, 0, Collections.EMPTY_LIST, 0 );
220    }
221
222
223    /**
224     * {@inheritDoc}
225     */
226    public boolean available()
227    {
228        return index >= 0 && index < end;
229    }
230
231
232    /**
233     * {@inheritDoc}
234     */
235    public void before( E element ) throws Exception
236    {
237        checkNotClosed( "before()" );
238
239        if ( comparator == null )
240        {
241            throw new IllegalStateException();
242        }
243
244        // handle some special cases
245        if ( list.size() == 0 )
246        {
247            return;
248        }
249        else if ( list.size() == 1 )
250        {
251            if ( comparator.compare( element, list.get( 0 ) ) <= 0 )
252            {
253                beforeFirst();
254            }
255            else
256            {
257                afterLast();
258            }
259        }
260
261        throw new UnsupportedOperationException( I18n.err( I18n.ERR_02008_LIST_MAY_BE_SORTED ) );
262    }
263
264
265    /**
266     * {@inheritDoc}
267     */
268    public void after( E element ) throws Exception
269    {
270        checkNotClosed( "after()" );
271
272        if ( comparator == null )
273        {
274            throw new IllegalStateException();
275        }
276
277        // handle some special cases
278        if ( list.size() == 0 )
279        {
280            return;
281        }
282        else if ( list.size() == 1 )
283        {
284            if ( comparator.compare( element, list.get( 0 ) ) >= 0 )
285            {
286                afterLast();
287            }
288            else
289            {
290                beforeFirst();
291            }
292        }
293
294        throw new UnsupportedOperationException( I18n.err( I18n.ERR_02008_LIST_MAY_BE_SORTED ) );
295    }
296
297
298    /**
299     * {@inheritDoc}
300     */
301    public void beforeFirst() throws Exception
302    {
303        checkNotClosed( "beforeFirst()" );
304        this.index = -1;
305    }
306
307
308    /**
309     * {@inheritDoc}
310     */
311    public void afterLast() throws Exception
312    {
313        checkNotClosed( "afterLast()" );
314        this.index = end;
315    }
316
317
318    /**
319     * {@inheritDoc}
320     */
321    public boolean first() throws Exception
322    {
323        checkNotClosed( "first()" );
324
325        if ( list.size() > 0 )
326        {
327            index = start;
328
329            return true;
330        }
331
332        return false;
333    }
334
335
336    /**
337     * {@inheritDoc}
338     */
339    public boolean last() throws Exception
340    {
341        checkNotClosed( "last()" );
342
343        if ( list.size() > 0 )
344        {
345            index = end - 1;
346
347            return true;
348        }
349
350        return false;
351    }
352
353
354    /**
355     * {@inheritDoc}
356     */
357    @Override
358    public boolean isFirst() throws Exception
359    {
360        checkNotClosed( "isFirst()" );
361
362        return list.size() > 0 && index == start;
363    }
364
365
366    /**
367     * {@inheritDoc}
368     */
369    @Override
370    public boolean isLast() throws Exception
371    {
372        checkNotClosed( "isLast()" );
373
374        return list.size() > 0 && index == end - 1;
375    }
376
377
378    /**
379     * {@inheritDoc}
380     */
381    @Override
382    public boolean isAfterLast() throws Exception
383    {
384        checkNotClosed( "isAfterLast()" );
385        return index == end;
386    }
387
388
389    /**
390     * {@inheritDoc}
391     */
392    @Override
393    public boolean isBeforeFirst() throws Exception
394    {
395        checkNotClosed( "isBeforeFirst()" );
396        return index == -1;
397    }
398
399
400    /**
401     * {@inheritDoc}
402     */
403    public boolean previous() throws Exception
404    {
405        checkNotClosed( "previous()" );
406
407        // if parked at -1 we cannot go backwards
408        if ( index == -1 )
409        {
410            return false;
411        }
412
413        // if the index moved back is still greater than or eq to start then OK
414        if ( index - 1 >= start )
415        {
416            index--;
417
418            return true;
419        }
420
421        // if the index currently less than or equal to start we need to park it at -1 and return false
422        if ( index <= start )
423        {
424            index = -1;
425
426            return false;
427        }
428
429        if ( list.size() <= 0 )
430        {
431            index = -1;
432        }
433
434        return false;
435    }
436
437
438    /**
439     * {@inheritDoc}
440     */
441    public boolean next() throws Exception
442    {
443        checkNotClosed( "next()" );
444
445        // if parked at -1 we advance to the start index and return true
446        if ( ( list.size() > 0 ) && ( index == -1 ) )
447        {
448            index = start;
449
450            return true;
451        }
452
453        // if the index plus one is less than the end then increment and return true
454        if ( ( list.size() > 0 ) && ( index + 1 < end ) )
455        {
456            index++;
457
458            return true;
459        }
460
461        // if the index plus one is equal to the end then increment and return false
462        if ( ( list.size() > 0 ) && ( index + 1 == end ) )
463        {
464            index++;
465
466            return false;
467        }
468
469        if ( list.size() <= 0 )
470        {
471            index = end;
472        }
473
474        return false;
475    }
476
477
478    /**
479     * {@inheritDoc}
480     */
481    public E get() throws Exception
482    {
483        checkNotClosed( "get()" );
484
485        if ( ( index < start ) || ( index >= end ) )
486        {
487            throw new IOException( I18n.err( I18n.ERR_02009_CURSOR_NOT_POSITIONED ) );
488        }
489
490        return list.get( index );
491    }
492}