001: /*
002: * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
003: * Copyright (C) 2006 - Javolution (http://javolution.org/)
004: * All rights reserved.
005: *
006: * Permission to use, copy, modify, and distribute this software is
007: * freely granted, provided that this notice is preserved.
008: */
009: package javolution.text;
010:
011: import j2me.lang.CharSequence;
012: import j2me.text.ParsePosition;
013: import javolution.Javolution;
014: import javolution.context.ObjectFactory;
015: import javolution.lang.ClassInitializer;
016: import javolution.lang.Reflection;
017: import javolution.util.FastMap;
018:
019: import java.io.IOException;
020:
021: /**
022: * <p> This class represents the base format for text parsing and formatting;
023: * it supports {@link CharSequence} and {@link javolution.text.Appendable}
024: * interfaces for greater flexibility.</p>
025: *
026: * <p> It is possible to retrieve the format for any class for which the
027: * format has been registered (typically during class initialization).
028: * For example:[code]
029: * public class Complex extends RealtimeObject {
030: * private static final TextFormat<Complex> CARTESIAN = ...;
031: * static { // Sets default format to cartesian, users may change it later (e.g. polar).
032: * TextFormat.setInstance(Complex.class, CARTESIAN);
033: * }
034: * public Complex valueOf(CharSequence csq) {
035: * return TextFormat.getInstance(Complex.class).parse(csq);
036: * }
037: * public Text toText() {
038: * return TextFormat.getInstance(Complex.class).format(this);
039: * }
040: * }[/code]</p>
041: *
042: * <p> For parsing/formatting of primitive types, the {@link TypeFormat}
043: * utility class is recommended.</p>
044: *
045: * <p> <i>Note:</i> The format behavior may depend upon
046: * {@link javolution.context.LocalContext local} settings in which
047: * case concurrent threads may parse differently!</p>
048: *
049: * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle </a>
050: * @version 5.1, July 4, 2007
051: */
052: public abstract class TextFormat/*<T>*/{
053:
054: /**
055: * Holds the class to format mapping.
056: */
057: private static final FastMap FORMATS = new FastMap()
058: .setShared(true);
059:
060: /**
061: * Default constructor.
062: */
063: protected TextFormat() {
064: }
065:
066: /**
067: * Returns the text format for instances of specified type (class or
068: * interface). The following types are always recognized:<code><ul>
069: * <li>java.lang.Boolean</li>
070: * <li>java.lang.Character</li>
071: * <li>java.lang.Byte</li>
072: * <li>java.lang.Short</li>
073: * <li>java.lang.Integer</li>
074: * <li>java.lang.Long</li>
075: * <li>java.lang.Float</li>
076: * <li>java.lang.Double</li>
077: * <li>java.lang.Class</li>
078: * </ul></code>
079: * Users may register additional types using the {@link #setInstance
080: * TextFormat.setInstance(Class, TextFormat)} static method.
081: * For example:[code]
082: * TextFormat<Font> fontFormat = new TextFormat() {
083: * public Appendable format(Font font, Appendable dest) throws IOException {
084: * return dest.append(font.getName());
085: * }
086: * public Font parse(CharSequence csq, Cursor cursor) {
087: * CharSequence fontName = csq.subSequence(cursor.getIndex(), cursor.getEndIndex());
088: * cursor.increment(fontName.length());
089: * return Font.decode(fontName.toString());
090: * }
091: * });
092: * TextFormat.setInstance(Font.class, fontFormat); // Registers format for java.awt.Font
093: * [/code]
094: *
095: * @param cls the class for which the default format is returned.
096: * @return the format for instances of the specified class or
097: * <code>null</code> if unkown.
098: */
099: public static/*<T>*/TextFormat/*<T>*/getInstance(Class/*<T>*/cls) {
100: TextFormat format = (TextFormat) FORMATS.get(cls);
101: return (format != null) ? format : searchFormat(cls);
102: }
103:
104: private static TextFormat searchFormat(Class cls) {
105: if (cls == null)
106: return null;
107: ClassInitializer.initialize(cls); // Ensures class static initializer are run.
108: TextFormat format = (TextFormat) FORMATS.get(cls);
109: return (format != null) ? format
110: : searchFormat(super classOf(cls));
111: }
112:
113: private static Class super classOf(Class cls) {
114: /*@JVM-1.4+@
115: if (true) return cls.getSuperclass();
116: /**/
117: return null;
118: }
119:
120: /**
121: * Associates the specified format to the specified type (class or
122: * interface).
123: *
124: * @param cls the class for which the default format is returned.
125: * @param format the format for instances of the specified calss class.
126: */
127: public static/*<T>*/void setInstance(Class/*<T>*/cls,
128: TextFormat/*<T>*/format) {
129:
130: // The specified class is initialized prior to setting
131: // the format to ensure that the default format (typically in the
132: // class static initializer) does not override the new format.
133: ClassInitializer.initialize(cls);
134:
135: FORMATS.put(cls, format); // Thread-safe (shared map).
136: }
137:
138: /**
139: * Formats the specified object into an <code>Appendable</code>
140: *
141: * @param obj the object to format.
142: * @param dest the appendable destination.
143: * @return the specified <code>Appendable</code>.
144: * @throws IOException if an I/O exception occurs.
145: */
146: public abstract Appendable format(Object/*{T}*/obj, Appendable dest)
147: throws IOException;
148:
149: /**
150: * Parses a portion of the specified <code>CharSequence</code> from the
151: * specified position to produce an object. If parsing succeeds, then the
152: * index of the <code>cursor</code> argument is updated to the index after
153: * the last character used.
154: *
155: * @param csq the <code>CharSequence</code> to parse.
156: * @param cursor the cursor holding the current parsing index.
157: * @return the object parsed from the specified character sub-sequence.
158: * @throws RuntimeException if any problem occurs while parsing the
159: * specified character sequence (e.g. illegal syntax).
160: */
161: public abstract Object/*{T}*/parse(CharSequence csq, Cursor cursor);
162:
163: /**
164: * Formats the specified object into a {@link TextBuilder} (convenience
165: * method which does not raise IOException).
166: *
167: * @param obj the object to format.
168: * @param dest the text builder destination.
169: * @return the specified text builder.
170: */
171: public final Appendable format(Object/*{T}*/obj, TextBuilder dest) {
172: try {
173: return format(obj, (Appendable) dest);
174: } catch (IOException e) {
175: throw new Error(); // Cannot happen.
176: }
177: }
178:
179: /**
180: * Formats the specified object to a {@link Text} instance
181: * (convenience method).
182: *
183: * @param obj the object being formated.
184: * @return the text representing the specified object.
185: */
186: public final Text format(Object/*{T}*/obj) {
187: TextBuilder tb = TextBuilder.newInstance();
188: format(obj, tb);
189: Text txt = tb.toText();
190: TextBuilder.recycle(tb);
191: return txt;
192: }
193:
194: /**
195: * Parses a whole character sequence from the beginning to produce an object
196: * (convenience method).
197: *
198: * @param csq the whole character sequence to parse.
199: * @return the corresponding object.
200: * @throws IllegalArgumentException if the specified character sequence
201: * cannot be fully parsed.
202: */
203: public final Object/*{T}*/parse(CharSequence csq) {
204: Cursor cursor = Cursor.newInstance(0, csq.length());
205: Object/*{T}*/obj = parse(csq, cursor);
206: if (cursor.hasNext())
207: throw new IllegalArgumentException("Incomplete Parsing");
208: Cursor.recycle(cursor);
209: return obj;
210: }
211:
212: /**
213: * This class represents a parsing cursor over a character sequence
214: * (or subsequence). A cursor location may start and end at any predefined
215: * location within the character sequence iterated over (equivalent to
216: * parsing a subsequence of the character sequence input).
217: */
218: public static class Cursor extends ParsePosition {
219:
220: /**
221: * Holds the cursor factory.
222: */
223: private static final ObjectFactory FACTORY = new ObjectFactory() {
224: public Object create() {
225: return new Cursor();
226: }
227: };
228:
229: /**
230: * Holds the cursor index.
231: */
232: private int _index;
233:
234: /**
235: * Holds the start index.
236: */
237: private int _start;
238:
239: /**
240: * Holds the end index.
241: */
242: private int _end;
243:
244: /**
245: * Default constructor.
246: */
247: private Cursor() {
248: super (0);
249: }
250:
251: /**
252: * Returns a new, preallocated or {@link #recycle recycled} cursor
253: * instance (on the stack when executing in a {@link
254: * javolution.context.StackContext StackContext}).
255: *
256: * @param start the start index.
257: * @param end the end index (index after the last character to be read).
258: * @return a new or recycled cursor instance.
259: */
260: public static Cursor newInstance(int start, int end) {
261: Cursor cursor = (Cursor) FACTORY.object();
262: cursor._start = cursor._index = start;
263: cursor._end = end;
264: cursor.setErrorIndex(-1);
265: return cursor;
266: }
267:
268: /**
269: * Recycles a cursor {@link #newInstance instance} immediately
270: * (on the stack when executing in a {@link
271: * javolution.context.StackContext StackContext}).
272: *
273: * @param instance the cursor instance being recycled.
274: */
275: public static void recycle(Cursor instance) {
276: FACTORY.recycle(instance);
277: }
278:
279: /**
280: * Returns this cursor index.
281: *
282: * @return the index of the next character to parse.
283: */
284: public final int getIndex() {
285: return _index;
286: }
287:
288: /**
289: * Returns this cursor start index.
290: *
291: * @return the start index.
292: */
293: public final int getStartIndex() {
294: return _start;
295: }
296:
297: /**
298: * Returns this cursor end index.
299: *
300: * @return the end index.
301: */
302: public final int getEndIndex() {
303: return _end;
304: }
305:
306: /**
307: * Returns the error index of this cursor if
308: * {@link #setErrorIndex set}; otherwise returns the current
309: * {@link #getIndex index}.
310: *
311: * @return the error index.
312: */
313: public final int getErrorIndex() {
314: int errorIndex = this .getErrorIndex();
315: return errorIndex >= 0 ? errorIndex : _index;
316: }
317:
318: /**
319: * Sets the cursor current index.
320: *
321: * @param i the index of the next character to parse.
322: * @throws IllegalArgumentException
323: * if <code>((i < getStartIndex()) || (i > getEndIndex()))</code>
324: */
325: public final void setIndex(int i) {
326: if ((i < _start) || (i > _end))
327: throw new IllegalArgumentException();
328: _index = i;
329: }
330:
331: /**
332: * Sets this cursor start index.
333: *
334: * @param start the start index.
335: */
336: public final void setStartIndex(int start) {
337: _start = start;
338: }
339:
340: /**
341: * Sets this cursor end index.
342: *
343: * @param end the end index.
344: */
345: public final void setEndIndex(int end) {
346: _end = end;
347: }
348:
349: /**
350: * Sets this cursor error index.
351: *
352: * @param errorIndex the error index.
353: */
354: public final void setErrorIndex(int errorIndex) {
355: super .setErrorIndex(errorIndex);
356: }
357:
358: /**
359: * Indicates if this cursor has not reached the end index.
360: *
361: * @return <code>this.getIndex() < this.getEndIndex()</code>
362: */
363: public final boolean hasNext() {
364: return _index < _end;
365: }
366:
367: /**
368: * Returns the next character at the cursor position in the specified
369: * character sequence and increments the cursor position by one.
370: * For example:[code]
371: * for (char c=cursor.next(csq); c != 0; c = cursor.next(csq)) {
372: * ...
373: * }
374: * }[/code]
375: *
376: * @param csq the character sequence iterated by this cursor.
377: * @return the character at the current cursor position in the
378: * specified character sequence or <code>'\u0000'</code>
379: * if the end index has already been reached.
380: */
381: public final char next(CharSequence csq) {
382: return (_index < _end) ? csq.charAt(_index++) : 0;
383: }
384:
385: /**
386: * Indicates if this cursor points to the specified character
387: * in the specified character sequence.
388: *
389: * @param c the character.
390: * @param csq the character sequence iterated by this cursor.
391: * @return <code>true</code> if the cursor next character is the
392: * one specified; <code>false</code> otherwise.
393: */
394: public final boolean at(char c, CharSequence csq) {
395: return (_index < _end) && (csq.charAt(_index) == c);
396: }
397:
398: /**
399: * Indicates if this cursor points to one of the specified character.
400: *
401: * @param charSet the character set
402: * @param csq the character sequence iterated by this cursor.
403: * @return <code>true</code> if the cursor next character is one
404: * of the character contained by the character set;
405: * <code>false</code> otherwise.
406: */
407: public final boolean at(CharSet charSet, CharSequence csq) {
408: return (_index < _end)
409: && (charSet.contains(csq.charAt(_index)));
410: }
411:
412: /**
413: * Indicates if this cursor points to the specified characters
414: * in the specified character sequence.
415: *
416: * @param pattern the characters searched for.
417: * @param csq the character sequence iterated by this cursor.
418: * @return <code>true</code> if the cursor next character are the
419: * one specified in the pattern; <code>false</code> otherwise.
420: */
421: public final boolean at(String pattern, CharSequence csq) {
422: return (_index < _end)
423: && (csq.charAt(_index) == pattern.charAt(0)) ? match(
424: pattern, csq)
425: : false;
426: }
427:
428: private final boolean match(String pattern, CharSequence csq) {
429: for (int i = 1, j = _index + 1, n = pattern.length(), m = _end; i < n;) {
430: if ((j >= m)
431: || (csq.charAt(j++) != pattern.charAt(i++)))
432: return false;
433: }
434: return true;
435: }
436:
437: /**
438: * Moves this cursor forward until it points to a character
439: * different from the character specified.
440: *
441: * @param c the character to skip.
442: * @param csq the character sequence iterated by this cursor.
443: * @return <code>true</code> if this cursor points to a character
444: * different from the ones specified; <code>false</code>
445: * otherwise (e.g. end of sequence reached).
446: */
447: public final boolean skip(char c, CharSequence csq) {
448: while ((_index < _end) && (csq.charAt(_index) == c)) {
449: _index++;
450: }
451: return _index < _end;
452: }
453:
454: /**
455: * Moves this cursor forward until it points to a character
456: * different from any of the character in the specified set.
457: * For example: [code]
458: * // Reads numbers separated by tabulations or spaces.
459: * FastTable<Integer> numbers = new FastTable<Integer>();
460: * while (cursor.skip(CharSet.SPACE_OR_TAB, csq)) {
461: * numbers.add(TypeFormat.parseInt(csq, cursor));
462: * }[/code]
463: *
464: * @param charSet the character to skip.
465: * @param csq the character sequence iterated by this cursor.
466: * @return <code>true</code> if this cursor points to a character
467: * different from the ones specified; <code>false</code>
468: * otherwise (e.g. end of sequence reached).
469: */
470: public final boolean skip(CharSet charSet, CharSequence csq) {
471: while ((_index < _end)
472: && (charSet.contains(csq.charAt(_index)))) {
473: _index++;
474: }
475: return _index < _end;
476: }
477:
478: /**
479: * Increments the cursor index by one.
480: *
481: * @return <code>this</code>
482: */
483: public final Cursor increment() {
484: _index++;
485: return this ;
486: }
487:
488: /**
489: * Increments the cursor index by the specified value.
490: *
491: * @param i the increment value.
492: * @return <code>this</code>
493: */
494: public final Cursor increment(int i) {
495: _index += i;
496: return this ;
497: }
498:
499: /**
500: * Returns the string representation of this cursor.
501: *
502: * @return the index value as a string.
503: */
504: public String toString() {
505: return String.valueOf(_index);
506: }
507:
508: /**
509: * Indicates if this cursor is equals to the specified object.
510: *
511: * @return <code>true</code> if the specified object is a cursor
512: * at the same index; <code>false</code> otherwise.
513: */
514: public boolean equals(Object obj) {
515: if (obj == null)
516: return false;
517: if (!(obj instanceof Cursor))
518: return false;
519: return _index == ((Cursor) obj)._index;
520: }
521:
522: /**
523: * Returns the hash code for this cursor.
524: *
525: * @return the hash code value for this object
526: */
527: public int hashCode() {
528: return _index;
529: }
530:
531: }
532:
533: // Predefined formats.
534: static {
535: FORMATS.put(new Boolean(true).getClass(), new TextFormat() {
536:
537: public Appendable format(Object obj, Appendable dest)
538: throws IOException {
539: return TypeFormat.format(
540: ((Boolean) obj).booleanValue(), dest);
541: }
542:
543: public Object parse(CharSequence csq, Cursor cursor) {
544: return new Boolean(TypeFormat.parseBoolean(csq, cursor));
545: }
546:
547: });
548: FORMATS.put(new Character(' ').getClass(), new TextFormat() {
549:
550: public Appendable format(Object obj, Appendable dest)
551: throws IOException {
552: return dest.append(((Character) obj).charValue());
553: }
554:
555: public Object parse(CharSequence csq, Cursor cursor) {
556: return new Character(cursor.next(csq));
557: }
558:
559: });
560: FORMATS.put(new Byte((byte) 0).getClass(), new TextFormat() {
561:
562: public Appendable format(Object obj, Appendable dest)
563: throws IOException {
564: return TypeFormat
565: .format(((Byte) obj).byteValue(), dest);
566: }
567:
568: public Object parse(CharSequence csq, Cursor cursor) {
569: return new Byte(TypeFormat.parseByte(csq, 10, cursor));
570: }
571:
572: });
573: FORMATS.put(new Short((short) 0).getClass(), new TextFormat() {
574:
575: public Appendable format(Object obj, Appendable dest)
576: throws IOException {
577: return TypeFormat.format(((Short) obj).shortValue(),
578: dest);
579: }
580:
581: public Object parse(CharSequence csq, Cursor cursor) {
582: return new Short(TypeFormat.parseShort(csq, 10, cursor));
583: }
584:
585: });
586: FORMATS.put(new Integer(0).getClass(), new TextFormat() {
587:
588: public Appendable format(Object obj, Appendable dest)
589: throws IOException {
590: return TypeFormat.format(((Integer) obj).intValue(),
591: dest);
592: }
593:
594: public Object parse(CharSequence csq, Cursor cursor) {
595: return new Integer(TypeFormat.parseInt(csq, 10, cursor));
596: }
597:
598: });
599: FORMATS.put(new Long(0).getClass(), new TextFormat() {
600:
601: public Appendable format(Object obj, Appendable dest)
602: throws IOException {
603: return TypeFormat
604: .format(((Long) obj).longValue(), dest);
605: }
606:
607: public Object parse(CharSequence csq, Cursor cursor) {
608: return new Long(TypeFormat.parseLong(csq, 10, cursor));
609: }
610:
611: });
612: FORMATS.put("".getClass().getClass(), new TextFormat() {
613:
614: public Appendable format(Object obj, Appendable dest)
615: throws IOException {
616: return dest.append(Javolution
617: .j2meToCharSeq(((Class) obj).getName()));
618: }
619:
620: public Object parse(CharSequence csq, Cursor cursor) {
621: Text txt = Text.valueOf(csq.subSequence(cursor
622: .getIndex(), cursor.getEndIndex()));
623: int index = txt.indexOfAny(CharSet.WHITESPACES);
624: int length = index < 0 ? txt.length() : index;
625: Text className = txt.subtext(0, length);
626: Class cls;
627: try {
628: cls = Reflection.getClass(className);
629: } catch (ClassNotFoundException e) {
630: throw new IllegalArgumentException("Class "
631: + className + " Not Found");
632: }
633: cursor.increment(length);
634: return cls;
635: }
636:
637: });
638:
639: /*@JVM-1.1+@
640: FORMATS.put(new Float(0).getClass(), new TextFormat() {
641:
642: public Appendable format(Object obj, Appendable dest)
643: throws IOException {
644: return TypeFormat.format(((Float) obj).floatValue(), dest);
645: }
646:
647: public Object parse(CharSequence csq, Cursor cursor) {
648: return new Float(TypeFormat.parseFloat(csq, cursor));
649: }
650:
651: });
652: FORMATS.put(new Double(0).getClass(), new TextFormat() {
653:
654: public Appendable format(Object obj, Appendable dest)
655: throws IOException {
656: return TypeFormat.format(((Double) obj).doubleValue(), dest);
657: }
658:
659: public Object parse(CharSequence csq, Cursor cursor) {
660: return new Double(TypeFormat.parseDouble(csq, cursor));
661: }
662:
663: });
664: /**/
665:
666: }
667: }
|