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