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