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    
017    package com.google.common.collect;
018    
019    import static com.google.common.base.Preconditions.checkArgument;
020    import static com.google.common.base.Preconditions.checkNotNull;
021    
022    import com.google.common.annotations.GwtCompatible;
023    import com.google.common.annotations.GwtIncompatible;
024    
025    import java.io.IOException;
026    import java.io.ObjectInputStream;
027    import java.io.ObjectOutputStream;
028    import java.util.EnumMap;
029    import java.util.Map;
030    
031    /**
032     * A {@code BiMap} backed by two {@code EnumMap} instances. Null keys and values
033     * are not permitted. An {@code EnumBiMap} and its inverse are both
034     * serializable.
035     * 
036     * <p>See the Guava User Guide article on <a href=
037     * "http://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#BiMap">
038     * {@code BiMap}</a>.
039     *
040     * @author Mike Bostock
041     * @since 2.0 (imported from Google Collections Library)
042     */
043    @GwtCompatible(emulated = true)
044    public final class EnumBiMap<K extends Enum<K>, V extends Enum<V>>
045        extends AbstractBiMap<K, V> {
046      private transient Class<K> keyType;
047      private transient Class<V> valueType;
048    
049      /**
050       * Returns a new, empty {@code EnumBiMap} using the specified key and value
051       * types.
052       *
053       * @param keyType the key type
054       * @param valueType the value type
055       */
056      public static <K extends Enum<K>, V extends Enum<V>> EnumBiMap<K, V>
057          create(Class<K> keyType, Class<V> valueType) {
058        return new EnumBiMap<K, V>(keyType, valueType);
059      }
060    
061      /**
062       * Returns a new bimap with the same mappings as the specified map. If the
063       * specified map is an {@code EnumBiMap}, the new bimap has the same types as
064       * the provided map. Otherwise, the specified map must contain at least one
065       * mapping, in order to determine the key and value types.
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}
069       *     instance and contains no mappings
070       */
071      public static <K extends Enum<K>, V extends Enum<V>> EnumBiMap<K, V>
072          create(Map<K, V> map) {
073        EnumBiMap<K, V> bimap = create(inferKeyType(map), inferValueType(map));
074        bimap.putAll(map);
075        return bimap;
076      }
077    
078      private EnumBiMap(Class<K> keyType, Class<V> valueType) {
079        super(WellBehavedMap.wrap(new EnumMap<K, V>(keyType)),
080            WellBehavedMap.wrap(new EnumMap<V, K>(valueType)));
081        this.keyType = keyType;
082        this.valueType = valueType;
083      }
084    
085      static <K extends Enum<K>> Class<K> inferKeyType(Map<K, ?> map) {
086        if (map instanceof EnumBiMap) {
087          return ((EnumBiMap<K, ?>) map).keyType();
088        }
089        if (map instanceof EnumHashBiMap) {
090          return ((EnumHashBiMap<K, ?>) map).keyType();
091        }
092        checkArgument(!map.isEmpty());
093        return map.keySet().iterator().next().getDeclaringClass();
094      }
095    
096      private static <V extends Enum<V>> Class<V> inferValueType(Map<?, V> map) {
097        if (map instanceof EnumBiMap) {
098          return ((EnumBiMap<?, V>) map).valueType;
099        }
100        checkArgument(!map.isEmpty());
101        return map.values().iterator().next().getDeclaringClass();
102      }
103    
104      /** Returns the associated key type. */
105      public Class<K> keyType() {
106        return keyType;
107      }
108    
109      /** Returns the associated value type. */
110      public Class<V> valueType() {
111        return valueType;
112      }
113    
114      @Override
115      K checkKey(K key) {
116        return checkNotNull(key);
117      }
118    
119      @Override
120      V checkValue(V value) {
121        return checkNotNull(value);
122      }
123    
124      /**
125       * @serialData the key class, value class, number of entries, first key, first
126       *     value, second key, second value, and so on.
127       */
128      @GwtIncompatible("java.io.ObjectOutputStream")
129      private void writeObject(ObjectOutputStream stream) throws IOException {
130        stream.defaultWriteObject();
131        stream.writeObject(keyType);
132        stream.writeObject(valueType);
133        Serialization.writeMap(this, stream);
134      }
135    
136      @SuppressWarnings("unchecked") // reading fields populated by writeObject
137      @GwtIncompatible("java.io.ObjectInputStream")
138      private void readObject(ObjectInputStream stream)
139          throws IOException, ClassNotFoundException {
140        stream.defaultReadObject();
141        keyType = (Class<K>) stream.readObject();
142        valueType = (Class<V>) stream.readObject();
143        setDelegates(
144            WellBehavedMap.wrap(new EnumMap<K, V>(keyType)),
145            WellBehavedMap.wrap(new EnumMap<V, K>(valueType)));
146        Serialization.populateMap(this, stream);
147      }
148    
149      @GwtIncompatible("not needed in emulated source.")
150      private static final long serialVersionUID = 0;
151    }