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}