/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.struct.serial;
import io.netty.buffer.ByteBuf;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import divconq.lang.chars.Special;
import divconq.lang.chars.Utf8Decoder;
import divconq.lang.Memory;
import divconq.lang.StringBuilder32;
import divconq.struct.builder.BuilderStateException;
import divconq.struct.builder.ICompositeBuilder;
import divconq.util.HexUtil;
import divconq.util.StringUtil;
import divconq.util.TimeUtil;
/**
* Load a Json like structure from buffer. Excepts data to be available one
* buffer at a time. Buffers can probably be very small, at least 64 bytes if not
* less.
*
* @author Andy
*
*/
public class BufferToCompositeParser {
protected ICompositeBuilder builder = null;
protected Utf8Decoder decoder = new Utf8Decoder();
protected boolean done = false;
public ICompositeBuilder getBuilder() {
return builder;
}
public boolean isDone() {
return this.done;
}
public BufferToCompositeParser(ICompositeBuilder builder) {
this.builder = builder;
}
/*
* @param input buffer to read and parse
*/
public void parseStruct(ByteBuf input) throws Exception {
while (true) {
StringBuilder32 str = this.decoder.processBytesUntilSpecial(input);
if (str == null)
break; // we need more buffer
if (this.parseChunk(str))
break;
}
}
public void parseStruct(ByteBuffer input) throws Exception {
while (true) {
StringBuilder32 str = this.decoder.processBytesUntilSpecial(input);
if (str == null)
break; // we need more buffer
if (this.parseChunk(str))
break;
}
}
public void parseStruct(Memory input) throws Exception {
while (true) {
StringBuilder32 str = this.decoder.processBytesUntilSpecial(input);
if (str == null)
break; // we need more buffer
if (this.parseChunk(str))
break;
}
}
// return true if at end
public boolean parseChunk(StringBuilder32 str) throws Exception {
if (str.length() > 0) {
if (this.builder.needFieldName())
this.builder.value(str.toString());
else
this.toScalar(this.builder, str);
}
int special = this.decoder.getLastSpecialCharacter();
// at the end (there should be no outstanding text
if (special == Special.End.getCode()) {
this.done = true;
return true;
}
if (special == Special.StartRec.getCode())
this.builder.startRecord();
else if (special == Special.EndRec.getCode())
this.builder.endRecord();
else if (special == Special.StartList.getCode())
this.builder.startList();
else if (special == Special.EndList.getCode())
this.builder.endList();
// is for delineation only does not do anything
//else if (special == Special.Scalar.getCode())
// this.builder.scalar();
else if (special == Special.Field.getCode())
this.builder.field();
return false;
}
protected void toScalar(ICompositeBuilder rb, CharSequence val) throws BuilderStateException {
if (StringUtil.isEmpty(val)) {
rb.value(null);
return;
}
if ((val.charAt(0) == '`') || (val.charAt(0) == '*')) {
// unescape the string
StringBuilder sb = new StringBuilder();
boolean escape = false;
for (int i = 1; i < val.length(); i++) {
char v = val.charAt(i);
if (v == '\\') {
if (escape) {
sb.append('\\');
escape = false;
}
else {
escape = true;
}
}
else if (v == 'n') {
if (escape) {
sb.append('\n');
escape = false;
}
else {
sb.append('n');
}
}
else if (v == 't') {
if (escape) {
sb.append('\t');
escape = false;
}
else {
sb.append('t');
}
}
//else if (escape && ((v == 'b') || (v == 'r') || (v == 'f') || (v == 'u') || Character.isDigit(v))) {
else if (escape) { // only t and n allowed
throw new BuilderStateException("Invalid escape character: " + v);
}
else {
sb.append(v);
escape = false;
}
}
if (val.charAt(0) == '*')
rb.rawJson(sb);
else
rb.value(sb);
return;
}
/*
if (val.charAt(0) == '*') {
rb.rawJson(val.subSequence(1, val.length()));
return;
}
*/
if (val.charAt(0) == '(') {
rb.value(new BigDecimal(val.subSequence(1, val.length()).toString()));
return;
}
if (val.charAt(0) == ')') {
try {
rb.value(new Long(val.subSequence(1, val.length()).toString()));
return;
}
catch (Exception x) {
}
try {
rb.value(new BigInteger(val.subSequence(1, val.length()).toString()));
}
catch (Exception x) {
}
return;
}
if (val.charAt(0) == '@') {
if (val.length() == 1)
rb.value(null);
else
rb.value(TimeUtil.stampFmt.parseDateTime(val.subSequence(1, val.length()).toString()));
return;
}
if (val.charAt(0) == '&') {
if (val.length() == 1)
rb.value(null);
else
rb.value(val.subSequence(1, val.length()));
return;
}
// TODO is it just 1?
if (val.charAt(0) == '!') {
if (val.length() == 1)
rb.value(null);
else
rb.value("1".equals(val.subSequence(1, val.length())) ? Boolean.TRUE : Boolean.FALSE);
return;
}
if (val.charAt(0) == '%') {
if (val.length() == 1)
rb.value(null);
else
rb.value(new Memory(HexUtil.decodeHex(val.subSequence(1, val.length()))));
return;
}
//if (val.charAt(0) == '@') {
// rb.value(TimeUtil.stampFmt.parseDateTime(val.subSequence(1, val.length()).toString()));
// return;
//}
if (val.charAt(0) == '$') {
if (val.length() == 1)
rb.value(null);
else
rb.value(TimeUtil.parseBigDateTime(val.subSequence(1, val.length()).toString()));
return;
}
String val2 = val.toString().toLowerCase();
if (val2.equals("null")) {
rb.value(null);
return;
}
if (val2.equals("true")) {
rb.value(Boolean.TRUE);
return;
}
if (val2.equals("false")) {
rb.value(Boolean.FALSE);
return;
}
try {
rb.value(val2);
}
catch (Exception x) {
}
return;
}
}