blob: f8586eae4ab1f282070540b1d80bff644e3bfddc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.jackrabbit.oak.commons.json;
/**
* A fast Jsop writer / reader.
*/
public class JsopStream implements JsopReader, JsopWriter {
private boolean needComma;
private int len, pos, lastPos, valuesLen;
private int[] tokens = new int[4];
private Object[] values = new Object[4];
// write
@Override
public JsopStream append(JsopWriter w) {
JsopStream s = (JsopStream) w;
for (int i = s.pos; i < s.len; i++) {
int token = s.tokens[i];
switch (token & 255) {
case STRING:
case NUMBER:
case IDENTIFIER:
case COMMENT:
Object o = s.values[token >> 8];
addToken((token & 255) + addValue(o));
break;
default:
addToken(token);
}
}
return this;
}
private void addToken(int x) {
if (tokens.length < len + 1) {
growTokens();
}
tokens[len++] = x;
}
private int addValue(Object x) {
if (values.length < valuesLen + 1) {
growValues();
}
values[valuesLen] = x;
return valuesLen++ << 8;
}
private void growTokens() {
int[] t2 = new int[tokens.length * 2];
System.arraycopy(tokens, 0, t2, 0, len);
tokens = t2;
}
private void growValues() {
Object[] v2 = new Object[values.length * 2];
System.arraycopy(values, 0, v2, 0, valuesLen);
values = v2;
}
@Override
public JsopStream tag(char tag) {
addToken(tag);
needComma = false;
return this;
}
@Override
public JsopStream array() {
optionalComma();
addToken('[');
needComma = false;
return this;
}
@Override
public JsopStream encodedValue(String raw) {
optionalComma();
addToken(COMMENT + addValue(raw));
needComma = true;
return this;
}
@Override
public JsopStream endArray() {
addToken(']');
needComma = true;
return this;
}
@Override
public JsopStream endObject() {
addToken('}');
needComma = true;
return this;
}
@Override
public JsopStream key(String key) {
optionalComma();
addToken(STRING + addValue(key));
addToken(':');
needComma = false;
return this;
}
@Override
public JsopStream newline() {
addToken('\n');
return this;
}
@Override
public JsopStream object() {
optionalComma();
addToken('{');
needComma = false;
return this;
}
@Override
public JsopStream value(String value) {
optionalComma();
if (value == null) {
addToken(NULL);
} else {
addToken(STRING + addValue(value));
}
needComma = true;
return this;
}
@Override
public JsopStream value(long x) {
optionalComma();
addToken(NUMBER + addValue(Long.valueOf(x)));
needComma = true;
return this;
}
@Override
public JsopStream value(boolean b) {
optionalComma();
addToken(b ? TRUE : FALSE);
needComma = true;
return this;
}
@Override
public void resetReader() {
pos = 0;
}
@Override
public void resetWriter() {
needComma = false;
len = 0;
}
@Override
public void setLineLength(int i) {
// ignore
}
private void optionalComma() {
if (needComma) {
addToken(',');
}
}
// read
@Override
public String getToken() {
int x = tokens[lastPos];
switch (x & 255) {
case STRING:
case NUMBER:
case IDENTIFIER:
case COMMENT:
return values[x >> 8].toString();
case TRUE:
return "true";
case FALSE:
return "false";
case NULL:
return "null";
}
return Character.toString((char) (x & 255));
}
@Override
public int getTokenType() {
return tokens[lastPos] & 255;
}
@Override
public boolean matches(int type) {
if (getType() == type) {
lastPos = pos;
pos++;
return true;
}
return false;
}
private int getType() {
skipNewline();
return tokens[pos] & 255;
}
@Override
public String read(int type) {
if (matches(type)) {
return getToken();
}
throw new IllegalArgumentException("expected: " + type + " got: " + tokens[pos]);
}
@Override
public int read() {
int t = getType();
lastPos = pos++;
return t;
}
private void skipNewline() {
while (true) {
int x = tokens[pos];
if (x != '\n') {
return;
}
pos++;
}
}
@Override
public String readRawValue() {
skipNewline();
int x = tokens[pos];
lastPos = pos++;
switch (x & 255) {
case COMMENT:
case NUMBER:
case IDENTIFIER:
return values[x >> 8].toString();
case STRING:
return JsopBuilder.encode(values[x >> 8].toString());
case TRUE:
return "true";
case FALSE:
return "false";
case NULL:
return "null";
case '[':
StringBuilder buff = new StringBuilder();
buff.append('[');
while (true) {
String s = readRawValue();
buff.append(s);
if ("]".equals(s)) {
break;
}
}
return buff.toString();
}
return Character.toString((char) (x & 255));
}
@Override
public String readString() {
return read(STRING);
}
@Override
public String toString() {
JsopBuilder buff = new JsopBuilder();
for (int i = 0; i < len; i++) {
int x = tokens[i];
switch (x & 255) {
case '{':
buff.object();
break;
case '}':
buff.endObject();
break;
case '[':
buff.array();
break;
case ']':
buff.endArray();
break;
case STRING:
buff.value(values[x >> 8].toString());
break;
case TRUE:
buff.value(true);
break;
case FALSE:
buff.value(false);
break;
case NULL:
buff.value(null);
break;
case IDENTIFIER:
case NUMBER:
case COMMENT:
buff.encodedValue(values[x >> 8].toString());
break;
default:
buff.tag((char) (x & 255));
}
}
return buff.toString();
}
}