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}