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(); } } }; }