/** * Copyright (c) 2009 - 2011 AppWork UG(haftungsbeschränkt) <e-mail@appwork.org> * * This file is part of org.appwork.storage.simplejson * * This software is licensed under the Artistic License 2.0, * see the LICENSE file or http://www.opensource.org/licenses/artistic-license-2.0.php * for details */ package org.appwork.storage.simplejson; /** * @author thomas */ public class JSonFactory { public static boolean DEBUG = false; private int global = 0; private char c; private final String str; final StringBuilder sb; private final StringBuilder sb2; private int counter; private String debug; public JSonFactory(final String json) { this.str = json; this.sb = new StringBuilder(); this.sb2 = new StringBuilder(); this.counter = 0; } private ParserException bam(final String expected) { final String pre = this.str.substring(Math.max(this.global - 20, 0), this.global); final StringBuilder sb = new StringBuilder(); sb.append(expected); sb.append("\r\n\t"); sb.append(pre); sb.append(this.str.substring(this.global, Math.min(this.str.length(), this.global + 20))); sb.append("\r\n\t"); for (int i = 1; i < pre.length(); i++) { sb.append("-"); } sb.append('\u2934'); return new ParserException(sb.toString()); } private String findString() throws ParserException { // string try { this.sb.delete(0, this.sb.length()); this.c = this.str.charAt(this.global++); if (this.c != '\"') { throw this.bam("'\"' expected"); } boolean escaped = false; while (true) { this.c = this.str.charAt(this.global++); switch (this.c) { case '\"': return this.sb.toString(); case '\\': escaped = true; while ((this.c = this.str.charAt(this.global++)) == '\\') { escaped = !escaped; if (!escaped) { this.sb.append("\\"); } } if (escaped) { switch (this.c) { case '"': this.sb.append('"'); continue; case '\\': this.sb.append('\\'); continue; case 'r': this.sb.append('\r'); continue; case 'n': this.sb.append('\n'); continue; case 't': this.sb.append('\t'); continue; case 'f': this.sb.append('\f'); continue; case 'b': this.sb.append('\b'); continue; case 'u': this.sb2.delete(0, this.sb2.length()); this.global++; this.counter = this.global + 4; for (; this.global < this.counter; this.global++) { this.c = this.getChar(); if (this.sb2.length() > 0 || this.c != '0') { this.sb2.append(this.c); } } this.global--; this.sb.append((char) Short.parseShort(this.sb2.toString(), 16)); continue; default: throw this.bam("illegal escape char"); } } break; default: this.sb.append(this.c); } } } catch (final StringIndexOutOfBoundsException e) { this.global--; throw this.bam("Unexpected End of String \"" + this.sb.toString()); } } private char getChar() { if (JSonFactory.DEBUG) { final String pos = this.str.substring(0, this.global); this.debug = pos + this.str.substring(this.global) + "\r\n"; for (int i = 0; i < pos.length(); i++) { this.debug += "-"; } this.debug += '\u2934'; System.err.println(this.debug); } return this.str.charAt(this.global); } public JSonNode parse() throws ParserException { final JSonNode ret = this.parseValue(); this.skipWhiteSpace(); if (this.global != this.str.length()) { this.global++; throw this.bam("Unexpected End of JSonString"); } return ret; } private JSonArray parseArray() throws ParserException { this.global++; final JSonArray ret = new JSonArray(); while (true) { // skip whitespace this.skipWhiteSpace(); this.c = this.getChar(); switch (this.c) { case ']': this.global++; return ret; case ',': throw this.bam("Value missing"); default: ret.add(this.parseValue()); this.skipWhiteSpace(); this.c = this.getChar(); switch (this.c) { case ',': // ok another round: this.global++; continue; case ']': // end this.global++; return ret; default: throw this.bam("']' or ',' expected"); } } } } private JSonValue parseNumber() throws ParserException, NoNumberException { this.sb.delete(0, this.sb.length()); boolean pointFound = false; boolean potFound = false; this.c = this.getChar(); if (this.c == '+' || this.c == '-' || Character.isDigit(this.c)) { this.sb.append(this.c); while (this.global + 1 < this.str.length()) { this.global++; this.c = this.getChar(); if (Character.isDigit(this.c) || !pointFound && this.c == '.' || pointFound && this.c == 'e' || pointFound && this.c == 'E' || potFound && this.c == '+' || potFound && this.c == '-') { if (this.c == '.') { pointFound = true; } else if (pointFound && (this.c == 'e' || this.c == 'E')) { potFound = true; } this.sb.append(this.c); } else { this.global--; break; } } this.global++; if (pointFound) { return new JSonValue(Double.parseDouble(this.sb.toString())); } else { return new JSonValue(Long.parseLong(this.sb.toString())); } } else { throw new NoNumberException(); } } private JSonObject parseObject() throws ParserException { String key; this.global++; final JSonObject ret = new JSonObject(); while (true) { // check for object end markers this.skipWhiteSpace(); this.c = this.getChar(); switch (this.c) { case '}': this.global++; return ret; case '"': key = this.findString(); this.skipWhiteSpace(); this.c = this.getChar(); if (this.c != ':') { throw this.bam("':' expected"); } this.global++; this.skipWhiteSpace(); ret.put(key, this.parseValue()); this.skipWhiteSpace(); this.c = this.getChar(); switch (this.c) { case ',': // ok another value...probably this.global++; continue; case '}': // end of object: this.global++; return ret; default: throw this.bam("', or }' expected"); } default: throw this.bam("\", or }' expected"); } } } private JSonValue parseString() throws ParserException { return new JSonValue(this.findString()); } private JSonNode parseValue() throws ParserException { this.global = this.skipWhiteSpace(); switch (this.getChar()) { case '{': return this.parseObject(); case '[': return this.parseArray(); case 'n': // null this.global += 4; return new JSonValue(null); case 't': // true; this.global += 4; return new JSonValue(true); case 'f': // false; this.global += 5; return new JSonValue(false); case '"': return this.parseString(); } try { return this.parseNumber(); } catch (final NoNumberException e) { this.global++; throw this.bam("Illegal Char"); } } private int skipWhiteSpace() { while (this.global < this.str.length()) { if (!Character.isWhitespace(this.str.charAt(this.global++))) { this.global--; break; } } return this.global; } }