001 /*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2008-2017 the original author or authors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package griffon.util;
019
020 import griffon.core.editors.ExtendedPropertyEditor;
021 import griffon.core.editors.PropertyEditorResolver;
022 import griffon.exceptions.GriffonException;
023 import griffon.exceptions.TypeConversionException;
024
025 import javax.annotation.Nonnull;
026 import javax.annotation.Nullable;
027 import java.beans.PropertyEditor;
028 import java.lang.reflect.Array;
029 import java.math.BigDecimal;
030 import java.math.BigInteger;
031 import java.util.ArrayList;
032 import java.util.Collection;
033 import java.util.HashSet;
034 import java.util.Iterator;
035 import java.util.List;
036 import java.util.Map;
037 import java.util.Set;
038
039 import static griffon.util.GriffonNameUtils.isNotBlank;
040 import static java.util.Objects.requireNonNull;
041
042 /**
043 * Utility class for dealing with type conversions.
044 *
045 * @author Andres Almiray
046 * @since 2.0.0
047 */
048 public final class TypeUtils {
049
050 private static final String ERROR_VALUE_NULL = "Argument 'value' must not be null";
051
052 private TypeUtils() {
053 // prevent instantiation
054 }
055
056 public static boolean castToBoolean(@Nonnull Object value) {
057 requireNonNull(value, ERROR_VALUE_NULL);
058 if (value instanceof Boolean) {
059 return (Boolean) value;
060 }
061 return "true".equalsIgnoreCase(String.valueOf(value));
062 }
063
064 public static char castToChar(@Nonnull Object value) {
065 requireNonNull(value, ERROR_VALUE_NULL);
066 if (value instanceof Character) {
067 return (Character) value;
068 }
069 return String.valueOf(value).charAt(0);
070 }
071
072 public static byte castToByte(@Nonnull Object value) {
073 requireNonNull(value, ERROR_VALUE_NULL);
074 if (value instanceof Number) {
075 return ((Number) value).byteValue();
076 }
077 return Byte.valueOf(String.valueOf(value));
078 }
079
080 public static short castToShort(@Nonnull Object value) {
081 requireNonNull(value, ERROR_VALUE_NULL);
082 if (value instanceof Number) {
083 return ((Number) value).shortValue();
084 }
085 return Short.valueOf(String.valueOf(value));
086 }
087
088 public static int castToInt(@Nonnull Object value) {
089 requireNonNull(value, ERROR_VALUE_NULL);
090 if (value instanceof Number) {
091 return ((Number) value).intValue();
092 }
093 return Integer.valueOf(String.valueOf(value));
094 }
095
096 public static long castToLong(@Nonnull Object value) {
097 requireNonNull(value, ERROR_VALUE_NULL);
098 if (value instanceof Number) {
099 return ((Number) value).longValue();
100 }
101 return Long.valueOf(String.valueOf(value));
102 }
103
104 public static float castToFloat(@Nonnull Object value) {
105 requireNonNull(value, ERROR_VALUE_NULL);
106 if (value instanceof Number) {
107 return ((Number) value).floatValue();
108 }
109 return Float.valueOf(String.valueOf(value));
110 }
111
112 public static double castToDouble(@Nonnull Object value) {
113 requireNonNull(value, ERROR_VALUE_NULL);
114 if (value instanceof Number) {
115 return ((Number) value).doubleValue();
116 }
117 return Double.valueOf(String.valueOf(value));
118 }
119
120 public static BigInteger castToBigInteger(@Nonnull Object value) {
121 requireNonNull(value, ERROR_VALUE_NULL);
122 if (value instanceof BigInteger) {
123 return (BigInteger) value;
124 }
125 return new BigInteger(String.valueOf(value));
126 }
127
128 public static BigDecimal castToBigDecimal(@Nonnull Object value) {
129 requireNonNull(value, ERROR_VALUE_NULL);
130 if (value instanceof BigDecimal) {
131 return (BigDecimal) value;
132 }
133 return new BigDecimal(String.valueOf(value));
134 }
135
136 @Nullable
137 public static Number castToNumber(@Nonnull Object value) {
138 requireNonNull(value, ERROR_VALUE_NULL);
139 if (value instanceof Number) {
140 return (Number) value;
141 }
142 throw new IllegalArgumentException("Don't know how to cast '" + value + "' to " + Number.class.getName());
143 }
144
145 public static boolean castToBoolean(@Nullable Object value, boolean defaultValue) {
146 return value == null ? defaultValue : castToBoolean(value);
147 }
148
149 public static char castToChar(@Nullable Object value, char defaultValue) {
150 return value == null ? defaultValue : castToChar(value);
151 }
152
153 public static byte castToByte(@Nullable Object value, byte defaultValue) {
154 return value == null ? defaultValue : castToByte(value);
155 }
156
157 public static short castToShort(@Nullable Object value, short defaultValue) {
158 return value == null ? defaultValue : castToShort(value);
159 }
160
161 public static int castToInt(@Nullable Object value, int defaultValue) {
162 return value == null ? defaultValue : castToInt(value);
163 }
164
165 public static long castToLong(@Nullable Object value, long defaultValue) {
166 return value == null ? defaultValue : castToLong(value);
167 }
168
169 public static float castToFloat(@Nullable Object value, float defaultValue) {
170 return value == null ? defaultValue : castToFloat(value);
171 }
172
173 public static double castToDouble(@Nullable Object value, double defaultValue) {
174 return value == null ? defaultValue : castToDouble(value);
175 }
176
177 @Nullable
178 public static Number castToNumber(@Nullable Object value, @Nullable Number defaultValue) {
179 return value == null ? defaultValue : castToNumber(value);
180 }
181
182 @Nullable
183 public static BigInteger castToBigInteger(@Nullable Object value, @Nullable BigInteger defaultValue) {
184 return value == null ? defaultValue : castToBigInteger(value);
185 }
186
187 @Nullable
188 public static BigDecimal castToBigDecimal(@Nullable Object value, @Nullable BigDecimal defaultValue) {
189 return value == null ? defaultValue : castToBigDecimal(value);
190 }
191
192 @Nonnull
193 public static <T> T convertValue(@Nonnull Class<T> targetType, @Nonnull Object value) {
194 return convertValue(targetType, value, null);
195 }
196
197 @Nonnull
198 @SuppressWarnings("unchecked")
199 public static <T> T convertValue(@Nonnull Class<T> targetType, @Nonnull Object value, @Nullable String format) {
200 requireNonNull(targetType, "Argument 'targetType' must not be null");
201 requireNonNull(value, ERROR_VALUE_NULL);
202 if (targetType.isAssignableFrom(value.getClass())) {
203 return (T) value;
204 }
205
206 if (null == format) {
207 if (isBoolean(targetType) && isBoolean(value.getClass())) {
208 return (T) value;
209 } else if (isCharacter(targetType) && isCharacter(value.getClass())) {
210 return (T) value;
211 } else if (isNumber(targetType) && isNumber(value.getClass())) {
212 if (isByte(targetType)) {
213 return (T) ((Byte) castToByte(value));
214 } else if (isShort(targetType)) {
215 return (T) ((Short) castToShort(value));
216 } else if (isInteger(targetType)) {
217 return (T) ((Integer) castToInt(value));
218 } else if (isLong(targetType)) {
219 return (T) ((Long) castToLong(value));
220 } else if (isFloat(targetType)) {
221 return (T) ((Float) castToFloat(value));
222 } else if (isDouble(targetType)) {
223 return (T) ((Double) castToDouble(value));
224 } else if (isBigInteger(targetType)) {
225 return targetType.cast(BigInteger.valueOf(((Number) value).longValue()));
226 } else if (isBigDecimal(targetType)) {
227 return targetType.cast(BigDecimal.valueOf(((Number) value).doubleValue()));
228 }
229 }
230 }
231
232 PropertyEditor targetEditor = resolveTargetPropertyEditor(targetType, format);
233 if (targetEditor != null) {
234 targetEditor.setValue(value);
235 return (T) targetEditor.getValue();
236 }
237
238 throw new TypeConversionException(value, targetType);
239 }
240
241 @Nullable
242 private static <T> PropertyEditor resolveTargetPropertyEditor(@Nonnull Class<T> targetType, @Nullable String format) {
243 PropertyEditor editor = doResolveTargetPropertyEditor(targetType);
244 if (editor instanceof ExtendedPropertyEditor && isNotBlank(format)) {
245 ((ExtendedPropertyEditor) editor).setFormat(format);
246 }
247 return editor;
248 }
249
250 @Nullable
251 private static <T> PropertyEditor doResolveTargetPropertyEditor(@Nonnull Class<T> targetType) {
252 return PropertyEditorResolver.findEditor(targetType);
253 }
254
255 public static boolean isBoolean(@Nonnull Class<?> type) {
256 return Boolean.class == type || Boolean.TYPE == type;
257 }
258
259 public static boolean isCharacter(@Nonnull Class<?> type) {
260 return Character.class == type || Character.TYPE == type;
261 }
262
263 public static boolean isByte(@Nonnull Class<?> type) {
264 return Byte.class == type || Byte.TYPE == type;
265 }
266
267 public static boolean isShort(@Nonnull Class<?> type) {
268 return Short.class == type || Short.TYPE == type;
269 }
270
271 public static boolean isInteger(@Nonnull Class<?> type) {
272 return Integer.class == type || Integer.TYPE == type;
273 }
274
275 public static boolean isLong(@Nonnull Class<?> type) {
276 return Long.class == type || Long.TYPE == type;
277 }
278
279 public static boolean isFloat(@Nonnull Class<?> type) {
280 return Float.class == type || Float.TYPE == type;
281 }
282
283 public static boolean isDouble(@Nonnull Class<?> type) {
284 return Double.class == type || Double.TYPE == type;
285 }
286
287 public static boolean isBigInteger(@Nonnull Class<?> type) {
288 return BigInteger.class == type;
289 }
290
291 public static boolean isBigDecimal(@Nonnull Class<?> type) {
292 return BigDecimal.class == type;
293 }
294
295 public static boolean isNumber(@Nonnull Class<?> type) {
296 return Number.class == type ||
297 isByte(type) ||
298 isShort(type) ||
299 isInteger(type) ||
300 isLong(type) ||
301 isFloat(type) ||
302 isDouble(type) ||
303 isBigInteger(type) ||
304 isBigDecimal(type);
305 }
306
307 // == The following methods taken from
308 // org.codehaus.groovy.runtime.DefaultGroovyMethods
309 // org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation
310
311 public static boolean equals(@Nullable Object left, @Nullable Object right) {
312 if (left == right) { return true; }
313 if (left == null || right == null) { return false; }
314
315 // handle arrays on both sides as special case for efficiency
316 Class<?> leftClass = left.getClass();
317 Class<?> rightClass = right.getClass();
318 if (leftClass.isArray() && rightClass.isArray()) {
319 return arrayEqual(left, right);
320 }
321 if (leftClass.isArray() && leftClass.getComponentType().isPrimitive()) {
322 left = primitiveArrayToList(left);
323 }
324 if (rightClass.isArray() && rightClass.getComponentType().isPrimitive()) {
325 right = primitiveArrayToList(right);
326 }
327 if (left instanceof Object[] && right instanceof List) {
328 return equals((Object[]) left, (List) right);
329 }
330 if (left instanceof List && right instanceof Object[]) {
331 return equals((List) left, (Object[]) right);
332 }
333 if (left instanceof List && right instanceof List) {
334 return equals((List) left, (List) right);
335 }
336 if (left instanceof Map.Entry && right instanceof Map.Entry) {
337 Object k1 = ((Map.Entry) left).getKey();
338 Object k2 = ((Map.Entry) right).getKey();
339 if (k1 == k2 || (k1 != null && k1.equals(k2))) {
340 Object v1 = ((Map.Entry) left).getValue();
341 Object v2 = ((Map.Entry) right).getValue();
342 if (v1 == v2 || (v1 != null && equals(v1, v2))) { return true; }
343 }
344 return false;
345 }
346
347 return left.equals(right);
348 }
349
350 public static boolean arrayEqual(@Nullable Object left, @Nullable Object right) {
351 if (left == null) {
352 return right == null;
353 }
354 if (right == null) {
355 return false;
356 }
357 if (Array.getLength(left) != Array.getLength(right)) {
358 return false;
359 }
360 for (int i = 0; i < Array.getLength(left); i++) {
361 Object l = Array.get(left, i);
362 Object r = Array.get(right, i);
363 if (!equals(l, r)) { return false; }
364 }
365 return true;
366 }
367
368 /**
369 * Compare the contents of this array to the contents of the given array.
370 *
371 * @param left an int array
372 * @param right the array being compared
373 *
374 * @return true if the contents of both arrays are equal.
375 */
376 public static boolean equals(int[] left, int[] right) {
377 if (left == null) {
378 return right == null;
379 }
380 if (right == null) {
381 return false;
382 }
383 if (left == right) {
384 return true;
385 }
386 if (left.length != right.length) {
387 return false;
388 }
389 for (int i = 0; i < left.length; i++) {
390 if (left[i] != right[i]) { return false; }
391 }
392 return true;
393 }
394
395 /**
396 * Determines if the contents of this array are equal to the
397 * contents of the given list, in the same order. This returns
398 * <code>false</code> if either collection is <code>null</code>.
399 *
400 * @param left an array
401 * @param right the List being compared
402 *
403 * @return true if the contents of both collections are equal
404 */
405 public static boolean equals(Object[] left, List right) {
406 return doEquals(left, right);
407 }
408
409 /**
410 * Determines if the contents of this list are equal to the
411 * contents of the given array in the same order. This returns
412 * <code>false</code> if either collection is <code>null</code>.
413 *
414 * @param left a List
415 * @param right the Object[] being compared to
416 *
417 * @return true if the contents of both collections are equal
418 */
419 public static boolean equals(List left, Object[] right) {
420 return doEquals(right, left);
421 }
422
423 private static boolean doEquals(Object[] left, List<?> right) {
424 if (left == null) {
425 return right == null;
426 }
427 if (right == null) {
428 return false;
429 }
430 if (left.length != right.size()) {
431 return false;
432 }
433 for (int i = left.length - 1; i >= 0; i--) {
434 final Object o1 = left[i];
435 final Object o2 = right.get(i);
436 if (o1 == null) {
437 if (o2 != null) { return false; }
438 } else if (!equals(o1, o2)) {
439 return false;
440 }
441 }
442 return true;
443 }
444
445 /**
446 * Compare the contents of two Lists. Order matters.
447 * If numbers exist in the Lists, then they are compared as numbers,
448 * for example 2 == 2L. If both lists are <code>null</code>, the result
449 * is true; rightwise if either list is <code>null</code>, the result
450 * is <code>false</code>.
451 *
452 * @param left a List
453 * @param right the List being compared to
454 *
455 * @return boolean <code>true</code> if the contents of both lists are identical,
456 * <code>false</code> rightwise.
457 */
458 public static <T> boolean equals(List<T> left, List<T> right) {
459 if (left == null) {
460 return right == null;
461 }
462 if (right == null) {
463 return false;
464 }
465 if (left == right) {
466 return true;
467 }
468 if (left.size() != right.size()) {
469 return false;
470 }
471 final Iterator<T> it1 = left.iterator(), it2 = right.iterator();
472 while (it1.hasNext()) {
473 final T o1 = it1.next();
474 final T o2 = it2.next();
475 if (o1 == null) {
476 if (o2 != null) { return false; }
477 } else if (!equals(o1, o2)) {
478 return false;
479 }
480 }
481 return true;
482 }
483
484 /**
485 * Compare the contents of two Sets for equality using Groovy's coercion rules.
486 * <p/>
487 * Returns <tt>true</tt> if the two sets have the same size, and every member
488 * of the specified set is contained in this set (or equivalently, every member
489 * of this set is contained in the specified set).
490 * If numbers exist in the sets, then they are compared as numbers,
491 * for example 2 == 2L. If both sets are <code>null</code>, the result
492 * is true; rightwise if either set is <code>null</code>, the result
493 * is <code>false</code>.
494 *
495 * @param left a Set
496 * @param right the Set being compared to
497 *
498 * @return <tt>true</tt> if the contents of both sets are identical
499 *
500 * @since 1.8.0
501 */
502 public static <T> boolean equals(Set<T> left, Set<T> right) {
503 if (left == null) {
504 return right == null;
505 }
506 if (right == null) {
507 return false;
508 }
509 if (left == right) {
510 return true;
511 }
512 if (left.size() != right.size()) {
513 return false;
514 }
515 final Iterator<T> it1 = left.iterator();
516 Collection<T> rightItems = new HashSet<T>(right);
517 while (it1.hasNext()) {
518 final Object o1 = it1.next();
519 final Iterator<T> it2 = rightItems.iterator();
520 T foundItem = null;
521 boolean found = false;
522 while (it2.hasNext() && foundItem == null) {
523 final T o2 = it2.next();
524 if (equals(o1, o2)) {
525 foundItem = o2;
526 found = true;
527 }
528 }
529 if (!found) { return false; }
530 rightItems.remove(foundItem);
531 }
532 return rightItems.isEmpty();
533 }
534
535
536 /**
537 * Compares two Maps treating coerced numerical values as identical.
538 * <p/>
539 *
540 * @param left this Map
541 * @param right the Map being compared to
542 *
543 * @return <tt>true</tt> if the contents of both maps are identical
544 */
545 public static <K, V> boolean equals(Map<K, V> left, Map<K, V> right) {
546 if (left == null) {
547 return right == null;
548 }
549 if (right == null) {
550 return false;
551 }
552 if (left == right) {
553 return true;
554 }
555 if (left.size() != right.size()) {
556 return false;
557 }
558 if (!left.keySet().equals(right.keySet())) {
559 return false;
560 }
561 for (K key : left.keySet()) {
562 if (!equals(left.get(key), right.get(key))) {
563 return false;
564 }
565 }
566 return true;
567 }
568
569 /**
570 * Allows conversion of arrays into a mutable List
571 *
572 * @param array an array
573 *
574 * @return the array as a List
575 */
576 public static List primitiveArrayToList(Object array) {
577 int size = Array.getLength(array);
578 List list = new ArrayList(size);
579 for (int i = 0; i < size; i++) {
580 Object item = Array.get(array, i);
581 if (item != null && item.getClass().isArray() && item.getClass().getComponentType().isPrimitive()) {
582 item = primitiveArrayToList(item);
583 }
584 list.add(item);
585 }
586 return list;
587 }
588
589 private static boolean isValidCharacterString(Object value) {
590 if (value instanceof String) {
591 String s = (String) value;
592 if (s.length() == 1) {
593 return true;
594 }
595 }
596 return false;
597 }
598
599 @SuppressWarnings("unchecked")
600 public static int compareTo(Object left, Object right) {
601 if (left == right) {
602 return 0;
603 }
604 if (left == null) {
605 return -1;
606 } else if (right == null) {
607 return 1;
608 }
609 if (left instanceof Comparable) {
610 if (left instanceof Number) {
611 if (isValidCharacterString(right)) {
612 return compareTo((Number) left, (Character) castToChar(right));
613 }
614 if (right instanceof Character || right instanceof Number) {
615 return compareTo((Number) left, castToNumber(right));
616 }
617 } else if (left instanceof Character) {
618 if (isValidCharacterString(right)) {
619 return compareTo((Character) left, (Character) castToChar(right));
620 }
621 if (right instanceof Number) {
622 return compareTo((Character) left, (Number) right);
623 }
624 } else if (right instanceof Number) {
625 if (isValidCharacterString(left)) {
626 return compareTo((Character) castToChar(left), (Number) right);
627 }
628 } else if (left instanceof String && right instanceof Character) {
629 return ((String) left).compareTo(right.toString());
630 } else if (left instanceof CharSequence && right instanceof CharSequence) {
631 return String.valueOf(left).compareTo(String.valueOf(right));
632 }
633 if (left.getClass().isAssignableFrom(right.getClass())
634 || (right.getClass() != Object.class && right.getClass().isAssignableFrom(left.getClass())) //GROOVY-4046
635 || (left instanceof CharSequence && right instanceof CharSequence)) {
636 Comparable comparable = (Comparable) left;
637 return comparable.compareTo(right);
638 }
639 }
640
641 throw new GriffonException("Cannot compare " + left.getClass().getName() + " with value '" +
642 left + "' and " + right.getClass().getName() + " with value '" + right + "'");
643 }
644
645 public static int compareTo(Character left, Number right) {
646 return compareTo(Integer.valueOf(left), right);
647 }
648
649 public static int compareTo(Number left, Character right) {
650 return compareTo(left, Integer.valueOf(right));
651 }
652
653 public static int compareTo(Character left, Character right) {
654 return compareTo(Integer.valueOf(left), right);
655 }
656
657 public static int compareTo(Number left, Number right) {
658 if (isFloatingPoint(left) || isFloatingPoint(right)) {
659 return Double.compare(left.doubleValue(), right.doubleValue());
660 }
661 if (isBigDecimal(left) || isBigDecimal(right)) {
662 return castToBigDecimal(left).compareTo(castToBigDecimal(right));
663 }
664 if (isBigInteger(left) || isBigInteger(right)) {
665 return castToBigInteger(left).compareTo(castToBigInteger(right));
666 }
667 if (isLong(left) || isLong(right)) {
668 return Long.compare(left.longValue(), right.longValue());
669 }
670 return Integer.compare(left.intValue(), right.intValue());
671 }
672
673 public static boolean isFloatingPoint(Number number) {
674 return number instanceof Double || number instanceof Float;
675 }
676
677 public static boolean isInteger(Number number) {
678 return number instanceof Integer;
679 }
680
681 public static boolean isLong(Number number) {
682 return number instanceof Long;
683 }
684
685 public static boolean isBigDecimal(Number number) {
686 return number instanceof BigDecimal;
687 }
688
689 public static boolean isBigInteger(Number number) {
690 return number instanceof BigInteger;
691 }
692 }
|