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