/* * #%L * ACS AEM Commons Bundle * %% * Copyright (C) 2013 Adobe * %% * 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. * #L% */ package com.adobe.acs.commons.util; import aQute.bnd.annotation.ProviderType; import org.apache.commons.collections.IteratorUtils; import org.apache.commons.lang.ClassUtils; import org.apache.commons.lang.StringUtils; import org.apache.sling.api.resource.ValueMap; import org.apache.sling.api.wrappers.ValueMapDecorator; import org.apache.sling.commons.json.JSONException; import org.apache.sling.commons.json.JSONObject; import org.joda.time.format.ISODateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; @ProviderType public final class TypeUtil { private static final Logger log = LoggerFactory.getLogger(TypeUtil.class); private static final Pattern JSON_DATE = Pattern.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}[-+]{1}[0-9]{2}[:]{0,1}[0-9]{2}$"); private TypeUtil() { } /** * Turn a even length Array into a Map. The Array is expected to be in the * format: { key1, value1, key2, value2, ... , keyN, valueN } * * @param <T> * @param list * @return */ public static <T> Map<T, T> arrayToMap(T[] list) { final HashMap<T, T> map = new HashMap<T, T>(); if (list == null) { return map; } if (list.length > 0 && (list.length % 2) == 1) { throw new IllegalArgumentException( "Array must be even in length, representing a series of Key, Value pairs."); } for (int i = 0; i < list.length; i++) { map.put(list[i], list[++i]); } return map; } /** * Converts a JSONObject to a simple Map. This will only work properly for * JSONObjects of depth 1. * <p/> * Resulting map will be type'd <String, T> where T is the type of the second parameter (klass) * * @param json * @param klass * @return */ @SuppressWarnings("unchecked") public static <T> Map<String, T> toMap(JSONObject json, Class<T> klass) throws JSONException { final HashMap<String, T> map = new HashMap<String, T>(); final List<?> keys = IteratorUtils.toList(json.keys()); for (final Object key : keys) { final String strKey = key.toString(); final Object obj = json.get(strKey); if (klass.isInstance(obj)) { // Only add objects of this type map.put(strKey, (T) obj); } } return map; } /** * Convenience wrapper for toMap(jsonObj, Object.class). * * @param json * @return */ public static Map<String, Object> toMap(JSONObject json) throws JSONException { return toMap(json, Object.class); } /** * Determines the type of the parameter object. * <p/> * TODO - review this method * * @param object * @param <T> * @return */ @SuppressWarnings({ "unchecked", "PMD.CollapsibleIfStatements" }) public static <T> Class<T> getType(final Object object) { if (object instanceof Double || object instanceof Float) { return (Class<T>) Double.class; } else if (object instanceof Number) { return (Class<T>) Long.class; } else if (object instanceof Boolean) { return (Class<T>) Boolean.class; } else if (object instanceof String) { if (JSON_DATE.matcher((String) object).matches()) { return (Class<T>) Date.class; } } else if(object instanceof Calendar) { return (Class<T>) Calendar.class; } else if(object instanceof Date) { return (Class<T>) Date.class; } return (Class<T>) String.class; } /** * Converts a limited set of String representations to their corresponding Objects * <p/> * Supports * * Double * * Long * * Integer * * Boolean (true/false) * * Dates in string format of ISODateTimeFormat * <p/> * Else, null is returned. * * @param data the String representation of the data * @param klass the target class type of the provided data * @param <T> the target class type of the provided data * @return the derived object representing the data as specified by the klass */ public static <T> T toObjectType(String data, Class<T> klass) { if (Double.class.equals(klass)) { try { return klass.cast(Double.parseDouble(data)); } catch (NumberFormatException ex) { return null; } } else if (Long.class.equals(klass)) { try { return klass.cast(Long.parseLong(data)); } catch (NumberFormatException ex) { return null; } } else if (Integer.class.equals(klass)) { try { return klass.cast(Long.parseLong(data)); } catch (NumberFormatException ex) { return null; } } else if (StringUtils.equalsIgnoreCase("true", data)) { return klass.cast(Boolean.TRUE); } else if (StringUtils.equalsIgnoreCase("false", data)) { return klass.cast(Boolean.FALSE); } else if (JSON_DATE.matcher(data).matches()) { return klass.cast(ISODateTimeFormat.dateTimeParser().parseDateTime(data).toDate()); } else { return klass.cast(data); } } /** * Gets the default string representation of the parameter object. * * @param obj * @param klass * @return */ public static String toString(final Object obj, final Class<?> klass) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { return toString(obj, klass, null); } /** * Gets a custom string representation based on the parameter (0 argument) methodName. * * @param obj * @param klass * @param methodName * @return */ public static String toString(final Object obj, final Class<?> klass, String methodName) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { if (StringUtils.isBlank(methodName)) { methodName = "toString"; } boolean isPrimitiveOrWrapped = obj.getClass().isPrimitive() || ClassUtils.wrapperToPrimitive(obj.getClass()) != null; if (isPrimitiveOrWrapped) { return String.valueOf(obj); } else if (Date.class.equals(klass)) { return ((Date) obj).toString(); } else if (Calendar.class.equals(klass)) { return ((Calendar) obj).getTime().toString(); } else if(isArray(obj)) { return toStringFromArray(obj); } else { Method method = klass.getMethod(methodName); return (String) method.invoke(obj); } } /** * Attempt to create a string representation of an object. * * @param obj the object to represent as a string * @return the string representation of the object */ public static String toString(final Object obj) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { if (obj == null) { return "null"; } boolean isPrimitiveOrWrapped = obj.getClass().isPrimitive() || ClassUtils.wrapperToPrimitive(obj.getClass()) != null; if (isPrimitiveOrWrapped) { return String.valueOf(obj); } else if (obj instanceof Date) { return ((Date) obj).toString(); } else if (obj instanceof Calendar) { return ((Calendar) obj).getTime().toString(); } else if(isArray(obj)) { return toStringFromArray(obj); } else { Method method = obj.getClass().getMethod("toString"); return (String) method.invoke(obj); } } /** * Transforms a Map of <String, ?> into a ValueMap. * * @param map * @return a ValueMap of the parameter map */ public static ValueMap toValueMap(final Map<String, ?> map) { final Map<String, Object> objectMap = new LinkedHashMap<String, Object>(map.size()); for (final Map.Entry<String, ?> entry : map.entrySet()) { objectMap.put(entry.getKey(), entry.getValue()); } return new ValueMapDecorator(objectMap); } private static boolean isArray(final Object obj) { return obj instanceof Object[] || obj instanceof boolean[] || obj instanceof byte[] || obj instanceof short[] || obj instanceof char[] || obj instanceof int[] || obj instanceof long[] || obj instanceof float[] || obj instanceof double[]; } private static String toStringFromArray(final Object obj) { if (obj instanceof Object[]) { return Arrays.deepToString((Object[]) obj); } else if (obj instanceof boolean[]) { return Arrays.toString((boolean[]) obj); } else if (obj instanceof byte[]) { return Arrays.toString((byte[]) obj); } else if (obj instanceof short[]) { return Arrays.toString((short[]) obj); } else if (obj instanceof char[]) { return Arrays.toString((char[]) obj); } else if (obj instanceof int[]) { return Arrays.toString((int[]) obj); } else if (obj instanceof long[]) { return Arrays.toString((long[]) obj); } else if (obj instanceof float[]) { return Arrays.toString((float[]) obj); } else if (obj instanceof double[]) { return Arrays.toString((double[]) obj); } log.warn("Object is not an Array"); return null; } }