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 org.jspecify.annotations.Nullable; 033 034/** 035 * A {@code BiMap} backed by an {@code EnumMap} instance for keys-to-values, and a {@code HashMap} 036 * instance for values-to-keys. Null keys are not permitted, but null values are. An {@code 037 * EnumHashBiMap} and its inverse are both serializable. 038 * 039 * <p>See the Guava User Guide article on <a href= 040 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#bimap">{@code BiMap}</a>. 041 * 042 * @author Mike Bostock 043 * @since 2.0 044 */ 045@GwtCompatible(emulated = true) 046@J2ktIncompatible 047public final class EnumHashBiMap<K extends Enum<K>, V extends @Nullable Object> 048 extends AbstractBiMap<K, V> { 049 transient Class<K> keyTypeOrObjectUnderJ2cl; 050 051 /** 052 * Returns a new, empty {@code EnumHashBiMap} using the specified key type. 053 * 054 * @param keyType the key type 055 */ 056 public static <K extends Enum<K>, V extends @Nullable Object> EnumHashBiMap<K, V> create( 057 Class<K> keyType) { 058 return new EnumHashBiMap<>(keyType); 059 } 060 061 /** 062 * Constructs a new bimap with the same mappings as the specified map. If the specified map is an 063 * {@code EnumHashBiMap} or an {@link EnumBiMap}, the new bimap has the same key type as the input 064 * bimap. Otherwise, the specified map must contain at least one mapping, in order to determine 065 * the key type. 066 * 067 * @param map the map whose mappings are to be placed in this map 068 * @throws IllegalArgumentException if map is not an {@code EnumBiMap} or an {@code EnumHashBiMap} 069 * instance and contains no mappings 070 */ 071 public static <K extends Enum<K>, V extends @Nullable Object> EnumHashBiMap<K, V> create( 072 Map<K, ? extends V> map) { 073 EnumHashBiMap<K, V> bimap = create(EnumBiMap.inferKeyTypeOrObjectUnderJ2cl(map)); 074 bimap.putAll(map); 075 return bimap; 076 } 077 078 private EnumHashBiMap(Class<K> keyType) { 079 super(new EnumMap<K, V>(keyType), new HashMap<V, K>()); 080 // TODO: cpovirk - Pre-size the HashMap based on the number of enum values? 081 this.keyTypeOrObjectUnderJ2cl = keyType; 082 } 083 084 // Overriding these 3 methods to show that values may be null (but not keys) 085 086 @Override 087 K checkKey(K key) { 088 return checkNotNull(key); 089 } 090 091 @CanIgnoreReturnValue 092 @Override 093 @SuppressWarnings("RedundantOverride") // b/192446478: RedundantOverride ignores some annotations. 094 // TODO(b/192446998): Remove this override after tools understand nullness better. 095 public @Nullable V put(K key, @ParametricNullness V value) { 096 return super.put(key, value); 097 } 098 099 @CanIgnoreReturnValue 100 @Override 101 @SuppressWarnings("RedundantOverride") // b/192446478: RedundantOverride ignores some annotations. 102 // TODO(b/192446998): Remove this override after tools understand nullness better. 103 public @Nullable V forcePut(K key, @ParametricNullness V value) { 104 return super.forcePut(key, value); 105 } 106 107 /** Returns the associated key type. */ 108 @GwtIncompatible 109 public Class<K> keyType() { 110 return keyTypeOrObjectUnderJ2cl; 111 } 112 113 /** 114 * @serialData the key class, number of entries, first key, first value, second key, second value, 115 * and so on. 116 */ 117 @GwtIncompatible // java.io.ObjectOutputStream 118 private void writeObject(ObjectOutputStream stream) throws IOException { 119 stream.defaultWriteObject(); 120 stream.writeObject(keyTypeOrObjectUnderJ2cl); 121 Serialization.writeMap(this, stream); 122 } 123 124 @SuppressWarnings("unchecked") // reading field populated by writeObject 125 @GwtIncompatible // java.io.ObjectInputStream 126 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 127 stream.defaultReadObject(); 128 keyTypeOrObjectUnderJ2cl = (Class<K>) requireNonNull(stream.readObject()); 129 /* 130 * TODO: cpovirk - Pre-size the HashMap based on the number of enum values? (But *not* based on 131 * the number of entries in the map, as that makes it easy for hostile inputs to trigger lots of 132 * allocation—not that any program should be deserializing hostile inputs to begin with!) 133 */ 134 setDelegates(new EnumMap<K, V>(keyTypeOrObjectUnderJ2cl), new HashMap<V, K>()); 135 Serialization.populateMap(this, stream); 136 } 137 138 @GwtIncompatible // only needed in emulated source. 139 private static final long serialVersionUID = 0; 140}