/*
* Created on Dec 31, 2004
*/
package ecologylab.serialization.types;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.regex.Pattern;
import ecologylab.serialization.FieldDescriptor;
import ecologylab.serialization.ScalarUnmarshallingContext;
import ecologylab.serialization.TranslationContext;
import ecologylab.serialization.annotations.simpl_inherit;
import ecologylab.serialization.annotations.simpl_scalar;
import ecologylab.serialization.formatenums.Format;
/**
* Basic unit of the scalar type system. Manages marshalling from a Java class that represents a
* scalar, to a String, and from a String to that Java class.
* <p/>
* The ScalarType object is a means for associating a type name with a type index. It also knows how
* to create an instance of the type, given a String representation. If the ScalarType is a
* reference type, this is done with getInstance(String); if the ScalarType is a primitive, this is
* done with getValue(String), which cannot appear as a method in this, the base class, because it
* will return a different primitive type for each such Type.
* <p/>
* Note: unlike with ElementState subtypes, translation of these is controlled entirely by the name
* of the underlying Java class that gets translated, and not by the class name of subclasses of
* this.
*
* @author andruid
*/
@simpl_inherit
public abstract class ScalarType<T> extends SimplType
implements CrossLanguageTypeConstants
{
@simpl_scalar
boolean isPrimitive;
public static final Object DEFAULT_VALUE = null;
public static final String DEFAULT_VALUE_STRING = "null";
/**
* Blank constructor for S.IM.PL deserialization.
*/
public ScalarType()
{
}
/**
* Constructor is protected because there should only be 1 instance that gets re-used, for each
* type. To get the instance of this type object for use in translations, call
* <code>TypeRegistry.get("type-string")</code>.
* @param cSharpTypeName TODO
* @param cSharpTypeName TODO
* @param objectiveCTypeName TODO
* @param objectiveCTypeName TODO
* @param dbTypeName TODO
* @param dbTypeName TODO
*
*/
protected ScalarType(Class<? extends T> javaClass, String cSharpTypeName, String objectiveCTypeName, String dbTypeName)
{
super(javaClass, true, cSharpTypeName, objectiveCTypeName, dbTypeName);
this.isPrimitive = javaClass.isPrimitive();
}
protected ScalarType(Class thatClass)
{
this(thatClass, null, null, null);
}
/**
* If <code>this</code> is a reference type, build an appropriate Object, given a String
* representation. If it is a primitive type, return a boxed value.
*
* @param value
* String representation of the instance.
* @param formatStrings
* Array of formatting values.
* @param scalarUnmarshallingContext
* TODO
*/
abstract public T getInstance(String value, String[] formatStrings,
ScalarUnmarshallingContext scalarUnmarshallingContext);
/**
* Construct an instance, using the subclass of this for marshalling, with null for the format
* Strings.
*
* @param value
* @return
*/
public T getInstance(String value)
{
return getInstance(value, null, null);
}
/**
* Set the field in the context, using the valueString, converting it to the appropriate type
* using a subclass of this.
* <p/>
* Many different types of exceptions may be thrown. These include IllegalAccessException on the
* one hand, which would come from problems with using reflection to access the Field. This is
* very unlikely.
* <p/>
* More likely are problems with conversion of the parameter value into into an object or
* primitive of the proper type.
*
* @param context
* The object whose field should be modified.
* @param field
* The field to be set.
* @param valueString
* String representation of the value to set the field to. This Type will convert the
* value to the appropriate type, using getInstance(String) for reference types, and type
* specific getValue(String) methods for primitive types.
* @param scalarUnmarshallingContext
* TODO
* @return true if the field is set properly, or if the parameter value that is passed in is null.
* false if the field cannot be accessed, or if value cannot be converted to the
* appropriate type.
*/
public boolean setField(Object context, Field field, String valueString, String[] format,
ScalarUnmarshallingContext scalarUnmarshallingContext)
{
if (valueString == null)
return true;
boolean result = false;
T referenceObject;
try
{
referenceObject = getInstance(valueString, format, scalarUnmarshallingContext);
if (referenceObject != null)
{
field.set(context, referenceObject);
result = true;
}
}
catch (Exception e)
{
setFieldError(field, valueString, e);
}
return result;
}
/**
* Set the field in the context, using the valueString, converting it to the appropriate type
* using a subclass of this.
* <p/>
* The format annotations passed through to the ScalarType subclass will be null.
* <p/>
* Many different types of exceptions may be thrown. These include IllegalAccessException on the
* one hand, which would come from problems with using reflection to access the Field. This is
* very unlikely.
* <p/>
* More likely are problems with conversion of the parameter value into into an object or
* primitive of the proper type.
*
* @param context
* The object whose field should be modified.
* @param field
* The field to be set.
* @param valueString
* String representation of the value to set the field to. This Type will convert the
* value to the appropriate type, using getInstance(String) for reference types, and type
* specific getValue(String) methods for primitive types.
*
* @return true if the field is set properly, or if the parameter value that is passed in is null.
* false if the field cannot be accessed, or if value cannot be converted to the
* appropriate type.
*/
public boolean setField(Object object, Field field, String value)
{
return setField(object, field, value, null, null);
}
/**
* Display an error message that arose while setting field to value.
*
* @param field
* @param value
* @param e
*/
protected void setFieldError(Field field, String value, Exception e)
{
error("Got " + e + " while trying to set field " + field + " to " + value);
}
/**
* @return Returns the integer index associated with this type.
*/
/*
* public int getIndex() { return index; }
*/
/**
* Find out if this is a reference type or a primitive types.
*
* @return true for a primitive type. false for a reference type.
*/
public boolean isPrimitive()
{
return isPrimitive;
}
/**
* Return true if this type may need escaping when emitted as XML.
*
* @return true, by default, for all reference types (includes Strings, PURLs, ...); false
* otherwise.
*/
public boolean needsEscaping()
{
return isReference();
}
/**
* Find out if this is a reference type or a primitive types.
*
* @return true for a reference type. false for a primitive type.
*/
public boolean isReference()
{
return !isPrimitive;
}
/**
* The string representation for a Field of this type. Reference scalar types should NOT override
* this. They should simply override marshall(instance), which this method calls.
* <p/>
* Primitive types cannot create such an instance, from the value of a field, and so must
* override.
*/
public String toString(Field field, Object context)
{
String result = "COULDNT CONVERT!";
try
{
T instance = (T) field.get(context);
if (instance == null)
result = DEFAULT_VALUE_STRING;
else
result = marshall(instance, null);
}
catch (Exception e)
{
e.printStackTrace();
}
return result;
}
/**
* Get the value from the Field, in the context. Append its value to the buffy.
* <p/>
* Should only be called *after* checking !isDefault() yourself.
*
* @param buffy
* @param context
* @param serializationContext TODO
* @param field
* @param needsEscaping
* TODO
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public void appendValue(Appendable buffy, FieldDescriptor fieldDescriptor, Object context, TranslationContext serializationContext, Format format)
throws IllegalArgumentException, IllegalAccessException, IOException
{
Object instance = fieldDescriptor.getValue(context);
appendValue((T) instance, buffy, !fieldDescriptor.isCDATA(), serializationContext, format);
}
/**
* Get a String representation of the instance, using this. The default just calls the toString()
* method on the instance.
*
* @param instance
* @param serializationContext TODO
* @return
*/
public String marshall(T instance, TranslationContext serializationContext)
{
return instance.toString();
}
/**
* Get the value from the Field, in the context. Append its value to the buffy.
* <p/>
* Should only be called *after* checking !isDefault() yourself.
*
* @param buffy
* @param field
* @param context
* @param needsEscaping
* TODO
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public void appendValue(StringBuilder buffy, FieldDescriptor fieldDescriptor, Object context)
throws IllegalArgumentException, IllegalAccessException
{
try
{
Object instance = fieldDescriptor.getField().get(context);
appendValue((T) instance, buffy, !fieldDescriptor.isCDATA(), null);
}
catch (IllegalArgumentException e)
{
throw e;
}
}
public void appendValue(T instance, StringBuilder buffy, boolean needsEscaping, TranslationContext serializationContext)
{
buffy.append(marshall(instance, serializationContext));
}
public void appendValue(T instance, Appendable appendable, boolean needsEscaping, TranslationContext serializationContext, Format format)
throws IOException
{
appendable.append(marshall(instance, serializationContext));
}
/**
* The default value for this type, as a String. This value is the one that translateToXML(...)
* wont bother emitting.
*
* In this case, "null".
*/
public String defaultValueString()
{
return DEFAULT_VALUE_STRING;
}
/**
* The default value for this, in its own type. Not meaningful for primitive types.
*
* @return
*/
public T defaultValue()
{
return null;
}
public final int defaultValueLength()
{
return defaultValueString().length();
}
public boolean isDefaultValue(String value)
{
String defaultValue = defaultValueString();
return (defaultValue.length() == value.length()) && defaultValue.equals(value);
}
public boolean isDefaultValue(Field field, Object context) throws IllegalArgumentException,
IllegalAccessException
{
Object fieldValue = field.get(context);
return fieldValue == null || DEFAULT_VALUE_STRING.equals(fieldValue.toString());
}
/**
* Returns whether or not this is a floating point value of some sort; Types that are floating
* point values should override this method to return true.
*
* The implication of returning true is that the precision of this can be controlled when it is
* emitted as XML.
*
* @return false
*/
public boolean isFloatingPoint()
{
return false;
}
public boolean allowNewLines()
{
return true;
}
public static final String DEFAULT_DELIMS = " \n\t";
public static final Pattern DEFAULT_DELIMS_TOKENIZER = Pattern.compile("([" + DEFAULT_DELIMS
+ "]*)([^" + DEFAULT_DELIMS + "]+)");
public static final String MINIMAL_DELIM = " ";
/**
* For editing: these are the valid delimiters for separating tokens that make up a field of this
* type.
*
* @return
*/
public Pattern delimitersTokenizer()
{
return DEFAULT_DELIMS_TOKENIZER;
}
public String delimeters()
{
return DEFAULT_DELIMS;
}
/**
* The most basic and fundamental delimiter to use between characters.
*
* @return The base implementation, here, returns a space.
*/
public String primaryDelimiter()
{
return MINIMAL_DELIM;
}
/**
* When editing, determines whether delimiters can be included in token strings.
*
* @return
*/
// FIXME -- Add String delimitersAfter to TextChunk -- interleaved with TextTokens, and
// get rid of this!!!
public boolean allowDelimitersInTokens()
{
return false;
}
/**
* When editing, do not allow the user to include these characters in the resulting value String.
*
* @return
*/
public String illegalChars()
{
return "";
}
/**
* When editing, is the field one that should be part of the Term model?
*
* @return true for Strings
*/
public boolean composedOfTerms()
{
return true;
}
String fieldTypeName;
public String fieldTypeName()
{
String result = fieldTypeName;
if (result == null)
{
result = this.getSimpleName();
int index = result.indexOf("Type");
if (index != -1)
{
result = result.substring(0, index);
}
// if (isPrimitive())
// {
// char first = Character.toLowerCase(result.charAt(0));
// if (result.length() > 1)
// result = first + result.substring(1);
// else
// result = Character.toString(first);
// }
fieldTypeName = result;
}
return result;
}
/**
* Used to describe scalar types used for serializing the type system, itself. They cannot be
* unmarshalled in Java, only marshalled. Code may be written to access their String
* representations in other languages.
*
* @return false for almost all ScalarTypes
*/
public boolean isMarshallOnly()
{
return false;
}
public ScalarType operativeScalarType()
{
return this;
}
/**
* Used to fill seams between direct scalar types, and those with a nested field that actually
* stores the value.
*
* @param externalField
* @return Depending on the type, either the external field, or one within the type.
*/
public Field operativeField(Field externalField)
{
return externalField;
}
/**
* Used to fill seams between direct scalar types, and those with a nested field that actually
* stores the value.
*
* @param largerContext
* @param field
*
* @return Depending on the type, the larger context passed in, or the object value of the field
* within the context.
*/
public T unpackContext(Object largerContext, Field field)
{
return (T) largerContext;
}
protected static String getNullStringIfNull(FieldDescriptor fieldDescriptor, Object context)
throws IllegalArgumentException, IllegalAccessException
{
return (fieldDescriptor.getField() == null || fieldDescriptor.getField().get(context) == null) ? "null" : null;
}
/**
* The name to use when declaring a field in C# cross-compilation.
* For ScalarType, be aggressive in seeking a suitable type name.
*
* @return cSharpTypeName, if one was passed in explicitly. otherwise, assume its the same as javaTypeName, and pass that.
*/
@Override
public String deriveCSharpTypeName()
{
String cSharpTypeName = super.getCSharpTypeName();
return cSharpTypeName != null ? cSharpTypeName : super.getJavaTypeName();
}
/**
* The name to use when declaring a field in Objective C cross-compilation.
* For ScalarType, be aggressive in seeking a suitable type name.
*
* @return objectiveCTypeName, if one was passed in explicitly. otherwise, assume its the same as simple name, and pass that.
*/
@Override
public String deriveObjectiveCTypeName()
{
String objectiveCTypeName = super.getObjectiveCTypeName();
return objectiveCTypeName != null ? objectiveCTypeName : super.getSimpleName();
}
@Override
public boolean isScalar()
{
return true;
}
}