001    /*
002     * Copyright 2006 Mat Gessel <mat.gessel@gmail.com>
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package asquare.gwt.debug.client;
017    
018    import asquare.gwt.debug.client.impl.DebugImpl;
019    
020    import com.google.gwt.core.client.GWT;
021    import com.google.gwt.core.client.JavaScriptObject;
022    import com.google.gwt.user.client.Event;
023    
024    /**
025     * <p>
026     * Facade for debugging methods. Creates methods for printing debug statements
027     * from JSNI and native JavaScript. You can enable/disable debug methods by
028     * pressing the <code>Esc</code> key twice sequentially.
029     * <p>
030     * Uses a {@link asquare.gwt.debug.client.DebugEventListener DebugEventListener}
031     * to listen for the enabler key. <em>See
032     * {@link asquare.gwt.debug.client.DebugEventListener DebugEventListener} for
033     * potential conflicts with PopupPanel and DialogBox.</em>
034     * <p>
035     * A stub version of this class is provided which will remove this class
036     * definition from the final deliverable if placed ahead of this implementation
037     * in the classpath. The stub class will prevent debug statements from being
038     * displayed, however, the string will still be created by the browser's
039     * interpreter. To entirely remove debugging statements from the deliverable you
040     * will need to do something like this:
041     * 
042     * <pre>
043     * if (Debug.ENABLED)
044     * {
045     *      Debug.println(&quot;Foo.bar(&quot; + value1 + ',' + value2 + ')');
046     * }
047     * </pre>
048     * <code>Debug.ENABLED</code> is <code>false</code> in the stub version.
049     * </p>
050     * <P>
051     * The compiler may optimize out the class if you <strong>only</strong>
052     * reference this class through JSNI. Consider calling
053     * {@link #init() Debug.init()} in your entry point or test case if you are
054     * getting this message: <br/>
055     * <code>com.google.gwt.core.client.JavaScriptException: JavaScript TypeError exception: Object doesn't support this property or method</code>.
056     * 
057     * @see DebugConsole
058     */
059    public class Debug
060    {
061            public static final boolean ENABLED = true;
062            
063            /**
064             * The default debug enabler key (<code>Esc</code>).
065             */
066            public static final char DEFAULT_ENABLE_KEY = (char) 27;
067            
068            private static boolean s_runtimeEnabled = false;
069            
070            private static boolean s_initRunning = false;
071            
072            private static boolean s_initialized = false;
073            
074            private static DebugEventListener s_eventTracer = null;
075            
076            private static Enabler s_enabler = null;
077            
078            private static char s_enableKey = DEFAULT_ENABLE_KEY;
079            
080            private static DebugImpl s_impl = (DebugImpl) GWT.create(DebugImpl.class);
081            
082            static
083            {
084                    init();
085            }
086            
087            /**
088             * This is called automatically when the class (script) is loaded. If you
089             * only reference this class via JavaScript you need to call init() (or
090             * another method) to ensure this class is not optimized out by the
091             * compiler.
092             */
093            public static void init()
094            {
095                    if (ENABLED && ! s_initialized && ! s_initRunning)
096                    {
097                            s_initRunning = true;
098                            
099                            jsInit();
100                            
101                            (s_enabler = new Enabler()).install();
102                            
103                            s_initialized = true;
104                            s_initRunning = false;
105                    }
106            }
107            
108            /**
109             * Sets up a "native" JS API so you don't have to type the contorted JNI
110             * syntax to print debug statements from JS.
111             */
112            private static native void jsInit() /*-{
113                    if ($wnd.Debug === undefined)
114                    {
115                             $wnd.Debug = new Object();
116                    }  
117                    
118                    if (typeof Debug == "undefined")
119                    {
120                            Debug = $wnd.Debug;
121                    }
122                    
123                    Debug.enable = $wnd.Debug.enable = function()
124                    {
125                            @asquare.gwt.debug.client.Debug::enable()();
126                    };
127                    
128                    Debug.enableSilently = $wnd.Debug.enableSilently = function()
129                    {
130                            @asquare.gwt.debug.client.Debug::enableSilently()();
131                    };
132                    
133                    Debug.disable = $wnd.Debug.disable = function()
134                    {
135                            @asquare.gwt.debug.client.Debug::disable()();
136                    };
137                    
138                    Debug.print = $wnd.Debug.print = function(object)
139                    {
140                            @asquare.gwt.debug.client.Debug::print(Ljava/lang/String;)("" + object);
141                    };
142                    
143                    Debug.println = $wnd.Debug.println = function(object)
144                    {
145                            @asquare.gwt.debug.client.Debug::println(Ljava/lang/String;)("" + object);
146                    };
147                    
148                    Debug.prettyPrint = $wnd.Debug.prettyPrint = function(object)
149                    {
150                            if (typeof object == "undefined")
151                            {
152                                    Debug.println("undefined");
153                            }
154                            else if (object == null)
155                            {
156                                    Debug.println("null");
157                            }
158                            else if (object instanceof Array)
159                            {
160                                    Debug.println("[Array length=" + object.length + "]");
161                            }
162                            else if (object.nodeName)
163                            {
164                                    Debug.println("[object " + object.nodeName + "]");
165                            }
166                            else
167                            {
168                                    Debug.println(object);
169                            }
170                    };
171                    
172                    Debug.dump = $wnd.Debug.dump = function(object)
173                    {
174                            Debug.prettyPrint(object);
175                            if (object instanceof Array)
176                            {
177                                    for (var i = 0; i < object.length; i++)
178                                    {
179                                            Debug.println("  [" + i + "]" + object[i]);
180                                    }
181                            }
182                            else
183                            {
184                                    for (var member in object)
185                                    {
186                                            Debug.print("  +" + member + "=");
187                                            try
188                                            {
189                                                    Debug.prettyPrint(object[member]);
190                                            }
191                                            catch(e)
192                                            {
193                                                    Debug.println("(Exception caught: " + e + ")");
194                                            }
195                                    }
196                            }
197                    };
198            }-*/;
199            
200            /**
201             * Get the key which enables &amp; disables debugging. 
202             * 
203             * @return a keycode representing the enabler key
204             */
205            public static char getEnableKey()
206            {
207                    return s_enableKey;
208            }
209            
210            /**
211             * Set the key which enables &amp; disables debugging. 
212             * 
213             * @param enableKey a keycode representing the enabler key
214             */
215            public static void setEnableKey(char enableKey)
216            {
217                    s_enableKey = enableKey;
218                    if (s_enabler != null)
219                    {
220                            s_enabler.setEnableKey(s_enableKey);
221                    }
222            }
223            
224            /**
225             * Enable debugging, but does not show the in-browser console. 
226             */
227            public static void enableSilently()
228            {
229                    if (ENABLED)
230                    {
231                            s_runtimeEnabled = true;
232                            s_enabler.setEnabled(true);
233                    }
234            }
235            
236            /**
237             * Enables debugging and prints a message stating so. 
238             */
239            public static void enable()
240            {
241                    if (ENABLED && ! s_runtimeEnabled)
242                    {
243                            s_runtimeEnabled = true;
244                            s_enabler.setEnabled(true);
245                            Debug.println("Debug enabled");
246                    }
247            }
248            
249            /**
250             * Disables debugging, prints a message stating so and hides the in-browser console. 
251             */
252            public static void disable()
253            {
254                    if (ENABLED)
255                    {
256                            Debug.println("Debug disabled");
257                            s_enabler.setEnabled(false);
258                            s_runtimeEnabled = false;
259                    }
260            }
261            
262            /**
263             * Is debugging enabled at compile time and runtime. 
264             * 
265             * @return true if enabled
266             */
267            public static boolean isEnabled()
268            {
269                    return ENABLED && s_runtimeEnabled;
270            }
271            
272            /**
273             * Prints a debugging message to the {@link DebugConsole} widget. The
274             * message is mirrored to the browser's native console, if supported. In
275             * hosted mode the message is mirrored to {@link System#out} and the GWT
276             * Shell.
277             * 
278             * @param message
279             * @see DebugConsole
280             */
281            public static void print(String message)
282            {
283                    if (ENABLED && s_runtimeEnabled)
284                    {
285                            System.out.print(message);
286                            GWT.log(message, null);
287                            printToBrowserConsole(message);
288                            DebugConsole.getInstance().print(message);
289                    }
290            }
291            
292            /**
293             * Prints a debugging message to the {@link DebugConsole} widget, follwed by
294             * "\r\n". The message is mirrored to the browser's native console, if
295             * supported. In hosted mode the message is mirrored to {@link System#out}
296             * and the GWT Shell.
297             * 
298             * @param message
299             * @see DebugConsole
300             */
301            public static void println(String message)
302            {
303                    if (ENABLED && s_runtimeEnabled)
304                    {
305                            System.out.println(message);
306                            GWT.log(message, null);
307                            printToBrowserConsole(message);
308                            DebugConsole.getInstance().println(message);
309                    }
310            }
311            
312            /**
313             * Prints a message to the native browser console, if available. Otherwise
314             * does nothing.
315             * 
316             * @param message
317             */
318            public static void printToBrowserConsole(String message)
319            {
320                    s_impl.printToBrowserConsole(message);
321            }
322            
323            /**
324             * Prints a description of a JavaScript object. Not very useful for Java
325             * objects.
326             * 
327             * @see #prettyPrint(JavaScriptObject)
328             */
329            public static native void prettyPrint(Object o) /*-{
330                    if (@asquare.gwt.debug.client.Debug::isEnabled()())
331                    {
332                            Debug.prettyPrint(o);
333                    }
334            }-*/;
335            
336            /**
337             * Prints a description of a JavaScript object. Not very useful for Java
338             * objects.
339             */
340            public static native void prettyPrint(JavaScriptObject o) /*-{
341                    if (@asquare.gwt.debug.client.Debug::isEnabled()())
342                    {
343                            Debug.prettyPrint(o);
344                    }
345            }-*/;
346            
347            /**
348             * Displays members of native JavaScript objects. Not very useful for Java
349             * objects.
350             * 
351             * @see #dump(JavaScriptObject)
352             */
353            public static native void dump(Object o) /*-{
354                    if (@asquare.gwt.debug.client.Debug::isEnabled()())
355                    {
356                            Debug.dump(o);
357                    }
358            }-*/;
359            
360            /**
361             * Displays members of native JavaScript objects. This is implemented in
362             * JavaScript where Java objects are opaque. GWT doesn't support reflection
363             * so we can't see objects members in Java.
364             */
365            public static native void dump(JavaScriptObject o) /*-{
366                    if (@asquare.gwt.debug.client.Debug::isEnabled()())
367                    {
368                            Debug.dump(o);
369                    }
370            }-*/;
371            
372            /**
373             * Installs an experimental event tracer. Press the specified key twice to
374             * start tracing. A message will be printed to the console for each event
375             * matching eventMask.
376             * <p>
377             * Some events are not tracing in Firefox at this time, even though the
378             * events trigger listeners in GWT widgets.
379             * </p>
380             * 
381             * @param enableKey a keyCode or (char) 0 for the default ('e')
382             * @param eventMask a bitwise event mask, 0 for no events or -1 for the
383             *            default
384             * @see DebugEventListener
385             * @see Event
386             */
387            public static void installEventTracer(char enableKey, int eventMask)
388            {
389                    if (ENABLED && s_eventTracer == null)
390                    {
391                            s_eventTracer = new DebugEventListener(enableKey, eventMask, null);
392                            s_eventTracer.install();
393                    }
394            }
395            
396            /**
397             * @see #installEventTracer(char, int)
398             */
399            public static void uninstallEventTracer()
400            {
401                    if (s_eventTracer != null)
402                    {
403                            s_eventTracer.uninstall();
404                            s_eventTracer = null;
405                    }
406            }
407            
408            /**
409             * Listens for the enable key and enables/disables debugging. 
410             */
411            private static final class Enabler extends DebugEventListener
412            {
413                    Enabler()
414                    {
415                            super(Debug.DEFAULT_ENABLE_KEY, 0, "Debug enabler");
416                    }
417                    
418                    protected void doDisabled()
419                    {
420                            Debug.disable();
421                    }
422                    
423                    protected void doEnabled()
424                    {
425                            Debug.enable();
426                    }
427                    
428                    protected void doEvent(Event event)
429                    {
430                            // NOOP
431                    }
432            }
433    }