/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * !# */ package net.ontopia.utils; import java.util.Collection; import java.util.Iterator; import java.util.Set; // WARNING: The internals of this class have strong dependencies of // the internals of the CompactHashSet class. So make sure that they // are sync. /** * INTERNAL: */ public class UniqueSet<E> extends CompactHashSet<E> { public UniqueSet() { super(); } public UniqueSet(int size) { super(size); } public UniqueSet(Collection<E> c) { super(c.size() < INITIAL_SIZE ? INITIAL_SIZE : c.size()); Iterator<E> e = c.iterator(); while (e.hasNext()) { super.add(e.next()); } } // Number of external references to this particular set private int refcount; //! private final static UniqueSet EMPTY_SET = new UniqueSet(); private final static int OP_ADD = 1; private final static int OP_REMOVE = 2; public UniqueSet(UniqueSet<E> s) { super(s.objects.length); // clone members System.arraycopy(s.objects, 0, this.objects, 0, s.objects.length); this.elements = s.elements; this.freecells = s.freecells; } public UniqueSet(UniqueSet<E> s1, UniqueSet<E> s2) { super((s1.objects.length > s2.objects.length ? s1.objects.length : s2.objects.length)); UniqueSet<E> set1; UniqueSet<E> set2; if (s1.elements > s2.elements) { set1 = s1; set2 = s2; } else { set1 = s2; set2 = s1; } // clone members System.arraycopy(set1.objects, 0, this.objects, 0, set1.objects.length); this.elements = set1.elements; this.freecells = set1.freecells; // add other sets elements individually Iterator<E> iter = set2.iterator(); for (int i=0; i < set2.elements; i++) { super.add(iter.next()); } } private UniqueSet(UniqueSet<E> s, int op, E o) { this(s); if (op == OP_ADD) { // add object super.add(o); } else if (op == OP_REMOVE) { // remove object super.remove(o); } else { throw new IllegalArgumentException("Unknown op argument: " + op); } } public int getReferenceCount() { return refcount; } /** * INTERNAL: Get the internal representation of a given set. The * initial reference count is 1. */ public UniqueSet<E> get(Set<E> set) { UniqueSet<E> set2; //! if (set.isEmpty()) { //! // Replace with EMPTY_SET if empty. //! set2 = EMPTY_SET; //! if (set2.refcount == 0) super.add(EMPTY_SET); //! } else { // Look up candidate set in set pool set2 = (UniqueSet<E>)lookup(set); if (set2 == null) { // Create new set if no existing set found // FIXME: Could use 'set' variable if compatible and manageable set2 = new UniqueSet<E>(set); super.add(set2); } //!} set2.refcount++; return set2; } /** * INTERNAL: Looks up the object in the hashset and returns the * actual object stored at the hashset. Note that this might be * another object, but one that is considered to be equal. */ protected E lookup(Object o) { if (o == null) o = nullObject; int hash = o.hashCode(); int index = (hash & 0x7FFFFFFF) % objects.length; int offset = 1; // search for the object (continue while !null and !this object) while(objects[index] != null && !(objects[index].hashCode() == hash && objects[index].equals(o))) { index = ((index + offset) & 0x7FFFFFFF) % objects.length; offset = offset*2 + 1; if (offset == -1) offset = 2; } return objects[index]; } public void dereference(UniqueSet<E> set) { //! if (set == EMPTY_SET) { //! if (set.refcount < 1) //! throw new IllegalArgumentException("Set " + set + " is not registered with this pool."); //! set.refcount--; //! } else { //! if (!contains(set)) //! throw new IllegalArgumentException("Set " + set + " is not registered with this pool."); //! // decrement reference count //! set.refcount--; //! } if (!contains((E)set)) throw new IllegalArgumentException("Set " + set + " is not registered with this pool."); set.refcount--; if (set.refcount == 0) super.remove((E)set); //! if (set.refcount == 0) System.out.println("DEREF: " + set); } protected boolean equalsAdd(UniqueSet<E> other, Object o) { if (other.elements+1 != elements || !contains((E)o)) return false; for (int i=0; i < other.objects.length; i++) { if (other.objects[i] == null || other.objects[i] == deletedObject) continue; if (!contains((E)other.objects[i])) return false; } return true; } protected boolean equalsRemove(UniqueSet<E> other, Object o) { if (other.elements-1 != elements || contains((E)o)) return false; for (int i=0; i < other.objects.length; i++) { if (other.objects[i] == null || other.objects[i] == deletedObject) continue; if (!contains((E)other.objects[i]) && !other.objects[i].equals(o)) return false; } return true; } public UniqueSet<E> add(UniqueSet<E> set, Object o, boolean dereference) { if (o == null) o = nullObject; if (set.contains((E)o)) return set; // decrement reference count if (dereference) dereference(set); int hash = set.hashCode() + o.hashCode(); int index = (hash & 0x7FFFFFFF) % objects.length; int offset = 1; // search for the added set while(objects[index] != null && !(objects[index].hashCode() == hash && ((UniqueSet)objects[index]).equalsAdd(set, o))) { index = ((index + offset) & 0x7FFFFFFF) % objects.length; offset = offset*2 + 1; if (offset == -1) offset = 2; } if (objects[index] == null || objects[index] == deletedObject) { // wasn't present already UniqueSet newset = new UniqueSet(set, OP_ADD, o); // reference count = 1 newset.refcount = 1; //! System.out.println("ADDED: " + newset.refcount + " " + newset); super.add(newset); return newset; } else { // was there already // increment reference count UniqueSet oldset = (UniqueSet)objects[index]; oldset.refcount++; //! System.out.println("REUSE: " + oldset.refcount + " " + oldset); return oldset; } } @SuppressWarnings("unchecked") public UniqueSet<E> remove(UniqueSet<E> set, Object o, boolean dereference) { if (o == null) o = nullObject; if (!set.contains((E)o)) return set; // decrement reference count if (dereference) dereference(set); int hash = set.hashCode() - o.hashCode(); int index = (hash & 0x7FFFFFFF) % objects.length; int offset = 1; // search for the removed set while(objects[index] != null && !(objects[index].hashCode() == hash && ((UniqueSet<E>)objects[index]).equalsRemove(set, o))) { index = ((index + offset) & 0x7FFFFFFF) % objects.length; offset = offset*2 + 1; if (offset == -1) offset = 2; } if (objects[index] == null || objects[index] == deletedObject) { // wasn't present already UniqueSet<E> newset = new UniqueSet<E>(set, OP_REMOVE, (E)o); // reference count = 1 newset.refcount = 1; //! System.out.println("ADDED: " + newset.refcount + " " + newset); super.add(newset); return newset; } else { // was there already // increment reference count UniqueSet<E> oldset = (UniqueSet<E>)objects[index]; oldset.refcount++; //! System.out.println("REUSE: " + oldset.refcount + " " + oldset); return oldset; } } @SuppressWarnings("unchecked") public void dump() { System.out.println("Reference count: " + refcount); Iterator<E> iter = iterator(); for (int i=0; i < elements; i++) { UniqueSet<E> set = (UniqueSet<E>)iter.next(); System.out.println("=> " + set.refcount + ": " + set); } } // ---- Set is immutable public boolean add(Object o) { throw new UnsupportedOperationException(); } public boolean remove(Object o) { throw new UnsupportedOperationException(); } public boolean addAll(Collection<? extends E> coll) { throw new UnsupportedOperationException(); } public boolean removeAll(Collection<?> coll) { throw new UnsupportedOperationException(); } public boolean retainAll(Collection<?> coll) { throw new UnsupportedOperationException(); } public void clear() { throw new UnsupportedOperationException(); } public String toString() { return super.toString() + getReferenceCount(); } protected int hc; protected int hc_modCount = -1; public int hashCode() { // recompute hashcode only when neccessary if (modCount != hc_modCount) { hc_modCount = modCount; hc = super.hashCode(); } return hc; } }