inital commit
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
package com.reliancy.rec;
|
||||
|
||||
/** Similar to a SAX interface used by parsers for XML and JSON to assemble DOM structures.
|
||||
* Simply gets notified of events during parsing.
|
||||
* @author amer
|
||||
*/
|
||||
public interface DecoderSink {
|
||||
void beginDocument(Rec init);
|
||||
Rec endDocument();
|
||||
void beginElement(String name);
|
||||
void endElement(String name);
|
||||
void setKey(String name);
|
||||
void setValue(CharSequence seq);
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.reliancy.rec;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/** Base class of meta objects.
|
||||
* We use it to describe certain meta information. We derive from it Slot.
|
||||
* We define keys list of slots on the header level to describe slots.
|
||||
*/
|
||||
public class Hdr {
|
||||
public static final int FLAG_ARRAY =0x0001;
|
||||
public static final int FLAG_CHANGED =0x0002;
|
||||
public static final int FLAG_HIDDEN =0x0004;
|
||||
public static final int FLAG_LOCKED =0x0008;
|
||||
int flags;
|
||||
String name;
|
||||
String label;
|
||||
Class<?> type;
|
||||
final ArrayList<Slot> keys;
|
||||
|
||||
public Hdr(String name) {
|
||||
this.name=name;
|
||||
keys=new ArrayList<>();
|
||||
}
|
||||
public Hdr(String name,Class<?> type) {
|
||||
this.name=name;
|
||||
this.type=type;
|
||||
keys=new ArrayList<>();
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
StringBuilder ret=new StringBuilder();
|
||||
ret.append("{").append("flags:").append(flags).append(",name:").append(name);
|
||||
ret.append(",dim:").append(keys.size()).append("}");
|
||||
return ret.toString();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public String getLabel() {
|
||||
return label!=null?label:name;
|
||||
}
|
||||
public void setLabel(String name) {
|
||||
this.label = name;
|
||||
}
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
public void setType(Class<?> type) {
|
||||
this.type = type;
|
||||
}
|
||||
public Hdr raiseFlags(int f){
|
||||
flags|=f;
|
||||
return this;
|
||||
}
|
||||
public Hdr clearFlags(int f){
|
||||
flags&=~f;
|
||||
return this;
|
||||
}
|
||||
public boolean checkFlags(int f){
|
||||
return (flags & f)!=0;
|
||||
}
|
||||
public <T extends Hdr> T castAs(Class<T> clazz){
|
||||
return clazz.cast(this);
|
||||
}
|
||||
public int findSlot(String name){
|
||||
return findSlot(name,0);
|
||||
}
|
||||
public int findSlot(String name,int ofs){
|
||||
ListIterator<Slot> it=keys.listIterator(ofs);
|
||||
while(it.hasNext()){
|
||||
int index=it.nextIndex();
|
||||
Slot e=it.next();
|
||||
if(e.getName().equalsIgnoreCase(name)) return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
public int findSlot(Slot s,int ofs){
|
||||
ListIterator<Slot> it=keys.listIterator(ofs);
|
||||
while(it.hasNext()){
|
||||
int index=it.nextIndex();
|
||||
Slot e=it.next();
|
||||
if(e==s) return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/**
|
||||
* this version will get or create a slot by given name.
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public Slot getSlot(String name){
|
||||
int index=findSlot(name);
|
||||
if(index<0){
|
||||
return new Slot(name);
|
||||
}else{
|
||||
return getSlot(index);
|
||||
}
|
||||
}
|
||||
public Slot getSlot(int pos){
|
||||
return keys.get(pos);
|
||||
}
|
||||
public Hdr removeSlot(int pos){
|
||||
keys.remove(pos);
|
||||
return this;
|
||||
}
|
||||
public Hdr addSlot(Slot s){
|
||||
keys.add(s);
|
||||
return this;
|
||||
}
|
||||
public Hdr setSlot(int index,Slot s){
|
||||
keys.set(index,s);
|
||||
return this;
|
||||
}
|
||||
public Slot[] slots(Slot... slots){
|
||||
if(slots!=null && slots.length>0){
|
||||
keys.clear();
|
||||
for(int i=0;i<slots.length;i++) keys.add(slots[i]);
|
||||
}
|
||||
return keys.toArray(new Slot[keys.size()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.reliancy.rec;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Static methods related to JSON format.
|
||||
*/
|
||||
public class JSON {
|
||||
private JSON(){
|
||||
}
|
||||
public static final Rec reads(CharSequence seq){
|
||||
JSONDecoder dec=new JSONDecoder();
|
||||
dec.beginDocument();
|
||||
dec.parse(0, seq);
|
||||
return dec.endDocument();
|
||||
}
|
||||
public static final void writes(Rec rec,Appendable sink) throws IOException{
|
||||
JSONEncoder.encode(rec, sink);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package com.reliancy.rec;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import com.reliancy.util.Tokenizer;
|
||||
import com.reliancy.util.Handy;
|
||||
|
||||
/** Special class which will tokenize string according to rules for JSON and feed the info to a listener.
|
||||
*
|
||||
* @author amer
|
||||
*/
|
||||
public class JSONDecoder implements TextDecoder,DecoderSink {
|
||||
DecoderSink handler;
|
||||
String[] inBody;
|
||||
String[] sets;
|
||||
String lastToken=null;
|
||||
StringBuilder out = new StringBuilder();
|
||||
public JSONDecoder(DecoderSink h){
|
||||
handler=h;
|
||||
String delimChars="{}[],;:=";
|
||||
String escapeChars="'\"";
|
||||
String whiteChars=" \t\r\f\n";//" \t\r\f\n";
|
||||
inBody = new String[]{delimChars,escapeChars,whiteChars};
|
||||
sets=inBody;
|
||||
}
|
||||
public JSONDecoder(){
|
||||
this(null);
|
||||
handler=this;
|
||||
}
|
||||
@Override
|
||||
public int parse(int offset,CharSequence in){
|
||||
int noffset=0;
|
||||
while((noffset = Tokenizer.nextToken(offset, in, out, sets))!=offset){
|
||||
offset=noffset;
|
||||
if(out.length()==0) continue;
|
||||
String token=out.toString();
|
||||
out.setLength(0);
|
||||
if("{".equals(token)){
|
||||
if(lastToken!=null){
|
||||
if(lastToken.startsWith("/*") || lastToken.startsWith("//")){
|
||||
handler.setValue(lastToken); // support comments in our stream
|
||||
}else{
|
||||
handler.setKey(lastToken); // we consider string before { a key or name unless comment
|
||||
}
|
||||
lastToken=null;
|
||||
}
|
||||
handler.beginElement("object");
|
||||
}else if("}".equals(token)){
|
||||
if(lastToken!=null){
|
||||
handler.setValue(lastToken);
|
||||
lastToken=null;
|
||||
}
|
||||
handler.endElement("object");
|
||||
}else if("[".equals(token)){
|
||||
if(lastToken!=null){
|
||||
handler.setValue(lastToken);
|
||||
lastToken=null;
|
||||
}
|
||||
handler.beginElement("array");
|
||||
}else if("]".equals(token)){
|
||||
if(lastToken!=null){
|
||||
handler.setValue(lastToken);
|
||||
lastToken=null;
|
||||
}
|
||||
handler.endElement("array");
|
||||
}else if(",".equals(token) || ";".equals(token)){
|
||||
if(lastToken!=null){
|
||||
handler.setValue(lastToken);
|
||||
lastToken=null;
|
||||
}
|
||||
}else if(":".equals(token) || "=".equals(token)){
|
||||
if(lastToken!=null){
|
||||
handler.setKey(lastToken);
|
||||
lastToken=null;
|
||||
}
|
||||
}else{
|
||||
lastToken=token;
|
||||
}
|
||||
}
|
||||
if(lastToken!=null){
|
||||
handler.setValue(lastToken);
|
||||
lastToken=null;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
Slot KEY=new Slot("__key",String.class);
|
||||
/** We use a stack structure to manage recusion. */
|
||||
LinkedList<Rec> stack=new LinkedList<Rec>();
|
||||
/** will not add white space only nodes. */
|
||||
boolean whitespaceIgnored=true;
|
||||
boolean entitycharsIgnored=false;
|
||||
|
||||
public boolean isWhitespaceIgnored() {
|
||||
return whitespaceIgnored;
|
||||
}
|
||||
|
||||
public void setWhitespaceIgnored(boolean whitespaceIgnored) {
|
||||
this.whitespaceIgnored = whitespaceIgnored;
|
||||
}
|
||||
|
||||
public boolean isEntitycharsIgnored() {
|
||||
return entitycharsIgnored;
|
||||
}
|
||||
|
||||
public void setEntitycharsIgnored(boolean entitycharsIgnored) {
|
||||
this.entitycharsIgnored = entitycharsIgnored;
|
||||
}
|
||||
|
||||
public Rec getRoot() {
|
||||
return stack.getLast();
|
||||
}
|
||||
public Rec getSubject(){
|
||||
if(stack.isEmpty()) return null;
|
||||
return stack.getFirst();
|
||||
}
|
||||
public void pushSubject(Rec n){
|
||||
stack.push(n);
|
||||
}
|
||||
public Rec popSubject(){
|
||||
Rec child=stack.pop();
|
||||
Rec parent=getSubject();
|
||||
if(parent==null) return child;
|
||||
if(parent.isArray()){
|
||||
parent.add(child);
|
||||
}else{
|
||||
String key=(String) parent.get(KEY,null);
|
||||
Slot keyslot=parent.getSlot(key);
|
||||
parent.remove(KEY).set(keyslot,child);
|
||||
// if array and has key it should bomb
|
||||
//parent.setArray(false);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
public void beginDocument() {
|
||||
beginDocument(null);
|
||||
}
|
||||
@Override
|
||||
public void beginDocument(Rec init) {
|
||||
sets=inBody;
|
||||
out.setLength(0);
|
||||
lastToken=null;
|
||||
stack.clear();
|
||||
Rec arr=new Obj(true);
|
||||
stack.push(arr);
|
||||
//System.out.println("BeginDoc");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rec endDocument() {
|
||||
// need to set the actual parent
|
||||
while(stack.getFirst()!=stack.getLast()){
|
||||
popSubject();
|
||||
}
|
||||
// now adjust the root if it is array with only one child - one we added in start document as first element
|
||||
Rec root=getSubject();
|
||||
if(root.isArray() && root.count()==1 && root.get(0) instanceof Rec){
|
||||
// ok we collapse our array from above - since we only have one object
|
||||
Object bb=root.get(0);
|
||||
Rec b=(Rec)bb ;
|
||||
popSubject();
|
||||
pushSubject(b);
|
||||
}
|
||||
//System.out.println("EndDoc");
|
||||
return getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginElement(String name) {
|
||||
Rec element=new Obj("array".equals(name));
|
||||
//element.setAttr(0);
|
||||
pushSubject(element);
|
||||
//System.out.println("BeginElement:"+name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String name) {
|
||||
// check if the correct end element is sent
|
||||
Rec sub=this.getSubject();
|
||||
if(!sub.isArray()) sub.remove(KEY);
|
||||
// finally pop the root
|
||||
popSubject();
|
||||
//System.out.println("EndElement:"+name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKey(String name) {
|
||||
Rec sub=this.getSubject();
|
||||
String key=(String) sub.get(KEY,null);
|
||||
if(key!=null){
|
||||
// something is wrong - our tokizer might have ignored escape char or input has forgotten a delimiter
|
||||
// we try to split name because it would contain key and value merged
|
||||
int split=0;
|
||||
if(name.startsWith("\"")) split=name.indexOf('\"', 1);
|
||||
if(name.startsWith("'")) split=name.indexOf('\'', 1);
|
||||
String val=name.substring(0,split+1);
|
||||
setValue(val);
|
||||
name=name.substring(split+1);
|
||||
}
|
||||
int start=0;int stop=name.length();
|
||||
while(start<stop && (name.charAt(start)=='"' || name.charAt(start)=='\'')) start++;
|
||||
while(start<stop && (name.charAt(stop-1)=='"' || name.charAt(stop-1)=='\'')) stop--;
|
||||
sub.set(KEY, name.subSequence(start, stop));
|
||||
//System.out.println("BeginAttribute:"+name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(CharSequence seq) {
|
||||
if(seq==null) return;
|
||||
Rec sub=this.getSubject();
|
||||
String key=(String) sub.get(KEY,null);
|
||||
if(key==null){
|
||||
if(isWhitespaceIgnored() && Handy.isEmpty(seq)){
|
||||
// skip empty strings
|
||||
return;
|
||||
}
|
||||
// now key we are adding to body
|
||||
Object val=interpretString(seq);
|
||||
sub.add(val);
|
||||
}else{
|
||||
// we are setting attribute
|
||||
Object val=interpretString(seq);
|
||||
Slot keyslot=sub.getSlot(key);
|
||||
sub.remove(KEY).set(keyslot,val);
|
||||
// it should bomb if array and comes with key
|
||||
//sub.setArray(false); // if it needs to be array why does it have a key
|
||||
}
|
||||
//System.out.println("Data:"+seq);
|
||||
}
|
||||
public Object interpretString(CharSequence seq){
|
||||
int start=0;int stop=seq.length();
|
||||
while(start<stop && seq.charAt(start)=='"' && seq.charAt(stop-1)=='"'){
|
||||
start++;
|
||||
stop--;
|
||||
}
|
||||
if(start==0 && stop==seq.length()){
|
||||
// we do not trim single quotes unless double are missing
|
||||
while(start<stop && seq.charAt(start)=='\'' && seq.charAt(stop-1)=='\''){
|
||||
start++;
|
||||
stop--;
|
||||
}
|
||||
}
|
||||
seq=seq.subSequence(start, stop);
|
||||
Object val=seq;
|
||||
if(start==0){
|
||||
String sVal=String.valueOf(seq);
|
||||
// we did not have quotes - so try to interpet a few things
|
||||
if("null".equalsIgnoreCase(sVal)){
|
||||
val=null;
|
||||
}else
|
||||
if("true".equalsIgnoreCase(sVal)){
|
||||
val=Boolean.TRUE;
|
||||
}else
|
||||
if("false".equalsIgnoreCase(sVal)){
|
||||
val=Boolean.FALSE;
|
||||
}else
|
||||
if(Handy.isNumeric(sVal)){
|
||||
if (sVal.indexOf(".") >= 0) {
|
||||
val = Double.parseDouble(sVal);
|
||||
} else {
|
||||
val = Integer.parseInt(sVal);
|
||||
}
|
||||
}else if(this.isEntitycharsIgnored()==false && seq!=null && seq.length()>0){
|
||||
// maybe it is a string after all
|
||||
val=unescape(seq);
|
||||
}
|
||||
}else if(this.isEntitycharsIgnored()==false && seq!=null && seq.length()>0){
|
||||
// we had quotes so lets decode escaed chars
|
||||
val=unescape(seq);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public static CharSequence unescape(CharSequence str) {
|
||||
StringBuilder buf = null;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char ch = str.charAt(i);
|
||||
if (ch == '\\' && i < (str.length() - 1)) {
|
||||
i = i + 1;
|
||||
char ch2 = str.charAt(i);
|
||||
switch (ch2) {
|
||||
case '"':
|
||||
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||
buf.append("\"");
|
||||
break;
|
||||
case '\\':
|
||||
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||
buf.append("\\");
|
||||
break;
|
||||
case '/':
|
||||
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||
buf.append("/");
|
||||
break;
|
||||
case 'b':
|
||||
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||
buf.append("\b");
|
||||
break;
|
||||
case 'f':
|
||||
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||
buf.append("\f");
|
||||
break;
|
||||
case 'n':
|
||||
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||
buf.append("\n");
|
||||
break;
|
||||
case 'r':
|
||||
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||
buf.append("\r");
|
||||
break;
|
||||
case 't':
|
||||
if(buf==null) buf=new StringBuilder(i>0?str.subSequence(0, i-1):"");
|
||||
buf.append("\t");
|
||||
break;
|
||||
default:
|
||||
if(buf!=null) buf.append(ch);
|
||||
}
|
||||
} else {
|
||||
if(buf!=null) buf.append(ch);
|
||||
}
|
||||
}
|
||||
return buf!=null?buf.toString():str;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package com.reliancy.rec;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class JSONEncoder{
|
||||
public JSONEncoder(){
|
||||
}
|
||||
/**
|
||||
* We encode into an appendable various primitives and Rec.
|
||||
* If appendable null then we just compute expected size.
|
||||
* keys are not escaped they better not contain any special chars.
|
||||
* values are quoted and escaped unless we detect a string that looks like a json object those are passed thru.
|
||||
* in the past we tried to deduce if quoting was needed, but this is not the place to do so because we do not know how many times
|
||||
* value was escaped so the only thing we can assume is that it needs to be escaped. So feeding a value that is quoted and
|
||||
* escaped will return back on parse the same and will need to dequoted and descaped once more but that shoudl work fine with
|
||||
* whoever quoted it in the upstream in the first place.
|
||||
* @param val property value
|
||||
* @param o encoding output
|
||||
* @return length in characters of encoded result
|
||||
* @throws IOException
|
||||
*/
|
||||
public static int encode(Object val,Appendable o) throws IOException {
|
||||
int len = 0;
|
||||
/*
|
||||
// first key
|
||||
if (key != null) {
|
||||
if (o != null) {
|
||||
o.append('"').append(key).append("\":");
|
||||
}
|
||||
len += 3 + key.length();
|
||||
}
|
||||
*/
|
||||
// now value
|
||||
if (val instanceof Object[]) {
|
||||
Object[] valval = (Object[]) val;
|
||||
if (o != null) {
|
||||
o.append('[');
|
||||
}
|
||||
int index = 0;
|
||||
for (Object obj : valval) {
|
||||
if (index++ > 0) {
|
||||
len += 1;
|
||||
if (o != null) {
|
||||
o.append(",");
|
||||
}
|
||||
}
|
||||
len += encode(obj, o);
|
||||
}
|
||||
if (o != null) {
|
||||
o.append(']');
|
||||
}
|
||||
len += 2;
|
||||
} else if (val instanceof List) {
|
||||
List<?> valval = (List<?>) val;
|
||||
if (o != null) {
|
||||
o.append('[');
|
||||
}
|
||||
int index = 0;
|
||||
for (Object obj : valval) {
|
||||
if (index++ > 0) {
|
||||
len += 1;
|
||||
if (o != null) {
|
||||
o.append(",");
|
||||
}
|
||||
}
|
||||
len += encode(obj, o);
|
||||
}
|
||||
if (o != null) {
|
||||
o.append(']');
|
||||
}
|
||||
len += 2;
|
||||
} else if (val instanceof Map) {
|
||||
len+=encodeMap((Map<?,?>)val,o);
|
||||
} else if (val instanceof Rec) {
|
||||
len += encodeRec((Rec) val, o);
|
||||
} else if (val instanceof Number || val instanceof Boolean) {
|
||||
String str = val.toString();
|
||||
if (o != null) {
|
||||
o.append(str);
|
||||
}
|
||||
len += str.length();
|
||||
}else if(val instanceof int[]){
|
||||
int[] valval = (int[]) val;
|
||||
if (o != null) {
|
||||
o.append('[');
|
||||
}
|
||||
int index = 0;
|
||||
for (int obj : valval) {
|
||||
if (index++ > 0) {
|
||||
len += 1;
|
||||
if (o != null) {
|
||||
o.append(",");
|
||||
}
|
||||
}
|
||||
if(o!=null) o.append(String.valueOf(obj));
|
||||
len += 1;
|
||||
}
|
||||
if (o != null) {
|
||||
o.append(']');
|
||||
}
|
||||
len += 2;
|
||||
}else if(val instanceof float[]){
|
||||
float[] valval = (float[]) val;
|
||||
if (o != null) {
|
||||
o.append('[');
|
||||
}
|
||||
int index = 0;
|
||||
for (float obj : valval) {
|
||||
if (index++ > 0) {
|
||||
len += 1;
|
||||
if (o != null) {
|
||||
o.append(",");
|
||||
}
|
||||
}
|
||||
if(o!=null) o.append(String.valueOf(obj));
|
||||
len += 1;
|
||||
}
|
||||
if (o != null) {
|
||||
o.append(']');
|
||||
}
|
||||
len += 2;
|
||||
}else if (val instanceof Object) {
|
||||
String str = val.toString();
|
||||
boolean jsontxt = false;
|
||||
jsontxt |= str.length() > 0 && str.startsWith("{") && str.endsWith("}");
|
||||
jsontxt |= str.length() > 0 && str.startsWith("[") && str.endsWith("]");
|
||||
//boolean quoted=str.length() > 1 && str.startsWith("\"") && str.endsWith("\"");
|
||||
// embedded json is not quoted and not escaped
|
||||
// all other text is quoted otherwise we will prevent quoted quotes (those would be swallowed)
|
||||
// we will not try to be smart if someone added an item that is quoted already it will be escaped and queotes retained
|
||||
// we must be consistent so that repeated parse and encode works and not too smart here
|
||||
// we need to put quotes around unless
|
||||
if (!jsontxt) {
|
||||
str = escape(str);
|
||||
if (o != null) {
|
||||
o.append('"');
|
||||
}
|
||||
len += 1;
|
||||
}
|
||||
if (o != null) {
|
||||
o.append(str);
|
||||
}
|
||||
len += str.length();
|
||||
if (!jsontxt) {
|
||||
if (o != null) {
|
||||
o.append('"');
|
||||
}
|
||||
len += 1;
|
||||
}
|
||||
} else if (val == null) {
|
||||
String str = "null";
|
||||
if (o != null) {
|
||||
o.append(str);
|
||||
}
|
||||
len += str.length();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
public static int encodeMap(Map<?,?> valval,Appendable o) throws IOException{
|
||||
int len=0;
|
||||
if (o != null) {
|
||||
o.append('{');
|
||||
}
|
||||
int index = 0;
|
||||
for (Object obj : valval.keySet()) {
|
||||
if (index++ > 0) {
|
||||
len += 1;
|
||||
if (o != null) {
|
||||
o.append(",");
|
||||
}
|
||||
}
|
||||
String key=obj.toString();
|
||||
if (o != null) {
|
||||
o.append('"').append(key).append("\":");
|
||||
}
|
||||
len += 3 + key.length();
|
||||
len += encode(valval.get(obj), o);
|
||||
}
|
||||
if (o != null) {
|
||||
o.append('}');
|
||||
}
|
||||
len += 2;
|
||||
return len;
|
||||
}
|
||||
public static int encodeRec(Rec val,Appendable o) throws IOException{
|
||||
int len=0;
|
||||
if (o != null) {
|
||||
o.append(val.isArray()?"[":"{");
|
||||
}
|
||||
for (int i=0;i<val.count();i++) {
|
||||
Slot k=val.getSlot(i);
|
||||
Object v=val.get(i);
|
||||
if (i > 0) {
|
||||
len += 1;
|
||||
if (o != null) {
|
||||
o.append(",");
|
||||
}
|
||||
}
|
||||
if(k!=null){
|
||||
String key=k.getName();
|
||||
if (o != null) {
|
||||
o.append('"').append(key).append("\":");
|
||||
}
|
||||
len += 3 + key.length();
|
||||
}
|
||||
len += encode(v, o);
|
||||
}
|
||||
if (o != null) {
|
||||
o.append(val.isArray()?"]":"}");
|
||||
}
|
||||
len += 2;
|
||||
return len;
|
||||
}
|
||||
/**
|
||||
* @param str
|
||||
* @return true if the string includes any of the special chars.
|
||||
*/
|
||||
public static boolean needsEscaping(String str) {
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char ch = str.charAt(i);
|
||||
switch (ch) {
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* this helper method handle quotes and control chars.
|
||||
* @param str input string
|
||||
* @return output after encoding special chars
|
||||
*/
|
||||
public static String escape(String str) {
|
||||
StringBuilder buf = null;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char ch = str.charAt(i);
|
||||
switch (ch) {
|
||||
case '"':
|
||||
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||
buf.append("\\\"");
|
||||
break;
|
||||
case '\\':
|
||||
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||
buf.append("\\\\");
|
||||
break;
|
||||
case '/':
|
||||
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||
buf.append("\\/");
|
||||
break;
|
||||
case '\b':
|
||||
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||
buf.append("\\b");
|
||||
break;
|
||||
case '\f':
|
||||
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||
buf.append("\\f");
|
||||
break;
|
||||
case '\n':
|
||||
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||
buf.append("\\n");
|
||||
break;
|
||||
case '\r':
|
||||
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||
buf.append("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||
buf.append("\\t");
|
||||
break;
|
||||
default:
|
||||
if(buf!=null) buf.append(ch);
|
||||
}
|
||||
}
|
||||
return buf!=null?buf.toString():str;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.reliancy.rec;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Default implementation of a Rec.
|
||||
* We separate keys and values because Obj could just be an array.
|
||||
* If object is declated an array keys are nonexistant and rec related methods will return null or crash.
|
||||
* Our setters return this object to main the calls chainable.
|
||||
* Also positional calls accept negative values which reference from end backward.
|
||||
*/
|
||||
public class Obj implements Rec{
|
||||
final List<Object> values;
|
||||
final Hdr meta;
|
||||
|
||||
public Obj() {
|
||||
values=new ArrayList<>();
|
||||
meta=new Slot(null);
|
||||
}
|
||||
public Obj(boolean is_array) {
|
||||
values=new ArrayList<>();
|
||||
meta=new Slot(null);
|
||||
if(is_array) meta.raiseFlags(Hdr.FLAG_ARRAY);
|
||||
}
|
||||
public Obj(List<Slot> k,List<Object> v) {
|
||||
values=v;
|
||||
meta=new Slot(null);
|
||||
meta.keys.addAll(k);
|
||||
}
|
||||
/**
|
||||
* This ctor is reserved for derivations with fixed slot definitions.
|
||||
* This constructor will inspect static Slot members and construct keys that way
|
||||
* if meta named.
|
||||
* @param def
|
||||
*/
|
||||
protected Obj(Hdr def){
|
||||
values=new ArrayList<>();
|
||||
meta=def;
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
StringBuilder buf=new StringBuilder();
|
||||
toString(buf);
|
||||
return buf.toString();
|
||||
}
|
||||
public int toString(StringBuilder buf){
|
||||
boolean is_arr=isArray();
|
||||
int length0=buf.length();// length before anything done
|
||||
//StringBuffer indent=new StringBuffer(); // detect indent
|
||||
//for(int i=length0;i>0 && Character.isWhitespace(buf.charAt(i));i--){
|
||||
// indent.append(buf.codePointAt(i));
|
||||
//}
|
||||
buf.append(is_arr?"[":"{");
|
||||
if(is_arr){
|
||||
for(int pos=0;pos<count();pos++){
|
||||
if(pos>0) buf.append(",");
|
||||
Object val=this.get(pos);
|
||||
if(val instanceof Obj) ((Obj)val).toString(buf);
|
||||
else if(val!=null) buf.append(val.toString());
|
||||
else buf.append("null");
|
||||
}
|
||||
}else{
|
||||
for(int pos=0;pos<count();pos++){
|
||||
if(pos>0) buf.append(",");
|
||||
Slot s=getSlot(pos);
|
||||
buf.append(s.getName()+":");
|
||||
Object val=this.get(pos);
|
||||
if(val!=null) s.toString(val,buf); else buf.append("null");
|
||||
}
|
||||
}
|
||||
buf.append(is_arr?"]":"}");
|
||||
return buf.length()-length0;
|
||||
}
|
||||
@Override
|
||||
public Hdr meta(){
|
||||
return meta;
|
||||
}
|
||||
@Override
|
||||
public boolean isArray(){
|
||||
return meta==null || meta.checkFlags(Hdr.FLAG_ARRAY);
|
||||
}
|
||||
@Override
|
||||
public int count() {
|
||||
return values.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rec set(int pos, Object val) {
|
||||
if(pos<0) pos=count()+pos;
|
||||
values.set(pos,val);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(int pos) {
|
||||
if(pos<0) pos=count()+pos;
|
||||
return values.get(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rec add(Object val) {
|
||||
values.add(val);
|
||||
if(!isArray()) meta.addSlot(new Slot("arg"+count(),Object.class));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rec remove(int s) {
|
||||
values.remove(s);
|
||||
if(!isArray()) meta.removeSlot(s);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Rec set(Slot s, Object val) {
|
||||
if(s==null) throw new IllegalArgumentException("invalid key provided");
|
||||
if(isArray()) throw new IllegalStateException("array not mappable with:"+s.getName());
|
||||
int index=s.getPosition(); // try slot position
|
||||
if(index<0) index=meta.findSlot(s.getName());// fall back to search if slot not set
|
||||
if(index<0){
|
||||
values.add(val);
|
||||
meta.addSlot(s);
|
||||
}else{
|
||||
values.set(index,val);
|
||||
meta.setSlot(index,s);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Returns value by slot key.
|
||||
* If the underlying rec is a vec/array this method might work if slot is positioned else it will
|
||||
* return def value.
|
||||
*/
|
||||
@Override
|
||||
public Object get(Slot s, Object def) {
|
||||
if(s==null) throw new IllegalArgumentException("invalid key provided");
|
||||
//if(keys==null) throw new IllegalStateException("array not mappable with:"+s.getName());
|
||||
int index=s.getPosition(); // try slot position
|
||||
if(index<0 && !isArray()) index=meta.findSlot(s.getName());// fall back to search if slot not set
|
||||
return index<0?def:values.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rec remove(Slot s) {
|
||||
int index=s.getPosition(); // try slot position
|
||||
if(index<0 && !isArray()) index=meta.findSlot(s.getName());// fall back to search if slot not set
|
||||
if(index>=0) remove(index);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.reliancy.rec;
|
||||
|
||||
/**
|
||||
* A record representation like in JSON.
|
||||
* This is either an array or a map of fields.
|
||||
* Each field definition we call a slot.
|
||||
*/
|
||||
public interface Rec extends Vec{
|
||||
public Rec set(Slot s,Object val);
|
||||
public Object get(Slot s,Object def);
|
||||
public Rec remove(Slot s);
|
||||
public default Slot getSlot(String name){
|
||||
Hdr m=meta();
|
||||
return m!=null?m.getSlot(name):null;
|
||||
}
|
||||
public default Slot getSlot(int pos){
|
||||
Hdr m=meta();
|
||||
return m!=null?m.getSlot(pos):null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.reliancy.rec;
|
||||
|
||||
/**
|
||||
* Slot is a definition of a value start with the name.
|
||||
* We use it to define columns/fields of records.
|
||||
* It is also used as header of actual records.
|
||||
*/
|
||||
public class Slot extends Hdr {
|
||||
|
||||
public static interface Initializer{
|
||||
Object getInitalValue(Slot s,Rec rec);
|
||||
}
|
||||
public static final Initializer DEFAULT_INITIALIZER=new Initializer(){
|
||||
public Object getInitalValue(Slot s,Rec rec) {return s.getDefaultValue();}
|
||||
};
|
||||
int position;
|
||||
Object defaultValue;
|
||||
Initializer initValue;
|
||||
|
||||
public Slot(String name){
|
||||
this(name,Object.class);
|
||||
}
|
||||
public Slot(String name,Class<?> type){
|
||||
super(name,type);
|
||||
this.position=-1;
|
||||
this.initValue=DEFAULT_INITIALIZER;
|
||||
}
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
public void setPosition(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
public Object getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
public void setDefaultValue(Object defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
public Initializer getInitValue() {
|
||||
return initValue;
|
||||
}
|
||||
public void setInitValue(Initializer initValue) {
|
||||
this.initValue = initValue;
|
||||
}
|
||||
public int toString(Object val, StringBuilder buf) {
|
||||
int length0=buf.length();
|
||||
if(val instanceof Obj) ((Obj)val).toString(buf);
|
||||
else if(val!=null) buf.append(val.toString());
|
||||
else buf.append("null");
|
||||
return buf.length()-length0;
|
||||
}
|
||||
public Object get(Rec r,Object def){
|
||||
return r.get(this, def);
|
||||
}
|
||||
public Slot set(Rec r,Object val){
|
||||
r.set(this, val);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package com.reliancy.rec;
|
||||
|
||||
/** An interface used in parser implementation.
|
||||
*
|
||||
* @author amer
|
||||
*/
|
||||
public interface TextDecoder {
|
||||
void beginDocument(Rec init);
|
||||
Rec endDocument();
|
||||
public int parse(int offset,CharSequence in);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.reliancy.rec;
|
||||
/**
|
||||
* dimensioned container of values.
|
||||
* Our setters return this object to make the calls chainable.
|
||||
* Also positional calls accept negative values which reference from end backward.
|
||||
*
|
||||
*/
|
||||
public interface Vec {
|
||||
public default boolean isArray(){
|
||||
return meta().checkFlags(Hdr.FLAG_ARRAY);
|
||||
}
|
||||
public Hdr meta();
|
||||
public int count();
|
||||
public Rec set(int pos,Object val);
|
||||
public Object get(int pos);
|
||||
public Rec add(Object val);
|
||||
public Rec remove(int s);
|
||||
}
|
||||
Reference in New Issue
Block a user