/*
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.
*
*
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.
*
*
Features:
*
* - Observable Pattern: Fires {@link BagChanged} events on modifications
* - In-Memory Storage: Backed by {@link ArrayList} by default
* - Extensible: Can be subclassed for lazy-loading or database-backed collections
* - Standard Collection: Implements full {@link Collection} interface
*
*
* Event Types:
*
* - {@link BagChanged#ADD} - element(s) added
* - {@link BagChanged#REMOVE} - element(s) removed
* - {@link BagChanged#ACCESS} - element accessed (for lazy loading)
* - {@link BagChanged#POST_LOAD} - after loading from external source
* - {@link BagChanged#PRE_SAVE} - before saving to external source
*
*
* Usage Example:
* {@code
* Bag 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
* }
*
* Fluent API:
* The {@link #append(Object)} method provides a chainable alternative to {@link #add(Object)}:
*
{@code
* Bag names = new Bag<>()
* .append("Alice")
* .append("Bob")
* .append("Charlie");
* }
*
* Construction:
* {@code
* // Empty bag
* Bag bag1 = new Bag<>();
*
* // From iterable
* Bag bag2 = new Bag<>(someList);
*
* // From iterator
* Bag bag3 = new Bag<>(resultIterator);
* }
*
* Extension for Virtual Collections:
* Subclasses can override methods to implement lazy loading or database-backed storage:
*
{@code
* public class LazyBag extends Bag {
* @Override
* public Iterator iterator() {
* notifyObservers(new BagChanged<>(this, BagChanged.ACCESS));
* // Load from database on first access
* return super.iterator();
* }
* }
* }
*
* @param the type of elements in this bag
* @see Observable
* @see Collection
* @see BagChanged
*/
public class Bag extends Observable implements Collection{
/** event to send to observers. */
public static final class BagChanged{
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 bag;
final int operation;
final Object[] arguments;
public BagChanged(Bag p,int op,Object ... args){
bag=p;
operation=op;
arguments=args;
}
public Bag getBag() {
return bag;
}
public int getOperation() {
return operation;
}
public Object[] getArguments() {
return arguments;
}
}
final ArrayList items=new ArrayList<>();
public Bag(){
}
public Bag(Iterable o){
this(o.iterator());
}
public Bag(Iterator 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 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 listIterator(){
return listIterator(0);
}
public ListIterator listIterator(int offset){
return items.listIterator(offset);
}
@Override
public Iterator iterator() {
return items.iterator();
}
@Override
public Object[] toArray() {
return toArray(new Object[size()]);
}
@Override
public T[] toArray(T[] a) {
return items.toArray(a);
}
@Override
public boolean add(E e) {
if(items.contains(e)) return true;
if(countObservers()>0){
BagChanged evt=new Bag.BagChanged<>(this,BagChanged.ADD,e);
setChanged();
notifyObservers(evt);
}
return items.add(e);
}
public Bag append(E e){
add(e);
return this;
}
@Override
public boolean remove(Object o) {
if(!contains(o)) return false;
if(countObservers()>0){
BagChanged 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 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 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);
}
}