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