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 org.checkerframework.checker.nullness.compatqual.NullableDecl; 027 028/** 029 * A {@link ClassToInstanceMap} whose contents will never change, with many other important 030 * 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<>(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 created by the {@link 064 * 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 * 073 * <pre>{@code 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(); 080 * }</pre> 081 * 082 * <p>After invoking {@link #build()} it is still possible to add more entries and build again. 083 * Thus each map generated by this builder will be a superset 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 keys are not allowed, 092 * 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. Duplicate keys are not 102 * 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 specified by its key 106 */ 107 @CanIgnoreReturnValue 108 public <T extends B> Builder<B> putAll(Map<? extends Class<? extends T>, ? extends T> map) { 109 for (Entry<? extends Class<? extends T>, ? extends T> entry : map.entrySet()) { 110 Class<? extends T> type = entry.getKey(); 111 T value = entry.getValue(); 112 mapBuilder.put(type, cast(type, value)); 113 } 114 return this; 115 } 116 117 private static <B, T extends B> T cast(Class<T> type, B value) { 118 return Primitives.wrap(type).cast(value); 119 } 120 121 /** 122 * Returns a new immutable class-to-instance map containing the entries provided to this 123 * builder. 124 * 125 * @throws IllegalArgumentException if duplicate keys were added 126 */ 127 public ImmutableClassToInstanceMap<B> build() { 128 ImmutableMap<Class<? extends B>, B> map = mapBuilder.build(); 129 if (map.isEmpty()) { 130 return of(); 131 } else { 132 return new ImmutableClassToInstanceMap<B>(map); 133 } 134 } 135 } 136 137 /** 138 * Returns an immutable map containing the same entries as {@code map}. If {@code map} somehow 139 * contains entries with duplicate keys (for example, if it is a {@code SortedMap} whose 140 * comparator is not <i>consistent with equals</i>), the results of this method are undefined. 141 * 142 * <p><b>Note:</b> Despite what the method name suggests, if {@code map} is an {@code 143 * ImmutableClassToInstanceMap}, no copy will actually be performed. 144 * 145 * @throws NullPointerException if any key or value in {@code map} is null 146 * @throws ClassCastException if any value is not an instance of the type 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 @NullableDecl 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 @CanIgnoreReturnValue 184 @Deprecated 185 @Override 186 public <T extends B> T putInstance(Class<T> type, T value) { 187 throw new UnsupportedOperationException(); 188 } 189 190 Object readResolve() { 191 return isEmpty() ? of() : this; 192 } 193}