com.hazelcast.util.collection.InflatableSet Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2015, Hazelcast, Inc. All Rights Reserved.
*
* 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.hazelcast.util.collection;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* Provides fast {@link Set} implementation for cases where items are known to not
* contain duplicates.
*
* It requires creation via {@link com.hazelcast.util.collection.InflatableSet.Builder}
*
* The builder doesn't not call equals/hash methods on initial data insertion hence it avoids
* performance penalty in the case these methods are expensive. It also means it does
* not detect duplicates - it's a responsibility of a caller to make sure no duplicated
* entries are inserted.
*
* Once InflatableSet is constructed via {@link Builder#build()} then it act as regular set. It has
* been designed to mimic {@link HashSet}. On new entry insertion or look-up via
* {@link #contains(Object)} it inflates itself: The backing list is copied into
* internal {@link HashSet}. This obviously costs time and space. We are make a bet the
* Set won't be modified in most cases.
*
* It's intended to be use in cases where a contract mandates us to return Set,
* but we know our data contains not duplicates. It performs the best in cases
* biased towards sequential iterations.
*
* @param the type of elements maintained by this set
*/
public final class InflatableSet extends AbstractSet implements Set, Serializable, Cloneable {
private static final long serialVersionUID = 0L;
private enum State {
//Only array-backed representation exists
COMPACT,
//both array-backed & hashset-backed representation exist.
//this is needed as we are creating HashSet on contains()
//but we don't want invalidate existing iterators.
HYBRID,
//only hashset based representation exists
INFLATED
}
private final List compactList;
private Set inflatedSet;
private State state;
/**
* This constructor is intended to be used by {@link com.hazelcast.util.collection.InflatableSet.Builder} only.
*
* @param compactList list of elements for the InflatableSet
*/
private InflatableSet(List compactList) {
this.state = State.COMPACT;
this.compactList = compactList;
}
/**
* This copy-constructor is intended to be used by {@link #clone()} method only.
*
* @param other other InflatableSet which should be cloned
*/
private InflatableSet(InflatableSet other) {
compactList = new ArrayList(other.compactList.size());
compactList.addAll(other.compactList);
if (other.inflatedSet != null) {
inflatedSet = new HashSet(other.inflatedSet);
}
state = other.state;
}
public static Builder newBuilder(int initialCapacity) {
return new Builder(initialCapacity);
}
@Override
public int size() {
if (state == State.INFLATED) {
return inflatedSet.size();
} else {
return compactList.size();
}
}
@Override
public boolean isEmpty() {
if (state == State.INFLATED) {
return inflatedSet.isEmpty();
} else {
return compactList.isEmpty();
}
}
@Override
public boolean contains(Object o) {
if (state == State.COMPACT) {
toHybridState();
}
return inflatedSet.contains(o);
}
@Override
public Iterator iterator() {
if (state == State.INFLATED) {
return inflatedSet.iterator();
}
return new HybridIterator();
}
@Override
public boolean add(T t) {
toInflatedState();
return inflatedSet.add(t);
}
@Override
public boolean remove(Object o) {
switch (state) {
case HYBRID:
compactList.remove(o);
return inflatedSet.remove(o);
case INFLATED:
return inflatedSet.remove(o);
default:
return compactList.remove(o);
}
}
@Override
public void clear() {
switch (state) {
case HYBRID:
inflatedSet.clear();
compactList.clear();
break;
case INFLATED:
inflatedSet.clear();
break;
default:
compactList.clear();
}
}
/**
* Returns a shallow copy of this InflatableSet instance: the keys and
* values themselves are not cloned.
*
* @return a shallow copy of this set
*/
@Override
@SuppressFBWarnings("CN_IDIOM")
protected Object clone() {
return new InflatableSet(this);
}
private void inflateIfNeeded() {
if (inflatedSet == null) {
inflatedSet = new HashSet(compactList);
}
}
private void toHybridState() {
if (state == State.HYBRID) {
return;
}
state = State.HYBRID;
inflateIfNeeded();
}
private void toInflatedState() {
if (state == State.INFLATED) {
return;
}
state = State.INFLATED;
inflateIfNeeded();
invalidateIterators();
}
private void invalidateIterators() {
if (compactList.size() == 0) {
compactList.clear();
} else {
compactList.remove(0);
}
}
private class HybridIterator implements Iterator {
private Iterator innerIterator;
private T currentValue;
public HybridIterator() {
innerIterator = compactList.iterator();
}
@Override
public boolean hasNext() {
return innerIterator.hasNext();
}
@Override
public T next() {
currentValue = innerIterator.next();
return currentValue;
}
@Override
public void remove() {
innerIterator.remove();
if (inflatedSet != null) {
inflatedSet.remove(currentValue);
}
}
}
/**
* Builder for {@link InflatableSet}
* This is the only way to create a new instance of InflatableSet
*
* @param the type of elements maintained by this set
*/
public static final class Builder {
private List list;
private Builder(int initialCapacity) {
this.list = new ArrayList(initialCapacity);
}
public Builder add(T item) {
list.add(item);
return this;
}
public InflatableSet build() {
InflatableSet set = new InflatableSet(list);
//make sure no further insertions are possible
list = Collections.emptyList();
return set;
}
}
}