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 **/
019package lucee.runtime.converter;
020
021import java.io.IOException;
022import java.io.Serializable;
023import java.io.Writer;
024import java.util.Date;
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.lang.StringUtil;
034import lucee.runtime.Component;
035import lucee.runtime.ComponentScope;
036import lucee.runtime.ComponentSpecificAccess;
037import lucee.runtime.PageContext;
038import lucee.runtime.component.Property;
039import lucee.runtime.exp.PageException;
040import lucee.runtime.functions.displayFormatting.DateFormat;
041import lucee.runtime.functions.displayFormatting.TimeFormat;
042import lucee.runtime.op.Caster;
043import lucee.runtime.orm.ORMUtil;
044import lucee.runtime.text.xml.XMLCaster;
045import lucee.runtime.type.Array;
046import lucee.runtime.type.Collection;
047import lucee.runtime.type.Collection.Key;
048import lucee.runtime.type.KeyImpl;
049import lucee.runtime.type.ObjectWrap;
050import lucee.runtime.type.Query;
051import lucee.runtime.type.Struct;
052import lucee.runtime.type.UDF;
053import lucee.runtime.type.dt.DateTime;
054import lucee.runtime.type.dt.DateTimeImpl;
055import lucee.runtime.type.dt.TimeSpan;
056import lucee.runtime.type.util.ComponentProUtil;
057import lucee.runtime.type.util.ComponentUtil;
058import lucee.runtime.type.util.KeyConstants;
059
060import org.w3c.dom.Node;
061
062/**
063 * class to serialize and desirilize WDDX Packes
064 */
065public final class ScriptConverter extends ConverterSupport {
066        private static final Collection.Key REMOTING_FETCH = KeyImpl.intern("remotingFetch");
067    
068        private int deep=1;
069        private boolean ignoreRemotingFetch=true;
070        
071    /**
072     * constructor of the class
073     */
074    public ScriptConverter() {
075    }
076    public ScriptConverter(boolean ignoreRemotingFetch) {
077        this.ignoreRemotingFetch=ignoreRemotingFetch;
078    }
079        
080        
081        /**
082         * serialize Serializable class
083         * @param serializable
084     * @param sb
085         * @throws ConverterException
086     */
087    private void _serializeSerializable(Serializable serializable, StringBuilder sb) throws ConverterException {
088       
089        sb.append(goIn());
090            sb.append("evaluateJava('");
091            try {
092                    sb.append(JavaConverter.serialize(serializable));
093        } catch (IOException e) {
094            throw toConverterException(e);
095        }
096            sb.append("')");
097        
098    }
099        
100        /**
101         * serialize a Date
102         * @param date Date to serialize
103         * @param sb
104         * @throws ConverterException
105         */
106        private void _serializeDate(Date date, StringBuilder sb) throws ConverterException {
107                _serializeDateTime(new DateTimeImpl(date),sb);
108        }
109        /**
110         * serialize a DateTime
111         * @param dateTime DateTime to serialize
112         * @param sb
113         * @throws ConverterException
114         */
115        private void _serializeDateTime(DateTime dateTime, StringBuilder sb) throws ConverterException {
116           
117
118            try {
119                sb.append(goIn());
120                    sb.append("createDateTime(");
121                    sb.append(DateFormat.call(null,dateTime,"yyyy,m,d"));
122                    sb.append(',');
123                    sb.append(TimeFormat.call(null,dateTime,"H,m,s,l,\"z\""));
124                    sb.append(')');
125                } 
126            catch (PageException e) {
127                        throw toConverterException(e);
128                }
129        }
130
131        /**
132         * serialize a Array
133         * @param array Array to serialize
134         * @param sb
135         * @param done 
136         * @throws ConverterException
137         */
138        private void _serializeArray(Array array, StringBuilder sb, Set<Object> done) throws ConverterException {
139                _serializeList(array.toList(),sb,done);
140        }
141        
142        /**
143         * serialize a List (as Array)
144         * @param list List to serialize
145         * @param sb
146         * @param done 
147         * @throws ConverterException
148         */
149        private void _serializeList(List list, StringBuilder sb, Set<Object> done) throws ConverterException {
150                
151            sb.append(goIn());
152            sb.append("[");
153            boolean doIt=false;
154                ListIterator it=list.listIterator();
155                while(it.hasNext()) {
156                    if(doIt)sb.append(',');
157                    doIt=true;
158                        _serialize(it.next(),sb,done);
159                }
160                
161                sb.append(']');
162        }
163
164    /**
165     * serialize a Struct
166     * @param struct Struct to serialize
167     * @param sb
168     * @param done 
169     * @throws ConverterException
170     */
171    public void _serializeStruct(Struct struct, StringBuilder sb, Set<Object> done) throws ConverterException {
172        sb.append(goIn());
173        sb.append('{');
174        Iterator it=struct.keyIterator();
175        boolean doIt=false;
176        deep++;
177        while(it.hasNext()) {
178            String key=Caster.toString(it.next(),"");
179            if(doIt)sb.append(',');
180            doIt=true;
181            sb.append('\'');
182            sb.append(escape(key));
183            sb.append('\'');
184            sb.append(':');
185            _serialize(struct.get(key,null),sb,done);
186        }
187        deep--;
188        
189        sb.append('}');
190    }
191    
192    public String serializeStruct(Struct struct, Set<Collection.Key> ignoreSet) throws ConverterException {
193        StringBuilder sb =new StringBuilder();
194        sb.append(goIn());
195        sb.append("{");
196        boolean hasIgnores=ignoreSet!=null;
197        Iterator<Key> it = struct.keyIterator();
198        boolean doIt=false;
199        deep++;
200        Key key;
201        while(it.hasNext()) {
202            key = it.next();
203            if(hasIgnores && ignoreSet.contains(key)) continue;
204            if(doIt)sb.append(',');
205            doIt=true;
206            sb.append('\'');
207            sb.append(escape(key.getString()));
208            sb.append('\'');
209            sb.append(':');
210            _serialize(struct.get(key,null),sb,new HashSet<Object>());
211        }
212        deep--;
213        
214        return sb.append('}').toString();
215    }
216
217    /**
218     * serialize a Map (as Struct)
219     * @param map Map to serialize
220     * @param sb
221     * @param done 
222     * @throws ConverterException
223     */
224    private void _serializeMap(Map map, StringBuilder sb, Set<Object> done) throws ConverterException {
225        if(map instanceof Serializable) {
226                _serializeSerializable((Serializable)map,sb);
227                return;
228        }
229        sb.append(goIn());
230        sb.append("{");
231        
232        Iterator it=map.keySet().iterator();
233        boolean doIt=false;
234        deep++;
235        while(it.hasNext()) {
236            Object key=it.next();
237            if(doIt)sb.append(',');
238            doIt=true;
239            sb.append('\'');
240            sb.append(escape(key.toString()));
241            sb.append('\'');
242            sb.append(':');
243            _serialize(map.get(key),sb,done);
244        }
245        deep--;
246        
247        sb.append('}');
248    }
249    /**
250     * serialize a Component
251     * @param component Component to serialize
252     * @param sb
253     * @param done 
254     * @throws ConverterException
255     */
256    private void _serializeComponent(Component c, StringBuilder sb, Set<Object> done) throws ConverterException {
257
258                ComponentSpecificAccess cw = new ComponentSpecificAccess(Component.ACCESS_PRIVATE,c);  
259
260                sb.append(goIn());
261                try {
262                        sb.append("evaluateComponent('"+c.getAbsName()+"','"+ComponentUtil.md5(c)+"',{");
263                } catch (Exception e) {
264                        throw toConverterException(e);
265                }
266                
267                boolean doIt=false;
268                Object member;
269            {
270                        
271                        Iterator<Entry<Key, Object>> it = cw.entryIterator();
272                deep++;
273                Entry<Key, Object> e;
274                while(it.hasNext()) {
275                    e = it.next();
276                    member = e.getValue();
277                    if(member instanceof UDF)continue;
278                    if(doIt)sb.append(',');
279                    doIt=true;
280                    sb.append('\'');
281                    sb.append(escape(e.getKey().getString()));
282                    sb.append('\'');
283                    sb.append(':');
284                    _serialize(member,sb,done);
285                }
286                sb.append("}");
287                deep--;
288                }
289        {
290                boolean isPeristent = ComponentProUtil.isPersistent(c);
291                
292                ComponentScope scope = c.getComponentScope();
293                Iterator<Entry<Key, Object>> it = scope.entryIterator();
294            sb.append(",{");
295                deep++;
296                doIt=false;
297                Property p;
298            Boolean remotingFetch;
299                Struct props = ignoreRemotingFetch?null:ComponentUtil.getPropertiesAsStruct(c,false);
300                Entry<Key, Object> e;
301                Key k;
302                while(it.hasNext()) {
303                        e = it.next();
304                        k = e.getKey();
305                //String key=Caster.toString(it.next(),"");
306                if(KeyConstants._THIS.equalsIgnoreCase(k))continue;
307                if(!ignoreRemotingFetch) {
308                        p=(Property) props.get(k,null);
309                        if(p!=null) {
310                                remotingFetch=Caster.toBoolean(p.getDynamicAttributes().get(REMOTING_FETCH,null),null);
311                        if(remotingFetch==null){
312                                                if(isPeristent  && ORMUtil.isRelated(p)) continue;
313                                }
314                                else if(!remotingFetch.booleanValue()) continue;
315                        }
316                        }
317                
318                
319                
320                member = e.getValue();
321                if(member instanceof UDF)continue;
322                if(doIt)sb.append(',');
323                doIt=true;
324                sb.append('\'');
325                sb.append(escape(k.getString()));
326                sb.append('\'');
327                sb.append(':');
328                _serialize(member,sb,done);
329            }
330            sb.append("}");
331            deep--;
332        }
333        
334        sb.append(")");
335        //sb.append("");
336        //throw new ConverterException("can't serialize a component "+component.getDisplayName());
337    }
338
339        /**
340         * serialize a Query
341         * @param query Query to serialize
342         * @param sb
343         * @param done 
344         * @throws ConverterException
345         */
346        private void _serializeQuery(Query query, StringBuilder sb, Set<Object> done) throws ConverterException {
347                
348                //Collection.Key[] keys = query.keys();
349                Iterator<Key> it = query.keyIterator();
350                Key k;
351                sb.append(goIn());
352                sb.append("query(");
353                
354        
355                deep++;
356                boolean oDoIt=false;
357                int len=query.getRecordcount();
358                while(it.hasNext()) {
359                        k = it.next();
360                    if(oDoIt)sb.append(',');
361                    oDoIt=true;
362                    sb.append(goIn());
363            sb.append('\'');
364            sb.append(escape(k.getString()));
365            sb.append('\'');
366                        sb.append(":[");
367                        boolean doIt=false;
368                        for(int y=1;y<=len;y++) {
369                            if(doIt)sb.append(',');
370                            doIt=true;
371                            try {
372                                        _serialize(query.getAt(k,y),sb,done);
373                                } catch (PageException e) {
374                                        _serialize(e.getMessage(),sb,done);
375                                }
376                        }
377                        sb.append(']');
378                }
379                deep--;
380                
381                sb.append(')');
382                
383        }
384        
385        /**
386         * serialize a Object to his xml Format represenation
387         * @param object Object to serialize
388         * @param sb StringBuilder to write data
389         * @param done 
390         * @throws ConverterException
391         */
392        private void _serialize(Object object, StringBuilder sb, Set<Object> done) throws ConverterException {
393                //try   {
394                        deep++;
395                        // NULL
396                        if(object==null) {
397                            sb.append(goIn());
398                            sb.append("nullValue()");
399                            deep--;
400                            return;
401                        }
402                        // String
403                        if(object instanceof String) {
404                            sb.append(goIn());
405                            sb.append("'");
406                            sb.append(escape(object.toString()));
407                            sb.append("'");
408                            deep--;
409                            return;
410                        }
411                        // Number
412                        if(object instanceof Number) {
413                            sb.append(goIn());
414                            sb.append(Caster.toString(((Number)object)));
415                            deep--;
416                            return;
417                        }
418                        // Boolean
419                        if(object instanceof Boolean) {
420                            sb.append(goIn());
421                            sb.append(Caster.toString(((Boolean)object).booleanValue()));
422                            deep--;
423                            return;
424                        }
425                        // DateTime
426                        if(object instanceof DateTime) {
427                                _serializeDateTime((DateTime)object,sb);
428                            deep--;
429                            return;
430                        }
431                        // Date
432                        if(object instanceof Date) {
433                                _serializeDate((Date)object,sb);
434                            deep--;
435                            return;
436                        }
437                // XML
438                if(object instanceof Node) {
439                    _serializeXML((Node)object,sb);
440                            deep--;
441                            return;
442                }
443                        if(object instanceof ObjectWrap) {
444                                try {
445                                        _serialize(((ObjectWrap)object).getEmbededObject(), sb,done);
446                                } catch (PageException e) {
447                                        throw toConverterException(e);
448                                }
449                            deep--;
450                            return;
451                        }
452                // Timespan
453                if(object instanceof TimeSpan) {
454                        _serializeTimeSpan((TimeSpan) object,sb);
455                            deep--;
456                            return;
457                }
458                        Object raw = LazyConverter.toRaw(object);
459                if(done.contains(raw)) {
460                        sb.append(goIn());
461                            sb.append("nullValue()");
462                            deep--;
463                            return;
464                }
465                        
466                        done.add(raw);
467                        try {
468                        // Component
469                        if(object instanceof Component) {
470                            _serializeComponent((Component)object,sb,done);
471                                    deep--;
472                                    return;
473                        }
474                
475                        // Struct
476                        if(object instanceof Struct) {
477                            _serializeStruct((Struct)object,sb,done);
478                                    deep--;
479                                    return;
480                        }
481                        // Map
482                        if(object instanceof Map) {
483                            _serializeMap((Map)object,sb,done);
484                                    deep--;
485                                    return;
486                        }
487                                // Array
488                                if(object instanceof Array) {
489                                        _serializeArray((Array)object,sb,done);
490                                    deep--;
491                                    return;
492                                }
493                                // List
494                                if(object instanceof List) {
495                                        _serializeList((List)object,sb,done);
496                                    deep--;
497                                    return;
498                                }
499                        // Query
500                        if(object instanceof Query) {
501                            _serializeQuery((Query)object,sb,done);
502                                    deep--;
503                                    return;
504                        }
505                                // String Converter
506                                if(object instanceof ScriptConvertable) {
507                                    sb.append(((ScriptConvertable)object).serialize());
508                                    deep--;
509                                    return;
510                                }
511                                if(object instanceof Serializable) {
512                                        _serializeSerializable((Serializable)object,sb);
513                                    deep--;
514                                    return;
515                                }
516                        }
517                        finally {
518                                done.remove(raw);
519                        }
520                        throw new ConverterException("can't serialize Object of type [ "+Caster.toClassName(object)+" ]");
521                        //deep--;
522                /*}
523                catch(StackOverflowError soe){
524                        throw soe;
525                }*/
526        }
527        
528
529
530    private void _serializeXML(Node node, StringBuilder sb) {
531        node=XMLCaster.toRawNode(node);
532        sb.append(goIn());
533            sb.append("xmlParse('");
534            sb.append(escape(XMLCaster.toString(node,"")));
535            sb.append("')");
536        
537        }
538
539    private void _serializeTimeSpan(TimeSpan span, StringBuilder sb) {
540        
541                sb.append(goIn());
542                    sb.append("createTimeSpan(");
543                    sb.append(span.getDay());
544                    sb.append(',');
545                    sb.append(span.getHour());
546                    sb.append(',');
547                    sb.append(span.getMinute());
548                    sb.append(',');
549                    sb.append(span.getSecond());
550                    sb.append(')');
551                
552        }
553
554
555        private String escape(String str) {
556        return StringUtil.replace(StringUtil.replace(str,"'","''",false),"#","##",false);
557    }
558
559        @Override
560        public void writeOut(PageContext pc, Object source, Writer writer) throws ConverterException, IOException {
561                writer.write(serialize(source));
562                writer.flush();
563        }
564
565    /**
566         * serialize a Object to his literal Format
567         * @param object Object to serialize
568         * @return serialized wddx package
569         * @throws ConverterException
570         */
571        public String serialize(Object object) throws ConverterException {
572                deep=0;
573                StringBuilder sb=new StringBuilder();
574                _serialize(object,sb,new HashSet<Object>());
575                return sb.toString();
576        }
577        
578        
579        /**
580         * @return return current blockquote
581         */
582        private String goIn() {
583            /*StringBuilder rtn=new StringBuilder('\n');
584                for(int i=0;i<deep;i++) rtn.append('\t');
585                return rtn.toString();
586                /*/
587                
588                return "";
589        }
590
591
592}