| // Copyright 2014 The Bazel Authors. 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.google.devtools.build.lib.graph; | |
| import com.google.devtools.build.lib.collect.compacthashset.CompactHashSet; | |
| import java.util.ArrayList; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.List; | |
| /** | |
| * Wraps collection implementation. Automatically switches from one to another implementation | |
| * depending on the number of storing elements. | |
| * | |
| * <p>Effective collection implementation depends on the size of collection. | |
| * | |
| * <ul> | |
| * <li>For 1 - singleton immutable List. | |
| * <li>For [2..6] - ArrayList. | |
| * <li>For [7...) - CompactHashSet. | |
| * </ul> | |
| * | |
| * @param <T> | |
| */ | |
| final class ConcurrentCollectionWrapper<T> { | |
| private static final int ARRAYLIST_THRESHOLD = 6; | |
| private static final int INITIAL_HASHSET_CAPACITY = 12; | |
| // The succs and preds set representation changes depending on its size. | |
| // It is implemented using the following collections: | |
| // - null for size = 0. | |
| // - Collections$SingletonList for size = 1. | |
| // - ArrayList(6) for size = [2..6]. | |
| // - CompactHashSet(12) for size > 6. | |
| // These numbers were chosen based on profiling. | |
| // TODO(dbabkin): according to VCS history this profiling info was obtained for | |
| // ArrayList/HashSet. Then HashSet had been replaced by CompactHashSet. Optimal threshold for | |
| // ArrayList/CompactHashSet may differ from 6. | |
| private volatile Collection<T> collection = null; | |
| /** | |
| * Returns {@code Collections.unmodifiableCollection} wrapper around collection. Iteration over | |
| * returned collection at the same time with concurrent modification will cause {@code | |
| * java.util.ConcurrentModificationException} | |
| */ | |
| public Collection<T> get() { | |
| Collection<T> collection = this.collection; | |
| return collection == null | |
| ? Collections.emptyList() | |
| : Collections.unmodifiableCollection(collection); | |
| } | |
| synchronized Collection<T> clear() { | |
| Collection<T> old = collection; | |
| collection = null; | |
| return old != null ? old : Collections.emptyList(); | |
| } | |
| public int size() { | |
| Collection<T> collection = this.collection; | |
| return collection == null ? 0 : collection.size(); | |
| } | |
| /** | |
| * Adds 'value' to wrapped collection. Replacing this collection instance for CompactHashSet from | |
| * ArrayList. | |
| * | |
| * @return {@code true} if the collection was modified; {@code false} if the collection was not | |
| * modified | |
| */ | |
| public synchronized boolean add(T value) { | |
| Collection<T> collection = this.collection; | |
| if (collection == null) { | |
| // null -> SingletonList | |
| this.collection = Collections.singletonList(value); | |
| return true; | |
| } | |
| if (collection.contains(value)) { | |
| // already exists in this collection | |
| return false; | |
| } | |
| int previousSize = collection.size(); | |
| if (previousSize == 1) { | |
| // SingletonList -> ArrayList | |
| Collection<T> newList = new ArrayList<>(ARRAYLIST_THRESHOLD); | |
| newList.addAll(collection); | |
| newList.add(value); | |
| this.collection = newList; | |
| } else if (previousSize < ARRAYLIST_THRESHOLD) { | |
| // ArrayList | |
| collection.add(value); | |
| } else if (previousSize == ARRAYLIST_THRESHOLD) { | |
| // ArrayList -> CompactHashSet | |
| Collection<T> newSet = CompactHashSet.createWithExpectedSize(INITIAL_HASHSET_CAPACITY); | |
| newSet.addAll(collection); | |
| newSet.add(value); | |
| this.collection = newSet; | |
| } else { | |
| // HashSet | |
| collection.add(value); | |
| } | |
| return true; | |
| } | |
| /** | |
| * Removes 'value' from wrapped collection. Replacing this collection instance for ArrayList from | |
| * CompactHashSet. | |
| * | |
| * @return {@code true} if the collection was modified; {@code false} if the set collection not | |
| * modified | |
| */ | |
| public synchronized boolean remove(T value) { | |
| Collection<T> collection = this.collection; | |
| if (collection == null) { | |
| // null | |
| return false; | |
| } | |
| int previousSize = collection.size(); | |
| if (previousSize == 1) { | |
| if (collection.contains(value)) { | |
| // -> null | |
| this.collection = null; | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| // now remove the value | |
| if (collection.remove(value)) { | |
| // may need to change representation | |
| if (previousSize == 2) { | |
| // -> SingletonList | |
| List<T> list = Collections.singletonList(collection.iterator().next()); | |
| this.collection = list; | |
| return true; | |
| } else if (previousSize == 1 + ARRAYLIST_THRESHOLD) { | |
| // -> ArrayList | |
| Collection<T> newArrayList = new ArrayList<>(ARRAYLIST_THRESHOLD); | |
| newArrayList.addAll(collection); | |
| this.collection = newArrayList; | |
| return true; | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| } |