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