1/*
2 * Copyright (C) 2007 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.common.collect;
18
19import static com.google.common.base.Preconditions.checkArgument;
20
21import com.google.common.annotations.GwtCompatible;
22import com.google.common.annotations.GwtIncompatible;
23
24import java.io.IOException;
25import java.io.ObjectInputStream;
26import java.io.ObjectOutputStream;
27import java.util.EnumMap;
28import java.util.Map;
29
30/**
31 * A {@code BiMap} backed by two {@code EnumMap} instances. Null keys and values
32 * are not permitted. An {@code EnumBiMap} and its inverse are both
33 * serializable.
34 *
35 * @author Mike Bostock
36 * @since 2.0 (imported from Google Collections Library)
37 */
38@GwtCompatible(emulated = true)
39public final class EnumBiMap<K extends Enum<K>, V extends Enum<V>>
40    extends AbstractBiMap<K, V> {
41  private transient Class<K> keyType;
42  private transient Class<V> valueType;
43
44  /**
45   * Returns a new, empty {@code EnumBiMap} using the specified key and value
46   * types.
47   *
48   * @param keyType the key type
49   * @param valueType the value type
50   */
51  public static <K extends Enum<K>, V extends Enum<V>> EnumBiMap<K, V>
52      create(Class<K> keyType, Class<V> valueType) {
53    return new EnumBiMap<K, V>(keyType, valueType);
54  }
55
56  /**
57   * Returns a new bimap with the same mappings as the specified map. If the
58   * specified map is an {@code EnumBiMap}, the new bimap has the same types as
59   * the provided map. Otherwise, the specified map must contain at least one
60   * mapping, in order to determine the key and value types.
61   *
62   * @param map the map whose mappings are to be placed in this map
63   * @throws IllegalArgumentException if map is not an {@code EnumBiMap}
64   *     instance and contains no mappings
65   */
66  public static <K extends Enum<K>, V extends Enum<V>> EnumBiMap<K, V>
67      create(Map<K, V> map) {
68    EnumBiMap<K, V> bimap = create(inferKeyType(map), inferValueType(map));
69    bimap.putAll(map);
70    return bimap;
71  }
72
73  private EnumBiMap(Class<K> keyType, Class<V> valueType) {
74    super(WellBehavedMap.wrap(new EnumMap<K, V>(keyType)),
75        WellBehavedMap.wrap(new EnumMap<V, K>(valueType)));
76    this.keyType = keyType;
77    this.valueType = valueType;
78  }
79
80  static <K extends Enum<K>> Class<K> inferKeyType(Map<K, ?> map) {
81    if (map instanceof EnumBiMap) {
82      return ((EnumBiMap<K, ?>) map).keyType();
83    }
84    if (map instanceof EnumHashBiMap) {
85      return ((EnumHashBiMap<K, ?>) map).keyType();
86    }
87    checkArgument(!map.isEmpty());
88    return map.keySet().iterator().next().getDeclaringClass();
89  }
90
91  private static <V extends Enum<V>> Class<V> inferValueType(Map<?, V> map) {
92    if (map instanceof EnumBiMap) {
93      return ((EnumBiMap<?, V>) map).valueType;
94    }
95    checkArgument(!map.isEmpty());
96    return map.values().iterator().next().getDeclaringClass();
97  }
98
99  /** Returns the associated key type. */
100  public Class<K> keyType() {
101    return keyType;
102  }
103
104  /** Returns the associated value type. */
105  public Class<V> valueType() {
106    return valueType;
107  }
108
109  /**
110   * @serialData the key class, value class, number of entries, first key, first
111   *     value, second key, second value, and so on.
112   */
113  @GwtIncompatible("java.io.ObjectOutputStream")
114  private void writeObject(ObjectOutputStream stream) throws IOException {
115    stream.defaultWriteObject();
116    stream.writeObject(keyType);
117    stream.writeObject(valueType);
118    Serialization.writeMap(this, stream);
119  }
120
121  @SuppressWarnings("unchecked") // reading fields populated by writeObject
122  @GwtIncompatible("java.io.ObjectInputStream")
123  private void readObject(ObjectInputStream stream)
124      throws IOException, ClassNotFoundException {
125    stream.defaultReadObject();
126    keyType = (Class<K>) stream.readObject();
127    valueType = (Class<V>) stream.readObject();
128    setDelegates(
129        WellBehavedMap.wrap(new EnumMap<K, V>(keyType)),
130        WellBehavedMap.wrap(new EnumMap<V, K>(valueType)));
131    Serialization.populateMap(this, stream);
132  }
133
134  @GwtIncompatible("not needed in emulated source.")
135  private static final long serialVersionUID = 0;
136}
137