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 <E> 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 <E> 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 <E> 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 <E> 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}