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