View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2014 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.sling.commons.resource;
21  
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.LinkedHashMap;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.TreeMap;
28  
29  import org.apache.sling.api.resource.ValueMap;
30  import org.apache.sling.api.wrappers.ValueMapDecorator;
31  import org.jetbrains.annotations.NotNull;
32  import org.jetbrains.annotations.Nullable;
33  import org.osgi.annotation.versioning.ProviderType;
34  
35  /**
36   * {@link ValueMap} that does not support changing its content.
37   *
38   * <p>
39   * All methods that may change the content will throw a {@link UnsupportedOperationException}.
40   * </p>
41   *
42   * <p>
43   * Static convenience methods provide similar behavior as Guava ImmutableMap variants.
44   * </p>
45   */
46  @ProviderType
47  public final class ImmutableValueMap implements ValueMap {
48  
49    private final ValueMap map;
50  
51    /**
52     * @param map Value map
53     */
54    ImmutableValueMap(@NotNull ValueMap map) {
55      this.map = map;
56    }
57  
58    /**
59     * @param map Map
60     */
61    ImmutableValueMap(@NotNull Map<String, Object> map) {
62      this.map = new ValueMapDecorator(map);
63    }
64  
65    @Override
66    public @Nullable <T> T get(@NotNull String name, @NotNull Class<T> type) {
67      return this.map.get(name, type);
68    }
69  
70    @Override
71    public @NotNull <T> T get(@NotNull String name, @NotNull T defaultValue) {
72      return this.map.get(name, defaultValue);
73    }
74  
75    @Override
76    public int size() {
77      return this.map.size();
78    }
79  
80    @Override
81    public boolean isEmpty() {
82      return this.map.isEmpty();
83    }
84  
85    @Override
86    public boolean containsKey(Object key) {
87      return this.map.containsKey(key);
88    }
89  
90    @Override
91    public boolean containsValue(Object value) {
92      return this.map.containsValue(value);
93    }
94  
95    @Override
96    public Object get(Object key) {
97      return this.map.get(key);
98    }
99  
100   @Override
101   public Set<String> keySet() {
102     return this.map.keySet();
103   }
104 
105   @Override
106   public Collection<Object> values() {
107     return this.map.values();
108   }
109 
110   @Override
111   public Set<Entry<String, Object>> entrySet() {
112     return Collections.unmodifiableSet(this.map.entrySet());
113   }
114 
115   @Override
116   public int hashCode() {
117     return this.map.hashCode();
118   }
119 
120   @Override
121   public boolean equals(Object obj) {
122     if (!(obj instanceof ImmutableValueMap)) {
123       return false;
124     }
125     return this.map.entrySet().equals(((ImmutableValueMap)obj).map.entrySet());
126   }
127 
128   @Override
129   public String toString() {
130     return new TreeMap<>(map).toString();
131   }
132 
133   // mutable operations not supported
134   /**
135    * @deprecated Unsupported operation
136    */
137   @Override
138   @Deprecated(since = "1.0.0")
139   public Object put(String key, Object value) {
140     throw new UnsupportedOperationException();
141   }
142 
143   /**
144    * @deprecated Unsupported operation
145    */
146   @Override
147   @Deprecated(since = "1.0.0")
148   public Object remove(Object key) {
149     throw new UnsupportedOperationException();
150   }
151 
152   /**
153    * @deprecated Unsupported operation
154    */
155   @Override
156   @Deprecated(since = "1.0.0")
157   public void putAll(Map<? extends String, ? extends Object> m) {
158     throw new UnsupportedOperationException();
159   }
160 
161   /**
162    * @deprecated Unsupported operation
163    */
164   @Override
165   @Deprecated(since = "1.0.0")
166   public void clear() {
167     throw new UnsupportedOperationException();
168   }
169 
170 
171   /**
172    * Returns the empty map. This map behaves and performs comparably to {@link Collections#emptyMap}, and is preferable
173    * mainly for consistency
174    * and maintainability of your code.
175    * @return ImmutableValueMap
176    */
177   public static @NotNull ImmutableValueMap of() {
178     return new ImmutableValueMap(EMPTY);
179   }
180 
181   /**
182    * Returns an immutable map containing a single entry. This map behaves and
183    * performs comparably to {@link Collections#singletonMap} but will not accept
184    * a null key or value. It is preferable mainly for consistency and
185    * maintainability of your code.
186    * @param k1 Key 1
187    * @param v1 Value 1
188    * @return ImmutableValueMap
189    */
190   public static @NotNull ImmutableValueMap of(@NotNull String k1, @NotNull Object v1) {
191     Map<String, Object> map = new LinkedHashMap<>();
192     map.put(k1, v1);
193     return new ImmutableValueMap(Collections.unmodifiableMap(map));
194   }
195 
196   /**
197    * Returns an immutable map containing the given entries, in order.
198    * @param k1 Key 1
199    * @param v1 Value 1
200    * @param k2 Key 2
201    * @param v2 Value 2
202    * @return ImmutableValueMap
203    * @throws IllegalArgumentException if duplicate keys are provided
204    */
205   public static @NotNull ImmutableValueMap of(@NotNull String k1, @NotNull Object v1,
206       @NotNull String k2, @NotNull Object v2) {
207     Map<String, Object> map = new LinkedHashMap<>();
208     map.put(k1, v1);
209     map.put(k2, v2);
210     return new ImmutableValueMap(Collections.unmodifiableMap(map));
211   }
212 
213   /**
214    * Returns an immutable map containing the given entries, in order.
215    * @param k1 Key 1
216    * @param v1 Value 1
217    * @param k2 Key 2
218    * @param v2 Value 2
219    * @param k3 Key 3
220    * @param v3 Value 3
221    * @return ImmutableValueMap
222    * @throws IllegalArgumentException if duplicate keys are provided
223    */
224   public static @NotNull ImmutableValueMap of(
225       @NotNull String k1, @NotNull Object v1,
226       @NotNull String k2, @NotNull Object v2,
227       @NotNull String k3, @NotNull Object v3) {
228     Map<String, Object> map = new LinkedHashMap<>();
229     map.put(k1, v1);
230     map.put(k2, v2);
231     map.put(k3, v3);
232     return new ImmutableValueMap(Collections.unmodifiableMap(map));
233   }
234 
235   /**
236    * Returns an immutable map containing the given entries, in order.
237    * @param k1 Key 1
238    * @param v1 Value 1
239    * @param k2 Key 2
240    * @param v2 Value 2
241    * @param k3 Key 3
242    * @param v3 Value 3
243    * @param k4 Key 4
244    * @param v4 Value 4
245    * @return ImmutableValueMap
246    * @throws IllegalArgumentException if duplicate keys are provided
247    */
248   @SuppressWarnings({ "java:S107", "PMD.UseObjectForClearerAPI" })
249   public static @NotNull ImmutableValueMap of(
250       @NotNull String k1, @NotNull Object v1,
251       @NotNull String k2, @NotNull Object v2,
252       @NotNull String k3, @NotNull Object v3,
253       @NotNull String k4, @NotNull Object v4) {
254     Map<String, Object> map = new LinkedHashMap<>();
255     map.put(k1, v1);
256     map.put(k2, v2);
257     map.put(k3, v3);
258     map.put(k4, v4);
259     return new ImmutableValueMap(Collections.unmodifiableMap(map));
260   }
261 
262   /**
263    * Returns an immutable map containing the given entries, in order.
264    * @param k1 Key 1
265    * @param v1 Value 1
266    * @param k2 Key 2
267    * @param v2 Value 2
268    * @param k3 Key 3
269    * @param v3 Value 3
270    * @param k4 Key 4
271    * @param v4 Value 4
272    * @param k5 Key 5
273    * @param v5 Value 5
274    * @return ImmutableValueMap
275    * @throws IllegalArgumentException if duplicate keys are provided
276    */
277   @SuppressWarnings({ "java:S107", "PMD.UseObjectForClearerAPI" })
278   public static ImmutableValueMap of(
279       @NotNull String k1, @NotNull Object v1,
280       @NotNull String k2, @NotNull Object v2,
281       @NotNull String k3, @NotNull Object v3,
282       @NotNull String k4, @NotNull Object v4,
283       @NotNull String k5, @NotNull Object v5) {
284     Map<String, Object> map = new LinkedHashMap<>();
285     map.put(k1, v1);
286     map.put(k2, v2);
287     map.put(k3, v3);
288     map.put(k4, v4);
289     map.put(k5, v5);
290     return new ImmutableValueMap(Collections.unmodifiableMap(map));
291   }
292 
293   // looking for of() with > 5 entries? Use the builder instead.
294 
295   /**
296    * Returns a new builder. The generated builder is equivalent to the builder
297    * created by the {@link Builder} constructor.
298    * @return Builder
299    */
300   public static @NotNull Builder builder() {
301     return new Builder();
302   }
303 
304   /**
305    * Returns an immutable map containing the same entries as {@code map}. If {@code map} somehow contains entries with
306    * duplicate keys (for example, if
307    * it is a {@code SortedMap} whose comparator is not <i>consistent with
308    * equals</i>), the results of this method are undefined.
309    *
310    * <p>
311    * Despite the method name, this method attempts to avoid actually copying the data when it is safe to do so. The
312    * exact circumstances under which a copy will or will not be performed are undocumented and subject to change.
313    * </p>
314    * @param map Map
315    * @return ImmutableValueMap
316    * @throws NullPointerException if any key or value in {@code map} is null
317    */
318   public static @NotNull ImmutableValueMap copyOf(@NotNull Map<String, Object> map) {
319     return new ImmutableValueMap(Collections.unmodifiableMap(map));
320   }
321 
322   /**
323    * Builder interface for {@link ImmutableValueMap}.
324    */
325   public static final class Builder {
326 
327     private final @NotNull Map<String, Object> map = new LinkedHashMap<>();
328 
329     /**
330      * Associates {@code key} with {@code value} in the built map. Duplicate
331      * keys are not allowed, and will cause {@link #build} to fail.
332      * @param key Key
333      * @param value value
334      * @return this
335      */
336     public @NotNull Builder put(@NotNull String key, @NotNull Object value) {
337       map.put(key, value);
338       return this;
339     }
340 
341     /**
342      * Adds the given {@code entry} to the map, making it immutable if
343      * necessary. Duplicate keys are not allowed, and will cause {@link #build} to fail.
344      * @param entry Entry
345      * @return this
346      */
347     public @NotNull Builder put(@NotNull Entry<String, Object> entry) {
348       return put(entry.getKey(), entry.getValue());
349     }
350 
351     /**
352      * Associates all of the given map's keys and values in the built map.
353      * Duplicate keys are not allowed, and will cause {@link #build} to fail.
354      * @param value Value
355      * @return this
356      * @throws NullPointerException if any key or value in {@code map} is null
357      */
358     public @NotNull Builder putAll(@NotNull Map<String, Object> value) {
359       map.putAll(value);
360       return this;
361     }
362 
363     /**
364      * Returns a newly-created immutable map.
365      * @return ImmutableValueMap
366      * @throws IllegalArgumentException if duplicate keys were added
367      */
368     public @NotNull ImmutableValueMap build() {
369       if (map.isEmpty()) {
370         return of();
371       }
372       else {
373         return new ImmutableValueMap(map);
374       }
375     }
376   }
377 
378 }