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