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   *  http://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      /** Speedup for logs */
47      private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled();
48  
49      /** The inner List */
50      private final List<E> list;
51  
52      /** The associated comparator */
53      private final Comparator<E> comparator;
54  
55      /** The starting position for the cursor in the list. It can be > 0 */
56      private final int start;
57  
58      /** The ending position for the cursor in the list. It can be < List.size() */
59      private final int end;
60      /** The current position in the list */
61  
62      private int index = -1;
63  
64  
65      /**
66       * Creates a new ListCursor with lower (inclusive) and upper (exclusive)
67       * bounds.
68       *
69       * As with all Cursors, this ListCursor requires a successful return from
70       * advance operations (next() or previous()) to properly return values
71       * using the get() operation.
72       *
73       * @param comparator an optional comparator to use for ordering
74       * @param start the lower bound index
75       * @param list the list this ListCursor operates on
76       * @param end the upper bound index
77       */
78      public ListCursor( Comparator<E> comparator, int start, List<E> list, int end )
79      {
80          if ( list == null )
81          {
82              list = Collections.emptyList();
83          }
84  
85          if ( ( start < 0 ) || ( start > list.size() ) )
86          {
87              throw new IllegalArgumentException( I18n.err( I18n.ERR_02005_START_INDEX_OUT_OF_RANGE, start ) );
88          }
89  
90          if ( ( end < 0 ) || ( end > list.size() ) )
91          {
92              throw new IllegalArgumentException( I18n.err( I18n.ERR_02006_END_INDEX_OUT_OF_RANGE, end ) );
93          }
94  
95          // check list is not empty list since the empty list is the only situation
96          // where we allow for start to equal the end: in other cases it makes no sense
97          if ( !list.isEmpty() && ( start >= end ) )
98          {
99              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 }