package net.sourceforge.pain.db;
import java.util.*;
/**
* This class implements the <tt>Set</tt> interface. Only not null String values supported.<br>
* PAiN Db will not support more general serializable objects Set as it brokes database design<br>
* but Strings(Immutable) Sets are too convinient to be missed.
* This class is still experimental and could be removed with next release.
* (possible usage alternative: Cookie class and StingKeyMap)
*/
public final class DbStringSet extends DbCollection implements Set {
private static final DbStringSet.SetEntry[] ZERO_DATA = new DbStringSet.SetEntry[0];
private final static float DEFAULT_LOAD_FACTOR = 0.9F;
private final static float DEFAULT_EXTENTION_FACTOR = 1.2F;
private int nElements;
private DbStringSet.SetEntry[] data;
private int modCount = 0;
/**load from image during startup*/
DbStringSet(final DbObject owner, final String[] image, final int fid) {
super(owner, fid);
restoreFromImage(image);
}
/** new object with set field during runtime */
public DbStringSet(final DbObject owner, final int fid) {
super(owner, fid);
nElements = 0;
data = ZERO_DATA;
}
private void restoreFromImage(final String[] image) {
nElements = image.length;
data = nElements == 0 ? ZERO_DATA : new SetEntry[(int) (nElements / DEFAULT_LOAD_FACTOR)];
for (int i = 0; i < image.length; i++) {
new SetEntry(image[i]);
}
}
DbStringSet.SetEntry[] _getData() {
return data;
}
int _size() {
return nElements;
}
void _clear() {
if (nElements == 0) {
return;
}
for (int i = 0; i < data.length; i++) {
for (SetEntry e = data[i]; e != null; e = e.next) {
e.unlinkFromMap();
}
}
modCount++;
nElements = 0;
}
private int _getIndex(final Object key) {
return (key.hashCode() & 0x7FFFFFFF) % data.length;
}
private void _remove(final SetEntry e) {
e.unlinkFromMap();
nElements--;
modCount++;
}
private void checkRehash(final int n) {
final int newNElements = nElements + n;
if (newNElements > data.length * DEFAULT_LOAD_FACTOR) {
rehash(newNElements);
}
}
private void rehash(final int minCapacity) {
modCount++;
int newCapacity = (int) (data.length * DEFAULT_EXTENTION_FACTOR);
if (newCapacity < minCapacity) {
newCapacity = (int) (minCapacity * DEFAULT_EXTENTION_FACTOR);
}
final SetEntry[] oldData = data;
data = new SetEntry[newCapacity];
for (int i = 0; i < oldData.length; i++) {
for (SetEntry e = oldData[i]; e != null;) {
final SetEntry next = e.next;
e.rehash();
e = next;
}
}
}
public int size() {
owner.ensureReal();
return nElements;
}
public boolean isEmpty() {
return size() == 0;
}
public boolean contains(final Object o) {
owner.ensureReal();
if (nElements == 0 || o == null || !(o instanceof String)) {
return false;
}
final int index = _getIndex(o);
for (SetEntry e = data[index]; e != null; e = e.next) {
if (e.value.equals(o)) {
return true;
}
}
return false;
}
public Iterator iterator() {
return new SetIterator();
}
public Object[] toArray() {
owner.ensureReal();
final String[] arr = new String[nElements];
int j = 0;
for (int i = 0; i < data.length; i++) {
for (SetEntry e = data[i]; e != null; e = e.next) {
arr[j] = e.value;
j++;
}
}
return arr;
}
public Object[] toArray(Object a[]) {
owner.ensureReal();
if (a.length < nElements) {
a = (Object[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), nElements);
}
int j = 0;
for (int i = 0; i < data.length; i++) {
for (SetEntry e = data[i]; e != null; e = e.next) {
a[j] = e.value;
j++;
}
}
if (a.length > nElements) {
a[nElements] = null;
}
return a;
}
public boolean add(final Object o) {
owner.ensureReal();
if (o == null) {
throw new NullPointerException();
}
checkString(o);
owner.onCollectionChange(this);
checkRehash(1);
final int index = _getIndex(o);
for (SetEntry e = data[index]; e != null; e = e.next) {
if (e.value.equals(o)) {
_remove(e);
}
}
modCount++;
new SetEntry((String) o);
nElements++;
return true;
}
private static void checkString(Object o) {
if (!(o instanceof String)) {
throw new IllegalArgumentException("not a string:" + o);
}
}
public boolean remove(final Object o) {
owner.ensureReal();
if (nElements == 0 || o == null) {
return false;
}
checkString(o);
final int index = _getIndex(o);
for (SetEntry e = data[index]; e != null; e = e.next) {
if (e.value.equals(o)) {
owner.onCollectionChange(this);
_remove(e);
return true;
}
}
return false;
}
public boolean containsAll(final Collection c) {
throw new UnsupportedOperationException();
}
public boolean addAll(final Collection c) {
throw new UnsupportedOperationException();
}
public boolean retainAll(final Collection c) {
throw new UnsupportedOperationException();
}
public boolean removeAll(final Collection c) {
throw new UnsupportedOperationException();
}
public void clear() {
owner.ensureReal();
owner.onCollectionChange(this);
_clear();
}
Object createBackupImage() {
if (nElements == 0) {
return DbConstants.ZERO_STRING_ARRAY;
}
final String image[] = new String[nElements];
int pos = 0;
for (int i = 0; i < data.length; i++) {
for (DbStringSet.SetEntry e = data[i]; e != null; e = e.next) {
image[pos] = e.value;
pos += 1;
}
}
return image;
}
void restoreFromBackup(final Object o) {
final String image[] = o == null ? DbConstants.ZERO_STRING_ARRAY : (String[]) o;
_clear();
restoreFromImage(image);
}
//----------------- inners -----------------------//
final class SetEntry {
SetEntry next;
private SetEntry prev;
String value;
SetEntry(final String str) {
value = str;
final int index = _getIndex(value);
next = data[index];
data[index] = this;
if (next != null) {
next.prev = this;
}
}
void rehash() {
final int index = _getIndex(value);
next = data[index];
data[index] = this;
if (next != null) {
next.prev = this;
}
}
void _onTargetDelete() {
owner.onCollectionChange(DbStringSet.this);
_remove(this);
}
private void unlinkFromMap() {
final int index = _getIndex(value);
if (data[index] == this) {
data[index] = next;
} else {
prev.next = next;
}
if (next != null) {
next.prev = prev;
}
}
}
final class SetIterator implements Iterator {
private int expectedModCount;
private SetEntry e = null;
private int index = -1;
private int nPassed;
public SetIterator() {
nPassed = 0;
expectedModCount = modCount;
}
public boolean hasNext() {
checkState();
return (nPassed < nElements);
}
public Object next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
e = (e == null || e.next == null) ? findWithNextIndex() : e.next;
nPassed++;
return e.value;
}
private SetEntry findWithNextIndex() {
while (++index < data.length) {
final SetEntry e = data[index];
if (e != null) {
return e;
}
}
return null;
}
private SetEntry findWithPrevIndex() {
while (--index >= 0) {
SetEntry e = data[index];
if (e != null) {
while (e.next != null) {
e = e.next;
}
return e;
}
}
return null;
}
public void remove() {
checkState();
if (e == null) { //staying on first element
throw new IllegalStateException();
}
final SetEntry re = e;
e = (e.prev == null) ? findWithPrevIndex() : e.prev;
owner.onCollectionChange(DbStringSet.this);
_remove(re);
expectedModCount++;
nPassed--;
}
private void checkState() {
owner.ensureReal();
checkForComodifications();
}
private void checkForComodifications() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
};
}