001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019 020package lucee.runtime.converter; 021 022import java.io.IOException; 023import java.io.Writer; 024import java.util.Calendar; 025import java.util.HashSet; 026import java.util.Iterator; 027import java.util.List; 028import java.util.ListIterator; 029import java.util.Map; 030import java.util.Map.Entry; 031import java.util.Set; 032 033import lucee.commons.date.JREDateTimeUtil; 034import lucee.commons.lang.StringUtil; 035import lucee.runtime.PageContext; 036import lucee.runtime.engine.ThreadLocalPageContext; 037import lucee.runtime.op.Caster; 038import lucee.runtime.op.Decision; 039import lucee.runtime.type.Array; 040import lucee.runtime.type.Collection; 041import lucee.runtime.type.Collection.Key; 042import lucee.runtime.type.Query; 043import lucee.runtime.type.Struct; 044import lucee.runtime.type.dt.DateTime; 045import lucee.runtime.type.util.CollectionUtil; 046 047 048/** 049 * class to serialize to Convert CFML Objects (query,array,struct usw) to a JavaScript representation 050 */ 051public final class JSConverter extends ConverterSupport { 052 053 private static final String NULL = "null"; 054 private boolean useShortcuts=false; 055 private boolean useWDDX=true; 056 057 /** 058 * serialize a CFML object to a JavaScript Object 059 * @param object object to serialize 060 * @param clientVariableName name of the variable to create 061 * @return vonverte Javascript Code as String 062 * @throws ConverterException 063 */ 064 public String serialize(Object object, String clientVariableName) throws ConverterException { 065 StringBuilder sb=new StringBuilder(); 066 _serialize(clientVariableName,object,sb,new HashSet<Object>()); 067 String str = sb.toString().trim(); 068 return clientVariableName+"="+str+(StringUtil.endsWith(str, ';')?"":";"); 069 //return sb.toString(); 070 } 071 072 073 074 075 @Override 076 public void writeOut(PageContext pc, Object source, Writer writer) throws ConverterException, IOException { 077 writer.write(_serialize(source)); 078 writer.flush(); 079 } 080 private String _serialize(Object object) throws ConverterException { 081 StringBuilder sb=new StringBuilder(); 082 _serialize("tmp",object,sb,new HashSet<Object>()); 083 String str = sb.toString().trim(); 084 return str+(StringUtil.endsWith(str, ';')?"":";"); 085 //return sb.toString(); 086 } 087 088 089 090 private void _serialize(String name,Object object,StringBuilder sb,Set<Object> done) throws ConverterException { 091 // NULL 092 if(object==null) { 093 sb.append(goIn()); 094 sb.append(NULL+";"); 095 return; 096 } 097 // CharSequence (String, StringBuilder ...) 098 if(object instanceof CharSequence) { 099 sb.append(goIn()); 100 sb.append(StringUtil.escapeJS(object.toString(),'"')); 101 sb.append(";"); 102 return; 103 } 104 // Number 105 if(object instanceof Number) { 106 sb.append(goIn()); 107 sb.append("\""); 108 sb.append(Caster.toString(((Number)object))); 109 sb.append("\";"); 110 return; 111 } 112 // Date 113 if(Decision.isDateSimple(object,false)) { 114 _serializeDateTime(Caster.toDate(object,false,null,null),sb); 115 return; 116 } 117 // Boolean 118 if(object instanceof Boolean) { 119 sb.append(goIn()); 120 sb.append("\""); 121 sb.append((((Boolean)object).booleanValue()?"true":"false")); 122 sb.append("\";"); 123 return; 124 } 125 126 Object raw = LazyConverter.toRaw(object); 127 if(done.contains(raw)){ 128 sb.append(NULL+";"); 129 return; 130 } 131 done.add(raw); 132 try { 133 // Struct 134 if(object instanceof Struct) { 135 _serializeStruct(name,(Struct)object,sb,done); 136 return; 137 } 138 // Map 139 if(object instanceof Map) { 140 _serializeMap(name,(Map)object,sb,done); 141 return; 142 } 143 // List 144 if(object instanceof List) { 145 _serializeList(name,(List)object,sb,done); 146 return; 147 } 148 // Array 149 if(Decision.isArray(object)) { 150 _serializeArray(name,Caster.toArray(object,null),sb,done); 151 return; 152 } 153 // Query 154 if(object instanceof Query) { 155 _serializeQuery(name,(Query)object,sb,done); 156 return; 157 } 158 } 159 finally { 160 done.remove(raw); 161 } 162 163 throw new ConverterException("can't serialize Object of type ["+Caster.toClassName(object)+"] to a js representation"); 164 //deep--; 165 //return rtn; 166 } 167 168 169 170 /** 171 * serialize a Array 172 * @param name 173 * @param array Array to serialize 174 * @param sb 175 * @param done 176 * @return serialized array 177 * @throws ConverterException 178 */ 179 private void _serializeArray(String name, Array array, StringBuilder sb, Set<Object> done) throws ConverterException { 180 _serializeList(name,array.toList(),sb,done); 181 } 182 183 /** 184 * serialize a List (as Array) 185 * @param name 186 * @param list List to serialize 187 * @param sb 188 * @param done 189 * @return serialized list 190 * @throws ConverterException 191 */ 192 private void _serializeList(String name, List list, StringBuilder sb, Set<Object> done) throws ConverterException { 193 194 195 if(useShortcuts)sb.append("[];"); 196 else sb.append("new Array();"); 197 198 ListIterator it=list.listIterator(); 199 int index=-1; 200 while(it.hasNext()) { 201 //if(index!=-1)sb.append(","); 202 index = it.nextIndex(); 203 sb.append(name+"["+index+"]="); 204 _serialize(name+"["+index+"]",it.next(),sb,done); 205 //sb.append(";"); 206 } 207 } 208 209 /** 210 * serialize a Struct 211 * @param name 212 * @param struct Struct to serialize 213 * @param done 214 * @param sb2 215 * @return serialized struct 216 * @throws ConverterException 217 */ 218 private String _serializeStruct(String name, Struct struct, StringBuilder sb, Set<Object> done) throws ConverterException { 219 if(useShortcuts)sb.append("{};"); 220 else sb.append("new Object();"); 221 222 Iterator<Entry<Key, Object>> it = struct.entryIterator(); 223 Entry<Key, Object> e; 224 while(it.hasNext()) { 225 e = it.next(); 226 // lower case ist ok! 227 String key=StringUtil.escapeJS(Caster.toString(e.getKey().getLowerString(),""),'"'); 228 sb.append(name+"["+key+"]="); 229 _serialize(name+"["+key+"]",e.getValue(),sb,done); 230 } 231 return sb.toString(); 232 } 233 234 /** 235 * serialize a Map (as Struct) 236 * @param name 237 * @param map Map to serialize 238 * @param done 239 * @param sb2 240 * @return serialized map 241 * @throws ConverterException 242 */ 243 private String _serializeMap(String name, Map map, StringBuilder sb, Set<Object> done) throws ConverterException { 244 245 if(useShortcuts)sb.append("{}"); 246 else sb.append("new Object();"); 247 Iterator it=map.keySet().iterator(); 248 while(it.hasNext()) { 249 Object key=it.next(); 250 String skey=StringUtil.toLowerCase(StringUtil.escapeJS(key.toString(),'"')); 251 sb.append(name+"["+skey+"]="); 252 _serialize(name+"["+skey+"]",map.get(key),sb,done); 253 //sb.append(";"); 254 } 255 return sb.toString(); 256 } 257 258 /** 259 * serialize a Query 260 * @param query Query to serialize 261 * @param done 262 * @return serialized query 263 * @throws ConverterException 264 */ 265 private void _serializeQuery(String name,Query query,StringBuilder sb, Set<Object> done) throws ConverterException { 266 if(useWDDX)_serializeWDDXQuery(name,query,sb,done); 267 else _serializeASQuery(name,query,sb,done); 268 } 269 270 private void _serializeWDDXQuery(String name,Query query,StringBuilder sb, Set<Object> done) throws ConverterException { 271 Iterator<Key> it = query.keyIterator(); 272 Key k; 273 sb.append("new WddxRecordset();"); 274 275 int recordcount=query.getRecordcount(); 276 int i=-1; 277 while(it.hasNext()) { 278 i++; 279 k = it.next(); 280 if(useShortcuts)sb.append("col"+i+"=[];"); 281 else sb.append("col"+i+"=new Array();"); 282 // lower case ist ok! 283 String skey = StringUtil.escapeJS(k.getLowerString(),'"'); 284 for(int y=0;y<recordcount;y++) { 285 286 sb.append("col"+i+"["+y+"]="); 287 288 _serialize("col"+i+"["+y+"]",query.getAt(k,y+1,null),sb,done); 289 290 } 291 sb.append(name+"["+skey+"]=col"+i+";col"+i+"=null;"); 292 } 293 } 294 295 private void _serializeASQuery(String name,Query query,StringBuilder sb, Set<Object> done) throws ConverterException { 296 297 Collection.Key[] keys = CollectionUtil.keys(query); 298 String[] strKeys = new String[keys.length]; 299 for(int i=0;i<strKeys.length;i++) { 300 strKeys[i] = StringUtil.escapeJS(keys[i].getString(),'"'); 301 } 302 if(useShortcuts)sb.append("[];"); 303 else sb.append("new Array();"); 304 305 int recordcount=query.getRecordcount(); 306 for(int i=0;i<recordcount;i++) { 307 if(useShortcuts)sb.append(name+"["+i+"]={};"); 308 else sb.append(name+"["+i+"]=new Object();"); 309 310 for(int y=0;y<strKeys.length;y++) { 311 sb.append(name+"["+i+"]["+strKeys[y]+"]="); 312 _serialize(name+"["+i+"]["+strKeys[y]+"]",query.getAt(keys[y],i+1,null),sb,done); 313 } 314 } 315 } 316 317 318 319 /** 320 * serialize a DateTime 321 * @param dateTime DateTime to serialize 322 * @param sb 323 * @param sb 324 * @throws ConverterException 325 */ 326 private synchronized void _serializeDateTime(DateTime dateTime, StringBuilder sb) { 327 328 Calendar c = JREDateTimeUtil.getThreadCalendar(ThreadLocalPageContext.getTimeZone()); 329 c.setTime(dateTime); 330 sb.append(goIn()); 331 sb.append("new Date("); 332 sb.append(c.get(Calendar.YEAR)); 333 sb.append(","); 334 sb.append(c.get(Calendar.MONTH)); 335 sb.append(","); 336 sb.append(c.get(Calendar.DAY_OF_MONTH)); 337 sb.append(","); 338 sb.append(c.get(Calendar.HOUR_OF_DAY)); 339 sb.append(","); 340 sb.append(c.get(Calendar.MINUTE)); 341 sb.append(","); 342 sb.append(c.get(Calendar.SECOND)); 343 sb.append(");"); 344 } 345 346 private String goIn() { 347 //StringBuilder rtn=new StringBuilder(deep); 348 //for(int i=0;i<deep;i++) rtn.append('\t'); 349 return "";//rtn.toString(); 350 } 351 352 public void useShortcuts(boolean useShortcuts) { 353 this.useShortcuts=useShortcuts; 354 355 } 356 357 public void useWDDX(boolean useWDDX) { 358 this.useWDDX=useWDDX; 359 } 360 361 /* 362 * @param args 363 * @throws Exception 364 365 public static void main(String[] args) throws Exception { 366 JSConverter js=new JSConverter(); 367 Query query=QueryNew.call(null,"aaa,bbb,ccc"); 368 QueryAddRow.call(null,query); 369 QuerySetCell.call(null,query,"aaa","1.1"); 370 QuerySetCell.call(null,query,"bbb","1.2"); 371 QuerySetCell.call(null,query,"ccc","1.3"); 372 QueryAddRow.call(null,query); 373 QuerySetCell.call(null,query,"aaa","2.1"); 374 QuerySetCell.call(null,query,"bbb","2.2"); 375 QuerySetCell.call(null,query,"ccc","2.3"); 376 QueryAddRow.call(null,query); 377 QuerySetCell.call(null,query,"aaa","3.1"); 378 QuerySetCell.call(null,query,"bbb","3.2"); 379 QuerySetCell.call(null,query,"ccc","3.3<hello>"); 380 Array arr2=List ToArray.call(null,"111,222"); 381 Array arr=List ToArray.call(null,"aaaa,bbb,ccc,dddd,eee"); 382 383 arr.set(10,arr2); 384 385 Struct sct= new Struct(); 386 sct.set("aaa","val1"); 387 sct.set("bbb","val2"); 388 sct.set("ccc","val3"); 389 sct.set("ddd",arr2); 390 391 /* 392 }*/ 393}