001package com.avaje.ebean.common; 002 003import com.avaje.ebean.bean.BeanCollectionAdd; 004import com.avaje.ebean.bean.BeanCollectionLoader; 005import com.avaje.ebean.bean.EntityBean; 006 007import java.io.Serializable; 008import java.util.ArrayList; 009import java.util.Collection; 010import java.util.Collections; 011import java.util.Iterator; 012import java.util.List; 013import java.util.ListIterator; 014 015/** 016 * List capable of lazy loading. 017 */ 018public final class BeanList<E> extends AbstractBeanCollection<E> implements List<E>, BeanCollectionAdd { 019 020 private static final long serialVersionUID = 1L; 021 022 /** 023 * The underlying List implementation. 024 */ 025 private List<E> list; 026 027 /** 028 * Specify the underlying List implementation. 029 */ 030 public BeanList(List<E> list) { 031 super(); 032 this.list = list; 033 } 034 035 /** 036 * Uses an ArrayList as the underlying List implementation. 037 */ 038 public BeanList() { 039 this(new ArrayList<E>()); 040 } 041 042 /** 043 * Used to create deferred fetch proxy. 044 */ 045 public BeanList(BeanCollectionLoader loader, EntityBean ownerBean, String propertyName) { 046 super(loader, ownerBean, propertyName); 047 } 048 049 @Override 050 public void reset(EntityBean ownerBean, String propertyName) { 051 this.ownerBean = ownerBean; 052 this.propertyName = propertyName; 053 this.list = null; 054 this.touched = false; 055 } 056 057 @Override 058 public boolean isEmptyAndUntouched() { 059 return !touched && (list == null || list.isEmpty()); 060 } 061 062 @SuppressWarnings("unchecked") 063 public void addEntityBean(EntityBean bean) { 064 list.add((E) bean); 065 } 066 067 @SuppressWarnings("unchecked") 068 public void internalAdd(Object bean) { 069 if (list == null) { 070 list = new ArrayList<E>(); 071 } 072 if (bean != null) { 073 list.add((E) bean); 074 } 075 } 076 077 @Override 078 public void internalAddWithCheck(Object bean) { 079 if (list == null || !list.contains(bean)) { 080 internalAdd(bean); 081 } 082 } 083 084 public boolean checkEmptyLazyLoad() { 085 if (list == null) { 086 list = new ArrayList<E>(); 087 return true; 088 } else { 089 return false; 090 } 091 } 092 093 private void initClear() { 094 synchronized (this) { 095 if (list == null) { 096 if (modifyListening) { 097 lazyLoadCollection(true); 098 } else { 099 list = new ArrayList<E>(); 100 } 101 } 102 touched(true); 103 } 104 } 105 106 private void initAsUntouched() { 107 init(false); 108 } 109 110 private void init() { 111 init(true); 112 } 113 114 private void init(boolean setTouched) { 115 synchronized (this) { 116 if (list == null) { 117 lazyLoadCollection(false); 118 } 119 touched(setTouched); 120 } 121 } 122 123 /** 124 * Set the actual underlying list. 125 * <p> 126 * This is primarily for the deferred fetching function. 127 * </p> 128 */ 129 @SuppressWarnings("unchecked") 130 public void setActualList(List<?> list) { 131 this.list = (List<E>) list; 132 } 133 134 /** 135 * Return the actual underlying list. 136 */ 137 public List<E> getActualList() { 138 return list; 139 } 140 141 public Collection<E> getActualDetails() { 142 return list; 143 } 144 145 @Override 146 public Collection<?> getActualEntries() { 147 return list; 148 } 149 150 /** 151 * Return true if the underlying list is populated. 152 */ 153 public boolean isPopulated() { 154 return list != null; 155 } 156 157 /** 158 * Return true if this is a reference (lazy loading) bean collection. This is 159 * the same as !isPopulated(); 160 */ 161 public boolean isReference() { 162 return list == null; 163 } 164 165 public String toString() { 166 StringBuilder sb = new StringBuilder(50); 167 sb.append("BeanList "); 168 if (isReadOnly()) { 169 sb.append("readOnly "); 170 } 171 if (list == null) { 172 sb.append("deferred "); 173 174 } else { 175 sb.append("size[").append(list.size()).append("] "); 176 sb.append("list").append(list).append(""); 177 } 178 return sb.toString(); 179 } 180 181 /** 182 * Equal if obj is a List and equal in a list sense. 183 * <p> 184 * Specifically obj does not need to be a BeanList but any list. This does not 185 * use the FindMany, fetchedMaxRows or finishedFetch properties in the equals 186 * test. 187 * </p> 188 */ 189 public boolean equals(Object obj) { 190 init(); 191 return list.equals(obj); 192 } 193 194 public int hashCode() { 195 init(); 196 return list.hashCode(); 197 } 198 199 // -----------------------------------------------------// 200 // The additional methods are here 201 // -----------------------------------------------------// 202 203 // -----------------------------------------------------// 204 // proxy method for List 205 // -----------------------------------------------------// 206 207 public void add(int index, E element) { 208 checkReadOnly(); 209 init(); 210 if (modifyAddListening) { 211 modifyAddition(element); 212 } 213 list.add(index, element); 214 } 215 216 @Override 217 public void addBean(E bean) { 218 add(bean); 219 } 220 221 public boolean add(E o) { 222 checkReadOnly(); 223 init(); 224 if (modifyAddListening) { 225 if (list.add(o)) { 226 modifyAddition(o); 227 return true; 228 } else { 229 return false; 230 } 231 } 232 return list.add(o); 233 } 234 235 public boolean addAll(Collection<? extends E> c) { 236 checkReadOnly(); 237 init(); 238 if (modifyAddListening) { 239 // all elements in c are added (no contains checking) 240 getModifyHolder().modifyAdditionAll(c); 241 } 242 return list.addAll(c); 243 } 244 245 public boolean addAll(int index, Collection<? extends E> c) { 246 checkReadOnly(); 247 init(); 248 if (modifyAddListening) { 249 // all elements in c are added (no contains checking) 250 getModifyHolder().modifyAdditionAll(c); 251 } 252 return list.addAll(index, c); 253 } 254 255 public void clear() { 256 checkReadOnly(); 257 // TODO: when clear() and not initialised could be more clever 258 // and fetch just the Id's 259 initClear(); 260 if (modifyRemoveListening) { 261 for (int i = 0; i < list.size(); i++) { 262 getModifyHolder().modifyRemoval(list.get(i)); 263 } 264 } 265 list.clear(); 266 } 267 268 public boolean contains(Object o) { 269 init(); 270 return list.contains(o); 271 } 272 273 public boolean containsAll(Collection<?> c) { 274 init(); 275 return list.containsAll(c); 276 } 277 278 public E get(int index) { 279 init(); 280 return list.get(index); 281 } 282 283 public int indexOf(Object o) { 284 init(); 285 return list.indexOf(o); 286 } 287 288 public boolean isEmpty() { 289 initAsUntouched(); 290 return list.isEmpty(); 291 } 292 293 public Iterator<E> iterator() { 294 init(); 295 if (isReadOnly()) { 296 return new ReadOnlyListIterator<E>(list.listIterator()); 297 } 298 if (modifyListening) { 299 Iterator<E> it = list.iterator(); 300 return new ModifyIterator<E>(this, it); 301 } 302 return list.iterator(); 303 } 304 305 public int lastIndexOf(Object o) { 306 init(); 307 return list.lastIndexOf(o); 308 } 309 310 public ListIterator<E> listIterator() { 311 init(); 312 if (isReadOnly()) { 313 return new ReadOnlyListIterator<E>(list.listIterator()); 314 } 315 if (modifyListening) { 316 ListIterator<E> it = list.listIterator(); 317 return new ModifyListIterator<E>(this, it); 318 } 319 return list.listIterator(); 320 } 321 322 public ListIterator<E> listIterator(int index) { 323 init(); 324 if (isReadOnly()) { 325 return new ReadOnlyListIterator<E>(list.listIterator(index)); 326 } 327 if (modifyListening) { 328 ListIterator<E> it = list.listIterator(index); 329 return new ModifyListIterator<E>(this, it); 330 } 331 return list.listIterator(index); 332 } 333 334 @Override 335 public void removeBean(E bean) { 336 if (list.remove(bean)) { 337 getModifyHolder().modifyRemoval(bean); 338 } 339 } 340 341 public E remove(int index) { 342 checkReadOnly(); 343 init(); 344 if (modifyRemoveListening) { 345 E o = list.remove(index); 346 modifyRemoval(o); 347 return o; 348 } 349 return list.remove(index); 350 } 351 352 public boolean remove(Object o) { 353 checkReadOnly(); 354 init(); 355 if (modifyRemoveListening) { 356 boolean isRemove = list.remove(o); 357 if (isRemove) { 358 modifyRemoval(o); 359 } 360 return isRemove; 361 } 362 return list.remove(o); 363 } 364 365 public boolean removeAll(Collection<?> beans) { 366 checkReadOnly(); 367 init(); 368 if (modifyRemoveListening) { 369 boolean changed = false; 370 for (Object bean : beans) { 371 if (list.remove(bean)) { 372 // register this bean as having been removed 373 modifyRemoval(bean); 374 changed = true; 375 } 376 } 377 return changed; 378 } 379 return list.removeAll(beans); 380 } 381 382 public boolean retainAll(Collection<?> retainBeans) { 383 checkReadOnly(); 384 init(); 385 if (modifyRemoveListening) { 386 boolean changed = false; 387 Iterator<E> it = list.iterator(); 388 while (it.hasNext()) { 389 Object bean = it.next(); 390 if (!retainBeans.contains(bean)) { 391 // removing this bean 392 it.remove(); 393 modifyRemoval(bean); 394 changed = true; 395 } 396 } 397 return changed; 398 } 399 return list.retainAll(retainBeans); 400 } 401 402 public E set(int index, E element) { 403 checkReadOnly(); 404 init(); 405 if (modifyListening) { 406 E o = list.set(index, element); 407 modifyAddition(element); 408 modifyRemoval(o); 409 return o; 410 } 411 return list.set(index, element); 412 } 413 414 public int size() { 415 init(); 416 return list.size(); 417 } 418 419 public List<E> subList(int fromIndex, int toIndex) { 420 init(); 421 if (isReadOnly()) { 422 return Collections.unmodifiableList(list.subList(fromIndex, toIndex)); 423 } 424 if (modifyListening) { 425 return new ModifyList<E>(this, list.subList(fromIndex, toIndex)); 426 } 427 return list.subList(fromIndex, toIndex); 428 } 429 430 public Object[] toArray() { 431 init(); 432 return list.toArray(); 433 } 434 435 public <T> T[] toArray(T[] a) { 436 init(); 437 //noinspection SuspiciousToArrayCall 438 return list.toArray(a); 439 } 440 441 private static class ReadOnlyListIterator<E> implements ListIterator<E>, Serializable { 442 443 private static final long serialVersionUID = 3097271091406323699L; 444 445 private final ListIterator<E> i; 446 447 ReadOnlyListIterator(ListIterator<E> i) { 448 this.i = i; 449 } 450 451 public void add(E o) { 452 throw new IllegalStateException("This collection is in ReadOnly mode"); 453 } 454 455 public void remove() { 456 throw new IllegalStateException("This collection is in ReadOnly mode"); 457 } 458 459 public void set(E o) { 460 throw new IllegalStateException("This collection is in ReadOnly mode"); 461 } 462 463 public boolean hasNext() { 464 return i.hasNext(); 465 } 466 467 public boolean hasPrevious() { 468 return i.hasPrevious(); 469 } 470 471 public E next() { 472 return i.next(); 473 } 474 475 public int nextIndex() { 476 return i.nextIndex(); 477 } 478 479 public E previous() { 480 return i.previous(); 481 } 482 483 public int previousIndex() { 484 return i.previousIndex(); 485 } 486 487 } 488}