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