    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    }