001/*
002 * Copyright (C) 2009 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;
020
021import com.google.common.primitives.Primitives;
022
023import java.io.Serializable;
024import java.util.Map;
025
026import javax.annotation.Nullable;
027
028/**
029 * A {@link ClassToInstanceMap} whose contents will never change, with many
030 * other important properties detailed at {@link ImmutableCollection}.
031 *
032 * @author Kevin Bourrillion
033 * @since 2.0
034 */
035public final class ImmutableClassToInstanceMap<B> extends ForwardingMap<Class<? extends B>, B>
036    implements ClassToInstanceMap<B>, Serializable {
037
038  private static final ImmutableClassToInstanceMap<Object> EMPTY =
039      new ImmutableClassToInstanceMap<Object>(ImmutableMap.<Class<?>, Object>of());
040
041  /**
042   * Returns an empty {@code ImmutableClassToInstanceMap}.
043   *
044   * @since 19.0
045   */
046  @SuppressWarnings("unchecked")
047  public static <B> ImmutableClassToInstanceMap<B> of() {
048    return (ImmutableClassToInstanceMap<B>) EMPTY;
049  }
050
051  /**
052   * Returns an {@code ImmutableClassToInstanceMap} containing a single entry.
053   *
054   * @since 19.0
055   */
056  public static <B, T extends B> ImmutableClassToInstanceMap<B> of(Class<T> type, T value) {
057    ImmutableMap<Class<? extends B>, B> map = ImmutableMap.<Class<? extends B>, B>of(type, value);
058    return new ImmutableClassToInstanceMap<B>(map);
059  }
060
061  /**
062   * Returns a new builder. The generated builder is equivalent to the builder
063   * created by the {@link Builder} constructor.
064   */
065  public static <B> Builder<B> builder() {
066    return new Builder<B>();
067  }
068
069  /**
070   * A builder for creating immutable class-to-instance maps. Example:
071   * <pre>   {@code
072   *
073   *   static final ImmutableClassToInstanceMap<Handler> HANDLERS =
074   *       new ImmutableClassToInstanceMap.Builder<Handler>()
075   *           .put(FooHandler.class, new FooHandler())
076   *           .put(BarHandler.class, new SubBarHandler())
077   *           .put(Handler.class, new QuuxHandler())
078   *           .build();}</pre>
079   *
080   * <p>After invoking {@link #build()} it is still possible to add more entries
081   * and build again. Thus each map generated by this builder will be a superset
082   * of any map generated before it.
083   *
084   * @since 2.0
085   */
086  public static final class Builder<B> {
087    private final ImmutableMap.Builder<Class<? extends B>, B> mapBuilder = ImmutableMap.builder();
088
089    /**
090     * Associates {@code key} with {@code value} in the built map. Duplicate
091     * keys are not allowed, and will cause {@link #build} to fail.
092     */
093    public <T extends B> Builder<B> put(Class<T> key, T value) {
094      mapBuilder.put(key, value);
095      return this;
096    }
097
098    /**
099     * Associates all of {@code map's} keys and values in the built map.
100     * Duplicate keys are not allowed, and will cause {@link #build} to fail.
101     *
102     * @throws NullPointerException if any key or value in {@code map} is null
103     * @throws ClassCastException if any value is not an instance of the type
104     *     specified by its key
105     */
106    public <T extends B> Builder<B> putAll(Map<? extends Class<? extends T>, ? extends T> map) {
107      for (Entry<? extends Class<? extends T>, ? extends T> entry : map.entrySet()) {
108        Class<? extends T> type = entry.getKey();
109        T value = entry.getValue();
110        mapBuilder.put(type, cast(type, value));
111      }
112      return this;
113    }
114
115    private static <B, T extends B> T cast(Class<T> type, B value) {
116      return Primitives.wrap(type).cast(value);
117    }
118
119    /**
120     * Returns a new immutable class-to-instance map containing the entries
121     * provided to this builder.
122     *
123     * @throws IllegalArgumentException if duplicate keys were added
124     */
125    public ImmutableClassToInstanceMap<B> build() {
126      ImmutableMap<Class<? extends B>, B> map = mapBuilder.build();
127      if (map.isEmpty()) {
128        return of();
129      } else {
130        return new ImmutableClassToInstanceMap<B>(map);
131      }
132    }
133  }
134
135  /**
136   * Returns an immutable map containing the same entries as {@code map}. If
137   * {@code map} somehow contains entries with duplicate keys (for example, if
138   * it is a {@code SortedMap} whose comparator is not <i>consistent with
139   * equals</i>), the results of this method are undefined.
140   *
141   * <p><b>Note:</b> Despite what the method name suggests, if {@code map} is
142   * an {@code ImmutableClassToInstanceMap}, no copy will actually be performed.
143   *
144   * @throws NullPointerException if any key or value in {@code map} is null
145   * @throws ClassCastException if any value is not an instance of the type
146   *     specified by its key
147   */
148  public static <B, S extends B> ImmutableClassToInstanceMap<B> copyOf(
149      Map<? extends Class<? extends S>, ? extends S> map) {
150    if (map instanceof ImmutableClassToInstanceMap) {
151      @SuppressWarnings("unchecked") // covariant casts safe (unmodifiable)
152      // Eclipse won't compile if we cast to the parameterized type.
153      ImmutableClassToInstanceMap<B> cast = (ImmutableClassToInstanceMap) map;
154      return cast;
155    }
156    return new Builder<B>().putAll(map).build();
157  }
158
159  private final ImmutableMap<Class<? extends B>, B> delegate;
160
161  private ImmutableClassToInstanceMap(ImmutableMap<Class<? extends B>, B> delegate) {
162    this.delegate = delegate;
163  }
164
165  @Override
166  protected Map<Class<? extends B>, B> delegate() {
167    return delegate;
168  }
169
170  @Override
171  @SuppressWarnings("unchecked") // value could not get in if not a T
172  @Nullable
173  public <T extends B> T getInstance(Class<T> type) {
174    return (T) delegate.get(checkNotNull(type));
175  }
176
177  /**
178   * Guaranteed to throw an exception and leave the map unmodified.
179   *
180   * @throws UnsupportedOperationException always
181   * @deprecated Unsupported operation.
182   */
183  @Deprecated
184  @Override
185  public <T extends B> T putInstance(Class<T> type, T value) {
186    throw new UnsupportedOperationException();
187  }
188
189  Object readResolve() {
190    return isEmpty() ? of() : this;
191  }
192}