All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.util.collection.InflatableSet Maven / Gradle / Ivy

/*
 * Copyright (c) 2008-2018, 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 com.hazelcast.nio.serialization.SerializableByConvention;
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;

import static com.hazelcast.util.Preconditions.checkNotNull;

/**
 * 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 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 the responsibility of the caller to make sure no duplicated * entries are inserted. *

* Once InflatableSet is constructed via {@link Builder#build()} then it acts as a regular set. It has * been designed to mimic {@link HashSet}. On new entry insertion or lookup via * {@link #contains(Object)} it inflates itself: The backing list is copied into * internal {@link HashSet}. This obviously costs time and space. We are making a bet the * Set won't be modified in most cases. *

* It's intended to be used in cases where the contract mandates us to return Set, * but we know our data does not contain duplicates. It performs best in cases * biased towards sequential iteration. * * @param the type of elements maintained by this set */ @SerializableByConvention 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); } public static Builder newBuilder(List list) { return new Builder(list); } @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(value = "CN_IDIOM", justification = "Deliberate, documented contract violation") @SuppressWarnings({"checkstyle:superclone", "CloneDoesntCallSuperClone"}) 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; 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); } private Builder(List list) { this.list = checkNotNull(list, "list cannot be null"); } public int size() { return list.size(); } 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; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy