/*
* Copyright (C) 2012 Facebook, Inc.
*
* 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 com.facebook.collections.specialized;
import com.facebook.collections.Trackable;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import gnu.trove.impl.sync.TSynchronizedIntSet;
import gnu.trove.iterator.TIntIterator;
import gnu.trove.set.TIntSet;
import gnu.trove.set.hash.TIntHashSet;
import javax.annotation.concurrent.GuardedBy;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* making this take a Long for compatibility, but operates on integers
*/
public class IntegerHashSet implements SnapshotableSet<Long>, Trackable {
private static final float MAX_LOAD_FACTOR = 2 / 3.0f;
private final TIntSet set;
private final Object mutex = new Object();
private final int maxCapacity;
@GuardedBy("mutex")
private volatile boolean hasChanged;
public IntegerHashSet(int initialCapacity, int maxCapacity) {
Preconditions.checkArgument(
initialCapacity <= maxCapacity,
"initial capacity of %s cannot be larger than max of %s",
initialCapacity,
maxCapacity
);
set = new TSynchronizedIntSet(new TIntHashSet(initialCapacity, MAX_LOAD_FACTOR, -1), mutex);
this.maxCapacity = maxCapacity;
}
public IntegerHashSet(int maxCapacity) {
this(Math.max(maxCapacity / 16, 1), maxCapacity);
}
private IntegerHashSet(IntegerHashSet set) {
this.set = new TSynchronizedIntSet(new TIntHashSet(set.set), mutex);
this.maxCapacity = set.maxCapacity;
}
@Override
public boolean contains(Object value) {
if (value instanceof Integer || value instanceof Long) {
synchronized (mutex) {
return set.contains(((Number) value).intValue());
}
}
return false;
}
@VisibleForTesting
boolean add(Integer value) {
return add(value.longValue());
}
@Override
public boolean add(Long value) {
synchronized (mutex) {
// there must be room for at least one element, even adding duplicates; the point is, no
// add() should be called once we're maxed out.
Preconditions.checkState(
set.size() < maxCapacity,
"set is size %s which means we're full, but someone's calling add. Why?",
set.size()
);
if (set.add(value.intValue())) {
hasChanged = true;
return true;
}
}
return false;
}
@Override
public boolean remove(Object value) {
if (value instanceof Integer || value instanceof Long) {
synchronized (mutex) {
if (set.remove(((Number) value).intValue())) {
hasChanged = true;
}
}
}
return hasChanged;
}
@Override
public int size() {
return set.size();
}
@Override
public boolean isEmpty() {
return set.isEmpty();
}
@Override
public Iterator<Long> iterator() {
synchronized (mutex) {
return new Iterator<Long>() {
private TIntIterator iterator = set.iterator();
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
@SuppressWarnings("IteratorNextCanNotThrowNoSuchElementException")
public Long next() throws NoSuchElementException {
return (long) iterator.next();
}
@Override
public void remove() {
synchronized (mutex) {
iterator.remove();
hasChanged = true;
}
}
};
}
}
@Override
public Object[] toArray() {
int[] ints = set.toArray();
Object[] result = new Object[ints.length];
for (int i = 0; i < result.length; i++) {
result[i] = ints[i];
}
return result;
}
@Override
public <T> T[] toArray(T[] result) {
int[] ints = set.toArray();
if (result.length < ints.length) {
//noinspection unchecked
result = (T[]) Array.newInstance(result.getClass().getComponentType(), ints.length);
}
for (int i = 0; i < result.length; i++) {
result[i] = (T) Integer.valueOf(ints[i]);
}
return result;
}
@Override
public boolean containsAll(Collection<?> values) {
return set.containsAll(values);
}
@Override
public boolean addAll(Collection<? extends Long> values) {
boolean retVal = false;
for (Long value : values) {
Preconditions.checkState(
set.size() < maxCapacity,
"set is size %s which means we're full, but someone's calling add. Why?",
set.size()
);
retVal |= set.add(value.intValue());
}
return retVal;
}
@Override
public boolean retainAll(Collection<?> values) {
boolean methodHasChanged;
synchronized (mutex) {
methodHasChanged = set.retainAll(values);
hasChanged |= methodHasChanged;
}
return methodHasChanged;
}
@Override
public boolean removeAll(Collection<?> values) {
boolean methodHasChanged;
synchronized (mutex) {
methodHasChanged = set.removeAll(values);
hasChanged |= methodHasChanged;
}
return methodHasChanged;
}
@Override
public void clear() {
synchronized (mutex) {
if (!set.isEmpty()) {
set.clear();
hasChanged = true;
}
}
}
@Override
public SnapshotableSet<Long> makeSnapshot() {
synchronized (mutex) {
return new IntegerHashSet(this);
}
}
@Override
public SnapshotableSet<Long> makeTransientSnapshot() {
return makeSnapshot();
}
@Override
public boolean hasChanged() {
synchronized (mutex) {
try {
return hasChanged;
} finally {
hasChanged = false;
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof IntegerHashSet) {
IntegerHashSet integerHashSet = (IntegerHashSet) o;
return !(set != null ? !set.equals(integerHashSet.set) : integerHashSet.set != null);
} else if (o instanceof Set) {
Set otherSet = (Set) o;
return (this.set != null && this.set.size() == otherSet.size() && this.containsAll(otherSet));
} else {
return this.set == null && o == null;
}
}
@Override
public int hashCode() {
int result = set != null ? set.hashCode() : 0;
result = 31 * result + (mutex != null ? mutex.hashCode() : 0);
result = 31 * result + (hasChanged ? 1 : 0);
return result;
}
}