/
area/
classes/net/sourceforge/pain/console/
classes/net/sourceforge/pain/logic/
classes/net/sourceforge/pain/logic/event/
classes/net/sourceforge/pain/logic/fn/util/
classes/net/sourceforge/pain/plugin/
classes/net/sourceforge/pain/plugin/reset/
classes/net/sourceforge/pain/plugin/shutdown/
classes/net/sourceforge/pain/plugin/social/
classes/net/sourceforge/pain/util/
classest/net/sourceforge/pain/db/data/
doc/
doc/paindb/resources/
src/net/sourceforge/pain/console/
src/net/sourceforge/pain/console/telnet/
src/net/sourceforge/pain/logic/
src/net/sourceforge/pain/logic/event/
src/net/sourceforge/pain/logic/fn/util/
src/net/sourceforge/pain/plugin/
src/net/sourceforge/pain/plugin/command/
src/net/sourceforge/pain/plugin/reset/
src/net/sourceforge/pain/plugin/shutdown/
src/net/sourceforge/pain/plugin/social/
src/net/sourceforge/pain/util/
tests/net/sourceforge/pain/db/data/
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();
			}
		}
	};
}