/*
* Javolution - Java(TM) Solution for Real-Time and Embedded Systems
* Copyright (C) 2006 - Javolution (http://javolution.org/)
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software is
* freely granted, provided that this notice is preserved.
*/
package javolution.text;
import java.io.IOException;
import java.lang.CharSequence;
import javolution.context.LocalContext;
import javolution.lang.Realtime;
import javolution.lang.Reflection;
import java.lang.Appendable;
/**
* <p> This class represents the base format for text parsing and formatting;
* it supports the {@link CharSequence} and {@link Appendable} interfaces
* for greater flexibility.</p>
*
* <p> Instances of this class are typically used as static member of a
* class to define the default textual representation of its instances.
* [code]
* public class Complex extends Number {
*
* // Defines the default format for complex numbers (Cartesian form)
* protected static TextFormat<Complex> TEXT_FORMAT = new TextFormat<Complex> (Complex.class) { ... }
*
* public static Complex valueOf(CharSequence csq) {
* return TEXT_FORMAT.parse(csq);
* }
*
* }[/code]
* The format associated to any given class/object can be dynamically retrieved.
* [code]
* public abstract class Number implements ValueType {
*
* public final Text toText() {
* return TextFormat.getInstance(this.getClass()).format(this);
* }
*
* public final String toString() {
* return TextFormat.getInstance(this.getClass()).formatToString(this);
* }
* }[/code]
* The default format can be locally overriden.
* [code]
* LocalContext.enter();
* try {
* TextFormat<Complex> polarFormat = new TextFormat<Complex>(null) {...} // Unbound format.
* TextFormat.setInstance(Complex.class, polarFormat); // Local setting (no impact on others thread).
* System.out.println(complex); // Displays complex in polar coordinates.
* } finally {
* LocalContext.exit(); // Reverts to previous cartesian setting.
* }[/code]
* </p>
*
* <p> For parsing/formatting of primitive types, the {@link TypeFormat}
* utility class is recommended.</p>
*
* @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle </a>
* @version 5.5, March 20, 2010
*/
public abstract class TextFormat <T> {
/**
* Defines the static format bound to the specified class.
*
* @param forClass the class to which the format is bound or <code>null</code>
* if the format is not bound to any class.
* @throws IllegalArgumentException if the specified class is already
* bound to another format.
*/
protected TextFormat(Class <T> forClass) {
if (forClass == null)
return; // Dynamic format.
Reflection.getInstance().setField(new LocalReference(this), forClass, LocalReference.class);
}
private static class LocalReference extends LocalContext.Reference {
public LocalReference(TextFormat defaultFormat) {
super(defaultFormat);
}
}
/**
* <p> Returns the default format for instances of the specified class.</p>
*
* <p> A default format exist for the following predefined types:
* <code><ul>
* <li>java.lang.Object (formatting only, e.g. "org.acmes.Foo#123")</li>
* <li>java.lang.String</li>
* <li>java.lang.Boolean</li>
* <li>java.lang.Character</li>
* <li>java.lang.Byte</li>
* <li>java.lang.Short</li>
* <li>java.lang.Integer</li>
* <li>java.lang.Long</li>
* <li>java.lang.Float</li>
* <li>java.lang.Double</li>
* <li>java.lang.Class</li>
* <li>javolution.util.Index</li>
* <li>javolution.text.Text</li>
* </ul></code>
*
* <p> If there is no format found for the specified class, the
* default format for <code>java.lang.Object</code> is returned.</p>
*
* @param forClass the class for which a compatible format is returned.
* @return the most specialized format compatible with the specified class.
*/
public static <T> TextFormat <T> getDefault(Class <? extends T> forClass) {
Predefined.init(); // Forces initialization.
LocalReference localReference = (LocalReference) Reflection.getInstance().getField(forClass, LocalReference.class, true);
return (localReference == null) ? Predefined.OBJECT_FORMAT : (TextFormat <T> ) localReference.getDefault();
}
/**
* <p> Returns the current format for instances of the specified class.</p>
*
* @param forClass the class to which a format has been bound.
* @return the most specialized format compatible with the specified class.
*/
public static <T> TextFormat <T> getInstance(Class <? extends T> forClass) {
Predefined.init(); // Forces initialization.
LocalReference localReference = (LocalReference) Reflection.getInstance().getField(forClass, LocalReference.class, true);
return (localReference == null) ? Predefined.OBJECT_FORMAT : (TextFormat <T> ) localReference.get();
}
/**
* Overrides the default format for the specified class ({@link LocalContext local setting}).
*
* @param forClass the class for which the format is locally overriden.
* @param format the new format (typically unbound).
* @throws IllegalArgumentException if the speficied class has not default format defined.
*/
public static <T> void setInstance(Class <? extends T> forClass, TextFormat <T> format) {
Predefined.init(); // Forces initialization.
LocalReference localReference = (LocalReference) Reflection.getInstance().getField(forClass, LocalReference.class, false);
if (localReference == null)
throw new IllegalArgumentException("Cannot override default format for class " + forClass + " (no default format defined)");
localReference.set(format);
}
/**
* Indicates if this format supports parsing (default <code>true</code>).
*
* @return <code>false</code> if any of the parse method throws
* <code>UnsupportedOperationException</code>
*/
public boolean isParsingSupported() {
return true;
}
/**
* Formats the specified object into an <code>Appendable</code>
*
* @param obj the object to format.
* @param dest the appendable destination.
* @return the specified <code>Appendable</code>.
* @throws IOException if an I/O exception occurs.
*/
public abstract Appendable format( T obj, Appendable dest)
throws IOException;
/**
* Parses a portion of the specified <code>CharSequence</code> from the
* specified position to produce an object. If parsing succeeds, then the
* index of the <code>cursor</code> argument is updated to the index after
* the last character used.
*
* @param csq the <code>CharSequence</code> to parse.
* @param cursor the cursor holding the current parsing index.
* @return the object parsed from the specified character sub-sequence.
* @throws IllegalArgumentException if any problem occurs while parsing the
* specified character sequence (e.g. illegal syntax).
*/
public abstract T parse(CharSequence csq, Cursor cursor) throws IllegalArgumentException;
/**
* Formats the specified object into a {@link TextBuilder} (convenience
* method which does not raise IOException).
*
* @param obj the object to format.
* @param dest the text builder destination.
* @return the specified text builder.
*/
public final TextBuilder format( T obj, TextBuilder dest) {
try {
format(obj, (Appendable) dest);
return dest;
} catch (IOException e) {
throw new Error(); // Cannot happen.
}
}
/**
* Formats the specified object to a {@link Text} instance
* (convenience method equivalent to
* <code>format(obj, TextBuilder.newInstance()).toText()</code>).
*
* @param obj the object being formated.
* @return the text representing the specified object.
*/
public final Text format( T obj) {
TextBuilder tb = TextBuilder.newInstance();
try {
format(obj, (Appendable) tb);
return tb.toText();
} catch (IOException e) {
throw new Error(); // Cannot happen.
} finally {
TextBuilder.recycle(tb);
}
}
/**
* Convenience methods equivalent to but faster than
* <code>format(obj).toString())</code>
*
* @param obj the object being formated.
* @return the string representing the specified object.
*/
public final String formatToString( T obj) {
TextBuilder tb = TextBuilder.newInstance();
try {
format(obj, (Appendable) tb);
return tb.toString();
} catch (IOException e) {
throw new Error(); // Cannot happen.
} finally {
TextBuilder.recycle(tb);
}
}
/**
* Parses a whole character sequence from the beginning to produce an object
* (convenience method).
*
* @param csq the whole character sequence to parse.
* @return <code>parse(csq, new Cursor())</code>
* @throws IllegalArgumentException if the specified character sequence
* cannot be fully parsed (e.g. extraneous characters).
*/
public final T parse(CharSequence csq) throws IllegalArgumentException {
Cursor cursor = Cursor.newInstance();
try {
T obj = parse(csq, cursor);
if (cursor.getIndex() < csq.length())
throw new IllegalArgumentException(
"Extraneous characters in \"" + csq + "\"");
return obj;
} finally {
Cursor.recycle(cursor);
}
}
private static class Predefined {
//
// Sets the default formats for predefined types.
//
static final TextFormat OBJECT_FORMAT = new TextFormat(Object.class) {
public Appendable format(Object obj, Appendable dest)
throws IOException {
dest.append(j2meToCharSeq(obj.getClass().getName()));
dest.append('#');
return TypeFormat.format(System.identityHashCode(obj), dest);
}
public boolean isParsingSupported() {
return false;
}
public Object parse(CharSequence csq, Cursor cursor) {
throw new java.lang.UnsupportedOperationException("Parsing not supported");
}
};
static final TextFormat STRING_FORMAT = new TextFormat(String.class) {
public Appendable format(Object obj, Appendable dest)
throws IOException {
return dest.append(j2meToCharSeq(obj));
}
public Object parse(CharSequence csq, Cursor cursor) {
String str = csq.subSequence(cursor.getIndex(), csq.length()).toString();
cursor.setIndex(csq.length());
return str;
}
};
static final TextFormat BOOLEAN_FORMAT = new TextFormat(Boolean.class) {
public Appendable format(Object obj, Appendable dest)
throws IOException {
return TypeFormat.format(((Boolean) obj).booleanValue(), dest);
}
public Object parse(CharSequence csq, Cursor cursor) {
return TypeFormat.parseBoolean(csq, cursor) ? Boolean.TRUE : Boolean.FALSE;
}
};
static final TextFormat CHARACTER_FORMAT = new TextFormat(Character.class) {
public Appendable format(Object obj, Appendable dest)
throws IOException {
return dest.append(((Character) obj).charValue());
}
public Object parse(CharSequence csq, Cursor cursor) {
return new Character(cursor.nextChar(csq));
}
};
static final TextFormat BYTE_FORMAT = new TextFormat(Byte.class) {
public Appendable format(Object obj, Appendable dest)
throws IOException {
return TypeFormat.format(((Byte) obj).byteValue(), dest);
}
public Object parse(CharSequence csq, Cursor cursor) {
return new Byte(TypeFormat.parseByte(csq, 10, cursor));
}
};
static final TextFormat SHORT_FORMAT = new TextFormat(Short.class) {
public Appendable format(Object obj, Appendable dest)
throws IOException {
return TypeFormat.format(((Short) obj).shortValue(), dest);
}
public Object parse(CharSequence csq, Cursor cursor) {
return new Short(TypeFormat.parseShort(csq, 10, cursor));
}
};
static final TextFormat INTEGER_FORMAT = new TextFormat(Integer.class) {
public Appendable format(Object obj, Appendable dest)
throws IOException {
return TypeFormat.format(((Integer) obj).intValue(), dest);
}
public Object parse(CharSequence csq, Cursor cursor) {
return new Integer(TypeFormat.parseInt(csq, 10, cursor));
}
};
static final TextFormat LONG_FORMAT = new TextFormat(Long.class) {
public Appendable format(Object obj, Appendable dest)
throws IOException {
return TypeFormat.format(((Long) obj).longValue(), dest);
}
public Object parse(CharSequence csq, Cursor cursor) {
return new Long(TypeFormat.parseLong(csq, 10, cursor));
}
};
static final TextFormat FLOAT_FORMAT = new TextFormat(Float.class) {
public Appendable format(Object obj, Appendable dest)
throws IOException {
return TypeFormat.format(((Float) obj).floatValue(), dest);
}
public Object parse(CharSequence csq, Cursor cursor) {
return new Float(TypeFormat.parseFloat(csq, cursor));
}
};
static final TextFormat DOUBLE_FORMAT = new TextFormat(Double.class) {
public Appendable format(Object obj, Appendable dest)
throws IOException {
return TypeFormat.format(((Double) obj).doubleValue(), dest);
}
public Object parse(CharSequence csq, Cursor cursor) {
return new Double(TypeFormat.parseDouble(csq, cursor));
}
};
static final TextFormat CLASS_FORMAT = new TextFormat(Class.class) {
public Appendable format(Object obj, Appendable dest)
throws IOException {
return dest.append(j2meToCharSeq(((Class) obj).getName()));
}
public Object parse(CharSequence csq, Cursor cursor) {
CharSequence className = cursor.nextToken(csq, CharSet.WHITESPACES);
if (className == null)
throw new IllegalArgumentException("No class name found");
Class cls = Reflection.getInstance().getClass(className);
if (cls != null)
return cls;
throw new IllegalArgumentException("Class \"" + className + "\" not found (see javolution.lang.Reflection)");
}
};
static final TextFormat TEXT_FORMAT = new TextFormat(Text.class) {
public Appendable format(Object obj, Appendable dest)
throws IOException {
return dest.append((Text) obj);
}
public Object parse(CharSequence csq, Cursor cursor) {
CharSequence subCsq = csq.subSequence(cursor.getIndex(), csq.length());
if (subCsq instanceof Realtime)
return ((Realtime) subCsq).toText();
return Text.valueOf(subCsq.toString());
}
};
private static void init() {
// Do nothing.
}
}
private static CharSequence j2meToCharSeq(Object str) {
/**/
return (CharSequence) str;
}
private static Text dummy(Object str) { // Never used.
/**/
return str == null ? null : Text.valueOf(str);
}
}