com.cedarsoftware.util.io.Readers Maven / Gradle / Ivy
package com.cedarsoftware.util.io;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* All special readers for json-io are stored here. Special readers are not needed for handling
* user-defined classes. However, special readers are built/supplied by json-io for many of the
* primitive types and other JDK classes simply to allow for a more concise form.
*
* @author John DeRegnaucourt ([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
*
* 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.*
*/
public class Readers
{
private Readers () {}
private static final String DAYS = "(monday|mon|tuesday|tues|tue|wednesday|wed|thursday|thur|thu|friday|fri|saturday|sat|sunday|sun)"; // longer before shorter matters
private static final String MOS = "(January|Jan|February|Feb|March|Mar|April|Apr|May|June|Jun|July|Jul|August|Aug|September|Sept|Sep|October|Oct|November|Nov|December|Dec)";
private static final Pattern datePattern1 = Pattern.compile("(\\d{4})[./-](\\d{1,2})[./-](\\d{1,2})");
private static final Pattern datePattern2 = Pattern.compile("(\\d{1,2})[./-](\\d{1,2})[./-](\\d{4})");
private static final Pattern datePattern3 = Pattern.compile(MOS + "[ ]*[,]?[ ]*(\\d{1,2})(st|nd|rd|th|)[ ]*[,]?[ ]*(\\d{4})", Pattern.CASE_INSENSITIVE);
private static final Pattern datePattern4 = Pattern.compile("(\\d{1,2})(st|nd|rd|th|)[ ]*[,]?[ ]*" + MOS + "[ ]*[,]?[ ]*(\\d{4})", Pattern.CASE_INSENSITIVE);
private static final Pattern datePattern5 = Pattern.compile("(\\d{4})[ ]*[,]?[ ]*" + MOS + "[ ]*[,]?[ ]*(\\d{1,2})(st|nd|rd|th|)", Pattern.CASE_INSENSITIVE);
private static final Pattern datePattern6 = Pattern.compile(DAYS + "[ ]+" + MOS + "[ ]+(\\d{1,2})[ ]+(\\d{2}:\\d{2}:\\d{2})[ ]+[A-Z]{1,4}\\s+(\\d{4})", Pattern.CASE_INSENSITIVE);
private static final Pattern timePattern1 = Pattern.compile("(\\d{2})[.:](\\d{2})[.:](\\d{2})[.](\\d{1,10})([+-]\\d{2}[:]?\\d{2}|Z)?");
private static final Pattern timePattern2 = Pattern.compile("(\\d{2})[.:](\\d{2})[.:](\\d{2})([+-]\\d{2}[:]?\\d{2}|Z)?");
private static final Pattern timePattern3 = Pattern.compile("(\\d{2})[.:](\\d{2})([+-]\\d{2}[:]?\\d{2}|Z)?");
private static final Pattern dayPattern = Pattern.compile(DAYS, Pattern.CASE_INSENSITIVE);
private static final Map months = new LinkedHashMap();
static
{
// Month name to number map
months.put("jan", "1");
months.put("january", "1");
months.put("feb", "2");
months.put("february", "2");
months.put("mar", "3");
months.put("march", "3");
months.put("apr", "4");
months.put("april", "4");
months.put("may", "5");
months.put("jun", "6");
months.put("june", "6");
months.put("jul", "7");
months.put("july", "7");
months.put("aug", "8");
months.put("august", "8");
months.put("sep", "9");
months.put("sept", "9");
months.put("september", "9");
months.put("oct", "10");
months.put("october", "10");
months.put("nov", "11");
months.put("november", "11");
months.put("dec", "12");
months.put("december", "12");
}
public static class TimeZoneReader implements JsonReader.JsonClassReaderEx
{
public Object read(Object o, Deque> stack, Map args)
{
JsonObject jObj = (JsonObject)o;
Object zone = jObj.get("zone");
if (zone == null)
{
throw new JsonIoException("java.util.TimeZone must specify 'zone' field");
}
jObj.target = TimeZone.getTimeZone((String) zone);
return jObj.target;
}
}
public static class LocaleReader implements JsonReader.JsonClassReaderEx
{
public Object read(Object o, Deque> stack, Map args)
{
JsonObject jObj = (JsonObject) o;
Object language = jObj.get("language");
if (language == null)
{
throw new JsonIoException("java.util.Locale must specify 'language' field");
}
Object country = jObj.get("country");
Object variant = jObj.get("variant");
if (country == null)
{
jObj.target = new Locale((String) language);
return jObj.target;
}
if (variant == null)
{
jObj.target = new Locale((String) language, (String) country);
return jObj.target;
}
jObj.target = new Locale((String) language, (String) country, (String) variant);
return jObj.target;
}
}
public static class CalendarReader implements JsonReader.JsonClassReaderEx
{
public Object read(Object o, Deque> stack, Map args)
{
String time = null;
try
{
JsonObject jObj = (JsonObject) o;
time = (String) jObj.get("time");
if (time == null)
{
throw new JsonIoException("Calendar missing 'time' field");
}
Date date = MetaUtils.dateFormat.get().parse(time);
Class c;
if (jObj.getTarget() != null)
{
c = jObj.getTarget().getClass();
}
else
{
Object type = jObj.type;
c = classForName((String) type);
}
Calendar calendar = (Calendar) newInstance(c, jObj);
calendar.setTime(date);
jObj.setTarget(calendar);
String zone = (String) jObj.get("zone");
if (zone != null)
{
calendar.setTimeZone(TimeZone.getTimeZone(zone));
}
return calendar;
}
catch(Exception e)
{
throw new JsonIoException("Failed to parse calendar, time: " + time);
}
}
}
public static class DateReader implements JsonReader.JsonClassReaderEx
{
public Object read(Object o, Deque> stack, Map args)
{
if (o instanceof Long)
{
return new Date((Long) o);
}
else if (o instanceof String)
{
return parseDate((String) o);
}
else if (o instanceof JsonObject)
{
JsonObject jObj = (JsonObject) o;
Object val = jObj.get("value");
if (val instanceof Long)
{
return new Date((Long) val);
}
else if (val instanceof String)
{
return parseDate((String) val);
}
throw new JsonIoException("Unable to parse date: " + o);
}
else
{
throw new JsonIoException("Unable to parse date, encountered unknown object: " + o);
}
}
private Date parseDate(String dateStr)
{
dateStr = dateStr.trim();
if (dateStr.isEmpty())
{
return null;
}
// Determine which date pattern (Matcher) to use
Matcher matcher = datePattern1.matcher(dateStr);
String year, month = null, day, mon = null, remains;
if (matcher.find())
{
year = matcher.group(1);
month = matcher.group(2);
day = matcher.group(3);
remains = matcher.replaceFirst("");
}
else
{
matcher = datePattern2.matcher(dateStr);
if (matcher.find())
{
month = matcher.group(1);
day = matcher.group(2);
year = matcher.group(3);
remains = matcher.replaceFirst("");
}
else
{
matcher = datePattern3.matcher(dateStr);
if (matcher.find())
{
mon = matcher.group(1);
day = matcher.group(2);
year = matcher.group(4);
remains = matcher.replaceFirst("");
}
else
{
matcher = datePattern4.matcher(dateStr);
if (matcher.find())
{
day = matcher.group(1);
mon = matcher.group(3);
year = matcher.group(4);
remains = matcher.replaceFirst("");
}
else
{
matcher = datePattern5.matcher(dateStr);
if (matcher.find())
{
year = matcher.group(1);
mon = matcher.group(2);
day = matcher.group(3);
remains = matcher.replaceFirst("");
}
else
{
matcher = datePattern6.matcher(dateStr);
if (!matcher.find())
{
throw new JsonIoException("Unable to parse: " + dateStr);
}
year = matcher.group(5);
mon = matcher.group(2);
day = matcher.group(3);
remains = matcher.group(4);
}
}
}
}
}
if (mon != null)
{ // Month will always be in Map, because regex forces this.
month = months.get(mon.trim().toLowerCase());
}
// Determine which date pattern (Matcher) to use
String hour = null, min = null, sec = "00", milli = "0", tz = null;
remains = remains.trim();
matcher = timePattern1.matcher(remains);
if (matcher.find())
{
hour = matcher.group(1);
min = matcher.group(2);
sec = matcher.group(3);
milli = matcher.group(4);
if (matcher.groupCount() > 4)
{
tz = matcher.group(5);
}
}
else
{
matcher = timePattern2.matcher(remains);
if (matcher.find())
{
hour = matcher.group(1);
min = matcher.group(2);
sec = matcher.group(3);
if (matcher.groupCount() > 3)
{
tz = matcher.group(4);
}
}
else
{
matcher = timePattern3.matcher(remains);
if (matcher.find())
{
hour = matcher.group(1);
min = matcher.group(2);
if (matcher.groupCount() > 2)
{
tz = matcher.group(3);
}
}
else
{
matcher = null;
}
}
}
if (matcher != null)
{
remains = matcher.replaceFirst("");
}
// Clear out day of week (mon, tue, wed, ...)
if (remains != null && remains.length() > 0)
{
Matcher dayMatcher = dayPattern.matcher(remains);
if (dayMatcher.find())
{
remains = dayMatcher.replaceFirst("").trim();
}
}
if (remains != null && remains.length() > 0)
{
remains = remains.trim();
if (!remains.equals(",") && (!remains.equals("T")))
{
throw new JsonIoException("Issue parsing data/time, other characters present: " + remains);
}
}
Calendar c = Calendar.getInstance();
c.clear();
if (tz != null)
{
if ("z".equalsIgnoreCase(tz))
{
c.setTimeZone(TimeZone.getTimeZone("GMT"));
}
else
{
c.setTimeZone(TimeZone.getTimeZone("GMT" + tz));
}
}
// Regex prevents these from ever failing to parse
int y = Integer.parseInt(year);
int m = Integer.parseInt(month) - 1; // months are 0-based
int d = Integer.parseInt(day);
if (m < 0 || m > 11)
{
throw new JsonIoException("Month must be between 1 and 12 inclusive, date: " + dateStr);
}
if (d < 1 || d > 31)
{
throw new JsonIoException("Day must be between 1 and 31 inclusive, date: " + dateStr);
}
if (matcher == null)
{ // no [valid] time portion
c.set(y, m, d);
}
else
{
// Regex prevents these from ever failing to parse.
int h = Integer.parseInt(hour);
int mn = Integer.parseInt(min);
int s = Integer.parseInt(sec);
int ms = Integer.parseInt(milli);
if (h > 23)
{
throw new JsonIoException("Hour must be between 0 and 23 inclusive, time: " + dateStr);
}
if (mn > 59)
{
throw new JsonIoException("Minute must be between 0 and 59 inclusive, time: " + dateStr);
}
if (s > 59)
{
throw new JsonIoException("Second must be between 0 and 59 inclusive, time: " + dateStr);
}
// regex enforces millis to number
c.set(y, m, d, h, mn, s);
c.set(Calendar.MILLISECOND, ms);
}
return c.getTime();
}
}
public static class SqlDateReader extends DateReader
{
public Object read(Object o, Deque> stack, Map args)
{
return new java.sql.Date(((Date) super.read(o, stack, args)).getTime());
}
}
public static class StringReader implements JsonReader.JsonClassReaderEx
{
public Object read(Object o, Deque> stack, Map args)
{
if (o instanceof String)
{
return o;
}
if (MetaUtils.isPrimitive(o.getClass()))
{
return o.toString();
}
JsonObject jObj = (JsonObject) o;
if (jObj.containsKey("value"))
{
jObj.target = jObj.get("value");
return jObj.target;
}
throw new JsonIoException("String missing 'value' field");
}
}
public static class ClassReader implements JsonReader.JsonClassReaderEx
{
public Object read(Object o, Deque> stack, Map args)
{
if (o instanceof String)
{
return classForName((String) o);
}
JsonObject jObj = (JsonObject) o;
if (jObj.containsKey("value"))
{
jObj.target = classForName((String) jObj.get("value"));
return jObj.target;
}
throw new JsonIoException("Class missing 'value' field");
}
}
public static class BigIntegerReader implements JsonReader.JsonClassReaderEx
{
public Object read(Object o, Deque> stack, Map args)
{
JsonObject jObj = null;
Object value = o;
if (o instanceof JsonObject)
{
jObj = (JsonObject) o;
if (jObj.containsKey("value"))
{
value = jObj.get("value");
}
else
{
throw new JsonIoException("BigInteger missing 'value' field");
}
}
if (value instanceof JsonObject)
{
JsonObject valueObj = (JsonObject)value;
if ("java.math.BigDecimal".equals(valueObj.type))
{
BigDecimalReader reader = new BigDecimalReader();
value = reader.read(value, stack, args);
}
else if ("java.math.BigInteger".equals(valueObj.type))
{
value = read(value, stack, args);
}
else
{
return bigIntegerFrom(valueObj.get("value"));
}
}
BigInteger x = bigIntegerFrom(value);
if (jObj != null)
{
jObj.target = x;
}
return x;
}
}
/**
* @param value to be converted to BigInteger. Can be a null which will return null. Can be
* BigInteger in which case it will be returned as-is. Can also be String, BigDecimal,
* Boolean (which will be returned as BigInteger.ZERO or .ONE), byte, short, int, long, float,
* or double. If an unknown type is passed in, an exception will be thrown.
* @return a BigInteger from the given input. A best attempt will be made to support
* as many input types as possible. For example, if the input is a Boolean, a BigInteger of
* 1 or 0 will be returned. If the input is a String "", a null will be returned. If the
* input is a Double, Float, or BigDecimal, a BigInteger will be returned that retains the
* integer portion (fractional part is dropped). The input can be a Byte, Short, Integer,
* or Long.
*/
public static BigInteger bigIntegerFrom(Object value)
{
if (value == null)
{
return null;
}
else if (value instanceof BigInteger)
{
return (BigInteger) value;
}
else if (value instanceof String)
{
String s = (String) value;
if ("".equals(s.trim()))
{ // Allows "" to be used to assign null to BigInteger field.
return null;
}
try
{
return new BigInteger(MetaUtils.removeLeadingAndTrailingQuotes(s));
}
catch (Exception e)
{
throw new JsonIoException("Could not parse '" + value + "' as BigInteger.", e);
}
}
else if (value instanceof BigDecimal)
{
BigDecimal bd = (BigDecimal) value;
return bd.toBigInteger();
}
else if (value instanceof Boolean)
{
return (Boolean) value ? BigInteger.ONE : BigInteger.ZERO;
}
else if (value instanceof Double || value instanceof Float)
{
return new BigDecimal(((Number)value).doubleValue()).toBigInteger();
}
else if (value instanceof Long || value instanceof Integer ||
value instanceof Short || value instanceof Byte)
{
return new BigInteger(value.toString());
}
throw new JsonIoException("Could not convert value: " + value.toString() + " to BigInteger.");
}
public static class BigDecimalReader implements JsonReader.JsonClassReaderEx
{
public Object read(Object o, Deque> stack, Map args)
{
JsonObject jObj = null;
Object value = o;
if (o instanceof JsonObject)
{
jObj = (JsonObject) o;
if (jObj.containsKey("value"))
{
value = jObj.get("value");
}
else
{
throw new JsonIoException("BigDecimal missing 'value' field");
}
}
if (value instanceof JsonObject)
{
JsonObject valueObj = (JsonObject)value;
if ("java.math.BigInteger".equals(valueObj.type))
{
BigIntegerReader reader = new BigIntegerReader();
value = reader.read(value, stack, args);
}
else if ("java.math.BigDecimal".equals(valueObj.type))
{
value = read(value, stack, args);
}
else
{
return bigDecimalFrom(valueObj.get("value"));
}
}
BigDecimal x = bigDecimalFrom(value);
if (jObj != null)
{
jObj.target = x;
}
return x;
}
}
/**
* @param value to be converted to BigDecimal. Can be a null which will return null. Can be
* BigDecimal in which case it will be returned as-is. Can also be String, BigInteger,
* Boolean (which will be returned as BigDecimal.ZERO or .ONE), byte, short, int, long, float,
* or double. If an unknown type is passed in, an exception will be thrown.
*
* @return a BigDecimal from the given input. A best attempt will be made to support
* as many input types as possible. For example, if the input is a Boolean, a BigDecimal of
* 1 or 0 will be returned. If the input is a String "", a null will be returned. The input
* can be a Byte, Short, Integer, Long, or BigInteger.
*/
public static BigDecimal bigDecimalFrom(Object value)
{
if (value == null)
{
return null;
}
else if (value instanceof BigDecimal)
{
return (BigDecimal) value;
}
else if (value instanceof String)
{
String s = (String) value;
if ("".equals(s.trim()))
{
return null;
}
try
{
return new BigDecimal(MetaUtils.removeLeadingAndTrailingQuotes(s));
}
catch (Exception e)
{
throw new JsonIoException("Could not parse '" + s + "' as BigDecimal.", e);
}
}
else if (value instanceof BigInteger)
{
return new BigDecimal((BigInteger) value);
}
else if (value instanceof Boolean)
{
return (Boolean) value ? BigDecimal.ONE : BigDecimal.ZERO;
}
else if (value instanceof Long || value instanceof Integer || value instanceof Double ||
value instanceof Short || value instanceof Byte || value instanceof Float)
{
return new BigDecimal(value.toString());
}
throw new JsonIoException("Could not convert value: " + value.toString() + " to BigInteger.");
}
public static class StringBuilderReader implements JsonReader.JsonClassReaderEx
{
public Object read(Object o, Deque> stack, Map args)
{
if (o instanceof String)
{
return new StringBuilder((String) o);
}
JsonObject jObj = (JsonObject) o;
if (jObj.containsKey("value"))
{
jObj.target = new StringBuilder((String) jObj.get("value"));
return jObj.target;
}
throw new JsonIoException("StringBuilder missing 'value' field");
}
}
public static class StringBufferReader implements JsonReader.JsonClassReaderEx
{
public Object read(Object o, Deque> stack, Map args)
{
if (o instanceof String)
{
return new StringBuffer((String) o);
}
JsonObject jObj = (JsonObject) o;
if (jObj.containsKey("value"))
{
jObj.target = new StringBuffer((String) jObj.get("value"));
return jObj.target;
}
throw new JsonIoException("StringBuffer missing 'value' field");
}
}
public static class TimestampReader implements JsonReader.JsonClassReaderEx
{
public Object read(Object o, Deque> stack, Map args)
{
JsonObject jObj = (JsonObject) o;
Object time = jObj.get("time");
if (time == null)
{
throw new JsonIoException("java.sql.Timestamp must specify 'time' field");
}
Object nanos = jObj.get("nanos");
if (nanos == null)
{
jObj.target = new Timestamp(Long.valueOf((String) time));
return jObj.target;
}
Timestamp tstamp = new Timestamp(Long.valueOf((String) time));
tstamp.setNanos(Integer.valueOf((String) nanos));
jObj.target = tstamp;
return jObj.target;
}
}
// ========== Maintain dependency knowledge in once place, down here =========
static Class classForName(String name)
{
return MetaUtils.classForName(name);
}
static Object newInstance(Class c, JsonObject jsonObject)
{
return JsonReader.newInstance(c, jsonObject);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy