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