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 * https://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 > 0 */ 053 private final int start; 054 055 /** The ending position for the cursor in the list. It can be < 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 <E> 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 <E> 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 <E> 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 <E> 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}