/* * Copyright 2008-2016 the original author or 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 com.nominanuda.zen.obj; import static com.nominanuda.zen.common.Check.notNull; import static com.nominanuda.zen.common.Ex.EX; import static com.nominanuda.zen.obj.JsonDeserializer.JSON_DESERIALIZER; import static org.antlr.v4.runtime.tree.ParseTreeWalker.DEFAULT; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.io.UncheckedIOException; import javax.annotation.Nullable; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.BailErrorStrategy; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ErrorNode; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; import com.nominanuda.zen.obj.SimpleJsonLexer; import com.nominanuda.zen.obj.SimpleJsonListener; import com.nominanuda.zen.obj.SimpleJsonParser; import com.nominanuda.zen.obj.SimpleJsonParser.ArrayContext; import com.nominanuda.zen.obj.SimpleJsonParser.BoolContext; import com.nominanuda.zen.obj.SimpleJsonParser.ElementsContext; import com.nominanuda.zen.obj.SimpleJsonParser.MembersContext; import com.nominanuda.zen.obj.SimpleJsonParser.NullValueContext; import com.nominanuda.zen.obj.SimpleJsonParser.NumberContext; import com.nominanuda.zen.obj.SimpleJsonParser.ObjectContext; import com.nominanuda.zen.obj.SimpleJsonParser.PairContext; import com.nominanuda.zen.obj.SimpleJsonParser.ProgramContext; import com.nominanuda.zen.obj.SimpleJsonParser.StringContext; import com.nominanuda.zen.obj.SimpleJsonParser.ValueContext; /** * <pre> * SimpleJson is a simplification of JSON syntax: single quotes substitute double quotes * strings that don't contain spaces and don't start with a number char can omit quotes * commas and colons are optional * {a:b c 1 'my key' true} is the equivalent of {"a":"b", "c":1, "my key":true} * </pre> */ public class SimpleJixParser implements SimpleJsonListener { private final Reader in; private final JixHandler jixHandler; private SimpleJixParser(Reader in, JixHandler jixHandler) { this.in = notNull(in); this.jixHandler = notNull(jixHandler); } public static @Nullable Object parse(String simpleJson) { JixBuilder b = new JixBuilder(); parse(new StringReader(simpleJson), b); return b.get(); } public static void parse(Reader in, JixHandler jixHandler) { SimpleJixParser p = new SimpleJixParser(in, jixHandler); p.parseInternal(); } public static Obj obj(String simpleJson) { return (Obj)parse(simpleJson); } public static Arr arr(String simpleJson) { return (Arr)parse(simpleJson); } private void parseInternal() throws UncheckedIOException { try { ANTLRInputStream src = new ANTLRInputStream(in); SimpleJsonLexer lexer = new SimpleJsonLexer(src); CommonTokenStream tokens = new CommonTokenStream(lexer); SimpleJsonParser parser = new SimpleJsonParser(tokens); ParserRuleContext tree = new ParserRuleContext(); parser.setContext(tree); parser.setErrorHandler(new BailErrorStrategy()); SimpleJsonListener listener = this; ParseTree /*ProgramContext*/ parseTree = parser.program(); DEFAULT.walk(listener, parseTree); } catch (IOException e) { throw EX.uncheckedIO(e); } finally { try { in.close(); } catch (IOException e) { } } } @Override public void enterNumber(NumberContext ctx) { jixHandler.val(Val.of(JSON_DESERIALIZER.deserializeNumber(ctx.start.getText()))); } @Override public void exitNumber(NumberContext ctx) { } @Override public void enterBool(BoolContext ctx) { jixHandler.val(Val.of(Boolean.valueOf(ctx.start.getText()))); } @Override public void exitBool(BoolContext ctx) { } @Override public void enterArray(ArrayContext ctx) { jixHandler.startArr(); } @Override public void exitArray(ArrayContext ctx) { jixHandler.endArr(); } @Override public void enterElements(ElementsContext ctx) { } @Override public void exitElements(ElementsContext ctx) { } @Override public void enterMembers(MembersContext ctx) { } @Override public void exitMembers(MembersContext ctx) { } @Override public void enterProgram(ProgramContext ctx) { } @Override public void exitProgram(ProgramContext ctx) { } @Override public void enterValue(ValueContext ctx) { } @Override public void exitValue(ValueContext ctx) { } @Override public void enterNullValue(NullValueContext ctx) { jixHandler.val(Val.NULL); } @Override public void exitNullValue(NullValueContext ctx) { } private boolean keyWaiting = false; @Override public void enterString(StringContext ctx) { if(keyWaiting) { jixHandler.key(Key.of(unquoteIfQuoted(ctx.start.getText()))); keyWaiting = false; } else { jixHandler.val(Val.of(unquoteIfQuoted(ctx.start.getText()))); } } private static final char SINGLE_QUOTE = '\''; private String unquoteIfQuoted(String text) { int lenMinusOne = text.length() - 1; return text.charAt(0) == SINGLE_QUOTE && text.charAt(lenMinusOne) == SINGLE_QUOTE ? text.substring(1, lenMinusOne) : text; } @Override public void exitString(StringContext ctx) { } @Override public void enterPair(PairContext ctx) { keyWaiting = true; } @Override public void exitPair(PairContext ctx) { } @Override public void enterObject(ObjectContext ctx) { jixHandler.startObj(); } @Override public void exitObject(ObjectContext ctx) { jixHandler.endObj(); } @Override public void enterEveryRule(ParserRuleContext ctx) { } @Override public void exitEveryRule(ParserRuleContext ctx) { } @Override public void visitTerminal(TerminalNode node) { } @Override public void visitErrorNode(ErrorNode node) { } }