1 /*
2 * Copyright (C) 2014 jsonwebtoken.io
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 package io.jsonwebtoken.lang;
17
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.Enumeration;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Properties;
26
27 public final class Collections {
28
29 //for code coverage
30 private static final Collections INSTANCE = new Collections();
31
32 private Collections(){}
33
34 /**
35 * Return <code>true</code> if the supplied Collection is <code>null</code>
36 * or empty. Otherwise, return <code>false</code>.
37 * @param collection the Collection to check
38 * @return whether the given Collection is empty
39 */
40 public static boolean isEmpty(Collection collection) {
41 return (collection == null || collection.isEmpty());
42 }
43
44 /**
45 * Returns the collection's size or {@code 0} if the collection is {@code null}.
46 *
47 * @param collection the collection to check.
48 * @return the collection's size or {@code 0} if the collection is {@code null}.
49 * @since 0.9.2
50 */
51 public static int size(Collection collection) {
52 return collection == null ? 0 : collection.size();
53 }
54
55 /**
56 * Returns the map's size or {@code 0} if the map is {@code null}.
57 *
58 * @param map the map to check
59 * @return the map's size or {@code 0} if the map is {@code null}.
60 * @since 0.9.2
61 */
62 public static int size(Map map) {
63 return map == null ? 0 : map.size();
64 }
65
66 /**
67 * Return <code>true</code> if the supplied Map is <code>null</code>
68 * or empty. Otherwise, return <code>false</code>.
69 * @param map the Map to check
70 * @return whether the given Map is empty
71 */
72 public static boolean isEmpty(Map map) {
73 return (map == null || map.isEmpty());
74 }
75
76 /**
77 * Convert the supplied array into a List. A primitive array gets
78 * converted into a List of the appropriate wrapper type.
79 * <p>A <code>null</code> source value will be converted to an
80 * empty List.
81 * @param source the (potentially primitive) array
82 * @return the converted List result
83 * @see Objects#toObjectArray(Object)
84 */
85 public static List arrayToList(Object source) {
86 return Arrays.asList(Objects.toObjectArray(source));
87 }
88
89 /**
90 * Merge the given array into the given Collection.
91 * @param array the array to merge (may be <code>null</code>)
92 * @param collection the target Collection to merge the array into
93 */
94 @SuppressWarnings("unchecked")
95 public static void mergeArrayIntoCollection(Object array, Collection collection) {
96 if (collection == null) {
97 throw new IllegalArgumentException("Collection must not be null");
98 }
99 Object[] arr = Objects.toObjectArray(array);
100 for (Object elem : arr) {
101 collection.add(elem);
102 }
103 }
104
105 /**
106 * Merge the given Properties instance into the given Map,
107 * copying all properties (key-value pairs) over.
108 * <p>Uses <code>Properties.propertyNames()</code> to even catch
109 * default properties linked into the original Properties instance.
110 * @param props the Properties instance to merge (may be <code>null</code>)
111 * @param map the target Map to merge the properties into
112 */
113 @SuppressWarnings("unchecked")
114 public static void mergePropertiesIntoMap(Properties props, Map map) {
115 if (map == null) {
116 throw new IllegalArgumentException("Map must not be null");
117 }
118 if (props != null) {
119 for (Enumeration en = props.propertyNames(); en.hasMoreElements();) {
120 String key = (String) en.nextElement();
121 Object value = props.getProperty(key);
122 if (value == null) {
123 // Potentially a non-String value...
124 value = props.get(key);
125 }
126 map.put(key, value);
127 }
128 }
129 }
130
131
132 /**
133 * Check whether the given Iterator contains the given element.
134 * @param iterator the Iterator to check
135 * @param element the element to look for
136 * @return <code>true</code> if found, <code>false</code> else
137 */
138 public static boolean contains(Iterator iterator, Object element) {
139 if (iterator != null) {
140 while (iterator.hasNext()) {
141 Object candidate = iterator.next();
142 if (Objects.nullSafeEquals(candidate, element)) {
143 return true;
144 }
145 }
146 }
147 return false;
148 }
149
150 /**
151 * Check whether the given Enumeration contains the given element.
152 * @param enumeration the Enumeration to check
153 * @param element the element to look for
154 * @return <code>true</code> if found, <code>false</code> else
155 */
156 public static boolean contains(Enumeration enumeration, Object element) {
157 if (enumeration != null) {
158 while (enumeration.hasMoreElements()) {
159 Object candidate = enumeration.nextElement();
160 if (Objects.nullSafeEquals(candidate, element)) {
161 return true;
162 }
163 }
164 }
165 return false;
166 }
167
168 /**
169 * Check whether the given Collection contains the given element instance.
170 * <p>Enforces the given instance to be present, rather than returning
171 * <code>true</code> for an equal element as well.
172 * @param collection the Collection to check
173 * @param element the element to look for
174 * @return <code>true</code> if found, <code>false</code> else
175 */
176 public static boolean containsInstance(Collection collection, Object element) {
177 if (collection != null) {
178 for (Object candidate : collection) {
179 if (candidate == element) {
180 return true;
181 }
182 }
183 }
184 return false;
185 }
186
187 /**
188 * Return <code>true</code> if any element in '<code>candidates</code>' is
189 * contained in '<code>source</code>'; otherwise returns <code>false</code>.
190 * @param source the source Collection
191 * @param candidates the candidates to search for
192 * @return whether any of the candidates has been found
193 */
194 public static boolean containsAny(Collection source, Collection candidates) {
195 if (isEmpty(source) || isEmpty(candidates)) {
196 return false;
197 }
198 for (Object candidate : candidates) {
199 if (source.contains(candidate)) {
200 return true;
201 }
202 }
203 return false;
204 }
205
206 /**
207 * Return the first element in '<code>candidates</code>' that is contained in
208 * '<code>source</code>'. If no element in '<code>candidates</code>' is present in
209 * '<code>source</code>' returns <code>null</code>. Iteration order is
210 * {@link Collection} implementation specific.
211 * @param source the source Collection
212 * @param candidates the candidates to search for
213 * @return the first present object, or <code>null</code> if not found
214 */
215 public static Object findFirstMatch(Collection source, Collection candidates) {
216 if (isEmpty(source) || isEmpty(candidates)) {
217 return null;
218 }
219 for (Object candidate : candidates) {
220 if (source.contains(candidate)) {
221 return candidate;
222 }
223 }
224 return null;
225 }
226
227 /**
228 * Find a single value of the given type in the given Collection.
229 * @param collection the Collection to search
230 * @param type the type to look for
231 * @return a value of the given type found if there is a clear match,
232 * or <code>null</code> if none or more than one such value found
233 */
234 @SuppressWarnings("unchecked")
235 public static <T> T findValueOfType(Collection<?> collection, Class<T> type) {
236 if (isEmpty(collection)) {
237 return null;
238 }
239 T value = null;
240 for (Object element : collection) {
241 if (type == null || type.isInstance(element)) {
242 if (value != null) {
243 // More than one value found... no clear single value.
244 return null;
245 }
246 value = (T) element;
247 }
248 }
249 return value;
250 }
251
252 /**
253 * Find a single value of one of the given types in the given Collection:
254 * searching the Collection for a value of the first type, then
255 * searching for a value of the second type, etc.
256 * @param collection the collection to search
257 * @param types the types to look for, in prioritized order
258 * @return a value of one of the given types found if there is a clear match,
259 * or <code>null</code> if none or more than one such value found
260 */
261 public static Object findValueOfType(Collection<?> collection, Class<?>[] types) {
262 if (isEmpty(collection) || Objects.isEmpty(types)) {
263 return null;
264 }
265 for (Class<?> type : types) {
266 Object value = findValueOfType(collection, type);
267 if (value != null) {
268 return value;
269 }
270 }
271 return null;
272 }
273
274 /**
275 * Determine whether the given Collection only contains a single unique object.
276 * @param collection the Collection to check
277 * @return <code>true</code> if the collection contains a single reference or
278 * multiple references to the same instance, <code>false</code> else
279 */
280 public static boolean hasUniqueObject(Collection collection) {
281 if (isEmpty(collection)) {
282 return false;
283 }
284 boolean hasCandidate = false;
285 Object candidate = null;
286 for (Object elem : collection) {
287 if (!hasCandidate) {
288 hasCandidate = true;
289 candidate = elem;
290 }
291 else if (candidate != elem) {
292 return false;
293 }
294 }
295 return true;
296 }
297
298 /**
299 * Find the common element type of the given Collection, if any.
300 * @param collection the Collection to check
301 * @return the common element type, or <code>null</code> if no clear
302 * common type has been found (or the collection was empty)
303 */
304 public static Class<?> findCommonElementType(Collection collection) {
305 if (isEmpty(collection)) {
306 return null;
307 }
308 Class<?> candidate = null;
309 for (Object val : collection) {
310 if (val != null) {
311 if (candidate == null) {
312 candidate = val.getClass();
313 }
314 else if (candidate != val.getClass()) {
315 return null;
316 }
317 }
318 }
319 return candidate;
320 }
321
322 /**
323 * Marshal the elements from the given enumeration into an array of the given type.
324 * Enumeration elements must be assignable to the type of the given array. The array
325 * returned will be a different instance than the array given.
326 */
327 public static <A,E extends A> A[] toArray(Enumeration<E> enumeration, A[] array) {
328 ArrayList<A> elements = new ArrayList<A>();
329 while (enumeration.hasMoreElements()) {
330 elements.add(enumeration.nextElement());
331 }
332 return elements.toArray(array);
333 }
334
335 /**
336 * Adapt an enumeration to an iterator.
337 * @param enumeration the enumeration
338 * @return the iterator
339 */
340 public static <E> Iterator<E> toIterator(Enumeration<E> enumeration) {
341 return new EnumerationIterator<E>(enumeration);
342 }
343
344 /**
345 * Iterator wrapping an Enumeration.
346 */
347 private static class EnumerationIterator<E> implements Iterator<E> {
348
349 private Enumeration<E> enumeration;
350
351 public EnumerationIterator(Enumeration<E> enumeration) {
352 this.enumeration = enumeration;
353 }
354
355 public boolean hasNext() {
356 return this.enumeration.hasMoreElements();
357 }
358
359 public E next() {
360 return this.enumeration.nextElement();
361 }
362
363 public void remove() throws UnsupportedOperationException {
364 throw new UnsupportedOperationException("Not supported");
365 }
366 }
367 }
368
369