Refocus Jabba on web runtime and improve file serving
This commit is contained in:
@@ -1,230 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
/** Description of a terminal operation with a slice of dbo objects as input or output.
|
||||
* This object is not just for reading but also bulk updating.
|
||||
* It will be used to describe a multi DBO read or write and to then also track results.
|
||||
* At its core are action traits which are classes that define either loading,saving or deleting.
|
||||
* The items field is a consumable object when consumed the action is done.
|
||||
* So for loading we iterate once done it cannot be done again. Also when items are provided for saving
|
||||
* once iterated over and saved we are done.
|
||||
*/
|
||||
public class Action implements Iterable<DBO>,SiphonIterator<DBO>{
|
||||
public static class Trait{
|
||||
public String toString(){return getClass().getSimpleName();}
|
||||
}
|
||||
public static class Load extends Trait{
|
||||
int limit,offset;
|
||||
Check filter;
|
||||
}
|
||||
public static class Save extends Trait{
|
||||
|
||||
}
|
||||
public static class Delete extends Trait{
|
||||
Check filter;
|
||||
}
|
||||
|
||||
Terminal terminal;
|
||||
Trait trait;
|
||||
Entity entity;
|
||||
Object[] params;
|
||||
SiphonIterator<DBO> items;
|
||||
|
||||
public Action(){
|
||||
trait=null;
|
||||
}
|
||||
public Action(Trait t){
|
||||
trait=t;
|
||||
}
|
||||
public Action(Terminal t){
|
||||
terminal=t;
|
||||
trait=null;
|
||||
}
|
||||
public Action execute() throws IOException{
|
||||
return terminal.execute(this);
|
||||
}
|
||||
|
||||
public Terminal getTerminal() {
|
||||
return terminal;
|
||||
}
|
||||
public Action setTerminal(Terminal terminal) {
|
||||
this.terminal = terminal;
|
||||
return this;
|
||||
}
|
||||
public Trait getTrait() {
|
||||
return trait;
|
||||
}
|
||||
public Action setTrait(Trait t) {
|
||||
this.trait = t;
|
||||
return this;
|
||||
}
|
||||
public Entity getEntity() {
|
||||
return entity;
|
||||
}
|
||||
public Action setEntity(Entity entity) {
|
||||
this.entity = entity;
|
||||
return this;
|
||||
}
|
||||
public void clear(){
|
||||
terminal=null;
|
||||
trait=null;
|
||||
entity=null;
|
||||
setItems((DBO)null);
|
||||
}
|
||||
public Action load(Entity ent){
|
||||
trait=new Load();
|
||||
entity=ent;
|
||||
return this;
|
||||
}
|
||||
public Action load(Class<? extends DBO> cls){
|
||||
trait=new Load();
|
||||
entity=Entity.recall(cls);
|
||||
return this;
|
||||
}
|
||||
public Action save(Entity ent){
|
||||
trait=new Save();
|
||||
entity=ent;
|
||||
return this;
|
||||
}
|
||||
public Action delete(Entity ent){
|
||||
trait=new Delete();
|
||||
entity=ent;
|
||||
return this;
|
||||
}
|
||||
public Action params(Object...p){
|
||||
params=p;
|
||||
return this;
|
||||
}
|
||||
public Action setItems(final DBO ...itms){
|
||||
SiphonIterator<DBO> it=null;
|
||||
if(itms!=null){
|
||||
it=new SiphonIterator<DBO>() {
|
||||
private int index = 0;
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return itms.length > index;
|
||||
}
|
||||
@Override
|
||||
public DBO next() {
|
||||
return itms[index++];
|
||||
}
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
}
|
||||
};
|
||||
}
|
||||
return setItems(it);
|
||||
}
|
||||
public Action setItems(final Collection<DBO> itms){
|
||||
SiphonIterator<DBO> it=null;
|
||||
if(itms!=null){
|
||||
it=new SiphonIterator<DBO>() {
|
||||
private final Iterator<DBO> str = itms.iterator();
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return str.hasNext();
|
||||
}
|
||||
@Override
|
||||
public DBO next() {
|
||||
return str.next();
|
||||
}
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
}
|
||||
};
|
||||
}
|
||||
return setItems(it);
|
||||
}
|
||||
public Action setItems(SiphonIterator<DBO> itms){
|
||||
if(items==itms) return this;
|
||||
if(items!=null){
|
||||
try {
|
||||
items.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
items=itms;
|
||||
return this;
|
||||
}
|
||||
protected SiphonIterator<DBO> getItems(){
|
||||
return items;
|
||||
}
|
||||
@Override
|
||||
public Iterator<DBO> iterator() {
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return items!=null?items.hasNext():false;
|
||||
}
|
||||
@Override
|
||||
public DBO next() {
|
||||
return items.next();
|
||||
}
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if(items!=null){
|
||||
items.close();
|
||||
items=null;
|
||||
if(terminal!=null) terminal.end(this);
|
||||
}
|
||||
}
|
||||
public Action limit(int max) {
|
||||
((Load)trait).limit=max;
|
||||
return this;
|
||||
}
|
||||
public Action filterBy(Check... c){
|
||||
Check filter=null;
|
||||
if(c!=null){
|
||||
if(c.length>1) filter=Check.and(c);
|
||||
else filter=c[0];
|
||||
}
|
||||
if(trait instanceof Load){
|
||||
((Load)trait).filter=filter;
|
||||
}else
|
||||
if(trait instanceof Delete){
|
||||
((Delete)trait).filter=filter;
|
||||
}else{
|
||||
throw new IllegalStateException("filtering not supported by trait:"+trait);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public Check getFilter(){
|
||||
if(trait instanceof Load){
|
||||
return ((Load)trait).filter;
|
||||
}else
|
||||
if(trait instanceof Delete){
|
||||
return ((Delete)trait).filter;
|
||||
}else{
|
||||
throw new IllegalStateException("filtering not supported by trait:"+trait);
|
||||
}
|
||||
}
|
||||
public Action if_pk(Object... id) {
|
||||
Field pk=entity.getPk();
|
||||
return filterBy(pk.eq(id));
|
||||
}
|
||||
public DBO first() {
|
||||
try{
|
||||
if(this.hasNext()){
|
||||
return this.next();
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}finally{
|
||||
clear();
|
||||
}
|
||||
}
|
||||
public boolean isDone(){
|
||||
return items==null || items.hasNext()==false;
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Observable;
|
||||
|
||||
/** A more or less virtual collection of items.
|
||||
* this object is a suitable holder of resultsets. it will be overridable so
|
||||
* we can create specialized virtual holders that use backends. by itself it will
|
||||
* implement in memory list.
|
||||
* also this class is an observable and we can monitor update to it.
|
||||
*/
|
||||
public class Bag<E> extends Observable implements Collection<E>{
|
||||
/** event to send to observers. */
|
||||
public static final class BagChanged<E>{
|
||||
public static final int ADD=0;
|
||||
public static final int REMOVE=1;
|
||||
public static final int ACCESS=2;
|
||||
public static final int POST_LOAD=3;
|
||||
public static final int PRE_SAVE=4;
|
||||
final Bag<E> bag;
|
||||
final int operation;
|
||||
final Object[] arguments;
|
||||
|
||||
public BagChanged(Bag<E> p,int op,Object ... args){
|
||||
bag=p;
|
||||
operation=op;
|
||||
arguments=args;
|
||||
}
|
||||
public Bag<E> getBag() {
|
||||
return bag;
|
||||
}
|
||||
public int getOperation() {
|
||||
return operation;
|
||||
}
|
||||
public Object[] getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
final ArrayList<E> items=new ArrayList<>();
|
||||
|
||||
public Bag(){
|
||||
}
|
||||
public Bag(Iterable<E> o){
|
||||
this(o.iterator());
|
||||
}
|
||||
public Bag(Iterator<E> o){
|
||||
while(o.hasNext()) add(o.next());
|
||||
}
|
||||
@Override
|
||||
public int size() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return size()==0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
final Iterator<E> it=iterator();
|
||||
while(it.hasNext()){
|
||||
final E e=it.next();
|
||||
if(e!=null && o!=null && e.equals(o)) return true;
|
||||
else if(e==o) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
for (Object e : c) if (!contains(e)) return false;
|
||||
return true;
|
||||
}
|
||||
public ListIterator<E> listIterator(){
|
||||
return listIterator(0);
|
||||
}
|
||||
public ListIterator<E> listIterator(int offset){
|
||||
return items.listIterator(offset);
|
||||
}
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return items.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return toArray(new Object[size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return items.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
if(items.contains(e)) return true;
|
||||
if(countObservers()>0){
|
||||
BagChanged<E> evt=new Bag.BagChanged<>(this,BagChanged.ADD,e);
|
||||
setChanged();
|
||||
notifyObservers(evt);
|
||||
}
|
||||
return items.add(e);
|
||||
}
|
||||
public Bag<E> append(E e){
|
||||
add(e);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
if(!contains(o)) return false;
|
||||
if(countObservers()>0){
|
||||
BagChanged<E> evt=new Bag.BagChanged<>(this,BagChanged.REMOVE,o);
|
||||
setChanged();
|
||||
notifyObservers(evt);
|
||||
}
|
||||
return items.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
if(countObservers()>0){
|
||||
BagChanged<E> evt=new Bag.BagChanged<>(this,BagChanged.ADD,c.toArray());
|
||||
setChanged();
|
||||
notifyObservers(evt);
|
||||
}
|
||||
if(c==null || c.size()==0) return false;
|
||||
c.forEach(e->{this.append(e);});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
if(countObservers()>0){
|
||||
BagChanged<E> evt=new Bag.BagChanged<>(this,BagChanged.REMOVE,c!=null?c.toArray():null);
|
||||
setChanged();
|
||||
notifyObservers(evt);
|
||||
}
|
||||
if(c!=null){
|
||||
return items.removeAll(c);
|
||||
}else{
|
||||
items.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
return items.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
removeAll(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/** constraint on a field.
|
||||
* conditions can be leafs or groups such as and,or,not
|
||||
*/
|
||||
public class Check implements Iterable<Check> {
|
||||
public static abstract class Op{
|
||||
public abstract boolean met(Check c,Object val);
|
||||
}
|
||||
/** logical AND operation. */
|
||||
public static Op AND=new Op(){
|
||||
public String toString(){return "AND";}
|
||||
public boolean met(Check c,Object val){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/** logical OR operation. */
|
||||
public static Op OR=new Op(){
|
||||
public String toString(){return "OR";}
|
||||
public boolean met(Check c,Object val){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/** logical NOT operation. */
|
||||
public static Op NOT=new Op(){
|
||||
public String toString(){return "NOT";}
|
||||
public boolean met(Check c,Object val){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/** arithmetic equal test. */
|
||||
public static Op EQ=new Op(){
|
||||
public String toString(){return "=";}
|
||||
public boolean met(Check c,Object val){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/** arithmetic negated equal test. */
|
||||
public static Op NEQ=new Op(){
|
||||
public String toString(){return "<>";}
|
||||
public boolean met(Check c,Object val){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/** greater than check. */
|
||||
public static Op GT=new Op(){
|
||||
public String toString(){return ">";}
|
||||
public boolean met(Check c,Object val){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/** greater than or equal check. */
|
||||
public static Op GTE=new Op(){
|
||||
public String toString(){return ">=";}
|
||||
public boolean met(Check c,Object val){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/** less than check. */
|
||||
public static Op LT=new Op(){
|
||||
public String toString(){return "<";}
|
||||
public boolean met(Check c,Object val){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/** less than or equal check. */
|
||||
public static Op LTE=new Op(){
|
||||
public String toString(){return "<=";}
|
||||
public boolean met(Check c,Object val){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/** like check case insensitive. */
|
||||
public static Op LIKE=new Op(){
|
||||
public String toString(){return "LIKE";}
|
||||
public boolean met(Check c,Object val){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/** set membership check. */
|
||||
public static Op IN=new Op(){
|
||||
public String toString(){return "IN";}
|
||||
public boolean met(Check c,Object val){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/** negated set membership check. */
|
||||
public static Op NOT_IN=new Op(){
|
||||
public String toString(){return "NOT IN";}
|
||||
public boolean met(Check c,Object val){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/** iterator over checks.
|
||||
*
|
||||
*/
|
||||
public static class CheckIterator implements Iterator<Check>{
|
||||
final Check root;
|
||||
Check cur;
|
||||
int index;
|
||||
public CheckIterator(Check ch){
|
||||
root=ch;
|
||||
cur=root;
|
||||
index=0;
|
||||
}
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return cur.isLeaf()==false && index<cur.args.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Check next() {
|
||||
return (Check)cur.args[index++];
|
||||
}
|
||||
|
||||
}
|
||||
public static Check and(Check... c) {
|
||||
return new Check(AND,c);
|
||||
}
|
||||
public static Check all(Check... c) {
|
||||
return new Check(AND,c);
|
||||
}
|
||||
public static Check or(Check... c) {
|
||||
return new Check(OR,c);
|
||||
}
|
||||
public static Check any(Check... c) {
|
||||
return new Check(OR,c);
|
||||
}
|
||||
public static Check not(Check... c) {
|
||||
return new Check(NOT,c);
|
||||
}
|
||||
public static Check none(Check... c) {
|
||||
return new Check(NOT,c);
|
||||
}
|
||||
public static Check eq(Field pk, Object... args) {
|
||||
Object id=args;
|
||||
if(id!=null && args.length==1) id=args[0];
|
||||
return new Check(EQ,pk,id);
|
||||
}
|
||||
public static Check neq(Field pk, Object... args) {
|
||||
Object id=args;
|
||||
if(id!=null && args.length==1) id=args[0];
|
||||
return new Check(NEQ,pk,id);
|
||||
}
|
||||
public static Check gt(Field pk, Object... args) {
|
||||
Object id=args;
|
||||
if(id!=null && args.length==1) id=args[0];
|
||||
return new Check(GT,pk,id);
|
||||
}
|
||||
public static Check gte(Field pk, Object... args) {
|
||||
Object id=args;
|
||||
if(id!=null && args.length==1) id=args[0];
|
||||
return new Check(GTE,pk,id);
|
||||
}
|
||||
public static Check lt(Field pk, Object... args) {
|
||||
Object id=args;
|
||||
if(id!=null && args.length==1) id=args[0];
|
||||
return new Check(LT,pk,id);
|
||||
}
|
||||
public static Check lte(Field pk, Object... args) {
|
||||
Object id=args;
|
||||
if(id!=null && args.length==1) id=args[0];
|
||||
return new Check(LTE,pk,id);
|
||||
}
|
||||
public static Check like(Field pk, Object... args) {
|
||||
Object id=args;
|
||||
if(id!=null && args.length==1) id=args[0];
|
||||
return new Check(LIKE,pk,id);
|
||||
}
|
||||
public static Check in(Field pk, Object... id) {
|
||||
return new Check(IN,pk,id);
|
||||
}
|
||||
public static Check not_in(Field pk, Object... id) {
|
||||
return new Check(NOT_IN,pk,id);
|
||||
}
|
||||
Op code;
|
||||
boolean leaf;
|
||||
Object[] args;
|
||||
boolean locked;
|
||||
|
||||
public Check(Op code,Field f,Object val){
|
||||
this.code=code;
|
||||
leaf=true;
|
||||
args=new Object[]{f,val};
|
||||
}
|
||||
public Check(Op code,Check ... sub){
|
||||
this.code=code;
|
||||
leaf=false;
|
||||
args=sub;
|
||||
}
|
||||
public Check setLocked(boolean f){
|
||||
locked=f;
|
||||
return this;
|
||||
}
|
||||
public boolean isLocked(){
|
||||
return locked;
|
||||
}
|
||||
public Op getCode(){
|
||||
return code;
|
||||
}
|
||||
public boolean isLeaf(){
|
||||
return leaf;
|
||||
}
|
||||
public boolean met(Object val){
|
||||
return code.met(this,val);
|
||||
}
|
||||
@Override
|
||||
public Iterator<Check> iterator() {
|
||||
return new CheckIterator(this);
|
||||
}
|
||||
public int getChildCount(){
|
||||
return leaf?0:args.length;
|
||||
}
|
||||
public Check getChild(int index){
|
||||
return leaf?null:(Check)args[index];
|
||||
}
|
||||
public Field getField(){
|
||||
return (Field)args[0];
|
||||
}
|
||||
public Object getValue(){
|
||||
return (Object)args[1];
|
||||
}
|
||||
public Check setValue(Object val){
|
||||
if(locked) throw new IllegalStateException("check value is locked");
|
||||
if(!leaf) throw new IllegalStateException("check is not a leaf");
|
||||
args[1]=val;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.reliancy.rec.Hdr;
|
||||
import com.reliancy.rec.JSON;
|
||||
import com.reliancy.rec.Rec;
|
||||
import com.reliancy.rec.Slot;
|
||||
|
||||
/** Instance of an entity, usually a row in a table.
|
||||
*
|
||||
*/
|
||||
public class DBO implements Rec{
|
||||
public static enum Status{
|
||||
NEW,USED,DELETED,COMPUTED
|
||||
}
|
||||
Terminal terminal;
|
||||
Entity type;
|
||||
Status status;
|
||||
Object[] values;
|
||||
|
||||
public DBO() {
|
||||
Class<? extends DBO> cls=this.getClass();
|
||||
if(cls!=DBO.class){
|
||||
Entity ent=Entity.recall(cls);
|
||||
setType(ent);
|
||||
}
|
||||
status=Status.NEW;
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
try {
|
||||
StringBuffer ret=new StringBuffer();
|
||||
JSON.writes(this,ret);
|
||||
return ret.toString();
|
||||
} catch (IOException e) {
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
public Terminal getTerminal() {
|
||||
return terminal;
|
||||
}
|
||||
public DBO setTerminal(Terminal terminal) {
|
||||
this.terminal = terminal;
|
||||
return this;
|
||||
}
|
||||
public Status getStatus(){
|
||||
return status;
|
||||
}
|
||||
public DBO setStatus(Status s) {
|
||||
this.status = s;
|
||||
return this;
|
||||
}
|
||||
public final Entity getType() {
|
||||
return type;
|
||||
}
|
||||
public final DBO setType(Entity type) {
|
||||
this.type = type;
|
||||
if(type==null){
|
||||
values=null;
|
||||
}else{
|
||||
values=new Object[type.count()];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public Hdr meta() {
|
||||
return type;
|
||||
}
|
||||
@Override
|
||||
public int count() {
|
||||
return values!=null?values.length:0;
|
||||
}
|
||||
@Override
|
||||
public Rec set(int pos, Object val) {
|
||||
if(pos<0) pos=count()+pos;
|
||||
values[pos]=val;
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public Object get(int pos) {
|
||||
if(pos<0) pos=count()+pos;
|
||||
return values[pos];
|
||||
}
|
||||
@Override
|
||||
public Rec add(Object val) {
|
||||
throw new UnsupportedOperationException("dbo is not array");
|
||||
}
|
||||
@Override
|
||||
public Rec remove(int s) {
|
||||
throw new UnsupportedOperationException("dbo is not array");
|
||||
}
|
||||
@Override
|
||||
public Rec set(Slot s, Object val) {
|
||||
if(s==null) throw new IllegalArgumentException("invalid key provided");
|
||||
int index=s.getPosition(); // try slot position
|
||||
//if(index<0) index=type.findSlot(s.getName());// fall back to search if slot not set
|
||||
if(index<0){
|
||||
throw new IllegalArgumentException("invalid key provided:"+s.getName());
|
||||
}else{
|
||||
values[index]=val;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public Object get(Slot s, Object def) {
|
||||
if(s==null) throw new IllegalArgumentException("invalid key provided");
|
||||
int index=s.getPosition(); // try slot position
|
||||
//if(index<0) index=type.findSlot(s.getName());// fall back to search if slot not set
|
||||
if(index<0) throw new IllegalArgumentException("invalid key provided:"+s.getName());
|
||||
Object ret=values[index];
|
||||
return ret==null?def:ret;
|
||||
}
|
||||
@Override
|
||||
public Rec remove(Slot s) {
|
||||
throw new UnsupportedOperationException("dbo is not resizable");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import com.reliancy.rec.Hdr;
|
||||
import com.reliancy.rec.Slot;
|
||||
|
||||
/** Describes an object structure, usually a table.
|
||||
*
|
||||
*/
|
||||
public class Entity extends Hdr{
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public static @interface Info {
|
||||
String name();
|
||||
}
|
||||
static final HashMap<String,Entity> registry=new HashMap<>();
|
||||
public static final void publish(Entity ent){
|
||||
registry.put(ent.getName(),ent);
|
||||
registry.put(ent.getId(),ent);
|
||||
}
|
||||
public static final void retract(Entity ent){
|
||||
if(ent==null) return;
|
||||
Collection<Entity> vals=registry.values();
|
||||
while(vals.remove(ent)){}
|
||||
}
|
||||
public static final void retract(Class<? extends DBO> cls){
|
||||
Entity ent=recall(cls.getSimpleName());
|
||||
if(ent!=null){
|
||||
retract(ent);
|
||||
}
|
||||
}
|
||||
public static final Entity recall(String name){
|
||||
return registry.get(name);
|
||||
}
|
||||
|
||||
public static final Entity recall(Class<? extends DBO> cls){
|
||||
Entity ent=recall(cls.getSimpleName());
|
||||
if(ent==null){
|
||||
ent=publish(cls);
|
||||
}
|
||||
return ent;
|
||||
}
|
||||
/**
|
||||
* this method will analyze a DBO class and forumate an Entity object out of it.
|
||||
* @param cls
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Entity publish(Class<? extends DBO> cls){
|
||||
Entity ret=registry.get(cls.getSimpleName());
|
||||
if(ret!=null) return ret;
|
||||
//System.out.println("Analyzing:"+cls);
|
||||
Class<?> base=cls.getSuperclass();
|
||||
Entity base_ent=null;
|
||||
int position0=0;
|
||||
if(base!=null && base!=DBO.class){
|
||||
base_ent=publish((Class<? extends DBO>)base);
|
||||
position0=base_ent.count();
|
||||
}
|
||||
java.lang.reflect.Field[] declaredFields = cls.getDeclaredFields();
|
||||
ArrayList<Field> slots=new ArrayList<>();
|
||||
for (java.lang.reflect.Field field : declaredFields) {
|
||||
if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
String sf_name=field.getName();
|
||||
Field slot=(Field) field.get(cls);
|
||||
// Only set ID if not already set (allows explicit database column name mapping)
|
||||
// Use Field's name (database column name) if available, otherwise use Java field name
|
||||
if(slot.getId()==null || slot.getId().isEmpty()){
|
||||
String dbName=slot.getName(); // This is the name passed to Field constructor (e.g., "created_on")
|
||||
if(dbName!=null && !dbName.isEmpty()){
|
||||
slot.setId(dbName);
|
||||
}else{
|
||||
slot.setId(sf_name); // Fallback to Java field name
|
||||
}
|
||||
}
|
||||
slot.setPosition(position0+slots.size());
|
||||
slots.add(slot);
|
||||
//System.out.println(sf_name+":"+slot+" atpos:"+slot.getPosition());
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
Info info=cls.getAnnotation(Info.class);
|
||||
ret=new Entity(info!=null?info.name():cls.getSimpleName()).setId(cls.getSimpleName());
|
||||
ret.setBase(base_ent);
|
||||
ret.setType(cls);
|
||||
ret.getOwnSlots().addAll(slots);
|
||||
publish(ret);
|
||||
return ret;
|
||||
}
|
||||
Entity base;
|
||||
String id;
|
||||
Field pk;
|
||||
public Entity(String name) {
|
||||
super(name);
|
||||
}
|
||||
@Override
|
||||
public Slot makeSlot(String name){
|
||||
return new Field(name);
|
||||
}
|
||||
@Override
|
||||
public Iterator<Slot> iterator(int offset){
|
||||
if(offset>0) throw new IllegalArgumentException("Offset not supported");
|
||||
final Entity ent=this;
|
||||
return new Iterator<Slot>(){
|
||||
final FieldSlice slice=new FieldSlice(ent).including(Field.FLAG_STORABLE);
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return slice.hasNext();
|
||||
}
|
||||
@Override
|
||||
public Slot next() {
|
||||
return slice.next();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
@Override
|
||||
public int count(){
|
||||
return super.count()+(base!=null?base.count():0);
|
||||
}
|
||||
/**
|
||||
* gets a slot which could be here or in base.
|
||||
*/
|
||||
@Override
|
||||
public Slot getSlot(int pos){
|
||||
if(base!=null){ // we got base
|
||||
int ofs=base.count();
|
||||
if(pos<ofs) return base.getSlot(pos);
|
||||
else return super.getSlot(pos-ofs);
|
||||
}else{ // regular no base
|
||||
return super.getSlot(pos);
|
||||
}
|
||||
}
|
||||
public Field getField(int index){
|
||||
return (Field)getSlot(index);
|
||||
}
|
||||
public int getDepth(){
|
||||
return base!=null?1+base.getDepth():0;
|
||||
}
|
||||
public Entity getBase() {
|
||||
return base;
|
||||
}
|
||||
public Entity setBase(Entity base) {
|
||||
this.base = base;
|
||||
return this;
|
||||
}
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
public Entity setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
public Entity setPk(Field pk) {
|
||||
this.pk = pk;
|
||||
return this;
|
||||
}
|
||||
public Field getPk(){
|
||||
if(pk!=null) return pk;
|
||||
// try to locate the pk - this now gos over base as well
|
||||
for(int i=0;i<count() && pk==null;i++){
|
||||
Field pp=(Field) getSlot(i);
|
||||
if(pp.isPk()) pk=pp;
|
||||
}
|
||||
return pk;
|
||||
}
|
||||
public DBO newInstance() throws InstantiationException, IllegalAccessException{
|
||||
return newInstance(null).setStatus(DBO.Status.NEW);
|
||||
}
|
||||
public DBO newInstance(Terminal t) throws InstantiationException, IllegalAccessException{
|
||||
Class<?> cls=getType();
|
||||
DBO ret=(DBO) cls.newInstance();
|
||||
ret.setType(this).setTerminal(t).setStatus(DBO.Status.NEW);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Date;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import com.reliancy.rec.Slot;
|
||||
/**
|
||||
* Description of a column or property.
|
||||
*/
|
||||
public class Field extends Slot {
|
||||
public static Field Int(String name){
|
||||
return new Field(name,Integer.class);
|
||||
}
|
||||
public static Field Str(String name){
|
||||
return new Field(name,String.class);
|
||||
}
|
||||
public static Field Bool(String name){
|
||||
return new Field(name,Boolean.class);
|
||||
}
|
||||
public static Field Float(String name){
|
||||
return new Field(name,Float.class);
|
||||
}
|
||||
public static Field Num(String name){
|
||||
return new Field(name,BigDecimal.class);
|
||||
}
|
||||
public static Field Date(String name){
|
||||
return new Field(name,Date.class);
|
||||
}
|
||||
public static Field DateTime(String name){
|
||||
return new Field(name,Timestamp.class);
|
||||
}
|
||||
public static final int FLAG_PK =0x0100;
|
||||
public static final int FLAG_AUTOINC =0x0200;
|
||||
String id;
|
||||
String typeParams;
|
||||
public Field(String name) {
|
||||
super(name);
|
||||
this.raiseFlags(Field.FLAG_STORABLE);
|
||||
}
|
||||
public Field(String name,Class<?> typ) {
|
||||
super(name,typ);
|
||||
this.raiseFlags(Field.FLAG_STORABLE);
|
||||
}
|
||||
@Override
|
||||
public boolean equals(String str){
|
||||
return super.equals(str) || (id!=null && id.equalsIgnoreCase(str));
|
||||
}
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
public boolean isPk() {
|
||||
return checkFlags(FLAG_PK);
|
||||
}
|
||||
public Field setPk(boolean pk) {
|
||||
if(pk) raiseFlags(FLAG_PK); else clearFlags(FLAG_PK);
|
||||
return this;
|
||||
}
|
||||
public boolean isAutoIncrement() {
|
||||
return checkFlags(FLAG_AUTOINC);
|
||||
}
|
||||
public Field setAutoIncrement(boolean pk) {
|
||||
if(pk) raiseFlags(FLAG_AUTOINC); else clearFlags(FLAG_AUTOINC);
|
||||
return this;
|
||||
}
|
||||
public String getTypeParams() {
|
||||
return typeParams;
|
||||
}
|
||||
public Field setTypeParams(String p) {
|
||||
typeParams=p;
|
||||
return this;
|
||||
}
|
||||
public Check eq(Object... val) {
|
||||
return Check.eq(this,val);
|
||||
}
|
||||
public Check neq(Object... val) {
|
||||
return Check.neq(this,val);
|
||||
}
|
||||
public Check gt(Object... val) {
|
||||
return Check.gt(this,val);
|
||||
}
|
||||
public Check gte(Object... val) {
|
||||
return Check.gte(this,val);
|
||||
}
|
||||
public Check lt(Object... val) {
|
||||
return Check.lt(this,val);
|
||||
}
|
||||
public Check lte(Object... val) {
|
||||
return Check.lte(this,val);
|
||||
}
|
||||
public Check like(Object... val) {
|
||||
return Check.like(this,val);
|
||||
}
|
||||
public Check in(Object... val) {
|
||||
return Check.in(this,val);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/** Field iterator matching flags over entity hierarchy.
|
||||
*
|
||||
*/public class FieldSlice implements Iterator<Field>,Iterable<Field>{
|
||||
protected final Entity entity;
|
||||
protected FieldSlice sup;
|
||||
protected int includeMask;
|
||||
protected int excludeMask;
|
||||
protected int raw_index;
|
||||
protected Field next_field;
|
||||
protected int next_index;
|
||||
protected Entity next_entity;
|
||||
public FieldSlice(Entity ent){
|
||||
entity=ent;
|
||||
if(entity.getBase()!=null){
|
||||
sup=new FieldSlice(ent.getBase());
|
||||
}else{
|
||||
sup=null;
|
||||
}
|
||||
raw_index=-1;
|
||||
next_index=-1;
|
||||
}
|
||||
public FieldSlice including(int mask){
|
||||
includeMask=mask;
|
||||
if(sup!=null) sup.including(mask);
|
||||
return this;
|
||||
}
|
||||
public FieldSlice excluding(int mask){
|
||||
excludeMask=mask;
|
||||
if(sup!=null) sup.excluding(mask);
|
||||
return this;
|
||||
}
|
||||
/** we add rewind capability to allow reuse of same fieldslice.
|
||||
* i.e we use it to generate recipe, then later to properly enumerate values.
|
||||
*/
|
||||
public FieldSlice rewind(){
|
||||
raw_index=-1;
|
||||
next_index=-1;
|
||||
next_field=null;
|
||||
next_entity=null;
|
||||
if(sup!=null) sup.rewind();
|
||||
return this;
|
||||
}
|
||||
// search for next valid field
|
||||
public final Field findNext(){
|
||||
List<?> local=entity.getOwnSlots();
|
||||
if(raw_index>=local.size()) return null; // we have exhausted local supply
|
||||
next_field=null; // clear prev result
|
||||
// search at base
|
||||
if(sup!=null && sup.hasNext()){
|
||||
next_field=sup.next();
|
||||
next_index++;
|
||||
next_entity=sup.nextEntity();
|
||||
return next_field;
|
||||
}
|
||||
next_entity=entity;
|
||||
// now search locally
|
||||
for(raw_index=raw_index+1;raw_index<local.size();raw_index++){
|
||||
Field f=(Field) local.get(raw_index);
|
||||
int attr=f.getFlags();
|
||||
if((attr & excludeMask)!=0) continue; // skip if in exluding set
|
||||
if((attr & includeMask)==0) continue; // skip if not in including set
|
||||
next_field=f;
|
||||
next_index+=1;
|
||||
break;
|
||||
}
|
||||
return next_field;
|
||||
}
|
||||
@Override
|
||||
public Iterator<Field> iterator() {
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
Field next=findNext();
|
||||
return next!=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Field next() {
|
||||
return next_field;
|
||||
}
|
||||
|
||||
public int nextIndex(){
|
||||
return next_field!=null?next_index:-1;
|
||||
}
|
||||
public Entity nextEntity(){
|
||||
return next_entity;
|
||||
}
|
||||
public DBO makeRecord() throws InstantiationException, IllegalAccessException{
|
||||
return entity.newInstance();
|
||||
}
|
||||
public void writeRecord(DBO rec,Object val){
|
||||
rec.set(next_field, val);
|
||||
}
|
||||
public Object readRecord(DBO rec,Object def){
|
||||
return rec.get(next_field, def);
|
||||
}
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.reliancy.util.Handy;
|
||||
|
||||
public final class SQL implements Appendable{
|
||||
public final static Object NULL=new Object();
|
||||
public final static String WS=" ";
|
||||
public final static String SELECT="SELECT";
|
||||
public final static String INSERT="INSERT INTO";
|
||||
public final static String UPDATE="UPDATE";
|
||||
public final static String DELETE="DELETE";
|
||||
public final static String FROM="FROM";
|
||||
public final static String INNER_JOIN="INNER JOIN";
|
||||
public final static String ON="ON";
|
||||
public final static String SET="SET";
|
||||
public final static String WHERE="WHERE";
|
||||
|
||||
final StringBuffer buffer=new StringBuffer();
|
||||
final SQLTerminal terminal;
|
||||
final String ql,qr;
|
||||
final HashMap<Entity,String> entAlias=new HashMap<>();
|
||||
|
||||
public SQL(SQLTerminal terminal){
|
||||
this.terminal=terminal;
|
||||
ql=terminal!=null?terminal.getQuoteLeft():"\"";
|
||||
qr=terminal!=null?terminal.getQuoteRight():"\"";
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
return buffer.toString();
|
||||
}
|
||||
@Override
|
||||
public final SQL append(CharSequence csq){
|
||||
buffer.append(csq);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public final SQL append(CharSequence csq, int start, int end){
|
||||
buffer.append(csq,start,end);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public final SQL append(char c){
|
||||
buffer.append(c);
|
||||
return this;
|
||||
}
|
||||
public final SQL select(){
|
||||
append(SELECT);
|
||||
return this;
|
||||
}
|
||||
public final SQL insert(){
|
||||
append(INSERT);
|
||||
return this;
|
||||
}
|
||||
public final SQL update(){
|
||||
append(UPDATE);
|
||||
return this;
|
||||
}
|
||||
public final SQL delete(){
|
||||
append(DELETE);
|
||||
return this;
|
||||
}
|
||||
public final SQL from(){
|
||||
append(WS).append(FROM).append(WS);
|
||||
return this;
|
||||
}
|
||||
public final SQL inner_join(){
|
||||
append(WS).append(INNER_JOIN).append(WS);
|
||||
return this;
|
||||
}
|
||||
public final SQL on(){
|
||||
append(WS).append(ON).append(WS);
|
||||
return this;
|
||||
}
|
||||
public final String wrap(String id){
|
||||
if(id.startsWith(ql) && id.endsWith(qr)){
|
||||
return id;
|
||||
}else{
|
||||
return ql+id.replace(".",qr+"."+ql)+qr;
|
||||
}
|
||||
}
|
||||
public final SQL id(String id){
|
||||
if(id.startsWith(ql) && id.endsWith(qr)){
|
||||
append(id);
|
||||
}else{
|
||||
append(ql).append(id.replace(".",qr+"."+ql)).append(qr);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public final String getAlias(Entity e){
|
||||
String eAlias=entAlias.get(e);
|
||||
if(eAlias!=null) return eAlias;
|
||||
eAlias="e"+entAlias.size();
|
||||
entAlias.put(e,eAlias);
|
||||
return eAlias;
|
||||
}
|
||||
public final SQL select(Entity ent,FieldSlice fit){
|
||||
entAlias.clear();;
|
||||
select();
|
||||
while(fit.hasNext()){
|
||||
int index=fit.nextIndex();
|
||||
Field f=fit.next();
|
||||
Entity e=fit.nextEntity();
|
||||
String alias=getAlias(e);
|
||||
//System.out.println("It:"+index+":/"+f+"/"+e+"/"+alias);
|
||||
append(index==0?" ":",");
|
||||
// Use getId() if set (database column name), otherwise use getName()
|
||||
String colName=(f.getId()!=null && !f.getId().isEmpty())?f.getId():f.getName();
|
||||
append(alias).append(".").id(colName);
|
||||
}
|
||||
from();
|
||||
String eAlias=getAlias(ent);
|
||||
id(ent.getName()).append(" ").append(eAlias);
|
||||
for(Entity b=ent.getBase();b!=null;b=b.getBase()){
|
||||
String bAlias=getAlias(b);
|
||||
inner_join();
|
||||
id(b.getName()).append(" ").append(bAlias);
|
||||
on();
|
||||
Field bPk=b.getPk();
|
||||
Field ePk=ent.getPk();
|
||||
// Use getId() if set (database column name), otherwise use getName()
|
||||
String ePkName=(ePk.getId()!=null && !ePk.getId().isEmpty())?ePk.getId():ePk.getName();
|
||||
String bPkName=(bPk.getId()!=null && !bPk.getId().isEmpty())?bPk.getId():bPk.getName();
|
||||
append(eAlias).append(".").id(ePkName);
|
||||
append("=");
|
||||
append(bAlias).append(".").id(bPkName);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public final SQL where(){
|
||||
append(WS).append(WHERE).append(WS);
|
||||
return this;
|
||||
}
|
||||
public final SQL where(Check filter) {
|
||||
append(WS).append(WHERE).append(WS).check(filter);
|
||||
return this;
|
||||
}
|
||||
/// using entalias locate field entity and its prefix
|
||||
public final String getFieldPrefix(Field f){
|
||||
for(Map.Entry<Entity,String> e:entAlias.entrySet()){
|
||||
Entity ent=e.getKey();
|
||||
String prefix=e.getValue();
|
||||
if(ent.isOwned(f)) return prefix+".";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
public final SQL check(Check filter) {
|
||||
if(filter.isLeaf()){
|
||||
Check.Op op=filter.getCode();
|
||||
Field field=filter.getField();
|
||||
// Use getId() if set (database column name), otherwise use getName()
|
||||
String fieldName=(field.getId()!=null && !field.getId().isEmpty())?field.getId():field.getName();
|
||||
String fname=wrap(fieldName);
|
||||
String opname=op.toString();
|
||||
String arg="?";
|
||||
Object val=filter.getValue();
|
||||
if(op==Check.LIKE && terminal!=null && terminal.getProtocol().contains(":postgre")){
|
||||
opname="ILIKE";
|
||||
}
|
||||
if(op==Check.IN){
|
||||
arg="("+Handy.toString(val)+")";
|
||||
}
|
||||
if(Handy.isEmpty(val)){
|
||||
// if not value then shortcuircuid condition
|
||||
fname="1";
|
||||
opname="=";
|
||||
arg="1";
|
||||
}
|
||||
if(val==NULL){
|
||||
arg="NULL";
|
||||
if(op==Check.EQ) opname="IS";
|
||||
if(op==Check.NEQ) opname="IS NOT";
|
||||
}
|
||||
append("(");
|
||||
String fprefix=getFieldPrefix(field);
|
||||
append(fprefix).append(fname).append(WS).append(opname).append(WS).append(arg);
|
||||
append(")");
|
||||
}else{
|
||||
Check.Op op=filter.getCode();
|
||||
String delim=op.toString();
|
||||
if(op==Check.NOT){
|
||||
append(delim).append("(").check(filter.getChild(0)).append(")");
|
||||
}else{
|
||||
append("(");
|
||||
for(int i=0;i<filter.getChildCount();i++){
|
||||
if(i>0) append(WS).append(delim).append(WS);
|
||||
check(filter.getChild(i));
|
||||
}
|
||||
append(")");
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/** fills params list with non-trivial parameters.
|
||||
* we place this method here to be as close as possible to the one which generates the sql code.
|
||||
* check and check_export must be in synch.
|
||||
* @param filter check operation to perform over conditions.
|
||||
* @param params extracted params.
|
||||
*/
|
||||
public final void check_export(Check filter,List<Object> params) {
|
||||
if(filter.isLeaf()){
|
||||
Check.Op op=filter.getCode();
|
||||
Object val=filter.getValue();
|
||||
if(Handy.isEmpty(val) || val==NULL || op==Check.IN) return; // skip over empty or NULL values
|
||||
params.add(val);
|
||||
}else{
|
||||
for(Check ch:filter) check_export(ch,params);
|
||||
}
|
||||
}
|
||||
/** fills check values from dbo record where equal and not-equal are used.
|
||||
* we place this method here to be as close as possible to the one which generates the sql code.
|
||||
* check and check_import must be in synch.
|
||||
* @param filter set of checks
|
||||
* @param rec record to check
|
||||
*/
|
||||
public final void check_import(Check filter,DBO rec) {
|
||||
if(filter.isLeaf()){
|
||||
if(filter.isLocked()) return; // no import on locked condition
|
||||
Check.Op op=filter.getCode();
|
||||
if(op!=Check.EQ && op!=Check.NEQ) return; // skip over all conditions except = and <>
|
||||
Field f=filter.getField();
|
||||
Object val=f.get(rec,null);
|
||||
filter.setValue(val);
|
||||
}else{
|
||||
for(Check ch:filter) check_import(ch,rec);
|
||||
}
|
||||
}
|
||||
public final SQL insert(Entity entity,List<Field> supplied){
|
||||
insert();
|
||||
append(SQL.WS).id(entity.getName()).append(" (");
|
||||
StringBuffer ext=new StringBuffer();
|
||||
String delim="";
|
||||
Field pk=entity.getPk();
|
||||
if(!entity.isOwned(pk)){
|
||||
String pkName=(pk.getId()!=null && !pk.getId().isEmpty())?pk.getId():pk.getName();
|
||||
append(delim).id(pkName);
|
||||
ext.append(delim).append("?");
|
||||
delim=",";
|
||||
}
|
||||
for(int index=0;index<supplied.size();index++){
|
||||
Field f=supplied.get(index);
|
||||
if(index>0) delim=",";
|
||||
String fName=(f.getId()!=null && !f.getId().isEmpty())?f.getId():f.getName();
|
||||
append(delim).id(fName);
|
||||
ext.append(delim).append("?");
|
||||
}
|
||||
append(") VALUES (").append(ext).append(")");
|
||||
return this;
|
||||
}
|
||||
public final SQL update(Entity entity,List<Field> supplied){
|
||||
update();
|
||||
append(SQL.WS).id(entity.getName()).append(" SET ");
|
||||
for(int index=0;index<supplied.size();index++){
|
||||
Field f=supplied.get(index);
|
||||
String delim=index==0?"":",";
|
||||
append(delim);
|
||||
String fName=(f.getId()!=null && !f.getId().isEmpty())?f.getId():f.getName();
|
||||
id(fName).append("=?");
|
||||
}
|
||||
where();
|
||||
Field pk=entity.getPk();
|
||||
String pkName=(pk.getId()!=null && !pk.getId().isEmpty())?pk.getId():pk.getName();
|
||||
id(pkName).append("=?");
|
||||
return this;
|
||||
}
|
||||
public final SQL delete(Entity entity){
|
||||
delete().from().id(entity.getName());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
/** Helper object which impleents DBO deleting.
|
||||
* It manages the recipe and the prepared statmenet.
|
||||
* The cleaner works in two ways.
|
||||
* If you supply items iterator it will delete by pk id those items.
|
||||
* If you supply a Check filter and no items then it will delete based on a where statement.
|
||||
*/
|
||||
public class SQLCleaner implements Closeable{
|
||||
protected final Entity entity;
|
||||
protected final SQLTerminal terminal;
|
||||
protected final SQLCleaner base; /// used for nesting
|
||||
protected final SQL sql;
|
||||
protected final ArrayList<Object> params;
|
||||
protected Check filter;
|
||||
protected Connection external;
|
||||
protected PreparedStatement deleteStmt;
|
||||
protected int itemsDeleted;
|
||||
protected Exception error;
|
||||
|
||||
public SQLCleaner(Entity ent,SQLTerminal t) {
|
||||
entity=ent;
|
||||
terminal=t;
|
||||
base=(entity.getBase()!=null)?new SQLCleaner(entity.getBase(),t):null;
|
||||
sql=new SQL(terminal);
|
||||
params=new ArrayList<>();
|
||||
}
|
||||
public SQL compileRecipe(){
|
||||
if(filter==null){
|
||||
// no filter we go with PK
|
||||
Field pk=entity.getPk();
|
||||
filter=pk.eq("?");
|
||||
}
|
||||
sql.delete(entity).where(filter);
|
||||
return sql;
|
||||
}
|
||||
public boolean isLinkExternal(){
|
||||
return external!=null;
|
||||
}
|
||||
public SQLCleaner setExternalLink(Connection link){
|
||||
external=link;
|
||||
return this;
|
||||
}
|
||||
protected Connection getExternalLink(){
|
||||
return external;
|
||||
}
|
||||
protected Connection getInternalLink(){
|
||||
try{
|
||||
if(deleteStmt!=null) return deleteStmt.getConnection();
|
||||
}catch(SQLException ex){
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public SQLCleaner open() throws SQLException{
|
||||
return open(null);
|
||||
}
|
||||
public SQLCleaner open(Check where) throws SQLException{
|
||||
this.filter=where;
|
||||
Connection link=isLinkExternal()?getExternalLink():terminal.getConnection();
|
||||
if(base!=null) base.setExternalLink(link).open(filter); // definitely external link for base
|
||||
SQL delSQL=compileRecipe();
|
||||
//System.out.println("DEL:"+delSQL+"/"+filter);
|
||||
deleteStmt=link.prepareStatement(delSQL.toString());
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public void close() throws IOException{
|
||||
if(base!=null) base.close(); // since link is external it will not close link just the rest
|
||||
Connection link=getInternalLink();
|
||||
if(deleteStmt!=null){
|
||||
try{
|
||||
deleteStmt.close();
|
||||
}catch(SQLException ex){
|
||||
if(error==null) error=ex;
|
||||
}
|
||||
}
|
||||
try{
|
||||
if(link!=null && !isLinkExternal()) link.close();
|
||||
external=null;
|
||||
}catch(SQLException ex){
|
||||
if(error==null) error=ex;
|
||||
}
|
||||
if(error!=null){
|
||||
if(error instanceof IOException) throw (IOException)error;
|
||||
else throw new IOException(error);
|
||||
}
|
||||
|
||||
}
|
||||
public void flush(Iterator<DBO> items) throws SQLException {
|
||||
Connection link=getInternalLink();
|
||||
boolean autocommited=link.getAutoCommit();
|
||||
try{
|
||||
link.setAutoCommit(false);
|
||||
if(items==null){ // deleting by filter
|
||||
throw new SQLException("delete by filter not implemented yet");
|
||||
// we would need to leave the primary filter
|
||||
// we would use filter in an ID in (SUBQUERY)
|
||||
// this is because filter could reference all entities and we have inheritance so multiple
|
||||
// we would generate a select statement with filter and selecting only ID
|
||||
}else{ // deleting by incoming records
|
||||
while(items.hasNext()){
|
||||
DBO rec=items.next();
|
||||
deleteRecord(rec);
|
||||
}
|
||||
}
|
||||
if(!link.getAutoCommit()){
|
||||
link.commit();
|
||||
}
|
||||
}catch(SQLException ex){
|
||||
if(!link.getAutoCommit()){
|
||||
link.rollback();
|
||||
}
|
||||
throw ex;
|
||||
}finally{
|
||||
link.setAutoCommit(autocommited);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This calls one delete. It can and is called from outside in case of nesting when link is external.
|
||||
* @param rec database object to delete
|
||||
* @throws SQLException sql related error
|
||||
*/
|
||||
public boolean deleteRecord(DBO rec) throws SQLException{
|
||||
if(rec==null) return false;
|
||||
if(base!=null) base.deleteRecord(rec); // save the superclass first
|
||||
sql.check_import(filter,rec); // get values from dbo
|
||||
params.clear();
|
||||
sql.check_export(filter,params); // move them into params
|
||||
for(int pindex=0;pindex<params.size();pindex++){
|
||||
Object val=params.get(pindex);
|
||||
deleteStmt.setObject(pindex+1,val);
|
||||
}
|
||||
int dcode=deleteStmt.executeUpdate();
|
||||
itemsDeleted+=dcode;
|
||||
return dcode>0;
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import com.reliancy.dbo.Action.Load;
|
||||
|
||||
|
||||
/** SQLIterator will delay closing a connection and will iterate over result set.
|
||||
* TODO: no support for orderby yet
|
||||
*/
|
||||
public class SQLReader implements SiphonIterator<DBO>{
|
||||
protected final Entity entity;
|
||||
protected final SQLTerminal terminal;
|
||||
protected final SQL sql;
|
||||
protected FieldSlice slice;
|
||||
protected ResultSet result;
|
||||
protected Exception error;
|
||||
|
||||
public SQLReader(Entity ent,SQLTerminal t) {
|
||||
this.entity=ent;
|
||||
terminal=t;
|
||||
// slice controls sql fields but also lets us correctly import values later
|
||||
slice=new FieldSlice(entity).including(Field.FLAG_STORABLE);
|
||||
sql=new SQL(terminal);
|
||||
}
|
||||
public SQLReader open() throws SQLException{
|
||||
return open(null);
|
||||
}
|
||||
public SQLReader open(Action action) throws SQLException{
|
||||
error=null;
|
||||
if(action==null){
|
||||
sql.select(entity,slice); // simple case
|
||||
}else{
|
||||
compileRecipe(action); // complete case
|
||||
}
|
||||
//System.out.println("SQL:"+sql);
|
||||
Connection link=terminal.getConnection();
|
||||
PreparedStatement prep=link.prepareStatement(sql.toString());
|
||||
if(action!=null){
|
||||
Load tr=(Load) action.getTrait();
|
||||
if(tr.filter!=null){
|
||||
ArrayList<Object> params=new ArrayList<>();
|
||||
sql.check_export(tr.filter, params);
|
||||
for(int pindex=0;pindex<params.size();pindex++){
|
||||
Object val=params.get(pindex);
|
||||
prep.setObject(pindex+1,val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result=prep.executeQuery();
|
||||
if(link.getAutoCommit()==false) link.commit();
|
||||
//action.setItems(this); -- maybe we want multiple readers on same actions - leave this to terminal
|
||||
return this;
|
||||
}
|
||||
public SQL compileRecipe(Action action){
|
||||
sql.select(action.getEntity(),slice);
|
||||
Load tr=(Load) action.getTrait();
|
||||
if(tr.filter!=null){
|
||||
sql.where(tr.filter);
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
try {
|
||||
return error==null?result.next():false;
|
||||
} catch (SQLException e) {
|
||||
error=e;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBO next() {
|
||||
try {
|
||||
DBO ret=(DBO) slice.makeRecord();
|
||||
FieldSlice fit=slice.rewind();
|
||||
while(fit.hasNext()){
|
||||
int findex=fit.nextIndex();
|
||||
//Field field=fit.next();
|
||||
Object val=result.getObject(findex+1);
|
||||
fit.writeRecord(ret, val);
|
||||
}
|
||||
ret.setStatus(DBO.Status.USED);
|
||||
return ret;
|
||||
} catch (Exception e) {
|
||||
error=e;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if(result!=null){
|
||||
Statement stmt=null;
|
||||
Connection link=null;
|
||||
try{
|
||||
stmt=result.getStatement();
|
||||
link=stmt!=null?stmt.getConnection():null;
|
||||
if(!result.isClosed()) result.close();
|
||||
}catch(SQLException ex){
|
||||
if(error==null) error=ex;
|
||||
}
|
||||
try{
|
||||
if(stmt!=null) stmt.close();
|
||||
}catch(SQLException ex){
|
||||
if(error==null) error=ex;
|
||||
}
|
||||
try{
|
||||
if(link!=null) link.close();
|
||||
}catch(SQLException ex){
|
||||
if(error==null) error=ex;
|
||||
}
|
||||
}
|
||||
if(error!=null){
|
||||
if(error instanceof IOException) throw (IOException)error;
|
||||
else throw new IOException(error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.sql.Blob;
|
||||
import java.sql.Clob;
|
||||
import java.sql.Connection;
|
||||
import java.sql.JDBCType;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.reliancy.util.Path;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
/** SQL particular implementation of a terminal.
|
||||
* It will use a connection pool under it to take care of connection re-use.
|
||||
*
|
||||
*/
|
||||
public class SQLTerminal implements Terminal{
|
||||
HikariConfig config = new HikariConfig();
|
||||
HikariDataSource ds;
|
||||
Path url;
|
||||
String quoteLeft="\""; // quotes could be subject to sql flavour
|
||||
String quoteRight="\"";
|
||||
|
||||
public SQLTerminal(String url){
|
||||
this.url=new Path(url);
|
||||
String proto=this.url.getProtocol();
|
||||
if(!proto.startsWith("jdbc:")) proto="jdbc:"+proto;
|
||||
String u=proto+"://"+this.url.getHost()+":"+this.url.getPort()+"/"+this.url.getDatabase();
|
||||
config.setJdbcUrl(u);
|
||||
config.setUsername(this.url.getUserid());
|
||||
config.setPassword(this.url.getPassword());
|
||||
//config.setAutoCommit(false); -- do this in batch cases only
|
||||
config.addDataSourceProperty( "cachePrepStmts" , "true" );
|
||||
config.addDataSourceProperty( "prepStmtCacheSize" , "250" );
|
||||
config.addDataSourceProperty( "prepStmtCacheSqlLimit" , "2048" );
|
||||
ds = new HikariDataSource( config );
|
||||
}
|
||||
public Connection getConnection() throws SQLException{
|
||||
return ds.getConnection();
|
||||
}
|
||||
@Override
|
||||
public Action execute(Action q) throws IOException{
|
||||
// System.out.println("Executing..."+q.getTrait());
|
||||
Action.Trait tr=q.getTrait();
|
||||
if(tr instanceof Action.Load){
|
||||
Entity ent=q.getEntity();
|
||||
SQLReader reader=new SQLReader(ent,this);
|
||||
try {
|
||||
reader.open(q);
|
||||
q.setItems(reader);
|
||||
} catch (SQLException e) {
|
||||
reader.close();
|
||||
throw new IOException(e);
|
||||
}
|
||||
//System.out.println("Executing...Done");
|
||||
return q;
|
||||
}else if(tr instanceof Action.Save){
|
||||
Entity ent=q.getEntity();
|
||||
try(SQLWriter writer=new SQLWriter(ent,this)) {
|
||||
writer.open();
|
||||
writer.flush(q.getItems());
|
||||
//System.out.println("Executing...Done");
|
||||
return q;
|
||||
}catch(SQLException e){
|
||||
throw new IOException(e);
|
||||
}
|
||||
}else if(tr instanceof Action.Delete){
|
||||
Entity ent=q.getEntity();
|
||||
try(SQLCleaner cleaner=new SQLCleaner(ent,this)) {
|
||||
cleaner.open();
|
||||
cleaner.flush(q.getItems());
|
||||
//System.out.println("Executing...Done");
|
||||
return q;
|
||||
}catch(SQLException e){
|
||||
throw new IOException(e);
|
||||
}
|
||||
}else{
|
||||
throw new UnsupportedOperationException("Trait not supported:"+tr);
|
||||
}
|
||||
}
|
||||
public String getProtocol() {
|
||||
return url.getProtocol();
|
||||
}
|
||||
public String getQuoteLeft(){
|
||||
return this.quoteLeft;
|
||||
}
|
||||
public String getQuoteRight(){
|
||||
return this.quoteRight;
|
||||
}
|
||||
final HashMap<Integer,Class<?>> sql2java=new HashMap<>();
|
||||
final HashMap<Class<?>,Integer> java2sql=new HashMap<>();
|
||||
public Map<Class<?>,Integer> getJava2SQL(){
|
||||
if(!java2sql.isEmpty()) return java2sql;
|
||||
String protocol=url.getProtocol();
|
||||
java2sql.put(java.math.BigDecimal.class,Types.DECIMAL);
|
||||
java2sql.put(java.math.BigInteger.class,Types.DECIMAL);
|
||||
java2sql.put(Boolean.class,protocol.contains(":oracle")?Types.INTEGER:Types.BOOLEAN);
|
||||
java2sql.put(Byte.class,Types.TINYINT);
|
||||
java2sql.put(Short.class,Types.SMALLINT);
|
||||
java2sql.put(Integer.class,Types.INTEGER);
|
||||
java2sql.put(Long.class,Types.BIGINT);
|
||||
java2sql.put(Float.class,Types.FLOAT);
|
||||
java2sql.put(Double.class,Types.DOUBLE);
|
||||
java2sql.put(byte[].class,Types.VARBINARY);
|
||||
java2sql.put(Blob.class,Types.BLOB);
|
||||
java2sql.put(char[].class,Types.VARCHAR);
|
||||
java2sql.put(String.class,Types.VARCHAR);
|
||||
java2sql.put(StringBuffer.class,Types.VARCHAR);
|
||||
java2sql.put(Clob.class,Types.CLOB);
|
||||
java2sql.put(java.sql.Date.class,Types.DATE);
|
||||
java2sql.put(java.sql.Time.class,Types.TIME);
|
||||
java2sql.put(java.sql.Timestamp.class,Types.TIMESTAMP);
|
||||
java2sql.put(Array.class,Types.ARRAY);
|
||||
return java2sql;
|
||||
}
|
||||
public Map<Integer,Class<?>> getSQL2Java(){
|
||||
if(!sql2java.isEmpty()) return sql2java;
|
||||
//String protocol=url.getProtocol();
|
||||
sql2java.put(Types.NUMERIC,java.math.BigDecimal.class);
|
||||
sql2java.put(Types.DECIMAL,java.math.BigDecimal.class);
|
||||
sql2java.put(Types.BIT,Boolean.class);
|
||||
sql2java.put(Types.BOOLEAN,Boolean.class);
|
||||
sql2java.put(Types.TINYINT,Byte.class);
|
||||
sql2java.put(Types.SMALLINT,Short.class);
|
||||
sql2java.put(Types.INTEGER,Integer.class);
|
||||
sql2java.put(Types.BIGINT,Long.class);
|
||||
sql2java.put(Types.REAL,Float.class);
|
||||
sql2java.put(Types.FLOAT,Float.class);
|
||||
sql2java.put(Types.DOUBLE,Double.class);
|
||||
sql2java.put(Types.BINARY,byte[].class);
|
||||
sql2java.put(Types.VARBINARY,byte[].class);
|
||||
sql2java.put(Types.LONGVARBINARY,byte[].class);
|
||||
sql2java.put(Types.CHAR,String.class);
|
||||
sql2java.put(Types.NCHAR,String.class);
|
||||
sql2java.put(Types.VARCHAR,String.class);
|
||||
sql2java.put(Types.NVARCHAR,String.class);
|
||||
sql2java.put(Types.LONGVARCHAR,String.class);
|
||||
sql2java.put(Types.LONGNVARCHAR,String.class);
|
||||
sql2java.put(Types.DATE,java.sql.Date.class);
|
||||
sql2java.put(Types.TIME,java.sql.Time.class);
|
||||
sql2java.put(Types.TIMESTAMP,java.sql.Timestamp.class);
|
||||
sql2java.put(Types.BLOB,byte[].class);
|
||||
sql2java.put(Types.CLOB,char[].class);
|
||||
sql2java.put(Types.ARRAY,java.sql.Array.class);
|
||||
sql2java.put(Types.JAVA_OBJECT,Object.class);
|
||||
return sql2java;
|
||||
}
|
||||
/**
|
||||
* Returns back java class for given id and or name.
|
||||
* The name is not used in default implementation.
|
||||
* @param typeid sql type to map
|
||||
* @return Class matching sql typeid.
|
||||
*/
|
||||
public Class<?> getJavaType(int typeid) {
|
||||
Class<?> ret=getSQL2Java().get(typeid);
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* This method will correct cases when sqltype is varchar (12) but type name is date or similar.
|
||||
* @param sqltype
|
||||
* @param type_name
|
||||
* @return tries to promote sqltype given type name to something more specific.
|
||||
*/
|
||||
public int getTypeId(int sqltype,String type_name){
|
||||
if(type_name==null) return sqltype;
|
||||
type_name=type_name.toLowerCase();
|
||||
if(sqltype==Types.VARCHAR || sqltype==Types.CHAR){
|
||||
if(type_name.equals("date")) sqltype=Types.DATE;
|
||||
if(type_name.equals("time")) sqltype=Types.TIME;
|
||||
if(type_name.equals("datetime")) sqltype=Types.TIMESTAMP;
|
||||
}
|
||||
return sqltype;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cls
|
||||
* @param createParams
|
||||
* @return SQL type given java class and create params
|
||||
*/
|
||||
public int getTypeId(Class<?> cls,String createParams){
|
||||
int ret=getJava2SQL().get(cls);
|
||||
return ret;
|
||||
}
|
||||
public String getTypeName(Class<?> cls,String createParams){
|
||||
int id=this.getTypeId(cls, createParams);
|
||||
String ret = JDBCType.valueOf(id).getName();
|
||||
if(ret==null) return null;
|
||||
String protocol=url.getProtocol();
|
||||
if(protocol.contains(":sqlserver")){
|
||||
if("boolean".equalsIgnoreCase(ret)) ret="BIT";
|
||||
if("timestamp".equalsIgnoreCase(ret)) ret="DATETIME";
|
||||
if("double".equalsIgnoreCase(ret)) ret="float";
|
||||
if("float".equalsIgnoreCase(ret)) ret="real";
|
||||
}
|
||||
if(protocol.contains(":postgre")){
|
||||
if("varbinary".equalsIgnoreCase(ret)) ret="bytea";
|
||||
if("double".equalsIgnoreCase(ret)) ret="double precision";
|
||||
}
|
||||
if("varchar".equalsIgnoreCase(ret) && (createParams!=null && !createParams.isEmpty())){
|
||||
long size=Long.parseLong(createParams);
|
||||
if(protocol.contains(":sqlserver")) ret=size>8000?ret.concat("(").concat("MAX").concat(")"):ret.concat("(").concat(String.valueOf(size)).concat(")");
|
||||
else if(protocol.contains(":oracle")) ret=size>2000?"CLOB":ret.concat("(").concat(String.valueOf(size)).concat(")");
|
||||
else if(protocol.contains(":mysql")) ret=size>Character.MAX_VALUE?"TEXT":ret.concat("(").concat(String.valueOf(size)).concat(")");
|
||||
else if(protocol.contains(":h2")) ret=size>Integer.MAX_VALUE?"CLOB":ret.concat("(").concat(String.valueOf(size)).concat(")");
|
||||
else if(protocol.contains(":postgre")) ret=size>Character.MAX_VALUE?"TEXT":ret.concat("(").concat(String.valueOf(size)).concat(")");
|
||||
else ret=(size>Character.MAX_VALUE)?"CLOB":ret.concat("(").concat(String.valueOf(size)).concat(")");
|
||||
}
|
||||
String args=null;
|
||||
if(ret.indexOf('(')==-1 && createParams!=null && !createParams.isEmpty()){
|
||||
if("decimal".equalsIgnoreCase(ret)) args=createParams;
|
||||
if("numeric".equalsIgnoreCase(ret)) args=createParams;
|
||||
}
|
||||
if(args!=null){
|
||||
ret=ret.concat("(").concat(args).concat(")");
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import com.reliancy.util.Handy;
|
||||
|
||||
/** Helper object which impleents DBO saving. It manages the recipe and the prepared statmenet.
|
||||
* Also keeps track of which fields are sent down to DB.
|
||||
* The writer does the work in flush method during which it exhausts the items. While in flush it
|
||||
* will disable autocommit and enable it at the end. We could call flush multiple times to send multiple
|
||||
* batches down to DB.
|
||||
* Initially we would ctor with Action object but actually we need to generate writes for different entities
|
||||
* especially in inheritance cases.
|
||||
*/
|
||||
public class SQLWriter implements Closeable{
|
||||
protected final Entity entity;
|
||||
protected final SQLTerminal terminal;
|
||||
protected final SQLWriter base; /// used for nesting
|
||||
protected final ArrayList<Field> supplied=new ArrayList<Field>();
|
||||
protected final ArrayList<Field> generated=new ArrayList<Field>();
|
||||
protected String insertSQL;
|
||||
protected String updateSQL;
|
||||
protected Connection external;
|
||||
protected PreparedStatement insertStmt;
|
||||
protected PreparedStatement updateStmt;
|
||||
protected int itemsInserted;
|
||||
protected int itemsUpdated;
|
||||
protected Exception error;
|
||||
|
||||
public SQLWriter(Entity ent,SQLTerminal t) {
|
||||
entity=ent;
|
||||
terminal=t;
|
||||
base=(entity.getBase()!=null)?new SQLWriter(entity.getBase(),t):null;
|
||||
// we select proper fields for this entity
|
||||
FieldSlice slice=new FieldSlice(entity).including(Field.FLAG_STORABLE); // includes all even autoincrement
|
||||
while(slice.hasNext()){
|
||||
Field f=slice.next();
|
||||
Entity e=slice.nextEntity();
|
||||
if(e!=entity) continue; // skip if not part of this entity
|
||||
if(f.isAutoIncrement()){
|
||||
generated.add(f);
|
||||
}else{
|
||||
supplied.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
public String compileInsertRecipe(){
|
||||
if(insertSQL!=null) return insertSQL;
|
||||
SQL buf=new SQL(terminal);
|
||||
buf.insert(entity,supplied);
|
||||
insertSQL=buf.toString();
|
||||
return insertSQL;
|
||||
}
|
||||
public String compileUpdateRecipe(){
|
||||
if(updateSQL!=null) return updateSQL;
|
||||
SQL buf=new SQL(terminal);
|
||||
buf.update(entity,supplied);
|
||||
updateSQL=buf.toString();
|
||||
return updateSQL;
|
||||
}
|
||||
public boolean isLinkExternal(){
|
||||
return external!=null;
|
||||
}
|
||||
public SQLWriter setExternalLink(Connection link){
|
||||
external=link;
|
||||
return this;
|
||||
}
|
||||
protected Connection getExternalLink(){
|
||||
return external;
|
||||
}
|
||||
protected Connection getInternalLink(){
|
||||
try{
|
||||
if(insertStmt!=null) return insertStmt.getConnection();
|
||||
if(updateStmt!=null) return updateStmt.getConnection();
|
||||
}catch(SQLException ex){
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public SQLWriter open() throws SQLException{
|
||||
Connection link=isLinkExternal()?getExternalLink():terminal.getConnection();
|
||||
if(base!=null) base.setExternalLink(link).open(); // definitely external link for base
|
||||
String inSql=compileInsertRecipe();
|
||||
String upSql=compileUpdateRecipe();
|
||||
//System.out.println("INS:"+inSql);
|
||||
//System.out.println("UPD:"+upSql);
|
||||
String[] genkeys=new String[generated.size()];
|
||||
for(int i=0;i<generated.size();i++){
|
||||
Field f=generated.get(i);
|
||||
genkeys[i]=f.getName();
|
||||
}
|
||||
insertStmt=link.prepareStatement(inSql,genkeys);
|
||||
updateStmt=link.prepareStatement(upSql);
|
||||
//result=prep.executeQuery();
|
||||
//if(link.getAutoCommit()==false) link.commit();
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public void close() throws IOException{
|
||||
if(base!=null) base.close(); // since link is external it will not close link just the rest
|
||||
Connection link=getInternalLink();
|
||||
if(insertStmt!=null){
|
||||
try{
|
||||
insertStmt.close();
|
||||
}catch(SQLException ex){
|
||||
if(error==null) error=ex;
|
||||
}
|
||||
}
|
||||
if(updateStmt!=null){
|
||||
try{
|
||||
updateStmt.close();
|
||||
}catch(SQLException ex){
|
||||
if(error==null) error=ex;
|
||||
}
|
||||
}
|
||||
try{
|
||||
if(link!=null && external!=link) link.close();
|
||||
external=null;
|
||||
}catch(SQLException ex){
|
||||
if(error==null) error=ex;
|
||||
}
|
||||
if(error!=null){
|
||||
if(error instanceof IOException) throw (IOException)error;
|
||||
else throw new IOException(error);
|
||||
}
|
||||
|
||||
}
|
||||
public void flush(Iterator<DBO> items) throws SQLException {
|
||||
Connection link=isLinkExternal()?getExternalLink():getInternalLink();
|
||||
boolean autocommited=link.getAutoCommit();
|
||||
try{
|
||||
link.setAutoCommit(false);
|
||||
while(items.hasNext()){
|
||||
DBO rec=items.next();
|
||||
writeRecord(rec);
|
||||
}
|
||||
if(!link.getAutoCommit()){
|
||||
link.commit();
|
||||
}
|
||||
}catch(SQLException ex){
|
||||
if(!link.getAutoCommit()){
|
||||
link.rollback();
|
||||
}
|
||||
throw ex;
|
||||
}finally{
|
||||
link.setAutoCommit(autocommited);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This calls one update/insert. It can and is called from outside in case of nesting when link is external.
|
||||
* @param rec
|
||||
* @throws SQLException
|
||||
*/
|
||||
public boolean writeRecord(DBO rec) throws SQLException{
|
||||
if(base!=null) base.writeRecord(rec); // save the superclass first
|
||||
// select mode
|
||||
int pindex=0;
|
||||
Field pk=entity.getPk();
|
||||
boolean pk_owned=entity.isOwned(pk);
|
||||
PreparedStatement stmt=null;
|
||||
if(rec.getStatus()==DBO.Status.NEW){
|
||||
stmt=insertStmt;
|
||||
// need to inject pk here is not owned
|
||||
if(!pk_owned) stmt.setObject(++pindex,pk.get(rec,null),terminal.getTypeId(pk.getType(),pk.getTypeParams()));
|
||||
}
|
||||
if(rec.getStatus()==DBO.Status.USED){
|
||||
stmt=updateStmt;
|
||||
// update has a pk condition for sure
|
||||
Object pkval=pk.get(rec,null);
|
||||
if(Handy.isEmpty(pkval)) throw new SQLException("Used object with empty PK");
|
||||
//System.out.println("UPDT:"+stmt+"/"+pkval);
|
||||
stmt.setObject(
|
||||
supplied.size()+1,
|
||||
pkval,
|
||||
terminal.getTypeId(pk.getType(),pk.getTypeParams())
|
||||
);
|
||||
}
|
||||
if(stmt==null) return false;
|
||||
// copy values
|
||||
for(int index=0;index<supplied.size();index++){
|
||||
Field f=supplied.get(index);
|
||||
pindex+=1;
|
||||
int tid=terminal.getTypeId(f.getType(),f.getTypeParams());
|
||||
Object val=f.get(rec,null);
|
||||
//System.out.println("Param:"+pindex+":"+f.getName()+":"+val);
|
||||
stmt.setObject(pindex,val,tid);
|
||||
}
|
||||
int ucode=stmt.executeUpdate();
|
||||
//System.out.println("UCode:"+ucode);
|
||||
if(rec.getStatus()==DBO.Status.NEW){
|
||||
this.itemsInserted+=ucode;
|
||||
if(ucode>0 && !generated.isEmpty()){
|
||||
try (ResultSet keys = stmt.getGeneratedKeys()) {
|
||||
if(keys.next()){
|
||||
for(int i=0;i<generated.size();i++){
|
||||
Field f=generated.get(i);
|
||||
Object autoval=keys.getObject(i+1);
|
||||
f.set(rec,autoval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}else
|
||||
if(rec.getStatus()==DBO.Status.USED){
|
||||
this.itemsUpdated+=ucode;
|
||||
}
|
||||
return ucode>0;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.Iterator;
|
||||
/** Iterator interface suitable for dbo resultsets.
|
||||
*
|
||||
*/
|
||||
public interface SiphonIterator<T> extends Iterator<T>, Closeable {
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/** Endpoint for dbo objects.
|
||||
* this interface will implemet CRUD plus control over a databse or folder.
|
||||
* control will be implemented via meta terminal which will return specialized terminals for each entity and running actions on it
|
||||
* will modify the entity structure.
|
||||
*
|
||||
* the core of the temrminal will be the Action object. The others will just be wrappers for item actions.
|
||||
* the action will be a read or write query with session management.
|
||||
*/
|
||||
public interface Terminal {
|
||||
public Action execute(Action q) throws IOException;
|
||||
|
||||
public default Action begin(){
|
||||
return begin(null);
|
||||
}
|
||||
public default Action begin(String sig){
|
||||
return new Action(this);
|
||||
}
|
||||
public default void end(Action act){
|
||||
act.clear();
|
||||
}
|
||||
public default Terminal meta(Entity ent){
|
||||
return null;
|
||||
}
|
||||
public default <T extends DBO> T load(Class<T> cls,Object...id) throws IOException {
|
||||
Entity ent=Entity.recall(cls);
|
||||
String sig="/"+ent.getName()+"/load";
|
||||
try(Action act=begin(sig).load(ent).limit(1).if_pk(id).execute()){
|
||||
return cls.cast(act.first());
|
||||
}
|
||||
}
|
||||
public default DBO load(Entity ent,Object...id) throws IOException {
|
||||
String sig="/"+ent.getName()+"/load";
|
||||
try(Action act=begin(sig).load(ent).limit(1).if_pk(id).execute()){
|
||||
return act.first();
|
||||
}
|
||||
}
|
||||
public default boolean save(DBO rec) throws IOException{
|
||||
Entity ent=rec.getType();
|
||||
String sig="/"+ent.getName()+"/save";
|
||||
try(Action act=begin(sig).save(ent).setItems(rec).execute()){
|
||||
return act.isDone();
|
||||
}
|
||||
}
|
||||
public default boolean delete(DBO rec) throws IOException {
|
||||
if(rec==null) return false;
|
||||
Entity ent=rec.getType();
|
||||
String sig="/"+ent.getName()+"/delete";
|
||||
try(Action act=begin(sig).delete(ent).setItems(rec).execute()){
|
||||
return act.isDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.reliancy.io;
|
||||
|
||||
/**
|
||||
* Small protocol-oriented helpers used by Jabba I/O codecs.
|
||||
*
|
||||
* This intentionally avoids depending on the broader bstore/jabba utility stack.
|
||||
*/
|
||||
public final class Handy {
|
||||
private Handy() {
|
||||
}
|
||||
|
||||
public static boolean isEmpty(CharSequence seq) {
|
||||
if (seq == null) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < seq.length(); i++) {
|
||||
if (!Character.isWhitespace(seq.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isNumeric(String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
int start = value.charAt(0) == '-' || value.charAt(0) == '+' ? 1 : 0;
|
||||
if (start >= value.length()) {
|
||||
return false;
|
||||
}
|
||||
boolean sawDigit = false;
|
||||
boolean sawDot = false;
|
||||
boolean sawExponent = false;
|
||||
for (int i = start; i < value.length(); i++) {
|
||||
char ch = value.charAt(i);
|
||||
if (Character.isDigit(ch)) {
|
||||
sawDigit = true;
|
||||
continue;
|
||||
}
|
||||
if (ch == '.' && !sawDot && !sawExponent) {
|
||||
sawDot = true;
|
||||
continue;
|
||||
}
|
||||
if ((ch == 'e' || ch == 'E') && !sawExponent && sawDigit && i + 1 < value.length()) {
|
||||
sawExponent = true;
|
||||
sawDigit = false;
|
||||
if (value.charAt(i + 1) == '+' || value.charAt(i + 1) == '-') {
|
||||
i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return sawDigit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
package com.reliancy.io;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Lightweight JSON decoder that produces common Java object trees.
|
||||
*/
|
||||
public final class JsonDecoder {
|
||||
private final CharSequence input;
|
||||
private int index;
|
||||
|
||||
private JsonDecoder(CharSequence input) {
|
||||
this.input = input == null ? "" : input;
|
||||
}
|
||||
|
||||
public static Object decode(CharSequence input) {
|
||||
JsonDecoder parser = new JsonDecoder(input);
|
||||
Object value = parser.readValue();
|
||||
parser.skipWhitespace();
|
||||
if (parser.hasMore()) {
|
||||
throw parser.error("Unexpected trailing content");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static Map<String, Object> decodeObject(CharSequence input) {
|
||||
Object value = decode(input);
|
||||
if (value instanceof Map<?, ?> map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> typed = (Map<String, Object>) map;
|
||||
return typed;
|
||||
}
|
||||
throw new IllegalArgumentException("JSON payload is not an object");
|
||||
}
|
||||
|
||||
public static List<Object> decodeArray(CharSequence input) {
|
||||
Object value = decode(input);
|
||||
if (value instanceof List<?> list) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object> typed = (List<Object>) list;
|
||||
return typed;
|
||||
}
|
||||
throw new IllegalArgumentException("JSON payload is not an array");
|
||||
}
|
||||
|
||||
private Object readValue() {
|
||||
skipWhitespace();
|
||||
if (!hasMore()) {
|
||||
throw error("Unexpected end of input");
|
||||
}
|
||||
char ch = current();
|
||||
switch (ch) {
|
||||
case '{':
|
||||
return readObject();
|
||||
case '[':
|
||||
return readArray();
|
||||
case '"':
|
||||
return readString();
|
||||
case 't':
|
||||
expectLiteral("true");
|
||||
return Boolean.TRUE;
|
||||
case 'f':
|
||||
expectLiteral("false");
|
||||
return Boolean.FALSE;
|
||||
case 'n':
|
||||
expectLiteral("null");
|
||||
return null;
|
||||
default:
|
||||
if (ch == '-' || Character.isDigit(ch)) {
|
||||
return readNumber();
|
||||
}
|
||||
throw error("Unexpected token");
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> readObject() {
|
||||
expect('{');
|
||||
LinkedHashMap<String, Object> result = new LinkedHashMap<>();
|
||||
skipWhitespace();
|
||||
if (peek('}')) {
|
||||
index++;
|
||||
return result;
|
||||
}
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
String key = readString();
|
||||
skipWhitespace();
|
||||
expect(':');
|
||||
Object value = readValue();
|
||||
result.put(key, value);
|
||||
skipWhitespace();
|
||||
if (peek('}')) {
|
||||
index++;
|
||||
return result;
|
||||
}
|
||||
expect(',');
|
||||
}
|
||||
}
|
||||
|
||||
private List<Object> readArray() {
|
||||
expect('[');
|
||||
ArrayList<Object> result = new ArrayList<>();
|
||||
skipWhitespace();
|
||||
if (peek(']')) {
|
||||
index++;
|
||||
return result;
|
||||
}
|
||||
while (true) {
|
||||
result.add(readValue());
|
||||
skipWhitespace();
|
||||
if (peek(']')) {
|
||||
index++;
|
||||
return result;
|
||||
}
|
||||
expect(',');
|
||||
}
|
||||
}
|
||||
|
||||
private String readString() {
|
||||
expect('"');
|
||||
StringBuilder buf = new StringBuilder();
|
||||
while (hasMore()) {
|
||||
char ch = input.charAt(index++);
|
||||
if (ch == '"') {
|
||||
return buf.toString();
|
||||
}
|
||||
if (ch != '\\') {
|
||||
buf.append(ch);
|
||||
continue;
|
||||
}
|
||||
if (!hasMore()) {
|
||||
throw error("Unterminated escape sequence");
|
||||
}
|
||||
char esc = input.charAt(index++);
|
||||
switch (esc) {
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
buf.append(esc);
|
||||
break;
|
||||
case 'b':
|
||||
buf.append('\b');
|
||||
break;
|
||||
case 'f':
|
||||
buf.append('\f');
|
||||
break;
|
||||
case 'n':
|
||||
buf.append('\n');
|
||||
break;
|
||||
case 'r':
|
||||
buf.append('\r');
|
||||
break;
|
||||
case 't':
|
||||
buf.append('\t');
|
||||
break;
|
||||
case 'u':
|
||||
buf.append(readUnicodeEscape());
|
||||
break;
|
||||
default:
|
||||
throw error("Unsupported escape sequence");
|
||||
}
|
||||
}
|
||||
throw error("Unterminated string");
|
||||
}
|
||||
|
||||
private Number readNumber() {
|
||||
int start = index;
|
||||
if (peek('-')) {
|
||||
index++;
|
||||
}
|
||||
readDigits();
|
||||
boolean fractional = false;
|
||||
if (peek('.')) {
|
||||
fractional = true;
|
||||
index++;
|
||||
readDigits();
|
||||
}
|
||||
if (peek('e') || peek('E')) {
|
||||
fractional = true;
|
||||
index++;
|
||||
if (peek('+') || peek('-')) {
|
||||
index++;
|
||||
}
|
||||
readDigits();
|
||||
}
|
||||
String token = input.subSequence(start, index).toString();
|
||||
if (!Handy.isNumeric(token)) {
|
||||
throw error("Invalid number");
|
||||
}
|
||||
if (fractional) {
|
||||
return Double.valueOf(token);
|
||||
}
|
||||
try {
|
||||
return Integer.valueOf(token);
|
||||
} catch (NumberFormatException ignore) {
|
||||
return Long.valueOf(token);
|
||||
}
|
||||
}
|
||||
|
||||
private char readUnicodeEscape() {
|
||||
if (index + 4 > input.length()) {
|
||||
throw error("Invalid unicode escape");
|
||||
}
|
||||
int value = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
char ch = input.charAt(index++);
|
||||
int digit = Character.digit(ch, 16);
|
||||
if (digit < 0) {
|
||||
throw error("Invalid unicode escape");
|
||||
}
|
||||
value = (value << 4) + digit;
|
||||
}
|
||||
return (char) value;
|
||||
}
|
||||
|
||||
private void readDigits() {
|
||||
int start = index;
|
||||
while (hasMore() && Character.isDigit(current())) {
|
||||
index++;
|
||||
}
|
||||
if (start == index) {
|
||||
throw error("Expected digit");
|
||||
}
|
||||
}
|
||||
|
||||
private void expectLiteral(String literal) {
|
||||
for (int i = 0; i < literal.length(); i++) {
|
||||
if (!hasMore() || input.charAt(index++) != literal.charAt(i)) {
|
||||
throw error("Expected literal " + literal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void expect(char expected) {
|
||||
skipWhitespace();
|
||||
if (!peek(expected)) {
|
||||
throw error("Expected '" + expected + "'");
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
private void skipWhitespace() {
|
||||
while (hasMore() && Character.isWhitespace(current())) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasMore() {
|
||||
return index < input.length();
|
||||
}
|
||||
|
||||
private boolean peek(char ch) {
|
||||
return hasMore() && current() == ch;
|
||||
}
|
||||
|
||||
private char current() {
|
||||
return input.charAt(index);
|
||||
}
|
||||
|
||||
private IllegalArgumentException error(String message) {
|
||||
return new IllegalArgumentException(message + " at offset " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.reliancy.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Lightweight JSON encoder for common Java object trees used in web payloads.
|
||||
*/
|
||||
public final class JsonEncoder {
|
||||
private JsonEncoder() {
|
||||
}
|
||||
|
||||
public static int encode(Object value, Appendable out) throws IOException {
|
||||
if (value == null) {
|
||||
if (out != null) {
|
||||
out.append("null");
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
if (value instanceof String || value instanceof CharSequence || value instanceof Character) {
|
||||
return writeQuoted(String.valueOf(value), out);
|
||||
}
|
||||
if (value instanceof Number || value instanceof Boolean) {
|
||||
String text = String.valueOf(value);
|
||||
if (out != null) {
|
||||
out.append(text);
|
||||
}
|
||||
return text.length();
|
||||
}
|
||||
if (value instanceof Map<?, ?> map) {
|
||||
return encodeMap(map, out);
|
||||
}
|
||||
if (value instanceof Iterable<?> iterable) {
|
||||
return encodeIterable(iterable.iterator(), out);
|
||||
}
|
||||
if (value instanceof Iterator<?> iterator) {
|
||||
return encodeIterable(iterator, out);
|
||||
}
|
||||
Class<?> type = value.getClass();
|
||||
if (type.isArray()) {
|
||||
return encodeArray(value, out);
|
||||
}
|
||||
return writeQuoted(String.valueOf(value), out);
|
||||
}
|
||||
|
||||
public static int encodeMap(Map<?, ?> map, Appendable out) throws IOException {
|
||||
int len = 2;
|
||||
if (out != null) {
|
||||
out.append('{');
|
||||
}
|
||||
int index = 0;
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
if (index++ > 0) {
|
||||
len += 1;
|
||||
if (out != null) {
|
||||
out.append(',');
|
||||
}
|
||||
}
|
||||
len += writeQuoted(String.valueOf(entry.getKey()), out);
|
||||
len += 1;
|
||||
if (out != null) {
|
||||
out.append(':');
|
||||
}
|
||||
len += encode(entry.getValue(), out);
|
||||
}
|
||||
if (out != null) {
|
||||
out.append('}');
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
public static int encodeArray(Object array, Appendable out) throws IOException {
|
||||
int len = 2;
|
||||
if (out != null) {
|
||||
out.append('[');
|
||||
}
|
||||
int size = Array.getLength(array);
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (i > 0) {
|
||||
len += 1;
|
||||
if (out != null) {
|
||||
out.append(',');
|
||||
}
|
||||
}
|
||||
len += encode(Array.get(array, i), out);
|
||||
}
|
||||
if (out != null) {
|
||||
out.append(']');
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
public static int encodeIterable(Iterator<?> iterator, Appendable out) throws IOException {
|
||||
int len = 2;
|
||||
if (out != null) {
|
||||
out.append('[');
|
||||
}
|
||||
int index = 0;
|
||||
while (iterator.hasNext()) {
|
||||
if (index++ > 0) {
|
||||
len += 1;
|
||||
if (out != null) {
|
||||
out.append(',');
|
||||
}
|
||||
}
|
||||
len += encode(iterator.next(), out);
|
||||
}
|
||||
if (out != null) {
|
||||
out.append(']');
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
public static boolean needsEscaping(CharSequence text) {
|
||||
if (text == null) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char ch = text.charAt(i);
|
||||
if (ch == '"' || ch == '\\' || ch == '/' || ch < 0x20) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static CharSequence escape(CharSequence text) {
|
||||
if (text == null || text.length() == 0 || !needsEscaping(text)) {
|
||||
return text == null ? "" : text;
|
||||
}
|
||||
StringBuilder buf = new StringBuilder(text.length() + 8);
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char ch = text.charAt(i);
|
||||
switch (ch) {
|
||||
case '"':
|
||||
buf.append("\\\"");
|
||||
break;
|
||||
case '\\':
|
||||
buf.append("\\\\");
|
||||
break;
|
||||
case '/':
|
||||
buf.append("\\/");
|
||||
break;
|
||||
case '\b':
|
||||
buf.append("\\b");
|
||||
break;
|
||||
case '\f':
|
||||
buf.append("\\f");
|
||||
break;
|
||||
case '\n':
|
||||
buf.append("\\n");
|
||||
break;
|
||||
case '\r':
|
||||
buf.append("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
buf.append("\\t");
|
||||
break;
|
||||
default:
|
||||
if (ch < 0x20) {
|
||||
buf.append(String.format("\\u%04x", (int) ch));
|
||||
} else {
|
||||
buf.append(ch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static int writeQuoted(String text, Appendable out) throws IOException {
|
||||
CharSequence escaped = escape(text);
|
||||
if (out != null) {
|
||||
out.append('"').append(escaped).append('"');
|
||||
}
|
||||
return escaped.length() + 2;
|
||||
}
|
||||
}
|
||||
@@ -55,12 +55,15 @@ public class AppSessionFilter extends Processor{
|
||||
css.setAppSession(ss);
|
||||
}
|
||||
@Override
|
||||
public void afterServe(Request request, Response response) throws IOException {
|
||||
CallSession css=CallSession.getInstance();
|
||||
AppSession ss=(AppSession) css.getAppSession();
|
||||
// Determine if request is HTTPS
|
||||
boolean isSecure="https".equalsIgnoreCase(request.getProtocol()) ||
|
||||
"https".equalsIgnoreCase(request.getScheme());
|
||||
public void afterServe(Request request, Response response) throws IOException {
|
||||
CallSession css=CallSession.getInstance();
|
||||
AppSession ss=(AppSession) css.getAppSession();
|
||||
if(ss==null){
|
||||
return;
|
||||
}
|
||||
// Determine if request is HTTPS
|
||||
boolean isSecure="https".equalsIgnoreCase(request.getProtocol()) ||
|
||||
"https".equalsIgnoreCase(request.getScheme());
|
||||
// Set secure=true for HTTPS, HttpOnly=true always for security
|
||||
response.setCookie(KEY_NAME,ss.id,15*60,isSecure,true);
|
||||
}
|
||||
|
||||
@@ -5,20 +5,20 @@ Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.jabba;
|
||||
import com.reliancy.util.Handy;
|
||||
import com.reliancy.util.LRUCache;
|
||||
import com.reliancy.util.Resources;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.slf4j.Logger;
|
||||
package com.reliancy.jabba;
|
||||
import com.reliancy.util.Handy;
|
||||
import com.reliancy.util.LRUCache;
|
||||
import com.reliancy.util.Resources;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/** FileServer is an module and endpoint that exposes multiple URLs thru which files are served.
|
||||
* First it will be just get(tting), later
|
||||
@@ -29,41 +29,73 @@ import org.slf4j.Logger;
|
||||
* Please note Router is for routing.
|
||||
* Bucket is there to process input/output given verbs over resources under it.
|
||||
*/
|
||||
public class FileServer extends EndPoint implements AppModule,Resources.PathRewrite{
|
||||
/** Bucket interface to abstract i/o and provide easier extensibility.
|
||||
* asContainer matches path and then returns local-to-packet path.
|
||||
* signature returns a hash over lastModified or content that reflects modification.
|
||||
*/
|
||||
public static interface Bucket{
|
||||
String getPrefix();
|
||||
String asContained(String path);
|
||||
boolean equals(String pref);
|
||||
InputStream openSource(String local_path,FileServer user) throws IOException;
|
||||
OutputStream openSink(String local_path,FileServer user) throws IOException;
|
||||
String signature(String local_path);
|
||||
}
|
||||
public static class FileBucket implements Bucket{
|
||||
final String prefix;
|
||||
String[] extAllowed;
|
||||
Object[] domain;
|
||||
LRUCache<String,Long> hit_history=new LRUCache<>(2*Runtime.getRuntime().availableProcessors());
|
||||
|
||||
public FileBucket(String prefix){
|
||||
this.prefix=prefix;
|
||||
extAllowed=new String[]{};
|
||||
domain=new Object[]{};
|
||||
public class FileServer extends EndPoint implements AppModule,Resources.PathRewrite{
|
||||
public static class CachedAsset{
|
||||
final URL source;
|
||||
final long lastModified;
|
||||
final long contentLength;
|
||||
final byte[] content;
|
||||
final String signature;
|
||||
|
||||
public CachedAsset(URL source,long lastModified,long contentLength,byte[] content){
|
||||
this.source=source;
|
||||
this.lastModified=lastModified;
|
||||
this.contentLength=contentLength;
|
||||
this.content=content;
|
||||
if(lastModified>0 || contentLength>=0){
|
||||
signature=Handy.hashMD5(lastModified+":"+contentLength);
|
||||
}else if(content!=null){
|
||||
signature=Handy.hashMD5(new String(content,java.nio.charset.StandardCharsets.ISO_8859_1));
|
||||
}else{
|
||||
signature=null;
|
||||
}
|
||||
}
|
||||
public InputStream openSource() throws IOException{
|
||||
if(content!=null) return new ByteArrayInputStream(content);
|
||||
if(source==null) return null;
|
||||
return source.openStream();
|
||||
}
|
||||
public boolean hasContent(){
|
||||
return content!=null;
|
||||
}
|
||||
}
|
||||
/** Bucket interface to abstract i/o and provide easier extensibility.
|
||||
* asContainer matches path and then returns local-to-packet path.
|
||||
* signature returns a hash over lastModified or content that reflects modification.
|
||||
*/
|
||||
public static interface Bucket{
|
||||
String getPrefix();
|
||||
String asContained(String path);
|
||||
boolean equals(String pref);
|
||||
InputStream openSource(String local_path,FileServer user) throws IOException;
|
||||
OutputStream openSink(String local_path,FileServer user) throws IOException;
|
||||
String signature(String local_path);
|
||||
Long lastModified(String local_path);
|
||||
Long contentLength(String local_path);
|
||||
CachedAsset getAsset(String local_path,FileServer user) throws IOException;
|
||||
}
|
||||
public static class FileBucket implements Bucket{
|
||||
final String prefix;
|
||||
String[] extAllowed;
|
||||
Object[] domain;
|
||||
LRUCache<String,CachedAsset> hit_history=new LRUCache<>(64);
|
||||
|
||||
public FileBucket(String prefix){
|
||||
this.prefix=prefix;
|
||||
extAllowed=new String[]{};
|
||||
domain=new Object[]{};
|
||||
}
|
||||
@Override
|
||||
public final String getPrefix(){return prefix;}
|
||||
@Override
|
||||
public String asContained(String path) {
|
||||
if(!path.startsWith(prefix)) return null; // not contained
|
||||
String local_path=path.replace(prefix,"");
|
||||
if(extAllowed.length==0) return local_path;
|
||||
for(String ext:extAllowed){
|
||||
if(path.endsWith(ext)){
|
||||
return local_path;
|
||||
}
|
||||
@Override
|
||||
public String asContained(String path) {
|
||||
if(!path.startsWith(prefix)) return null; // not contained
|
||||
String local_path=path.substring(prefix.length());
|
||||
if(extAllowed.length==0) return local_path;
|
||||
for(String ext:extAllowed){
|
||||
if(path.endsWith(ext)){
|
||||
return local_path;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -79,35 +111,71 @@ public class FileServer extends EndPoint implements AppModule,Resources.PathRewr
|
||||
domain=sp;
|
||||
return this;
|
||||
}
|
||||
public Object[] getDomain(){
|
||||
return (domain!=null && domain.length>0)?domain:Resources.search_path;
|
||||
}
|
||||
public InputStream openSource(String local_path,FileServer user) throws IOException{
|
||||
Object[] sp=getDomain();
|
||||
URL f=Resources.findFirst(user,local_path, sp);
|
||||
if(f==null) return null; // skip if rpath not located
|
||||
URLConnection conn=f.openConnection();
|
||||
hit_history.put(local_path,conn.getLastModified()); // pull last modified for signature
|
||||
return conn.getInputStream();
|
||||
}
|
||||
public OutputStream openSink(String local_path,FileServer user) throws IOException{
|
||||
return null;
|
||||
}
|
||||
public String signature(String local_path){
|
||||
Long last_modified=hit_history.get(local_path);
|
||||
if(last_modified==null) return null;
|
||||
String sig=String.valueOf(last_modified);
|
||||
return Handy.hashMD5(sig);
|
||||
}
|
||||
}
|
||||
final ArrayList<Bucket> buckets=new ArrayList<>();
|
||||
String diskPrefix; // will be prefixed to source if file
|
||||
String classPrefix; // will be prefixed to source if class
|
||||
String urlPrefix; // will be prefixed to source if URL
|
||||
public FileServer(String url_path,String offset,Object ... source){
|
||||
super(null);
|
||||
diskPrefix=classPrefix=offset;
|
||||
addBucket(new FileBucket(url_path).setDomain(source));
|
||||
public Object[] getDomain(){
|
||||
return (domain!=null && domain.length>0)?domain:Resources.search_path;
|
||||
}
|
||||
@Override
|
||||
public CachedAsset getAsset(String local_path,FileServer user) throws IOException{
|
||||
Object[] sp=getDomain();
|
||||
URL f=Resources.findFirst(user,local_path, sp);
|
||||
if(f==null) return null;
|
||||
URLConnection conn=f.openConnection();
|
||||
long lastModified=conn.getLastModified();
|
||||
long contentLength=conn.getContentLengthLong();
|
||||
CachedAsset cached=hit_history.get(local_path);
|
||||
if(cached!=null
|
||||
&& cached.lastModified==lastModified
|
||||
&& cached.contentLength==contentLength){
|
||||
return cached;
|
||||
}
|
||||
byte[] content=null;
|
||||
if(user.shouldCacheContent(local_path,contentLength)){
|
||||
try(InputStream is=conn.getInputStream()){
|
||||
content=is.readAllBytes();
|
||||
contentLength=content.length;
|
||||
}
|
||||
}
|
||||
CachedAsset asset=new CachedAsset(f,lastModified,contentLength,content);
|
||||
hit_history.remove(local_path);
|
||||
hit_history.put(local_path,asset);
|
||||
return asset;
|
||||
}
|
||||
public InputStream openSource(String local_path,FileServer user) throws IOException{
|
||||
CachedAsset asset=getAsset(local_path,user);
|
||||
return asset!=null?asset.openSource():null;
|
||||
}
|
||||
public OutputStream openSink(String local_path,FileServer user) throws IOException{
|
||||
return null;
|
||||
}
|
||||
public String signature(String local_path){
|
||||
CachedAsset asset=hit_history.get(local_path);
|
||||
return asset!=null?asset.signature:null;
|
||||
}
|
||||
@Override
|
||||
public Long lastModified(String local_path){
|
||||
CachedAsset asset=hit_history.get(local_path);
|
||||
return asset!=null && asset.lastModified>0?asset.lastModified:null;
|
||||
}
|
||||
@Override
|
||||
public Long contentLength(String local_path){
|
||||
CachedAsset asset=hit_history.get(local_path);
|
||||
return asset!=null && asset.contentLength>=0?asset.contentLength:null;
|
||||
}
|
||||
}
|
||||
final ArrayList<Bucket> buckets=new ArrayList<>();
|
||||
String diskPrefix; // will be prefixed to source if file
|
||||
String classPrefix; // will be prefixed to source if class
|
||||
String urlPrefix; // will be prefixed to source if URL
|
||||
String indexFile;
|
||||
String fallbackFile;
|
||||
String assetCacheControl="public, max-age=3600";
|
||||
String indexCacheControl="no-cache";
|
||||
String fallbackCacheControl="no-cache";
|
||||
long memoryCacheContentLimit=256*1024;
|
||||
public FileServer(String url_path,String offset,Object ... source){
|
||||
super(null);
|
||||
diskPrefix=classPrefix=offset;
|
||||
addBucket(new FileBucket(url_path).setDomain(source));
|
||||
}
|
||||
public FileServer(){
|
||||
super(null);
|
||||
@@ -120,10 +188,34 @@ public class FileServer extends EndPoint implements AppModule,Resources.PathRewr
|
||||
classPrefix=prefix;
|
||||
return this;
|
||||
}
|
||||
public FileServer setURLOffset(String offset){
|
||||
urlPrefix=offset;
|
||||
return this;
|
||||
}
|
||||
public FileServer setURLOffset(String offset){
|
||||
urlPrefix=offset;
|
||||
return this;
|
||||
}
|
||||
public FileServer setIndexFile(String path){
|
||||
indexFile=path;
|
||||
return this;
|
||||
}
|
||||
public FileServer setFallbackFile(String path){
|
||||
fallbackFile=path;
|
||||
return this;
|
||||
}
|
||||
public FileServer setAssetCacheControl(String value){
|
||||
assetCacheControl=value;
|
||||
return this;
|
||||
}
|
||||
public FileServer setIndexCacheControl(String value){
|
||||
indexCacheControl=value;
|
||||
return this;
|
||||
}
|
||||
public FileServer setFallbackCacheControl(String value){
|
||||
fallbackCacheControl=value;
|
||||
return this;
|
||||
}
|
||||
public FileServer setMemoryCacheContentLimit(long value){
|
||||
memoryCacheContentLimit=value;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* we prefix our path for disk and class contexts.
|
||||
*/
|
||||
@@ -136,41 +228,35 @@ public class FileServer extends EndPoint implements AppModule,Resources.PathRewr
|
||||
return path;
|
||||
}
|
||||
@Override
|
||||
public void serve(Request request, Response response) throws IOException {
|
||||
String verb=request.getVerb();
|
||||
String path=request.getPath();
|
||||
Logger logger=log();
|
||||
boolean atDebug=logger.isDebugEnabled();
|
||||
if(atDebug) logger.debug("{}:{}",verb,path);
|
||||
if(HTTP.VERB_GET.equals(verb)){
|
||||
for(Bucket bucket:buckets){
|
||||
String local_path=bucket.asContained(path);
|
||||
if(local_path==null) continue; // this bucket is not accepting
|
||||
try(InputStream ins=bucket.openSource(local_path,this)){
|
||||
if(ins==null) continue; // url did not take
|
||||
String etag=bucket.signature(local_path);
|
||||
if(etag!=null){
|
||||
response.setHeader("Cache-Control","max-age=0, must-revalidate");
|
||||
response.setHeader("ETag",etag);
|
||||
String etag_old=request.getHeader("If-None-Match");
|
||||
if(etag.equals(etag_old)){
|
||||
// we got same etag no change
|
||||
response.setStatus(Response.HTTP_NOT_MODIFIED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(atDebug) logger.debug("\tfound:"+local_path);
|
||||
String ctype=HTTP.ext2mime(local_path);
|
||||
response.setStatus(Response.HTTP_OK);
|
||||
response.setContentType(ctype);
|
||||
ResponseEncoder enc=response.getEncoder();
|
||||
enc.writeStream(ins);
|
||||
return; // we got something
|
||||
}
|
||||
}
|
||||
}else{
|
||||
// these verbs are not supported
|
||||
}
|
||||
public void serve(Request request, Response response) throws IOException {
|
||||
String verb=request.getVerb();
|
||||
String path=request.getPath();
|
||||
Logger logger=log();
|
||||
boolean atDebug=logger.isDebugEnabled();
|
||||
if(atDebug) logger.debug("{}:{}",verb,path);
|
||||
if(HTTP.VERB_GET.equals(verb) || HTTP.VERB_HEAD.equals(verb)){
|
||||
for(Bucket bucket:buckets){
|
||||
String local_path=bucket.asContained(path);
|
||||
if(local_path==null) continue; // this bucket is not accepting
|
||||
if(isUnsafePath(local_path)){
|
||||
response.setStatus(Response.HTTP_BAD_REQUEST);
|
||||
response.getEncoder().writeln("invalid file path:"+path);
|
||||
logger.warn("rejected unsafe path:{}",path);
|
||||
return;
|
||||
}
|
||||
String resolved_path=resolveLocalPath(local_path);
|
||||
if(tryServe(bucket,resolved_path,request,response,atDebug,false)){
|
||||
return;
|
||||
}
|
||||
if(shouldServeFallback(local_path,request) && fallbackFile!=null){
|
||||
if(tryServe(bucket,fallbackFile,request,response,atDebug,true)){
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
// these verbs are not supported
|
||||
}
|
||||
response.setStatus(Response.HTTP_NOT_FOUND);
|
||||
response.getEncoder().writeln("missing file:"+path);
|
||||
logger.error("not found:{}",path);
|
||||
@@ -214,11 +300,106 @@ public class FileServer extends EndPoint implements AppModule,Resources.PathRewr
|
||||
for(Bucket b:buckets) if(b.equals(url_path)) return b;
|
||||
return null;
|
||||
}
|
||||
public Iterator<Bucket> enumBuckets(){
|
||||
return buckets.iterator();
|
||||
}
|
||||
public void publish(App app) {
|
||||
Router rep=app.getRouter();
|
||||
for(Bucket b:buckets) rep.addRoute("GET",b.getPrefix()+".*",this);
|
||||
}
|
||||
}
|
||||
public Iterator<Bucket> enumBuckets(){
|
||||
return buckets.iterator();
|
||||
}
|
||||
public void publish(App app) {
|
||||
Router rep=app.getRouter();
|
||||
for(Bucket b:buckets){
|
||||
rep.addRoute("GET",b.getPrefix()+".*",this);
|
||||
rep.addRoute("HEAD",b.getPrefix()+".*",this);
|
||||
}
|
||||
}
|
||||
protected String resolveLocalPath(String local_path){
|
||||
String normalized=normalizeLocalPath(local_path);
|
||||
if((normalized==null || normalized.isEmpty()) && indexFile!=null){
|
||||
return indexFile;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
protected String normalizeLocalPath(String local_path){
|
||||
if(local_path==null) return null;
|
||||
local_path=local_path.replace('\\','/');
|
||||
while(local_path.startsWith("/")) local_path=local_path.substring(1);
|
||||
if(local_path.equals(".")) return "";
|
||||
return local_path;
|
||||
}
|
||||
protected boolean shouldServeFallback(String local_path,Request request){
|
||||
String normalized=normalizeLocalPath(local_path);
|
||||
if(normalized==null) return false;
|
||||
if(!acceptsHtml(request)) return false;
|
||||
if(normalized.isEmpty()) return fallbackFile!=null;
|
||||
int lastSlash=normalized.lastIndexOf('/');
|
||||
int lastDot=normalized.lastIndexOf('.');
|
||||
return lastDot < lastSlash + 1;
|
||||
}
|
||||
protected boolean tryServe(Bucket bucket,String local_path,Request request,Response response,boolean atDebug,boolean isFallback) throws IOException{
|
||||
if(local_path==null) return false;
|
||||
CachedAsset asset=bucket.getAsset(local_path,this);
|
||||
if(asset==null) return false;
|
||||
try(InputStream ins=asset.openSource()){
|
||||
if(ins==null) return false;
|
||||
response.setHeader("X-Content-Type-Options","nosniff");
|
||||
String etag=asset.signature;
|
||||
if(etag!=null){
|
||||
response.setHeader("Cache-Control",cacheControlFor(local_path,isFallback));
|
||||
response.setHeader("ETag",etag);
|
||||
String etag_old=request.getHeader("If-None-Match");
|
||||
if(etag.equals(etag_old)){
|
||||
response.setStatus(Response.HTTP_NOT_MODIFIED);
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
response.setHeader("Cache-Control",cacheControlFor(local_path,isFallback));
|
||||
}
|
||||
Long lastModified=asset.lastModified>0?asset.lastModified:null;
|
||||
if(lastModified!=null && lastModified>0){
|
||||
response.setHeader("Last-Modified",java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME.format(
|
||||
java.time.Instant.ofEpochMilli(lastModified).atZone(java.time.ZoneOffset.UTC)
|
||||
));
|
||||
}
|
||||
Long contentLength=asset.contentLength>=0?asset.contentLength:null;
|
||||
if(contentLength!=null && contentLength>=0){
|
||||
response.setHeader("Content-Length",String.valueOf(contentLength));
|
||||
}
|
||||
if(atDebug) log().debug("\tfound:{}",local_path);
|
||||
String ctype=HTTP.ext2mime(local_path);
|
||||
response.setStatus(Response.HTTP_OK);
|
||||
response.setContentType(ctype!=null?ctype:HTTP.MIME_BYTES);
|
||||
if(!HTTP.VERB_HEAD.equals(request.getVerb())){
|
||||
if(asset.hasContent()){
|
||||
response.getEncoder().writeBytes(asset.content,0,asset.content.length);
|
||||
}else{
|
||||
response.getEncoder().writeStream(ins);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
protected boolean shouldCacheContent(String local_path,long contentLength){
|
||||
if(contentLength<0) return false;
|
||||
return contentLength<=memoryCacheContentLimit;
|
||||
}
|
||||
protected boolean acceptsHtml(Request request){
|
||||
if(request==null) return true;
|
||||
String accept=request.getHeader("Accept");
|
||||
if(accept==null || accept.trim().isEmpty()) return true;
|
||||
accept=accept.toLowerCase();
|
||||
return accept.contains("text/html") || accept.contains("application/xhtml+xml");
|
||||
}
|
||||
protected boolean isUnsafePath(String local_path){
|
||||
String normalized=normalizeLocalPath(local_path);
|
||||
if(normalized==null) return false;
|
||||
return normalized.equals("..") || normalized.startsWith("../") || normalized.contains("/../");
|
||||
}
|
||||
protected String cacheControlFor(String local_path,boolean isFallback){
|
||||
String normalized=normalizeLocalPath(local_path);
|
||||
if(normalized==null || normalized.isEmpty() || normalized.equals(indexFile)){
|
||||
return indexCacheControl;
|
||||
}
|
||||
if(isFallback){
|
||||
return fallbackCacheControl;
|
||||
}
|
||||
return assetCacheControl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ import java.util.Iterator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.reliancy.rec.JSONEncoder;
|
||||
import com.reliancy.util.CodeException;
|
||||
import com.reliancy.io.JsonEncoder;
|
||||
import com.reliancy.util.CodeException;
|
||||
import com.reliancy.jabba.ui.Rendering;
|
||||
import com.reliancy.jabba.ui.Template;
|
||||
|
||||
@@ -88,15 +88,15 @@ public class ResponseEncoder implements Appendable,Closeable{
|
||||
if(writer!=null) writer.flush();
|
||||
if(out!=null) out.flush();
|
||||
}
|
||||
public ResponseEncoder writeBytes(byte[] buf,int offset,int len) throws IOException{
|
||||
|
||||
try{
|
||||
response.transitionTo(ResponseState.WRITING);
|
||||
getOutputStream().write(buf,offset, len);
|
||||
}finally{
|
||||
if(response.getState() == ResponseState.WRITING) {
|
||||
response.transitionTo(ResponseState.WRITTEN);
|
||||
}
|
||||
public ResponseEncoder writeBytes(byte[] buf,int offset,int len) throws IOException{
|
||||
OutputStream os=getOutputStream();
|
||||
try{
|
||||
response.transitionTo(ResponseState.WRITING);
|
||||
os.write(buf,offset, len);
|
||||
}finally{
|
||||
if(response.getState() == ResponseState.WRITING) {
|
||||
response.transitionTo(ResponseState.WRITTEN);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -227,7 +227,7 @@ public class ResponseEncoder implements Appendable,Closeable{
|
||||
StringBuilder title=new StringBuilder();
|
||||
StringBuilder detail=new StringBuilder();
|
||||
CodeException.fillUserMessage(ex, detail, title);
|
||||
String body=MessageFormat.format(template,JSONEncoder.escape(title),JSONEncoder.escape(detail));
|
||||
String body=MessageFormat.format(template,JsonEncoder.escape(title),JsonEncoder.escape(detail));
|
||||
writeString(body);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -213,7 +213,9 @@ public class JettyApp extends App implements Servlet {
|
||||
new com.reliancy.jabba.servlet.ServletRequest(httpRequest);
|
||||
final com.reliancy.jabba.servlet.ServletResponse resp =
|
||||
new com.reliancy.jabba.servlet.ServletResponse(req, httpResponse);
|
||||
final CallSession ss=CallSession.getInstance();
|
||||
// Use a fresh CallSession per request so async work shares request state
|
||||
// without reusing stale thread-local session objects across requests.
|
||||
final CallSession ss=new CallSession();
|
||||
// install executor just in case we need it, especially for async processing
|
||||
ss.setExecutor(
|
||||
jetty.getThreadPool() != null ?
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.rec;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/** 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 sub-slots.
|
||||
*
|
||||
* This class describes structure of Fields or Entities via the keys array of slots.
|
||||
* Additionally we provide a number of methods to locate, set or get or remove or add slots.
|
||||
* However slots could reside in other places such as base classes and so getOwnSlots will return a
|
||||
* bare list of slots in this object while all other methods will take into account other sources.
|
||||
* We do this to stay consistent at Rec level when Hdr inheritance comes into play.
|
||||
*/
|
||||
public class Hdr {
|
||||
public static final int FLAG_ARRAY =0x0001;
|
||||
public static final int FLAG_CHANGED =0x0002;
|
||||
public static final int FLAG_STORABLE =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(name).append(":");
|
||||
ret.append("{")
|
||||
.append("flags:").append(flags)
|
||||
.append(",dim:").append(count())
|
||||
.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 int getFlags(){
|
||||
return flags;
|
||||
}
|
||||
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 List<Slot> getOwnSlots(){
|
||||
return keys;
|
||||
}
|
||||
public boolean isOwned(Slot s){
|
||||
return keys.contains(s);
|
||||
}
|
||||
public Iterator<Slot> iterator(int offset){
|
||||
return keys.listIterator(offset);
|
||||
}
|
||||
public int indexOf(String name){
|
||||
return indexOf(name,0);
|
||||
}
|
||||
public int indexOf(String name,int ofs){
|
||||
Iterator<Slot> it=iterator(ofs);
|
||||
int index=-1;
|
||||
while(it.hasNext()){
|
||||
index+=1;
|
||||
Slot e=it.next();
|
||||
//if(e.getName().equalsIgnoreCase(name)) return index;
|
||||
if(e.equals(name)) return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
public int indexOf(Slot s,int ofs){
|
||||
Iterator<Slot> it=iterator(ofs);
|
||||
int index=-1;
|
||||
while(it.hasNext()){
|
||||
index+=1;
|
||||
Slot e=it.next();
|
||||
if(e==s) return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
public Slot makeSlot(String name){
|
||||
return new Slot(name);
|
||||
}
|
||||
/**
|
||||
* this version will get or create a slot by given name.
|
||||
* @param name slot name
|
||||
* @param make wether to create if not present
|
||||
* @return Slot or null.
|
||||
*/
|
||||
public Slot getSlot(String name,boolean make){
|
||||
int index=indexOf(name);
|
||||
if(index<0){
|
||||
return make?makeSlot(name):null;
|
||||
}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 int count(){
|
||||
return keys.size();
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
public static final String toString(Rec rec){
|
||||
StringBuffer buf=new StringBuffer();
|
||||
try {
|
||||
writes(rec,buf);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,333 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
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.
|
||||
* TODO: reuse headers in an array if same structure
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
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).toString();
|
||||
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 CharSequence escape(CharSequence 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.subSequence(0,i));
|
||||
buf.append("\\\"");
|
||||
break;
|
||||
case '\\':
|
||||
if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
|
||||
buf.append("\\\\");
|
||||
break;
|
||||
case '/':
|
||||
if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
|
||||
buf.append("\\/");
|
||||
break;
|
||||
case '\b':
|
||||
if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
|
||||
buf.append("\\b");
|
||||
break;
|
||||
case '\f':
|
||||
if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
|
||||
buf.append("\\f");
|
||||
break;
|
||||
case '\n':
|
||||
if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
|
||||
buf.append("\\n");
|
||||
break;
|
||||
case '\r':
|
||||
if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
|
||||
buf.append("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
|
||||
buf.append("\\t");
|
||||
break;
|
||||
default:
|
||||
if(buf!=null) buf.append(ch);
|
||||
}
|
||||
}
|
||||
return buf!=null?buf:str;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
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.indexOf(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.indexOf(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.indexOf(s.getName());// fall back to search if slot not set
|
||||
if(index>=0) remove(index);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
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,true):null;
|
||||
}
|
||||
public default Slot getSlot(int pos){
|
||||
Hdr m=meta();
|
||||
return m!=null?m.getSlot(pos):null;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
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.getInitValue();}
|
||||
};
|
||||
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 boolean equals(String str){
|
||||
return name.equalsIgnoreCase(str);
|
||||
}
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
public Slot setPosition(int position) {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
public Object getInitValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
public Slot setInitValue(Object defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
return this;
|
||||
}
|
||||
public Initializer getInitVia() {
|
||||
return initValue;
|
||||
}
|
||||
public Slot setInitVia(Initializer initValue) {
|
||||
this.initValue = initValue;
|
||||
return this;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
package com.reliancy.util;
|
||||
import java.util.HashMap;
|
||||
|
||||
/** One exception to rule them all.
|
||||
* This exception works with ResultCode and represents and instance with context information.
|
||||
* If a ResultCode is deemed parametric then we use provided parameters to update it when generating a message.
|
||||
*
|
||||
* @author amer
|
||||
*/
|
||||
public class CodeException extends RuntimeException {
|
||||
|
||||
protected final int code;
|
||||
protected final HashMap<String,Object> context=new HashMap<>();
|
||||
public CodeException(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
public CodeException(Throwable cause, int code) {
|
||||
super(cause);
|
||||
this.code = code;
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
return getMessage();
|
||||
}
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
public ResultCode getResultCode(){
|
||||
return ResultCode.get(code);
|
||||
}
|
||||
@Override
|
||||
public String getMessage() {
|
||||
ResultCode rcode=getResultCode();
|
||||
if(rcode!=null){
|
||||
boolean wrapped=(rcode.getCode()==ResultCode.FAILURE);
|
||||
String msg=rcode.getMessage();
|
||||
if(msg.contains("$")){
|
||||
for(String key:context.keySet()){
|
||||
Object obj=context.get(key);
|
||||
if(obj==null) continue;
|
||||
String val=String.valueOf(obj);
|
||||
msg=msg.replaceAll("\\$\\{"+key+"\\}",val);
|
||||
}
|
||||
}else if(this.getCause()!=null && wrapped){
|
||||
msg=CodeException.getUserMessage(this.getCause());
|
||||
}
|
||||
return msg;
|
||||
}else{
|
||||
return "("+String.format("%08X", code)+")";
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String name) {
|
||||
return (T)context.get(name);
|
||||
}
|
||||
|
||||
public CodeException put(String name, String value) {
|
||||
context.put(name, value);
|
||||
return this;
|
||||
}
|
||||
public static CodeException wrap(Throwable exception) {
|
||||
if (exception instanceof CodeException) {
|
||||
CodeException se = (CodeException)exception;
|
||||
return se;
|
||||
} else {
|
||||
return new CodeException(exception,ResultCode.FAILURE);
|
||||
}
|
||||
}
|
||||
public static String getUserMessage(Throwable ex,Object context) {
|
||||
return getUserMessage(ex);
|
||||
}
|
||||
public static String getUserMessage(Throwable ex) {
|
||||
StringBuilder buf=new StringBuilder();
|
||||
fillUserMessage(ex,buf,null);
|
||||
return buf.toString();
|
||||
}
|
||||
public static Throwable fillUserMessage(Throwable ex,StringBuilder msg,StringBuilder title) {
|
||||
Throwable c = ex;
|
||||
//System.out.println(">>>"+c+"/"+c.getCause());
|
||||
while(c.getCause()!=null){
|
||||
Throwable cc= c.getCause();
|
||||
if(c.getMessage()==null){
|
||||
c=cc;continue;
|
||||
}
|
||||
String cMsg=c.getMessage();
|
||||
String ccMsg=cc.getMessage();
|
||||
//System.out.println("!!!"+cMsg+"/"+c.getClass().getName()+"/"+cc.getClass().getName());
|
||||
boolean wrapped=(c instanceof CodeException) && ((CodeException)c).getCode()==ResultCode.FAILURE;
|
||||
boolean plain_at=cMsg.equals(c.getClass().getName());
|
||||
boolean plain_sub=cMsg.equals(cc.getClass().getName());
|
||||
boolean same_msg=cMsg.equalsIgnoreCase(ccMsg);
|
||||
//System.out.println("\t"+plain_sub+"#"+cc+"$"+cc.getCause()+"*"+cc.getMessage());
|
||||
if(plain_at || plain_sub || cMsg.startsWith(cc.getClass().getName()+":") || same_msg || wrapped){
|
||||
c=cc;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
//System.out.println("CC:"+c);
|
||||
// take care of title
|
||||
String _title=c.getClass().getSimpleName();
|
||||
if(c instanceof CodeException){
|
||||
CodeException cc=(CodeException) c;
|
||||
if(cc.getCause()!=null){
|
||||
_title=cc.getClass().getSimpleName();
|
||||
}else{
|
||||
// we do not have a cause
|
||||
int code=cc.getCode();
|
||||
ResultCode rcode=ResultCode.get(code);
|
||||
if(rcode!=null) _title=rcode.getSource();
|
||||
}
|
||||
}
|
||||
if(title!=null) title.append(_title);
|
||||
// now take care of detail
|
||||
String _msg=c.getLocalizedMessage();
|
||||
if(_msg==null || _msg.trim().isEmpty()){
|
||||
_msg=c.getClass().getSimpleName();
|
||||
StackTraceElement[] se=c.getStackTrace();
|
||||
if(se!=null && se.length>0) _msg+="\n\t at "+se[0].toString();
|
||||
}
|
||||
String prefString="Exception:";
|
||||
String prefString2="Error:";
|
||||
int prefix=_msg.lastIndexOf(prefString);
|
||||
if(prefix<0) prefix=_msg.lastIndexOf(prefString2);
|
||||
if(prefix>0 && _msg.substring(0, prefix).contains(".")) _msg=_msg.substring(prefix+prefString.length());
|
||||
if(msg!=null) msg.append(_msg);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -1,593 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
/**
|
||||
* Common utility methods.
|
||||
*/
|
||||
public final class Handy {
|
||||
public static final String WHITE=" \t\r\f\n";
|
||||
|
||||
/** place left-right around verb.
|
||||
* @param verb body of text
|
||||
* @param left to add left of verb
|
||||
* @param left to add right of verb
|
||||
* @return adjusted text
|
||||
* */
|
||||
public static String wrap(String verb, String left, String right) {
|
||||
if(verb==null) verb="";
|
||||
if(verb.startsWith(left) && verb.endsWith(right)) return verb;
|
||||
return left+verb.trim()+right;
|
||||
}
|
||||
/** remove left-right around verb.
|
||||
* @param verb body of text
|
||||
* @param left to remove left of verb
|
||||
* @param left to remove right of verb
|
||||
* @return adjusted text
|
||||
**/
|
||||
public static String unwrap(String verb, String left, String right) {
|
||||
if(verb==null) return verb;
|
||||
String ret=verb.trim();
|
||||
if(ret.startsWith(left) && ret.endsWith(right)){
|
||||
ret=ret.substring(left.length());
|
||||
ret=ret.substring(0,ret.length()-right.length());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/** remove any chars elements from left of verb.
|
||||
* @param verb body of text
|
||||
* @return adjusted text
|
||||
*/
|
||||
public static String trimLeft(String verb,String chars){
|
||||
while(verb.length()>0 && chars.indexOf(verb.charAt(0))!=-1){
|
||||
verb=verb.substring(1);
|
||||
}
|
||||
return verb;
|
||||
}
|
||||
/** remove any chars elements from right of verb.
|
||||
* @param verb body of text
|
||||
* @return adjusted text
|
||||
*/
|
||||
public static String trimRight(String verb,String chars){
|
||||
while(verb.length()>0 && chars.indexOf(verb.charAt(verb.length()-1))!=-1){
|
||||
verb=verb.substring(0,verb.length()-1);
|
||||
}
|
||||
return verb;
|
||||
}
|
||||
/** remove any chars elements from right and right of verb.
|
||||
* @param verb body of text
|
||||
* @return adjusted text
|
||||
*/
|
||||
public static String trimBoth(String verb,String chars){
|
||||
verb=trimLeft(verb, chars);
|
||||
verb=trimRight(verb, chars);
|
||||
return verb;
|
||||
}
|
||||
/** remove any chars elements from right and right of verb symetrically. trims whitespace first. */
|
||||
public static String trimEvenly(String verb,String chars){
|
||||
verb=trimBoth(verb," \t\n\r\f");
|
||||
while(verb.length()>1){
|
||||
char left=verb.charAt(0);
|
||||
char right=verb.charAt(verb.length()-1);
|
||||
if(left!=right) break; // left-right not even
|
||||
if(chars.indexOf(left)<0) break; // even but not in chars list
|
||||
verb=verb.substring(1,verb.length()-1);
|
||||
}
|
||||
return verb;
|
||||
}
|
||||
public static <T> T nz(T val, T def){
|
||||
return val!=null?val:def;
|
||||
}
|
||||
/** Convert incoming value to an expected class.
|
||||
* @param clazz expected class
|
||||
* @param val observed value
|
||||
* @return val converted to type clazz.
|
||||
*/
|
||||
public static Object normalize(Class<?> clazz, Object val ) {
|
||||
if(val==null) return null; // we are null
|
||||
if(clazz.isAssignableFrom(val.getClass())) return val; // we are assignable
|
||||
if(val instanceof String){
|
||||
String value=(String) val;
|
||||
if(value.isEmpty() || value.equals("''") || value.equals("\"\"")) return null;
|
||||
if( Boolean.class==( clazz ) || boolean.class==( clazz ) ) return Boolean.parseBoolean( value );
|
||||
if( Byte.class==( clazz ) || byte.class==( clazz ) ) return Byte.parseByte( value );
|
||||
if( Short.class==( clazz ) || short.class==( clazz ) ) return Short.parseShort( value );
|
||||
if( Integer.class==( clazz ) || int.class==( clazz ) ) return Integer.parseInt( value );
|
||||
if( Long.class==( clazz ) || long.class==( clazz )) return Long.parseLong( value );
|
||||
if( Float.class==( clazz ) || float.class==( clazz ) ) return Float.parseFloat( value );
|
||||
if( Double.class==( clazz ) || double.class==( clazz )) return Double.parseDouble( value );
|
||||
}
|
||||
if(clazz==String.class || clazz==CharSequence.class){
|
||||
return String.valueOf(val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
/**
|
||||
* This method is a bit more complex because it locks onto two delimiters one for grouping and other
|
||||
* for decimal point and chooses those from a list of [space],'`. which are used all over the world in different places.
|
||||
* Returns true if the string only contains digits and numeric characters.
|
||||
* This should match 1000000 also 1,000,000 and also 1,000,000.00 but it is still not possible to differentiate between 1,000 =1000 in us
|
||||
* from 1000,00 whch is used in europe. So it is difficult to normalize the string so it could process any number.
|
||||
* @param str string to test
|
||||
* @return trie if string looks numeric or is null/empty
|
||||
*/
|
||||
public static final boolean isNumeric(String str){
|
||||
int strLen;
|
||||
if (str == null || (strLen = str.length()) == 0) {
|
||||
return true;
|
||||
}
|
||||
String delims=" ,.'`";
|
||||
int delimNumber=0; // 0 we load, 1 we let one more, 2 we exit on second occurance
|
||||
char delimUsed=0; // char used as delim
|
||||
boolean delimLast=false;
|
||||
int digitCount=0;
|
||||
for (int i = 0; i < strLen; i++) {
|
||||
char ch=str.charAt(i);
|
||||
boolean accept=Character.isDigit(ch);
|
||||
if(accept) digitCount++;
|
||||
accept=accept || (ch=='-' && i==0);
|
||||
accept=accept || (ch=='+' && i==0);
|
||||
if(delims.indexOf(ch)>=0){
|
||||
accept=!delimLast; // prevent delims following each other
|
||||
delimLast=true;
|
||||
if(delimNumber==0){
|
||||
delimNumber=1;
|
||||
delimUsed=ch;
|
||||
}else if(delimNumber==1){
|
||||
if(delimUsed!=ch) delimNumber=2;
|
||||
delimUsed=ch;
|
||||
}else{
|
||||
// we have seen two different delim and whatever is coming here is breaking numeric format like second delim second time or some otehr
|
||||
accept=false;
|
||||
}
|
||||
}else{
|
||||
delimLast=false;
|
||||
}
|
||||
if(!accept) return false;
|
||||
}
|
||||
return digitCount>0;
|
||||
}
|
||||
/**
|
||||
* @return true if the string is null, empty or contains only white space.
|
||||
*/
|
||||
public static boolean isBlank(CharSequence str) {
|
||||
int strLen;
|
||||
if (str == null || (strLen = str.length()) == 0) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < strLen; i++) {
|
||||
if ((Character.isWhitespace(str.charAt(i)) == false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Provides a unified notion of what constitutes an empty value.
|
||||
* For any object if it is null.
|
||||
* For string also if it is blank
|
||||
* For arrays and lists and collection and maps also if no entries or keys exist.
|
||||
* @param value anything
|
||||
* @return true if any of the above matches
|
||||
*/
|
||||
public static boolean isEmpty(Object value){
|
||||
if(value==null) return true;
|
||||
if(value instanceof CharSequence){
|
||||
return isBlank((CharSequence)value);
|
||||
}
|
||||
Class<?> cls=value.getClass();
|
||||
if(cls.isArray()) {
|
||||
if(value instanceof Object[]){
|
||||
Object[] arr=(Object[]) value;
|
||||
if(arr.length==0) return true;
|
||||
for(int i=0;i<arr.length;i++) if(isEmpty(arr[i])==false) return false;
|
||||
return true;
|
||||
}if(value instanceof byte[]){
|
||||
return ((byte[])value).length==0;
|
||||
}else if(value instanceof short[]){
|
||||
return ((short[])value).length==0;
|
||||
}else if(value instanceof int[]){
|
||||
return ((int[])value).length==0;
|
||||
}else if(value instanceof long[]){
|
||||
return ((long[])value).length==0;
|
||||
}else if(value instanceof float[]){
|
||||
return ((float[])value).length==0;
|
||||
}else if(value instanceof double[]){
|
||||
return ((double[])value).length==0;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if(value instanceof Collection){
|
||||
Collection<?> c=(Collection<?>)value;
|
||||
if(c.isEmpty()) return true;
|
||||
for(Object o:c){
|
||||
if(isEmpty(o)==false) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/** Attempts to take a compact string and beautify it.
|
||||
* Will uppercase the first letter. Will also expand CamelCase.
|
||||
* Also will replace _ with empty space.
|
||||
* @param str
|
||||
* @return nicely formatted string ready for display
|
||||
*/
|
||||
public static final String prettyPrint(String str){
|
||||
if(str==null) return "";
|
||||
boolean fix=false;
|
||||
char prevCh=0;
|
||||
if(str.startsWith("org.") || str.startsWith("net.") || str.startsWith("com.") || str.startsWith("java.")){
|
||||
str=str.substring(1+str.lastIndexOf('.')); // we strip class name paths
|
||||
}
|
||||
for(int i=0;i<str.length();i++){
|
||||
char currCh=str.charAt(i);
|
||||
if(i==0 && Character.isLowerCase(currCh)) fix=true;
|
||||
if(Character.isLowerCase(prevCh) && Character.isUpperCase(currCh)) fix=true;
|
||||
if(Character.isUpperCase(prevCh) && Character.isUpperCase(currCh) && i<(str.length()-1) && Character.isLowerCase(str.charAt(i+1))) fix=true;
|
||||
if(!Character.isLetter(currCh)) fix=true;
|
||||
prevCh=currCh;
|
||||
}
|
||||
if(!fix) return str;
|
||||
StringBuilder bufs=new StringBuilder();
|
||||
boolean toUC=false;
|
||||
for(int i=0;i<str.length();i++){
|
||||
char currCh=str.charAt(i);
|
||||
if(currCh=='_') currCh=' ';
|
||||
prevCh=bufs.length()>0?bufs.charAt(bufs.length()-1):currCh;
|
||||
if(Character.isWhitespace(currCh)){
|
||||
if(!Character.isWhitespace(prevCh)){
|
||||
bufs.append(' ');
|
||||
}
|
||||
continue; // ignore repeated whitespace otherwise emit space
|
||||
}
|
||||
if(bufs.length()==0){
|
||||
toUC=true;
|
||||
}else if((!Character.isUpperCase(prevCh) && ("-+/%*".indexOf(prevCh)==-1 || Character.isLetter(prevCh))) && Character.isUpperCase(currCh)){
|
||||
// non uc (a not one of operands) behind, uc ahead
|
||||
bufs.append(" ");
|
||||
}else if(Character.isLetter(prevCh) && Character.isDigit(currCh)){
|
||||
// letter behind, digit ahead
|
||||
bufs.append(" ");
|
||||
}else if(Character.isUpperCase(prevCh) && Character.isUpperCase(currCh) && i<(str.length()-1) && Character.isLowerCase(str.charAt(i+1))){
|
||||
// behind me uppercase infrom uppercase then lowercase
|
||||
bufs.append(" ");
|
||||
}
|
||||
bufs.append(toUC?Character.toUpperCase(currCh):currCh);
|
||||
toUC=false;
|
||||
}
|
||||
while(bufs.length()>0 && Character.isWhitespace(bufs.charAt(bufs.length()-1))){
|
||||
// trims whitespace from end
|
||||
bufs.setLength(bufs.length()-1);
|
||||
}
|
||||
return bufs.toString();
|
||||
}
|
||||
/** Attempts to take a user string and compact it to camel case.
|
||||
* @param value more or less presentable string
|
||||
* @return nicely compact string
|
||||
*/
|
||||
public static String toCamelCase(String value) {
|
||||
if(value==null || value.trim().isEmpty()) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
//final char delimChar = ' ';
|
||||
boolean flip = false;
|
||||
for (int charInd = 0; charInd < value.length(); charInd++) {
|
||||
char ch = value.charAt(charInd);
|
||||
if (Character.isWhitespace(ch)) {
|
||||
flip = true;
|
||||
}else if(flip){
|
||||
flip = false;
|
||||
if(ch==Character.toLowerCase(ch)) sb.append("_");
|
||||
sb.append(ch);
|
||||
}else{
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
public static byte[] deflate(byte[] content) throws IOException{
|
||||
Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION,true);
|
||||
deflater.setInput(content);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(content.length);
|
||||
deflater.finish();
|
||||
byte[] buffer = new byte[1024];
|
||||
while (!deflater.finished()) {
|
||||
int count = deflater.deflate(buffer); // returns the generated code... index
|
||||
outputStream.write(buffer, 0, count);
|
||||
}
|
||||
outputStream.close();
|
||||
byte[] output = outputStream.toByteArray();
|
||||
return output;
|
||||
}
|
||||
|
||||
public static byte[] inflate(byte[] contentBytes) throws IOException, DataFormatException{
|
||||
Inflater inflater = new Inflater(true);
|
||||
inflater.setInput(contentBytes);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(contentBytes.length);
|
||||
byte[] buffer = new byte[1024];
|
||||
while (!inflater.finished()) {
|
||||
int count = inflater.inflate(buffer);
|
||||
outputStream.write(buffer, 0, count);
|
||||
}
|
||||
outputStream.close();
|
||||
byte[] output = outputStream.toByteArray();
|
||||
return output;
|
||||
}
|
||||
|
||||
public static void shuffle(Object[] e){
|
||||
Random rn = new Random();
|
||||
for(int i=0;i<e.length;i++){
|
||||
int other=rn.nextInt(e.length);
|
||||
Object tmp=e[i];
|
||||
e[i]=e[other];
|
||||
e[other]=tmp;
|
||||
}
|
||||
}
|
||||
public static String encodeBase64(byte[] data){
|
||||
return Base64.getEncoder().encodeToString(data);
|
||||
}
|
||||
public static byte[] decodeBase64(String data){
|
||||
return Base64.getDecoder().decode(data);
|
||||
}
|
||||
/** Simple XOR encryption of a map of key-value pairs.
|
||||
* We randomize the order of key value pairs to make the string more unpredictable.
|
||||
* Returned string is base64 and web safe
|
||||
* @param key encryption key
|
||||
* @param m map of param-value pairs to encrypt values.
|
||||
* @return a string of encoded map param-value pairs which were then encrypted
|
||||
*/
|
||||
public static final String encrypt(String key,Map<String,String> m){
|
||||
String ret=null;
|
||||
Object[] es=m.keySet().toArray();
|
||||
shuffle(es); // we shuffle entries to confuse the string a bit
|
||||
StringBuilder buf=new StringBuilder();
|
||||
for(int i=0;i<es.length;i++){
|
||||
Object e=es[i];
|
||||
if(i>0) buf.append("\n");
|
||||
buf.append(e).append(":").append(m.get(e));
|
||||
}
|
||||
ret=encryptString(key,buf.toString());
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* This method will encrypt a string and return BASE 64 string that is web safe.
|
||||
* TO make the string web safe we replace + with - and / with _
|
||||
* Must revert this change on the reverse.
|
||||
* @param key
|
||||
* @param ret
|
||||
* @return
|
||||
*/
|
||||
public static final String encryptString(String key,String ret){
|
||||
try{
|
||||
byte[] bkey=key.getBytes("UTF-8");
|
||||
byte[] bstr=ret.getBytes("UTF-8");
|
||||
for(int i=0;i<bstr.length;i++){
|
||||
bstr[i]=(byte)(bstr[i] ^ bkey[i%bkey.length]);
|
||||
}
|
||||
// now need to encode this
|
||||
ret=encodeBase64(bstr);
|
||||
ret=ret.replace('+','-');
|
||||
ret=ret.replace('/','_');
|
||||
ret=ret.replace('=','.');
|
||||
ret=ret.replace("\n","");
|
||||
}catch(Exception e){
|
||||
ret="";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/**Reverses the effects of encrypt.
|
||||
* Also changes
|
||||
* @param key
|
||||
* @param m
|
||||
* @return values decrypted and parsed into key-value pair along newline.
|
||||
*/
|
||||
public static final Map<String,String> decrypt(String key,String m){
|
||||
m=decryptString(key,m);
|
||||
Map<String,String> ret=new HashMap<>();
|
||||
//System.out.println("Output:"+m);
|
||||
Tokenizer tokz=new Tokenizer(m);
|
||||
tokz.setDelimChars("\n");
|
||||
tokz.setWhiteChars(null);
|
||||
for(String t=tokz.nextToken();t!=null;t=tokz.nextToken()){
|
||||
if("\n".equals(t)) continue;
|
||||
String[] kv=t.split(":",2);
|
||||
ret.put(kv[0],kv.length>1?kv[1]:null);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
public static final String decryptString(String key,String m){
|
||||
try{
|
||||
//m=URLDecoder.decode(m, "UTF-8");
|
||||
m=m.replace('-','+');
|
||||
m=m.replace('_','/');
|
||||
m=m.replace('.','=');
|
||||
byte[] bkey=key.getBytes("UTF-8");
|
||||
byte[] bstr=decodeBase64(m);
|
||||
for(int i=0;i<bstr.length;i++){
|
||||
bstr[i]=(byte)(bstr[i] ^ bkey[i%bkey.length]);
|
||||
}
|
||||
m=new String(bstr,"UTF-8");
|
||||
}catch(Exception e){
|
||||
}
|
||||
return m;
|
||||
}
|
||||
/**
|
||||
* Generates a hash string with the algorithm name prefixed.
|
||||
* @param message text to hash
|
||||
* @param algorithm algorithm to use
|
||||
* @return hash digest
|
||||
*/
|
||||
public static String hashString(String message, String algorithm) throws NoSuchAlgorithmException, UnsupportedEncodingException{
|
||||
if(message==null) return message;
|
||||
MessageDigest digest = MessageDigest.getInstance(algorithm);
|
||||
byte[] hashedBytes = digest.digest(message.getBytes("UTF-8"));
|
||||
return algorithm.toLowerCase()+":"+encodeBase64(hashedBytes);
|
||||
}
|
||||
/** hash text using sha256. */
|
||||
public static String hashSHA256(String message){
|
||||
try{
|
||||
return hashString(message,"SHA-256");
|
||||
}catch(Exception ex){
|
||||
return "sha-256:"+Integer.toHexString(message.hashCode());
|
||||
}
|
||||
}
|
||||
public static String hashMD5(String input){
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] messageDigest = md.digest(input.getBytes());
|
||||
BigInteger no = new BigInteger(1, messageDigest);
|
||||
String hashtext = no.toString(16);
|
||||
while (hashtext.length() < 32) {
|
||||
hashtext = "0" + hashtext;
|
||||
}
|
||||
return hashtext;
|
||||
}catch (NoSuchAlgorithmException e) { // For specifying wrong message digest algorithms
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
public static String toHexString(byte[] hash){
|
||||
char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
|
||||
StringBuilder sb = new StringBuilder(hash.length * 2);
|
||||
for (byte b : hash) {
|
||||
sb.append(HEX_CHARS[(b & 0xF0) >> 4]);
|
||||
sb.append(HEX_CHARS[b & 0x0F]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
/**
|
||||
* Finds first occurrence of sub inside body with and without case.
|
||||
* We implement this search via a FSM and ignore the case.
|
||||
* @param body text to search
|
||||
* @param sub subsequence to find
|
||||
* @param offset offset from 0
|
||||
* @return offset of next occurance starting at offiset
|
||||
*/
|
||||
public static final int indexOf(CharSequence body,CharSequence sub,int offset){
|
||||
if(body==null) return -1;
|
||||
int state=0;
|
||||
int blen=body.length();
|
||||
int slen=sub.length();
|
||||
boolean ignorecase=true;
|
||||
for(int index=offset;index<blen;index++){
|
||||
char bC=body.charAt(index);
|
||||
char sC=sub.charAt(state);
|
||||
if(ignorecase){
|
||||
bC=Character.toLowerCase(bC);
|
||||
sC=Character.toLowerCase(sC);
|
||||
}
|
||||
if(bC==sC) state+=1; else state=0;
|
||||
if(state>=slen) return index-slen+1; // we found a match
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/**
|
||||
* Will trim the string from left and right and remove any of the symbols.
|
||||
* @param trim text to strip
|
||||
* @param sym set of characters to trim
|
||||
*/
|
||||
public static String trim(String trim,String sym) {
|
||||
if(trim==null || trim.length()==0) return trim;
|
||||
int start=0;
|
||||
int end=trim.length();
|
||||
while(start<trim.length() && sym.indexOf(trim.charAt(start))!=-1) start++;
|
||||
while(0<end && sym.indexOf(trim.charAt(end-1))!=-1) end--;
|
||||
if(start==0 && end==trim.length()) return trim;
|
||||
return start<end?trim.substring(start, end):"";
|
||||
}
|
||||
/** will copy contents of a list into a fixed length array */
|
||||
public static String[] asArray(List<String> all) {
|
||||
if(all==null) return null;
|
||||
String[] ret=new String[all.size()];
|
||||
all.toArray(ret);
|
||||
return ret;
|
||||
}
|
||||
public static String toString(Object...args){
|
||||
StringBuilder buf=new StringBuilder();
|
||||
if(args.length>1){
|
||||
buf.append("[");
|
||||
for(int i=0;i<args.length;i++) buf.append(i==0?"":",").append(toString(args[i]));
|
||||
buf.append("]");
|
||||
}else if(args.length==1){
|
||||
Object arg=args[0];
|
||||
if(arg instanceof Iterable){
|
||||
java.util.Iterator<?> it=((Iterable<?>)arg).iterator();
|
||||
buf.append("[");
|
||||
while(it.hasNext()) buf.append(buf.length()>1?",":"").append(it.next());
|
||||
buf.append("]");
|
||||
}else
|
||||
if(arg instanceof java.util.Map){
|
||||
java.util.Map<?,?> marg=(Map<?,?>) arg;
|
||||
buf.append("{");
|
||||
for(java.util.Map.Entry<?,?>e:marg.entrySet()){
|
||||
buf.append(e.getKey().toString()).append(":").append(toString(e.getValue()));
|
||||
}
|
||||
buf.append("}");
|
||||
}else{
|
||||
buf.append(String.valueOf(arg));
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
/** splitting without using regex.
|
||||
* leading or trailing delims will produce empty tokens.
|
||||
* if you need to split on a set of single chars please use tokenizer.
|
||||
* @param delim delim string
|
||||
* @param str body of text to chop
|
||||
* @param delim_count maximal number of splits or -1 for all
|
||||
* @return array of tokens
|
||||
*/
|
||||
public static String[] split(String delim,String str,int delim_count) {
|
||||
ArrayList<String> ret=new ArrayList<>();
|
||||
int delimLen=delim!=null?delim.length():0;
|
||||
int len=str.length();
|
||||
int index=0;
|
||||
int delimCnt=0; // track splits
|
||||
int delimAt=0; // last delim position
|
||||
while(index<len){
|
||||
if(delim_count>0 && delimCnt>=delim_count) break; // reached limit of delims
|
||||
delimAt=delimLen>0?str.indexOf(delim, index):index+1;
|
||||
if(delimAt<0) break; // no more delims
|
||||
// we got a hit
|
||||
delimCnt+=1;
|
||||
ret.add(str.substring(index, delimAt)); // add token
|
||||
index=delimAt+delimLen;
|
||||
}
|
||||
if(index<len || delimAt>0){
|
||||
// add remainder (no more delim or delim at end)
|
||||
ret.add(str.substring(index));
|
||||
}
|
||||
return ret.toArray(new String[ret.size()]);
|
||||
}
|
||||
/** split a string as often as possible. */
|
||||
public static String[] split(String delim,String str) {
|
||||
return split(delim,str,-1);
|
||||
}
|
||||
@SafeVarargs
|
||||
public static <T> Iterator<T> chainIterators(Iterator<T>...its){
|
||||
return new JointIterator<T>(its);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.reliancy.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/** Chains multiple iterators to act as one.
|
||||
*
|
||||
*/
|
||||
public class JointIterator<T> implements Iterator<T> {
|
||||
final Iterator<T> iterators[];
|
||||
int cursor;
|
||||
@SafeVarargs
|
||||
public JointIterator(Iterator<T> ...its){
|
||||
this.iterators=its;
|
||||
cursor=0;
|
||||
}
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
while(cursor<iterators.length){
|
||||
if(iterators[cursor].hasNext()) return true;
|
||||
cursor+=1; // cursor exhausted got to next iterator
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if(cursor<iterators.length){
|
||||
return iterators[cursor].next();
|
||||
}else{
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package com.reliancy.util;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
/** Least recently used cache is a useful map of sorts.
|
||||
* It has a fixed capacity and it forgets least used entries if new are added.
|
||||
* If an allocator is installed it is consulted on cache miss.
|
||||
* If a disposer is installed is is consulted on cache overflow.
|
||||
* We can provide the same object that implements both and make use of a pool.
|
||||
*/
|
||||
public class LRUCache<K,V>{
|
||||
public static interface Allocator<K,V>{
|
||||
V request(K key);
|
||||
}
|
||||
public static interface Disposer<K,V>{
|
||||
void release(K key,V val);
|
||||
}
|
||||
final Map<K,V> data;
|
||||
int capacity;
|
||||
final LinkedList<K> order=new LinkedList<>();
|
||||
Allocator<K,V> allocator;
|
||||
Disposer<K,V> disposer;
|
||||
|
||||
public LRUCache(int capacity,Map<K,V> backend){
|
||||
this.capacity=capacity;
|
||||
data=backend!=null?backend:new HashMap<K,V>();
|
||||
}
|
||||
public LRUCache(int capacity){
|
||||
this(capacity,null);
|
||||
}
|
||||
public LRUCache<K,V> setAllocator(Allocator<K,V> a){
|
||||
allocator=a;
|
||||
return this;
|
||||
}
|
||||
public LRUCache<K,V> setDisposer(Disposer<K,V> a){
|
||||
disposer=a;
|
||||
return this;
|
||||
}
|
||||
public int size() {
|
||||
return data.size();
|
||||
}
|
||||
public boolean containsKey(Object key) {
|
||||
return data.containsKey(key);
|
||||
}
|
||||
public boolean containsValue(Object value) {
|
||||
return data.containsValue(value);
|
||||
}
|
||||
public V get(K key) {
|
||||
V ret=data.get(key);
|
||||
if(ret!=null){
|
||||
//cache is hit
|
||||
order.remove(key);
|
||||
order.addFirst(key);
|
||||
}else{
|
||||
//cache is missed
|
||||
ret=allocator!=null?allocator.request(key):null;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
public V put(K key, V value) {
|
||||
if(order.size()>=capacity){
|
||||
// capacity is reached
|
||||
K last=order.removeLast();
|
||||
data.remove(last);
|
||||
if(disposer!=null) disposer.release(key, value);
|
||||
}
|
||||
order.addFirst(key);
|
||||
return data.put(key,value);
|
||||
}
|
||||
public V remove(Object key) {
|
||||
order.remove(key);
|
||||
return data.remove(key);
|
||||
}
|
||||
public void clear() {
|
||||
order.clear();
|
||||
data.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.reliancy.util;
|
||||
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogManager;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/** Logging support based on JUL.
|
||||
* We implement a deferred logmanager that survives shutdownhook until we release.
|
||||
*/
|
||||
public class Log {
|
||||
static {
|
||||
// must be called before any Logger method is used.
|
||||
System.setProperty("java.util.logging.manager", DeferredMgr.class.getName());
|
||||
System.setProperty("java.util.logging.SimpleFormatter.format","%1$tF %1$tT %4$-7s [%3$s] %5$s%6$s%n");
|
||||
}
|
||||
public static class DeferredMgr extends LogManager {
|
||||
@Override public void reset() { /* don't reset yet. */ }
|
||||
private void resetFinally() { super.reset(); }
|
||||
}
|
||||
public static Logger setup(){
|
||||
Logger root_logger=Logger.getLogger("");
|
||||
return root_logger;
|
||||
}
|
||||
public static void cleanup(){
|
||||
LogManager mgr=LogManager.getLogManager();
|
||||
if(mgr instanceof DeferredMgr){
|
||||
((DeferredMgr)mgr).resetFinally();
|
||||
}
|
||||
}
|
||||
public static void setLevel(Logger logger,String level_name){
|
||||
if(level_name==null || level_name.isEmpty()) level_name="ERROR";
|
||||
level_name=level_name.toUpperCase();
|
||||
switch(level_name){
|
||||
case "v":{
|
||||
level_name="WARN";
|
||||
break;
|
||||
}
|
||||
case "vv":{
|
||||
level_name="INFO";
|
||||
break;
|
||||
}
|
||||
case "vvv":{
|
||||
level_name="DEBUG";
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch(level_name){
|
||||
case "WARN":{
|
||||
level_name="WARNING";
|
||||
break;
|
||||
}
|
||||
case "DEBUG":{
|
||||
level_name="FINER";
|
||||
break;
|
||||
}
|
||||
case "ERROR":{
|
||||
level_name="SEVERE";
|
||||
break;
|
||||
}
|
||||
}
|
||||
Level lvl=Level.parse(level_name);
|
||||
logger.setLevel(lvl);
|
||||
for (Handler h : logger.getHandlers()) {
|
||||
h.setLevel(lvl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,440 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
package com.reliancy.util;
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
|
||||
/** Path to a resource almost identical to a URL.
|
||||
* It might but should not hold any handles. It holds the address and
|
||||
* possibly takes care of looking up addresses.
|
||||
* The richest syntax is:
|
||||
* <pre> {@code PROTOCOL://USER:PWD@MACHINE:PORT/DATABASE?key=val&... } </pre>
|
||||
* Properties are held in their own string and need to be decoded.
|
||||
*
|
||||
* We use forward slash for path delimitation of database portion. For the rest we preserve other slashes to allow windows domain\\user or server\\instance.
|
||||
* Special chars are [/@?,:;]
|
||||
* if any are found at the end of protocol we skip ://
|
||||
* if any are found at the beginning of properties we skip ?
|
||||
* if any are found at the beginning of database we skip /
|
||||
*
|
||||
* In windows we have volume or drive letters which are postfixed by colon : then slashes. We treat the volume as part of database.
|
||||
* We store the database without first slash to allow specification of relative paths.
|
||||
* To render it as absolute set protocol or host to empty string instead of null. Then a slash will be prefixed and you will get absolute path.
|
||||
* @author amer
|
||||
*/
|
||||
public class Path {
|
||||
static final String SYMBOLS="/@?,:;";
|
||||
String connectstring;
|
||||
String protocol; ///< protocol guides the interpretation of the other elements in conn, string
|
||||
String userid; ///< authentication
|
||||
String password; ///< authorization
|
||||
String host; ///< machine or computer
|
||||
String port; ///< access to computer
|
||||
String database; ///< name of database or filename
|
||||
String properties; ///< properties are what follows ? in a url
|
||||
|
||||
public Path(String connect,boolean do_parse) {
|
||||
if(do_parse){
|
||||
parse(connect);
|
||||
}else{
|
||||
connectstring=connect;
|
||||
}
|
||||
}
|
||||
public Path(String connect) {
|
||||
this(connect,true);
|
||||
}
|
||||
|
||||
public Path(Path in) {
|
||||
connectstring=in.connectstring;
|
||||
protocol=in.protocol;
|
||||
userid=in.userid;
|
||||
password=in.password;
|
||||
host=in.host;
|
||||
port=in.port;
|
||||
database=in.database;
|
||||
properties=in.properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return assemble();
|
||||
}
|
||||
/**
|
||||
* Converts the path to a file.
|
||||
* If absolute is true
|
||||
* @param absolute if true forms absolute path else will return relative path
|
||||
* @return
|
||||
*/
|
||||
public File toFile(boolean absolute){
|
||||
String proto=getProtocol();
|
||||
String host=getHost();
|
||||
try{
|
||||
setProtocol(absolute?"":null);
|
||||
setHost(absolute?"":null);
|
||||
String path=toString();
|
||||
return new File(path);
|
||||
}finally{
|
||||
setProtocol(proto);
|
||||
setHost(host);
|
||||
}
|
||||
}
|
||||
public URL toURL() throws MalformedURLException{
|
||||
String path=toString();
|
||||
if(Handy.isBlank(getHost()) && path.contains("://") && !path.contains(":///")) path=path.replace("://",":///");
|
||||
return new URL(path);
|
||||
}
|
||||
public Path clear(){
|
||||
connectstring=null;
|
||||
protocol=null;
|
||||
userid=null;
|
||||
password=null;
|
||||
host=null;
|
||||
port=null;
|
||||
database=null;
|
||||
properties=null;
|
||||
return this;
|
||||
}
|
||||
public String assemble() {
|
||||
if(connectstring!=null) return connectstring;
|
||||
// assemble the connect string
|
||||
StringBuilder buf=new StringBuilder();
|
||||
//boolean absolute=false;
|
||||
if(!Handy.isBlank(protocol)){
|
||||
buf.append(protocol);
|
||||
if(SYMBOLS.indexOf(protocol.charAt(protocol.length()-1))<0) buf.append("://");
|
||||
}
|
||||
if(!Handy.isBlank(host)){
|
||||
if(userid!=null && password!=null){
|
||||
buf.append(userid).append(":").append(password).append("@");
|
||||
}
|
||||
buf.append(host);
|
||||
if(port!=null) buf.append(":").append(port);
|
||||
}
|
||||
if(!Handy.isBlank(database)){
|
||||
if(buf.length()>0 && SYMBOLS.indexOf(database.charAt(0))<0){
|
||||
// we got something in front so we need to use slash
|
||||
buf.append("/");
|
||||
}else if(protocol!=null || host!=null){
|
||||
boolean winvol=database.length()>2 && database.charAt(1)==':' && (database.charAt(2)=='/' || database.charAt(2)=='\\');
|
||||
// we got nothing in front but if host or protocol empty but not null we treat as absolute
|
||||
if(!winvol) buf.append("/");
|
||||
}
|
||||
buf.append(database);
|
||||
}
|
||||
if(properties!=null){
|
||||
if(SYMBOLS.indexOf(properties.charAt(0))<0) buf.append("?");
|
||||
buf.append(properties);
|
||||
}
|
||||
connectstring=buf.toString();
|
||||
return connectstring;
|
||||
}
|
||||
|
||||
public Path parse(String connect) {
|
||||
clear();
|
||||
if (connect == null) {
|
||||
return this;
|
||||
}
|
||||
this.connectstring=connect;
|
||||
// first get protocol - everything up to : which is not followed by a symbol (includes :// but also c:/
|
||||
int oldst=0;
|
||||
int st=0;
|
||||
for(int i=0;i<(connectstring.length()-1);i++){
|
||||
char curr=connectstring.charAt(i);
|
||||
if(curr==':'){
|
||||
oldst=st;
|
||||
st=i;
|
||||
}else if(SYMBOLS.indexOf(curr)!=-1){
|
||||
if(curr=='@') st=oldst; // this will back out one : if protocl search ended with @ indicating a server
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(st==1){ st=0;} // this will supress single letter protocols i.e. c:/ ued in windows as part of database/file
|
||||
if(2==(st-oldst)) st=oldst;
|
||||
if(st>0){
|
||||
this.protocol=connectstring.substring(0,st);
|
||||
while(SYMBOLS.indexOf(connectstring.charAt(st))!=-1) st++; // advance over symbols
|
||||
}
|
||||
// next assume the rest is a file/database
|
||||
database = connectstring.substring(st);
|
||||
// now check for user id and password
|
||||
st = database.indexOf('@');
|
||||
boolean checkhost=st>=0;
|
||||
if(!Handy.isBlank(protocol)){
|
||||
checkhost=!protocol.contains(":file") && !protocol.contains(":mem") && !protocol.equals("file") && !protocol.equals("mem");;
|
||||
}
|
||||
if (st != -1) {
|
||||
userid = database.substring(0, st);
|
||||
if(userid.contains("%4")) try{userid=URLDecoder.decode(userid,"UTF-8");}catch(Exception e){}
|
||||
database = database.substring(st + 1);
|
||||
// now try to split user id into password if possible
|
||||
st = userid.indexOf(':');
|
||||
if (st != -1) {
|
||||
password = userid.substring(st + 1);
|
||||
userid = userid.substring(0, st);
|
||||
}
|
||||
}
|
||||
// ok next try to split up machine if possible (only for absolute urls)
|
||||
st = database.indexOf(':');
|
||||
if(st<0) st=database.indexOf('/');
|
||||
if (st != -1 && checkhost) {
|
||||
boolean portfollows=database.charAt(st)==':';
|
||||
host = database.substring(0, st);
|
||||
// now try to recover port
|
||||
if(portfollows){
|
||||
int st2 = database.indexOf(':',st+1);
|
||||
if(st2<0) st2 = database.indexOf('/',st+1);
|
||||
if (st2 != -1 && st2>(st+1)) { // we have a port
|
||||
port = database.substring(st + 1,st2);
|
||||
st=st2;
|
||||
}else{
|
||||
// no port we have : then / - which is used in windows to indicate volume and we treat as part of database
|
||||
st=-1;
|
||||
host="";
|
||||
}
|
||||
}
|
||||
database = database.substring(st + 1);
|
||||
}
|
||||
database=fixSlashes(database);
|
||||
// finally split the properties from database
|
||||
st = database.indexOf('?');
|
||||
if(st==-1) st=database.indexOf(';');
|
||||
if (st != -1) { // we have properties
|
||||
properties = database.substring(st);
|
||||
database = database.substring(0, st);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute ResourcePath will have a protocol
|
||||
*/
|
||||
public boolean isAbsolute() {
|
||||
return (protocol != null || host!=null);
|
||||
}
|
||||
/// will clear host and protocol using empty string thereby making database absolute path
|
||||
public Path setAbsolute(){
|
||||
setHost("");
|
||||
setProtocol("");
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
||||
public Path setDatabase(String database) {
|
||||
this.database = database;
|
||||
connectstring=null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public Path setHost(String host) {
|
||||
this.host = host;
|
||||
connectstring=null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public Path setPassword(String password) {
|
||||
this.password = password;
|
||||
connectstring=null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public Path setPort(String port) {
|
||||
this.port = port;
|
||||
connectstring=null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public Path setProtocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
connectstring=null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getUserid() {
|
||||
return userid;
|
||||
}
|
||||
|
||||
public Path setUserid(String userid) {
|
||||
this.userid = userid;
|
||||
connectstring=null;
|
||||
return this;
|
||||
}
|
||||
public String getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public Path setProperties(String userid) {
|
||||
this.properties = userid;
|
||||
connectstring=null;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public String getBase() {
|
||||
return Path.getBase(database);
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
return Path.getExtension(database);
|
||||
}
|
||||
|
||||
public String getPathItem() {
|
||||
return Path.getPathItem(database);
|
||||
}
|
||||
|
||||
/** Ensures that we use forward slashes and that single dot is not present mid or and the end.
|
||||
*
|
||||
* @param path a unix or windows or uri path
|
||||
* @return a path with forward slashes
|
||||
*/
|
||||
public static String fixSlashes(String path) {
|
||||
if(path==null || path.length()==0) return path;
|
||||
path=path.replace("\\", "/");
|
||||
path=path.replace("/./","/");
|
||||
while(true){
|
||||
if(path.endsWith("/")) path=path.substring(0,path.length()-1);
|
||||
else if(path.endsWith("/.")) path=path.substring(0,path.length()-2);
|
||||
else break;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/** returns database path given path and file.
|
||||
* We assume the path uses forward backslash for delimitation.
|
||||
*/
|
||||
public static String getBase(String path) {
|
||||
int st1 = path.lastIndexOf('/');
|
||||
int st2 = path.lastIndexOf('\\');
|
||||
int st=st2>st1?st2:st1;
|
||||
if (st == -1) {
|
||||
return null;
|
||||
}
|
||||
return path.substring(0, st);
|
||||
}
|
||||
|
||||
public static String getExtension(String path) {
|
||||
int st=Math.max(path.lastIndexOf('/'),path.lastIndexOf('\\'));
|
||||
int st2 = path.lastIndexOf('.');
|
||||
if (st2 == -1 || (st>0 && st2<st)) {
|
||||
return null;
|
||||
}
|
||||
return path.substring(st2 + 1);
|
||||
}
|
||||
|
||||
public static String getPathItem(String path) {
|
||||
path=path.replace('\\','/');
|
||||
int st11 = path.lastIndexOf('/');
|
||||
int st1=1+st11;
|
||||
int st2 = path.lastIndexOf('.');
|
||||
if (st2 <0) {
|
||||
st2 = path.length();
|
||||
}
|
||||
return path.substring(st1, st2);
|
||||
}
|
||||
/**
|
||||
* Assuming that url starts with base will return string beyond base in url.
|
||||
* @param base
|
||||
* @param url
|
||||
*/
|
||||
public static String getRemainder(String base,String url){
|
||||
if(base.length()>=url.length()) return null;
|
||||
return url.substring(base.length());
|
||||
}
|
||||
/**
|
||||
* unites two paths.
|
||||
* @param base
|
||||
* @param url
|
||||
*/
|
||||
public static String getUnion(String base,String url){
|
||||
if(base==null || base.isEmpty()){
|
||||
return url;
|
||||
}
|
||||
if(url==null || url.isEmpty()){
|
||||
return base;
|
||||
}
|
||||
//if(url.startsWith(base)) return url;
|
||||
if(Handy.indexOf(url,base,0)==0) return url;
|
||||
StringBuilder ret=new StringBuilder();
|
||||
ret.append(base);
|
||||
if(!base.endsWith("/") && !url.startsWith("/")) ret.append("/");
|
||||
if(base.endsWith("/") && url.startsWith("/")) ret.setLength(ret.length()-1);
|
||||
ret.append(url);
|
||||
return ret.toString();
|
||||
}
|
||||
/**
|
||||
* method will split paths used in linux and windows.
|
||||
* in particular for windows it checks if a single letter precedes a colon in which case it considers it a volume
|
||||
* and does not split there.
|
||||
* @param _paths paths joined with colon or semi-colon
|
||||
* @return array of paths
|
||||
*/
|
||||
public static String[] splitPaths(String _paths){
|
||||
String[] paths=_paths.replaceAll("(;|:|^)([a-zA-Z]):","$1$2##").split("[:;]");
|
||||
for (int i = 0; i < paths.length; i++) {
|
||||
String path=paths[i];
|
||||
path = path.replace("##",":");
|
||||
path=path.replace("/./","/");
|
||||
path=path.replace("//","/");
|
||||
path=path.replace("\\.\\","\\");
|
||||
path=path.replace("\\\\","\\");
|
||||
paths[i]=path;
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of key,value pairs in the order they occur in the string str.
|
||||
* @param str
|
||||
*/
|
||||
public static String[] splitProperties(String str) {
|
||||
if(str.startsWith("?")) str=str.substring(1);
|
||||
if(str.startsWith(";")) str=str.substring(1);
|
||||
return str.split("&");
|
||||
}
|
||||
public static String[] splitKeyValue(String str) {
|
||||
String[] t=Handy.split("=",str,1);
|
||||
if(t==null || t.length==0) return null;
|
||||
t[0]=Handy.trim(t[0],"'\"");
|
||||
try {
|
||||
t[1]=URLDecoder.decode(t[1],"UTF-8");
|
||||
t[1]=Handy.trim(t[1],"'\"");
|
||||
return t;
|
||||
} catch (Exception e) {
|
||||
if(t.length<2){
|
||||
return new String[]{t[0],null};
|
||||
}else{
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static String[] split(String str) {
|
||||
return Handy.split("/",str);
|
||||
}
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
package com.reliancy.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/** Static utility with helper methods to read or write resources.
|
||||
* The place where we host a global search path often used by others
|
||||
* such as Template or FileServe (unless overriden.)
|
||||
*/
|
||||
public class Resources {
|
||||
public static interface PathRewrite{
|
||||
public String rewritePath(String path,Object context);
|
||||
}
|
||||
public static Object[] search_path;
|
||||
/** appends one+ paths to search at position pos.
|
||||
* neg pos substracts from end
|
||||
*/
|
||||
public static Object[] appendSearch(int pos,Object ...src){
|
||||
//search_history.clear();
|
||||
if(search_path==null) search_path=new Object[0];
|
||||
if(pos<0) pos=search_path.length+pos+1;
|
||||
if(pos<0 || pos>search_path.length) throw new IndexOutOfBoundsException("at:"+pos);
|
||||
Object[] new_path=new Object[search_path.length+src.length];
|
||||
// first left side of old search path
|
||||
if(pos>0){
|
||||
System.arraycopy(search_path, 0, new_path, 0, pos);
|
||||
}
|
||||
// next new sources
|
||||
if(src.length>0){
|
||||
System.arraycopy(src, 0, new_path, pos, src.length);
|
||||
}
|
||||
// lastly right side of old search path
|
||||
System.arraycopy(search_path,pos, new_path,pos+src.length, search_path.length-pos);
|
||||
search_path=new_path;
|
||||
return search_path;
|
||||
}
|
||||
/** returns first good URL for path over sp or search_path.
|
||||
* Along the way it optionally rewrites path to adjust to search path context.
|
||||
* When possible it records lastModified timestamp in search_history for later lookup.
|
||||
* @param remap rewrite rule if any
|
||||
* @param path path to locate
|
||||
* @param sp search path reverts to search_path if not specified
|
||||
* @return URL that can be read.
|
||||
*/
|
||||
public static URL findFirst(PathRewrite remap,String path,Object ... sp){
|
||||
String path0=path;
|
||||
if(sp==null || sp.length==0) sp=search_path;
|
||||
for(Object base:sp){
|
||||
if(remap!=null) path=remap.rewritePath(path0,base);
|
||||
if(base instanceof Class){
|
||||
URL ret=((Class<?>)base).getResource(path);
|
||||
return ret;
|
||||
}else if(base instanceof String){
|
||||
File ff=new File(base.toString(),path);
|
||||
if(ff.exists()){
|
||||
try {
|
||||
URL ret=ff.toURI().toURL();
|
||||
//search_history.put(path0,ff.lastModified());
|
||||
return ret;
|
||||
} catch (MalformedURLException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}else if(base instanceof File){
|
||||
File ff=new File((File)base,path);
|
||||
if(ff.exists()){
|
||||
try {
|
||||
URL ret=ff.toURI().toURL();
|
||||
//search_history.put(path,ff.lastModified());
|
||||
return ret;
|
||||
} catch (MalformedURLException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}else if(base instanceof URL){
|
||||
try {
|
||||
URL ret=new URL((URL)base,path);
|
||||
String proto=ret.getProtocol();
|
||||
if(proto.equals("http") || proto.equals("https")){
|
||||
HttpURLConnection huc = null;
|
||||
try{
|
||||
huc=(HttpURLConnection) ret.openConnection();
|
||||
huc.setRequestMethod("HEAD");
|
||||
int responseCode = huc.getResponseCode();
|
||||
if(responseCode==HttpURLConnection.HTTP_OK){
|
||||
//search_history.put(path,huc.getLastModified());
|
||||
return ret;
|
||||
}
|
||||
}finally{
|
||||
if(huc!=null) huc.disconnect();
|
||||
}
|
||||
}
|
||||
if(proto.startsWith("jar")){
|
||||
JarURLConnection juc = null;
|
||||
juc=(JarURLConnection) ret.openConnection();
|
||||
if(juc.getJarEntry()!=null){
|
||||
//search_history.put(path,juc.getLastModified());
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if(proto.equals("file")){
|
||||
File f=new File(ret.getPath());
|
||||
if(f.exists()){
|
||||
//search_history.put(path,f.lastModified());
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
continue;
|
||||
} catch (IOException e2) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/** if recorded in previous searches returns time modified. */
|
||||
// public static Long lastModified(String path){
|
||||
// Long ret=search_history.get(path);
|
||||
// return ret;
|
||||
// }
|
||||
public static String toString(URL url) throws IOException{
|
||||
return toString(url,StandardCharsets.UTF_8);
|
||||
}
|
||||
public static String toString(URL url,Charset chs) throws IOException{
|
||||
try(InputStream is=url.openStream()){
|
||||
return readChars(is,chs).toString();
|
||||
}
|
||||
}
|
||||
public static byte[] toBytes(URL url) throws IOException{
|
||||
try(InputStream is=url.openStream()){
|
||||
return readBytes(is);
|
||||
}
|
||||
}
|
||||
public static long copy(InputStream input, OutputStream output, byte[] buffer) throws IOException {
|
||||
long count = 0;
|
||||
int n = 0;
|
||||
while (-1 != (n = input.read(buffer))) {
|
||||
output.write(buffer, 0, n);
|
||||
count += n;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
public static long copy(Reader input, Writer output, char[] buffer) throws IOException {
|
||||
long count = 0;
|
||||
int n = 0;
|
||||
while (-1 != (n = input.read(buffer))) {
|
||||
output.write(buffer, 0, n);
|
||||
count += n;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Reads a stream in one pass and returns bytes.
|
||||
* Uses internally Handy.copy and a 4K buffer.
|
||||
*/
|
||||
public static final byte[] readBytes(InputStream str) throws IOException{
|
||||
ByteArrayOutputStream bout=new ByteArrayOutputStream();
|
||||
Resources.copy(str, bout, new byte[4096]);
|
||||
return bout.toByteArray();
|
||||
}
|
||||
public static final CharSequence readChars(InputStream str) throws IOException{
|
||||
return readChars(str,StandardCharsets.UTF_8);
|
||||
}
|
||||
public static final CharSequence readChars(InputStream str,Charset chset) throws IOException{
|
||||
BufferedReader rdr=new BufferedReader(new InputStreamReader(str,chset));
|
||||
StringBuilder ret=new StringBuilder();
|
||||
for(String line=rdr.readLine();line!=null;line=rdr.readLine()){
|
||||
ret.append(line).append("\n");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
public static CharSequence readChars(Class<?> cls,String name){
|
||||
InputStream io=cls.getResourceAsStream(name);
|
||||
try{
|
||||
return readChars(io);
|
||||
}catch(Exception e){
|
||||
return null;
|
||||
}finally{
|
||||
if(io!=null) try{io.close();}catch(Exception e){}
|
||||
}
|
||||
}
|
||||
public static void writeChars(CharSequence seq,OutputStream out,Charset chset) throws IOException{
|
||||
OutputStreamWriter dout=new OutputStreamWriter(out,chset);
|
||||
dout.append(seq);
|
||||
dout.flush();
|
||||
}
|
||||
public static void writeChars(CharSequence seq,OutputStream out) throws IOException{
|
||||
writeChars(seq,out,StandardCharsets.UTF_8);
|
||||
}
|
||||
public static void writeBytes(int offset,int len,byte[] seq,OutputStream out) throws IOException{
|
||||
out.write(seq,offset, len);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/** Utility class to handle error codes and error messages.
|
||||
* Error codes are integers that describe outcome of an operation.
|
||||
*
|
||||
* In cases when return codes are used and not exceptions thrown it is a mess to keep track of what they mean.
|
||||
* With this class we define a uniform way of managing return codes and allow for additional information.
|
||||
* This additional information can be text that could be localized and give better user information about what happened.
|
||||
*
|
||||
* First we distinguish between success and failure. Any code that is negative is failure.
|
||||
* Default success code is 0 and provides no additional info. Any positive code is a warning or info and possibly carries extra meaning and
|
||||
* or description.
|
||||
*
|
||||
* success,failure,pending
|
||||
* source
|
||||
* parametric
|
||||
*/
|
||||
public class ResultCode {
|
||||
public static final byte TYPE_SUCCESS=0x0;
|
||||
public static final byte TYPE_PENDING=0x1;
|
||||
public static final byte TYPE_FAILURE=0xF;
|
||||
final int code;
|
||||
final String message;
|
||||
final String source;
|
||||
public ResultCode(byte type,short value,String src,String message) {
|
||||
this.message=message;
|
||||
this.source=src;
|
||||
this.code=ResultCode.getCode(type,value,source!=null?source.hashCode():0);
|
||||
}
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public byte getType() {
|
||||
return getType(code);
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return getValue(code);
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
int code=getCode();
|
||||
String context=getSource();
|
||||
String message=getMessage();
|
||||
if(context!=null){
|
||||
return context+"("+String.format("%08X", code)+"):"+message;
|
||||
}else{
|
||||
return "("+String.format("%08X", code)+"):"+message;
|
||||
}
|
||||
}
|
||||
protected static final HashMap<Integer,ResultCode> codes=new HashMap<>();
|
||||
public static final int getCode(byte type,int value,int source){
|
||||
int st=(type <<28) & 0xF0000000;
|
||||
int sc=(source <<8) & 0x0FFFFF00;
|
||||
int vl=(value) & 0x000000FF;
|
||||
return (int) (st | sc | vl);
|
||||
}
|
||||
public static final byte getType(int code){
|
||||
return (byte)((code>>28) & 0x0F);
|
||||
}
|
||||
public static final boolean testType(int code,byte st){
|
||||
return getType(code)==st;
|
||||
}
|
||||
public static final boolean isSuccess(int code){
|
||||
return testType(code,TYPE_SUCCESS);
|
||||
}
|
||||
public static final boolean isFailure(int code,int st){
|
||||
return testType(code,TYPE_FAILURE);
|
||||
}
|
||||
public static final boolean isPending(int code,int st){
|
||||
return testType(code,TYPE_PENDING);
|
||||
}
|
||||
public static final int getValue(int code){
|
||||
return (int)(code & 0x000000FF);
|
||||
}
|
||||
public static final int getSource(int code){
|
||||
return (int)((code & 0x0FFFFF00)>>8);
|
||||
}
|
||||
public static final synchronized ResultCode get(int code){
|
||||
if(codes==null) return null;
|
||||
return (ResultCode)codes.get(code);
|
||||
}
|
||||
public static final synchronized ResultCode put(ResultCode c){
|
||||
ResultCode old=(ResultCode) codes.get(c.getCode());
|
||||
codes.put(c.getCode(),c);
|
||||
return old;
|
||||
}
|
||||
public static final int define(byte type,int value,Class<?> source,String message){
|
||||
return define(type,value,source!=null?source.getSimpleName():null,message);
|
||||
}
|
||||
public static final int define(byte type,int value,String source,String message){
|
||||
int code=getCode(type,value,source!=null?source.hashCode():0);
|
||||
ResultCode c=get(code);
|
||||
if(c!=null){
|
||||
System.err.println("Result code redefinition(consider different value or source):"+c);
|
||||
return code;
|
||||
}
|
||||
c=new ResultCode(type, (short) value,source,message);
|
||||
put(c);
|
||||
return code;
|
||||
}
|
||||
public static final int defineSuccess(int value,Class<?> source,String message){
|
||||
return define(TYPE_SUCCESS,value,source,message);
|
||||
}
|
||||
public static final int defineFailure(int value,Class<?> source,String message){
|
||||
return define(TYPE_FAILURE,value,source,message);
|
||||
}
|
||||
public static final int definePending(int value,Class<?> source,String message){
|
||||
return define(TYPE_PENDING,value,source,message);
|
||||
}
|
||||
public static final int SUCCESS=ResultCode.defineSuccess(0,null,"Success");
|
||||
public static final int FAILURE=ResultCode.defineFailure(0,null,"Failure");
|
||||
public static final int PENDING=ResultCode.definePending(0,null,"Pending");
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
package com.reliancy.util;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
/** A utility to help us tokenize text along delimChars.
|
||||
* This class is a little better than the java version because it allows for escaped delimChars.
|
||||
* Delimiters are escaped with a slash, also single and double quotes supress delimiting when encountered.
|
||||
* @author amer
|
||||
*/
|
||||
public class Tokenizer implements Iterable<String>,Iterator<String>{
|
||||
public static final String WHITECHARS=" \t\r\f\n";
|
||||
public static final String DELIMCHARS=" ,:;=<>{}[]()";
|
||||
int offset;
|
||||
CharSequence input;
|
||||
String delimChars=DELIMCHARS;
|
||||
String escapeChars="'\"";
|
||||
String whiteChars=WHITECHARS;
|
||||
public Tokenizer(CharSequence input){
|
||||
this.input=input;
|
||||
}
|
||||
public Tokenizer(CharSequence input,int offset){
|
||||
this.input=input;
|
||||
this.offset=offset;
|
||||
}
|
||||
|
||||
public CharSequence getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public Tokenizer setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Tokenizer setInput(CharSequence input) {
|
||||
this.input = input;
|
||||
return this;
|
||||
}
|
||||
public boolean hasMoreTokens(){
|
||||
if(offset>=input.length()) return false;
|
||||
for(int i=offset;i<input.length();i++){
|
||||
char ch=input.charAt(i);
|
||||
if(Tokenizer.isElementOf(ch,whiteChars)==-1) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public String nextToken(){
|
||||
final StringBuilder out=new StringBuilder();
|
||||
if(nextToken(out)){
|
||||
return out.toString();
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public boolean nextToken(StringBuilder out){
|
||||
String[] sets={delimChars,escapeChars,whiteChars};
|
||||
int noffset=nextToken(offset,input,out,sets);
|
||||
if(noffset==offset){
|
||||
return false;
|
||||
}else{
|
||||
offset=noffset;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public String getDelimChars() {
|
||||
return delimChars;
|
||||
}
|
||||
|
||||
public Tokenizer setDelimChars(String delimChars) {
|
||||
this.delimChars = delimChars;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getEscapeChars() {
|
||||
return escapeChars;
|
||||
}
|
||||
|
||||
public Tokenizer setEscapeChars(String escapeChars) {
|
||||
this.escapeChars = escapeChars;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getWhiteChars() {
|
||||
return whiteChars;
|
||||
}
|
||||
|
||||
public Tokenizer setWhiteChars(String whiteChars) {
|
||||
this.whiteChars = whiteChars;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Utility method which collects all tokens and returns an array of them.
|
||||
* Use it for small length strings when parsing user input.
|
||||
* @param withdelims if tru returns delimiters as well
|
||||
*/
|
||||
public String[] getTokens(boolean withdelims){
|
||||
final ArrayList<String> buf=new ArrayList<String>();
|
||||
final StringBuilder out=new StringBuilder();
|
||||
boolean lastSkipped=false;
|
||||
while(this.nextToken(out)){
|
||||
String tok=out.toString();
|
||||
out.setLength(0);
|
||||
if(!withdelims && tok.length()==1 && isElementOf(tok.charAt(0),delimChars)!=-1){
|
||||
if(lastSkipped) buf.add("");
|
||||
lastSkipped=true;
|
||||
continue;
|
||||
}
|
||||
buf.add(tok);
|
||||
lastSkipped=false;
|
||||
}
|
||||
return buf.toArray(new String[buf.size()]);
|
||||
}
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.hasMoreTokens();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
return this.nextToken();
|
||||
}
|
||||
public static int isElementOf(char ch,String d){
|
||||
if(d==null) return -1;
|
||||
return d.indexOf(ch);
|
||||
}
|
||||
/**Returns the next token and updated offset.
|
||||
* This is an inline tokenizer for text parsing and the workhorse of the class.
|
||||
* It stops when it encounters a delimiter. It treats delimChars as tokens too.
|
||||
* It advances the offset whenever it was able to move be it delimiter or not.
|
||||
* We should not have to adjust it for repeated calls except for special cases.
|
||||
* @param offset
|
||||
* @param sets various char sets 0-delimiters,1-escape chars,3-white chars
|
||||
* @param input input chars
|
||||
* @param out value of the token
|
||||
* @return offset after processing
|
||||
*/
|
||||
public static int nextToken(int offset,CharSequence input, StringBuilder out,String[] sets){
|
||||
String delimChars=(sets!=null && sets.length>=1)?sets[0]:",:;=<>{}[]()";
|
||||
String escapeChars=(sets!=null && sets.length>=2)?sets[1]:null;
|
||||
String whiteChars=(sets!=null && sets.length>=3)?sets[2]:null;
|
||||
int escChar=-1; // if not -1 then we are escaping
|
||||
char lastChar=0;
|
||||
char curChar=0;
|
||||
int lastOffset=offset;
|
||||
int isWhiteChar=-1;
|
||||
int isDelimChar=-1;
|
||||
boolean weakEscape=false;
|
||||
int controlCount=0; // counts number of \\ to prevent shortcuit on even number
|
||||
while(offset<input.length()){ // only scan until the end
|
||||
lastChar=curChar;
|
||||
curChar=input.charAt(offset++); // from here on offset is ahead
|
||||
isWhiteChar=isElementOf(curChar,whiteChars);
|
||||
isDelimChar=isElementOf(curChar,delimChars);
|
||||
// determine if we should ignore testing for exit
|
||||
int isEscapeChar=isElementOf(curChar,escapeChars);
|
||||
controlCount=(lastChar=='\\')?controlCount+1:0; // control count counts number of \\ to
|
||||
if((controlCount%2)==1){
|
||||
isDelimChar=isEscapeChar=-1; // shortcircuit delimiting or escaping if prev char was \\ but only unevent number of times
|
||||
}
|
||||
if(escChar==-1){ // should we enter escaping
|
||||
if(isEscapeChar!=-1){
|
||||
// will enter escChar but only once
|
||||
escChar=isEscapeChar;
|
||||
}
|
||||
}else{ // should we exit escaping
|
||||
if(weakEscape==false) isDelimChar=-1; // shortcircuit delim signal if in escape mode and not weak
|
||||
// exit back to normal if escape found second time
|
||||
if(isEscapeChar==escChar){
|
||||
// special rule:if oldchar==curchar and next is not delim or whitespace we ignore escape char
|
||||
boolean isletter=offset<input.length() && !(isElementOf(input.charAt(offset),delimChars)!=-1 || isElementOf(input.charAt(offset),whiteChars)!=-1);
|
||||
if(lastChar==curChar && isletter){
|
||||
// we are special enter weak escaping (where delimiter is not ignored)
|
||||
// this will correct spurios double quotes but will recover forgotter delimiters between two parts
|
||||
// we stay in escape mode but listen for delims
|
||||
weakEscape=true;
|
||||
}else{
|
||||
escChar=-1; // we are exiting escaping
|
||||
}
|
||||
}
|
||||
}
|
||||
// emit chars and test for exit
|
||||
if(escChar>=0 || isWhiteChar==-1 || isDelimChar!=-1){
|
||||
// emit if escaping or if delimiter or not white char
|
||||
out.append(curChar);
|
||||
}
|
||||
if(isDelimChar!=-1) break; // exit delimiter found
|
||||
}
|
||||
if(isDelimChar!=-1 && lastOffset<(offset-1)){
|
||||
// fix end of out to not have a delimiter if it has any other string
|
||||
offset-=1;out.setLength(out.length()-1);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
/**Returns the next token and updated offset.
|
||||
* An improved inline tokenizer using various rules to control delimiting, escaping and text swallowing.
|
||||
* We supply an array of events or if none is provided a default delimiter event is constructed.
|
||||
* After that the events are used to control tokenization. We enter a loop and feed the input to
|
||||
* the events if one or more are armed or triggered (state >=0) we defer emiting chars to output until we determine what to do.
|
||||
* For events that do escape we just defer until end of escape is detected, for delimit we return back and
|
||||
* for supress we just swallow the input without emitting it.
|
||||
*/
|
||||
/*
|
||||
public static int nextToken(TokenizerRule state,int offset,CharSequence input, StringBuilder out){
|
||||
int emitCount=0;
|
||||
int oldOffset=offset;
|
||||
while(offset<input.length()){
|
||||
int st=state.consume(offset, input);
|
||||
st=(st==TokenizerRule.DO_DEFER && offset==(input.length()))?TokenizerRule.DO_EMIT:st;
|
||||
switch(st){
|
||||
case TokenizerRule.DO_EMIT:
|
||||
// we can emit what we got so far
|
||||
if(oldOffset<=offset){
|
||||
offset++;
|
||||
out.append(input,oldOffset,offset);
|
||||
emitCount+=(offset-oldOffset);
|
||||
oldOffset=offset;
|
||||
}
|
||||
break;
|
||||
case TokenizerRule.DO_DEFER:
|
||||
// we need to defer emitting
|
||||
offset++;
|
||||
break;
|
||||
case TokenizerRule.DO_SKIP:
|
||||
// we just skip over this input
|
||||
offset++;
|
||||
oldOffset=offset;
|
||||
break;
|
||||
case TokenizerRule.DO_EXITBEFORE:
|
||||
if(emitCount>0){
|
||||
offset-=(state.getSize()-1);
|
||||
}
|
||||
case TokenizerRule.DO_EXITAFTER:
|
||||
if(emitCount==0 && oldOffset<=offset){
|
||||
// if there is anything left
|
||||
offset++;
|
||||
out.append(input,oldOffset,offset);
|
||||
emitCount+=(offset-oldOffset);
|
||||
oldOffset=offset;
|
||||
}
|
||||
state.clear();
|
||||
default:
|
||||
return st>=0?st:offset;
|
||||
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
package com.reliancy.dbo;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Date;
|
||||
|
||||
import com.reliancy.rec.JSON;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
public class TerminalTest {
|
||||
@Entity.Info(
|
||||
name="dbo.Maps"
|
||||
)
|
||||
public static class Maps extends DBO{
|
||||
public static Field map_id=Field.Int("Map_id").setPk(true);
|
||||
public static Field map_name=Field.Str("Map_name");
|
||||
public static Field created=Field.DateTime("Created");
|
||||
public static Field active=Field.Bool("Active");
|
||||
static{
|
||||
//Entity.publish(Maps.class);
|
||||
}
|
||||
}
|
||||
@Entity.Info(
|
||||
name="public.securable"
|
||||
)
|
||||
public static class Securable extends DBO{
|
||||
public static Field id=Field.Int("id").setPk(true).setAutoIncrement(true);
|
||||
public static Field kind=Field.Str("kind");
|
||||
public static Field name=Field.Str("name");
|
||||
public static Field display_name=Field.Str("display_name");
|
||||
public static Field created=Field.DateTime("created_on");
|
||||
public static Field is_essential=Field.Bool("is_essential");
|
||||
static{
|
||||
//Entity.publish(Maps.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Entity.Info(
|
||||
name="public.product"
|
||||
)
|
||||
public static class Product extends Securable{
|
||||
public static Field valid_since=Field.DateTime("valid_since");
|
||||
public static Field valid_until=Field.DateTime("valid_until");
|
||||
public static Field short_info=Field.Str("short_info");
|
||||
|
||||
}
|
||||
|
||||
static SQLTerminal t;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeAllTestMethods() {
|
||||
System.out.println("Invoked once before all test methods");
|
||||
String url=System.getenv("DB_URL");
|
||||
System.out.println("DB URL:"+url);
|
||||
t=new SQLTerminal(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* jdbc connectivity
|
||||
* @throws IOException
|
||||
* @throws SQLException
|
||||
*/
|
||||
@Test
|
||||
public void connection() throws IOException, SQLException{
|
||||
try(Connection c=t.getConnection()){
|
||||
System.out.println("Connection:"+c);
|
||||
try (Statement stmt = c.createStatement()) {
|
||||
// use stmt here
|
||||
String sql = "SELECT * from \"dbo\".\"Maps\"";
|
||||
try (ResultSet resultSet = stmt.executeQuery(sql)) {
|
||||
// use resultSet here
|
||||
while (resultSet.next()) {
|
||||
System.out.println("ROw:"+resultSet.getInt("Map_id")+":"+resultSet.getString("Map_name"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@Test
|
||||
public void simpleCRUD() throws IOException, SQLException{
|
||||
System.out.println("SimpleCRUD");
|
||||
try(Action act=t.begin().load(Maps.class).execute()){
|
||||
for(DBO o:act){
|
||||
System.out.println("DBO:"+o);
|
||||
}
|
||||
}
|
||||
Entity.retract(Maps.class);
|
||||
}
|
||||
@Test
|
||||
public void complexCRUD() throws IOException, SQLException{
|
||||
System.out.println("ComplexCRUD");
|
||||
// Reading
|
||||
try(Action act=t.begin().load(Product.class).execute()){
|
||||
for(DBO o:act){
|
||||
System.out.println("DBO:"+o);
|
||||
}
|
||||
}
|
||||
//Saving
|
||||
Product p=new Product();
|
||||
p.setStatus(DBO.Status.USED);
|
||||
Product.id.set(p,35);
|
||||
Product.kind.set(p,Product.class.getSimpleName());
|
||||
Product.name.set(p,"myproduct");
|
||||
Product.created.set(p,new Date());
|
||||
Product.short_info.set(p,"a sweet melody:"+new java.sql.Timestamp(System.currentTimeMillis()));
|
||||
Product.display_name.set(p,"first entry");
|
||||
System.out.println("Update P0:"+JSON.toString(p));
|
||||
t.save(p);
|
||||
System.out.println("Update P1:"+JSON.toString(p));
|
||||
// Creating
|
||||
Product pp=new Product();
|
||||
Product.kind.set(pp,Product.class.getSimpleName());
|
||||
Product.name.set(pp,"myproduct");
|
||||
Product.created.set(pp,new Date());
|
||||
Product.short_info.set(pp,"a sweet melody:");
|
||||
Product.display_name.set(pp,"created entry:"+new java.sql.Timestamp(System.currentTimeMillis()));
|
||||
t.save(pp);
|
||||
System.out.println("Create PP0:"+JSON.toString(pp));
|
||||
pp=t.load(Product.class, Product.id.get(pp,null));
|
||||
System.out.println("Returning:"+pp);
|
||||
// Deleting
|
||||
t.delete(pp);
|
||||
//Entity.retract(Maps.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,9 +8,12 @@ You may not use this file except in compliance with the License.
|
||||
package com.reliancy.jabba;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -69,8 +72,37 @@ public class JettyAppTest {
|
||||
return "no arg response";
|
||||
}
|
||||
}
|
||||
public static class SpaTestApp extends JettyApp {
|
||||
private final String staticRoot;
|
||||
|
||||
public SpaTestApp(String staticRoot) {
|
||||
this.staticRoot = staticRoot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(Config conf) throws Exception {
|
||||
super.configure(conf);
|
||||
Router router = getRouter();
|
||||
if(router == null){
|
||||
router = new Router();
|
||||
setRouter(router);
|
||||
}
|
||||
router.importMethods(this);
|
||||
new FileServer("/","",staticRoot)
|
||||
.setIndexFile("index.html")
|
||||
.setFallbackFile("index.html")
|
||||
.publish(this);
|
||||
router.compile();
|
||||
}
|
||||
|
||||
@Routed(path="/api/ping")
|
||||
public String ping() {
|
||||
return "api pong";
|
||||
}
|
||||
}
|
||||
|
||||
private SimpleTestApp app;
|
||||
private JettyApp app;
|
||||
private Path spaRoot;
|
||||
private int testPort;
|
||||
private String baseUrl;
|
||||
|
||||
@@ -113,6 +145,20 @@ public class JettyAppTest {
|
||||
}
|
||||
app = null;
|
||||
}
|
||||
if(spaRoot != null){
|
||||
try {
|
||||
Files.walk(spaRoot)
|
||||
.sorted((a,b) -> b.compareTo(a))
|
||||
.forEach(path -> {
|
||||
try {
|
||||
Files.deleteIfExists(path);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
}
|
||||
spaRoot = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,6 +197,33 @@ public class JettyAppTest {
|
||||
throw new Exception(errorMsg);
|
||||
}
|
||||
}
|
||||
private HttpURLConnection httpRequest(String method, String path, String accept) throws Exception {
|
||||
URL url = new URL(baseUrl + path);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod(method);
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(5000);
|
||||
if(accept != null){
|
||||
conn.setRequestProperty("Accept", accept);
|
||||
}
|
||||
conn.connect();
|
||||
return conn;
|
||||
}
|
||||
private String readBody(HttpURLConnection conn) throws Exception {
|
||||
BufferedReader reader;
|
||||
if(conn.getResponseCode() >= 400){
|
||||
reader = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
|
||||
}else{
|
||||
reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
}
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while((line = reader.readLine()) != null){
|
||||
response.append(line);
|
||||
}
|
||||
reader.close();
|
||||
return response.toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleStringReturn() throws Exception {
|
||||
@@ -181,5 +254,89 @@ public class JettyAppTest {
|
||||
String result = httpGet("/testNoArg");
|
||||
assertEquals("No-arg method should work", "no arg response", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileServerCanServeSpaIndexAndFallback() throws Exception {
|
||||
if(app != null){
|
||||
app.end();
|
||||
Thread.sleep(300);
|
||||
}
|
||||
spaRoot = Files.createTempDirectory("jabba-spa");
|
||||
Files.writeString(spaRoot.resolve("index.html"), "<html><body>spa shell</body></html>");
|
||||
Files.writeString(spaRoot.resolve("app.js"), "console.log('spa');");
|
||||
|
||||
app = new SpaTestApp(spaRoot.toString());
|
||||
ArgsConfig config = new ArgsConfig();
|
||||
Config.SERVER_PORT.set(config, testPort);
|
||||
config.load();
|
||||
app.begin(config);
|
||||
int attempts = 0;
|
||||
while(!app.isStarted() && attempts < 20){
|
||||
Thread.sleep(100);
|
||||
attempts++;
|
||||
}
|
||||
Thread.sleep(200);
|
||||
|
||||
assertTrue("Root path should serve index file", readBody(httpRequest("GET", "/", "text/html")).contains("spa shell"));
|
||||
assertTrue("Nested frontend route should fallback to index file", readBody(httpRequest("GET", "/dashboard/settings", "text/html")).contains("spa shell"));
|
||||
assertEquals("API route should still reach routed endpoint", "api pong", httpGet("/api/ping"));
|
||||
assertTrue("Direct asset request should serve asset file", httpGet("/app.js").contains("console.log('spa');"));
|
||||
|
||||
HttpURLConnection headRoot = httpRequest("HEAD", "/", "text/html");
|
||||
assertEquals("HEAD should succeed on root file", HttpURLConnection.HTTP_OK, headRoot.getResponseCode());
|
||||
assertEquals("HEAD should report HTML content type", "text/html", headRoot.getContentType());
|
||||
assertNotNull("HEAD should expose ETag", headRoot.getHeaderField("ETag"));
|
||||
assertNotNull("HEAD should expose Last-Modified", headRoot.getHeaderField("Last-Modified"));
|
||||
assertEquals("HEAD should set nosniff", "nosniff", headRoot.getHeaderField("X-Content-Type-Options"));
|
||||
assertEquals("Index should default to no-cache", "no-cache", headRoot.getHeaderField("Cache-Control"));
|
||||
assertTrue("HEAD should report content length", headRoot.getContentLengthLong() > 0);
|
||||
|
||||
HttpURLConnection assetGet = httpRequest("GET", "/app.js", "*/*");
|
||||
assertEquals("Asset should be served successfully", HttpURLConnection.HTTP_OK, assetGet.getResponseCode());
|
||||
assertEquals("Asset should use asset cache policy", "public, max-age=3600", assetGet.getHeaderField("Cache-Control"));
|
||||
assertNotNull("Asset should expose Last-Modified", assetGet.getHeaderField("Last-Modified"));
|
||||
|
||||
HttpURLConnection apiMiss = httpRequest("GET", "/api/missing", "application/json");
|
||||
assertEquals("API-style missing route should stay 404 instead of SPA fallback", HttpURLConnection.HTTP_NOT_FOUND, apiMiss.getResponseCode());
|
||||
|
||||
HttpURLConnection traversal = httpRequest("GET", "/%2E%2E/secret.txt", "text/html");
|
||||
assertEquals("Traversal attempts should be rejected", HttpURLConnection.HTTP_BAD_REQUEST, traversal.getResponseCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileServerCachesSmallAssetsInMemory() throws Exception {
|
||||
Path cacheRoot = Files.createTempDirectory("jabba-cache");
|
||||
try {
|
||||
Files.writeString(cacheRoot.resolve("small.js"), "console.log('small');");
|
||||
StringBuilder large = new StringBuilder();
|
||||
for(int i=0;i<400;i++){
|
||||
large.append("0123456789abcdef");
|
||||
}
|
||||
Files.writeString(cacheRoot.resolve("large.js"), large.toString());
|
||||
|
||||
FileServer server = new FileServer("/", "", cacheRoot.toString())
|
||||
.setMemoryCacheContentLimit(256);
|
||||
FileServer.Bucket bucket = server.getBucket("/");
|
||||
|
||||
FileServer.CachedAsset small = bucket.getAsset("small.js", server);
|
||||
assertNotNull("Small asset should resolve", small);
|
||||
assertNotNull("Small asset should keep bytes in memory", small.content);
|
||||
assertTrue("Small asset length should be tracked", small.contentLength > 0);
|
||||
|
||||
FileServer.CachedAsset largeAsset = bucket.getAsset("large.js", server);
|
||||
assertNotNull("Large asset should resolve", largeAsset);
|
||||
assertNull("Large asset should not keep bytes in memory", largeAsset.content);
|
||||
assertTrue("Large asset length should be tracked", largeAsset.contentLength > 256);
|
||||
} finally {
|
||||
Files.walk(cacheRoot)
|
||||
.sorted((a,b) -> b.compareTo(a))
|
||||
.forEach(path -> {
|
||||
try {
|
||||
Files.deleteIfExists(path);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
package com.reliancy.rec;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ObjTest {
|
||||
/**
|
||||
* Plain CRUD
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
public void crudVec() throws IOException
|
||||
{
|
||||
Obj o=new Obj();
|
||||
Obj a=new Obj(true);
|
||||
System.out.println("O1:"+o);
|
||||
System.out.println("A1:"+a);
|
||||
a.add(1).add("three");
|
||||
o.add(1).add("three").set(new Slot("arr"),new String[]{"a","b","c"});
|
||||
System.out.println("O2meta:"+o.isArray()+"/"+o.meta());
|
||||
System.out.println("O2:"+o);
|
||||
System.out.println("A2:"+a);
|
||||
o.set(o.getSlot("car"),"bar");
|
||||
System.out.println("O3:"+o);
|
||||
StringBuilder json=new StringBuilder();
|
||||
JSON.writes(o,json);
|
||||
System.out.println("ENC:"+json);
|
||||
Rec dec=JSON.reads(json);
|
||||
System.out.println("DEC:"+dec);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011-2022 Reliancy LLC
|
||||
|
||||
Licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3.
|
||||
You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
You may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
package com.reliancy.util;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class HandyTest {
|
||||
/**
|
||||
* Plain CRUD
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
public void splitting() throws IOException
|
||||
{
|
||||
System.out.println("Splitting test...");
|
||||
String tst1="One,Two,Three";
|
||||
System.out.println(tst1+" over ,");
|
||||
for(String s:Handy.split(",",tst1)){
|
||||
System.out.println("\tt:"+s);
|
||||
}
|
||||
//System.out.println(tst1+" over ");
|
||||
//for(String s:Handy.split("",tst1)){
|
||||
// System.out.println("\tt:"+s);
|
||||
//}
|
||||
String tst2="AND A AND B ANDAND D AND";
|
||||
System.out.println(tst2+" over AND");
|
||||
for(String s:Handy.split("AND",tst2)){
|
||||
System.out.println("\tt:"+s);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user