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