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

io.github.tanyaofei.guava.common.collect.RegularImmutableMultiset Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2011 The Guava Authors
 *
 * 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 io.github.tanyaofei.guava.common.collect;

import com.google.errorprone.annotations.concurrent.LazyInit;
import io.github.tanyaofei.guava.common.annotations.GwtCompatible;
import io.github.tanyaofei.guava.common.annotations.VisibleForTesting;
import io.github.tanyaofei.guava.common.base.Objects;
import io.github.tanyaofei.guava.common.primitives.Ints;
import org.checkerframework.checker.nullness.qual.Nullable;

import javax.annotation.CheckForNull;
import java.util.Arrays;
import java.util.Collection;

import static io.github.tanyaofei.guava.common.base.Preconditions.checkNotNull;

/**
 * Implementation of {@link io.github.tanyaofei.guava.common.collect.ImmutableMultiset} with zero or more elements.
 *
 * @author Jared Levy
 * @author Louis Wasserman
 */
@GwtCompatible(emulated = true, serializable = true)
@SuppressWarnings("serial") // uses writeReplace(), not default serialization
@ElementTypesAreNonnullByDefault
class RegularImmutableMultiset extends io.github.tanyaofei.guava.common.collect.ImmutableMultiset {
  private static final Multisets.ImmutableEntry[] EMPTY_ARRAY = new Multisets.ImmutableEntry[0];
  static final io.github.tanyaofei.guava.common.collect.ImmutableMultiset EMPTY = create(io.github.tanyaofei.guava.common.collect.ImmutableList.>of());

  static  ImmutableMultiset create(Collection> entries) {
    int distinct = entries.size();
    @SuppressWarnings({"unchecked", "rawtypes"})
    Multisets.ImmutableEntry[] entryArray = new Multisets.ImmutableEntry[distinct];
    if (distinct == 0) {
      return new RegularImmutableMultiset<>(entryArray, EMPTY_ARRAY, 0, 0, io.github.tanyaofei.guava.common.collect.ImmutableSet.of());
    }
    int tableSize = io.github.tanyaofei.guava.common.collect.Hashing.closedTableSize(distinct, MAX_LOAD_FACTOR);
    int mask = tableSize - 1;
    @SuppressWarnings({"unchecked", "rawtypes"})
    Multisets.ImmutableEntry[] hashTable = new Multisets.ImmutableEntry[tableSize];

    int index = 0;
    int hashCode = 0;
    long size = 0;
    for (Entry entryWithWildcard : entries) {
      @SuppressWarnings("unchecked") // safe because we only read from it
      Entry entry = (Entry) entryWithWildcard;
      E element = checkNotNull(entry.getElement());
      int count = entry.getCount();
      int hash = element.hashCode();
      int bucket = io.github.tanyaofei.guava.common.collect.Hashing.smear(hash) & mask;
      Multisets.ImmutableEntry bucketHead = hashTable[bucket];
      Multisets.ImmutableEntry newEntry;
      if (bucketHead == null) {
        boolean canReuseEntry =
            entry instanceof Multisets.ImmutableEntry && !(entry instanceof NonTerminalEntry);
        newEntry =
            canReuseEntry ? (Multisets.ImmutableEntry) entry : new Multisets.ImmutableEntry(element, count);
      } else {
        newEntry = new NonTerminalEntry(element, count, bucketHead);
      }
      hashCode += hash ^ count;
      entryArray[index++] = newEntry;
      hashTable[bucket] = newEntry;
      size += count;
    }

    return hashFloodingDetected(hashTable)
        ? JdkBackedImmutableMultiset.create(ImmutableList.asImmutableList(entryArray))
        : new RegularImmutableMultiset(
            entryArray, hashTable, Ints.saturatedCast(size), hashCode, null);
  }

  private static boolean hashFloodingDetected(Multisets.ImmutableEntry[] hashTable) {
    for (int i = 0; i < hashTable.length; i++) {
      int bucketLength = 0;
      for (Multisets.ImmutableEntry entry = hashTable[i]; entry != null; entry = entry.nextInBucket()) {
        bucketLength++;
        if (bucketLength > MAX_HASH_BUCKET_LENGTH) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Closed addressing tends to perform well even with high load factors. Being conservative here
   * ensures that the table is still likely to be relatively sparse (hence it misses fast) while
   * saving space.
   */
  @VisibleForTesting static final double MAX_LOAD_FACTOR = 1.0;

  /**
   * Maximum allowed false positive probability of detecting a hash flooding attack given random
   * input.
   */
  @VisibleForTesting static final double HASH_FLOODING_FPP = 0.001;

  /**
   * Maximum allowed length of a hash table bucket before falling back to a j.u.HashMap based
   * implementation. Experimentally determined.
   */
  @VisibleForTesting static final int MAX_HASH_BUCKET_LENGTH = 9;

  private final transient Multisets.ImmutableEntry[] entries;
  private final transient Multisets.ImmutableEntry[] hashTable;
  private final transient int size;
  private final transient int hashCode;

  @LazyInit @CheckForNull private transient io.github.tanyaofei.guava.common.collect.ImmutableSet elementSet;

  private RegularImmutableMultiset(
      Multisets.ImmutableEntry[] entries,
      Multisets.ImmutableEntry[] hashTable,
      int size,
      int hashCode,
      @CheckForNull io.github.tanyaofei.guava.common.collect.ImmutableSet elementSet) {
    this.entries = entries;
    this.hashTable = hashTable;
    this.size = size;
    this.hashCode = hashCode;
    this.elementSet = elementSet;
  }

  private static final class NonTerminalEntry extends Multisets.ImmutableEntry {
    private final Multisets.ImmutableEntry nextInBucket;

    NonTerminalEntry(E element, int count, Multisets.ImmutableEntry nextInBucket) {
      super(element, count);
      this.nextInBucket = nextInBucket;
    }

    @Override
    public Multisets.ImmutableEntry nextInBucket() {
      return nextInBucket;
    }
  }

  @Override
  boolean isPartialView() {
    return false;
  }

  @Override
  public int count(@CheckForNull Object element) {
    Multisets.ImmutableEntry[] hashTable = this.hashTable;
    if (element == null || hashTable.length == 0) {
      return 0;
    }
    int hash = io.github.tanyaofei.guava.common.collect.Hashing.smearedHash(element);
    int mask = hashTable.length - 1;
    for (Multisets.ImmutableEntry entry = hashTable[hash & mask];
         entry != null;
         entry = entry.nextInBucket()) {
      if (Objects.equal(element, entry.getElement())) {
        return entry.getCount();
      }
    }
    return 0;
  }

  @Override
  public int size() {
    return size;
  }

  @Override
  public io.github.tanyaofei.guava.common.collect.ImmutableSet elementSet() {
    ImmutableSet result = elementSet;
    return (result == null) ? elementSet = new ElementSet(Arrays.asList(entries), this) : result;
  }

  @Override
  Entry getEntry(int index) {
    return entries[index];
  }

  @Override
  public int hashCode() {
    return hashCode;
  }
}