001package votorola.g.web.gwt; // Copyright 2010, 2012, Michael Allan.  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE.
002
003import com.google.gwt.core.client.*;
004import com.google.gwt.dom.client.Document;
005import com.google.gwt.event.shared.GwtEvent;
006import com.google.gwt.regexp.shared.*;
007import com.google.gwt.user.client.Window;
008import com.google.web.bindery.event.shared.EventBus;
009import com.google.web.bindery.event.shared.SimpleEventBus;
010import com.google.web.bindery.event.shared.UmbrellaException;
011import votorola.g.lang.*;
012import votorola.g.web.*;
013
014
015/** General utilities and services for GWT applications.
016  */
017public final class GWTX
018{
019
020
021    /** The single instance of GWTX.
022      */
023    public static GWTX i() { return instance; }
024
025
026        private static GWTX instance;
027
028        {   // constructed by GMod
029            if( instance != null ) throw new IllegalStateException();
030
031            instance = GWTX.this;
032        }
033
034
035
036   // ------------------------------------------------------------------------------------
037
038
039    /** The general event bus.
040      */
041    public EventBus bus() { return bus; }
042
043
044        private final EventBus bus = new SimpleEventBus()
045        {
046            public void fireEvent( final GwtEvent<?> e )
047            {
048                try{ super.fireEvent( e ); }
049                catch( Exception x ) { handleUncaughtException( x ); } // q.v. for reason
050            }
051            public void fireEventFromSource( final GwtEvent<?> e, final Object source )
052            {
053                try{ super.fireEventFromSource( e, source ); }
054                catch( Exception x ) { handleUncaughtException( x ); }
055            }
056        };
057
058
059
060    /** The browser under which the application is running.  The browser is also specified
061      * on the document body as style class "voB-<em>browser</em>", where <em>browser</em>
062      * is named in lowercase.
063      */
064    public Browser browser() { return browser; }
065
066        // No apparent way to read "user.agent" property on which GWT deferred binding is
067        // based.  Its values are too coarse in any case.  This is more flexible.
068
069
070        private final Browser browser;
071
072        {
073            final String userAgent = Window.Navigator.getUserAgent();
074            if( userAgent.contains( " Chrome/" )) browser = Browser.CHROME;
075            else browser = Browser.UNKNOWN;
076            Document.get().getBody().addClassName( "voB-" + browser.name().toLowerCase() );
077        }
078
079
080
081    /** The pattern of a dev mode URL.  The pattern splits the URL into groups: (1) the
082      * leading portion, (2) the <code>gwt.codesvr</code> parameter value and (3) the
083      * trailing portion which might be null (or empty depending on the browser).  If the
084      * trailing portion is non-null/non-empty, then it may be appended directly to the
085      * leading portion to produce a URL without the <code>gwt.codesvr</code> parameter;
086      * otherwise the leading portion alone will suffice for this purpose provided the
087      * trailing '?'  or '&' character is chopped.  An example of a dev mode URL is:
088      *
089      *     <blockquote><code>http://localhost/path?p1=x&q1=x&gwt.codesvr=localhost:9997&p3=x&q3=x</code></blockquote>
090      *
091      * The resulting values for the groups are (1) "http://localhost/path?p1=x&q1=x&",
092      * (2) "localhost:9997" and (3) "p3=x&q3=x".
093      *
094      *     @see GWT#isProdMode()
095      */
096    public static final RegExp DEV_MODE_LOCATION_PATTERN = RegExp.compile(
097      "^(.+\\?(?:.+&)?)gwt\\.codesvr=(.+?:[0-9]+)&?(.+)?$" );
098      // LEADING                      CODESVR       TRAILING
099
100
101
102    /** Shows the exception in an alert box along with instructions for developers to get
103      * more information.  Beware that a {@linkplain
104      * GWT#setUncaughtExceptionHandler(GWT.UncaughtExceptionHandler) universal handler}
105      * will not necessarily catch exceptions during event dispatch (GWT 2.3, 2.4, Firefox
106      * and Chrome, is it only DOM events that slip through?).  They tend to hit the
107      * JavaScript console as umbrella exceptions with no name or line number.  Consider
108      * therefore adding explicit catchers to the dispatcher where possible, otherwise to
109      * the individual handlers.
110      *
111      *     @see <a href='http://code.google.com/p/google-web-toolkit/wiki/WebModeExceptions' target='_top'
112      *                                                                   >WebModeExceptions</a>
113      */
114    public static void handleUncaughtException( final Throwable t )
115    {
116        final StringBuilder b = new StringBuilder();
117        b.append( "Wild exception: " );
118        b.append( t.toString() );
119        b.append( '\n' );
120        if( GWT.isProdMode() )
121        {
122            appendProd( t, b );
123            b.append(
124              "Consider re-running in devmode where a stack trace is output to the console." );
125        }
126        else
127        {
128            b.append( '\n' );
129            t.printStackTrace();
130            b.append( "A trace was output to the console." );
131        }
132        Window.alert( b.toString() );
133    }
134
135
136
137    /** A common string builder cleared for atomic reuse.  Consider calling
138      * stringBuilderClear().trimToSize() after building a very large string.
139      */
140    public static StringBuilder stringBuilderClear()
141    {
142        return StringBuilderX.clear( stringBuilder );
143    }
144
145
146        private static final StringBuilder stringBuilder = new StringBuilder();
147
148
149
150    /** Returns the same value if it is less than {@linkplain Integer#MAX_VALUE
151      * Integer.MAX_VALUE}, otherwise returns <code>min</code>.  This convenience method
152      * serves mainly to document that integral wrapping is not emulated by GWT.
153      *
154      *     @see <a href='https://developers.google.com/web-toolkit/doc/latest/DevGuideCodingBasicsCompatibility#language' target='_top'
155      *       >Language support</a>
156      */
157    public static int wrapMaxInteger( final int value, final int min )
158    {
159        return value > Integer.MAX_VALUE? min: value;
160    }
161
162
163
164//// P r i v a t e ///////////////////////////////////////////////////////////////////////
165
166
167    /** Appends details in lieu of a proper stack trace, which is hard to obtain in
168      * production mode because of limitiations in GWT's emulation of Java's stream
169      * classes.
170      */
171    private static void appendProd( Throwable t, final StringBuilder b )
172    {
173        appendProd_trace( t, b );
174        if( t instanceof UmbrellaException )
175        {
176            b.append( "Caused by: \n" );
177            final UmbrellaException xU = (UmbrellaException)t;
178            for( Throwable cause: xU.getCauses() )
179            {
180                b.append( cause.toString() );
181                appendProd_trace( cause, b );
182            }
183        }
184    }
185
186
187
188    private static void appendProd_trace( Throwable t, final StringBuilder b )
189    {
190        final StackTraceElement[] trace = t.getStackTrace();
191        int eN = trace.length;
192        if( eN == 0 )
193        {
194            b.append( "    No stack trace available.  It may help to add this to the module:\n" );
195            b.append( "    <set-property name='compiler.stackMode' value='emulated'/>\n" );
196        }
197        else
198        {
199            if( eN > 3 ) eN = 3; // max
200            for( int e = 0; e < eN; ++e )
201            {
202                final StackTraceElement elem = trace[e];
203                b.append( "    " );
204                b.append( elem.getClassName() );
205                b.append( '.' );
206                b.append( elem.getMethodName() );
207                b.append( " (" );
208                b.append( elem.getLineNumber() );
209                b.append( ")\n" );
210            }
211        }
212        b.append( "\n" );
213    }
214
215
216
217}