001/* 002 * Copyright (C) 2007 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.annotations.J2ktIncompatible; 023import com.google.common.primitives.Primitives; 024import com.google.errorprone.annotations.CanIgnoreReturnValue; 025import java.io.InvalidObjectException; 026import java.io.ObjectInputStream; 027import java.io.Serializable; 028import java.util.HashMap; 029import java.util.Iterator; 030import java.util.LinkedHashMap; 031import java.util.Map; 032import java.util.Set; 033import java.util.Spliterator; 034import javax.annotation.CheckForNull; 035import org.checkerframework.checker.nullness.qual.NonNull; 036import org.checkerframework.checker.nullness.qual.Nullable; 037 038/** 039 * A mutable class-to-instance map backed by an arbitrary user-provided map. See also {@link 040 * ImmutableClassToInstanceMap}. 041 * 042 * <p>See the Guava User Guide article on <a href= 043 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#classtoinstancemap">{@code 044 * ClassToInstanceMap}</a>. 045 * 046 * @author Kevin Bourrillion 047 * @since 2.0 048 */ 049@J2ktIncompatible 050@GwtIncompatible 051@SuppressWarnings("serial") // using writeReplace instead of standard serialization 052@ElementTypesAreNonnullByDefault 053public final class MutableClassToInstanceMap<B extends @Nullable Object> 054 extends ForwardingMap<Class<? extends @NonNull B>, B> 055 implements ClassToInstanceMap<B>, Serializable { 056 057 /** 058 * Returns a new {@code MutableClassToInstanceMap} instance backed by a {@link HashMap} using the 059 * default initial capacity and load factor. 060 */ 061 public static <B extends @Nullable Object> MutableClassToInstanceMap<B> create() { 062 return new MutableClassToInstanceMap<B>(new HashMap<Class<? extends @NonNull B>, B>()); 063 } 064 065 /** 066 * Returns a new {@code MutableClassToInstanceMap} instance backed by a given empty {@code 067 * backingMap}. The caller surrenders control of the backing map, and thus should not allow any 068 * direct references to it to remain accessible. 069 */ 070 public static <B extends @Nullable Object> MutableClassToInstanceMap<B> create( 071 Map<Class<? extends @NonNull B>, B> backingMap) { 072 return new MutableClassToInstanceMap<B>(backingMap); 073 } 074 075 private final Map<Class<? extends @NonNull B>, B> delegate; 076 077 private MutableClassToInstanceMap(Map<Class<? extends @NonNull B>, B> delegate) { 078 this.delegate = checkNotNull(delegate); 079 } 080 081 @Override 082 protected Map<Class<? extends @NonNull B>, B> delegate() { 083 return delegate; 084 } 085 086 /** 087 * Wraps the {@code setValue} implementation of an {@code Entry} to enforce the class constraint. 088 */ 089 private static <B extends @Nullable Object> Entry<Class<? extends @NonNull B>, B> checkedEntry( 090 final Entry<Class<? extends @NonNull B>, B> entry) { 091 return new ForwardingMapEntry<Class<? extends @NonNull B>, B>() { 092 @Override 093 protected Entry<Class<? extends @NonNull B>, B> delegate() { 094 return entry; 095 } 096 097 @Override 098 @ParametricNullness 099 public B setValue(@ParametricNullness B value) { 100 cast(getKey(), value); 101 return super.setValue(value); 102 } 103 }; 104 } 105 106 @Override 107 public Set<Entry<Class<? extends @NonNull B>, B>> entrySet() { 108 return new ForwardingSet<Entry<Class<? extends @NonNull B>, B>>() { 109 110 @Override 111 protected Set<Entry<Class<? extends @NonNull B>, B>> delegate() { 112 return MutableClassToInstanceMap.this.delegate().entrySet(); 113 } 114 115 @Override 116 public Spliterator<Entry<Class<? extends @NonNull B>, B>> spliterator() { 117 return CollectSpliterators.map( 118 delegate().spliterator(), MutableClassToInstanceMap::checkedEntry); 119 } 120 121 @Override 122 public Iterator<Entry<Class<? extends @NonNull B>, B>> iterator() { 123 return new TransformedIterator< 124 Entry<Class<? extends @NonNull B>, B>, Entry<Class<? extends @NonNull B>, B>>( 125 delegate().iterator()) { 126 @Override 127 Entry<Class<? extends @NonNull B>, B> transform( 128 Entry<Class<? extends @NonNull B>, B> from) { 129 return checkedEntry(from); 130 } 131 }; 132 } 133 134 @Override 135 public Object[] toArray() { 136 /* 137 * standardToArray returns `@Nullable Object[]` rather than `Object[]` but only because it 138 * can be used with collections that may contain null. This collection is a collection of 139 * non-null Entry objects (Entry objects that might contain null values but are not 140 * themselves null), so we can treat it as a plain `Object[]`. 141 */ 142 @SuppressWarnings("nullness") 143 Object[] result = standardToArray(); 144 return result; 145 } 146 147 @Override 148 @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations 149 public <T extends @Nullable Object> T[] toArray(T[] array) { 150 return standardToArray(array); 151 } 152 }; 153 } 154 155 @Override 156 @CanIgnoreReturnValue 157 @CheckForNull 158 public B put(Class<? extends @NonNull B> key, @ParametricNullness B value) { 159 cast(key, value); 160 return super.put(key, value); 161 } 162 163 @Override 164 public void putAll(Map<? extends Class<? extends @NonNull B>, ? extends B> map) { 165 Map<Class<? extends @NonNull B>, B> copy = new LinkedHashMap<>(map); 166 for (Entry<? extends Class<? extends @NonNull B>, B> entry : copy.entrySet()) { 167 cast(entry.getKey(), entry.getValue()); 168 } 169 super.putAll(copy); 170 } 171 172 @CanIgnoreReturnValue 173 @Override 174 @CheckForNull 175 public <T extends B> T putInstance(Class<@NonNull T> type, @ParametricNullness T value) { 176 return cast(type, put(type, value)); 177 } 178 179 @Override 180 @CheckForNull 181 public <T extends @NonNull B> T getInstance(Class<T> type) { 182 return cast(type, get(type)); 183 } 184 185 @CanIgnoreReturnValue 186 @CheckForNull 187 private static <T> T cast(Class<T> type, @CheckForNull Object value) { 188 return Primitives.wrap(type).cast(value); 189 } 190 191 private Object writeReplace() { 192 return new SerializedForm<>(delegate()); 193 } 194 195 private void readObject(ObjectInputStream stream) throws InvalidObjectException { 196 throw new InvalidObjectException("Use SerializedForm"); 197 } 198 199 /** Serialized form of the map, to avoid serializing the constraint. */ 200 private static final class SerializedForm<B extends @Nullable Object> implements Serializable { 201 private final Map<Class<? extends @NonNull B>, B> backingMap; 202 203 SerializedForm(Map<Class<? extends @NonNull B>, B> backingMap) { 204 this.backingMap = backingMap; 205 } 206 207 Object readResolve() { 208 return create(backingMap); 209 } 210 211 private static final long serialVersionUID = 0; 212 } 213}