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