ma.vi.base.lang.Literal Maven / Gradle / Ivy
Show all versions of com.vikmad.base Show documentation
/*
* Copyright (c) 2016 Vikash Madhow
*/
package ma.vi.base.lang;
import ma.vi.base.reflect.Classes;
import ma.vi.base.string.Escape;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.*;
import static com.google.common.base.Preconditions.checkArgument;
import static ma.vi.base.lang.Errors.unchecked;
import static ma.vi.base.reflect.Classes.componentType;
/**
*
* A literal is a value (object or primitive) which can be represented completely
* as a piece of text (ideally small and easily human-readable) and which can be
* reconstructed exactly from such a text representation. Values which fit this
* informal definition includes primitives and their wrappers, strings and dates.
*
*
*
* Literals can be saved as simple string values for text serialization instead
* of as references.
*
*
*
* A stronger definition of a literal is any value which cannot refer, directly or
* transitively, to itself, thus its representation as a graph will always be acyclic.
* With such a definition any object which does not contain a reference to itself
* can be made into a literal. This is in line with text representations such as XML
* which can be used to represent any object graph which does not have cyclic
* references. However, if applied completely, such a definition would convert
* a large number of objects to a single text form which would defeat the goal
* of the Mapper to provide a representation-independent intermediate form of an
* object graph.
*
*
*
* The above definition allows for arrays of literals to be treated as literals and,
* since an array of literal is a literal, both single-dimensional and multi-dimensional
* arrays of literals are literals. Such arrays are literalized as '[i0, i1, ...., in]'
* with the text of each item escaped where necessary to not conflict with the surrounding
* square braces and the item separator (,).
*
*
* @param The type of values that this is a literal for.
* @author Vikash Madhow ([email protected])
*/
public interface Literal {
/**
* Construct the value from its string representation.
*/
T toValue(String repr);
/**
* Returns the string representation of the value.
*/
String toText(T value);
/**
* The default representation of null as text used when literalising arrays of literal types
* and serializing objects to text.
*/
String NULL_LITERAL = "\\N";
/**
* Returns the Literal object for literalizing value of the specified class, or null
* if the class is not a literal. Literals for unknown literal classes should first be
* registered through {@link Register#add(Class, Literal)} for this method to return
* true for them.
*/
static Literal literal(Class cls) {
return Register.get(cls);
}
/**
* Returns true if the class is a literal type. Primitive types and their wrappers,
* enums, {@link String}, {@link Number} and {@link Date} are literals, and
* so are any class for which a has been registered in the the Literal register
* ({@link Register}).
*/
static boolean isLiteral(Class> cls) {
return Register.isLiteral(cls);
}
/**
* This is a convenience method equivalent to getting the literal for the class
* through {@link #literal(Class)} and using its {@link #toValue(String)}
* method to get reconstruct the value from the specified string representation.
*/
static T toValue(Class cls, String repr) {
return literal(cls).toValue(repr);
}
/**
* This is a convenience method equivalent to getting the literal for the class
* through {@link #literal(Class)} and using its {@link #toText(Object)}}
* method to get the string representation for specified value.
*/
static String toText(Class cls, T value) {
return literal(cls).toText(value);
}
/**
* Literalize nulls as the {@link #NULL_LITERAL} so that subclasses need only
* code for the non-null cases.
*/
abstract class NullableLiteral implements Literal {
@Override
public T toValue(String repr) {
return repr == null || repr.equals(NULL_LITERAL) ? null : toValueNonNull(repr);
}
@Override
public String toText(T value) {
return value == null ? NULL_LITERAL : toTextNonNull(value);
}
/**
* Called to reconstruct the value from its string representation when
* the later is non-null.
*/
protected abstract T toValueNonNull(String repr);
/**
* Called to produce the string representation of the value when the
* latter is non-null.
*/
protected abstract String toTextNonNull(T value);
NullableLiteral() {
}
}
/**
* Uses the {@link #toString()} method of the object to produce its text representation and
* a constructor taking a single-argument of String type reconstruct object from
* its string representation.
*/
class ReflectiveLiteral extends NullableLiteral {
private ReflectiveLiteral(Class cls) {
constructor = unchecked(() -> cls.getDeclaredConstructor(String.class));
}
@Override
public T toValueNonNull(String repr) {
return unchecked(() -> constructor.newInstance(repr));
}
@Override
public String toTextNonNull(T value) {
return value.toString();
}
/**
* Single string argument constructor for constructing the literals from their
* string representation.
*/
private final Constructor constructor;
}
/**
* Similar to {@link ReflectiveLiteral} but uses the wrapper class corresponding
* to a primitive class.
*/
class PrimitiveLiteral extends NullableLiteral {
private PrimitiveLiteral(Class cls) {
wrapperClass = Classes.wrapperClassOf(cls);
constructor = unchecked(() -> (Constructor) wrapperClass.getDeclaredConstructor(String.class));
}
@Override
public T toValueNonNull(String repr) {
return unchecked(() -> constructor.newInstance(repr));
}
@Override
public String toTextNonNull(T value) {
return value.toString();
}
/**
* The wrapper class for the primitive type being literalized.
*/
private final Class> wrapperClass;
/**
* Single string argument constructor for constructing the literals from their
* string representation.
*/
private final Constructor constructor;
}
/**
* A literalizer for the character type.
*/
class CharacterLiteral extends NullableLiteral {
@Override
public Character toValueNonNull(String repr) {
return repr.charAt(0);
}
@Override
public String toTextNonNull(Character value) {
return value.toString();
}
private CharacterLiteral() {
}
}
/**
* Literalization of dates using the {@link SimpleDateFormat} "dd-MMM-yyyy HH:mm:ss.SSS Z".
*/
class DateLiteral extends NullableLiteral {
@Override
public Date toValueNonNull(String repr) {
return unchecked(() -> df.parse(repr));
}
@Override
public String toTextNonNull(Date value) {
return df.format(value);
}
private DateLiteral() {
}
/**
* Date formatter for parsing and formatting dates in their literal forms.
*/
static final SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss.SSS");
}
/**
* Literalization of enums.
*/
class EnumLiteral> extends NullableLiteral {
private EnumLiteral(Class cls) {
valueOf = unchecked(() -> cls.getDeclaredMethod("valueOf", String.class));
}
@Override
public T toValueNonNull(String repr) {
return unchecked(() -> (T) valueOf.invoke(null, repr));
}
@Override
public String toTextNonNull(T value) {
return value.toString();
}
/**
* The enum valueOf method for reconstruction.
*/
private final Method valueOf;
}
/**
* Strings are literalized as themselves.
*/
class StringLiteral extends NullableLiteral {
@Override
protected String toValueNonNull(String repr) {
return repr;
}
@Override
protected String toTextNonNull(String value) {
return value;
}
private StringLiteral() {
}
}
/**
* Array of literals are literals, literalized as '[a, b, c, ....]'. This is a recursive definition
* allowing higher dimensional arrays of literals to be also considered literals.
*/
class ArrayLiteral extends NullableLiteral {
private ArrayLiteral(Class arrayClass) {
componentType = arrayClass.getComponentType();
componentTypeLiteral = literal(componentType);
multiDimensional = componentType.isArray();
}
@Override
protected T toValueNonNull(String repr) {
List