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.dump;
020
021import java.io.File;
022import java.lang.reflect.Field;
023import java.lang.reflect.Method;
024import java.sql.ResultSet;
025import java.text.SimpleDateFormat;
026import java.util.Calendar;
027import java.util.Date;
028import java.util.Enumeration;
029import java.util.Iterator;
030import java.util.List;
031import java.util.ListIterator;
032import java.util.Locale;
033import java.util.Map;
034import java.util.Set;
035
036import javax.servlet.http.Cookie;
037import javax.servlet.http.HttpSession;
038
039import lucee.commons.date.TimeZoneUtil;
040import lucee.commons.io.res.Resource;
041import lucee.commons.lang.ExceptionUtil;
042import lucee.commons.lang.IDGenerator;
043import lucee.commons.lang.StringUtil;
044import lucee.runtime.PageContext;
045import lucee.runtime.coder.Base64Coder;
046import lucee.runtime.converter.WDDXConverter;
047import lucee.runtime.exp.PageException;
048import lucee.runtime.net.rpc.Pojo;
049import lucee.runtime.op.Caster;
050import lucee.runtime.op.Decision;
051import lucee.runtime.text.xml.XMLCaster;
052import lucee.runtime.type.Array;
053import lucee.runtime.type.Collection;
054import lucee.runtime.type.ObjectWrap;
055import lucee.runtime.type.QueryImpl;
056import lucee.runtime.type.dt.DateTimeImpl;
057import lucee.runtime.type.scope.CookieImpl;
058
059import org.w3c.dom.NamedNodeMap;
060import org.w3c.dom.Node;
061import org.w3c.dom.NodeList;
062
063public class DumpUtil {
064
065        public static final DumpData MAX_LEVEL_REACHED;
066
067        static {
068
069                MAX_LEVEL_REACHED = new DumpTable("Max Level Reached","#e0e0e0","#ffcc99","#888888");
070                ((DumpTable)MAX_LEVEL_REACHED).appendRow( new DumpRow(1, new SimpleDumpData("[Max Dump Level Reached]") ) );
071        }
072
073        public static DumpData toDumpData(Object o, PageContext pageContext, int maxlevel, DumpProperties props) {
074                if(maxlevel<0)
075                        return MAX_LEVEL_REACHED;
076
077                // null
078                if(o == null) {
079                        DumpTable table=new DumpTable("null","#ff6600","#ffcc99","#000000");
080                        table.appendRow(new DumpRow(0,new SimpleDumpData("Empty:null")));
081                        return table;
082                }
083                if(o instanceof DumpData) {
084                        return ((DumpData)o);
085                }
086                // Date
087                if(o instanceof Date) {
088                        return new DateTimeImpl((Date) o).toDumpData(pageContext,maxlevel,props);
089                }
090                // Calendar
091                if(o instanceof Calendar) {
092                        Calendar c=(Calendar)o;
093                        
094                        SimpleDateFormat df = new SimpleDateFormat("EE, dd MMM yyyy HH:mm:ss zz",Locale.ENGLISH);
095                        df.setTimeZone(c.getTimeZone());
096                        
097                        DumpTable table=new DumpTable("date","#ff9900","#ffcc00","#000000");
098                        table.setTitle("java.util.Calendar");
099                        table.appendRow(1, new SimpleDumpData("Timezone"), new SimpleDumpData(TimeZoneUtil.toString(c.getTimeZone())));
100                        table.appendRow(1, new SimpleDumpData("Time"), new SimpleDumpData(df.format(c.getTime())));
101                
102                        return table;
103                }
104                // StringBuffer
105                if(o instanceof StringBuffer) {
106                        DumpTable dt=(DumpTable)toDumpData(o.toString(), pageContext, maxlevel, props);
107                        if(StringUtil.isEmpty(dt.getTitle()))
108                                dt.setTitle(Caster.toClassName(o));
109                        return dt;
110                }
111                // StringBuilder
112                if(o instanceof StringBuilder) {
113                        DumpTable dt=(DumpTable)toDumpData(o.toString(), pageContext, maxlevel, props);
114                        if(StringUtil.isEmpty(dt.getTitle()))
115                                dt.setTitle(Caster.toClassName(o));
116                        return dt;
117                }
118                // String
119                if(o instanceof String) {
120                        String str=(String) o;
121                        if(str.trim().startsWith("<wddxPacket ")) {
122                                try {
123                                        WDDXConverter converter =new WDDXConverter(pageContext.getTimeZone(),false,true);
124                                        converter.setTimeZone(pageContext.getTimeZone());
125                                        Object rst = converter.deserialize(str,false);
126                                        DumpData data = toDumpData(rst, pageContext, maxlevel, props);
127                                        
128                                        DumpTable table = new DumpTable("string","#cc9999","#ffffff","#000000");
129                                        table.setTitle("WDDX");
130                                        table.appendRow(1,new SimpleDumpData("encoded"),data);
131                                        table.appendRow(1,new SimpleDumpData("raw"),new SimpleDumpData(str));
132                                        return table;
133                                }
134                                catch(Throwable t) {
135                        ExceptionUtil.rethrowIfNecessary(t);
136                    }
137                        }
138                        DumpTable table = new DumpTable("string","#ff6600","#ffcc99","#000000");
139                        table.appendRow(1,new SimpleDumpData("string"),new SimpleDumpData(str));
140                        return table;
141                }
142                // Character
143                if(o instanceof Character) {
144                        DumpTable table = new DumpTable("character","#ff6600","#ffcc99","#000000");
145                        table.appendRow(1,new SimpleDumpData("character"),new SimpleDumpData(o.toString()));
146                        return table;
147                }
148                // Number
149                if(o instanceof Number) {
150                        DumpTable table = new DumpTable("numeric","#ff6600","#ffcc99","#000000");
151                        table.appendRow(1,new SimpleDumpData("number"),new SimpleDumpData(Caster.toString(((Number)o))));
152                        return table;
153                }
154                // Boolean
155                if(o instanceof Boolean) {
156                        DumpTable table = new DumpTable("boolean","#ff6600","#ffcc99","#000000");
157                        table.appendRow(1,new SimpleDumpData("boolean"),new SimpleDumpData(((Boolean)o).booleanValue()));
158                        return table;
159                }
160                // File
161                if(o instanceof File) {
162                        DumpTable table = new DumpTable("file","#ffcc00","#ffff66","#000000");
163                        table.appendRow(1,new SimpleDumpData("File"),new SimpleDumpData(o.toString()));
164                        return table;
165                }
166                // Cookie
167                if(o instanceof Cookie) {
168                        Cookie c=(Cookie) o;
169                        DumpTable table = new DumpTable("Cookie","#979EAA","#DEE9FB","#000000");
170                        table.setTitle("Cookie ("+c.getClass().getName()+")");
171                        table.appendRow(1,new SimpleDumpData("name"),new SimpleDumpData(c.getName()));
172                        table.appendRow(1,new SimpleDumpData("value"),new SimpleDumpData(c.getValue()));
173                        table.appendRow(1,new SimpleDumpData("path"),new SimpleDumpData(c.getPath()));
174                        table.appendRow(1,new SimpleDumpData("secure"),new SimpleDumpData(c.getSecure()));
175                        table.appendRow(1,new SimpleDumpData("maxAge"),new SimpleDumpData(c.getMaxAge()));
176                        table.appendRow(1,new SimpleDumpData("version"),new SimpleDumpData(c.getVersion()));
177                        table.appendRow(1,new SimpleDumpData("domain"),new SimpleDumpData(c.getDomain()));
178                        table.appendRow(1,new SimpleDumpData("httpOnly"),new SimpleDumpData(CookieImpl.isHTTPOnly(c)));
179                        table.appendRow(1,new SimpleDumpData("comment"),new SimpleDumpData(c.getComment()));
180                        return table;
181                }
182                // Resource
183                if(o instanceof Resource) {
184                        DumpTable table = new DumpTable("resource","#ffcc00","#ffff66","#000000");
185                        table.appendRow(1,new SimpleDumpData("Resource"),new SimpleDumpData(o.toString()));
186                        return table;
187                }
188                // byte[]
189                if(o instanceof byte[]) {
190                        byte[] bytes=(byte[]) o;
191                        int max=5000;
192                        DumpTable table = new DumpTable("array","#ff9900","#ffcc00","#000000");
193                        table.setTitle("Native Array  ("+Caster.toClassName(o)+")");
194                        StringBuilder sb=new StringBuilder("[");
195                        for(int i=0;i<bytes.length;i++) {
196                                if(i!=0)sb.append(",");
197                                sb.append(bytes[i]);
198                                if(i==max) {
199                                        sb.append(", ...truncated");
200                                        break;
201                                }
202                        }
203                        sb.append("]");
204                        table.appendRow(1,new SimpleDumpData("Raw"+(bytes.length<max?"":" (truncated)")),new SimpleDumpData(sb.toString()));
205
206
207                        if(bytes.length<max) {
208                                // base64
209                                table.appendRow(1,new SimpleDumpData("Base64 Encoded"),new SimpleDumpData(Base64Coder.encode(bytes)));
210                                /*try {
211                                        table.appendRow(1,new SimpleDumpData("CFML expression"),new SimpleDumpData("evaluateJava('"+JavaConverter.serialize(bytes)+"')"));
212                                        
213                                }
214                                catch (IOException e) {}*/
215                        }
216
217
218                        return table;   
219                }
220                // Collection.Key
221                if(o instanceof Collection.Key) {
222                        Collection.Key key=(Collection.Key) o;
223                        DumpTable table = new DumpTable("string","#ff6600","#ffcc99","#000000");
224                        table.appendRow(1,new SimpleDumpData("Collection.Key"),new SimpleDumpData(key.getString()));
225                        return table;
226                }
227
228                
229                String id=""+IDGenerator.intId();
230                String refid=ThreadLocalDump.get(o);
231                if(refid!=null) {
232                        DumpTable table = new DumpTable("ref","#ffffff","#cccccc","#000000");
233                        table.appendRow(1,new SimpleDumpData("Reference"),new SimpleDumpData(refid));
234                        table.setRef(refid);
235                        return setId(id,table);
236                }
237                
238                ThreadLocalDump.set(o,id);
239                try{
240
241                        int top = props.getMaxlevel();
242
243                        // Printable
244                        if(o instanceof Dumpable) {
245                DumpData dd = ((Dumpable)o).toDumpData(pageContext,maxlevel,props);
246                                return setId(id, dd);
247                        }
248                        // Map
249                        if(o instanceof Map) {
250                                Map map=(Map) o;
251                                Iterator it=map.keySet().iterator();
252
253                                DumpTable table = new DumpTable("struct","#ff9900","#ffcc00","#000000");
254                                table.setTitle("Map ("+Caster.toClassName(o)+")");
255
256                                while(it.hasNext()) {
257                                        Object next=it.next();
258                                        table.appendRow(1,toDumpData(next,pageContext,maxlevel,props),toDumpData(map.get(next),pageContext,maxlevel,props));
259                                }
260                                return setId(id,table);
261                        }
262
263                        // List
264                        if(o instanceof List) {
265                                List list=(List) o;
266                                ListIterator it=list.listIterator();
267                                
268                                DumpTable table = new DumpTable("array","#ff9900","#ffcc00","#000000");
269                                table.setTitle("Array (List)");
270                                if ( list.size() > top )
271                                        table.setComment("Rows: " + list.size() + " (showing top " + top + ")");
272
273                                int i = 0;
274                                while(it.hasNext() && i++ < top) {
275                                        table.appendRow(1,new SimpleDumpData(it.nextIndex()+1),toDumpData(it.next(),pageContext,maxlevel,props));
276                                }
277                                return setId(id,table);
278                        }
279                
280                        // Set
281                        if(o instanceof Set) {
282                                Set set=(Set) o;
283                                Iterator it = set.iterator();
284                                
285                                DumpTable table = new DumpTable("array","#ff9900","#ffcc00","#000000");
286                                table.setTitle("Set ("+set.getClass().getName()+")");
287
288                                int i = 0;
289                                while(it.hasNext() && i++ < top) {
290                                        table.appendRow(1,toDumpData(it.next(),pageContext,maxlevel,props));
291                                }
292                                return setId(id,table);
293                        }
294                        
295                        // Resultset
296                        if(o instanceof ResultSet) {
297                                try {
298                                        DumpData dd = new QueryImpl((ResultSet)o,"query",pageContext.getTimeZone()).toDumpData(pageContext,maxlevel,props);
299                                        if(dd instanceof DumpTable)
300                                                ((DumpTable)dd).setTitle(Caster.toClassName(o));
301                                        return setId(id,dd);
302                                } 
303                                catch (PageException e) {
304                                        
305                                }
306                        }
307                        // Enumeration
308                        if(o instanceof Enumeration) {
309                                Enumeration e=(Enumeration)o;
310                                
311                                DumpTable table = new DumpTable("enumeration","#ff9900","#ffcc00","#000000");
312                                table.setTitle("Enumeration");
313
314                                int i = 0;
315                                while(e.hasMoreElements() && i++ < top) {
316                                        table.appendRow(0,toDumpData(e.nextElement(),pageContext,maxlevel,props));
317                                }
318                                return setId(id,table);
319                        }
320                        // Object[]
321                        if(Decision.isNativeArray(o)) {
322                                Array arr;
323                                try {
324                                        arr = Caster.toArray(o);
325                                        DumpTable htmlBox = new DumpTable("array","#ff9900","#ffcc00","#000000");
326                                        htmlBox.setTitle("Native Array ("+Caster.toClassName(o)+")");
327                                
328                                        int length=arr.size();
329                                
330                                        for(int i=1;i<=length;i++) {
331                                                Object ox=null;
332                                                try {
333                                                        ox = arr.getE(i);
334                                                } catch (Exception e) {}
335                                                htmlBox.appendRow(1,new SimpleDumpData(i),toDumpData(ox,pageContext,maxlevel,props));
336                                        }
337                                        return setId(id,htmlBox);
338                                } 
339                                catch (PageException e) {
340                                        return setId(id,new SimpleDumpData(""));
341                                }
342                        }
343                        // Node
344                        if(o instanceof Node) {
345                            return setId(id,XMLCaster.toDumpData((Node)o, pageContext,maxlevel,props));                 
346                        }
347                        // ObjectWrap
348                        if(o instanceof ObjectWrap) {
349                                maxlevel++;
350                            return setId(id,toDumpData(((ObjectWrap)o).getEmbededObject(null), pageContext,maxlevel,props));                    
351                        }
352                        // NodeList
353                        if(o instanceof NodeList) {
354                                NodeList list=(NodeList)o;
355                                int len=list.getLength();
356                                DumpTable table = new DumpTable("xml","#cc9999","#ffffff","#000000");
357                                for(int i=0;i<len;i++) {
358                                        table.appendRow(1,new SimpleDumpData(i),toDumpData(list.item(i),pageContext,maxlevel,props));
359                                }
360                                return setId(id,table);
361                                
362                        }
363                        // AttributeMap
364                        if(o instanceof NamedNodeMap) {
365                                NamedNodeMap attr = (NamedNodeMap)o;
366                                int len = attr.getLength();
367                                DumpTable dt = new DumpTable("array","#ff9900","#ffcc00","#000000");
368                                dt.setTitle("NamedNodeMap ("+Caster.toClassName(o)+")");
369                                
370                                for(int i=0;i<len;i++) {
371                                        dt.appendRow(1,new SimpleDumpData(i),toDumpData(attr.item(i),pageContext,maxlevel,props));
372                                }
373                                return setId(id,dt);                    
374                        }
375                        // HttpSession
376                        if(o instanceof HttpSession) {
377                            HttpSession hs = (HttpSession)o;
378                            Enumeration e = hs.getAttributeNames();
379                            
380                            DumpTable htmlBox = new DumpTable("httpsession","#9999ff","#ccccff","#000000");
381                                htmlBox.setTitle("HttpSession");
382                            while(e.hasMoreElements()) {
383                                String key=e.nextElement().toString();
384                                htmlBox.appendRow(1,new SimpleDumpData(key),toDumpData(hs.getAttribute(key), pageContext,maxlevel,props));
385                            }
386                            return setId(id,htmlBox);
387                        }
388                        
389                        if(o instanceof Pojo) {
390                                DumpTable table = new DumpTable(o.getClass().getName(),"#ff99cc","#ffccff","#000000");
391                                
392                                Class clazz=o.getClass();
393                                if(o instanceof Class) clazz=(Class) o;
394                                String fullClassName=clazz.getName();
395                                int pos=fullClassName.lastIndexOf('.');
396                                String className=pos==-1?fullClassName:fullClassName.substring(pos+1);
397                                
398                                table.setTitle("Java Bean - "+className +" ("+fullClassName+")");
399                                table.appendRow(3,
400                                                new SimpleDumpData("Property Name"),
401                                                new SimpleDumpData("Value")
402                                );
403                                
404                                // collect the properties
405                                Method[] methods=clazz.getMethods();
406                                String propName;
407                                Object value;
408                                String exName=null;
409                                String exValue=null;
410                                for(int i=0;i<methods.length;i++) {
411                                        Method method = methods[i];
412                                        if(Object.class==method.getDeclaringClass()) continue;
413                                        propName=method.getName();
414                                        if(propName.startsWith("get") && method.getParameterTypes().length==0) {
415                                                propName=propName.substring(3);
416                                                value=null;
417                                                try {
418                                                        value=method.invoke(o, new Object[0]);
419                                                        if(exName==null && value instanceof String && ((String)value).length()<20) {
420                                                                exName=propName;
421                                                                exValue=value.toString();
422                                                        }
423                                                }
424                                                catch (Throwable t) {
425                                        ExceptionUtil.rethrowIfNecessary(t);
426                                        value="not able to retrieve the data:"+t.getMessage();
427                                    }
428                                                table.appendRow(0,
429                                                                new SimpleDumpData(propName),
430                                                                toDumpData(value, pageContext, maxlevel, props)
431                                                );
432                                        }
433                                }
434                                
435                                if(exName==null) {
436                                        exName="LastName";
437                                        exValue="Sorglos";
438                                }
439                                
440                                table.setComment("JavaBeans are reusable software components for Java." +
441                                                "\nThey are classes that encapsulate many objects into a single object (the bean)." +
442                                                "\nThey allow access to properties using getter and setter methods or directly." );
443                                                
444                                                /*"\n\nExample:\n" +
445                                                "   x=myBean.get"+exName+"(); // read a property with a getter method\n" +
446                                                "   x=myBean."+exName+"; // read a property directly\n" +
447                                                "   myBean.set"+exName+"(\""+exValue+"\"); // write a property with a setter method\n" +
448                                                "   myBean."+exName+"=\""+exValue+"\"; // write a property directly");*/
449                                
450                                
451                                
452                                
453                                return setId(id,table);
454                                
455                                
456                                
457
458                        }
459
460                // reflect
461                //else {
462                        DumpTable table = new DumpTable(o.getClass().getName(),"#cc9999","#ffcccc","#000000");
463                        
464                        Class clazz=o.getClass();
465                        if(o instanceof Class) clazz=(Class) o;
466                        String fullClassName=clazz.getName();
467                        int pos=fullClassName.lastIndexOf('.');
468                        String className=pos==-1?fullClassName:fullClassName.substring(pos+1);
469                        
470                        table.setTitle(className);
471                        table.appendRow(1,new SimpleDumpData("class"),new SimpleDumpData(fullClassName));
472                        
473                        // Fields
474                        Field[] fields=clazz.getFields();
475                        DumpTable fieldDump = new DumpTable("#cc9999","#ffcccc","#000000");
476                        fieldDump.appendRow(7,new SimpleDumpData("name"),new SimpleDumpData("pattern"),new SimpleDumpData("value"));
477                        for(int i=0;i<fields.length;i++) {
478                                Field field = fields[i];
479                                DumpData value;
480                                try {//print.out(o+":"+maxlevel);
481                                        value=new SimpleDumpData(Caster.toString(field.get(o), ""));
482                                } 
483                                catch (Exception e) {
484                                        value=new SimpleDumpData("");
485                                }
486                                fieldDump.appendRow(0,new SimpleDumpData(field.getName()),new SimpleDumpData(field.toString()),value);
487                        }
488                        if(fields.length>0)table.appendRow(1,new SimpleDumpData("fields"),fieldDump);
489                        
490                        // Methods
491                        StringBuilder objMethods=new StringBuilder();
492                        Method[] methods=clazz.getMethods();
493                        DumpTable methDump = new DumpTable("#cc9999","#ffcccc","#000000");
494                        methDump.appendRow(7,new SimpleDumpData("return"),new SimpleDumpData("interface"),new SimpleDumpData("exceptions"));
495                        for(int i=0;i<methods.length;i++) {
496                                Method method = methods[i];
497                                
498                                if(Object.class==method.getDeclaringClass()) {
499                                        if(objMethods.length()>0)objMethods.append(", ");
500                                        objMethods.append(method.getName());
501                                        continue;
502                                }
503                                
504                                // exceptions
505                                StringBuilder sbExp=new StringBuilder();
506                                Class[] exceptions = method.getExceptionTypes();
507                                for(int p=0;p<exceptions.length;p++){
508                                        if(p>0)sbExp.append("\n");
509                                        sbExp.append(Caster.toClassName(exceptions[p]));
510                                }
511                                
512                                // parameters
513                                StringBuilder sbParams=new StringBuilder(method.getName());
514                                sbParams.append('(');
515                                Class[] parameters = method.getParameterTypes();
516                                for(int p=0;p<parameters.length;p++){
517                                        if(p>0)sbParams.append(", ");
518                                        sbParams.append(Caster.toClassName(parameters[p]));
519                                }
520                                sbParams.append(')');
521                                
522                                methDump.appendRow(0,
523                                                new SimpleDumpData(Caster.toClassName(method.getReturnType())),
524
525                                                new SimpleDumpData(sbParams.toString()),
526                                                new SimpleDumpData(sbExp.toString())
527                                );
528                        }
529                        if(methods.length>0)table.appendRow(1,new SimpleDumpData("methods"),methDump);
530                        
531                        DumpTable inherited = new DumpTable("#cc9999","#ffcccc","#000000");
532                        inherited.appendRow(7,new SimpleDumpData("Methods inherited from java.lang.Object"));
533                        inherited.appendRow(0,new SimpleDumpData(objMethods.toString()));
534                        table.appendRow(1,new SimpleDumpData(""),inherited);
535                        return setId(id,table);
536                //}
537                }
538                finally{
539                        ThreadLocalDump.remove(o);
540                }
541        }
542
543        private static DumpData setId(String id, DumpData data) {
544                if(data instanceof DumpTable) {
545                        ((DumpTable)data).setId(id);
546                }
547                // TODO Auto-generated method stub
548                return data;
549        }
550
551        public static boolean keyValid(DumpProperties props,int level, String key) {
552                if(props.getMaxlevel()-level>1) return true;
553                
554                // show
555                Set set = props.getShow();
556                if(set!=null && !set.contains(StringUtil.toLowerCase(key)))
557                        return false;
558                
559                // hide
560                set = props.getHide();
561                if(set!=null && set.contains(StringUtil.toLowerCase(key)))
562                        return false;
563                
564                return true;
565        }
566        
567        public static boolean keyValid(DumpProperties props,int level, Collection.Key key) {
568                if(props.getMaxlevel()-level>1) return true;
569                
570                // show
571                Set set = props.getShow();
572                if(set!=null && !set.contains(key.getLowerString()))
573                        return false;
574                
575                // hide
576                set = props.getHide();
577                if(set!=null && set.contains(key.getLowerString()))
578                        return false;
579                
580                return true;
581        }
582        
583        
584        
585
586        public static DumpProperties toDumpProperties() {
587                return DumpProperties.DEFAULT;
588        }
589}