Last active
October 10, 2023 23:21
-
-
Save thomasdarimont/8aa5cf7d273ce5f3a9efc1ef7fa06cf2 to your computer and use it in GitHub Desktop.
PoC for Fetch API like API for JShell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package demo; | |
import java.net.URI; | |
import java.net.http.HttpClient; | |
import java.net.http.HttpRequest; | |
import java.net.http.HttpResponse; | |
import java.nio.charset.StandardCharsets; | |
import java.util.Map; | |
public class JShellFetchDemo { | |
public static void main(String[] args) throws Exception { | |
System.out.println(fetch("https://api.chucknorris.io/jokes/random").body()); | |
System.out.println(fetch("https://httpbin.org/get", Map.of("accept", "application/xml")).body()); | |
} | |
public static HttpResponse<String> fetch(String uri) throws Exception { | |
return fetch(uri, new Request(null, null, null)); | |
} | |
public static HttpResponse<String> fetch(String uri, Map<String, String> headers) throws Exception { | |
return fetch(uri, new Request(null, null, headers)); | |
} | |
public static HttpResponse<String> fetch(String uri, Request request) throws Exception { | |
var targetUri = URI.create(uri); | |
try (var client = HttpClient.newHttpClient()) { | |
var httpRequest = request.toHttpRequest(targetUri); | |
var responseBodyHandler = HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8); | |
return client.send(httpRequest, responseBodyHandler); | |
} | |
} | |
public record Request(Method method, String body, Map<String, String> headers) { | |
enum Method { | |
GET, HEAD, POST, PUT, PATCH, DELETE | |
} | |
public Request { | |
if (method == null) { | |
method = Method.GET; | |
} | |
if (headers == null) { | |
headers = Map.of(); | |
} | |
} | |
public HttpRequest.BodyPublisher bodyPublisher() { | |
if (Method.GET.equals(method) || Method.HEAD.equals(method)) { | |
return null; | |
} | |
if (body == null) { | |
return null; | |
} | |
return HttpRequest.BodyPublishers.ofString(body); | |
} | |
public HttpRequest toHttpRequest(URI uri) { | |
var requestBuilder = HttpRequest.newBuilder(); | |
requestBuilder = switch (method) { | |
case GET -> requestBuilder.GET(); | |
case HEAD -> requestBuilder.HEAD(); | |
case POST -> requestBuilder.POST(bodyPublisher()); | |
case PUT -> requestBuilder.PUT(bodyPublisher()); | |
case PATCH -> requestBuilder.method(method.name(), bodyPublisher()); | |
case DELETE -> requestBuilder.DELETE(); | |
}; | |
requestBuilder = requestBuilder.uri(uri); | |
if (!headers.isEmpty()) { | |
headers.forEach(requestBuilder::header); | |
} | |
return requestBuilder.build(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package demo; | |
import java.net.URI; | |
import java.net.http.HttpClient; | |
import java.net.http.HttpRequest; | |
import java.net.http.HttpResponse; | |
import java.nio.charset.StandardCharsets; | |
import java.io.IOException; | |
import java.io.Serializable; | |
import java.io.StringWriter; | |
import java.io.Writer; | |
import java.text.NumberFormat; | |
import java.text.ParseException; | |
import java.util.AbstractList; | |
import java.util.AbstractMap; | |
import java.util.Arrays; | |
import java.util.LinkedList; | |
import java.util.Objects; | |
import java.util.Set; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.concurrent.CopyOnWriteArrayList; | |
public class MiniFetch { | |
public static void main(String[] args) throws Exception { | |
System.out.println(fetch("https://api.chucknorris.io/jokes/random").body()); | |
System.out.println(fetchJson("https://api.chucknorris.io/jokes/random")); | |
} | |
public static HttpResponse<String> fetch(String uri) throws Exception { | |
var targetUri = URI.create(uri); | |
try (var client = HttpClient.newHttpClient()) { | |
var request = HttpRequest.newBuilder(targetUri).build(); | |
return client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); | |
} | |
} | |
public static Object fetchJson(String uri) throws Exception { | |
return new JSONOne.Parser(fetch(uri).body()).singleObject(); | |
} | |
// see https://github.com/cschanck/single-file-java/blob/master/src/main/java/org/sfj/JSONOne.java | |
public static class JSONOne { | |
/** | |
* JSON value types | |
*/ | |
public enum Type { | |
NUMBER, | |
STRING, | |
BOOLEAN, | |
MAP, | |
ARRAY, | |
NULL | |
} | |
/** | |
* A JSON object. Asking for the value as the worng type results in a | |
* ClassCastException. | |
*/ | |
public interface JObject extends Serializable { | |
default JArray arrayValue() { | |
throw new ClassCastException(); | |
} | |
default boolean boolValue() { | |
throw new ClassCastException(); | |
} | |
JSONOne.Type getType(); | |
default JMap mapValue() { | |
throw new ClassCastException(); | |
} | |
default boolean nullValue() { | |
return false; | |
} | |
default Number numberValue() { | |
throw new ClassCastException(); | |
} | |
Object pojoValue(); | |
default String stringValue() { | |
throw new ClassCastException(); | |
} | |
void print(Writer w, int indent, boolean compact) throws IOException; | |
default String print(boolean compact) throws IOException { | |
return print(0, compact); | |
} | |
default String print() throws IOException { | |
return print(0, true); | |
} | |
default String print(int indent, boolean compact) throws IOException { | |
StringWriter sw = new StringWriter(); | |
print(sw, indent, compact); | |
return sw.toString(); | |
} | |
} | |
private static abstract class AbstractJSONObject implements JObject { | |
private Type type; | |
private Object obj; | |
public AbstractJSONObject() { | |
} | |
public AbstractJSONObject(Type type, Object obj) { | |
this.type = type; | |
this.obj = obj; | |
} | |
@Override | |
public Type getType() { | |
return type; | |
} | |
@Override | |
public Object pojoValue() { | |
return obj; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) { | |
return true; | |
} | |
if (o == null || getClass() != o.getClass()) { | |
return false; | |
} | |
AbstractJSONObject object = (AbstractJSONObject) o; | |
return Objects.equals(obj, object.obj) && type == object.type; | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(obj, type); | |
} | |
@Override | |
public String toString() { | |
return obj.toString(); | |
} | |
static String indent(int indention) { | |
char[] c = new char[indention * 2]; | |
Arrays.fill(c, ' '); | |
return new String(c); | |
} | |
public void print(Writer w, int indent, boolean compact) throws IOException { | |
w.append(obj.toString()); | |
} | |
} | |
/** | |
* Number object. Represents a Number in JSON. Underlying Value | |
* POJO wil be up converted to either Long or Double, always. | |
*/ | |
public static class JNumber extends AbstractJSONObject { | |
private boolean isFixed; | |
public JNumber() { | |
} | |
public JNumber(Number obj) { | |
super(Type.NUMBER, obj instanceof Float ? obj.doubleValue() : obj instanceof Double ? obj : obj.longValue()); | |
this.isFixed = !(obj instanceof Double); | |
} | |
public boolean isFixed() { | |
return isFixed; | |
} | |
@Override | |
public Number numberValue() { | |
return (Number) pojoValue(); | |
} | |
} | |
/** | |
* Holds a string value. | |
*/ | |
public static class JString extends AbstractJSONObject { | |
public JString() { | |
} | |
public JString(CharSequence obj) { | |
super(Type.STRING, obj.toString()); | |
Objects.requireNonNull(obj); | |
} | |
@Override | |
public String stringValue() { | |
return (String) pojoValue(); | |
} | |
@Override | |
public void print(Writer w, int indent, boolean compact) throws IOException { | |
w.append('"'); | |
w.append(escapeString(stringValue())); | |
w.append('"'); | |
} | |
} | |
/** | |
* Boolean value | |
*/ | |
public static class JBoolean extends AbstractJSONObject { | |
public JBoolean() { | |
} | |
public JBoolean(Boolean obj) { | |
super(Type.BOOLEAN, obj); | |
} | |
@Override | |
public boolean boolValue() { | |
return (Boolean) pojoValue(); | |
} | |
@Override | |
public void print(Writer w, int indent, boolean compact) throws IOException { | |
w.append((boolValue() ? "true" : "false")); | |
} | |
} | |
/** | |
* Null value. | |
*/ | |
public static class JNull extends AbstractJSONObject { | |
public JNull() { | |
super(Type.NULL, null); | |
} | |
@Override | |
public boolean nullValue() { | |
return true; | |
} | |
@Override | |
public void print(Writer w, int indent, boolean compact) throws IOException { | |
w.append("null"); | |
} | |
@Override | |
public String toString() { | |
return "null"; | |
} | |
} | |
/** | |
* JSON Array class. List of {@link JSONOne.JObject}s. | |
*/ | |
public static class JArray extends AbstractList<JObject> implements JObject { | |
private final CopyOnWriteArrayList<JObject> list = new CopyOnWriteArrayList<>(); | |
public JArray() { | |
} | |
@Override | |
public JObject get(int index) { | |
return list.get(index); | |
} | |
@Override | |
public JArray arrayValue() { | |
return this; | |
} | |
@Override | |
public Type getType() { | |
return Type.ARRAY; | |
} | |
@Override | |
public Object pojoValue() { | |
return list; | |
} | |
@Override | |
public int size() { | |
return list.size(); | |
} | |
@Override | |
public JObject set(int index, JObject element) { | |
return list.set(index, element); | |
} | |
@Override | |
public void add(int index, JObject element) { | |
list.add(index, element); | |
} | |
@Override | |
public JObject remove(int index) { | |
return list.remove(index); | |
} | |
public JObject setNumber(int index, Number val) { | |
return list.set(index, new JNumber(val)); | |
} | |
public void addNumber(int index, Number val) { | |
list.add(index, new JNumber(val)); | |
} | |
public void addNumber(Number val) { | |
list.add(new JNumber(val)); | |
} | |
public JObject setString(int index, String val) { | |
return list.set(index, new JString(val)); | |
} | |
public void addString(int index, String val) { | |
list.add(index, new JString(val)); | |
} | |
public void addString(String val) { | |
list.add(new JString(val)); | |
} | |
public JObject setBoolean(int index, boolean val) { | |
return list.set(index, new JBoolean(val)); | |
} | |
public void addBoolean(int index, boolean val) { | |
list.add(index, new JBoolean(val)); | |
} | |
public void addBoolean(boolean val) { | |
list.add(new JBoolean(val)); | |
} | |
public JObject setNull(int index) { | |
return list.set(index, new JNull()); | |
} | |
public void addNull(int index) { | |
list.add(index, new JNull()); | |
} | |
public void addNull() { | |
list.add(new JNull()); | |
} | |
@Override | |
public void print(Writer w, int indent, boolean compact) throws IOException { | |
String ind1 = AbstractJSONObject.indent(indent); | |
String ind2 = AbstractJSONObject.indent(indent + 1); | |
w.append('['); | |
boolean first = true; | |
for (JObject obj : this) { | |
if (first) { | |
first = false; | |
} else { | |
w.append(','); | |
} | |
if (!compact) { | |
w.append(System.lineSeparator()); | |
w.append(ind2); | |
} | |
obj.print(w, indent + 1, compact); | |
} | |
if (!compact) { | |
w.append(System.lineSeparator()); | |
w.append(ind1); | |
} | |
w.append(']'); | |
} | |
} | |
/** | |
* JSON Map value. String to {@link JSONOne.JObject}. | |
*/ | |
public static class JMap extends AbstractMap<String, JObject> implements JObject { | |
private final ConcurrentHashMap<String, JObject> map = new ConcurrentHashMap<>(); | |
public JMap() { | |
} | |
@Override | |
public JMap mapValue() { | |
return this; | |
} | |
@Override | |
public Set<Entry<String, JObject>> entrySet() { | |
return map.entrySet(); | |
} | |
@Override | |
public Type getType() { | |
return Type.MAP; | |
} | |
@Override | |
public Object pojoValue() { | |
return map; | |
} | |
@Override | |
public boolean containsValue(Object value) { | |
return map.containsValue(value); | |
} | |
@Override | |
public boolean containsKey(Object key) { | |
return map.containsKey(key); | |
} | |
@Override | |
public JObject get(Object key) { | |
return map.get(key); | |
} | |
@Override | |
public JObject put(String key, JObject value) { | |
return map.put(key, value); | |
} | |
@Override | |
public JObject remove(Object key) { | |
return map.remove(key); | |
} | |
@Override | |
public void clear() { | |
map.clear(); | |
} | |
@Override | |
public Set<String> keySet() { | |
return map.keySet(); | |
} | |
private Object getTyped(String key, Type t, Object defVal) { | |
JObject p = get(key); | |
if (p != null) { | |
if (p.getType().equals(t)) { | |
switch (t) { | |
case MAP: | |
case ARRAY: | |
return p; | |
default: | |
return p.pojoValue(); | |
} | |
} | |
} | |
return defVal; | |
} | |
public Boolean getBoolean(String key, Boolean defValue) { | |
return (Boolean) getTyped(key, Type.BOOLEAN, defValue); | |
} | |
public Number getNumber(String key, Number defValue) { | |
return (Number) getTyped(key, Type.NUMBER, defValue); | |
} | |
public String getString(String key, String defValue) { | |
return (String) getTyped(key, Type.STRING, defValue); | |
} | |
public JArray getArray(String key, JArray defValue) { | |
return (JArray) getTyped(key, Type.ARRAY, defValue); | |
} | |
public JArray getMap(String key, JMap defValue) { | |
return (JArray) getTyped(key, Type.MAP, defValue); | |
} | |
public boolean isNull(String key, boolean defValue) { | |
JObject p = map.get(key); | |
if (p.getType().equals(Type.NULL)) { | |
return true; | |
} | |
return defValue; | |
} | |
public JMap putBoolean(String key, boolean value) { | |
map.put(key, new JBoolean(value)); | |
return this; | |
} | |
public JMap putNumber(String key, Number val) { | |
map.put(key, new JNumber(val)); | |
return this; | |
} | |
public JMap putString(String key, String value) { | |
map.put(key, new JString(value)); | |
return this; | |
} | |
public JMap putNull(String key) { | |
map.put(key, new JNull()); | |
return this; | |
} | |
public JMap putMap(String key, JMap value) { | |
map.put(key, value); | |
return this; | |
} | |
public JMap putArray(String key, JArray value) { | |
map.put(key, value); | |
return this; | |
} | |
@Override | |
public void print(Writer w, int indent, boolean compact) throws IOException { | |
String ind = AbstractJSONObject.indent(indent); | |
w.append('{'); | |
boolean first = true; | |
String ind2 = AbstractJSONObject.indent(indent + 1); | |
for (Entry<String, JObject> obj : this.entrySet()) { | |
if (first) { | |
first = false; | |
} else { | |
w.append(','); | |
} | |
if (!compact) { | |
w.append(System.lineSeparator()); | |
w.append(ind2); | |
} | |
w.append('"'); | |
w.append(obj.getKey()); | |
w.append('"'); | |
w.append(compact ? ":" : " : "); | |
obj.getValue().print(w, indent + 1, compact); | |
} | |
if (!compact) { | |
w.append(System.lineSeparator()); | |
w.append(ind); | |
} | |
w.append('}'); | |
} | |
} | |
enum TokenType { | |
LEFT_CURLY, | |
RIGHT_CURLY, | |
LEFT_BRACKET, | |
RIGHT_BACKET, | |
COMMA, | |
NUMBER, | |
STRING, | |
NULL, | |
EOF, | |
TRUE, | |
FALSE, | |
COLON, | |
} | |
static class Token { | |
final TokenType type; | |
final String lexeme; | |
final Object literal; | |
final int line; | |
Token(TokenType type, String lexeme, Object literal, int line) { | |
this.type = type; | |
this.lexeme = lexeme; | |
this.literal = literal; | |
this.line = line; | |
} | |
public String toString() { | |
return type + " " + lexeme + " " + literal; | |
} | |
} | |
/** | |
* Scanner for parsing. | |
*/ | |
static class Scanner { | |
private final String input; | |
private int current = 0; | |
private int start = 0; | |
private int line = 1; | |
private final LinkedList<Token> pushBack = new LinkedList<>(); | |
Scanner(String input) { | |
this.input = input; | |
} | |
ParseException error(int line, String message) { | |
return report(line, "", message); | |
} | |
ParseException report(int line, String where, String message) { | |
return new ParseException("[line " + line + "] Error" + where + ": " + message, line); | |
} | |
public Token nextOrFail(TokenType target) throws ParseException { | |
Token tok = nextToken(); | |
if (tok.type.equals(target)) { | |
return tok; | |
} | |
throw error(line, "mismatch type; looking for " + target); | |
} | |
void pushback(Token t) { | |
pushBack.addFirst(t); | |
} | |
char peek() { | |
if (isAtEnd()) { | |
return '\0'; | |
} | |
return input.charAt(current); | |
} | |
private boolean isAtEnd() { | |
return current >= input.length(); | |
} | |
char advance() { | |
if (isAtEnd()) { | |
return '\0'; | |
} | |
return input.charAt(current++); | |
} | |
private Token token(TokenType type) { | |
return token(type, null); | |
} | |
private Token token(TokenType type, Object literal) { | |
String text = input.substring(start, current); | |
start = current; | |
return new Token(type, text, literal, line); | |
} | |
public Token nextToken() throws ParseException { | |
if (!pushBack.isEmpty()) { | |
return pushBack.removeFirst(); | |
} | |
while (!isAtEnd()) { | |
char ch = advance(); | |
switch (ch) { | |
case '\n': | |
line++; | |
start++; | |
break; | |
case ' ': | |
case '\r': | |
case '\t': | |
start++; | |
break; | |
case ':': | |
return token(TokenType.COLON); | |
case '{': | |
return token(TokenType.LEFT_CURLY); | |
case '}': | |
return token(TokenType.RIGHT_CURLY); | |
case '[': | |
return token(TokenType.LEFT_BRACKET); | |
case ']': | |
return token(TokenType.RIGHT_BACKET); | |
case '"': | |
return stringToken(); | |
case '-': | |
case '+': | |
case '0': | |
case '1': | |
case '2': | |
case '3': | |
case '4': | |
case '5': | |
case '6': | |
case '7': | |
case '8': | |
case '9': | |
return numberToken(ch); | |
case 'n': | |
return advanceMatching("ull", false, TokenType.NULL); | |
case 't': | |
return advanceMatching("rue", false, TokenType.TRUE); | |
case 'f': | |
return advanceMatching("alse", false, TokenType.FALSE); | |
case ',': | |
return token(TokenType.COMMA); | |
} | |
} | |
return token(TokenType.EOF); | |
} | |
private Token numberToken(char ch) { | |
StringBuilder ret = new StringBuilder(); | |
integer(ch, ret); | |
String fract = fraction(); | |
if (fract.length() > 0) { | |
ret.append(fract); | |
digits(ret); | |
} | |
exponent(ret); | |
return token(TokenType.NUMBER, ret.toString()); | |
} | |
private void exponent(StringBuilder ret) { | |
if (Character.toUpperCase(peek()) == 'E') { | |
ret.append(advance()); | |
if (peek() == '-') { | |
ret.append(advance()); | |
} else if (peek() == '+') { | |
ret.append(advance()); | |
} | |
digits(ret); | |
} | |
} | |
private String integer(char first, StringBuilder ret) { | |
ret.append(first); | |
digits(ret); | |
return ret.toString(); | |
} | |
private String fraction() { | |
if (peek() == '.') { | |
advance(); | |
return "."; | |
} | |
return ""; | |
} | |
private String digits(StringBuilder ret) { | |
for (char p = peek(); Character.isDigit(p); p = peek()) { | |
ret.append(advance()); | |
} | |
return ret.toString(); | |
} | |
private Token advanceMatching(String next, boolean caseMatters, TokenType type) throws ParseException { | |
if (!caseMatters) { | |
next = next.toLowerCase(); | |
} | |
for (int i = 0; i < next.length(); i++) { | |
char c = advance(); | |
if (!caseMatters) { | |
c = Character.toLowerCase(c); | |
} | |
if (next.charAt(i) != c) { | |
throw error(line, "expected [" + next + "]"); | |
} | |
} | |
return token(type); | |
} | |
private Token stringToken() { | |
StringBuilder lit = new StringBuilder(); | |
for (char p = advance(); p != '"'; p = advance()) { | |
if (p == '\\') { | |
escape(lit); | |
} else { | |
lit.append(p); | |
} | |
} | |
return token(TokenType.STRING, lit.toString()); | |
} | |
private void escape(StringBuilder lit) { | |
char p = advance(); | |
switch (p) { | |
case 'u': | |
String utf = ((("" + advance()) + advance()) + advance()) + advance(); | |
int value = Integer.parseInt(utf, 16); | |
lit.append(Character.toChars(value)); | |
break; | |
case 'n': | |
lit.append('\n'); | |
break; | |
case 'r': | |
lit.append('\r'); | |
break; | |
case 'b': | |
lit.append('\b'); | |
break; | |
case 't': | |
lit.append('\t'); | |
break; | |
case 'f': | |
lit.append('\f'); | |
break; | |
default: | |
lit.append(p); | |
break; | |
} | |
} | |
} | |
/** | |
* Parser, parses reasonably standard JSON. | |
*/ | |
public static class Parser { | |
private final Scanner scanner; | |
private final NumberFormat nFormat = NumberFormat.getInstance(); | |
public Parser(String input) { | |
scanner = new Scanner(input); | |
} | |
private ParseException error(Token token, String message) { | |
if (token.type == TokenType.EOF) { | |
return scanner.report(token.line, " at end", message); | |
} | |
return scanner.report(token.line, " at '" + token.lexeme + "'", message); | |
} | |
public JObject singleObject() throws ParseException { | |
Token p = scanner.nextToken(); | |
switch (p.type) { | |
case LEFT_BRACKET: | |
return array(); | |
case LEFT_CURLY: | |
return map(); | |
case FALSE: | |
return new JBoolean(false); | |
case TRUE: | |
return new JBoolean(true); | |
case NULL: | |
return new JNull(); | |
case STRING: | |
return new JString((String) p.literal); | |
case NUMBER: | |
return new JNumber(nFormat.parse(p.lexeme)); | |
case EOF: | |
return null; | |
default: | |
throw error(p, "Unexpected token"); | |
} | |
} | |
private JMap map() throws ParseException { | |
JMap ret = new JMap(); | |
listOfMapEntries(ret); | |
return ret; | |
} | |
private Token peekToken() throws ParseException { | |
Token p = scanner.nextToken(); | |
if (p != null) { | |
scanner.pushback(p); | |
} | |
return p; | |
} | |
private void listOfMapEntries(JMap ret) throws ParseException { | |
if (peekToken().type.equals(TokenType.RIGHT_CURLY)) { | |
scanner.nextToken(); | |
return; | |
} | |
for (; ; ) { | |
String key = (String) scanner.nextOrFail(TokenType.STRING).literal; | |
scanner.nextOrFail(TokenType.COLON); | |
JObject obj = singleObject(); | |
ret.put(key, obj); | |
Token next = scanner.nextToken(); | |
switch (next.type) { | |
case COMMA: | |
if (peekToken().type.equals(TokenType.RIGHT_CURLY)) { | |
scanner.nextToken(); | |
return; | |
} | |
break; | |
case RIGHT_CURLY: | |
return; | |
default: | |
throw error(next, "expected ',' or '}'"); | |
} | |
} | |
} | |
private JArray array() throws ParseException { | |
JArray ret = new JArray(); | |
listOfArrayEntries(ret); | |
return ret; | |
} | |
private void listOfArrayEntries(JArray ret) throws ParseException { | |
if (peekToken().type.equals(TokenType.RIGHT_BACKET)) { | |
scanner.nextToken(); | |
return; | |
} | |
for (; ; ) { | |
JObject obj = singleObject(); | |
ret.add(obj); | |
Token next = scanner.nextToken(); | |
switch (next.type) { | |
case COMMA: | |
if (peekToken().type.equals(TokenType.RIGHT_BACKET)) { | |
scanner.nextToken(); | |
return; | |
} | |
break; | |
case RIGHT_BACKET: | |
return; | |
default: | |
throw error(next, "expected ',' or ']'"); | |
} | |
} | |
} | |
} | |
public static CharSequence unescapeString(CharSequence seq) { | |
StringBuilder w = new StringBuilder(); | |
final int len = seq.length(); | |
for (int pos = 0; pos < len; pos++) { | |
char ch = seq.charAt(pos); | |
if (ch == '\\') { | |
ch = seq.charAt(++pos); | |
switch (ch) { | |
case 'u': | |
String | |
utf = | |
Character.toString(seq.charAt(++pos)) + seq.charAt(++pos) + seq.charAt(++pos) + seq.charAt(++pos); | |
int value = Integer.parseInt(utf, 16); | |
w.append(Character.toChars(value)); | |
break; | |
case 'n': | |
w.append('\n'); | |
break; | |
case 'r': | |
w.append('\r'); | |
break; | |
case 'b': | |
w.append('\b'); | |
break; | |
case 't': | |
w.append('\t'); | |
break; | |
case 'f': | |
w.append('\f'); | |
break; | |
default: | |
w.append(ch); | |
break; | |
} | |
} else { | |
w.append(ch); | |
} | |
} | |
return w; | |
} | |
public static CharSequence escapeString(CharSequence seq) { | |
StringBuilder w = new StringBuilder(); | |
final int len = seq.length(); | |
for (int i = 0; i < len; i++) { | |
char ch = seq.charAt(i); | |
switch (ch) { | |
case '"': | |
w.append("\\\""); | |
break; | |
case '\\': | |
w.append("\\\\"); | |
break; | |
case '\b': | |
w.append("\\b"); | |
break; | |
case '\f': | |
w.append("\\f"); | |
break; | |
case '\n': | |
w.append("\\n"); | |
break; | |
case '\r': | |
w.append("\\r"); | |
break; | |
case '\t': | |
w.append("\\t"); | |
break; | |
default: | |
if ((ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) { | |
String ss = Integer.toHexString(ch); | |
w.append("\\u"); | |
for (int k = 0; k < 4 - ss.length(); k++) { | |
w.append('0'); | |
} | |
w.append(ss.toUpperCase()); | |
} else { | |
w.append(ch); | |
} | |
} | |
} | |
return w; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment