248 lines
6.9 KiB
Java
248 lines
6.9 KiB
Java
/*
|
|
Copyright (c) 2011-2022 Reliancy LLC
|
|
|
|
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
|
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
|
You may not use this file except in compliance with the License.
|
|
*/
|
|
|
|
package com.reliancy.dbo;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Iterator;
|
|
import java.util.ListIterator;
|
|
import java.util.Observable;
|
|
|
|
/**
|
|
* Observable collection wrapper for monitoring data modifications.
|
|
*
|
|
* <p>A {@code Bag} is a {@link Collection} implementation that extends {@link Observable}
|
|
* to notify observers of add/remove operations. It's designed as a holder for result sets
|
|
* and provides a foundation for creating virtual collections backed by external data sources.
|
|
*
|
|
* <h2>Features:</h2>
|
|
* <ul>
|
|
* <li><b>Observable Pattern</b>: Fires {@link BagChanged} events on modifications</li>
|
|
* <li><b>In-Memory Storage</b>: Backed by {@link ArrayList} by default</li>
|
|
* <li><b>Extensible</b>: Can be subclassed for lazy-loading or database-backed collections</li>
|
|
* <li><b>Standard Collection</b>: Implements full {@link Collection} interface</li>
|
|
* </ul>
|
|
*
|
|
* <h2>Event Types:</h2>
|
|
* <ul>
|
|
* <li>{@link BagChanged#ADD} - element(s) added</li>
|
|
* <li>{@link BagChanged#REMOVE} - element(s) removed</li>
|
|
* <li>{@link BagChanged#ACCESS} - element accessed (for lazy loading)</li>
|
|
* <li>{@link BagChanged#POST_LOAD} - after loading from external source</li>
|
|
* <li>{@link BagChanged#PRE_SAVE} - before saving to external source</li>
|
|
* </ul>
|
|
*
|
|
* <h2>Usage Example:</h2>
|
|
* <pre>{@code
|
|
* Bag<PersonDBO> people = new Bag<>();
|
|
*
|
|
* // Add observer
|
|
* people.addObserver((observable, event) -> {
|
|
* BagChanged<?> change = (BagChanged<?>) event;
|
|
* switch (change.getOperation()) {
|
|
* case BagChanged.ADD:
|
|
* System.out.println("Added: " + Arrays.toString(change.getArguments()));
|
|
* break;
|
|
* case BagChanged.REMOVE:
|
|
* System.out.println("Removed: " + Arrays.toString(change.getArguments()));
|
|
* break;
|
|
* }
|
|
* });
|
|
*
|
|
* // Modifications trigger events
|
|
* people.add(person1); // Observer notified
|
|
* people.remove(person1); // Observer notified
|
|
* }</pre>
|
|
*
|
|
* <h2>Fluent API:</h2>
|
|
* <p>The {@link #append(Object)} method provides a chainable alternative to {@link #add(Object)}:
|
|
* <pre>{@code
|
|
* Bag<String> names = new Bag<>()
|
|
* .append("Alice")
|
|
* .append("Bob")
|
|
* .append("Charlie");
|
|
* }</pre>
|
|
*
|
|
* <h2>Construction:</h2>
|
|
* <pre>{@code
|
|
* // Empty bag
|
|
* Bag<DBO> bag1 = new Bag<>();
|
|
*
|
|
* // From iterable
|
|
* Bag<DBO> bag2 = new Bag<>(someList);
|
|
*
|
|
* // From iterator
|
|
* Bag<DBO> bag3 = new Bag<>(resultIterator);
|
|
* }</pre>
|
|
*
|
|
* <h2>Extension for Virtual Collections:</h2>
|
|
* <p>Subclasses can override methods to implement lazy loading or database-backed storage:
|
|
* <pre>{@code
|
|
* public class LazyBag<E> extends Bag<E> {
|
|
* @Override
|
|
* public Iterator<E> iterator() {
|
|
* notifyObservers(new BagChanged<>(this, BagChanged.ACCESS));
|
|
* // Load from database on first access
|
|
* return super.iterator();
|
|
* }
|
|
* }
|
|
* }</pre>
|
|
*
|
|
* @param <E> the type of elements in this bag
|
|
* @see Observable
|
|
* @see Collection
|
|
* @see BagChanged
|
|
*/
|
|
public class Bag<E> extends Observable implements Collection<E>{
|
|
/** event to send to observers. */
|
|
public static final class BagChanged<E>{
|
|
public static final int ADD=0;
|
|
public static final int REMOVE=1;
|
|
public static final int ACCESS=2;
|
|
public static final int POST_LOAD=3;
|
|
public static final int PRE_SAVE=4;
|
|
final Bag<E> bag;
|
|
final int operation;
|
|
final Object[] arguments;
|
|
|
|
public BagChanged(Bag<E> p,int op,Object ... args){
|
|
bag=p;
|
|
operation=op;
|
|
arguments=args;
|
|
}
|
|
public Bag<E> getBag() {
|
|
return bag;
|
|
}
|
|
public int getOperation() {
|
|
return operation;
|
|
}
|
|
public Object[] getArguments() {
|
|
return arguments;
|
|
}
|
|
}
|
|
final ArrayList<E> items=new ArrayList<>();
|
|
|
|
public Bag(){
|
|
}
|
|
public Bag(Iterable<E> o){
|
|
this(o.iterator());
|
|
}
|
|
public Bag(Iterator<E> o){
|
|
while(o.hasNext()) add(o.next());
|
|
}
|
|
@Override
|
|
public int size() {
|
|
return items.size();
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return size()==0;
|
|
}
|
|
|
|
@Override
|
|
public boolean contains(Object o) {
|
|
final Iterator<E> it=iterator();
|
|
while(it.hasNext()){
|
|
final E e=it.next();
|
|
if(e!=null && o!=null && e.equals(o)) return true;
|
|
else if(e==o) return true;
|
|
}
|
|
return false;
|
|
}
|
|
@Override
|
|
public boolean containsAll(Collection<?> c) {
|
|
for (Object e : c) if (!contains(e)) return false;
|
|
return true;
|
|
}
|
|
public ListIterator<E> listIterator(){
|
|
return listIterator(0);
|
|
}
|
|
public ListIterator<E> listIterator(int offset){
|
|
return items.listIterator(offset);
|
|
}
|
|
@Override
|
|
public Iterator<E> iterator() {
|
|
return items.iterator();
|
|
}
|
|
|
|
@Override
|
|
public Object[] toArray() {
|
|
return toArray(new Object[size()]);
|
|
}
|
|
|
|
@Override
|
|
public <T> T[] toArray(T[] a) {
|
|
return items.toArray(a);
|
|
}
|
|
|
|
@Override
|
|
public boolean add(E e) {
|
|
if(items.contains(e)) return true;
|
|
if(countObservers()>0){
|
|
BagChanged<E> evt=new Bag.BagChanged<>(this,BagChanged.ADD,e);
|
|
setChanged();
|
|
notifyObservers(evt);
|
|
}
|
|
return items.add(e);
|
|
}
|
|
public Bag<E> append(E e){
|
|
add(e);
|
|
return this;
|
|
}
|
|
@Override
|
|
public boolean remove(Object o) {
|
|
if(!contains(o)) return false;
|
|
if(countObservers()>0){
|
|
BagChanged<E> evt=new Bag.BagChanged<>(this,BagChanged.REMOVE,o);
|
|
setChanged();
|
|
notifyObservers(evt);
|
|
}
|
|
return items.remove(o);
|
|
}
|
|
|
|
@Override
|
|
public boolean addAll(Collection<? extends E> c) {
|
|
if(countObservers()>0){
|
|
BagChanged<E> evt=new Bag.BagChanged<>(this,BagChanged.ADD,c.toArray());
|
|
setChanged();
|
|
notifyObservers(evt);
|
|
}
|
|
if(c==null || c.size()==0) return false;
|
|
c.forEach(e->{this.append(e);});
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean removeAll(Collection<?> c) {
|
|
if(countObservers()>0){
|
|
BagChanged<E> evt=new Bag.BagChanged<>(this,BagChanged.REMOVE,c!=null?c.toArray():null);
|
|
setChanged();
|
|
notifyObservers(evt);
|
|
}
|
|
if(c!=null){
|
|
return items.removeAll(c);
|
|
}else{
|
|
items.clear();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean retainAll(Collection<?> c) {
|
|
return items.retainAll(c);
|
|
}
|
|
|
|
@Override
|
|
public void clear() {
|
|
removeAll(null);
|
|
}
|
|
|
|
}
|