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