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

com.google.common.collect.RegularImmutableBiMap Maven / Gradle / Ivy

There is a newer version: 3.9
Show newest version
/*
 * Copyright (C) 2008 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 com.google.common.collect;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.GwtCompatible;

import java.io.Serializable;
import java.util.Collection;

import javax.annotation.Nullable;

/**
 * Bimap with two or more mappings.
 *
 * @author Louis Wasserman
 */
@GwtCompatible(serializable = true, emulated = true)
@SuppressWarnings("serial") // uses writeReplace(), not default serialization
class RegularImmutableBiMap extends ImmutableBiMap {
  private static class BiMapEntry extends ImmutableEntry {
    BiMapEntry(K key, V value) {
      super(key, value);
    }

    @Nullable
    BiMapEntry getNextInKToVBucket() {
      return null;
    }

    @Nullable
    BiMapEntry getNextInVToKBucket() {
      return null;
    }
  }
  
  private static class NonTerminalBiMapEntry extends BiMapEntry {
    @Nullable private final BiMapEntry nextInKToVBucket;
    @Nullable private final BiMapEntry nextInVToKBucket;

    NonTerminalBiMapEntry(K key, V value, @Nullable BiMapEntry nextInKToVBucket,
        @Nullable BiMapEntry nextInVToKBucket) {
      super(key, value);
      this.nextInKToVBucket = nextInKToVBucket;
      this.nextInVToKBucket = nextInVToKBucket;
    }

    @Override
    @Nullable
    BiMapEntry getNextInKToVBucket() {
      return nextInKToVBucket;
    }
    
    @Override
    @Nullable
    BiMapEntry getNextInVToKBucket() {
      return nextInVToKBucket;
    }
  }
  
  static final double MAX_LOAD_FACTOR = 1.2;
  
  private transient final BiMapEntry[] kToVTable;
  private transient final BiMapEntry[] vToKTable;
  private transient final BiMapEntry[] entries;
  private transient final int mask;
  private transient final int hashCode;
  
  RegularImmutableBiMap(Collection> entriesToAdd) {
    int n = entriesToAdd.size();
    int tableSize = Hashing.closedTableSize(n, MAX_LOAD_FACTOR);
    this.mask = tableSize - 1;
    BiMapEntry[] kToVTable = createEntryArray(tableSize);
    BiMapEntry[] vToKTable = createEntryArray(tableSize);
    BiMapEntry[] entries = createEntryArray(n);
    int i = 0;
    int hashCode = 0;
    
    for (Entry entry : entriesToAdd) {
      K key = checkNotNull(entry.getKey());
      V value = checkNotNull(entry.getValue());
      
      int keyHash = key.hashCode();
      int valueHash = value.hashCode();
      int keyBucket = Hashing.smear(keyHash) & mask;
      int valueBucket = Hashing.smear(valueHash) & mask;
      
      BiMapEntry nextInKToVBucket = kToVTable[keyBucket];
      for (BiMapEntry kToVEntry = nextInKToVBucket; kToVEntry != null;
           kToVEntry = kToVEntry.getNextInKToVBucket()) {
        if (key.equals(kToVEntry.getKey())) {
          throw new IllegalArgumentException("Multiple entries with same key: " +
              entry + " and " + kToVEntry);
        }
      }
      BiMapEntry nextInVToKBucket = vToKTable[valueBucket];
      for (BiMapEntry vToKEntry = nextInVToKBucket; vToKEntry != null;
           vToKEntry = vToKEntry.getNextInVToKBucket()) {
        if (value.equals(vToKEntry.getValue())) {
          throw new IllegalArgumentException("Multiple entries with same value: "
              + entry + " and " + vToKEntry);
        }
      }
      BiMapEntry newEntry =
          (nextInKToVBucket == null && nextInVToKBucket == null)
          ? new BiMapEntry(key, value)
          : new NonTerminalBiMapEntry(key, value, nextInKToVBucket, nextInVToKBucket);
      kToVTable[keyBucket] = newEntry;
      vToKTable[valueBucket] = newEntry;
      entries[i++] = newEntry;
      hashCode += keyHash ^ valueHash;
    }
    
    this.kToVTable = kToVTable;
    this.vToKTable = vToKTable;
    this.entries = entries;
    this.hashCode = hashCode;
  }
  
  @SuppressWarnings("unchecked")
  private static  BiMapEntry[] createEntryArray(int length) {
    return new BiMapEntry[length];
  }

  @Override
  @Nullable
  public V get(@Nullable Object key) {
    if (key == null) {
      return null;
    }
    int bucket = Hashing.smear(key.hashCode()) & mask;
    for (BiMapEntry entry = kToVTable[bucket]; entry != null;
         entry = entry.getNextInKToVBucket()) {
      if (key.equals(entry.getKey())) {
        return entry.getValue();
      }
    }
    return null;
  }

  @Override
  ImmutableSet> createEntrySet() {
    return new ImmutableMapEntrySet() {
      @Override
      ImmutableMap map() {
        return RegularImmutableBiMap.this;
      }

      @Override
      public UnmodifiableIterator> iterator() {
        return asList().iterator();
      }

      @Override
      ImmutableList> createAsList() {
        return new RegularImmutableAsList>(this, entries);
      }

      @Override
      boolean isHashCodeFast() {
        return true;
      }

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

  @Override
  boolean isPartialView() {
    return false;
  }

  @Override
  public int size() {
    return entries.length;
  }
  
  private transient ImmutableBiMap inverse;

  @Override
  public ImmutableBiMap inverse() {
    ImmutableBiMap result = inverse;
    return (result == null) ? inverse = new Inverse() : result;
  }
  
  private final class Inverse extends ImmutableBiMap {

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

    @Override
    public ImmutableBiMap inverse() {
      return RegularImmutableBiMap.this;
    }

    @Override
    public K get(@Nullable Object value) {
      if (value == null) {
        return null;
      }
      int bucket = Hashing.smear(value.hashCode()) & mask;
      for (BiMapEntry entry = vToKTable[bucket]; entry != null;
           entry = entry.getNextInVToKBucket()) {
        if (value.equals(entry.getValue())) {
          return entry.getKey();
        }
      }
      return null;
    }

    @Override
    ImmutableSet> createEntrySet() {
      return new InverseEntrySet();
    }
    
    final class InverseEntrySet extends ImmutableMapEntrySet {
      @Override
      ImmutableMap map() {
        return Inverse.this;
      }

      @Override
      boolean isHashCodeFast() {
        return true;
      }

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

      @Override
      public UnmodifiableIterator> iterator() {
        return asList().iterator();
      }

      @Override
      ImmutableList> createAsList() {
        return new ImmutableAsList>() {
          @Override
          public Entry get(int index) {
            Entry entry = entries[index];
            return Maps.immutableEntry(entry.getValue(), entry.getKey());
          }

          @Override
          ImmutableCollection> delegateCollection() {
            return InverseEntrySet.this;
          }
        };
      }
    }

    @Override
    boolean isPartialView() {
      return false;
    }
    
    @Override
    Object writeReplace() {
      return new InverseSerializedForm(RegularImmutableBiMap.this);
    }
  }
  
  private static class InverseSerializedForm implements Serializable {
    private final ImmutableBiMap forward;
    
    InverseSerializedForm(ImmutableBiMap forward) {
      this.forward = forward;
    }
    
    Object readResolve() {
      return forward.inverse();
    }
    
    private static final long serialVersionUID = 1;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy