View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *  https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.directory.api.ldap.model.cursor;
20  
21  
22  import java.io.IOException;
23  import java.util.Collections;
24  import java.util.Comparator;
25  import java.util.List;
26  
27  import org.apache.directory.api.i18n.I18n;
28  import org.apache.directory.api.ldap.model.constants.Loggers;
29  import org.apache.directory.api.ldap.model.exception.LdapException;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  
34  /**
35   * A simple implementation of a Cursor on a {@link List}.  Optionally, the
36   * Cursor may be limited to a specific range within the list.
37   *
38   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
39   * @param <E> The element on which this cursor will iterate
40   */
41  public class ListCursor<E> extends AbstractCursor<E>
42  {
43      /** A dedicated log for cursors */
44      private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() );
45  
46      /** The inner List */
47      private final List<E> list;
48  
49      /** The associated comparator */
50      private final Comparator<E> comparator;
51  
52      /** The starting position for the cursor in the list. It can be &gt; 0 */
53      private final int start;
54  
55      /** The ending position for the cursor in the list. It can be &lt; List.size() */
56      private final int end;
57      /** The current position in the list */
58  
59      private int index = -1;
60  
61  
62      /**
63       * Creates a new ListCursor with lower (inclusive) and upper (exclusive)
64       * bounds.
65       *
66       * As with all Cursors, this ListCursor requires a successful return from
67       * advance operations (next() or previous()) to properly return values
68       * using the get() operation.
69       *
70       * @param comparator an optional comparator to use for ordering
71       * @param start the lower bound index
72       * @param list the list this ListCursor operates on
73       * @param end the upper bound index
74       */
75      public ListCursor( Comparator<E> comparator, int start, List<E> list, int end )
76      {
77          if ( list == null )
78          {
79              list = Collections.emptyList();
80          }
81  
82          if ( ( start < 0 ) || ( start > list.size() ) )
83          {
84              throw new IllegalArgumentException( I18n.err( I18n.ERR_13105_START_INDEX_OUT_OF_RANGE, start ) );
85          }
86  
87          if ( ( end < 0 ) || ( end > list.size() ) )
88          {
89              throw new IllegalArgumentException( I18n.err( I18n.ERR_13106_END_INDEX_OUT_OF_RANGE, end ) );
90          }
91  
92          // check list is not empty list since the empty list is the only situation
93          // where we allow for start to equal the end: in other cases it makes no sense
94          if ( !list.isEmpty() && ( start >= end ) )
95          {
96              throw new IllegalArgumentException( I18n.err( I18n.ERR_13107_START_INDEX_ABOVE_END_INDEX, start, end ) );
97          }
98  
99          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 }