blob: 2730297b9f9ebcc68bd082e56f8db2451607019f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 Nikita Nemkin and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Nikita Nemkin <nikita@nemkin.ru> - initial implementation
*******************************************************************************/
package org.eclipse.swt.browser;
import java.util.*;
import org.eclipse.swt.*;
class JSON {
// Note: supported types are limited, see Browser.evaluate and BrowserFunction.function.
static class Reader {
char[] input;
int pos, end;
StringBuilder sb;
public Reader(char[] input, int start, int end) {
this.input = input;
this.pos = start;
this.end = end;
}
static enum Control {
END, ARRAY_END, COMMA
}
char nextChar() {
return (pos < end) ? input[pos++] : '\0';
}
void error() {
SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, " [decoding error at " + (pos - 1) +"]");
}
void readLiteral(String literal) {
for (int i = 0, len = literal.length(); i < len; i++) {
if (nextChar() != literal.charAt(i)) {
error();
}
}
}
int nextHexDigit() {
char c = nextChar();
if ('0' <= c && c <= '9') return c - '0';
if ('a' <= c && c <= 'f') return c - 'a' + 10;
if ('A' <= c && c <= 'F') return c - 'A' + 10;
error();
return 0;
}
char readEscape() {
char c = nextChar();
switch (c) {
case '"':
case '\\':
case '/': break;
case 'b': c = '\b'; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'u':
c = (char)((nextHexDigit() << 12) | (nextHexDigit() << 8)
| (nextHexDigit() << 4) | nextHexDigit());
break;
default: error();
}
return c;
}
String readString() {
char c;
int start = pos;
do {
c = nextChar();
if (c < 0x20) error();
if (c == '\\') {
if (sb == null) sb = new StringBuilder();
sb.append(input, start, pos - start - 1);
sb.append(readEscape());
start = pos;
}
} while (c != '"');
if (sb != null) {
sb.append(input, start, pos - 1 - start);
String result = sb.toString();
sb.setLength(0);
return result;
}
return String.valueOf(input, start, pos - start - 1);
}
double readNumber() {
int start = pos - 1;
while (true) {
char c = nextChar();
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
case 'e':
case 'E':
case '+':
case '-': continue;
default:
pos--;
case '\0':
try {
return Double.parseDouble(String.valueOf(input, start, pos - start));
} catch (NumberFormatException e) {
error();
}
}
}
}
Object readAny() {
while (true) {
char c = nextChar();
switch (c) {
case ' ':
case '\t':
case '\r':
case '\n': continue;
case '\0': return Control.END;
case '[': return readArray();
case ']': return Control.ARRAY_END;
case ',': return Control.COMMA;
case '"': return readString();
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-': return readNumber();
case 'n': readLiteral("ull"); return null;
case 't': readLiteral("rue"); return true;
case 'f': readLiteral("alse"); return false;
default: error();
}
}
}
Object readArray() {
Object item = readAny();
if (item == Control.ARRAY_END) return new Object[0];
if (item instanceof Control) error();
List<Object> items = new ArrayList<>();
items.add(item);
while (true) {
Object sep = readAny();
if (sep == Control.ARRAY_END) break;
if (sep != Control.COMMA) error();
item = readAny();
if (item instanceof Control) error();
items.add(item);
}
return items.toArray();
}
Object readTop() {
Object item = readAny();
if (item instanceof Control) error();
if (readAny() != Control.END) error();
return item;
}
}
static class Writer {
static final String[] ESCAPED = new String[96];
static {
for (int i = 0; i < 0x20; i++) {
ESCAPED[i] = String.format("\\u%04x", i);
}
ESCAPED['\b'] = "\\b";
ESCAPED['\f'] = "\\f";
ESCAPED['\n'] = "\\n";
ESCAPED['\r'] = "\\r";
ESCAPED['\t'] = "\\t";
ESCAPED['"'] = "\\\"";
}
StringBuilder sb = new StringBuilder();
public Writer(Object object) {
writeAny(object);
}
void writeAny(Object object) {
if (object == null) {
sb.append("null");
} else if (object instanceof Boolean) {
sb.append(object.toString());
} else if (object instanceof Long) {
sb.append((long)object);
} else if (object instanceof Integer) {
sb.append((int)object);
} else if (object instanceof Short) {
sb.append((short)object);
} else if (object instanceof Byte) {
sb.append((byte)object);
} else if (object instanceof Double) {
sb.append((double)object);
} else if (object instanceof Float) {
sb.append((float)object);
} else if (object instanceof String) {
writeString(object.toString());
} else if (object instanceof Object[]) {
writeArray((Object[])object);
} else {
SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, " [object not encodable: " + object.getClass() + "]");
}
}
void writeString(String s) {
sb.append('"');
int start = 0;
for (int i = 0, len = s.length(); i < len; i++) {
char c = s.charAt(i);
if (c < ESCAPED.length && ESCAPED[c] != null) {
sb.append(s, start, i);
sb.append(ESCAPED[c]);
start = i + 1;
}
}
sb.append(s, start, s.length());
sb.append('"');
}
void writeArray(Object[] array) {
sb.append('[');
boolean first = true;
for (Object item : array) {
if (!first) sb.append(',');
writeAny(item);
first = false;
}
sb.append(']');
}
@Override
public String toString() {
return sb.toString();
}
}
public static Object parse(char[] input) {
return new Reader(input, 0, input.length).readTop();
}
public static Object parse(String input) {
return parse(input.toCharArray());
}
public static String stringify(Object object) {
return new Writer(object).toString();
}
}