/* * Javolution - Java(TM) Solution for Real-Time and Embedded Systems * Copyright (C) 2009 - 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 j2me.lang.CharSequence; import j2me.text.ParsePosition; import javolution.context.ObjectFactory; import javolution.lang.Reusable; /** * <p> This class represents a parsing cursor over characters. Cursor * allows for token iterations over any {@link CharSequence}. * [code] * CharSequence csq = "this is a test"; * Cursor cursor = Cursor.newInstance(); * try { * for (CharSequence token; (token=cursor.nextToken(csq, ' '))!= null;) * System.out.println(token); * } finally { * Cursor.recycle(cursor); * } * [/code] * Prints the following output:<pre> * this * is * a * test</pre> * Cursors are typically used with {@link TextFormat} instances. * [code] * // Parses decimal number (e.g. "xxx.xxxxxExx" or "NaN") * public Decimal parse(CharSequence csq, Cursor cursor) { * if (cursor.skip("NaN", csq)) * return Decimal.NaN; * LargeInteger significand = LargeInteger.TEXT_FORMAT.parse(csq, cursor); * LargeInteger fraction = cursor.skip('.', csq) ? LargeInteger.TEXT_FORMAT.parse(csq, cursor) : LargeInteger.ZERO; * int exponent = cursor.skip(CharSet.valueOf('E', 'e'), csq) ? TypeFormat.parseInt(csq, 10, cursor) : 0; * int fractionDigits = fraction.digitLength(); * return Decimal.valueOf(significand.E(fractionDigits).plus(fraction), exponent - fractionDigits); * } * [/code] * </p> * * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a> * @version 5.4, November 19, 2009 */ public class Cursor extends ParsePosition implements Reusable { /** * Default constructor. */ public Cursor() { super(0); } /** * Returns a factory produced instance which can be {@link #recycle recycled} * after usage. * * @return a recyclable instance. */ public static Cursor newInstance() { return (Cursor) FACTORY.object(); } private static final ObjectFactory FACTORY = new ObjectFactory() { protected Object create() { return new Cursor(); } }; /** * Recycles the specified factory {@link #newInstance() produced} cursor. * * @param cursor the cursor to recycle. */ public static void recycle(Cursor cursor) { FACTORY.recycle(cursor); } /** * Returns this cursor index. * * @return the index of the next character to parse. */ public final int getIndex() { return super.getIndex(); } /** * Sets the cursor current index. * * @param i the index of the next character to parse. */ public void setIndex(int i) { super.setIndex(i); } /** * Indicates if this cursor points to the end of the specified * character sequence. * * @param csq the character sequence iterated by this cursor. * @return <code>getIndex() >= csq.length()</code> */ public final boolean atEnd(CharSequence csq) { return getIndex() >= csq.length(); } /** * Indicates if this cursor points to the specified character in the * specified character sequence. * * @param c the character to test. * @param csq the character sequence iterated by this cursor. * @return <code>csq.charAt(this.getIndex()) == c</code> */ public final boolean at(char c, CharSequence csq) { int i = getIndex(); return i < csq.length() ? csq.charAt(i) == c : false; } /** * Indicates if this cursor points to any of the specified character in the * specified character sequence. * * @param charSet any of the character to test. * @param csq the character sequence iterated by this cursor. * @return <code>csq.charAt(this.getIndex()) == c</code> */ public final boolean at(CharSet charSet, CharSequence csq) { int i = getIndex(); return i < csq.length() ? charSet.contains(csq.charAt(i)) : false; } /** * Indicates if this cursor points to the specified characters in the * specified sequence. * * @param str the characters to test. * @param csq the character sequence iterated by this cursor. * @return <code>true</code> if this cursor points to the specified * characters; <code>false</code> otherwise. */ public final boolean at(String str, CharSequence csq) { int i = getIndex(); int length = csq.length(); for (int j = 0; j < str.length();) { if ((i >= length) || (str.charAt(j++) != csq.charAt(i++))) return false; } return true; } /** * Returns the next character at this cursor position.The cursor * position is incremented by one. * * @param csq the character sequence iterated by this cursor. * @return the next character this cursor points to. * @throws IndexOutOfBoundsException if {@link #atEnd this.atEnd(csq)} */ public final char nextChar(CharSequence csq) { int i = getIndex(); setIndex(i + 1); return csq.charAt(i); } /** * Moves this cursor forward until it points to a character * different from the specified character. * * @param c the character to skip. * @param csq the character sequence iterated by this cursor. * @return <code>true</code> if this cursor has skipped at least one * character;<code>false</code> otherwise (e.g. end of sequence * reached). */ public final boolean skipAny(char c, CharSequence csq) { int i = getIndex(); int n = csq.length(); for (; (i < n) && (csq.charAt(i) == c); i++) { } if (i == getIndex()) return false; // Cursor did not moved. setIndex(i); return true; } /** * Moves this cursor forward until it points to a character * different from any of the character in the specified set. * For example: [code] * // Reads numbers separated by tabulations or spaces. * FastTable<Integer> numbers = new FastTable<Integer>(); * while (cursor.skipAny(CharSet.SPACE_OR_TAB, csq)) { * numbers.add(TypeFormat.parseInt(csq, cursor)); * }[/code] * * @param charSet the character to skip. * @param csq the character sequence iterated by this cursor. * @return <code>true</code> if this cursor has skipped at least one * character;<code>false</code> otherwise (e.g. end of sequence * reached). */ public final boolean skipAny(CharSet charSet, CharSequence csq) { int i = getIndex(); int n = csq.length(); for (; (i < n) && charSet.contains(csq.charAt(i)); i++) { } if (i == getIndex()) return false; // Cursor did not moved. setIndex(i); return true; } /** * Moves this cursor forward only if at the specified character. * This method is equivalent to: * [code] * if (at(c, csq)) * increment(); * [/code] * * @param c the character to skip. * @param csq the character sequence iterated by this cursor. * @return <code>true</code> if this cursor has skipped the specified * character;<code>false</code> otherwise. */ public final boolean skip(char c, CharSequence csq) { if (this.at(c, csq)) { this.increment(); return true; } else { return false; } } /** * Moves this cursor forward only if at any of the specified character. * This method is equivalent to: * [code] * if (at(charSet, csq)) * increment(); * [/code] * * @param charSet holding the characters to skip. * @param csq the character sequence iterated by this cursor. * @return <code>true</code> if this cursor has skipped any the specified * character;<code>false</code> otherwise. */ public final boolean skip(CharSet charSet, CharSequence csq) { if (this.at(charSet, csq)) { this.increment(); return true; } else { return false; } } /** * Moves this cursor forward only if at the specified string. * This method is equivalent to: * [code] * if (at(str, csq)) * increment(str.length()); * [/code] * * @param str the string to skip. * @param csq the character sequence iterated by this cursor. * @return <code>true</code> if this cursor has skipped the specified * string;<code>false</code> otherwise (e.g. end of sequence * reached). */ public final boolean skip(String str, CharSequence csq) { if (this.at(str, csq)) { this.increment(str.length()); return true; } else { return false; } } /** * Returns the subsequence from the specified cursor position not holding * the specified character. For example:[code] * CharSequence csq = "This is a test"; * for (CharSequence token; (token=cursor.nextToken(csq, ' '))!= null;) { * System.out.println(token); // Prints one word at a time. * }[/code] * * @param csq the character sequence iterated by this cursor. * @param c the character being skipped. * @return the subsequence not holding the specified character or * <code>null</code> if none. */ public final CharSequence nextToken(CharSequence csq, char c) { int n = csq.length(); for (int i = getIndex(); i < n; i++) { if (csq.charAt(i) != c) { int j = i; for (; (++j < n) && (csq.charAt(j) != c);) { // Loop until j at the end of sequence or at specified character. } setIndex(j); return csq.subSequence(i, j); } } setIndex(n); return null; } /** * Returns the subsequence from the specified cursor position not holding * any of the characters specified. For example:[code] * CharSequence csq = "This is a test"; * for (CharSequence token; (token=cursor.nextToken(csq, CharSet.WHITESPACE))!= null;) { * System.out.println(token); // Prints one word at a time. * }[/code] * * @param csq the character sequence iterated by this cursor. * @param charSet the characters being skipped. * @return the subsequence not holding the specified character or * <code>null</code> if none. */ public final CharSequence nextToken(CharSequence csq, CharSet charSet) { int n = csq.length(); for (int i = getIndex(); i < n; i++) { if (!charSet.contains(csq.charAt(i))) { int j = i; for (; (++j < n) && !charSet.contains(csq.charAt(j));) { // Loop until j at the end of sequence or at specified characters. } setIndex(j); return csq.subSequence(i, j); } } setIndex(n); return null; } /** * Increments the cursor index by one. * * @return <code>this</code> */ public final Cursor increment() { return increment(1); } /** * Increments the cursor index by the specified value. * * @param i the increment value. * @return <code>this</code> */ public final Cursor increment(int i) { setIndex(getIndex() + i); return this; } /** * Returns the string representation of this cursor. * * @return the index value as a string. */ public String toString() { return "Index: " + getIndex(); } /** * Indicates if this cursor is equals to the specified object. * * @return <code>true</code> if the specified object is a cursor * at the same index; <code>false</code> otherwise. */ public boolean equals(Object obj) { if (obj == null) return false; if (!(obj instanceof Cursor)) return false; return getIndex() == ((Cursor) obj).getIndex(); } /** * Returns the hash code for this cursor. * * @return the hash code value for this object */ public int hashCode() { return getIndex(); } /** * Resets this cursor instance. * * @see Reusable */ public void reset() { super.setIndex(0); super.setErrorIndex(-1); } }