All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cedarsoftware.util.convert.MapConversions Maven / Gradle / Ivy

The newest version!
package com.cedarsoftware.util.convert;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.sql.Timestamp;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Calendar;
import java.util.Currency;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;

import com.cedarsoftware.util.ClassUtilities;
import com.cedarsoftware.util.CollectionUtilities;
import com.cedarsoftware.util.ReflectionUtils;
import com.cedarsoftware.util.StringUtilities;

import static com.cedarsoftware.util.convert.Converter.getShortName;

/**
 * @author John DeRegnaucourt ([email protected])
 * @author Kenny Partlow ([email protected])
 *         
* Copyright (c) Cedar Software LLC *

* 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 *

* License *

* 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. */ final class MapConversions { static final String V = "_v"; static final String VALUE = "value"; static final String DATE = "date"; static final String SQL_DATE = "sqlDate"; static final String CALENDAR = "calendar"; static final String TIMESTAMP = "timestamp"; static final String DURATION = "duration"; static final String INSTANT = "instant"; static final String LOCALE = "locale"; static final String MONTH_DAY = "monthDay"; static final String YEAR_MONTH = "yearMonth"; static final String PERIOD = "period"; static final String ZONE_OFFSET = "zoneOffset"; static final String LOCAL_DATE = "localDate"; static final String LOCAL_TIME = "localTime"; static final String LOCAL_DATE_TIME = "localDateTime"; static final String OFFSET_TIME = "offsetTime"; static final String OFFSET_DATE_TIME = "offsetDateTime"; static final String ZONED_DATE_TIME = "zonedDateTime"; static final String ZONE = "zone"; static final String YEAR = "year"; static final String EPOCH_MILLIS = "epochMillis"; static final String MOST_SIG_BITS = "mostSigBits"; static final String LEAST_SIG_BITS = "leastSigBits"; static final String ID = "id"; static final String URI_KEY = "URI"; static final String URL_KEY = "URL"; static final String UUID = "UUID"; static final String CLASS = "class"; static final String MESSAGE = "message"; static final String DETAIL_MESSAGE = "detailMessage"; static final String CAUSE = "cause"; static final String CAUSE_MESSAGE = "causeMessage"; private static final Object NO_MATCH = new Object(); private MapConversions() {} private static final String[] VALUE_KEYS = {VALUE, V}; /** * The common dispatch method. It extracts the value (using getValue) from the map * and, if found, converts it to the target type. Otherwise, it calls fromMap() * to throw an exception. */ private static T dispatch(Object from, Converter converter, Class clazz, String[] keys) { Object value = getValue((Map) from, keys); if (value != NO_MATCH) { return converter.convert(value, clazz); } return fromMap(clazz, keys); } static Object toUUID(Object from, Converter converter) { Map map = (Map) from; Object mostSigBits = map.get(MOST_SIG_BITS); Object leastSigBits = map.get(LEAST_SIG_BITS); if (mostSigBits != null && leastSigBits != null) { long most = converter.convert(mostSigBits, long.class); long least = converter.convert(leastSigBits, long.class); return new UUID(most, least); } return dispatch(from, converter, UUID.class, new String[]{UUID, VALUE, V, MOST_SIG_BITS + ", " + LEAST_SIG_BITS}); } static Byte toByte(Object from, Converter converter) { return dispatch(from, converter, Byte.class, VALUE_KEYS); } static Short toShort(Object from, Converter converter) { return dispatch(from, converter, Short.class, VALUE_KEYS); } static Integer toInt(Object from, Converter converter) { return dispatch(from, converter, Integer.class, VALUE_KEYS); } static Long toLong(Object from, Converter converter) { return dispatch(from, converter, Long.class, VALUE_KEYS); } static Float toFloat(Object from, Converter converter) { return dispatch(from, converter, Float.class, VALUE_KEYS); } static Double toDouble(Object from, Converter converter) { return dispatch(from, converter, Double.class, VALUE_KEYS); } static Boolean toBoolean(Object from, Converter converter) { return dispatch(from, converter, Boolean.class, VALUE_KEYS); } static BigDecimal toBigDecimal(Object from, Converter converter) { return dispatch(from, converter, BigDecimal.class, VALUE_KEYS); } static BigInteger toBigInteger(Object from, Converter converter) { return dispatch(from, converter, BigInteger.class, VALUE_KEYS); } static String toString(Object from, Converter converter) { return dispatch(from, converter, String.class, VALUE_KEYS); } static StringBuffer toStringBuffer(Object from, Converter converter) { return dispatch(from, converter, StringBuffer.class, VALUE_KEYS); } static StringBuilder toStringBuilder(Object from, Converter converter) { return dispatch(from, converter, StringBuilder.class, VALUE_KEYS); } static Character toCharacter(Object from, Converter converter) { return dispatch(from, converter, char.class, VALUE_KEYS); } static AtomicInteger toAtomicInteger(Object from, Converter converter) { return dispatch(from, converter, AtomicInteger.class, VALUE_KEYS); } static AtomicLong toAtomicLong(Object from, Converter converter) { return dispatch(from, converter, AtomicLong.class, VALUE_KEYS); } static AtomicBoolean toAtomicBoolean(Object from, Converter converter) { return dispatch(from, converter, AtomicBoolean.class, VALUE_KEYS); } static Pattern toPattern(Object from, Converter converter) { return dispatch(from, converter, Pattern.class, VALUE_KEYS); } static Currency toCurrency(Object from, Converter converter) { return dispatch(from, converter, Currency.class, VALUE_KEYS); } private static final String[] SQL_DATE_KEYS = {SQL_DATE, VALUE, V, EPOCH_MILLIS}; static java.sql.Date toSqlDate(Object from, Converter converter) { return dispatch(from, converter, java.sql.Date.class, SQL_DATE_KEYS); } private static final String[] DATE_KEYS = {DATE, VALUE, V, EPOCH_MILLIS}; static Date toDate(Object from, Converter converter) { return dispatch(from, converter, Date.class, DATE_KEYS); } private static final String[] TIMESTAMP_KEYS = {TIMESTAMP, VALUE, V, EPOCH_MILLIS}; static Timestamp toTimestamp(Object from, Converter converter) { return dispatch(from, converter, Timestamp.class, TIMESTAMP_KEYS); } // Assuming ZONE_KEYS is defined as follows: private static final String[] ZONE_KEYS = {ZONE, ID, VALUE, V}; static TimeZone toTimeZone(Object from, Converter converter) { return dispatch(from, converter, TimeZone.class, ZONE_KEYS); } private static final String[] CALENDAR_KEYS = {CALENDAR, VALUE, V, EPOCH_MILLIS}; static Calendar toCalendar(Object from, Converter converter) { return dispatch(from, converter, Calendar.class, CALENDAR_KEYS); } private static final String[] LOCALE_KEYS = {LOCALE, VALUE, V}; static Locale toLocale(Object from, Converter converter) { return dispatch(from, converter, Locale.class, LOCALE_KEYS); } private static final String[] LOCAL_DATE_KEYS = {LOCAL_DATE, VALUE, V}; static LocalDate toLocalDate(Object from, Converter converter) { return dispatch(from, converter, LocalDate.class, LOCAL_DATE_KEYS); } private static final String[] LOCAL_TIME_KEYS = {LOCAL_TIME, VALUE, V}; static LocalTime toLocalTime(Object from, Converter converter) { return dispatch(from, converter, LocalTime.class, LOCAL_TIME_KEYS); } private static final String[] LDT_KEYS = {LOCAL_DATE_TIME, VALUE, V, EPOCH_MILLIS}; static LocalDateTime toLocalDateTime(Object from, Converter converter) { return dispatch(from, converter, LocalDateTime.class, LDT_KEYS); } private static final String[] OFFSET_TIME_KEYS = {OFFSET_TIME, VALUE, V}; static OffsetTime toOffsetTime(Object from, Converter converter) { return dispatch(from, converter, OffsetTime.class, OFFSET_TIME_KEYS); } private static final String[] OFFSET_KEYS = {OFFSET_DATE_TIME, VALUE, V, EPOCH_MILLIS}; static OffsetDateTime toOffsetDateTime(Object from, Converter converter) { return dispatch(from, converter, OffsetDateTime.class, OFFSET_KEYS); } private static final String[] ZDT_KEYS = {ZONED_DATE_TIME, VALUE, V, EPOCH_MILLIS}; static ZonedDateTime toZonedDateTime(Object from, Converter converter) { return dispatch(from, converter, ZonedDateTime.class, ZDT_KEYS); } private static final String[] CLASS_KEYS = {CLASS, VALUE, V}; static Class toClass(Object from, Converter converter) { return dispatch(from, converter, Class.class, CLASS_KEYS); } private static final String[] DURATION_KEYS = {DURATION, VALUE, V}; static Duration toDuration(Object from, Converter converter) { return dispatch(from, converter, Duration.class, DURATION_KEYS); } private static final String[] INSTANT_KEYS = {INSTANT, VALUE, V}; static Instant toInstant(Object from, Converter converter) { return dispatch(from, converter, Instant.class, INSTANT_KEYS); } private static final String[] MONTH_DAY_KEYS = {MONTH_DAY, VALUE, V}; static MonthDay toMonthDay(Object from, Converter converter) { return dispatch(from, converter, MonthDay.class, MONTH_DAY_KEYS); } private static final String[] YEAR_MONTH_KEYS = {YEAR_MONTH, VALUE, V}; static YearMonth toYearMonth(Object from, Converter converter) { return dispatch(from, converter, YearMonth.class, YEAR_MONTH_KEYS); } private static final String[] PERIOD_KEYS = {PERIOD, VALUE, V}; static Period toPeriod(Object from, Converter converter) { return dispatch(from, converter, Period.class, PERIOD_KEYS); } static ZoneId toZoneId(Object from, Converter converter) { return dispatch(from, converter, ZoneId.class, ZONE_KEYS); } private static final String[] ZONE_OFFSET_KEYS = {ZONE_OFFSET, VALUE, V}; static ZoneOffset toZoneOffset(Object from, Converter converter) { return dispatch(from, converter, ZoneOffset.class, ZONE_OFFSET_KEYS); } private static final String[] YEAR_KEYS = {YEAR, VALUE, V}; static Year toYear(Object from, Converter converter) { return dispatch(from, converter, Year.class, YEAR_KEYS); } private static final String[] URL_KEYS = {URL_KEY, VALUE, V}; static URL toURL(Object from, Converter converter) { return dispatch(from, converter, URL.class, URL_KEYS); } private static final String[] URI_KEYS = {URI_KEY, VALUE, V}; static URI toURI(Object from, Converter converter) { return dispatch(from, converter, URI.class, URI_KEYS); } /** * Converts a Map to a ByteBuffer by decoding a Base64-encoded string value. * * @param from The Map containing a Base64-encoded string under "value" or "_v" key * @param converter The Converter instance for configuration access * @return A ByteBuffer containing the decoded bytes * @throws IllegalArgumentException If the map is missing required keys or contains invalid data * @throws NullPointerException If the map or its required values are null */ static ByteBuffer toByteBuffer(Object from, Converter converter) { Map map = (Map) from; // Check for the value in preferred order (VALUE first, then V) Object valueObj = map.containsKey(VALUE) ? map.get(VALUE) : map.get(V); if (valueObj == null) { throw new IllegalArgumentException("Unable to convert map to ByteBuffer: Missing or null 'value' or '_v' field"); } if (!(valueObj instanceof String)) { throw new IllegalArgumentException("Unable to convert map to ByteBuffer: Value must be a Base64-encoded String, found: " + valueObj.getClass().getName()); } String base64 = (String) valueObj; try { // Decode the Base64 string into a byte array byte[] decoded = Base64.getDecoder().decode(base64); // Wrap the byte array with a ByteBuffer (creates a backed array that can be gc'd when no longer referenced) return ByteBuffer.wrap(decoded); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Unable to convert map to ByteBuffer: Invalid Base64 encoding", e); } } static CharBuffer toCharBuffer(Object from, Converter converter) { return dispatch(from, converter, CharBuffer.class, VALUE_KEYS); } static Throwable toThrowable(Object from, Converter converter, Class target) { Map map = (Map) from; try { // Make a mutable copy for safety Map namedParams = new LinkedHashMap<>(map); // Handle special case where cause is specified as a class name string Object causeValue = namedParams.get(CAUSE); if (causeValue instanceof String) { String causeClassName = (String) causeValue; String causeMessage = (String) namedParams.get(CAUSE_MESSAGE); if (StringUtilities.hasContent(causeClassName)) { Class causeClass = ClassUtilities.forName(causeClassName, ClassUtilities.getClassLoader(MapConversions.class)); if (causeClass != null) { Map causeMap = new LinkedHashMap<>(); if (causeMessage != null) { causeMap.put(MESSAGE, causeMessage); } // Recursively create the cause Throwable cause = (Throwable) ClassUtilities.newInstance(converter, causeClass, causeMap); namedParams.put(CAUSE, cause); } } // Remove the cause message since we've processed it namedParams.remove(CAUSE_MESSAGE); } else if (causeValue instanceof Map) { // If cause is a Map, recursively convert it Map causeMap = (Map) causeValue; Throwable cause = toThrowable(causeMap, converter, Throwable.class); namedParams.put(CAUSE, cause); } // If cause is already a Throwable, it will be used as-is // Add throwable-specific aliases to improve parameter matching addThrowableAliases(namedParams); // Determine the actual class to instantiate Class classToUse = target; String className = (String) namedParams.get(CLASS); if (StringUtilities.hasContent(className)) { Class specifiedClass = ClassUtilities.forName(className, ClassUtilities.getClassLoader(MapConversions.class)); if (specifiedClass != null && target.isAssignableFrom(specifiedClass)) { classToUse = specifiedClass; } } // Remove metadata that shouldn't be constructor parameters namedParams.remove(CLASS); // Let ClassUtilities.newInstance handle everything! // It will try parameter name matching, handle type conversions, fall back to positional if needed Throwable exception = (Throwable) ClassUtilities.newInstance(converter, classToUse, namedParams); // Clear the stack trace (as required by the original) // Note: ThrowableFactory may set a real stack trace later exception.setStackTrace(new StackTraceElement[0]); return exception; } catch (Exception e) { throw new IllegalArgumentException("Unable to create " + target.getName() + " from map: " + map, e); } } private static void addThrowableAliases(Map namedParams) { // Convert null messages to empty string to match original behavior String[] messageFields = {DETAIL_MESSAGE, MESSAGE, "msg"}; for (String field : messageFields) { if (namedParams.containsKey(field) && namedParams.get(field) == null) { namedParams.put(field, ""); } } // Map detailMessage/message to msg since many constructors use 'msg' as parameter name if (!namedParams.containsKey("msg")) { Object messageValue = null; if (namedParams.containsKey(DETAIL_MESSAGE)) { messageValue = namedParams.get(DETAIL_MESSAGE); } else if (namedParams.containsKey(MESSAGE)) { messageValue = namedParams.get(MESSAGE); } else if (namedParams.containsKey("reason")) { messageValue = namedParams.get("reason"); } else if (namedParams.containsKey("description")) { messageValue = namedParams.get("description"); } if (messageValue != null) { namedParams.put("msg", messageValue); } } // Also ensure message exists if we have detailMessage or other variants if (!namedParams.containsKey(MESSAGE)) { Object messageValue = null; if (namedParams.containsKey(DETAIL_MESSAGE)) { messageValue = namedParams.get(DETAIL_MESSAGE); } else if (namedParams.containsKey("msg")) { messageValue = namedParams.get("msg"); } if (messageValue != null) { namedParams.put(MESSAGE, messageValue); } } // For constructors that use 's' for string message if (!namedParams.containsKey("s")) { Object messageValue = namedParams.get(MESSAGE); if (messageValue == null) messageValue = namedParams.get("msg"); if (messageValue == null) messageValue = namedParams.get(DETAIL_MESSAGE); if (messageValue != null) { namedParams.put("s", messageValue); } } // Handle cause aliases if (!namedParams.containsKey(CAUSE) && namedParams.containsKey("rootCause")) { namedParams.put(CAUSE, namedParams.get("rootCause")); } if (!namedParams.containsKey("throwable") && namedParams.containsKey(CAUSE)) { namedParams.put("throwable", namedParams.get(CAUSE)); } // For constructors that use 't' for throwable if (!namedParams.containsKey("t")) { Object causeValue = namedParams.get(CAUSE); if (causeValue == null) causeValue = namedParams.get("throwable"); if (causeValue == null) causeValue = namedParams.get("rootCause"); if (causeValue != null) { namedParams.put("t", causeValue); } } // Handle boolean parameter aliases if (namedParams.containsKey("suppressionEnabled") && !namedParams.containsKey("enableSuppression")) { namedParams.put("enableSuppression", namedParams.get("suppressionEnabled")); } if (namedParams.containsKey("stackTraceWritable") && !namedParams.containsKey("writableStackTrace")) { namedParams.put("writableStackTrace", namedParams.get("stackTraceWritable")); } } static Map initMap(Object from, Converter converter) { Map map = new LinkedHashMap<>(); map.put(V, from); return map; } /** * Throws an IllegalArgumentException that tells the user which keys are needed. * * @param type the target type for conversion * @param keys one or more arrays of alternative keys (e.g. {"value", "_v"}) * @param target type (unused because the method always throws) * @return nothing—it always throws. */ private static T fromMap(Class type, String[] keys) { // Build the message. StringBuilder builder = new StringBuilder(); builder.append("To convert from Map to '") .append(getShortName(type)) .append("' the map must include: "); builder.append(formatKeys(keys)); builder.append(" as key with associated value."); throw new IllegalArgumentException(builder.toString()); } /** * Formats an array of keys into a natural-language list. *
    *
  • 1 key: [oneKey]
  • *
  • 2 keys: [oneKey] or [twoKey]
  • *
  • 3+ keys: [oneKey], [twoKey], or [threeKey]
  • *
* * @param keys an array of keys * @return a formatted String with each key in square brackets */ private static String formatKeys(String[] keys) { if (keys == null || keys.length == 0) { return ""; } if (keys.length == 1) { return "[" + keys[0] + "]"; } if (keys.length == 2) { return "[" + keys[0] + "] or [" + keys[1] + "]"; } // For 3 or more keys: StringBuilder sb = new StringBuilder(); for (int i = 0; i < keys.length; i++) { if (i > 0) { // Before the last element, prepend ", or " (if it is the last) or ", " (if not) if (i == keys.length - 1) { sb.append(", or "); } else { sb.append(", "); } } sb.append("[").append(keys[i]).append("]"); } return sb.toString(); } private static Object getValue(Map map, String[] keys) { String hadKey = null; Object value; for (String key : keys) { value = map.get(key); // Pick best value (if a String, it has content, if not a String, non-null) if (value != null && (!(value instanceof String) || StringUtilities.hasContent((String) value))) { return value; } // Record if there was an entry for the key if (map.containsKey(key)) { hadKey = key; } } if (hadKey != null) { return map.get(hadKey); } return NO_MATCH; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy