package net.varkhan.base.conversion.formats; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.*; /** * <b>XON syntax parser and converter</b>. * <p/> * XON is an extension of JSON that:<ul> * <li>recognizes both <em>arrays</em> (delimited by square brackets, as in JSON), * and <em>collections</em> (unordered bags of values, delimited by parentheses),</li> * <li>allows map syntax to omit field values for {@literal null} values, allowing * their use as a set.</li> * </ul><p/> * This class provides several static utilities, and helper objects, to serialize and deserialize * basic Java objects (boolean, number, String, arrays, List and Map) to and from their * representation as XON text. * <p/> * * @author varkhan * @date 3/18/12 * @time 2:38 PM */ public class Xon { protected Xon() { } /********************************************************************************* ** Literals and separators **/ protected static final char SEP_ENTRY =':'; protected static final char SEP_ELEMENT =','; protected static final char SEP_STRING ='"'; protected static final char SEP_VECTOR_S='['; protected static final char SEP_VECTOR_E=']'; protected static final char SEP_COLLEC_S='('; protected static final char SEP_COLLEC_E=')'; protected static final char SEP_OBJECT_S='{'; protected static final char SEP_OBJECT_E='}'; protected static final String LITERAL_TRUE ="true"; protected static final String LITERAL_FALSE="false"; protected static final String LITERAL_NULL ="null"; /********************************************************************************* ** XON equality testing **/ /** * Tests for deep equality of two XON objects. * * @param val1 the first object * @param val2 the second object * @return {@literal true} <i>iff</i> the two objects have the same XON type, and * either have the same value, or contain equal objects in the same positions or fields. */ @SuppressWarnings("unchecked") public static boolean equals(Object val1, Object val2) { if(val1==val2) return true; if(val1==null || val2==null) return false; if(val1 instanceof Boolean && val2 instanceof Boolean) return equalsBoolean((Boolean) val1, (Boolean) val2); if(val1 instanceof Number && val2 instanceof Number) return equalsNumber((Number) val1, (Number) val2); if(val1 instanceof CharSequence && val2 instanceof CharSequence) return equalsString((CharSequence) val1, (CharSequence) val2); // if(obj instanceof Map) // return equals((Map)val1, (Map)val2); // else if(obj instanceof List) { // return equals((List)val1, (List)val2); // } if(val1.getClass().isArray() && val2.getClass().isArray()) return equalsVector((Object[]) val1, (Object[]) val2); if(val1 instanceof Map && val2 instanceof Map) return equalsObject((Map<CharSequence,Object>) val1, (Map<CharSequence,Object>) val2); if(val1 instanceof Collection && val2 instanceof Collection) return equalsCollec((Collection<Object>) val1, (Collection<Object>) val2); // Incompatible types, no equality return false; } public static boolean equalsBoolean(Boolean val1, Boolean val2) { if(val1==val2) return true; if(val1==null || val2==null) return false; return val1.booleanValue()==val2.booleanValue(); } public static boolean equalsNumber(Number val1, Number val2) { if(val1==val2) return true; if(val1==null || val2==null) return false; return val1.longValue()==val2.longValue() && val1.doubleValue()==val2.doubleValue(); } public static boolean equalsString(CharSequence val1, CharSequence val2) { if(val1==val2) return true; if(val1==null || val2==null) return false; int len=val1.length(); if(len!=val2.length()) return false; for(int i=0; i<len; i++) if(val1.charAt(i)!=val2.charAt(i)) return false; return true; } public static <T> boolean equalsVector(T[] val1, T[] val2) { if(val1==val2) return true; if(val1==null || val2==null) return false; int len=val1.length; if(len!=val2.length) return false; for(int i=0; i<len; i++) if(!equals(val1[i],val2[i])) return false; return true; } public static <K,V> boolean equalsObject(Map<K,V> val1, Map<K,V> val2) { if(val1==val2) return true; if(val1==null || val2==null) return false; if(val1.size()!=val2.size()) return false; for(Map.Entry<K,V> e: val1.entrySet()) if(!equals(e.getValue(),val2.get(e.getKey()))) return false; for(Map.Entry<K,V> e: val2.entrySet()) if(!equals(val1.get(e.getKey()),e.getValue())) return false; return true; } public static <T> boolean equalsCollec(Collection<T> val1, Collection<T> val2) { if(val1==val2) return true; if(val1==null || val2==null) return false; Iterator<T> itr1 = val1.iterator(); Iterator<T> itr2 = val2.iterator(); while(itr1.hasNext() && itr2.hasNext()) if(!equals(itr1.next(),itr2.next())) return false; return !(itr1.hasNext()||itr2.hasNext()); } /********************************************************************************* ** XON writing **/ /** * <b>An interface that can be implemented by objects that can be written as Xon.</b> * <p/> * This interface defines a #writeXon(Appendable) method, that handles the * serialization of the object's data as Xon to a character stream. * <p/> * It is the responsibility of the implementor to ensure that <em>valid Xon</em> * is produced. */ public static interface Writable { public void writeXon(Appendable out) throws IOException; } /** * Writes an object as a String. * * @param val the object to write * @return a JSON string representation of thr object */ public static String write(Object val) { StringBuilder buf=new StringBuilder(); try { write(buf, val); } catch(IOException e) { /* ignore: StringBuilder doesn't throw this */ } return buf.toString(); } /** * Writes an object to an {@link Appendable}. * * @param out the output Appendable * @param obj the object to write * @param <A> the Appendable type * * @return the output Appendable (to facilitate chaining) * * @throws java.io.IOException if the output Appendable generated an exception */ @SuppressWarnings("unchecked") public static <A extends Appendable> A write(A out, Object obj) throws IOException { if(obj==null) { writeNull(out); } else if(obj instanceof Boolean) { writeBoolean(out, (Boolean) obj); } else if(obj instanceof Number) { writeNumber(out, (Number) obj); } else if(obj instanceof CharSequence) { out.append(SEP_STRING); writeString(out, (CharSequence) obj); out.append(SEP_STRING); } // else if(obj instanceof Map) { // out.append('{'); // writeMap(out, (Map<CharSequence,Object>) obj); // out.append('}'); // } // else if(obj instanceof List) { // out.append('('); // writeList(out, (List<Object>) obj); // out.append(')'); // } else if(obj.getClass().isArray()) { out.append(SEP_VECTOR_S); writeVector(out, (Object[]) obj); out.append(SEP_VECTOR_E); } else if(obj instanceof Map) { out.append('{'); writeObject(out, (Map<CharSequence,Object>) obj); out.append(SEP_OBJECT_E); } else if(obj instanceof Collection) { out.append(SEP_COLLEC_S); writeCollec(out, (Collection<Object>) obj); out.append(SEP_COLLEC_E); } else if(obj instanceof Writable) { ((Writable) obj).writeXon(out); } else throw new IllegalArgumentException("Cannot serialize object to XON: unknown class "+obj.getClass().getCanonicalName()); return out; } /** * Writes a map to an {@link Appendable}. * * @param out the output Appendable * @param map the map to write * @param <A> the Appendable type * * @return the output Appendable (to facilitate chaining) * * @throws java.io.IOException if the output Appendable generated an exception */ public static <A extends Appendable> A writeObject(A out, Map<? extends CharSequence,?> map) throws IOException { @SuppressWarnings("unchecked") Iterator<Map.Entry<CharSequence, Object>> it = ((Map<CharSequence,Object>) map).entrySet().iterator(); while(it.hasNext()) { Map.Entry<CharSequence, ?> x = it.next(); writeField(out, x.getKey().toString(), x.getValue()); if(it.hasNext()) out.append(SEP_ELEMENT); } return out; } // /** // * Writes a map to an {@link Appendable}. // * // * @param out the output Appendable // * @param map the map to write // * @param <A> the Appendable type // * // * @return the output Appendable (to facilitate chaining) // * // * @throws java.io.IOException if the output Appendable generated an exception // */ // public static <A extends Appendable> A writeMap(A out, Map<? extends CharSequence,?> map) throws IOException { // @SuppressWarnings("unchecked") // Iterator<? extends Map.Entry<CharSequence,Object>> it = ((Map<CharSequence,Object>) map).iterator(); // while(it.hasNext()) { // Map.Entry<CharSequence, ?> x = it.next(); // writeField(out, x.getKey().toString(), x.getValue()); // if(it.hasNext()) out.append(','); // } // return out; // } /** * Writes a collection to an {@link Appendable}. * * @param out the output Appendable * @param lst the collection to write * @param <A> the Appendable type * * @return the output Appendable (to facilitate chaining) * * @throws java.io.IOException if the output Appendable generated an exception */ public static <A extends Appendable> A writeCollec(A out, Collection<?> lst) throws IOException { boolean f = true; for(Object obj : lst) { if(f) f=false; else out.append(SEP_ELEMENT); write(out, obj); } return out; } /** * Writes a vector to an {@link Appendable}. * * @param out the output Appendable * @param lst the variadic array to write * @param <A> the Appendable type * * @return the output Appendable (to facilitate chaining) * * @throws java.io.IOException if the output Appendable generated an exception */ public static <A extends Appendable> A writeVector(A out, Object... lst) throws IOException { boolean f = true; for(Object obj : lst) { if(f) f=false; else out.append(SEP_ELEMENT); write(out, obj); } return out; } // /** // * Writes a list to an {@link Appendable}. // * // * @param out the output Appendable // * @param lst the list to write // * @param <A> the Appendable type // * // * @return the output Appendable (to facilitate chaining) // * // * @throws java.io.IOException if the output Appendable generated an exception // */ // public static <A extends Appendable> A writeList(A out, List<?> lst) throws IOException { // boolean f = true; // for(Object obj : lst) { // if(f) f=false; // else out.append(','); // writeObject(out, obj); // } // return out; // } public static <A extends Appendable> A writeField(A out, CharSequence key, Object val) throws IOException { out.append(SEP_STRING); writeName(out, key); out.append(SEP_STRING); if(val!=null) { out.append(SEP_ENTRY); write(out, val); } return out; } public static <A extends Appendable> A writeName(A out, CharSequence str) throws IOException { final int ls = str.length(); for(int i=0;i<ls;i++) { char c = str.charAt(i); switch (c) { case '\\': out.append("\\\\"); break; case '\"': out.append("\\\""); break; default: if (c >= ' ' && c < 127) out.append(c); else throw new IOException("Invalid XON field name: \""+str+"\""); break; } } return out; } public static <A extends Appendable> A writeString(A out, CharSequence str) throws IOException { final int ls = str.length(); for(int i=0;i<ls;i++) { char c= str.charAt(i); switch (c) { case '\\': out.append("\\\\"); break; case '\"': out.append("\\\""); break; case '\b': out.append("\\b"); break; case '\f': out.append("\\f"); break; case '\n': out.append("\\n"); break; case '\r': out.append("\\r"); break; case '\t': out.append("\\t"); break; default: if (c >= ' ' && c < 127) out.append(c); else out.append(String.format("\\u%04x" ,(int)c)); break; } } return out; } public static <A extends Appendable> A writeNumber(A out, Number n) throws IOException { if(n.longValue()==n.doubleValue()) { out.append(Long.toString(n.longValue())); } else { out.append(Double.toString(n.doubleValue())); } return out; } public static <A extends Appendable> A writeBoolean(A out, boolean b) throws IOException { if(b) out.append(LITERAL_TRUE); else out.append(LITERAL_FALSE); return out; } public static <A extends Appendable> A writeNull(A out) throws IOException { out.append(LITERAL_NULL); return out; } /********************************************************************************* ** XON reading **/ /** * State-aware wrapper for a reader and current char, needed to be able to read JSON types that do not have delimiters */ public static class Parser { private final Reader in; private int st = ' '; private int ln = 0; private int cn = 0; public Parser(Reader in) { this.in=in; } /** * The last character read. * @return the last character read, or -1 if EOS has been reached */ public int last() { return st; } /** * Reads one character from the stream. * @return the character read from the stream, or -1 if EOS has been reached * @throws java.io.IOException if an I/O error occurred while reading from the stream */ public int next() throws IOException { st = in.read(); if(st=='\n') { ln++; cn=0; } else if(st>=0) cn ++; return st; } /** * Reads and discards all whitespace characters until a non-whitespace character is reached * @return the last (non-whitespace) character read from the stream, or -1 if EOS has been reached * @throws java.io.IOException if an I/O error occurred while reading from the stream */ public int skipWhitespace() throws IOException { while(st>=0 && isWhiteSpace(st)) { next(); } return st; } protected boolean readBoolean() throws IOException, FormatException { StringBuilder buf = new StringBuilder(); while(st>=0 && st>='a' && st<='z') { buf.append((char)st); next(); } if(isEqualSequence(buf, LITERAL_FALSE)) return false; if(isEqualSequence(buf, LITERAL_TRUE)) return true; throw exception("Invalid boolean format",buf); } protected Number readNumber() throws IOException, FormatException { StringBuilder buf = new StringBuilder(); boolean isInteger = true; boolean isFloat = true; while(st>=0 && !isWhiteSpace(st)) { isInteger &= isValidIntegerChar(st); isFloat &= isValidNumberChar(st); buf.append((char)st); next(); } if(isInteger) try { return Long.parseLong(buf.toString()); } catch(NumberFormatException e) { throw exception("Invalid number format",buf,e); } if(isFloat) try { return Double.parseDouble(buf.toString()); } catch(NumberFormatException e) { throw exception("Invalid number format",buf,e); } throw exception("Invalid number format",buf); } protected Object readLiteral() throws IOException, FormatException { StringBuilder buf = new StringBuilder(); boolean isInteger = true; boolean isFloat = true; while(st>=0 && !isWhiteSpace(st) && !isReservedChar(st)) { isInteger &= isValidIntegerChar(st); isFloat &= isValidNumberChar(st); buf.append((char)st); next(); } if(isEqualSequence(buf, LITERAL_NULL)) return null; if(isEqualSequence(buf, LITERAL_FALSE)) return false; if(isEqualSequence(buf, LITERAL_TRUE)) return true; if(isInteger) try { return Long.parseLong(buf.toString()); } catch(NumberFormatException e) { throw exception("Invalid number format", buf, e); } if(isFloat) try { return Double.parseDouble(buf.toString()); } catch(NumberFormatException e) { throw exception("Invalid number format", buf, e); } throw exception("Invalid literal", buf); } protected String readString(int t) throws IOException, FormatException { StringBuilder buf = new StringBuilder(); // Read all characters until an unescaped terminator is found while(st>=0 && st!=t) { // Decode escape sequences if(st=='\\') { next(); if(st<=0) throw new IOException("Unterminated character escape"); switch(st) { case '\\': buf.append('\\'); break; case '\"': buf.append('\"'); break; case '\'': buf.append('\''); break; case 'b': buf.append('\b'); break; case 'f': buf.append('\f'); break; case 'n': buf.append('\n'); break; case 'r': buf.append('\r'); break; case 't': buf.append('\t'); break; case 'u': // Decode unicode escapes int x = 0; next(); if(st<0) throw exception("Unterminated unicode escape"); int d = asHexDigit(st); if(d<0) throw exception("Invalid unicode escape character "+(char) st); x |= d<<12; next(); if(st<0) throw exception("Unterminated unicode escape"); d = asHexDigit(st); if(d<0) throw exception("Invalid unicode escape character "+(char) st); x |= d<<8; next(); if(st<0) throw exception("Unterminated unicode escape"); d = asHexDigit(st); if(d<0) throw exception("Invalid unicode escape character "+(char) st); x |= d<<4; next(); if(st<0) throw exception("Unterminated unicode escape"); d = asHexDigit(st); if(d<0) throw exception("Invalid unicode escape character "+(char) st); x |= d; buf.append((char)x); break; default: buf.append((char)st); break; } } else buf.append((char)st); next(); } return buf.toString(); } protected List<Object> readCollec(char r, char t) throws IOException, FormatException { List<Object> lst = new ArrayList<Object>(); // Read all objects until t is found or the end of the stream is reached while(st>=0) { // Skip leading whitespace skipWhitespace(); if(st==t|| st<0) break; Object val = read(); lst.add(val); // Skip trailing whitespace skipWhitespace(); // Return on terminator if(st==t || st<0) break; // Validate and skip separator else if(st==r) next(); else throw exception("Unexpected character in collection"); } return lst; } protected Object[] readVector(char r, char t) throws IOException, FormatException { Object[] ary = new Object[16]; int len = 0; // Read all objects until t is found or the end of the stream is reached while(st>=0) { // Skip leading whitespace skipWhitespace(); if(st==t|| st<0) break; Object val = read(); if(len>=ary.length) { // Multiply capacity by 1.25 Object[] a = new Object[ary.length+(ary.length>>2)+1]; System.arraycopy(ary,0,a,0,len); ary = a; } ary[len++] = (val); // Skip trailing whitespace skipWhitespace(); // Return on terminator if(st==t || st<0) break; // Validate and skip separator else if(st==r) next(); else throw exception("Unexpected character in array"); } if(len<ary.length) { Object[] a = new Object[len]; System.arraycopy(ary,0,a,0,len); return a; } return ary; } protected Map<CharSequence,Object> readObject(char f, char k, char r, char t) throws IOException, FormatException { Map<CharSequence,Object> map = new LinkedHashMap<CharSequence,Object>(); // Read all objects until t is found or the end of the stream is reached while(st>=0) { // Skip leading whitespace skipWhitespace(); // Return on terminator if(st==t||st<0) break; String key; if(st==f) { // Skip first quote next(); key = readString(f); if(st!=f) throw exception("Unterminated field"); // Skip last quote next(); } else { // Parse in raw field: sequence of non-whitespace, non-reserved chars StringBuilder buf = new StringBuilder(); while(st>=0 && !isWhiteSpace(st) && !isReservedChar(st)) { buf.append((char)st); next(); } key = buf.toString(); } // Skip intermediary whitespace skipWhitespace(); // Return on terminator if(st==t||st<0) { map.put(key, null); break; } // Validate and skip entry separator, if no value specified else if(st==r) { map.put(key, null); next(); continue; } // Otherwise, verify we have a key separator else if(st!=k) throw exception("Unexpected character in map"); // Skip key separator next(); // Skip intermediary whitespace skipWhitespace(); Object val = read(); map.put(key, val); // Skip trailing whitespace skipWhitespace(); // Return on terminator if(st==t||st<0) break; // Validate and skip entry separator else if(st==r) next(); else throw exception("Unexpected character in map"); } return map; } protected Object read() throws IOException, FormatException { switch(st) { case SEP_OBJECT_S: { next(); Map<CharSequence,Object> obj=readObject(SEP_STRING, SEP_ENTRY, SEP_ELEMENT, SEP_OBJECT_E); if(st!=SEP_OBJECT_E) throw exception("Unterminated object"); next(); return obj; } case SEP_COLLEC_S: { // Skip first parenthesis next(); Collection<Object> obj=readCollec(SEP_ELEMENT, SEP_COLLEC_E); if(st!=SEP_COLLEC_E) throw exception("Unterminated collection"); next(); return obj; } case SEP_VECTOR_S: { // Skip first bracket next(); Object[] obj=readVector(SEP_ELEMENT, SEP_VECTOR_E); if(st!=SEP_VECTOR_E) throw exception("Unterminated vector"); next(); return obj; } case SEP_STRING: { // Skip first quote next(); String obj=readString(SEP_STRING); if(st!=SEP_STRING) throw exception("Unterminated string"); next(); return obj; } default: return readLiteral(); } } /** * Create a new format exception. * * @return a FormatException indicating line and column numbers */ public FormatException exception(String msg) { return new FormatException(msg + " at ln:"+ln+",cn:"+cn+" near '"+(char)st+"'", ln, cn, null); } /** * Create a new format exception. * * @return a FormatException indicating line and column numbers */ public FormatException exception(String msg, CharSequence ctx) { return new FormatException(msg + " at ln:"+ln+",cn:"+cn+" near '"+(char)st+"': "+ctx, ln, cn, ctx.toString()); } /** * Create a new format exception. * * @return a FormatException indicating line and column numbers */ public FormatException exception(String msg, CharSequence ctx, Throwable exc) { return new FormatException(msg + " at ln:"+ln+",cn:"+cn+" near '"+(char)st+"': "+ctx, ln, cn, ctx.toString(), exc); } } /** * Read a XON value from a string. * * @param str the string representation of the XON value * @return the XON value * @throws FormatException if the input is not valid XON */ public static Object read(CharSequence str) throws FormatException { if(str==null) return null; try { return read(new StringReader(str.toString())); } catch(IOException e) { return null; } } /** * Read a XON value from a stream. * * @param in the input stream containing a representation of the XON value * @return the XON value * @throws FormatException if the input is not valid XON * @throws IOException if reading from the input failed */ public static Object read(Reader in) throws IOException, FormatException { Parser p = new Parser(in); p.skipWhitespace(); return p.read(); } /** * Read a XON number from a stream. * * @param in the input stream containing a representation of the XON number * @return the XON number * @throws FormatException if the input is not valid XON * @throws IOException if reading from the input failed */ public static Number readNumber(Reader in) throws IOException, FormatException { Parser p = new Parser(in); p.skipWhitespace(); return p.readNumber(); } /** * Read a XON boolean from a stream. * * @param in the input stream containing a representation of the XON boolean * @return the XON boolean * @throws FormatException if the input is not valid XON * @throws IOException if reading from the input failed */ public static boolean readBoolean(Reader in) throws IOException, FormatException { Parser p = new Parser(in); p.skipWhitespace(); return p.readBoolean(); } /** * Read a XON string from a stream. * * @param in the input stream containing a representation of the XON string * @return the XON string * @throws FormatException if the input is not valid XON * @throws IOException if reading from the input failed */ public static String readString(Reader in) throws IOException, FormatException { Parser p = new Parser(in); int c = p.skipWhitespace(); char t=SEP_STRING; if(c!=t) throw p.exception("Invalid string format"); // Skip first quote p.next(); String obj=p.readString(t); c=p.last(); if(c!=t) throw p.exception("Unterminated string"); p.next(); return obj; } /** * Read a XON collection from a stream. * * @param in the input stream containing a representation of the XON collection * @return the XON collection * @throws FormatException if the input is not valid XON * @throws IOException if reading from the input failed */ public static List<Object> readCollec(Reader in) throws IOException, FormatException { Parser p = new Parser(in); int c = p.skipWhitespace(); if(c!=SEP_COLLEC_S) throw p.exception("Invalid collection format"); // Skip leading ( p.next(); List<Object> obj=p.readCollec(SEP_ELEMENT, SEP_COLLEC_E); c=p.last(); if(c!=SEP_COLLEC_E) throw p.exception("Unterminated collection"); // Skip trailing ) p.next(); return obj; } /** * Read a XON vector from a stream. * * @param in the input stream containing a representation of the XON vector * @return the XON vector * @throws FormatException if the input is not valid XON * @throws IOException if reading from the input failed */ public static Object[] readVector(Reader in) throws IOException, FormatException { Parser p = new Parser(in); int c = p.skipWhitespace(); if(c!=SEP_VECTOR_S) throw p.exception("Invalid vector format"); // Skip leading [ p.next(); Object[] obj=p.readVector(SEP_ELEMENT, SEP_VECTOR_E); c=p.last(); if(c!=SEP_VECTOR_E) throw p.exception("Unterminated vector"); // Skip trailing ] p.next(); return obj; } /** * Read a XON object (map) from a stream. * * @param in the input stream containing a representation of the XON object * @return the XON object * @throws FormatException if the input is not valid XON * @throws IOException if reading from the input failed */ public static Map<CharSequence,Object> readObject(Reader in) throws IOException, FormatException { Parser p = new Parser(in); int c = p.skipWhitespace(); if(c!=SEP_OBJECT_S) throw p.exception("Invalid map format"); // Skip leading { p.next(); Map<CharSequence,Object> obj=p.readObject(SEP_STRING, SEP_ENTRY, SEP_ELEMENT, SEP_OBJECT_E); c=p.last(); if(c!=SEP_OBJECT_E) throw p.exception("Unterminated map"); // Skip trailing p.next(); return obj; } /** * Checks whether a character is white-space. * * @param c the character to check * @return {@literal true} if the character is whitespace */ public static boolean isWhiteSpace(int c) { return c==' '||c=='\n'||c=='\r'||c=='\t'; } /** * Converts a decimal digit to its corresponding numeric value. * * @param c the character to convert * @return the decimal value of the character, or {@literal -1} if it is not a valid decimal digit */ protected static int asDecDigit(int c) { if(c>='0' && c<='9') return c-'0'; return -1; } /** * Converts an hexadecimal digit to its corresponding numeric value. * * @param c the character to convert * @return the hexadecimal value of the character, or {@literal -1} if it is not a valid hexadecimal digit */ protected static int asHexDigit(int c) { if(c>='0' && c<='9') return c-'0'; if(c>='A' && c<='F') return c-'A'+10; if(c>='a' && c<='f') return c-'a'+10; return -1; } /** * Checks whether a character is legal in the character representation of an integer number. * * @param c the character to check * @return {@literal true} if the character is legal in an integer number */ public static boolean isValidIntegerChar(int c) { return (c>='0' && c<='9') || c=='-' || c=='+'; } /** * Checks whether a character is legal in the character representation of a floating point number. * * @param c the character to check * @return {@literal true} if the character is legal in a floating point number */ public static boolean isValidNumberChar(int c) { return (c>='0' && c<='9') || c=='-' || c=='+' || c=='.' || c=='e' || c=='E'; } /** * Checks whether a character is a reserved XON character. * * @param c the character to check * @return {@literal true} if the character is one of ,:()[]{}'" */ public static boolean isReservedChar(int c) { return c=='\'' || c==SEP_STRING || c==SEP_ELEMENT || c==SEP_ENTRY || c==SEP_COLLEC_S || c==SEP_COLLEC_E || c==SEP_VECTOR_S || c==SEP_VECTOR_E || c==SEP_OBJECT_S || c==SEP_OBJECT_E ; } /** * Checks whether two character sequences are identical. * * @param o1 the first sequence * @param o2 the second sequence * @return {@literal true} iff the two sequences are both {@literal null}, * or have the same length and identical characters at each respective index */ protected static boolean isEqualSequence(CharSequence o1, CharSequence o2) { if(o1==o2) return true; if(o1==null || o2==null) return false; int l1=o1.length(); if(l1!=o2.length()) return false; for(int i=0; i<l1; i++) if(o1.charAt(i)!=o2.charAt(i)) return false; return true; } }