package railo.runtime.converter;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.w3c.dom.Node;
import railo.commons.lang.CFTypes;
import railo.runtime.Component;
import railo.runtime.ComponentScope;
import railo.runtime.ComponentWrap;
import railo.runtime.PageContext;
import railo.runtime.component.Property;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.java.JavaObject;
import railo.runtime.op.Caster;
import railo.runtime.op.Decision;
import railo.runtime.orm.ORMUtil;
import railo.runtime.reflection.Reflector;
import railo.runtime.text.xml.XMLCaster;
import railo.runtime.type.Array;
import railo.runtime.type.Collection;
import railo.runtime.type.Collection.Key;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.ObjectWrap;
import railo.runtime.type.Query;
import railo.runtime.type.Struct;
import railo.runtime.type.StructImpl;
import railo.runtime.type.UDF;
import railo.runtime.type.cfc.ComponentAccess;
import railo.runtime.type.dt.DateTime;
import railo.runtime.type.dt.DateTimeImpl;
import railo.runtime.type.dt.TimeSpan;
import railo.runtime.type.util.ArrayUtil;
import railo.runtime.type.util.CollectionUtil;
import railo.runtime.type.util.ComponentUtil;
/**
* class to serialize and desirilize WDDX Packes
*/
public final class JSONConverter extends ConverterSupport {
private static final Collection.Key REMOTING_FETCH = KeyImpl.intern("remotingFetch");
private static final Key TO_JSON = KeyImpl.intern("_toJson");
private static final Object NULL = new Object();
private static final String NULL_STRING = "";
private boolean ignoreRemotingFetch;
/**
* constructor of the class
*/
public JSONConverter(boolean ignoreRemotingFetch) {
this.ignoreRemotingFetch=ignoreRemotingFetch;
}
/**
* serialize Serializable class
* @param serializable
* @param sb
* @param serializeQueryByColumns
* @param done
* @throws ConverterException
*/
private void _serializeClass(PageContext pc,Set test,Class clazz,Object obj, StringBuffer sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
Struct sct=new StructImpl(Struct.TYPE_LINKED);
if(test==null)test=new HashSet();
// Fields
Field[] fields = clazz.getFields();
Field field;
for(int i=0;i<fields.length;i++){
field=fields[i];
if(obj!=null || (field.getModifiers()&Modifier.STATIC)>0)
try {
sct.setEL(field.getName(), testRecusrion(test,field.get(obj)));
} catch (Exception e) {
e.printStackTrace();
}
}
if(obj !=null){
// setters
Method[] setters=Reflector.getSetters(clazz);
for(int i=0;i<setters.length;i++){
sct.setEL(setters[i].getName().substring(3), NULL);
}
// getters
Method[] getters=Reflector.getGetters(clazz);
for(int i=0;i<getters.length;i++){
try {
sct.setEL(getters[i].getName().substring(3), testRecusrion(test,getters[i].invoke(obj, ArrayUtil.OBJECT_EMPTY)));
}
catch (Exception e) {}
}
}
test.add(clazz);
_serializeStruct(pc,test,sct, sb, serializeQueryByColumns, true,done);
}
private Object testRecusrion(Set test, Object obj) {
if(test.contains(obj.getClass())) return obj.getClass().getName();
return obj;
}
/**
* serialize a Date
* @param date Date to serialize
* @param sb
* @throws ConverterException
*/
private void _serializeDate(Date date, StringBuffer sb) {
_serializeDateTime(new DateTimeImpl(date),sb);
}
/**
* serialize a DateTime
* @param dateTime DateTime to serialize
* @param sb
* @throws ConverterException
*/
private void _serializeDateTime(DateTime dateTime, StringBuffer sb) {
sb.append('"');
//sb.append(escape(dateTime.toString()));
sb.append(escape(JSONDateFormat.format(dateTime,null)));
sb.append('"');
/*try {
sb.append(goIn());
sb.append("createDateTime(");
sb.append(DateFormat.call(null,dateTime,"yyyy,m,d"));
sb.append(' ');
sb.append(TimeFormat.call(null,dateTime,"HH:mm:ss"));
sb.append(')');
}
catch (PageException e) {
throw new ConverterException(e);
}*/
//Januar, 01 2000 01:01:01
}
/**
* serialize a Array
* @param array Array to serialize
* @param sb
* @param serializeQueryByColumns
* @param done
* @throws ConverterException
*/
private void _serializeArray(PageContext pc,Set test,Array array, StringBuffer sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
_serializeList(pc,test,array.toList(),sb,serializeQueryByColumns,done);
}
/**
* serialize a List (as Array)
* @param list List to serialize
* @param sb
* @param serializeQueryByColumns
* @param done
* @throws ConverterException
*/
private void _serializeList(PageContext pc,Set test,List list, StringBuffer sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
sb.append(goIn());
sb.append("[");
boolean doIt=false;
ListIterator it=list.listIterator();
while(it.hasNext()) {
if(doIt)sb.append(',');
doIt=true;
_serialize(pc,test,it.next(),sb,serializeQueryByColumns,done);
}
sb.append(']');
}
private void _serializeArray(PageContext pc,Set test,Object[] arr, StringBuffer sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
sb.append(goIn());
sb.append("[");
for(int i=0;i<arr.length;i++) {
if(i>0)sb.append(',');
_serialize(pc,test,arr[i],sb,serializeQueryByColumns,done);
}
sb.append(']');
}
/**
* serialize a Struct
* @param struct Struct to serialize
* @param sb
* @param serializeQueryByColumns
* @param addUDFs
* @param done
* @throws ConverterException
*/
public void _serializeStruct(PageContext pc,Set test,Struct struct, StringBuffer sb, boolean serializeQueryByColumns, boolean addUDFs, Set<Object> done) throws ConverterException {
// Component
if(struct instanceof Component){
String res = castToJson(pc, (Component)struct, NULL_STRING);
if(res!=NULL_STRING) {
sb.append(res);
return;
}
}
sb.append(goIn());
sb.append("{");
//Key[] keys = struct.keys();
//Key key;
Iterator<Entry<Key, Object>> it = struct.entryIterator();
Entry<Key, Object> e;
Object value;
boolean doIt=false;
while(it.hasNext()) {
e = it.next();
//key=keys[i];
value=e.getValue();
if(!addUDFs && (value instanceof UDF || value==null))continue;
if(doIt)sb.append(',');
doIt=true;
sb.append('"');
sb.append(escape(e.getKey().getString()));
sb.append('"');
sb.append(':');
_serialize(pc,test,value,sb,serializeQueryByColumns,done);
}
if(struct instanceof Component){
Boolean remotingFetch;
Component cp = (Component)struct;
boolean isPeristent=false;
try {
ComponentAccess ca = ComponentUtil.toComponentAccess(cp);
isPeristent=ca.isPersistent();
} catch (ExpressionException ee) {}
Property[] props = cp.getProperties(false);
ComponentScope scope = cp.getComponentScope();
for(int i=0;i<props.length;i++) {
if(!ignoreRemotingFetch) {
remotingFetch=Caster.toBoolean(props[i].getDynamicAttributes().get(REMOTING_FETCH,null),null);
if(remotingFetch==null){
if(isPeristent && ORMUtil.isRelated(props[i])) continue;
}
else if(!remotingFetch.booleanValue()) continue;
}
Key key = KeyImpl.getInstance(props[i].getName());
value=scope.get(key,null);
if(!addUDFs && (value instanceof UDF || value==null))continue;
if(doIt)sb.append(',');
doIt=true;
sb.append('"');
sb.append(escape(key.getString()));
sb.append('"');
sb.append(':');
_serialize(pc,test,value,sb,serializeQueryByColumns,done);
}
}
sb.append('}');
}
private static String castToJson(PageContext pc,Component cfc, String defaultValue) throws ConverterException {
Object o=cfc.get(TO_JSON,null);
if(!(o instanceof UDF)) return defaultValue;
UDF udf=(UDF) o;
if(udf.getReturnType()!=CFTypes.TYPE_VOID && udf.getFunctionArguments().length==0) {
try {
return Caster.toString(cfc.call(pc, TO_JSON, new Object[0]));
} catch (PageException e) {
e.printStackTrace();
throw toConverterException(e);
}
}
return defaultValue;
}
/**
* serialize a Map (as Struct)
* @param map Map to serialize
* @param sb
* @param serializeQueryByColumns
* @param done
* @throws ConverterException
*/
private void _serializeMap(PageContext pc,Set test,Map map, StringBuffer sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
sb.append(goIn());
sb.append("{");
Iterator it=map.keySet().iterator();
boolean doIt=false;
while(it.hasNext()) {
Object key=it.next();
if(doIt)sb.append(',');
doIt=true;
sb.append('"');
sb.append(escape(key.toString()));
sb.append('"');
sb.append(':');
_serialize(pc,test,map.get(key),sb,serializeQueryByColumns,done);
}
sb.append('}');
}
/**
* serialize a Component
* @param component Component to serialize
* @param sb
* @param serializeQueryByColumns
* @param done
* @throws ConverterException
*/
private void _serializeComponent(PageContext pc,Set test,Component component, StringBuffer sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
try {
ComponentWrap cw = ComponentWrap.toComponentWrap(Component.ACCESS_PRIVATE,component);
_serializeStruct(pc,test,cw, sb, serializeQueryByColumns,false,done);
}
catch (ExpressionException e) {
throw toConverterException(e);
}
}
private void _serializeUDF(PageContext pc,Set test,UDF udf, StringBuffer sb,boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
Struct sct=new StructImpl();
try {
// Meta
Struct meta = udf.getMetaData(pc);
sct.setEL("Metadata", meta);
// Parameters
sct.setEL("MethodAttributes", meta.get("PARAMETERS"));
}
catch (PageException e) {
throw toConverterException(e);
}
sct.setEL("Access", ComponentUtil.toStringAccess(udf.getAccess(),"public"));
sct.setEL("Output", Caster.toBoolean(udf.getOutput()));
sct.setEL("ReturnType", udf.getReturnTypeAsString());
try{
sct.setEL("PagePath", udf.getPageSource().getResource().getAbsolutePath());
}catch(Throwable t){}
_serializeStruct(pc,test,sct, sb, serializeQueryByColumns, true,done);
// TODO key SuperScope and next?
}
/**
* serialize a Query
* @param query Query to serialize
* @param sb
* @param serializeQueryByColumns
* @param done
* @throws ConverterException
*/
private void _serializeQuery(PageContext pc,Set test,Query query, StringBuffer sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
Collection.Key[] _keys = CollectionUtil.keys(query);
sb.append(goIn());
sb.append("{");
/*
{"DATA":[["a","b"],["c","d"]]}
{"DATA":{"aaa":["a","c"],"bbb":["b","d"]}}
* */
// Rowcount
if(serializeQueryByColumns){
sb.append("\"ROWCOUNT\":");
sb.append(Caster.toString(query.getRecordcount()));
sb.append(',');
}
// Columns
sb.append("\"COLUMNS\":[");
String[] cols = query.getColumns();
for(int i=0;i<cols.length;i++) {
if(i>0)sb.append(",\"");
else sb.append('"');
sb.append(escape(cols[i].toUpperCase()));
sb.append('"');
}
sb.append("],");
// Data
sb.append("\"DATA\":");
if(serializeQueryByColumns) {
sb.append('{');
boolean oDoIt=false;
int len=query.getRecordcount();
for(int i=0;i<_keys.length;i++) {
if(oDoIt)sb.append(',');
oDoIt=true;
sb.append(goIn());
sb.append('"');
sb.append(escape(_keys[i].getString()));
sb.append('"');
sb.append(":[");
boolean doIt=false;
for(int y=1;y<=len;y++) {
if(doIt)sb.append(',');
doIt=true;
try {
_serialize(pc,test,query.getAt(_keys[i],y),sb,serializeQueryByColumns,done);
} catch (PageException e) {
_serialize(pc,test,e.getMessage(),sb,serializeQueryByColumns,done);
}
}
sb.append(']');
}
sb.append('}');
}
else {
sb.append('[');
boolean oDoIt=false;
int len=query.getRecordcount();
for(int row=1;row<=len;row++) {
if(oDoIt)sb.append(',');
oDoIt=true;
sb.append("[");
boolean doIt=false;
for(int col=0;col<_keys.length;col++) {
if(doIt)sb.append(',');
doIt=true;
try {
_serialize(pc,test,query.getAt(_keys[col],row),sb,serializeQueryByColumns,done);
} catch (PageException e) {
_serialize(pc,test,e.getMessage(),sb,serializeQueryByColumns,done);
}
}
sb.append(']');
}
sb.append(']');
}
sb.append('}');
}
/**
* serialize a Object to his xml Format represenation
* @param object Object to serialize
* @param sb StringBuffer to write data
* @param serializeQueryByColumns
* @param done
* @throws ConverterException
*/
private void _serialize(PageContext pc,Set test,Object object, StringBuffer sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
// NULL
if(object==null || object==NULL) {
sb.append(goIn());
sb.append("null");
return;
}
// String
if(object instanceof String || object instanceof StringBuffer) {
sb.append(goIn());
sb.append('"');
sb.append(escape(object.toString()));
sb.append('"');
return;
}
// Character
if(object instanceof Character) {
sb.append(goIn());
sb.append('"');
sb.append(escape(String.valueOf(((Character)object).charValue())));
sb.append('"');
return;
}
// Number
if(object instanceof Number) {
sb.append(goIn());
sb.append(Caster.toString(((Number)object)));
return;
}
// Boolean
if(object instanceof Boolean) {
sb.append(goIn());
sb.append(Caster.toString(((Boolean)object).booleanValue()));
return;
}
// DateTime
if(object instanceof DateTime) {
_serializeDateTime((DateTime)object,sb);
return;
}
// Date
if(object instanceof Date) {
_serializeDate((Date)object,sb);
return;
}
// XML
if(object instanceof Node) {
_serializeXML((Node)object,sb);
return;
}
// Timespan
if(object instanceof TimeSpan) {
_serializeTimeSpan((TimeSpan) object,sb);
return;
}
// File
if(object instanceof File) {
_serialize(pc,test, ((File)object).getAbsolutePath(), sb, serializeQueryByColumns,done);
return;
}
// String Converter
if(object instanceof ScriptConvertable) {
sb.append(((ScriptConvertable)object).serialize());
return;
}
Object raw = LazyConverter.toRaw(object);
if(done.contains(raw)){
sb.append(goIn());
sb.append("null");
return;
}
done.add(raw);
try{
// Component
if(object instanceof Component) {
_serializeComponent(pc,test,(Component)object,sb,serializeQueryByColumns,done);
return;
}
// UDF
if(object instanceof UDF) {
_serializeUDF(pc,test,(UDF)object,sb,serializeQueryByColumns,done);
return;
}
// Struct
if(object instanceof Struct) {
_serializeStruct(pc,test,(Struct)object,sb,serializeQueryByColumns,true,done);
return;
}
// Map
if(object instanceof Map) {
_serializeMap(pc,test,(Map)object,sb,serializeQueryByColumns,done);
return;
}
// Array
if(object instanceof Array) {
_serializeArray(pc,test,(Array)object,sb,serializeQueryByColumns,done);
return;
}
// List
if(object instanceof List) {
_serializeList(pc,test,(List)object,sb,serializeQueryByColumns,done);
return;
}
// Query
if(object instanceof Query) {
_serializeQuery(pc,test,(Query)object,sb,serializeQueryByColumns,done);
return;
}
// Native Array
if(Decision.isNativeArray(object)){
if(object instanceof char[])
_serialize(pc,test,new String((char[])object), sb, serializeQueryByColumns,done);
else {
_serializeArray(pc,test,ArrayUtil.toReferenceType(object,ArrayUtil.OBJECT_EMPTY), sb, serializeQueryByColumns,done);
}
return;
}
// ObjectWrap
if(object instanceof ObjectWrap) {
try {
_serialize(pc,test,((ObjectWrap)object).getEmbededObject(), sb, serializeQueryByColumns,done);
} catch (PageException e) {
if(object instanceof JavaObject){
_serializeClass(pc,test,((JavaObject)object).getClazz(),null,sb,serializeQueryByColumns,done);
}
else throw new ConverterException("can't serialize Object of type [ "+Caster.toClassName(object)+" ]");
}
return;
}
_serializeClass(pc,test,object.getClass(),object,sb,serializeQueryByColumns,done);
}
finally{
done.remove(raw);
}
}
private void _serializeXML(Node node, StringBuffer sb) {
node=XMLCaster.toRawNode(node);
sb.append(goIn());
sb.append('"');
sb.append(escape(XMLCaster.toString(node,"")));
sb.append('"');
}
private void _serializeTimeSpan(TimeSpan span, StringBuffer sb) {
sb.append(goIn());
sb.append("createTimeSpan(");
sb.append(span.getDay());
sb.append(',');
sb.append(span.getHour());
sb.append(',');
sb.append(span.getMinute());
sb.append(',');
sb.append(span.getSecond());
sb.append(')');
}
public static String escape(String str) {
char[] arr=str.toCharArray();
StringBuffer rtn=new StringBuffer(arr.length);
for(int i=0;i<arr.length;i++) {
if(arr[i] < 128){
switch(arr[i]) {
case '\\': rtn.append("\\\\"); break;
case '/': rtn.append("\\/"); break;
case '\n': rtn.append("\\n"); break;
case '\r': rtn.append("\\r"); break;
case '\f': rtn.append("\\f"); break;
case '\b': rtn.append("\\b"); break;
case '\t': rtn.append("\\t"); break;
case '"' : rtn.append("\\\""); break;
default : rtn.append(arr[i]); break;
}
}
else {
if (arr[i] < 0x10) rtn.append("\\u000");
else if (arr[i] < 0x100) rtn.append( "\\u00");
else if (arr[i] < 0x1000) rtn.append( "\\u0");
else rtn.append( "\\u");
rtn.append(Integer.toHexString(arr[i]));
}
}
return rtn.toString();
}
/**
* serialize a Object to his literal Format
* @param object Object to serialize
* @param serializeQueryByColumns
* @return serialized wddx package
* @throws ConverterException
*/
public String serialize(PageContext pc,Object object, boolean serializeQueryByColumns) throws ConverterException {
StringBuffer sb=new StringBuffer();
_serialize(pc,null,object,sb,serializeQueryByColumns,new HashSet<Object>());
return sb.toString();
}
@Override
public void writeOut(PageContext pc, Object source, Writer writer) throws ConverterException, IOException {
writer.write(serialize(pc,source,false));
writer.flush();
}
/**
* @return return current blockquote
*/
private String goIn() {
return "";
}
}