WIP
This commit is contained in:
@@ -1,21 +1,28 @@
|
||||
package swiss.qpq.gajumaru.core.encoding;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
// ZJ: Tiny JSON parser, ported from zj.erl.
|
||||
//
|
||||
// NOTE:
|
||||
// Does not *quite* live up to ZJ in every respect, but is sufficient for GRIDS (maybe).
|
||||
// The most important difference is that this version makes use of `throw`. A lot.
|
||||
// That sucks, but I don't know a clean way to get the error tuple return style yet.
|
||||
|
||||
// Does not *quite* live up to ZJ in every respect, but is sufficient for GRIDS.
|
||||
// Uses standard Java exceptions for errors.
|
||||
|
||||
public final class ZJ {
|
||||
|
||||
public static class ParseException extends RuntimeException {
|
||||
public final int position;
|
||||
public ParseException(String message, int position) {
|
||||
super(message + " at position " + position);
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
private ZJ() {}
|
||||
|
||||
public static Object decode(String json) {
|
||||
@@ -47,10 +54,7 @@ public final class ZJ {
|
||||
} else if (value instanceof Object[]) {
|
||||
encodeArray((Object[]) value, sb);
|
||||
} else {
|
||||
// Fallback for other objects, though not strictly in ZJ
|
||||
sb.append('"');
|
||||
escape(value.toString(), sb);
|
||||
sb.append('"');
|
||||
throw new IllegalArgumentException("Unsupported JSON value type: " + value.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +127,12 @@ public final class ZJ {
|
||||
Object parse() {
|
||||
seek();
|
||||
if (pos >= json.length()) return null;
|
||||
return value();
|
||||
Object result = value();
|
||||
seek();
|
||||
if (pos != json.length()) {
|
||||
throw new ParseException("Unexpected trailing data", pos);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object value() {
|
||||
@@ -139,14 +148,14 @@ public final class ZJ {
|
||||
if (c == '-' || (c >= '0' && c <= '9')) {
|
||||
yield number();
|
||||
}
|
||||
throw new RuntimeException("Unexpected character at " + pos + ": " + c);
|
||||
throw new ParseException("Unexpected character: " + c, pos);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Map<String, Object> object() {
|
||||
pos++; // skip '{'
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
seek();
|
||||
if (pos < json.length() && json.charAt(pos) == '}') {
|
||||
pos++;
|
||||
@@ -157,7 +166,7 @@ public final class ZJ {
|
||||
String key = string();
|
||||
seek();
|
||||
if (pos >= json.length() || json.charAt(pos) != ':') {
|
||||
throw new RuntimeException("Expected ':' at " + pos);
|
||||
throw new ParseException("Expected ':'", pos);
|
||||
}
|
||||
pos++;
|
||||
seek();
|
||||
@@ -169,7 +178,7 @@ public final class ZJ {
|
||||
break;
|
||||
}
|
||||
if (pos >= json.length() || json.charAt(pos) != ',') {
|
||||
throw new RuntimeException("Expected ',' or '}' at " + pos);
|
||||
throw new ParseException("Expected ',' or '}'", pos);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
@@ -193,7 +202,7 @@ public final class ZJ {
|
||||
break;
|
||||
}
|
||||
if (pos >= json.length() || json.charAt(pos) != ',') {
|
||||
throw new RuntimeException("Expected ',' or ']' at " + pos);
|
||||
throw new ParseException("Expected ',' or ']'", pos);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
@@ -201,14 +210,15 @@ public final class ZJ {
|
||||
}
|
||||
|
||||
private String string() {
|
||||
if (json.charAt(pos) != '"') throw new RuntimeException("Expected '\"' at " + pos);
|
||||
if (json.charAt(pos) != '"') throw new ParseException("Expected '\"'", pos);
|
||||
pos++;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (pos < json.length()) {
|
||||
char c = json.charAt(pos++);
|
||||
if (c == '"') return sb.toString();
|
||||
if (c < 0x20) throw new ParseException("Unescaped control character", pos - 1);
|
||||
if (c == '\\') {
|
||||
if (pos >= json.length()) throw new RuntimeException("Unterminated escape at " + pos);
|
||||
if (pos >= json.length()) throw new ParseException("Unterminated escape", pos);
|
||||
char esc = json.charAt(pos++);
|
||||
switch (esc) {
|
||||
case '"' -> sb.append('"');
|
||||
@@ -220,18 +230,22 @@ public final class ZJ {
|
||||
case 'r' -> sb.append('\r');
|
||||
case 't' -> sb.append('\t');
|
||||
case 'u' -> {
|
||||
if (pos + 4 > json.length()) throw new RuntimeException("Invalid unicode escape");
|
||||
if (pos + 4 > json.length()) throw new ParseException("Invalid unicode escape", pos);
|
||||
String hex = json.substring(pos, pos + 4);
|
||||
sb.append((char) Integer.parseInt(hex, 16));
|
||||
try {
|
||||
sb.append((char) Integer.parseInt(hex, 16));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ParseException("Invalid hex in unicode escape", pos);
|
||||
}
|
||||
pos += 4;
|
||||
}
|
||||
default -> throw new RuntimeException("Unknown escape: " + esc);
|
||||
default -> throw new ParseException("Unknown escape: " + esc, pos - 1);
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Unterminated string");
|
||||
throw new ParseException("Unterminated string", pos);
|
||||
}
|
||||
|
||||
private Boolean bool(boolean expected) {
|
||||
@@ -240,7 +254,7 @@ public final class ZJ {
|
||||
pos += s.length();
|
||||
return expected;
|
||||
}
|
||||
throw new RuntimeException("Expected " + s + " at " + pos);
|
||||
throw new ParseException("Expected " + s, pos);
|
||||
}
|
||||
|
||||
private Object nil() {
|
||||
@@ -248,27 +262,47 @@ public final class ZJ {
|
||||
pos += 4;
|
||||
return null;
|
||||
}
|
||||
throw new RuntimeException("Expected null at " + pos);
|
||||
throw new ParseException("Expected null", pos);
|
||||
}
|
||||
|
||||
private Number number() {
|
||||
int start = pos;
|
||||
if (json.charAt(pos) == '-') pos++;
|
||||
while (pos < json.length() && Character.isDigit(json.charAt(pos))) pos++;
|
||||
boolean isFloat = false;
|
||||
if (pos < json.length() && json.charAt(pos) == '.') {
|
||||
isFloat = true;
|
||||
if (pos < json.length() && json.charAt(pos) == '-') pos++;
|
||||
|
||||
if (pos >= json.length()) throw new ParseException("Unexpected end of number", pos);
|
||||
|
||||
char c = json.charAt(pos);
|
||||
if (c == '0') {
|
||||
pos++;
|
||||
} else if (c >= '1' && c <= '9') {
|
||||
pos++;
|
||||
while (pos < json.length() && Character.isDigit(json.charAt(pos))) pos++;
|
||||
} else {
|
||||
throw new ParseException("Expected digit", pos);
|
||||
}
|
||||
|
||||
boolean isDecimal = false;
|
||||
if (pos < json.length() && json.charAt(pos) == '.') {
|
||||
isDecimal = true;
|
||||
pos++;
|
||||
if (pos >= json.length() || !Character.isDigit(json.charAt(pos))) {
|
||||
throw new ParseException("Expected digit after '.'", pos);
|
||||
}
|
||||
while (pos < json.length() && Character.isDigit(json.charAt(pos))) pos++;
|
||||
}
|
||||
|
||||
if (pos < json.length() && (json.charAt(pos) == 'e' || json.charAt(pos) == 'E')) {
|
||||
isFloat = true;
|
||||
isDecimal = true;
|
||||
pos++;
|
||||
if (pos < json.length() && (json.charAt(pos) == '+' || json.charAt(pos) == '-')) pos++;
|
||||
if (pos >= json.length() || !Character.isDigit(json.charAt(pos))) {
|
||||
throw new ParseException("Expected digit in exponent", pos);
|
||||
}
|
||||
while (pos < json.length() && Character.isDigit(json.charAt(pos))) pos++;
|
||||
}
|
||||
|
||||
String s = json.substring(start, pos);
|
||||
if (isFloat) return Double.parseDouble(s);
|
||||
if (isDecimal) return new BigDecimal(s);
|
||||
try {
|
||||
return Long.parseLong(s);
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
@@ -4,12 +4,11 @@ import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* GRIDS URL handling, ported from hz_grids.erl.
|
||||
*/
|
||||
// GRIDS URL handling, ported from hz_grids.erl.
|
||||
|
||||
public final class Grids {
|
||||
|
||||
private Grids() {}
|
||||
@@ -87,8 +86,8 @@ public final class Grids {
|
||||
} else if (path.startsWith("/1/d/")) {
|
||||
String subPath = path.substring(5);
|
||||
String httpScheme = "grids".equals(scheme) ? "https" : "http";
|
||||
String httpUrl = httpScheme + "://" + host + (uri.getPort() != -1 ? ":" + uri.getPort() : "") + subPath;
|
||||
if (query != null) httpUrl += "?" + query;
|
||||
String baseUrl = httpScheme + "://" + host + (uri.getPort() != -1 ? ":" + uri.getPort() : "") + subPath;
|
||||
String httpUrl = query != null ? baseUrl + "?" + query : baseUrl;
|
||||
return ParseResult.sign("grids".equals(scheme) ? Context.HTTPS : Context.HTTP, httpUrl);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown verb in path: " + path);
|
||||
@@ -103,7 +102,7 @@ public final class Grids {
|
||||
public static final String REQ_TYPE_ACK = "ack";
|
||||
|
||||
public static Map<String, Object> makeRequest(String type, Object payload, String publicId, String networkId) {
|
||||
Map<String, Object> req = new HashMap<>();
|
||||
Map<String, Object> req = new LinkedHashMap<>();
|
||||
req.put("grids", 1);
|
||||
req.put("chain", "gajumaru");
|
||||
req.put("network_id", networkId);
|
||||
@@ -114,7 +113,7 @@ public final class Grids {
|
||||
}
|
||||
|
||||
private static Map<String, String> parseQuery(String query) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
Map<String, String> params = new LinkedHashMap<>();
|
||||
if (query == null || query.isEmpty()) return params;
|
||||
String[] pairs = query.split("&");
|
||||
for (String pair : pairs) {
|
||||
@@ -133,8 +132,6 @@ public final class Grids {
|
||||
if (p == null || p.isEmpty()) return new byte[0];
|
||||
// The Erlang side uses uri_string:compose_query which percent-encodes.
|
||||
// URLDecoder.decode should have handled it in parseQuery.
|
||||
// Assuming payload is just string data or hex?
|
||||
// hz_grids.erl says: Payload = list_to_binary(proplists:get_value("p", ArgList, ""))
|
||||
return p.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user