/**
* Copyright 2010 The ForPlay Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package forplay.html;
import java.util.ArrayList;
import com.google.gwt.core.client.JavaScriptObject;
import forplay.core.Asserts;
import forplay.core.Json;
class HtmlJson implements Json {
class HtmlWriter implements Json.Writer {
private StringBuilder sb = new StringBuilder();
private String key;
private ArrayList<Boolean> inArrayStack = new ArrayList<Boolean>();
private ArrayList<Boolean> isFirstValueStack = new ArrayList<Boolean>();
@Override
public void array() {
maybePrependKey();
sb.append("[");
pushInArray(true);
pushIsFirstValue(true);
}
@Override
public void endArray() {
sb.append("]");
popInArray();
popIsFirstValue();
}
@Override
public void endObject() {
sb.append("}");
popInArray();
popIsFirstValue();
}
@Override
public void key(String key) {
Asserts.checkState(this.key == null);
this.key = key;
}
@Override
public void object() {
maybePrependKey(true);
sb.append("{");
pushInArray(false);
pushIsFirstValue(true);
}
@Override
public void value(boolean x) {
maybePrependKey();
sb.append(x);
}
@Override
public void value(double x) {
maybePrependKey();
sb.append(x);
}
@Override
public void value(int x) {
maybePrependKey();
sb.append(x);
}
@Override
public void value(String x) {
maybePrependKey();
sb.append("\"");
sb.append(x);
sb.append("\"");
}
@Override
public String write() {
return sb.toString();
}
private void maybePrependKey() {
maybePrependKey(false);
}
/**
* Prepend the key if not in an array.
*
* Note: if this isn't the first key, we output a leading comma as well.
*/
private void maybePrependKey(boolean isObject) {
// Special case for the opening object.
if (isObject && inArrayStack.size() == 0) {
return;
}
if (isFirstValue()) {
popIsFirstValue();
pushIsFirstValue(false);
} else {
sb.append(",");
}
if (inArray()) {
Asserts.checkState(this.key == null);
} else {
Asserts.checkState(this.key != null);
sb.append("\"");
sb.append(key);
sb.append("\":");
key = null;
}
}
private void pushInArray(boolean inArray) {
inArrayStack.add(inArray);
}
private boolean popInArray() {
return inArrayStack.remove(inArrayStack.size() - 1);
}
private boolean inArray() {
return inArrayStack.get(inArrayStack.size() - 1);
}
private void pushIsFirstValue(boolean isFirstValue) {
isFirstValueStack.add(isFirstValue);
}
private boolean popIsFirstValue() {
return isFirstValueStack.remove(isFirstValueStack.size() - 1);
}
private boolean isFirstValue() {
return isFirstValueStack.get(isFirstValueStack.size() - 1);
}
}
static class HtmlArray extends JavaScriptObject implements Json.Array {
protected HtmlArray() {
}
@Override
public final native Array getArray(int index) /*-{
return this[index];
}-*/;
@Override
public final native boolean getBoolean(int index) /*-{
return this[index];
}-*/;
// FIXME TODO XXX: remove this parseFloat once we fix all the JSON
@Override
public final native double getNumber(int index) /*-{
return parseFloat(this[index]);
}-*/;
@Override
public final native int getInt(int index) /*-{
if (!this[index]) return 0;
return parseInt(this[index]);
}-*/;
@Override
public final native Object getObject(int index) /*-{
return this[index];
}-*/;
@Override
public final native String getString(int index) /*-{
return this[index];
}-*/;
@Override
public final native int length() /*-{
return this.length;
}-*/;
}
static class HtmlObject extends JavaScriptObject implements Json.Object {
protected HtmlObject() {
}
@Override
public final native Array getArray(String key) /*-{
return this[key];
}-*/;
@Override
public final native boolean getBoolean(String key) /*-{
return this[key];
}-*/;
@Override
public final native int getInt(String key) /*-{
if (!this[key]) return 0;
return parseInt(this[key]);
}-*/;
// FIXME TODO XXX: remove this parseFloat once we fix all the JSON
@Override
public final native double getNumber(String key) /*-{
return parseFloat(this[key]);
}-*/;
@Override
public final native Object getObject(String key) /*-{
return this[key];
}-*/;
@Override
public final native String getString(String key) /*-{
return this[key];
}-*/;
@Override
public final native Array getKeys() /*-{
if (Object.prototype.keys) { return this.keys(); }
var keys = [];
for (var key in this) if (this.hasOwnProperty(key)) {
keys.push(key);
}
return keys;
}-*/;
}
private static native JavaScriptObject jsonParse(String json) /*-{
return JSON.parse(json);
}-*/;
@Override
public Writer newWriter() {
return new HtmlWriter();
}
@Override
public Object parse(String json) {
HtmlObject object = jsonParse(json).cast();
return object;
}
}