1/* 2 * Copyright (C) 2009 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.common.collect; 18 19import static com.google.common.base.Preconditions.checkNotNull; 20import static com.google.common.collect.Iterables.getOnlyElement; 21 22import java.io.Serializable; 23import java.util.Collections; 24import java.util.List; 25import java.util.Map; 26import java.util.Set; 27 28import javax.annotation.Nullable; 29 30/** 31 * GWT emulation of {@link ImmutableMap}. For non sorted maps, it is a thin 32 * wrapper around {@link java.util.Collections#emptyMap()}, {@link 33 * Collections#singletonMap(Object, Object)} and {@link java.util.LinkedHashMap} 34 * for empty, singleton and regular maps respectively. For sorted maps, it's 35 * a thin wrapper around {@link java.util.TreeMap}. 36 * 37 * @see ImmutableSortedMap 38 * 39 * @author Hayward Chan 40 */ 41public abstract class ImmutableMap<K, V> implements Map<K, V>, Serializable { 42 43 private transient final Map<K, V> delegate; 44 45 ImmutableMap() { 46 this.delegate = Collections.emptyMap(); 47 } 48 49 ImmutableMap(Map<? extends K, ? extends V> delegate) { 50 this.delegate = Collections.unmodifiableMap(delegate); 51 } 52 53 @SuppressWarnings("unchecked") 54 ImmutableMap(Entry<? extends K, ? extends V>... entries) { 55 Map<K, V> delegate = Maps.newLinkedHashMap(); 56 for (Entry<? extends K, ? extends V> entry : entries) { 57 K key = checkNotNull(entry.getKey()); 58 V previous = delegate.put(key, checkNotNull(entry.getValue())); 59 if (previous != null) { 60 throw new IllegalArgumentException("duplicate key: " + key); 61 } 62 } 63 this.delegate = Collections.unmodifiableMap(delegate); 64 } 65 66 // Casting to any type is safe because the set will never hold any elements. 67 @SuppressWarnings("unchecked") 68 public static <K, V> ImmutableMap<K, V> of() { 69 return (ImmutableMap<K, V>) EmptyImmutableMap.INSTANCE; 70 } 71 72 public static <K, V> ImmutableMap<K, V> of(K k1, V v1) { 73 return new SingletonImmutableMap<K, V>( 74 checkNotNull(k1), checkNotNull(v1)); 75 } 76 77 public static <K, V> ImmutableMap<K, V> of(K k1, V v1, K k2, V v2) { 78 return new RegularImmutableMap<K, V>(entryOf(k1, v1), entryOf(k2, v2)); 79 } 80 81 public static <K, V> ImmutableMap<K, V> of( 82 K k1, V v1, K k2, V v2, K k3, V v3) { 83 return new RegularImmutableMap<K, V>( 84 entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3)); 85 } 86 87 public static <K, V> ImmutableMap<K, V> of( 88 K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { 89 return new RegularImmutableMap<K, V>( 90 entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4)); 91 } 92 93 public static <K, V> ImmutableMap<K, V> of( 94 K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { 95 return new RegularImmutableMap<K, V>(entryOf(k1, v1), 96 entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4), entryOf(k5, v5)); 97 } 98 99 // looking for of() with > 5 entries? Use the builder instead. 100 101 public static <K, V> Builder<K, V> builder() { 102 return new Builder<K, V>(); 103 } 104 105 static <K, V> Entry<K, V> entryOf(K key, V value) { 106 return Maps.immutableEntry(checkNotNull(key), checkNotNull(value)); 107 } 108 109 public static class Builder<K, V> { 110 final List<Entry<K, V>> entries = Lists.newArrayList(); 111 112 public Builder() {} 113 114 public Builder<K, V> put(K key, V value) { 115 entries.add(entryOf(key, value)); 116 return this; 117 } 118 119 public Builder<K, V> put(Entry<? extends K, ? extends V> entry) { 120 if (entry instanceof ImmutableEntry<?, ?>) { 121 checkNotNull(entry.getKey()); 122 checkNotNull(entry.getValue()); 123 @SuppressWarnings("unchecked") // all supported methods are covariant 124 Entry<K, V> immutableEntry = (Entry<K, V>) entry; 125 entries.add(immutableEntry); 126 } else { 127 entries.add(entryOf((K) entry.getKey(), (V) entry.getValue())); 128 } 129 return this; 130 } 131 132 public Builder<K, V> putAll(Map<? extends K, ? extends V> map) { 133 for (Entry<? extends K, ? extends V> entry : map.entrySet()) { 134 put(entry.getKey(), entry.getValue()); 135 } 136 return this; 137 } 138 139 public ImmutableMap<K, V> build() { 140 return fromEntryList(entries); 141 } 142 143 private static <K, V> ImmutableMap<K, V> fromEntryList( 144 List<Entry<K, V>> entries) { 145 int size = entries.size(); 146 switch (size) { 147 case 0: 148 return of(); 149 case 1: 150 Entry<K, V> entry = getOnlyElement(entries); 151 return new SingletonImmutableMap<K, V>( 152 entry.getKey(), entry.getValue()); 153 default: 154 @SuppressWarnings("unchecked") 155 Entry<K, V>[] entryArray 156 = entries.toArray(new Entry[entries.size()]); 157 return new RegularImmutableMap<K, V>(entryArray); 158 } 159 } 160 } 161 162 public static <K, V> ImmutableMap<K, V> copyOf( 163 Map<? extends K, ? extends V> map) { 164 if ((map instanceof ImmutableMap) && !(map instanceof ImmutableSortedMap)) { 165 @SuppressWarnings("unchecked") // safe since map is not writable 166 ImmutableMap<K, V> kvMap = (ImmutableMap<K, V>) map; 167 return kvMap; 168 } 169 170 int size = map.size(); 171 switch (size) { 172 case 0: 173 return of(); 174 case 1: 175 Entry<? extends K, ? extends V> entry 176 = getOnlyElement(map.entrySet()); 177 return ImmutableMap.<K, V>of(entry.getKey(), entry.getValue()); 178 default: 179 Map<K, V> orderPreservingCopy = Maps.newLinkedHashMap(); 180 for (Entry<? extends K, ? extends V> e : map.entrySet()) { 181 orderPreservingCopy.put( 182 checkNotNull(e.getKey()), checkNotNull(e.getValue())); 183 } 184 return new RegularImmutableMap<K, V>(orderPreservingCopy); 185 } 186 } 187 188 boolean isPartialView(){ 189 return false; 190 } 191 192 public final V put(K k, V v) { 193 throw new UnsupportedOperationException(); 194 } 195 196 public final V remove(Object o) { 197 throw new UnsupportedOperationException(); 198 } 199 200 public final void putAll(Map<? extends K, ? extends V> map) { 201 throw new UnsupportedOperationException(); 202 } 203 204 public final void clear() { 205 throw new UnsupportedOperationException(); 206 } 207 208 public final boolean isEmpty() { 209 return delegate.isEmpty(); 210 } 211 212 public final boolean containsKey(@Nullable Object key) { 213 return Maps.safeContainsKey(delegate, key); 214 } 215 216 public final boolean containsValue(@Nullable Object value) { 217 return delegate.containsValue(value); 218 } 219 220 public final V get(@Nullable Object key) { 221 return (key == null) ? null : Maps.safeGet(delegate, key); 222 } 223 224 private transient ImmutableSet<Entry<K, V>> cachedEntrySet = null; 225 226 public final ImmutableSet<Entry<K, V>> entrySet() { 227 if (cachedEntrySet != null) { 228 return cachedEntrySet; 229 } 230 return cachedEntrySet = ImmutableSet.unsafeDelegate( 231 new ForwardingSet<Entry<K, V>>() { 232 @Override protected Set<Entry<K, V>> delegate() { 233 return delegate.entrySet(); 234 } 235 @Override public boolean contains(Object object) { 236 if (object instanceof Entry<?, ?> 237 && ((Entry<?, ?>) object).getKey() == null) { 238 return false; 239 } 240 try { 241 return super.contains(object); 242 } catch (ClassCastException e) { 243 return false; 244 } 245 } 246 @Override public <T> T[] toArray(T[] array) { 247 T[] result = super.toArray(array); 248 if (size() < result.length) { 249 // It works around a GWT bug where elements after last is not 250 // properly null'ed. 251 result[size()] = null; 252 } 253 return result; 254 } 255 }); 256 } 257 258 private transient ImmutableSet<K> cachedKeySet = null; 259 260 public ImmutableSet<K> keySet() { 261 if (cachedKeySet != null) { 262 return cachedKeySet; 263 } 264 return cachedKeySet = ImmutableSet.unsafeDelegate(delegate.keySet()); 265 } 266 267 private transient ImmutableCollection<V> cachedValues = null; 268 269 public ImmutableCollection<V> values() { 270 if (cachedValues != null) { 271 return cachedValues; 272 } 273 return cachedValues = ImmutableCollection.unsafeDelegate(delegate.values()); 274 } 275 276 public int size() { 277 return delegate.size(); 278 } 279 280 @Override public boolean equals(@Nullable Object object) { 281 return delegate.equals(object); 282 } 283 284 @Override public int hashCode() { 285 return delegate.hashCode(); 286 } 287 288 @Override public String toString() { 289 return delegate.toString(); 290 } 291} 292