001/* 002 * Copyright (C) 2007 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.google.common.collect; 018 019import static com.google.common.base.Preconditions.checkNotNull; 020import static java.util.Objects.requireNonNull; 021 022import com.google.common.annotations.GwtCompatible; 023import com.google.common.annotations.GwtIncompatible; 024import com.google.common.annotations.J2ktIncompatible; 025import com.google.errorprone.annotations.CanIgnoreReturnValue; 026import java.io.IOException; 027import java.io.ObjectInputStream; 028import java.io.ObjectOutputStream; 029import java.util.EnumMap; 030import java.util.HashMap; 031import java.util.Map; 032import javax.annotation.CheckForNull; 033import org.checkerframework.checker.nullness.qual.Nullable; 034 035/** 036 * A {@code BiMap} backed by an {@code EnumMap} instance for keys-to-values, and a {@code HashMap} 037 * instance for values-to-keys. Null keys are not permitted, but null values are. An {@code 038 * EnumHashBiMap} and its inverse are both serializable. 039 * 040 * <p>See the Guava User Guide article on <a href= 041 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#bimap">{@code BiMap}</a>. 042 * 043 * @author Mike Bostock 044 * @since 2.0 045 */ 046@GwtCompatible(emulated = true) 047@J2ktIncompatible 048@ElementTypesAreNonnullByDefault 049public final class EnumHashBiMap<K extends Enum<K>, V extends @Nullable Object> 050 extends AbstractBiMap<K, V> { 051 transient Class<K> keyTypeOrObjectUnderJ2cl; 052 053 /** 054 * Returns a new, empty {@code EnumHashBiMap} using the specified key type. 055 * 056 * @param keyType the key type 057 */ 058 public static <K extends Enum<K>, V extends @Nullable Object> EnumHashBiMap<K, V> create( 059 Class<K> keyType) { 060 return new EnumHashBiMap<>(keyType); 061 } 062 063 /** 064 * Constructs a new bimap with the same mappings as the specified map. If the specified map is an 065 * {@code EnumHashBiMap} or an {@link EnumBiMap}, the new bimap has the same key type as the input 066 * bimap. Otherwise, the specified map must contain at least one mapping, in order to determine 067 * the key type. 068 * 069 * @param map the map whose mappings are to be placed in this map 070 * @throws IllegalArgumentException if map is not an {@code EnumBiMap} or an {@code EnumHashBiMap} 071 * instance and contains no mappings 072 */ 073 public static <K extends Enum<K>, V extends @Nullable Object> EnumHashBiMap<K, V> create( 074 Map<K, ? extends V> map) { 075 EnumHashBiMap<K, V> bimap = create(EnumBiMap.inferKeyTypeOrObjectUnderJ2cl(map)); 076 bimap.putAll(map); 077 return bimap; 078 } 079 080 private EnumHashBiMap(Class<K> keyType) { 081 super(new EnumMap<K, V>(keyType), new HashMap<V, K>()); 082 // TODO: cpovirk - Pre-size the HashMap based on the number of enum values? 083 this.keyTypeOrObjectUnderJ2cl = keyType; 084 } 085 086 // Overriding these 3 methods to show that values may be null (but not keys) 087 088 @Override 089 K checkKey(K key) { 090 return checkNotNull(key); 091 } 092 093 @CanIgnoreReturnValue 094 @Override 095 @SuppressWarnings("RedundantOverride") // b/192446478: RedundantOverride ignores some annotations. 096 // TODO(b/192446998): Remove this override after tools understand nullness better. 097 @CheckForNull 098 public V put(K key, @ParametricNullness V value) { 099 return super.put(key, value); 100 } 101 102 @CanIgnoreReturnValue 103 @Override 104 @SuppressWarnings("RedundantOverride") // b/192446478: RedundantOverride ignores some annotations. 105 // TODO(b/192446998): Remove this override after tools understand nullness better. 106 @CheckForNull 107 public V forcePut(K key, @ParametricNullness V value) { 108 return super.forcePut(key, value); 109 } 110 111 /** Returns the associated key type. */ 112 @GwtIncompatible 113 public Class<K> keyType() { 114 return keyTypeOrObjectUnderJ2cl; 115 } 116 117 /** 118 * @serialData the key class, number of entries, first key, first value, second key, second value, 119 * and so on. 120 */ 121 @GwtIncompatible // java.io.ObjectOutputStream 122 private void writeObject(ObjectOutputStream stream) throws IOException { 123 stream.defaultWriteObject(); 124 stream.writeObject(keyTypeOrObjectUnderJ2cl); 125 Serialization.writeMap(this, stream); 126 } 127 128 @SuppressWarnings("unchecked") // reading field populated by writeObject 129 @GwtIncompatible // java.io.ObjectInputStream 130 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 131 stream.defaultReadObject(); 132 keyTypeOrObjectUnderJ2cl = (Class<K>) requireNonNull(stream.readObject()); 133 /* 134 * TODO: cpovirk - Pre-size the HashMap based on the number of enum values? (But *not* based on 135 * the number of entries in the map, as that makes it easy for hostile inputs to trigger lots of 136 * allocation—not that any program should be deserializing hostile inputs to begin with!) 137 */ 138 setDelegates(new EnumMap<K, V>(keyTypeOrObjectUnderJ2cl), new HashMap<V, K>()); 139 Serialization.populateMap(this, stream); 140 } 141 142 @GwtIncompatible // only needed in emulated source. 143 private static final long serialVersionUID = 0; 144}