/* * Copyright 2008-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package griffon.util; import griffon.core.editors.ExtendedPropertyEditor; import griffon.core.editors.PropertyEditorResolver; import griffon.exceptions.GriffonException; import griffon.exceptions.TypeConversionException; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.beans.PropertyEditor; import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static griffon.util.GriffonNameUtils.isBlank; import static java.util.Objects.requireNonNull; /** * Utility class for dealing with type conversions. * * @author Andres Almiray * @since 2.0.0 */ public final class TypeUtils { private static final String ERROR_VALUE_NULL = "Argument 'value' must not be null"; private TypeUtils() { // prevent instantiation } public static boolean castToBoolean(@Nonnull Object value) { requireNonNull(value, ERROR_VALUE_NULL); if (value instanceof Boolean) { return (Boolean) value; } return "true".equalsIgnoreCase(String.valueOf(value)); } public static char castToChar(@Nonnull Object value) { requireNonNull(value, ERROR_VALUE_NULL); if (value instanceof Character) { return (Character) value; } return String.valueOf(value).charAt(0); } public static byte castToByte(@Nonnull Object value) { requireNonNull(value, ERROR_VALUE_NULL); if (value instanceof Number) { return ((Number) value).byteValue(); } return Byte.valueOf(String.valueOf(value)); } public static short castToShort(@Nonnull Object value) { requireNonNull(value, ERROR_VALUE_NULL); if (value instanceof Number) { return ((Number) value).shortValue(); } return Short.valueOf(String.valueOf(value)); } public static int castToInt(@Nonnull Object value) { requireNonNull(value, ERROR_VALUE_NULL); if (value instanceof Number) { return ((Number) value).intValue(); } return Integer.valueOf(String.valueOf(value)); } public static long castToLong(@Nonnull Object value) { requireNonNull(value, ERROR_VALUE_NULL); if (value instanceof Number) { return ((Number) value).longValue(); } return Long.valueOf(String.valueOf(value)); } public static float castToFloat(@Nonnull Object value) { requireNonNull(value, ERROR_VALUE_NULL); if (value instanceof Number) { return ((Number) value).floatValue(); } return Float.valueOf(String.valueOf(value)); } public static double castToDouble(@Nonnull Object value) { requireNonNull(value, ERROR_VALUE_NULL); if (value instanceof Number) { return ((Number) value).doubleValue(); } return Double.valueOf(String.valueOf(value)); } public static BigInteger castToBigInteger(@Nonnull Object value) { requireNonNull(value, ERROR_VALUE_NULL); if (value instanceof BigInteger) { return (BigInteger) value; } return new BigInteger(String.valueOf(value)); } public static BigDecimal castToBigDecimal(@Nonnull Object value) { requireNonNull(value, ERROR_VALUE_NULL); if (value instanceof BigDecimal) { return (BigDecimal) value; } return new BigDecimal(String.valueOf(value)); } @Nullable public static Number castToNumber(@Nonnull Object value) { requireNonNull(value, ERROR_VALUE_NULL); if (value instanceof Number) { return (Number) value; } throw new IllegalArgumentException("Don't know how to cast '" + value + "' to " + Number.class.getName()); } public static boolean castToBoolean(@Nullable Object value, boolean defaultValue) { return value == null ? defaultValue : castToBoolean(value); } public static char castToChar(@Nullable Object value, char defaultValue) { return value == null ? defaultValue : castToChar(value); } public static byte castToByte(@Nullable Object value, byte defaultValue) { return value == null ? defaultValue : castToByte(value); } public static short castToShort(@Nullable Object value, short defaultValue) { return value == null ? defaultValue : castToShort(value); } public static int castToInt(@Nullable Object value, int defaultValue) { return value == null ? defaultValue : castToInt(value); } public static long castToLong(@Nullable Object value, long defaultValue) { return value == null ? defaultValue : castToLong(value); } public static float castToFloat(@Nullable Object value, float defaultValue) { return value == null ? defaultValue : castToFloat(value); } public static double castToDouble(@Nullable Object value, double defaultValue) { return value == null ? defaultValue : castToDouble(value); } @Nullable public static Number castToNumber(@Nullable Object value, @Nullable Number defaultValue) { return value == null ? defaultValue : castToNumber(value); } @Nullable public static BigInteger castToBigInteger(@Nullable Object value, @Nullable BigInteger defaultValue) { return value == null ? defaultValue : castToBigInteger(value); } @Nullable public static BigDecimal castToBigDecimal(@Nullable Object value, @Nullable BigDecimal defaultValue) { return value == null ? defaultValue : castToBigDecimal(value); } @Nonnull public static <T> T convertValue(@Nonnull Class<T> targetType, @Nonnull Object value) { return convertValue(targetType, value, null); } @Nonnull @SuppressWarnings("unchecked") public static <T> T convertValue(@Nonnull Class<T> targetType, @Nonnull Object value, @Nullable String format) { requireNonNull(targetType, "Argument 'targetType' must not be null"); requireNonNull(value, ERROR_VALUE_NULL); if (targetType.isAssignableFrom(value.getClass())) { return (T) value; } if (null == format) { if (isBoolean(targetType) && isBoolean(value.getClass())) { return (T) value; } else if (isCharacter(targetType) && isCharacter(value.getClass())) { return (T) value; } else if (isNumber(targetType) && isNumber(value.getClass())) { if (isByte(targetType)) { return (T) ((Byte) castToByte(value)); } else if (isShort(targetType)) { return (T) ((Short) castToShort(value)); } else if (isInteger(targetType)) { return (T) ((Integer) castToInt(value)); } else if (isLong(targetType)) { return (T) ((Long) castToLong(value)); } else if (isFloat(targetType)) { return (T) ((Float) castToFloat(value)); } else if (isDouble(targetType)) { return (T) ((Double) castToDouble(value)); } else if (isBigInteger(targetType)) { return targetType.cast(BigInteger.valueOf(((Number) value).longValue())); } else if (isBigDecimal(targetType)) { return targetType.cast(BigDecimal.valueOf(((Number) value).doubleValue())); } } } PropertyEditor targetEditor = resolveTargetPropertyEditor(targetType, format); if (targetEditor != null) { targetEditor.setValue(value); return (T) targetEditor.getValue(); } throw new TypeConversionException(value, targetType); } @Nullable private static <T> PropertyEditor resolveTargetPropertyEditor(@Nonnull Class<T> targetType, @Nullable String format) { PropertyEditor editor = doResolveTargetPropertyEditor(targetType); if (editor instanceof ExtendedPropertyEditor && !isBlank(format)) { ((ExtendedPropertyEditor) editor).setFormat(format); } return editor; } @Nullable private static <T> PropertyEditor doResolveTargetPropertyEditor(@Nonnull Class<T> targetType) { return PropertyEditorResolver.findEditor(targetType); } public static boolean isBoolean(@Nonnull Class<?> type) { return Boolean.class == type || Boolean.TYPE == type; } public static boolean isCharacter(@Nonnull Class<?> type) { return Character.class == type || Character.TYPE == type; } public static boolean isByte(@Nonnull Class<?> type) { return Byte.class == type || Byte.TYPE == type; } public static boolean isShort(@Nonnull Class<?> type) { return Short.class == type || Short.TYPE == type; } public static boolean isInteger(@Nonnull Class<?> type) { return Integer.class == type || Integer.TYPE == type; } public static boolean isLong(@Nonnull Class<?> type) { return Long.class == type || Long.TYPE == type; } public static boolean isFloat(@Nonnull Class<?> type) { return Float.class == type || Float.TYPE == type; } public static boolean isDouble(@Nonnull Class<?> type) { return Double.class == type || Double.TYPE == type; } public static boolean isBigInteger(@Nonnull Class<?> type) { return BigInteger.class == type; } public static boolean isBigDecimal(@Nonnull Class<?> type) { return BigDecimal.class == type; } public static boolean isNumber(@Nonnull Class<?> type) { return Number.class == type || isByte(type) || isShort(type) || isInteger(type) || isLong(type) || isFloat(type) || isDouble(type) || isBigInteger(type) || isBigDecimal(type); } // == The following methods taken from // org.codehaus.groovy.runtime.DefaultGroovyMethods // org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation public static boolean equals(@Nullable Object left, @Nullable Object right) { if (left == right) return true; if (left == null || right == null) return false; // handle arrays on both sides as special case for efficiency Class<?> leftClass = left.getClass(); Class<?> rightClass = right.getClass(); if (leftClass.isArray() && rightClass.isArray()) { return arrayEqual(left, right); } if (leftClass.isArray() && leftClass.getComponentType().isPrimitive()) { left = primitiveArrayToList(left); } if (rightClass.isArray() && rightClass.getComponentType().isPrimitive()) { right = primitiveArrayToList(right); } if (left instanceof Object[] && right instanceof List) { return equals((Object[]) left, (List) right); } if (left instanceof List && right instanceof Object[]) { return equals((List) left, (Object[]) right); } if (left instanceof List && right instanceof List) { return equals((List) left, (List) right); } if (left instanceof Map.Entry && right instanceof Map.Entry) { Object k1 = ((Map.Entry) left).getKey(); Object k2 = ((Map.Entry) right).getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = ((Map.Entry) left).getValue(); Object v2 = ((Map.Entry) right).getValue(); if (v1 == v2 || (v1 != null && equals(v1, v2))) return true; } return false; } return left.equals(right); } public static boolean arrayEqual(@Nullable Object left, @Nullable Object right) { if (left == null) { return right == null; } if (right == null) { return false; } if (Array.getLength(left) != Array.getLength(right)) { return false; } for (int i = 0; i < Array.getLength(left); i++) { Object l = Array.get(left, i); Object r = Array.get(right, i); if (!equals(l, r)) return false; } return true; } /** * Compare the contents of this array to the contents of the given array. * * @param left an int array * @param right the array being compared * @return true if the contents of both arrays are equal. */ public static boolean equals(int[] left, int[] right) { if (left == null) { return right == null; } if (right == null) { return false; } if (left == right) { return true; } if (left.length != right.length) { return false; } for (int i = 0; i < left.length; i++) { if (left[i] != right[i]) return false; } return true; } /** * Determines if the contents of this array are equal to the * contents of the given list, in the same order. This returns * <code>false</code> if either collection is <code>null</code>. * * @param left an array * @param right the List being compared * @return true if the contents of both collections are equal */ public static boolean equals(Object[] left, List right) { return doEquals(left, right); } /** * Determines if the contents of this list are equal to the * contents of the given array in the same order. This returns * <code>false</code> if either collection is <code>null</code>. * * @param left a List * @param right the Object[] being compared to * @return true if the contents of both collections are equal */ public static boolean equals(List left, Object[] right) { return doEquals(right, left); } private static boolean doEquals(Object[] left, List<?> right) { if (left == null) { return right == null; } if (right == null) { return false; } if (left.length != right.size()) { return false; } for (int i = left.length - 1; i >= 0; i--) { final Object o1 = left[i]; final Object o2 = right.get(i); if (o1 == null) { if (o2 != null) return false; } else if (!equals(o1, o2)) { return false; } } return true; } /** * Compare the contents of two Lists. Order matters. * If numbers exist in the Lists, then they are compared as numbers, * for example 2 == 2L. If both lists are <code>null</code>, the result * is true; rightwise if either list is <code>null</code>, the result * is <code>false</code>. * * @param left a List * @param right the List being compared to * @return boolean <code>true</code> if the contents of both lists are identical, * <code>false</code> rightwise. */ public static <T> boolean equals(List<T> left, List<T> right) { if (left == null) { return right == null; } if (right == null) { return false; } if (left == right) { return true; } if (left.size() != right.size()) { return false; } final Iterator<T> it1 = left.iterator(), it2 = right.iterator(); while (it1.hasNext()) { final T o1 = it1.next(); final T o2 = it2.next(); if (o1 == null) { if (o2 != null) return false; } else if (!equals(o1, o2)) { return false; } } return true; } /** * Compare the contents of two Sets for equality using Groovy's coercion rules. * <p/> * Returns <tt>true</tt> if the two sets have the same size, and every member * of the specified set is contained in this set (or equivalently, every member * of this set is contained in the specified set). * If numbers exist in the sets, then they are compared as numbers, * for example 2 == 2L. If both sets are <code>null</code>, the result * is true; rightwise if either set is <code>null</code>, the result * is <code>false</code>. * * @param left a Set * @param right the Set being compared to * @return <tt>true</tt> if the contents of both sets are identical * @since 1.8.0 */ public static <T> boolean equals(Set<T> left, Set<T> right) { if (left == null) { return right == null; } if (right == null) { return false; } if (left == right) { return true; } if (left.size() != right.size()) { return false; } final Iterator<T> it1 = left.iterator(); Collection<T> rightItems = new HashSet<T>(right); while (it1.hasNext()) { final Object o1 = it1.next(); final Iterator<T> it2 = rightItems.iterator(); T foundItem = null; boolean found = false; while (it2.hasNext() && foundItem == null) { final T o2 = it2.next(); if (equals(o1, o2)) { foundItem = o2; found = true; } } if (!found) return false; rightItems.remove(foundItem); } return rightItems.size() == 0; } /** * Compares two Maps treating coerced numerical values as identical. * <p/> * * @param left this Map * @param right the Map being compared to * @return <tt>true</tt> if the contents of both maps are identical */ public static <K, V> boolean equals(Map<K, V> left, Map<K, V> right) { if (left == null) { return right == null; } if (right == null) { return false; } if (left == right) { return true; } if (left.size() != right.size()) { return false; } if (!left.keySet().equals(right.keySet())) { return false; } for (K key : left.keySet()) { if (!equals(left.get(key), right.get(key))) { return false; } } return true; } /** * Allows conversion of arrays into a mutable List * * @param array an array * @return the array as a List */ public static List primitiveArrayToList(Object array) { int size = Array.getLength(array); List list = new ArrayList(size); for (int i = 0; i < size; i++) { Object item = Array.get(array, i); if (item != null && item.getClass().isArray() && item.getClass().getComponentType().isPrimitive()) { item = primitiveArrayToList(item); } list.add(item); } return list; } private static boolean isValidCharacterString(Object value) { if (value instanceof String) { String s = (String) value; if (s.length() == 1) { return true; } } return false; } @SuppressWarnings("unchecked") public static int compareTo(Object left, Object right) { if (left == right) { return 0; } if (left == null) { return -1; } else if (right == null) { return 1; } if (left instanceof Comparable) { if (left instanceof Number) { if (isValidCharacterString(right)) { return compareTo((Number) left, (Character) castToChar(right)); } if (right instanceof Character || right instanceof Number) { return compareTo((Number) left, castToNumber(right)); } } else if (left instanceof Character) { if (isValidCharacterString(right)) { return compareTo((Character) left, (Character) castToChar(right)); } if (right instanceof Number) { return compareTo((Character) left, (Number) right); } } else if (right instanceof Number) { if (isValidCharacterString(left)) { return compareTo((Character) castToChar(left), (Number) right); } } else if (left instanceof String && right instanceof Character) { return ((String) left).compareTo(right.toString()); } else if (left instanceof CharSequence && right instanceof CharSequence) { return String.valueOf(left).compareTo(String.valueOf(right)); } if (left.getClass().isAssignableFrom(right.getClass()) || (right.getClass() != Object.class && right.getClass().isAssignableFrom(left.getClass())) //GROOVY-4046 || (left instanceof CharSequence && right instanceof CharSequence)) { Comparable comparable = (Comparable) left; return comparable.compareTo(right); } } throw new GriffonException("Cannot compare " + left.getClass().getName() + " with value '" + left + "' and " + right.getClass().getName() + " with value '" + right + "'"); } public static int compareTo(Character left, Number right) { return compareTo(Integer.valueOf(left), right); } public static int compareTo(Number left, Character right) { return compareTo(left, Integer.valueOf(right)); } public static int compareTo(Character left, Character right) { return compareTo(Integer.valueOf(left), right); } public static int compareTo(Number left, Number right) { if (isFloatingPoint(left) || isFloatingPoint(right)) { return Double.compare(left.doubleValue(), right.doubleValue()); } if (isBigDecimal(left) || isBigDecimal(right)) { return castToBigDecimal(left).compareTo(castToBigDecimal(right)); } if (isBigInteger(left) || isBigInteger(right)) { return castToBigInteger(left).compareTo(castToBigInteger(right)); } if (isLong(left) || isLong(right)) { return Long.compare(left.longValue(), right.longValue()); } return Integer.compare(left.intValue(), right.intValue()); } public static boolean isFloatingPoint(Number number) { return number instanceof Double || number instanceof Float; } public static boolean isInteger(Number number) { return number instanceof Integer; } public static boolean isLong(Number number) { return number instanceof Long; } public static boolean isBigDecimal(Number number) { return number instanceof BigDecimal; } public static boolean isBigInteger(Number number) { return number instanceof BigInteger; } }