<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;DEYHQH07fip7ImA9WxNUFk0.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802</id><updated>2009-11-07T07:02:11.306-08:00</updated><title>Timefire</title><subtitle type="html">"Time is the school in which we learn,
Time is the fire in which we burn."</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://timepedia.blogspot.com/" /><link rel="hub" href="http://pubsubhubbub.appspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Timepedia</name><uri>http://www.blogger.com/profile/01408958797259838599</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>73</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><link rel="self" href="http://feeds.feedburner.com/blogspot/timepedia" type="application/atom+xml" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry gd:etag="W/&quot;CUMDQX4yeip7ImA9WxNTFk8.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-3371243308417222097</id><published>2009-08-17T20:13:00.000-07:00</published><updated>2009-08-18T12:24:30.092-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-18T12:24:30.092-07:00</app:edited><title>On Reducing the Size of Compressed Javascript (by up to 20%)</title><content type="html">Recently, I've been studying ways of reducing the download size of Javascript applications produced by Google Web Toolkit, while preserving or improving startup time. There are a number of ways to do this, the first of which is to transform Javascript code into a form that is naturally more succinct, while eliminating unused code, or deferring the load of some code until later.&lt;br /&gt;&lt;br /&gt;Unfortunately, there is a tradeoff inherent in some of the fancier ways of making JS more succinct that use run-time code generation. This is put to good use in libraries like &lt;i&gt;jQuery&lt;/i&gt;. For a large application, like Google Wave, this would increase startup time.  Another technique is to teach a compiler even better optimizations for reducing redundant code, work which is ongoing in Google Web Toolkit.&lt;br /&gt;&lt;br /&gt;Other attempts to reduce JS size have looked at ways to remove extraneous whitespace, shorten identifiers, and tokenize/pack JS statements, and while the former two are win-win one-way transforms, the latter has the trade-off requirement of being a reversible transform, meaning that it has to be decoded by an additional JS stage after load, a step that hurts startup performance.&lt;br /&gt;&lt;br /&gt;Instead, I was drawn to a reversible transform the browser already includes support for: gzip compression,  and decided to ask the question: what effect does the large-scale structure of the JS output code have on the &lt;i&gt;&lt;a href="http://en.wikipedia.org/wiki/DEFLATE_(algorithm)"&gt;DEFLATE&lt;/a&gt;&lt;/i&gt; algorithm of GZIP which is used to serve up compressed script? The answer it turns out, is substantial.&lt;br /&gt;&lt;h2&gt;Reversible Transforms&lt;/h2&gt;&lt;br /&gt;If we didn't care about startup performance, we could spend all the time in the world unpacking JS code by including a custom tailored compressor, perhaps the &lt;a href="http://en.wikipedia.org/wiki/Lzma"&gt;LZMA (Lempel-Ziv Markov Chaining)&lt;/a&gt; algorithm or &lt;a href="http://en.wikipedia.org/wiki/Prediction_by_partial_matching"&gt;PPM (Prediction by Partial Matching)&lt;/a&gt;, but unfortunately, these algorithms would be very slow to run in Javascript. &lt;br /&gt;&lt;br /&gt;That leaves us with the built in GZIP compression that most web browsers include support for. The question is, can we improve compression while remaining compatible with the browser's decoder? There is an existing example of improving GZIP by injecting a reversible transform: bzip2 and the &lt;a href="http://en.wikipedia.org/wiki/Burrows-Wheeler_transform"&gt;Burrows-Wheeler Transform&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;Which brings up an interesting idea, can we do something like BWT's sort but for Javascript, in a way that doesn't require an extra pass to 'undo' the sorting?&lt;br /&gt;&lt;h2&gt;Deflate: A digression&lt;/h2&gt;&lt;br /&gt;Before answering that question, it would be helpful to look at some of the restrictions of the &lt;i&gt;deflate&lt;/i&gt; algorithm, and how code ordering could affect the outcome. &lt;br /&gt;&lt;br /&gt;The deflate algorithm is a combination of two compression algorithms: &lt;a href="http://en.wikipedia.org/wiki/LZ77_and_LZ78"&gt;LZ77&lt;/a&gt; and &lt;a href="http://en.wikipedia.org/wiki/Huffman_coding"&gt;Huffman coding&lt;/a&gt;. Huffman coding is a variable length code technique where symbols are replaced with codes based on their frequency of occurrence. So for example, the most common letter in the English alphabet is the letter &lt;tt&gt;e&lt;/tt&gt;, so replacing &lt;tt&gt;e&lt;/tt&gt; with a short code, but giving &lt;tt&gt;q&lt;/tt&gt;, the least frequent character, a longer code, leads to shorter text overall.  Huffman encoding is order independent, so that 'eq' is the same length as 'qe'. &lt;br /&gt;&lt;br /&gt;LZ77 on the other hand, is a sliding window compression algorithm based on replacing strings with backwards references to previous strings in the input. For example, the string "this is a test" contains the substring 'is' repeated twice in a row, separated by a space, so that the second occurance of 'is' can be replaced with a length (2 characters, and a backwards distance (-3 positions),  called the length-distance pair. The compressor typically scans backwards in the input within a certain window (e.g. 8,192 characters or 32,768 characters) looking for matches and then encoding them as length-distance pairs. The compressor has some freedom as to how hard it will search for a match before giving up (something I'll get to later).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;One important effect of the sliding window limit is that if two Javascript functions with common substrings are separated by more than this distance, they cannot be matched.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;But DEFLATE has another trick up its sleeve. It encodes the output of the LZ77 algorithm using Huffman encoding, but uses one Huffman tree for the character literals and length codes, and another Huffman tree for the backwards distances. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Which suggests another potential gain is to try and arrange for the backwards distances to be both small, and frequently the same, so as it produce shorter Huffman codes.&lt;/b&gt;&lt;br /&gt;&lt;h2&gt;A sort with no undo&lt;/h2&gt;&lt;br /&gt;Thus far, intuition would tell us that if we could rearrange the input in order to bring more closely matched text closer together, we might be able to push up compression ratios, but how to do this without something to reverse the sort? Fortunately, unlike BWT, we are not working on plain text, but machine readable program code. We already know of something that rearranges code and moves it around, it's called a compiler!&lt;br /&gt;&lt;br /&gt;We would not want a one-way text compression sort which say, brings Hamlet's prologue, climax, and epilogue together and randomly rearranges the rest, but your browser has no problem running your Javascript code if function &lt;tt&gt;foo&lt;/tt&gt; is declared after &lt;tt&gt;bar&lt;/tt&gt;, or before it. Thus, as long as two statements do not have order-dependencies, we can arrange them freely, in fact, all top-level function declarations can be rearranged arbitrarily.&lt;br /&gt;&lt;h2&gt;Sort by Clustering&lt;/h2&gt;&lt;br /&gt;So, we've finally arrived at the point in which we have to devise our sort algorithm. What we want to do is, for any function &lt;tt&gt;Foo&lt;/tt&gt; ensure that the best-match for this function in the whole program appears closest within the sliding window that the compressor will use. But bringing two functions that are most similar together in a greedy algorithm fashion won't necessarily produce the optimal result, as it is possible that by moving function &lt;tt&gt;Bar&lt;/tt&gt; closer to function &lt;tt&gt;Foo&lt;/tt&gt;, you've moved it away from lots of other functions that were good matches as well.  As an example, consider these strings:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;"Hello World"&lt;br /&gt;"Hello World is a common test string used."&lt;br /&gt;...several thousand strings later including the phrase "hello world" ...&lt;br /&gt;"Common test strings used are metasyntactic variables like Foo and Bar."&lt;br /&gt;"Variables like Foo and Bar are very common."&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If we selectively moved the second string close to the first, we might prevent it from matching the other good matches later, especially if the window fills up.&lt;br /&gt;&lt;br /&gt;One idea I started to think about was to repurpose &lt;i&gt;Document Clustering&lt;/i&gt; techniques towards code. Document Clustering is commonly used in information retrieval systems to find related documents. Typically, a document is encoded using some technique to measure word importance, such as representing each word by its &lt;i&gt;&lt;a href="http://en.wikipedia.org/wiki/Tf-idf"&gt;term frequency inverse document frequency&lt;/a&gt;&lt;/i&gt;. Then, any two documents can be compared by some distance metric, for example, taking the tf-idf weightings of terms as a vector in N-space and computing the cosine between them.&lt;br /&gt;&lt;br /&gt;In this case, we'd let each function be a separate document, and the entire program be like the corpus of documents. We'd then choose some encoding to weigh Javascript grammar nodes by importance in a way that would produce good LZ77 matches, and then proceed in a bottom-up clustering fashion. First, we'd construct all the pairs of functions which match best. Pick a function, pair it with its best match, call that Cluster 1. Pick another function, pair it with its best match, call that Cluster 2, and so on. After this procedure is done, pick a Cluster, and find its nearest Cluster (according to some metric) and pair them up in a Cluster of 4 functions. After that's done, pair up 4-Clusters into Clusters of 8, and so on, until the final cluster encompasses the whole program.&lt;br /&gt;&lt;h2&gt;What's a good metric?&lt;/h2&gt;&lt;br /&gt;Ideally, a good metric for comparing two functions would take into account the way the GZIP compressor searches for matches and encodes them. It is an interesting theoretical question, but for practical implementation purposes, I needed something that performs reasonably well, now. One algorithm that is pretty good at finding strings of rough similarity, even in the presence of noise, is the dynamic programming edit-distance algorithm. It's deployed widely in one variant or another in the bioinformatics industry for gene sequence alignment (Smith-Waterman, HMMR, etc), but the version commonly used for general CS work is the &lt;a href="http://en.wikipedia.org/wiki/Levenshtein_distance"&gt;Levenshtein Distance&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Results&lt;/h2&gt;&lt;br /&gt;Taking Levenshtein Distance as my metric, I produced a greedy variant as a patch to the GWT compiler. The greedy variant does not implement bottom-up clustering, but instead, sorts all functions by length first (suggested by GWT team member Lex Spoon), and then performs a linear scan over the sorted functions, picking the best match each time to the previous output. The source code is &lt;a href="http://code.google.com/p/google-web-toolkit/source/detail?r=5972"&gt;here&lt;/a&gt;. Remarkably, even this simple algorithm produces nice gains? How much? Well, if you do nothing else, it produces a 5-7% gain in GWT's Showcase application when compressed with gzip -9. But there's more that we can do!&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Optimizing GWT's Obfuscation&lt;/h2&gt;&lt;br /&gt;When the GWT compiler is executed in obfuscated mode, it renames every single Javascript identifier in the whole program except for foreign Javascript. Ideally, you want the shortest id possible. Up until recently, GWT limited the first character of an identifier to an alphabet of 32-characters, and for strings of length 2 or more, it used base-64 characters. However, due to a clever patch by GWT community member Andriasyan (&lt;a href="http://code.google.com/p/google-web-toolkit/issues/detail?id=2448"&gt;Issue #2448&lt;/a&gt;), the first character can actually be chosen from a base-54 alphabet. This has the effect of shrinking output size by up to 1.75% prior to compression.&lt;br /&gt;&lt;br /&gt;We're not done yet! The GWT compiler has other tricks up its sleeve. It performs the renaming from the bottom-most scopes upwards, letting each scope reuse variable identifiers as they become free. However, it unfortunately did not insure that identifiers were picked in a stable order. Thus, a function of 3 variables could be declared as &lt;tt&gt;function foo(a,b,c){&lt;/tt&gt; or as &lt;tt&gt;function foo(b,c,a){&lt;/tt&gt;. Obviously, this would lead to suboptimal compression since every function of 3 variables should have the same suffix &lt;tt&gt; (a,b,c){&lt;/tt&gt;. The effect of making obfuscated identifier allocation have a stable sort order combined with the base-54 patch produces an incredible gain of 10.5% when compressed with gzip -9.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Choosing a different GZIP implementation&lt;/h2&gt;&lt;br /&gt;The deflate algorithm actually gives some leeway to the compressor implementor in terms of how matches are found. ZLIB's implementation on which GZIP is based is actually not the best implementation, although it might be the best &lt;i&gt;patent unencumbered one.&lt;/i&gt; Rather, the inventor of the &lt;b&gt;LZMA&lt;/b&gt; algorithm has his own DEFLATE implementation in his &lt;b&gt;7-zip&lt;/b&gt; utility, which produces 4% better output than gzip by my estimates.&lt;br /&gt;&lt;br /&gt;Combining base-54/base-64 obfuscated identifier encoding, stable sort-order for identifier allocation, my greedy clustering-by-edit-distance sort algorithm, and 7-zip as a gzip-compatible compressor, yields an incredible 21% reduction of the Showcase application. On a large 500k Javascript application, this means an additional 100k bandwidth is saved, with no performance penalty!&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;br /&gt;A general purpose technique (&lt;b&gt;Cromwell Clustering Transform?  (CCT) :)&lt;/b&gt;) for compilers to rearrange code for compression efficiency (vs say, cache locality) has been presented, which achieves non-trivial compression efficiency gains in Javascript output from the GWT compiler. Some of these techniques can also be applied to hand-written Javascript as well and included in 3rd party JS minification utilities.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Addendum&lt;/h2&gt;&lt;br /&gt;From reading a description of the algorithm, it may be hard to visualize. Here is sample output from GWT's Showcase application:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;function lU(a){gU();while(bU){bU.a.a.a.p=false;$wnd.alert(s5b+a+t5b);bU=bU.b}cU=null}&lt;br /&gt;function vR(a){qR();while(lR){lR.a.b.a.p=false;$wnd.alert(s5b+a+t5b);lR=lR.b}mR=null}&lt;br /&gt;function QR(a){LR();while(GR){GR.a.b.a.p=false;$wnd.alert(s5b+a+t5b);GR=GR.b}HR=null}&lt;br /&gt;function mS(a){hS();while(cS){cS.a.b.a.p=false;$wnd.alert(s5b+a+t5b);cS=cS.b}dS=null}&lt;br /&gt;function KS(a){FS();while(AS){AS.a.b.a.p=false;$wnd.alert(s5b+a+t5b);AS=AS.b}BS=null}&lt;br /&gt;function gT(a){bT();while(YS){YS.a.b.a.p=false;$wnd.alert(s5b+a+t5b);YS=YS.b}ZS=null}&lt;br /&gt;function PT(a){KT();while(FT){FT.a.b.a.p=false;$wnd.alert(s5b+a+t5b);FT=FT.b}GT=null}&lt;br /&gt;function JU(a){EU();while(zU){zU.a.b.a.p=false;$wnd.alert(s5b+a+t5b);zU=zU.b}AU=null}&lt;br /&gt;function fV(a){aV();while(XU){XU.a.a.a.p=false;$wnd.alert(s5b+a+t5b);XU=XU.b}YU=null}&lt;br /&gt;function DV(a){yV();while(tV){tV.a.a.a.p=false;$wnd.alert(s5b+a+t5b);tV=tV.b}uV=null}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;For any function, note that the one immediately following it contains large numbers of common substrings of length 3 or greater.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-3371243308417222097?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=TB-KJg_-RXQ:k-8q3UrdEao:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=TB-KJg_-RXQ:k-8q3UrdEao:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=TB-KJg_-RXQ:k-8q3UrdEao:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=TB-KJg_-RXQ:k-8q3UrdEao:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=TB-KJg_-RXQ:k-8q3UrdEao:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=TB-KJg_-RXQ:k-8q3UrdEao:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=TB-KJg_-RXQ:k-8q3UrdEao:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/TB-KJg_-RXQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/3371243308417222097/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=3371243308417222097" title="12 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/3371243308417222097?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/3371243308417222097?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/TB-KJg_-RXQ/on-reducing-size-of-compressed.html" title="On Reducing the Size of Compressed Javascript (by up to 20%)" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">12</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/08/on-reducing-size-of-compressed.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0QGQ3Y9fCp7ImA9WxJRE04.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-8673348949122587554</id><published>2009-05-14T12:49:00.001-07:00</published><updated>2009-05-14T13:02:02.864-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-14T13:02:02.864-07:00</app:edited><title>Gwt Query and Timefire @ Google I/O</title><content type="html">If you're attending &lt;a href="http://code.google.com/events/io/"&gt;Google I/O&lt;/a&gt; this year (and you should, it's not to late to &lt;a href="https://io2009.event-projects.com/"&gt;Register&lt;/a&gt;, $50 for Academia!) I'm doing two sessions. The first:&lt;br /&gt;&lt;a href="http://code.google.com/events/io/sessions.html"&gt;Progressively Enhance Ajax Applications with Google Web Toolkit and GQuery&lt;/a&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;&lt;br /&gt;Google Web Toolkit&lt;br /&gt;Ray Cromwell&lt;br /&gt;Thu 1:15p - 2:15p&lt;br /&gt;Don't throw away your Web 1.0 websites just yet. In this session, you'll learn how to take your existing websites, and layer AJAX on top of them using GQuery, a jQuery style library for GWT. Learn how GQuery benefits performance over existing solutions, increases productivity, and reduces defects by leveraging the GWT tool chain.&lt;br /&gt;&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;The second:&lt;br /&gt;&lt;a href="http://code.google.com/events/io/sessions.html"&gt;Building Applications With Google APIs&lt;br /&gt;&lt;/a&gt;&lt;blockquote&gt;&lt;i&gt;&lt;br /&gt;Google Web Toolkit&lt;br /&gt;Ray Cromwell&lt;br /&gt;Thu 3:45p - 4:45p&lt;br /&gt;Google offers a wide variety of APIs in many domains that together form a complete platform, from authentication and authorization, cloud computing, and social networking, to visualization, mobile computing, and Google Web Toolkit. In this talk, we will walk though a complex application that integrates many APIs together, how each can solve a different need in your application, how you can share code between GWT, Android, and App Engine, and how you can monetize your application with Google Checkout.&lt;br /&gt;&lt;/blockquote&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;In the first presentation, I'll detail the revamped and relaunched GWT Query library, a jQuery clone for GWT featuring fast implementation, terse syntax, type safety, and a few other tricks. If you've ever wanted to use GWT, but didn't want to program in a Desktop Widget metaphor, this talk is for you.&lt;br /&gt;&lt;br /&gt;The second presentation sounds stale (I couldn't write more exciting copy, honest!) but it is infact, the story of Timefire, a software-as-a-service visualization/analytics platform we are building that leverages a number of Google APIs, from AppEngine, to GWT, to Android, OpenSocial, Checkout, and beyond. If you've got a need, Google has an API for it, and in my opinion, what they have is looking more and more like a complete platform, from client to cloud.&lt;br /&gt;&lt;br /&gt;So if this sounds interesting, mark Thursday on your calendars, and come see the amazing things you can do with Google Web Toolkit.&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-8673348949122587554?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=3DCFwFtoy2k:JdZR1ETyp0I:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=3DCFwFtoy2k:JdZR1ETyp0I:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=3DCFwFtoy2k:JdZR1ETyp0I:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=3DCFwFtoy2k:JdZR1ETyp0I:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=3DCFwFtoy2k:JdZR1ETyp0I:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=3DCFwFtoy2k:JdZR1ETyp0I:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=3DCFwFtoy2k:JdZR1ETyp0I:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/3DCFwFtoy2k" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/8673348949122587554/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=8673348949122587554" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/8673348949122587554?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/8673348949122587554?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/3DCFwFtoy2k/gwt-query-and-timefire-google-io.html" title="Gwt Query and Timefire @ Google I/O" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/05/gwt-query-and-timefire-google-io.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkcEQXwyeyp7ImA9WxJTFk0.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-7023805243463162600</id><published>2009-04-24T13:43:00.000-07:00</published><updated>2009-04-24T14:20:00.293-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-24T14:20:00.293-07:00</app:edited><title>GWT RPC over arbitrary transports: Uber RPC</title><content type="html">A frequent request that pops up in the GWT groups is how to run GWT RPC over non-XHR transport mechanisms, like script-tag injection, cross-domain POSTs, or OpenSocial Gadget's &lt;code&gt;makeRequest()&lt;/code&gt;. &lt;br /&gt;&lt;br /&gt;There are a few ways to do this, including patching GWT itself, but the path of least resistance would be a module that you could inherit which would provide this functionality, without compromising or changing your client code in any way. &lt;br /&gt;&lt;h2&gt;Step 1: Overwrite GWT ServiceInterfaceProxyGenerator&lt;/h2&gt;&lt;br /&gt;In order to handle alternate transport mechanisms, we need to take over generation of the RPC client stub that is created by the builtin generators, to do this, we add the following entries to a module file:&lt;br /&gt;&lt;pre name="code" class="brush:xml"&gt;&lt;br /&gt; &lt;generate-with&lt;br /&gt;            class="com.google.gwt.user.rebind.rpc.UberServiceInterfaceProxyGenerator"&gt;&lt;br /&gt;        &lt;when-type-assignable&lt;br /&gt;                class="com.google.gwt.user.client.rpc.RemoteService"/&gt;&lt;br /&gt;    &lt;/generate-with&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This tells GWT to invoke our &lt;code&gt;UberServiceIntefaceProxyGenerator&lt;/code&gt; whenever someone calls &lt;code&gt;GWT.create(RemoteService.class)&lt;/code&gt;. There are some package-protected methods we need access to, so we arrange for our class to be in the &lt;code&gt;com.google.gwt.user.rebind.rpc&lt;/code&gt; package. Next, we want to modify the generator to allow substitution of arbitrary client stub superclasses.&lt;br /&gt;&lt;pre name="code" class="brush:xml"&gt;&lt;br /&gt;&amp;lt;define-property name="gwt.rpc.proxySuperclass"&lt;br /&gt;                     values="org_timepedia_uberrpc_client_RpcServiceProxy"/&gt;&lt;br /&gt;&amp;lt;set-property name="gwt.rpc.proxySuperclass"&lt;br /&gt;                  value="org_timepedia_uberrpc_client_RpcServiceProxy"/&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This construct is used to pass a compile time parameter to the generator as to which class will be used as a client stub, &lt;code&gt;org.timepedia.uberrpc.client.RpcServiceProxy&lt;/code&gt;.  Here is our proxy generator source, which exists merely to redirect to &lt;code&gt;UberProxyCreator&lt;/code&gt;&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;public class UberServiceInterfaceProxyGenerator extends Generator {&lt;br /&gt;&lt;br /&gt;  @Override&lt;br /&gt;  public String generate(TreeLogger logger, GeneratorContext ctx,&lt;br /&gt;      String requestedClass) throws UnableToCompleteException {&lt;br /&gt;&lt;br /&gt;    logger.log(TreeLogger.WARN, "Running UberProxyCreator", null);&lt;br /&gt;    &lt;br /&gt;    TypeOracle typeOracle = ctx.getTypeOracle();&lt;br /&gt;    assert (typeOracle != null);&lt;br /&gt;&lt;br /&gt;    JClassType remoteService = typeOracle.findType(requestedClass);&lt;br /&gt;    if (remoteService == null) {&lt;br /&gt;      logger.log(TreeLogger.ERROR, "Unable to find metadata for type '"&lt;br /&gt;          + requestedClass + "'", null);&lt;br /&gt;      throw new UnableToCompleteException();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    if (remoteService.isInterface() == null) {&lt;br /&gt;      logger.log(TreeLogger.ERROR, remoteService.getQualifiedSourceName()&lt;br /&gt;          + " is not an interface", null);&lt;br /&gt;      throw new UnableToCompleteException();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    UberProxyCreator proxyCreator = new UberProxyCreator(remoteService);&lt;br /&gt;&lt;br /&gt;    TreeLogger proxyLogger = logger.branch(TreeLogger.DEBUG,&lt;br /&gt;        "Generating client proxy for remote service interface '"&lt;br /&gt;            + remoteService.getQualifiedSourceName() + "'", null);&lt;br /&gt;&lt;br /&gt;    return proxyCreator.create(proxyLogger, ctx);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Step 2; Override the superclass of the generated client proxy stub&lt;/h2&gt;&lt;br /&gt;This source was mostly copied unchanged from the original, except for the line which calls &lt;code&gt;UberProxyCreator&lt;/code&gt;. The bulk of the work is done there. Again, I copied the source, but made just a few changes to the routine which creates the &lt;code&gt;SourceWriter&lt;/code&gt;&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;private SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext ctx,&lt;br /&gt;      JClassType serviceAsync) {&lt;br /&gt;    JPackage serviceIntfPkg = serviceAsync.getPackage();&lt;br /&gt;    String packageName = serviceIntfPkg == null ? "" : serviceIntfPkg.getName();&lt;br /&gt;    PrintWriter printWriter = ctx&lt;br /&gt;        .tryCreate(logger, packageName, getProxySimpleName());&lt;br /&gt;    if (printWriter == null) {&lt;br /&gt;      return null;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    ClassSourceFileComposerFactory composerFactory&lt;br /&gt;        = new ClassSourceFileComposerFactory(packageName, getProxySimpleName());&lt;br /&gt;&lt;br /&gt;    String[] imports = new String[]{RemoteServiceProxy.class.getCanonicalName(),&lt;br /&gt;        ClientSerializationStreamWriter.class.getCanonicalName(),&lt;br /&gt;        GWT.class.getCanonicalName(), ResponseReader.class.getCanonicalName(),&lt;br /&gt;        SerializationException.class.getCanonicalName()};&lt;br /&gt;    for (String imp : imports) {&lt;br /&gt;      composerFactory.addImport(imp);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    String rpcSuper = null;&lt;br /&gt;    try {&lt;br /&gt;      // retrieve user-defined superclass from module file&lt;br /&gt;      rpcSuper = ctx.getPropertyOracle()&lt;br /&gt;          .getPropertyValue(logger, "gwt.rpc.proxySuperclass");&lt;br /&gt;      if (rpcSuper != null) {&lt;br /&gt;        rpcSuper = rpcSuper.replaceAll("_", ".");&lt;br /&gt;      }&lt;br /&gt;    } catch (Exception e) {&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // allow defining a custom superclass to customize the RPC implementation&lt;br /&gt;    composerFactory.setSuperclass(rpcSuper);&lt;br /&gt;    composerFactory.addImplementedInterface(&lt;br /&gt;        serviceAsync.getErasedType().getQualifiedSourceName());&lt;br /&gt;&lt;br /&gt;    composerFactory.addImplementedInterface(&lt;br /&gt;        serviceAsync.getErasedType().getQualifiedSourceName());&lt;br /&gt;&lt;br /&gt;    return composerFactory.createSourceWriter(ctx, printWriter);&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Step 3: Write your own Proxy&lt;/h2&gt;&lt;br /&gt;This is where you come in, since you have to decide how you're going to transport the RPC payload, such as putting it as a URL GET parameter, a POST parameter, or using an OpenSocial container. Here is some skeleton code showing you how to override the &lt;code&gt;doInvoke&lt;/code&gt; method. This example is pseudo-code for how you'd do it using &lt;code&gt;gadgets.io.makeRequest()&lt;/code&gt; in an OpenSocial container.&lt;br /&gt;&lt;pre name="code" class="brush:java"&gt;&lt;br /&gt;public class RpcServiceProxy extends RemoteServiceProxy {&lt;br /&gt;&lt;br /&gt;  protected GadgetRpcServiceProxy(String moduleBaseURL,&lt;br /&gt;      String remoteServiceRelativePath, String serializationPolicyName,&lt;br /&gt;      Serializer serializer) {&lt;br /&gt;    super(moduleBaseURL, remoteServiceRelativePath, serializationPolicyName,&lt;br /&gt;        serializer);&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  static boolean isReturnValue(String encodedResponse) {&lt;br /&gt;     return encodedResponse.startsWith("//OK");&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;    * Return &lt;code&gt;true&lt;/code&gt; if the encoded response contains a checked&lt;br /&gt;    * exception that was thrown by the method invocation.&lt;br /&gt;    * &lt;br /&gt;    * @param encodedResponse&lt;br /&gt;    * @return &lt;code&gt;true&lt;/code&gt; if the encoded response contains a checked&lt;br /&gt;    *         exception that was thrown by the method invocation&lt;br /&gt;    */&lt;br /&gt;   static boolean isThrownException(String encodedResponse) {&lt;br /&gt;     return encodedResponse.startsWith("//EX");&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;  public static final String RPC_PAYLOAD_PARAM="rpcpayload";&lt;br /&gt;  &lt;br /&gt;  @Override&lt;br /&gt;  protected &lt;T&gt; Request doInvoke(&lt;br /&gt;      final RequestCallbackAdapter.ResponseReader responseReader, String methodName, int invocationCount,&lt;br /&gt;      String requestData, final AsyncCallback&lt;T&gt; tAsyncCallback) {&lt;br /&gt;&lt;br /&gt;    try {&lt;br /&gt;      makeRequest(getServiceEntryPoint(), "text/x-gwt-rpc; charset=utf-8", requestData, new AsyncCallback&lt;String&gt;() {&lt;br /&gt;        public void onFailure(Throwable throwable) {&lt;br /&gt;           tAsyncCallback.onFailure(new InvocationException("Unable to initiate the asynchronous service invocation -- check the network connection"));&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public void onSuccess(String encodedResponse) {&lt;br /&gt;          try {&lt;br /&gt;            if(isReturnValue(encodedResponse)) {&lt;br /&gt;              tAsyncCallback.onSuccess((T) responseReader.read(createStreamReader(encodedResponse)));&lt;br /&gt;            }&lt;br /&gt;            else if(isThrownException(encodedResponse)) {&lt;br /&gt;              tAsyncCallback.onFailure((Throwable)responseReader.read(createStreamReader(encodedResponse)));&lt;br /&gt;              &lt;br /&gt;            }&lt;br /&gt;            else {&lt;br /&gt;              tAsyncCallback.onFailure(new InvocationException("Unknown return value type"));&lt;br /&gt;            }&lt;br /&gt;          } catch (SerializationException e) {&lt;br /&gt;            tAsyncCallback.onFailure(new InvocationException("Failure deserializing object "+e));&lt;br /&gt;          }&lt;br /&gt;        }&lt;br /&gt;      });&lt;br /&gt;    } catch (Exception ex) {&lt;br /&gt;      InvocationException iex = new InvocationException(&lt;br /&gt;          "Unable to initiate the asynchronous service invocation -- check the network connection",&lt;br /&gt;          ex);&lt;br /&gt;      tAsyncCallback.onFailure(iex);&lt;br /&gt;    } finally {&lt;br /&gt;      if (RemoteServiceProxy.isStatsAvailable()&lt;br /&gt;          &amp;&amp; RemoteServiceProxy.stats(RemoteServiceProxy.bytesStat(methodName,&lt;br /&gt;          invocationCount, requestData.length(), "requestSent"))) {&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;    return null;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private native void makeRequest(String serviceEntryPoint, String contentType,&lt;br /&gt;      String requestData, AsyncCallback tAsyncCallback) /*-{&lt;br /&gt;      var params = {};&lt;br /&gt;      params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;&lt;br /&gt;      &lt;br /&gt;      params[gadgets.io.RequestParameters.AUTHORIZATION]=gadgets.io.AuthorizationType.SIGNED;&lt;br /&gt;      params[gadgets.io.RequestParameters.METHOD]=gadgets.io.MethodType.GET;&lt;br /&gt;      &lt;br /&gt;      gadgets.io.makeRequest(serviceEntryPoint+"?"+@org.timepedia.uberrpc.client.UberRpcServiceProxy::RPC_PAYLOAD_PARAM+"="+encodeURIComponent(requestData), function(resp) { &lt;br /&gt;          if(resp.errors &amp;&amp; resp.errors.length &gt; 0) {&lt;br /&gt;            tAsyncCallback.@com.google.gwt.user.client.rpc.AsyncCallback::onFailure(Ljava/lang/Throwable;)(null)&lt;br /&gt;          }&lt;br /&gt;          else {&lt;br /&gt;          tAsyncCallback.@com.google.gwt.user.client.rpc.AsyncCallback::onSuccess(Ljava/lang/Object;)(resp.text);&lt;br /&gt;          }&lt;br /&gt;      }, params);&lt;br /&gt;  }-*/;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Step 4: Modify RemoteServiceServlet&lt;/h2&gt;&lt;br /&gt;The last step is to modify RemoteServiceServlet so that it understands the new transport formats you've devised. Here's an example of one that would handle the incoming OpenSocial makeRequest(). This one handles GET or POST requests with the incoming payload as a form parameter.&lt;br /&gt;&lt;pre name="code" class="brush:java"&gt;&lt;br /&gt;public class GadgetServiceServlet extends RemoteServiceServlet {&lt;br /&gt;&lt;br /&gt;  @Override&lt;br /&gt;  protected void doGet(HttpServletRequest httpServletRequest,&lt;br /&gt;      HttpServletResponse httpServletResponse)&lt;br /&gt;      throws ServletException, IOException {&lt;br /&gt;    doPost(httpServletRequest, httpServletResponse);&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  @Override&lt;br /&gt;  protected String readContent(HttpServletRequest httpServletRequest)&lt;br /&gt;      throws ServletException, IOException {&lt;br /&gt;    String str = httpServletRequest.getMethod().equals("POST") ? RPCServletUtils&lt;br /&gt;        .readContentAsUtf8(httpServletRequest, false) : httpServletRequest&lt;br /&gt;        .getParameter(RpcServiceProxy.RPC_PAYLOAD_PARAM);&lt;br /&gt;    String ustr = URLDecoder.decode(str);&lt;br /&gt;    return ustr;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To use, you'd just subclass this servlet.&lt;br /&gt;&lt;h2&gt;The Sky's the Limit&lt;/h2&gt;&lt;br /&gt;Once you get to this point, you can start imagining all of the cool things you can do. Cross-domain POSTs using the &lt;a href="http://development.lombardi.com/?p=611"&gt;window.name&lt;/a&gt; trick. OAuth signed RPC requests, verified in the servlet, done by OpenSocial containers. FaceBook integration. RPC over JSON, Protocol Buffers, Thrift. I'm trying to cobble together some of this stuff for a future UberRPC module, but due to talks I'm giving at the upcoming Google I/O, I'm a little too swamped to make them release worthy at this point.&lt;br /&gt;&lt;br /&gt;Much credit goes to &lt;b&gt;Alex Epshteyn&lt;/b&gt; for his original proposal on the GWT Contributors list, which I picked up (over an inferior method I had been using to make Gadgets work), and integrated into a more graceful override of the default RPC behavior.&lt;br /&gt;&lt;br /&gt;If you're going to Google I/O, I will be doing two talks this year. One on Progressive Enhancement using GWT and a new library I've written, and a second on Building an Application on Google's Open Stack, which is a walkthrough of a sophisticated GWT app which leverages about a dozen Google APIs.&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-7023805243463162600?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=_H8Zaium-3U:hSGmzvCa83E:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=_H8Zaium-3U:hSGmzvCa83E:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=_H8Zaium-3U:hSGmzvCa83E:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=_H8Zaium-3U:hSGmzvCa83E:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=_H8Zaium-3U:hSGmzvCa83E:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=_H8Zaium-3U:hSGmzvCa83E:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=_H8Zaium-3U:hSGmzvCa83E:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/_H8Zaium-3U" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/7023805243463162600/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=7023805243463162600" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/7023805243463162600?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/7023805243463162600?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/_H8Zaium-3U/gwt-rpc-over-arbitrary-transports-uber.html" title="GWT RPC over arbitrary transports: Uber RPC" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/04/gwt-rpc-over-arbitrary-transports-uber.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkAHQng-eip7ImA9WxVaEUg.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-1759163326631528806</id><published>2009-04-07T19:43:00.000-07:00</published><updated>2009-04-07T19:45:33.652-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-07T19:45:33.652-07:00</app:edited><title>Small update to GWT Exporter</title><content type="html">I recently discovered a pretty critical bug in GWT Exporter that can cause an infinite loop doing export. This was fixed in version 2.06 which available in the trunk and maven repository.&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-1759163326631528806?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=mDEh9Xps49U:dN0M-rFWFxM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=mDEh9Xps49U:dN0M-rFWFxM:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=mDEh9Xps49U:dN0M-rFWFxM:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=mDEh9Xps49U:dN0M-rFWFxM:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=mDEh9Xps49U:dN0M-rFWFxM:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=mDEh9Xps49U:dN0M-rFWFxM:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=mDEh9Xps49U:dN0M-rFWFxM:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/mDEh9Xps49U" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/1759163326631528806/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=1759163326631528806" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/1759163326631528806?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/1759163326631528806?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/mDEh9Xps49U/small-update-to-gwt-exporter.html" title="Small update to GWT Exporter" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/04/small-update-to-gwt-exporter.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck8CRHo_eCp7ImA9WxVaEUo.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-309724405496464279</id><published>2009-04-07T17:54:00.000-07:00</published><updated>2009-04-07T23:07:45.440-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-07T23:07:45.440-07:00</app:edited><title>Google AppEngine and GWT now a marriage made in heaven</title><content type="html">The announcement that Google AppEngine now supports Java is incredible news. Not just because it opens the doors to running arbitrary JVM languages like Scala, JRuby, PHP, etc on AppEngine, but because of the ability to wire up Java on the client, and Java on the server, through Google Web Toolkit. You can use all of your familiar Java tools for editing, debugging, testing, and packaging. &lt;br /&gt;&lt;br /&gt;With the new system, you can write a POJO, add JPA or JDO annotations, and write server-side logic to persist these POJOs in either a RDBMS like MySQL, or in BigTable/AppEngine. Moreover, you can export your DAO or logic interfaces through GWT RPC, and call them directly from the client, seamlessly, and painlessly.&lt;br /&gt;&lt;h2&gt;Almost Painlessly&lt;/h2&gt;&lt;br /&gt;The one hitch you'll encounter as a GWT developer is trying to serialize or deserialize persistence capable types. This is nothing new for GWT developers who have tried this with Hibernate before, and there are workarounds such as Hibernate4GWT. This problem occurs because the persistence classes are enhanced with an extra field to hold state which enables them to work when detached from the persistence context.  GWT RPC computes its own CRC based on the fields of a class in order to ensure compatibility between server and client and the extra field causes problems.&lt;br /&gt;&lt;br /&gt;In general, when it comes to sending serialized ORM POJOs down the wire, I think it's a risky practice, because you're likely to pull in a lot more of the reachable object tree than you bargained for unless you're careful. A better approach might be to use DTOs based on ProtocolBuffers.&lt;br /&gt;&lt;br /&gt;However, it is sometimes nice to do it if your POJOs are relatively flat and you want to rapidly prototype. If your insist on using your ORM'ed POJOs over RPC, there is a trick to making it work.&lt;br /&gt;&lt;h2&gt;Making JDO/JPA enhanced classes work over GWT RPC&lt;/h2&gt;&lt;br /&gt;The first step to making things work is to disable detachable objects and tag your class as serializable.&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "false")&lt;br /&gt;public class MyPojo implements Serializable {&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This does two things. First, it tells the persistence engine that you'll be managing object identity, usually through a primary key, and secondly, when your POJO is accessed outside of a transaction/session, you want it to be &lt;b&gt;transient&lt;/b&gt; not &lt;b&gt;detached&lt;/b&gt;. A detached object remembers where it came from, so that after modifications, it can be reattached  and merged back into the datastore. A transient object forgets that it once came from the datastore, and thus if you try to repersist it, you'll end up inserting a copy.&lt;br /&gt;&lt;br /&gt;This has a major downside in terms of ease of use, but it does prevent the enhancer from injecting hidden fields into your class to manage detached state, and it is these hidden fields which break GWT RPC compatibility.&lt;br /&gt;&lt;h2&gt;But I don't want copies!&lt;/h2&gt;&lt;br /&gt;In using transient objects, you'll break a desired design pattern, which is to fetch an object through RPC, modify its properties, and send the same object back through RPC to be merged into the datastore. A quick and dirty work around is to use reflection to lookup an attached object on the server, copy all of the persistent fields from the transient object, and then merge the persistent object. Here's a prototype class that does this (but not recursively, so it doesn't handle anything but primitive fields):&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;public class PersistenceHelper {&lt;br /&gt;  public static &lt;T&gt; Object findPrimaryKey(T tInstance) {&lt;br /&gt;    if (tInstance == null) {&lt;br /&gt;      return null;&lt;br /&gt;    }&lt;br /&gt;    for (Field l : tInstance.getClass().getDeclaredFields()) {&lt;br /&gt;      if (l.getAnnotation(PrimaryKey.class) != null&lt;br /&gt;          || l.getAnnotation(Id.class) != null) {&lt;br /&gt;        l.setAccessible(true);&lt;br /&gt;        try {&lt;br /&gt;          return l.get(tInstance);&lt;br /&gt;        } catch (IllegalArgumentException e) {&lt;br /&gt;          e.printStackTrace();&lt;br /&gt;          return null;&lt;br /&gt;        } catch (IllegalAccessException e) {&lt;br /&gt;          e.printStackTrace();&lt;br /&gt;          return null;&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;    return new IllegalArgumentException(&lt;br /&gt;        "Class " + tInstance.getClass().getName()&lt;br /&gt;            + " does not have a method called getId()");&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public static &lt;T&gt; void copyPersistentFields(Object entity, T tInstance)&lt;br /&gt;      throws IllegalAccessException, NoSuchMethodException,&lt;br /&gt;      InvocationTargetException {&lt;br /&gt;    for (Method f : tInstance.getClass().getMethods()) {&lt;br /&gt;      if (f.getName().startsWith("set") &amp;&amp; Character&lt;br /&gt;          .isUpperCase(f.getName().charAt(3))) {&lt;br /&gt;       f.setAccessible(true);&lt;br /&gt;        Method getter = tInstance.getClass()&lt;br /&gt;            .getMethod("get" + f.getName().substring(3));&lt;br /&gt;        getter.setAccessible(true);&lt;br /&gt;        f.invoke(entity, getter.invoke(tInstance));&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The way you'd typically use this is as follows:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;public &lt;T&gt; T mergeTransient(T tInstance) {&lt;br /&gt;    EntityManager e = em.get();&lt;br /&gt;    if(e.contains(tInstance)) {&lt;br /&gt;      e.persist(tInstance);&lt;br /&gt;      return tInstance;&lt;br /&gt;    } else {&lt;br /&gt;      Object primaryKey = PersistenceHelper.findPrimaryKey(tInstance);&lt;br /&gt;      if(primaryKey != null) {&lt;br /&gt;        Object entity = e.find(tInstance.getClass(), primaryKey);&lt;br /&gt;        if(entity == null) {&lt;br /&gt;          e.persist(tInstance);&lt;br /&gt;          return tInstance;&lt;br /&gt;        }&lt;br /&gt;        else {&lt;br /&gt;          try {&lt;br /&gt;            PersistenceHelper.copyPersistentFields(entity, tInstance);&lt;br /&gt;          } catch (IllegalAccessException e1) {&lt;br /&gt;            e1.printStackTrace();&lt;br /&gt;            throw new IllegalArgumentException("Can't copy fields from transient class to persistent class.");&lt;br /&gt;          } catch (NoSuchMethodException e1) {&lt;br /&gt;            throw new IllegalArgumentException("Can't copy fields from transient class to persistent class.");&lt;br /&gt;          } catch (InvocationTargetException e1) {&lt;br /&gt;            throw new IllegalArgumentException("Can't copy fields from transient class to persistent class.");&lt;br /&gt;          }&lt;br /&gt;          e.persist(entity);&lt;br /&gt;          return (T) entity;&lt;br /&gt;        }&lt;br /&gt;      } else {&lt;br /&gt;        // primary key may be null, assume insert&lt;br /&gt;        e.persist(tInstance);&lt;br /&gt;        return tInstance;&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Less than ideal&lt;/h2&gt;&lt;br /&gt;After experimenting with this pattern, I've come to the conclusion that although it works, I don't feel warm and cozy serializing instances out of the datastore, I like to have full control over what I'm sending down to the client so I can optimize for size and speed. However, I don't want to write boilerplate sychronization code for DTOs. In a later article, I'll detail a pattern for using ProtocolBuffers with GWT and a DSL for terse/concise manipulation of them.&lt;br /&gt;&lt;h2&gt;It's still awesome&lt;/h2&gt;&lt;br /&gt;Even though there are some issue surrounding using RPC seamlessly with the Datastore ORM later, it completely trumps the time saved not having to do ANY configuration AT ALL to deploy an application. Words cannot describe how much of a time saver this is. No messing around with apt-get. No editing Apache configs. No Setting up log rotation and archiving. No dealing with backups. No bother with figuring out the right way to shard your db for your expected growth. No need to harden your own machines and firewalls. No need to provision anything but the application ID. &lt;br /&gt;&lt;br /&gt;To be sure, there are still things you can't do on AppEngine. I can't run JNI. I can't launch threads. I can't use Java2D/JAI/JavaSound. And probably most relevant, I can't host long-running comet sessions.  And if you really really need to do those, you can rent a server somewhere to do it.&lt;br /&gt;&lt;br /&gt;However, the majority of applications don't use these capabilies, and for these developers, AppEngine is an epic win.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-309724405496464279?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=F_52xuTBi98:QGc1TTlXiPY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=F_52xuTBi98:QGc1TTlXiPY:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=F_52xuTBi98:QGc1TTlXiPY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=F_52xuTBi98:QGc1TTlXiPY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=F_52xuTBi98:QGc1TTlXiPY:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=F_52xuTBi98:QGc1TTlXiPY:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=F_52xuTBi98:QGc1TTlXiPY:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/F_52xuTBi98" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/309724405496464279/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=309724405496464279" title="8 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/309724405496464279?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/309724405496464279?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/F_52xuTBi98/google-appengine-and-gwt-now-marriage.html" title="Google AppEngine and GWT now a marriage made in heaven" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">8</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/04/google-appengine-and-gwt-now-marriage.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0cBSHg_cCp7ImA9WxVbF0w.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-9197365961575606779</id><published>2009-04-02T15:28:00.001-07:00</published><updated>2009-04-02T16:30:59.648-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-02T16:30:59.648-07:00</app:edited><title>GWT's type system is more powerful than Java</title><content type="html">This may come as a shock to some people. Isn't GWT just Java syntax you say? Yes, it is, and it does not extend the Java grammar in anyway. Yet, it is nonetheless true that GWT is more powerful than Java.&lt;br /&gt;&lt;h2&gt;The Overlay Type&lt;/h2&gt;&lt;br /&gt;The reason why GWT is more powerful is because it is actually a unification of two type systems, Java and Javascript. And while it seems that these are relatively walled off from one another, there is a bridge that unites the two, and that is the &lt;a href="http://code.google.com/p/google-web-toolkit/wiki/OverlayTypes"&gt;GWT Overlay Type&lt;/a&gt; system.&lt;br /&gt;&lt;br /&gt;The overlay system in essence, permits the 'overlay' or attachment of Java types to Javascript objects. And since you can pass Java objects into Javascript and back, this means you can in fact, overlay types on Java as well. &lt;br /&gt;&lt;h2&gt;Categories and Extension Methods&lt;/h2&gt;&lt;br /&gt;Some languages have a facility called &lt;a href="http://docs.codehaus.org/display/GROOVY/Groovy+Categories"&gt;Categories&lt;/a&gt; or &lt;a href="http://en.wikipedia.org/wiki/Extension_method"&gt;Extension Methods&lt;/a&gt;, modern examples include Objective-C, Groovy, and C#. A category allows you to pretend as if an existing reference implements additional methods, when it actually doesn't.&lt;br /&gt;&lt;br /&gt;In reality, they are syntax sugar for invocation of static utility functions. That is:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;SomeType x = new SomeType();&lt;br /&gt;SomeTypeUtils.doSomething(x);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;becomes&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;SomeType x = new SomeType();&lt;br /&gt;x.doSomething();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Transparently, behind the scenes, the compiler rewrites the invocation of &lt;code&gt;x.doSomething()&lt;/code&gt; into &lt;code&gt;SomeTypeUtils.doSomething(x)&lt;/code&gt;. GWT's JavaScriptObject overlays are essentially Categories, as the compiler transparently rewrites methods that appear to exist on an Overlay into static method calls on the underlying JavaScriptObject. This is one reason why JSO methods have to be effectively final, as there is no polymorphism allowed.&lt;br /&gt;&lt;br /&gt;This all sounds very interesting and theoretical, but what's the practical benefit?&lt;br /&gt;&lt;h2&gt;Type-Safe Enumerations with Zero Overhead&lt;/h2&gt;&lt;br /&gt;Let's say you want to write a method that can set the display property of an element to one of the legal values, and only the legal values. Traditional approaches would include using a Java enum and writing a setter method that accepts only this type:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;enum DisplayType {&lt;br /&gt;   BLOCK, INLINE, TABLE, NONE;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static void setDisplay(Element e, DisplayType t) {&lt;br /&gt;   e.getStyle().setProperty("display", t.name().toLowerCase());&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Unfortunately, this will generate a lot of bloat, since each enum value is a class, the class must be initialized by a static initializer, and the ultimate CSS property value string has to be obtained through method calls that might not inline because of polymorphic dispatch.&lt;br /&gt;&lt;br /&gt;Think about what we want here. Don't we really just want to create a subclass of &lt;code&gt;java.lang.String&lt;/code&gt;, create a bunch of this String subclass constants, and write a method to accept that type? Unfortunately, java.lang.String is final. You can't subclass it in Java. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;But you can in GWT, and I'll show you how!&lt;/b&gt;&lt;br /&gt;&lt;h2&gt;Turn a String into a JSO&lt;/h2&gt;&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;public class DisplayType extends JavaScriptObject {&lt;br /&gt;   protected DisplayType() {}&lt;br /&gt;   public native static DisplayType make(String str) /*-{&lt;br /&gt;      return str;&lt;br /&gt;   }-*/;&lt;br /&gt;&lt;br /&gt;   public native String value() /*-{&lt;br /&gt;      return this;&lt;br /&gt;   }-*/;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;For brevity, I left out the extra code to support Hosted Mode. In hosted mode, you must wrap the 'str' argument in an array, e.g. &lt;code&gt;[str]&lt;/code&gt; and fetch it using &lt;code&gt;return this[0]&lt;/code&gt;, but that's not important. What this code is effectively doing is casting a &lt;code&gt;String&lt;/code&gt; into a &lt;code&gt;DisplayType&lt;/code&gt; and imposing an additional categorical method on this reference, which is &lt;code&gt;value()&lt;/code&gt;. Keep in mind, the &lt;code&gt;value()&lt;/code&gt; is never actually attached to the prototype of the underlying Javascript object.&lt;br /&gt;&lt;br /&gt;To use,&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;DisplayType NONE = DisplayType.make("none");&lt;br /&gt;DisplayType BLOCK = DisplayType.make("block");&lt;br /&gt;DisplayType INLINE = DisplayType.make("inline");&lt;br /&gt;DisplayType TABLE = DisplayType.make("TABLE");&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now, what is the output of the GWT compiler when compiling &lt;code&gt;element.getStyle().setProperty("display", BLOCK.value())&lt;/code&gt;? Here it is:&lt;br /&gt;&lt;pre name="code" class="brush: js"&gt;&lt;br /&gt;element.style['display']='block';&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In fact, even calling the &lt;code&gt;setDisplay()&lt;/code&gt; method with various &lt;code&gt;DisplayType&lt;/code&gt; enums results in inlined assignments to the element.style property with no method calls!&lt;br /&gt;&lt;h2&gt;Adding methods to Numbers&lt;/h2&gt;&lt;br /&gt;In Groovy, Scala, and some languages, you can even add methods to primitive integers. Using GWT overlay types, you can even do this!&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt; public class Int extends JavaScriptObject {&lt;br /&gt;    protected Int() {}&lt;br /&gt;    public static native Int make(int x) /*-{&lt;br /&gt;      return x;&lt;br /&gt;    }-*/;&lt;br /&gt;    &lt;br /&gt;    final public native int value() /*-{&lt;br /&gt;      return this;&lt;br /&gt;    }-*/;&lt;br /&gt;    &lt;br /&gt;    final public Int square() {&lt;br /&gt;      return make(value() * value());&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; int val = (int) Duration.currentTimeMillis();&lt;br /&gt; Int x = Int.make(val);&lt;br /&gt; Int sq = x.square();&lt;br /&gt; Window.alert(String.valueOf(sq.value()));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And what do you think the generated code looks like? Try this:&lt;br /&gt;&lt;pre name="code" class="brush: js"&gt;&lt;br /&gt;val = (new Date()).getTime();&lt;br /&gt;x = val;&lt;br /&gt;sq = x * x;&lt;br /&gt;$wnd.alert('' + sq);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There you have it. I successfully added a method to a primitive 'int' called &lt;code&gt;square()&lt;/code&gt; without using a wrapper, and with no overhead whatsoever.  This opens the doors to implementing primitive 'wrappers' for GWT for int, double, float, etc, that are not wrappers at all and have no overhead, which would be very useful in many circumstances where Integer, Double, Float,  in java.lang are too heavy.&lt;br /&gt;&lt;br /&gt;So, can we conclude from this that GWT is more powerful than Java? ;-)&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-9197365961575606779?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=5bo-NkF8s8Y:UJyUawo8_EA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=5bo-NkF8s8Y:UJyUawo8_EA:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=5bo-NkF8s8Y:UJyUawo8_EA:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=5bo-NkF8s8Y:UJyUawo8_EA:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=5bo-NkF8s8Y:UJyUawo8_EA:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=5bo-NkF8s8Y:UJyUawo8_EA:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=5bo-NkF8s8Y:UJyUawo8_EA:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/5bo-NkF8s8Y" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/9197365961575606779/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=9197365961575606779" title="11 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/9197365961575606779?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/9197365961575606779?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/5bo-NkF8s8Y/gwts-type-system-is-more-powerful-than.html" title="GWT's type system is more powerful than Java" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">11</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/04/gwts-type-system-is-more-powerful-than.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkcHQ3w-fSp7ImA9WxVVGEU.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-3164273180936690104</id><published>2009-03-12T12:46:00.000-07:00</published><updated>2009-03-12T13:00:32.255-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-12T13:00:32.255-07:00</app:edited><title>Relaxing constraints on GWT.create()</title><content type="html">Recently I've been playing around with some GWT ideas that really cry out for a more liberal deferred binding system. Currently, GWT imposes the restriction that deferred binding can only happen through the GWT.create() method.  There's a couple of problems with this:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Can't narrow type signature for custom library&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Can't create a method to decorate the returned result&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Can't parameterized or override the bindings at callee site&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;To illustrate this, I've compiled some motivation examples.&lt;br /&gt;&lt;h1&gt;RPC requests signed by OAuth&lt;/h1&gt;&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;interface MyService extends RemoteService&amp;lt;MyServiceAsync&gt; { ... }&lt;br /&gt;// example call&lt;br /&gt;OAuth.withSignature(MyService.class).method1(arg1, arg2, asyncCallback);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;public class OAuth {&lt;br /&gt;  @GwtCreate  // method must be static, class parameter must be final&lt;br /&gt;  public static &amp;lt;S, T extends RemoteService&amp;lt;S&gt;&gt; S withSignature(final Class&amp;lt;T&gt; service) {&lt;br /&gt;        // GWT.create with non-literal ONLY allowed &lt;br /&gt;        // if enclosing method is @GwtCreate and &lt;br /&gt;       // variable statically resolvable to literal param&lt;br /&gt;        S async = GWT.create(service);  &lt;br /&gt;        ((ServiceDefTarget)async).setRequestBuilderCallback(new OAuthRequestBuilderSigner());&lt;br /&gt;        return async;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Currently today, it would look like this:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;MyServiceAsync foo = GWT.create(MyService.class);&lt;br /&gt;RequestBuilder rb = foo.method1(arg1, arg2, asyncCallback);&lt;br /&gt;OAuthRequestBuilderSigner.sign(rb);&lt;br /&gt;rb.send();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The tight coupling at the callsite also makes it difficult to swap out implementations easy (like using AuthSub, or one of the other 4 Google Friend Connect auth techniques).  An alternative is to make a separate subtype of each with a custom generator, e.g.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;MyServiceOAuth extends MyService, GWT.create(MyServiceOAuth.class)&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;I'd argue that the above gets cumbersome with multiple services and multiple authentication types. I've taken some liberties above by adding a type parameter to RemoteService to make the return type statically resolvable, as well as allowing a global callback mechanism on ServiceDefTarget for RequestBuilder override. Note that type-safety is ensured, one can't call this method with something that is not a RemoteService, and it will be flagged at &lt;b&gt;edit-time&lt;/b&gt; in your IDE.&lt;br /&gt;&lt;h1&gt;An EasyMock library for Hosted/Web Mode&lt;/h1&gt;&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;Subscriber mock = GMock.mock(Subscriber.class);&lt;br /&gt;publisher.add(subscriber);&lt;br /&gt;//...&lt;br /&gt;GMock.replay(mock);&lt;br /&gt;&lt;br /&gt;public class GMock {&lt;br /&gt;  // selectively override module binding rules ONLY for this method&lt;br /&gt;  @GwtCreate(generator=com.gmock.rebind.GMockGenerator.class)&lt;br /&gt;  public static &amp;lt;T&gt; T mock(Class&amp;lt;T&gt; toMock) {&lt;br /&gt;        return GWT.create(toMock);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In this method, the &lt;code&gt;mock()&lt;/code&gt; method acts like &lt;code&gt;GWT.create()&lt;/code&gt; except that it overrides the current binding rules, forcing the specified generator.&lt;br /&gt;&lt;h1&gt;GWT Exporter&lt;/h1&gt;&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;Exporter.export(Foo.class);&lt;br /&gt;&lt;br /&gt;public class Exporter {&lt;br /&gt;   @GwtCreate&lt;br /&gt;   public static &amp;lt;T extends Exportable&gt; void export(Class&amp;lt;T&gt; exportable) {&lt;br /&gt;       ExporterImpl ximpl = GWT.create(exportable);&lt;br /&gt;       ximpl.export();&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note, the type safety, one can't try to Export a non-exportable class. This will be flagged at edit time in the IDE.&lt;br /&gt;&lt;h1&gt;GIN/Guice dependency injection&lt;/h1&gt;&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;Processor pimpl = Gin.inject(Processor.class, TestProcessorModule.class);&lt;br /&gt;&lt;br /&gt;public class Gin {&lt;br /&gt;  @GwtCreate(generator = com.google.gin.rebind.GInjectorGenerator)&lt;br /&gt;  public static &amp;lt;T, S extends AbstractGinModule&gt; &lt;br /&gt;      T inject(Class&amp;lt;T&gt; interf, @GwtCreateParam Class&amp;lt;S&gt; module) {&lt;br /&gt;         return GWT.create(interf, module);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This one is more controversial, but allows GWT.create() to be parameterized by literal metadata that is available to the generator. This is semantically equivalent to what we have today:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;@GinModule(TestProcessorModule.class)&lt;br /&gt;interface MyTestInjector extends GInjector {&lt;br /&gt;   Processor getProcessor();&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;but without the need to actually write the interface.&lt;br /&gt;&lt;h1&gt;Combing compile-time and run-time parameters&lt;/h1&gt;&lt;br /&gt;Final example, mix-and-match both compile-time variables and run-time variables:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;OAuth.withSignature(MyService.class, debugMode ? "/debugService" : "/MyService").method1(arg1, arg2, asyncCallback);&lt;br /&gt;&lt;br /&gt;public class OAuth {&lt;br /&gt;  @GwtCreate  // method must be static, class parameter must be final&lt;br /&gt;  public static &amp;lt;S, T extends RemoteService&amp;lt;S&gt;&gt; &lt;br /&gt;        S withSignature(final Class&amp;lt;T&gt; service, String endPoint) {&lt;br /&gt;        // GWT.create with non-literal ONLY allowed if enclosing method is &lt;br /&gt;        // @GwtCreate and variable statically resolvable to literal param&lt;br /&gt;        S async = GWT.create(service);  &lt;br /&gt;        ((ServiceDefTarget)async).setRequestBuilderCallback(new OAuthRequestBuilderSigner());&lt;br /&gt;        ((ServiceDefTarget)async).setServiceEntryPoint(endPoint);&lt;br /&gt;        return async;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-3164273180936690104?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=7IEBlWZ3Spo:WrlCEhahBas:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=7IEBlWZ3Spo:WrlCEhahBas:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=7IEBlWZ3Spo:WrlCEhahBas:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=7IEBlWZ3Spo:WrlCEhahBas:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=7IEBlWZ3Spo:WrlCEhahBas:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=7IEBlWZ3Spo:WrlCEhahBas:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=7IEBlWZ3Spo:WrlCEhahBas:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/7IEBlWZ3Spo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/3164273180936690104/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=3164273180936690104" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/3164273180936690104?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/3164273180936690104?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/7IEBlWZ3Spo/relaxing-constraints-on-gwtcreate.html" title="Relaxing constraints on GWT.create()" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/03/relaxing-constraints-on-gwtcreate.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEEFQHc4eip7ImA9WxVVE0U.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-8367385920916423474</id><published>2009-03-06T15:34:00.000-08:00</published><updated>2009-03-06T15:36:51.932-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-06T15:36:51.932-08:00</app:edited><title>Spring cleaning and the blog</title><content type="html">To try and improve readability and reach, I made a few changes today:&lt;br /&gt;&lt;br /&gt;1) Re-enabled SyntaxHighlighter for code snippets&lt;br /&gt;2) Widened the width of the main content to 600px (for code blocks)&lt;br /&gt;3) removed Twitter and added Google Friend Connect&lt;br /&gt;4) enabled CAPTCHA to reduce comment span&lt;br /&gt;5) tweak some colors so the subsections are easier to read.&lt;br /&gt;&lt;br /&gt;If you have any other advice for improving the Look and Feel, leave a comment.&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-8367385920916423474?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=0yarqcvFGxw:wP91ZfO4nY4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=0yarqcvFGxw:wP91ZfO4nY4:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=0yarqcvFGxw:wP91ZfO4nY4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=0yarqcvFGxw:wP91ZfO4nY4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=0yarqcvFGxw:wP91ZfO4nY4:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=0yarqcvFGxw:wP91ZfO4nY4:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=0yarqcvFGxw:wP91ZfO4nY4:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/0yarqcvFGxw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/8367385920916423474/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=8367385920916423474" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/8367385920916423474?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/8367385920916423474?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/0yarqcvFGxw/spring-cleaning-and-blog.html" title="Spring cleaning and the blog" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/03/spring-cleaning-and-blog.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0AHSXs7fyp7ImA9WxVVE0U.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-596031860531424032</id><published>2009-03-05T14:21:00.000-08:00</published><updated>2009-03-06T15:22:18.507-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-06T15:22:18.507-08:00</app:edited><title>Structural Typing for GWT and Javascript</title><content type="html">In a &lt;a href="http://timepedia.blogspot.com/2009/03/whats-next-for-gwt-exporter.html"&gt;previous post&lt;/a&gt; I described a type of impedance mismatch between Javascript and Java idioms that makes the GWT Exporter still less than ideal for supporting Javascript programmers. A short illustrative (but contrived) example:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;public class Customer implements Exportable {&lt;br /&gt;   private String firstName, lastName;&lt;br /&gt;   @Export String getFirstName() { &lt;br /&gt;      return firstName; &lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;   @Export void setFirstName(String fn) {&lt;br /&gt;      firstName = fn;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   @Export String getLastName() { &lt;br /&gt;      return lastName; &lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;   @Export void setLastName(String ln) {&lt;br /&gt;      lastName = fn;&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Today, you may use this export in Javascript like this:&lt;br /&gt;&lt;pre name="code" class="brush: js"&gt;&lt;br /&gt;var cust = new Customer();&lt;br /&gt;cust.setFirstName("Ray");&lt;br /&gt;cust.setLastName("Cromwell");&lt;br /&gt;Processor.doSomething(cust);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The example is contrived because you could use a constructor, but with more complex objects with nested types, you wouldn't use a constructor, but either a builder pattern, or inject the types after construction. &lt;br /&gt;&lt;br /&gt;Javascript developers however don't work in the world of Javabean interfaces, they prefer easy construction of configuration/builder information via object literals:&lt;br /&gt;&lt;pre name="code" class="brush: js"&gt;&lt;br /&gt;Processor.doSomething({firstName: "Ray", lastName: "Cromwell"})&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Moreover, when passing in say, a bind of event callbacks, they'd prefer to write:&lt;br /&gt;&lt;pre name="code" class="brush: js"&gt;&lt;br /&gt;foo.addEvents({&lt;br /&gt;    click: function(e) { ... }&lt;br /&gt;    move: function(e) { ... }&lt;br /&gt;    drop: function(e) { ... }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The challenge is to seemlessly bridge this idiomatic mismatch between JS and Java GWT code without actually having to write much bridging code or adapters.&lt;br /&gt;&lt;h2&gt;Structural Typing&lt;/h2&gt;&lt;br /&gt;Java is a &lt;a href="http://en.wikipedia.org/wiki/Manifest_typing"&gt;manifestly typed language&lt;/a&gt;. All types have to be declared, and type checking is done by explicit hierarchy. Javascript is a dynamically typed language, with essentially no type checking at all. Orthogonal to this is the concept of &lt;a href="http://en.wikipedia.org/wiki/Structural_type_system"&gt;Structural Typing&lt;/a&gt;. Haskell, ML, and Scala are all examples of languages which support structural typing. Structural typing was also planned for Javascript 2 before it got killed.&lt;br /&gt;&lt;br /&gt;So what's a structural type? Recall the &lt;code&gt;Customer&lt;/code&gt; example from the previous section. It was a class with two fields, firstName, and lastName, both Strings. If Java supported a structural type system, I could declare a method in two ways:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;public void process(Customer cust) { ... }&lt;br /&gt;&lt;br /&gt;public void process({firstName: String, lastName: String} cust) { ... } &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In this invented syntax, the &lt;code&gt;cust&lt;/code&gt; parameter to the second &lt;code&gt;process()&lt;/code&gt; function is an anonymous type, we don't know its real name. However, we are stating that as long as it consists of two fields named &lt;code&gt;"firstName"&lt;/code&gt;, and &lt;code&gt;"lastName"&lt;/code&gt;, and the types are both &lt;code&gt;String&lt;/code&gt;s, then we can access these fields and treat it like a Customer (although it may not be one)&lt;br /&gt;&lt;br /&gt;Hmm....I smell an idea...&lt;br /&gt;&lt;h2&gt;Structural Type Exports&lt;/h2&gt;&lt;br /&gt;What if I rewrite the Customer POJO class with a &lt;code&gt;@StructuralType&lt;/code&gt; annotation:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;@Export&lt;br /&gt;@StructuralType&lt;br /&gt;public class Customer implements Exportable { ... }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The dispatch code for an exported &lt;code&gt;process()&lt;/code&gt; function could then look like this:&lt;br /&gt;&lt;pre name="code" class="brush: js"&gt;&lt;br /&gt;$wnd.Processor.prototype.process = function(cust) {&lt;br /&gt;  if(cust instanceof $wnd.Customer &amp;&amp; isExportedInstance(cust)) {&lt;br /&gt;    // JSNI dispatch to @Processor::process(LCustomer;)(unwrap(cust));&lt;br /&gt;   }&lt;br /&gt;  else if(typeof(cust) == 'object') {&lt;br /&gt;      // cust is not an instance of the Customer POJO, but an object&lt;br /&gt;      if(cust.firstName != undefined &amp;&amp; cust.lastName != undefined) {&lt;br /&gt;          var scust = new Customer();&lt;br /&gt;          scust.setFirstName(cust.firstName);&lt;br /&gt;          scust.setLastName(cust.lastName); &lt;br /&gt;          @Processor::process(unwrap(scust));&lt;br /&gt;      }&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The GWT compiler would auto-inject this structural type check and initialization code simply by annotating a parameter or return type involved with &lt;code&gt;@StructuralType&lt;/code&gt;&lt;br /&gt;&lt;h2&gt;Refinements to the idea&lt;/h2&gt;&lt;br /&gt;Taking this a step further, one could override the expected type literal field names to be checked&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;@SType("fn")&lt;br /&gt;public void setFirstName(String firstName) { ... }&lt;br /&gt;@SType("ln")&lt;br /&gt;public void setLastName(String lastName) { ... }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;which would allow the object literal to be specified as &lt;code&gt;{fn: "Ray",ln: "Cromwell"}&lt;/code&gt;. Another extension would allow partial matches to succeed with default values supplied:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;@Optional("Ray")&lt;br /&gt;public void setFirstName(String firstName) { ... }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;which would reduce the structural type to just a &lt;code&gt;lastName&lt;/code&gt; field for matching purposes, but would allow the specification of &lt;code&gt;firstName&lt;/code&gt; to be supplied and injected into the setter as "Ray" if it wasn't present.&lt;br /&gt;&lt;h2&gt;Structurally Typed Interfaces&lt;/h2&gt;&lt;br /&gt;GWT Exporter already supports closure conversion for single-method Java interfaces. That is, if the following interface:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;public interface MyCallback {&lt;br /&gt;     public void go(Object arg);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;occurs as a type parameter in an exported method, the Javascript developer may supply &lt;code&gt;function(arg) { ... }&lt;/code&gt; and the GWT generated bridge code will automatically convert this into a GWT object instance that implements the &lt;code&gt;MyCallback&lt;/code&gt; interface type. What it cannot support is the example given earlier:&lt;br /&gt;&lt;pre name="code" class="brush: js"&gt;&lt;br /&gt;foo.addEvents({&lt;br /&gt;    click: function(e) { ... }&lt;br /&gt;    move: function(e) { ... }&lt;br /&gt;    drop: function(e) { ... }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;which would represent something like:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;public interface MyEventHandler {&lt;br /&gt;    public void onClick(Event e);&lt;br /&gt;    public void onMove(Event e);&lt;br /&gt;    public void onDrop(Event e);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;However, using structural typing conventions, and an &lt;code&gt;@StructuralType&lt;/code&gt; annotation on an interface, we can auto-convert object literals containing multiple function closures into a Java interface for GWT.&lt;br /&gt;&lt;br /&gt;I am still working out the details of the full overload resolution algorithm for overloaded function types, but some version of this proposal will make it into GWT Exporter 3.0.&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-596031860531424032?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=w4jrqjkVYkQ:sKxr7iCfFrI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=w4jrqjkVYkQ:sKxr7iCfFrI:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=w4jrqjkVYkQ:sKxr7iCfFrI:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=w4jrqjkVYkQ:sKxr7iCfFrI:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=w4jrqjkVYkQ:sKxr7iCfFrI:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=w4jrqjkVYkQ:sKxr7iCfFrI:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=w4jrqjkVYkQ:sKxr7iCfFrI:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/w4jrqjkVYkQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/596031860531424032/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=596031860531424032" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/596031860531424032?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/596031860531424032?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/w4jrqjkVYkQ/structural-typing-for-gwt-and.html" title="Structural Typing for GWT and Javascript" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/03/structural-typing-for-gwt-and.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEYDRn8-eip7ImA9WxVVE0U.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-873046768822963700</id><published>2009-03-05T14:09:00.000-08:00</published><updated>2009-03-06T15:29:37.152-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-06T15:29:37.152-08:00</app:edited><title>GWT Exporter 2.05 released</title><content type="html">The release adds two new features:&lt;br /&gt;&lt;br /&gt;1) arrays are return types or parameters supported, that is:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;@Export public double[] getFoo() { }&lt;br /&gt;@Export public SomeExportable[] getBar() {}&lt;br /&gt;@Export public void setFoo(double[] foo) { }&lt;br /&gt;@Export public void setBar(SomeExportable[] bar) { }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;now works, in both Web mode and Hosted Mode. &lt;br /&gt;&lt;br /&gt;2) Export Overlays implemented. This means you can now Export classes you don't own.&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;@ExportPackage("gwt")&lt;br /&gt;public interface WindowExport extends ExportOverlay&amp;lt;Window&gt; {&lt;br /&gt;   @Export void alert(String s);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;will export the GWT Window class into the namespace $wnd.gwt.Window, and export the alert() method of this class.&lt;br /&gt;&lt;br /&gt;The JARs are downloadable from: &lt;a href="http://timefire-repository.googlecode.com/svn/mavenrepo/org/timepedia/exporter/gwtexporter/2.05/"&gt;http://timefire-repository.googlecode.com/svn/mavenrepo/org/timepedia/exporter/gwtexporter/2.05/&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-873046768822963700?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=zzTBro6tKJI:z-IOc6yLQho:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=zzTBro6tKJI:z-IOc6yLQho:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=zzTBro6tKJI:z-IOc6yLQho:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=zzTBro6tKJI:z-IOc6yLQho:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=zzTBro6tKJI:z-IOc6yLQho:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=zzTBro6tKJI:z-IOc6yLQho:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=zzTBro6tKJI:z-IOc6yLQho:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/zzTBro6tKJI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/873046768822963700/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=873046768822963700" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/873046768822963700?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/873046768822963700?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/zzTBro6tKJI/gwt-exporter-205-released.html" title="GWT Exporter 2.05 released" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/03/gwt-exporter-205-released.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEUARXw9fCp7ImA9WxVVE0U.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-5669698379430808446</id><published>2009-03-03T14:26:00.000-08:00</published><updated>2009-03-06T15:30:44.264-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-06T15:30:44.264-08:00</app:edited><title>What's next for GWT Exporter?</title><content type="html">I have learned over the past year that a lot of Chronoscope users like to use the Javascript API, but the current API is unfortunately, a small sliver of the full power the GWT API offers. In dealing with increasing demand for more exported functions, I have run into some basic limitations that GWT-Exporter has that make it less than ideal for exporting idiomatic Javascript APIs.&lt;br /&gt;&lt;h2&gt;Exporting Classes you don't control&lt;/h2&gt;&lt;br /&gt;Since Chronoscope made the switch to the new GWT Incubator/GWT 1.6 Event handling system, I have been unable to export event handler functions, because the &lt;code&gt;HandlerRegistration&lt;/code&gt; type returned by the GWT event system is not an interface, making it difficult to wrap.  In general, if you don't own a class, you can't add Exporter annotations to it, which is problematic because there are lots of types you don't control.&lt;br /&gt;&lt;br /&gt;A new mechanism will be added in an upcoming GWT Exporter release, I dub Export Overlays, which will work something like this:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;@ExportPackage("gwt")&lt;br /&gt;public interface WindowExport extends ExportOverlay&amp;lt;Window&gt; {&lt;br /&gt;    @Export void alert(String message);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In the above code, we are exporting the GWT Window class in the Javascript package "gwt", but only exposing the alert() method. This will result in an exported wrapper in $wnd.gwt.Window with  the method published on $wnd.gwt.Window.prototype.alert.&lt;br /&gt;&lt;h2&gt;Fine control over Javascript-&gt;Java type conversions&lt;/h2&gt;&lt;br /&gt;Javascript doesn't support overloaded method dispatch as a language feature, but most Javascript libraries do use heavily overloaded functions with variable arguments and variable argument types. Currently, GWT Exporter supports Javascript overloading by arity, but not by type, so two Java methods of the same name and identical arity cannot be exported without overriding the exported name.&lt;br /&gt;&lt;br /&gt;Some of this will be mitigated by adding in automatic type checking for handling overloaded dispatch, but more is needed. Consider the following Javascript API:&lt;br /&gt;&lt;pre name="code" class="brush: js"&gt;&lt;br /&gt;plot.addHandler({&lt;br /&gt;    onMove: function(event) {  }&lt;br /&gt;    onHover: function(event) { }&lt;br /&gt;    onFocus: function(event) { }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is somewhat idiomatic Javascript, leveraging object literals to pass in multiple bindings. The current approach is to have 3 methods, addMoveHander, addHoverHandler, addFocusHandler, but this doesn't feel quite right to some Javascript programmers. Another problem is illustrated below:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;@Export&lt;br /&gt;public class Foo implements Exportable {&lt;br /&gt;   public void setFoo(Date date) { }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A Javascript developer would naturally want to write:&lt;br /&gt;&lt;pre name="code" class="brush: js"&gt;&lt;br /&gt;var date = new Date();&lt;br /&gt;foo.setFoo(date);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;But this won't work, because there is no automatic conversion in GWT Exporter between JS Date and java.util.Date. I could add this (as was done with JS String and java.lang.String), but that still leaves open the problem of the inability to pass Javascript types from third party Javascript libraries into GWT methods.&lt;br /&gt;&lt;h2&gt;Type Converter injection&lt;/h2&gt;&lt;br /&gt;What I'm currently looking at is a mechanism to declare Javascript fragments on Java methods which can type-convert arguments between the exported wrapper, before they invoke the underlying GWT method. Something like this:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;@Export&lt;br /&gt;public class Foo implements Exportable {&lt;br /&gt;  public void setFoo(@Convert(JsType.DATE, "new java.util.Date($_arg.getTime()$)") Date foo) { }   &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The syntax is still subject to change of course, but the idea is that you supply an annotation on each param, using an enum (STRING, FUNCTION, ARRAY, OBJECT, etc) which corresponds to the return value of the 'typeof' operator, or you use a special INSTANCE version which allows you to specify a match according to 'instanceof'.  Next, you supply a Java expression), including inline JSNI code (here shown in between $ $). The GWT Exporter then arranges to generate code to check the type, and inject your fragment before calling the underlying method.&lt;br /&gt;&lt;br /&gt;Another possibility is to add JSNI methods to Export Overlays which allow arbitrary conversion code to be specified and injected into the wrapper prior to invocation of methods.&lt;br /&gt;&lt;br /&gt;By combining this with the Export Overlay concept, it might be possible to deal with the idiomatic JS Literal pattern, while keeping JSNI methods out of classes that may not want them, or that you do not control.&lt;br /&gt;&lt;h2&gt;Primitive arrays an oversight&lt;/h2&gt;&lt;br /&gt;Finally, GWT Exporter currently does not support export of parameters or return types which are native Java arrays, which is a hole in the export type system. This will be added in a near future release.&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-5669698379430808446?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=VLZ-4vLFUO4:C6NWSinJTtc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=VLZ-4vLFUO4:C6NWSinJTtc:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=VLZ-4vLFUO4:C6NWSinJTtc:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=VLZ-4vLFUO4:C6NWSinJTtc:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=VLZ-4vLFUO4:C6NWSinJTtc:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=VLZ-4vLFUO4:C6NWSinJTtc:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=VLZ-4vLFUO4:C6NWSinJTtc:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/VLZ-4vLFUO4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/5669698379430808446/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=5669698379430808446" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/5669698379430808446?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/5669698379430808446?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/VLZ-4vLFUO4/whats-next-for-gwt-exporter.html" title="What's next for GWT Exporter?" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/03/whats-next-for-gwt-exporter.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEUCQ3gzfyp7ImA9WxVWEUs.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-197960992041688837</id><published>2009-02-19T21:54:00.000-08:00</published><updated>2009-02-20T13:44:22.687-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-20T13:44:22.687-08:00</app:edited><title>Small demo of Timelord</title><content type="html">Timefire is building a set of social collaboration and visualization tools to help people understand data better, and part of this has been the development of Chronoscope/Timescope, but an even larger part of this effort has been the development of an end user application that permits users to play with data, annotate it, and share their activity with friends.&lt;br /&gt;&lt;br /&gt;Timelord is the codename of our application, which runs as an embeddable widget, a Google Gadget, a Spreadsheet Gadget, or as a native Android application. It leverages OpenSocial when possible, to allow users to share activities, but also integrates with other contact sources, like GData Contacts API, Android address book, etc. &lt;br /&gt;&lt;br /&gt;One of the more important features of Timelord is the ability for users to create their own "mini-presentations" which script tours of the data. These mini-presentations are simply a special comment syntax, like Wiki-text or BBCode, to instruct the chart to perform actions which would normally be done programmatically, like adding markers, or zooming. &lt;br /&gt;&lt;br /&gt;A cool feature, I think, is the ability to do 2-way synchronization between these mini-presentations and YouTube video, which is demonstrated in the screencast below, courtesy of our friend Al Gore, and the YouTube Player API.&lt;br /&gt;&lt;br /&gt;Take it away, Mr Gore (best if watched in HD):&lt;br /&gt;&lt;object width="480" height="295"&gt;&lt;param name="movie" value="http://www.youtube.com/v/s5Y21t0u_Zw&amp;hl=en&amp;fs=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/s5Y21t0u_Zw&amp;hl=en&amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="295"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.youtube.com/watch?v=s5Y21t0u_Zw&amp;fmt=22"&gt;Watch it in HD here&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;UPDATE: YouTube seems to have disappeared my HD version, so there is an HD version mirrored at &lt;a href="http://vimeo.com/3292149"&gt;http://vimeo.com/3292149&lt;/a&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;I don't think I have to mention that this is all done in GWT? :)&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-197960992041688837?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=OIFV58OmeuE:1OTK5oN_j04:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=OIFV58OmeuE:1OTK5oN_j04:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=OIFV58OmeuE:1OTK5oN_j04:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=OIFV58OmeuE:1OTK5oN_j04:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=OIFV58OmeuE:1OTK5oN_j04:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=OIFV58OmeuE:1OTK5oN_j04:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=OIFV58OmeuE:1OTK5oN_j04:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/OIFV58OmeuE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/197960992041688837/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=197960992041688837" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/197960992041688837?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/197960992041688837?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/OIFV58OmeuE/small-demo-of-timelord.html" title="Small demo of Timelord" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/02/small-demo-of-timelord.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEICRHo9cSp7ImA9WxVQE0s.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-1483347796611558081</id><published>2009-01-30T17:44:00.000-08:00</published><updated>2009-01-30T17:49:25.469-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-30T17:49:25.469-08:00</app:edited><title>GWT Exporter now generates Javascript documentation</title><content type="html">I added a rough implementation of a Javadoc Doclet (my first!) to the exporter trunk. It is an extension of the Standard Doclet, so it not only generates Javadoc for your GWT classes, but automatically emits a file called "jsdoc.html" that includes documentation for every @Export in your source base. This allows you to automatically produce documentation for Javascript programmers from GWT source. I use the Maven Javadoc Plugin to execute this, but you can use ant tasks or the command line as well.&lt;br /&gt;&lt;br /&gt;It's quite ugly at the moment, no CSS stylesheet, but I'm working on a basic one. I'm happing to take any CSS donations from those who want to check out the trunk and play with it.&lt;br /&gt;&lt;br /&gt;Code project is : &lt;a href="http://code.google.com/p/gwt-exporter/"&gt;http://code.google.com/p/gwt-exporter/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-1483347796611558081?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=Q-VXxGi2EKM:v6KOyp9Nuv0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=Q-VXxGi2EKM:v6KOyp9Nuv0:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=Q-VXxGi2EKM:v6KOyp9Nuv0:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=Q-VXxGi2EKM:v6KOyp9Nuv0:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=Q-VXxGi2EKM:v6KOyp9Nuv0:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=Q-VXxGi2EKM:v6KOyp9Nuv0:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=Q-VXxGi2EKM:v6KOyp9Nuv0:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/Q-VXxGi2EKM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/1483347796611558081/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=1483347796611558081" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/1483347796611558081?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/1483347796611558081?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/Q-VXxGi2EKM/gwt-exporter-now-generates-javascript.html" title="GWT Exporter now generates Javascript documentation" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/01/gwt-exporter-now-generates-javascript.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUMGQ3c-fCp7ImA9WxVQEEQ.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-6769550557501351145</id><published>2009-01-27T13:52:00.000-08:00</published><updated>2009-01-27T13:57:02.954-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-27T13:57:02.954-08:00</app:edited><title>Screencast of Timescope, the commercial version of Chronoscope</title><content type="html">Hi Folks,&lt;br /&gt;  I've uploaded a &lt;a href="http://vimeo.com/2983154"&gt;screencast of Timescope&lt;/a&gt;, which is the commercial version of Chronoscope, to Vimeo. This is a sneak peak at some of the features in the upcoming release of Timescope, as well as Chronoscope 1.0. The chief differences between Timescope and Chronoscope, are a more accurate multiresolution algorithm that preserves data better when zooming out, new marker and renderer types (CandleStick charts for example), new marker types like Image, Axis, and Callout markers, iPhone support, a native Android version, as well as a Chart Server.&lt;br /&gt;&lt;br /&gt;The screencast is at: &lt;a href="http://vimeo.com/2983154"&gt;http://vimeo.com/2983154&lt;/a&gt;, I'd embed it here, but it has to be watched in HD in order to see what's going on.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-6769550557501351145?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=-TGBWuUqDGk:og2PypS9jtQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=-TGBWuUqDGk:og2PypS9jtQ:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=-TGBWuUqDGk:og2PypS9jtQ:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=-TGBWuUqDGk:og2PypS9jtQ:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=-TGBWuUqDGk:og2PypS9jtQ:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=-TGBWuUqDGk:og2PypS9jtQ:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=-TGBWuUqDGk:og2PypS9jtQ:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/-TGBWuUqDGk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/6769550557501351145/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=6769550557501351145" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/6769550557501351145?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/6769550557501351145?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/-TGBWuUqDGk/screencast-of-timescope-commercial.html" title="Screencast of Timescope, the commercial version of Chronoscope" /><author><name>Ray Cromwell</name><uri>http://www.blogger.com/profile/05070235196940096193</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06676410756360690868" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2009/01/screencast-of-timescope-commercial.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C04HQn85eCp7ImA9WxRbGEQ.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-1406274645105716</id><published>2008-12-09T21:57:00.000-08:00</published><updated>2008-12-09T22:52:13.120-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-09T22:52:13.120-08:00</app:edited><title>The OpenWeb language for the next 100 years is...</title><content type="html">Recently, there's been a lot of discussion about frameworks which translate or compile alternate programming languages to Javascript, John Resig's article being one of the more recent. At the heart of John's article is a lament that by abstracting people away from Javascript, they may not choose to learn the language. &lt;br /&gt;&lt;br /&gt;There is some merit to this, after all, many developers use programming languages which compile down to assembly, but never take the time to learn the CPU architecture. I'm guilty of such, I grew up in the 80s hacking 6502 (VIC20/C64)  and later 68k/80x86 code, but after my computer got powerful enough to compile C, I pretty much didn't keep up with ASM never learned MIPS, SPARC, or SSE (although I did take the time to learn DX9 shader assembly).&lt;br /&gt;&lt;br /&gt;John also mentions that sometimes abstractions leak, and when they do, it may be hard to debug problems if you aren't familiar with the native platform. This too is a fair point, of course, the same is true with assembly, in that sometimes you do need to step into assembler code when a heisenbug appears.&lt;br /&gt;&lt;br /&gt;One lesson to take away from this is that programmers should strive for deep knowledge of the tools they are using, from how compilers work, to library implementation, and underlying execution environment. Is it really good practice for a Java developer to learn nothing about the internals of the JVM, and how Hotspot optimizations work, for example?&lt;br /&gt;&lt;br /&gt;But I'd like to ask a more important question: What if future web applications aren't written in Javascript at all, would that really be a bad thing? What makes Javascript ideally suited for client side coding, which really means, DOM/CSS manipulation, event handling, and network I/O? You see, much of the Javascript community accepts that it is perfectly natural for server-side code to be written in any language: Java, Python, Perl, Ruby, PHP, C#, even Javascript. You do not see a huge push for a singular server-side language.  And the server-side has since developed into a very strong, diverse ecosystem, sprouting up robust VMs everywhere. And from there, robust library frameworks, like RoR, Django, etc.&lt;br /&gt;&lt;br /&gt;So why is it, that only a single language: Javascript, and it's various VM implementations, is treated as if it is the only True and Correct way to work on the client side? If Javascript VMs one day became more general purpose, accepting perhaps bytecode or some form of intermediate representation as input, and we ended up with Ruby, Scheme, Python, et al, running natively in the browser, would this be the end of the world?&lt;br /&gt;&lt;br /&gt;To listen to some Javascript evangelists, you would think so. The OpenWeb seems to mean that you have freedom and openness on the server, just don't dare write any non-Javascript on the client side, that's off limits!&lt;br /&gt;&lt;br /&gt;Unfortunately, we don't have a general purpose VM in the browser, like JVM, or CLR, or Parrot. Instead, we have Javascript, which is why all of these third party language translators are treating Javascript as if it were assembly or bytecode. But oh, the nerve of these translators! What an insult! How dare you compile some inferior language  or DSL down to basic Javascript, and not write beautiful and elegant human readable Javascript instead. &lt;br /&gt;&lt;br /&gt;On a more serious note, there appears to be a great deal of hypocrisy in some of the criticisms of third party frameworks as well. That is, it is impermissible to abstract away the Javascript language or try to "hide" the Web, that is, unless you are a Javascript framework which has nifty DSLs and Widgets which do exactly that -- like hiding the pain of getting CSS layouts right from newbies. &lt;br /&gt;&lt;br /&gt;The question for you is, do you believe Javascript is the next big programming language, the language that we should be programming the web with, 5 years, 10 years,  100 years from now? What I've learned from sitting through 20+ years of internet/usenet language flamewars is that no language is perfect, people have different tastes, different aesthetics, and different languages fill different niches.  Emacs vs VI will never be resolved. Static vs Dynamic typing will never be resolved. And no one language can satisfy everyone.&lt;br /&gt;&lt;br /&gt;This is not to say that there aren't concerns about gluing code written in multiple languages together and making them interoperate. This is being done on .NET CLR and JVM today, so it is certainly possible in the future to do it in the browser. On the other hand, many people today already work with incompatible siloed Javascript frameworks, so maybe it's not really an issue if Ruby code can't reuse Python code on the client.&lt;br /&gt;&lt;br /&gt;In the end, I would say to Javascript ninjas, relax. There will be more than enough people writing Javascript to sustain a vibrant community. You should welcome polyglots on the client as well as the server. &lt;br /&gt;&lt;br /&gt;Let a thousand flowers bloom.&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-1406274645105716?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=KhNovcYUq6Y:cUk1ypzNntI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=KhNovcYUq6Y:cUk1ypzNntI:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=KhNovcYUq6Y:cUk1ypzNntI:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=KhNovcYUq6Y:cUk1ypzNntI:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=KhNovcYUq6Y:cUk1ypzNntI:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=KhNovcYUq6Y:cUk1ypzNntI:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=KhNovcYUq6Y:cUk1ypzNntI:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/KhNovcYUq6Y" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/1406274645105716/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=1406274645105716" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/1406274645105716?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/1406274645105716?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/KhNovcYUq6Y/openweb-language-for-next-100-years-is.html" title="The OpenWeb language for the next 100 years is..." /><author><name>Timepedia</name><uri>http://www.blogger.com/profile/01408958797259838599</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="14869287118381606485" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2008/12/openweb-language-for-next-100-years-is.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0INQHo-fSp7ImA9WxRbF0o.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-2649349363775616955</id><published>2008-12-08T14:41:00.000-08:00</published><updated>2008-12-08T15:39:51.455-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-08T15:39:51.455-08:00</app:edited><title>Worst anti-GWT marketing ever</title><content type="html">There seems to be an increase in other frameworks attacking GWT, which is probably a indicator that Google is doing something right, but some of these attacks are downright silly, consider &lt;a rel="nofollow" href="http://jaxcent.com"&gt;Jaxcent.com&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;Unlike GWT, Jaxcent is a server-side framework. Instead of being compiled into JavaScript, the Java code directly runs on the server, and communicates with the client via a small JavaScript file.&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;There may be some concern that a server-side framework may be putting more burden on the server, compared to GWT. However, a GWT-like approach does require the server to maintain, manage and deliver multiple JavaScript files. &lt;b&gt;In real terms, that can be a significant server load.&lt;/b&gt; &lt;b&gt;In contrast,&lt;/b&gt; Jaxcent has a single small JavaScript file, that will be cached by normal browsers. &lt;b&gt;The actual load on the server is comparable to any server side pre-AJAX framework, such as servlets, JSP, ASP etc.&lt;/b&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Leaving aside the fact that it is ridiculous to assert that serving up static GWT Javascript files, which can be deployed to a CDN, typically are less than 150k compressed, and cacheable forever so that they are never fetched more than once, represents a signifcant server load, it is even more ridiculous to minimize the burden of keeping all UI state on the server, and making network around trips every time the user takes an action.&lt;br /&gt;&lt;br /&gt;Instead of a Rich Internet Application, you end up with a Dumb Terminal Client, that evokes the days of TN3270 and mainframes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-2649349363775616955?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=1NCEFtP3foA:RgZ11Pwpiyc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=1NCEFtP3foA:RgZ11Pwpiyc:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=1NCEFtP3foA:RgZ11Pwpiyc:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=1NCEFtP3foA:RgZ11Pwpiyc:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=1NCEFtP3foA:RgZ11Pwpiyc:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=1NCEFtP3foA:RgZ11Pwpiyc:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=1NCEFtP3foA:RgZ11Pwpiyc:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/1NCEFtP3foA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/2649349363775616955/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=2649349363775616955" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/2649349363775616955?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/2649349363775616955?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/1NCEFtP3foA/worst-anti-gwt-marketing-ever.html" title="Worst anti-GWT marketing ever" /><author><name>Timepedia</name><uri>http://www.blogger.com/profile/01408958797259838599</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="14869287118381606485" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2008/12/worst-anti-gwt-marketing-ever.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkEDSH04fip7ImA9WxRUEU4.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-1102534323612869929</id><published>2008-11-19T15:41:00.000-08:00</published><updated>2008-11-19T15:51:19.336-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-19T15:51:19.336-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="chronoscope gwt visualization iphone gesture touch" /><title>Chronoscope on iPhone</title><content type="html">If it hasn't been clear from my videos, I don't film them with a script or much forethought as to what I'm going to say or demo, which is one reason why I forgot to show off the iPhone version of Chronoscope. This time, it runs as Javascript in the Safari browser, but leverages iPhone gesture and touch events.  But will it always run as Javascript, or is there another possibility? Time will reveal all secrets.&lt;br /&gt;&lt;br /&gt;&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/M946ifp84Ls&amp;hl=en&amp;fs=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/M946ifp84Ls&amp;hl=en&amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-1102534323612869929?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=_FVwwWYNJUg:2clfq1yUt9U:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=_FVwwWYNJUg:2clfq1yUt9U:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=_FVwwWYNJUg:2clfq1yUt9U:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=_FVwwWYNJUg:2clfq1yUt9U:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=_FVwwWYNJUg:2clfq1yUt9U:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=_FVwwWYNJUg:2clfq1yUt9U:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=_FVwwWYNJUg:2clfq1yUt9U:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/_FVwwWYNJUg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/1102534323612869929/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=1102534323612869929" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/1102534323612869929?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/1102534323612869929?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/_FVwwWYNJUg/chronoscope-on-iphone.html" title="Chronoscope on iPhone" /><author><name>Timepedia</name><uri>http://www.blogger.com/profile/01408958797259838599</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="14869287118381606485" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2008/11/chronoscope-on-iphone.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkYHSXg_fyp7ImA9WxRUEEg.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-4429722888346809376</id><published>2008-11-18T17:17:00.000-08:00</published><updated>2008-11-18T17:28:58.647-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-18T17:28:58.647-08:00</app:edited><title>Chronoscope and GViz on Android</title><content type="html">It's been a while since I've updated the blog, with good reason as we've been very busy recently. However, I wanted to share with you the fruit of some of that labor.&lt;br /&gt;&lt;br /&gt;Long time readers will recall a prototype demo I posted a long time ago of Chronoscope running on Android. The original version was mainly a proof of concept that it was possible to reuse 98% of your code base and deploy nearly identical functionality both in browser, in the server, and on the phone.&lt;br /&gt;&lt;br /&gt;What hasn't been shown however, is the production version, rewritten, with improved performance. Now dubbed Timescope, the new version integrates tightly with Android, as well as our ChartServer, and Google's Visualization API, to allow you to generate charts that render dynamically in web browsers, but launch native high performance applications on the phone.&lt;br /&gt;&lt;br /&gt;Here's a video demonstrating a GViz chart, rendered  as a static clickable image from our chart server and viewed in the Android Web browser. Click on the image launches a native Android application which allows full interactivity.&lt;br /&gt;&lt;br /&gt;All of the full functionality of Chronoscope is retained, scalable to immense streaming datasets, annotatable via a variety of markers, a browser-like history mechanism, bookmarkable links, and a very flexible graph style sheet system.&lt;br /&gt;&lt;br /&gt;&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/qJkiiVE68u4&amp;amp;hl=en&amp;amp;fs=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/qJkiiVE68u4&amp;amp;hl=en&amp;amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;I'll be posting a fairly substantial update soon on all of the cool stuff we've been working on recently, which is nearing public release.&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-4429722888346809376?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=D6yDoJAWCXk:oLpy5Fco9pU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=D6yDoJAWCXk:oLpy5Fco9pU:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=D6yDoJAWCXk:oLpy5Fco9pU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=D6yDoJAWCXk:oLpy5Fco9pU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=D6yDoJAWCXk:oLpy5Fco9pU:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=D6yDoJAWCXk:oLpy5Fco9pU:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=D6yDoJAWCXk:oLpy5Fco9pU:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/D6yDoJAWCXk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/4429722888346809376/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=4429722888346809376" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/4429722888346809376?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/4429722888346809376?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/D6yDoJAWCXk/chronoscope-and-gviz-on-android.html" title="Chronoscope and GViz on Android" /><author><name>Timepedia</name><uri>http://www.blogger.com/profile/01408958797259838599</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="14869287118381606485" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2008/11/chronoscope-and-gviz-on-android.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkUNSX89fSp7ImA9WxRTFUs.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-8919280034348592156</id><published>2008-09-04T13:20:00.000-07:00</published><updated>2008-09-04T13:31:38.165-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-04T13:31:38.165-07:00</app:edited><title>Interesting observations about WebKit/Chrome and Chronoscope Benchmark</title><content type="html">Playing around with the benchmark some more, I noticed some strange things. The more I clicked the "Bench" button, the faster Chrome got. After about 4 runs of the benchmark, it increased as much as 2x in performance. Is Chrome using HotSpot-like techniques to count method invocations for inlining?&lt;br /&gt;&lt;br /&gt;Secondly, the animation on WebKit/Chrome gets choppy. This isn't because the Javascript isn't drawing the frames. The benchmark draws 100 frames, each in a setTimeout call to avoid slow script warnings. Browsers appear to defer reflecting any mutations to the UI until the Javascript thread yields the CPU back. That is, you don't see the results of a canvas fill() or stroke() until the CPU is yielded.  (Opera seems to work differently, but offers a GameCanvas which lets you lock/unlock progressive updates)&lt;br /&gt;&lt;br /&gt;Now, I would normally expect that the draw results would be copied/blitted/reflected into the Browser's document as soon as the Javascript thread yields. But what if the update is scheduled in the same timer/event queue as other pending setTimeout calls? And what if the next frame's scheduled timeout is before the native canvas update?&lt;br /&gt;&lt;br /&gt;Then what would happen is that the Javascript code would get the CPU again, erase the canvas, and draw the next frame. This would account for the choppiness and disappearance of intermediate frames.&lt;br /&gt;&lt;br /&gt;But it could also count for the performance differential between TraceMonkey/FF3.1 and WebKit/Chrome, in the sense that TraceMonkey might "pay the cost" of reflecting every frame into the browser's window which gets counted by my timing code, whereas WebKit/Chrome might get a free ride by skipping the update and somehow avoid the cost by deferring it.&lt;br /&gt;&lt;br /&gt;I'm not sure, and it would be interesting if people who are familiar with the internals of Gecko and WebKit could comment on how canvas updates are handled.&lt;br /&gt;&lt;br /&gt;In the meantime, I'll look at ways of ensuring that every frame is drawn, perhaps by increasing the intra-frame timeout interval.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-8919280034348592156?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=P19xjiAZXE4:kfK9YqYqjSQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=P19xjiAZXE4:kfK9YqYqjSQ:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=P19xjiAZXE4:kfK9YqYqjSQ:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=P19xjiAZXE4:kfK9YqYqjSQ:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=P19xjiAZXE4:kfK9YqYqjSQ:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=P19xjiAZXE4:kfK9YqYqjSQ:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=P19xjiAZXE4:kfK9YqYqjSQ:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/P19xjiAZXE4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/8919280034348592156/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=8919280034348592156" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/8919280034348592156?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/8919280034348592156?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/P19xjiAZXE4/interesting-observations-about.html" title="Interesting observations about WebKit/Chrome and Chronoscope Benchmark" /><author><name>Timepedia</name><uri>http://www.blogger.com/profile/01408958797259838599</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="14869287118381606485" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2008/09/interesting-observations-about.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkAGQn8zeCp7ImA9WxRTFU8.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-3735487102995126471</id><published>2008-09-04T02:25:00.000-07:00</published><updated>2008-09-04T02:32:03.180-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-04T02:32:03.180-07:00</app:edited><title>Chronoscope Browser Benchmark Live</title><content type="html">Hey guys, in order to assist in the browser wars, and to let everyone test browser performance, I've built a publicly hosted version of the benchmark I used to test Google Chrome vs Firefox 3.1 and Safari 4.&lt;br /&gt;&lt;br /&gt;Now you can run the same benchmarks too, just hop on over to &lt;a href="http://api.timepedia.org/benchmark"&gt;http://api.timepedia.org/benchmark&lt;/a&gt; and let the battle begin!&lt;br /&gt;&lt;br /&gt;As an errata for my previously published benchmarks, I did find that somehow my WebKit Nightly batch file wasn't invoking Safari correctly on my Win32 box (it was complaining about a file not found which I didn't see in the console), so my previous results were NOT with SquirrelFish. Upon rerunning with SquirrelFish, I find it actually does hold its own vs Chrome.&lt;br /&gt;&lt;br /&gt;If you're one of those poor folks who are running IE, don't try to run the benchmark. :) It might run, but chances are, you'll get a slow script warning. Also, it uses Flash for rendering instead of  Canvas, so it's not an Apples-to-Apples comparison.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-3735487102995126471?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=O117C6tWiug:6hBr2xwbyVY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=O117C6tWiug:6hBr2xwbyVY:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=O117C6tWiug:6hBr2xwbyVY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=O117C6tWiug:6hBr2xwbyVY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=O117C6tWiug:6hBr2xwbyVY:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=O117C6tWiug:6hBr2xwbyVY:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=O117C6tWiug:6hBr2xwbyVY:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/O117C6tWiug" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/3735487102995126471/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=3735487102995126471" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/3735487102995126471?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/3735487102995126471?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/O117C6tWiug/chronoscope-browser-benchmark-live.html" title="Chronoscope Browser Benchmark Live" /><author><name>Timepedia</name><uri>http://www.blogger.com/profile/01408958797259838599</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="14869287118381606485" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2008/09/chronoscope-browser-benchmark-live.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0YEQH08fSp7ImA9WxRTE0Q.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-7816993018853177792</id><published>2008-09-02T12:34:00.000-07:00</published><updated>2008-09-02T15:38:21.375-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-02T15:38:21.375-07:00</app:edited><title>Google Chrome smokes the competition on Chronoscope</title><content type="html">The current browser wars make me feel like I'm watching the Fast and the Furious, and Google's new Chrome browser is like Nitrous Oxide for the web. As soon as I heard that ex-HotSpot guys were working on it, I knew it could be good, as they did a marvelous job with Self (a fully dynamic language), as well as Java, but how good?&lt;br /&gt;&lt;br /&gt;To test, I decided to use my own codebase, &lt;a href="http://timepedia.org/"&gt;Chronoscope&lt;/a&gt;, for several reasons. First of all, it is not a microbenchmark, it exercises a big swath of the browser's code path. Secondly, it is computationally intensive, so if anything were going to show speed on a "real world" app, this would be it. Finally, I need visualizations to run faster, as I want to use &lt;a href="http://openwebfoundation.org/"&gt;Open Web technologies&lt;/a&gt; like Canvas or SVG rather than relying on Flash.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://timepedia.org/"&gt;Chronoscope&lt;/a&gt; has a workload which contains elements of the following:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Large amount of affine transforms calculated within Javascript code&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Many binary searches and recursive calls&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Frequent random and linear access to large objects (arrays with 30,000+ elements)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Numerous DOM calls to absolute position DIVs (emulate text rendering on Canvas)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Huge number of CanvasRenderingContext2D calls for rendering the chart&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Ubiquitous use of abstraction, delegation, inheritance&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://timepedia.org/"&gt;Chronoscope&lt;/a&gt; is written in GWT, and to some extent, the GWT compiler may negate some of Chrome's V8 technology in the sense that GWT "de-classes" many OO polymorphic dispatches into a more functional style of programming, removing as much dynamic dispatch as possible, and eliminating prototype lookups and function call overhead through inlining. I don't know if GWT hurts "hidden classes" or not, but it might be possible that if GWT didn't provide such optimizations, the performance differential might be larger.&lt;br /&gt;&lt;br /&gt;Despite this, the results are still good. The test consisted of calling the chart's redraw() function 100 times per trial, with 10 trials. The slowest and fastest trial are thrown out, and the mean and standard deviation are calculated on the remaining data.&lt;br /&gt;&lt;br /&gt;I tested on a Mac Pro 2.66Ghz with 6Gb of memory, OSX 1.5. The tests were conducted within a Parallels VM running XP2 Service Pack 2, given 2 CPUs and 2Gb of memory. For each browser, I rebooted the VM from a clean start, and ran only the test browser.&lt;br /&gt;&lt;br /&gt;The tests were conducted with the lastest version of Safari 3.1 for Windows (run via WebKit nightly batch file), the Firefox 3.1 nightly with TraceMonkey enabled, and the Chrome beta download.&lt;br /&gt;&lt;br /&gt;&lt;table border="1" cellspacing="0"&gt;&lt;br /&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Browser&lt;/th&gt;&lt;th&gt;Mean&lt;/th&gt;&lt;th&gt;Standard Deviation&lt;/th&gt;&lt;th&gt;Memory&lt;/th&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Firefox 3.1 (TraceMonkey)&lt;/td&gt;&lt;td&gt;3647ms&lt;/td&gt;&lt;td&gt;81ms&lt;/td&gt;&lt;td&gt;49M&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;Safari 3.1 (SquirrelFish)&lt;/td&gt;&lt;td&gt;3005ms&lt;/td&gt;&lt;td&gt;385ms&lt;/td&gt;&lt;td&gt;106M&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;td&gt;Chrome&lt;/td&gt;&lt;td&gt;1690ms&lt;/td&gt;&lt;td&gt;190ms&lt;/td&gt;&lt;td&gt;44M&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;Chrome looks to be twice as fast as the competition on real world apps. Moreover, it's performance per Mb of memory is good as well. Even more interesting was the variation in Firefox 3.1, which appears to be related to garbage collection or memory allocation. In some runs, it was very fast, but in others, it was 50% slower.&lt;br /&gt;&lt;br /&gt;As soon as Chrome is released on OSX, it will replace Safari for me, for two reasons. The first, the process isolation and speed features. Secondly, Chrome includes a very nice Firebug-like debugger/inspector that I like better than the Safari equivalent.&lt;br /&gt;&lt;br /&gt;So in summary, Chrome rocks! All they were missing from the launch was Vin Diesel.&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-7816993018853177792?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=xB843Tcz4D0:EdIGJx5YmNY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=xB843Tcz4D0:EdIGJx5YmNY:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=xB843Tcz4D0:EdIGJx5YmNY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=xB843Tcz4D0:EdIGJx5YmNY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=xB843Tcz4D0:EdIGJx5YmNY:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=xB843Tcz4D0:EdIGJx5YmNY:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=xB843Tcz4D0:EdIGJx5YmNY:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/xB843Tcz4D0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/7816993018853177792/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=7816993018853177792" title="10 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/7816993018853177792?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/7816993018853177792?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/xB843Tcz4D0/google-chrome-smokes-competition-on.html" title="Google Chrome smokes the competition on Chronoscope" /><author><name>Timepedia</name><uri>http://www.blogger.com/profile/01408958797259838599</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="14869287118381606485" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">10</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2008/09/google-chrome-smokes-competition-on.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0AMQHo6eSp7ImA9WxdaGUs.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-8654760084241564638</id><published>2008-08-28T15:12:00.000-07:00</published><updated>2008-08-28T15:16:21.411-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-08-28T15:16:21.411-07:00</app:edited><title>Powers of Often: Powers of Ten in Time</title><content type="html">Hi all. A little bit of self-pimping today. We're looking at doing a cool presentation at next years 2009 SXSW Conference using our technology. For those who like Chronoscope and want to see even cooler stuff, please follow this link &lt;a href="http://panelpicker.sxsw.com/ideas/view/1649"&gt;http://panelpicker.sxsw.com/ideas/view/1649&lt;/a&gt; and vote for our panel. &lt;br /&gt;&lt;br /&gt;Here's the description:&lt;br /&gt;&lt;b&gt;&lt;blockquote&gt;&lt;br /&gt;In 1977, Charles &amp; Ray Eames made a fascinating short film, Powers of Ten, showing the relative scales in the universe: from picnic, to city, to solar system, to galaxy, and so on, back to cells, molecules, and atomic nuclei. In the same spirit, Powers of Often will explore relative scales in time using real data and hard estimates: patterns of daily life, demographics, census data, generations, long term trends, forecasts, historical cycles, high-frequency finance, and solar cycles.&lt;br /&gt;&lt;/blockquote&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Thanks,&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-8654760084241564638?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=uPtYt0zW-WU:ZoK2DJHuwac:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=uPtYt0zW-WU:ZoK2DJHuwac:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=uPtYt0zW-WU:ZoK2DJHuwac:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=uPtYt0zW-WU:ZoK2DJHuwac:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=uPtYt0zW-WU:ZoK2DJHuwac:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=uPtYt0zW-WU:ZoK2DJHuwac:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=uPtYt0zW-WU:ZoK2DJHuwac:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/uPtYt0zW-WU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/8654760084241564638/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=8654760084241564638" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/8654760084241564638?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/8654760084241564638?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/uPtYt0zW-WU/powers-of-often-powers-of-ten-in-time.html" title="Powers of Often: Powers of Ten in Time" /><author><name>Timepedia</name><uri>http://www.blogger.com/profile/01408958797259838599</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="14869287118381606485" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2008/08/powers-of-often-powers-of-ten-in-time.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUMCRHw9fSp7ImA9WxVVE0U.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-3965935777377787131</id><published>2008-07-24T16:33:00.000-07:00</published><updated>2009-03-06T15:51:05.265-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-06T15:51:05.265-08:00</app:edited><title>Cross Domain FormPanel submissions in GWT</title><content type="html">While working on Timelord,  our next generation charting and analytics tool, I found the need to do cross-domain REST requests because Timelord may be hosted anywhere, but the server-side services are running on a fixed domain (hosted by Google App Engine).  Recently, &lt;s&gt;abuse&lt;/s&gt; cool hacks using the &lt;i&gt;window.name&lt;/i&gt; field have become en vogue, much like the earlier activity around fragment identifiers, and one particularly cool usage is the recent Dojo proposal to &lt;a href="http://www.sitepen.com/blog/2008/07/22/windowname-transport/"&gt;use window.name for cross-domain cross-frame communication&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;After a little experimenting, and some snooping on the Dojo patch, I implemented a quick patch to GWT using Deferred Binding, here's an example:&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;public class WindowNameFormPanelImpl extends FormPanelImpl {&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Gets the response html from the loaded iframe's name property&lt;br /&gt;   *&lt;br /&gt;   * @param iframe the iframe from which the response html is to be extracted&lt;br /&gt;   * @return the response html&lt;br /&gt;   */&lt;br /&gt;  public native String getContents(Element iframe) /*-{&lt;br /&gt;    try {&lt;br /&gt;      // Make sure the iframe's window &amp; document are loaded.&lt;br /&gt;      if (!iframe.contentWindow || !iframe.contentWindow.document)&lt;br /&gt;        return null;&lt;br /&gt;&lt;br /&gt;      // Get the response from window.name&lt;br /&gt;      return iframe.contentWindow.name;&lt;br /&gt;    } catch (e) {&lt;br /&gt;      return null;&lt;br /&gt;    }&lt;br /&gt;  }-*/;&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Hooks the iframe's onLoad event and the form's onSubmit event.&lt;br /&gt;   *&lt;br /&gt;   * @param iframe   the iframe whose onLoad event is to be hooked&lt;br /&gt;   * @param form     the form whose onSubmit event is to be hooked&lt;br /&gt;   * @param listener the listener to receive notification&lt;br /&gt;   */&lt;br /&gt;  public native void hookEvents(Element iframe, Element form,&lt;br /&gt;      FormPanelImplHost listener) /*-{&lt;br /&gt;    if (iframe) {&lt;br /&gt;      iframe.onload = function() {&lt;br /&gt;        // If there is no __formAction yet, this is a spurious onload&lt;br /&gt;        // generated when the iframe is first added to the DOM.&lt;br /&gt;        if (!iframe.__formAction)&lt;br /&gt;          return;&lt;br /&gt;&lt;br /&gt;        if(!iframe.__restoreSameDomain) {&lt;br /&gt;          iframe.__restoreSameDomain = true;&lt;br /&gt;          // restore same domain property of iframe to read window.name property&lt;br /&gt;          iframe.contentWindow.location = &lt;br /&gt;             @com.google.gwt.core.client.GWT::getModuleBaseURL()() + &lt;br /&gt;              "clear.cache.gif";&lt;br /&gt;          return;&lt;br /&gt;        }&lt;br /&gt;        listener.@com.google.gwt.user.client.ui.impl.FormPanelImplHost::onFrameLoad()();&lt;br /&gt;      };&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    form.onsubmit = function() {&lt;br /&gt;      // Hang on to the form's action url, needed in the&lt;br /&gt;      // onload/onreadystatechange handler.&lt;br /&gt;      if (iframe) {&lt;br /&gt;        iframe.__formAction = form.action;&lt;br /&gt;        iframe.__restoreSameDomain = false;&lt;br /&gt;      }&lt;br /&gt;      return listener.@com.google.gwt.user.client.ui.impl.FormPanelImplHost::onFormSubmit()();&lt;br /&gt;    };&lt;br /&gt;  }-*/;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And in your GWT module&lt;br /&gt;&lt;pre name="code" class="brush: xml"&gt;&lt;br /&gt; &amp;lt;replace-with class="org.timepedia.timelord.client.impl.WindowNameFormPanelImpl"&gt;&lt;br /&gt;                  &amp;lt;when-type-is class="com.google.gwt.user.client.ui.impl.FormPanelImpl"/&gt;&lt;br /&gt;  &amp;lt;/replace-with&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;With this code, you simply submit a FormPanel to a service with an additional parameter, like "windowname=true" which indicates you want window.name transport. The resulting output of a POST should contain something like "&amp;lt;script&gt;window.name = '....result string...'&amp;lt;/script&gt;. When the FormPanel.submit() method is invoked, this result will be passed to the FormHandler.onSubmitComplete(event), and you can retrieve the result via event.getResult().&lt;br /&gt;&lt;br /&gt;-Ray&lt;br /&gt;p.s. there are separate implementations for IE&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-3965935777377787131?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=7VkO9xvxN-A:KpaYedFGLe4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=7VkO9xvxN-A:KpaYedFGLe4:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=7VkO9xvxN-A:KpaYedFGLe4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=7VkO9xvxN-A:KpaYedFGLe4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=7VkO9xvxN-A:KpaYedFGLe4:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=7VkO9xvxN-A:KpaYedFGLe4:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=7VkO9xvxN-A:KpaYedFGLe4:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/7VkO9xvxN-A" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/3965935777377787131/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=3965935777377787131" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/3965935777377787131?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/3965935777377787131?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/7VkO9xvxN-A/cross-domain-formpanel-submissions-in.html" title="Cross Domain FormPanel submissions in GWT" /><author><name>Timepedia</name><uri>http://www.blogger.com/profile/01408958797259838599</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="14869287118381606485" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2008/07/cross-domain-formpanel-submissions-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0YMQHk7fSp7ImA9WxdWEUs.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-8457568367370141825</id><published>2008-07-04T02:50:00.000-07:00</published><updated>2008-07-04T02:53:01.705-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-07-04T02:53:01.705-07:00</app:edited><title>Chronoscope GViz API and Gadget released</title><content type="html">Hey guys, a beta of Chronoscope 0.86 (in the trunk) has been packaged up as a GViz API and Google Gadget (with Spreadsheets integration). Check it out at &lt;a href="http://timepedia.org/"&gt;Timepedia.org&lt;/a&gt; to see the new features. &lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-8457568367370141825?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=TLbmBAv7AAg:BacFKYeakfs:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=TLbmBAv7AAg:BacFKYeakfs:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=TLbmBAv7AAg:BacFKYeakfs:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=TLbmBAv7AAg:BacFKYeakfs:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=TLbmBAv7AAg:BacFKYeakfs:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=TLbmBAv7AAg:BacFKYeakfs:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=TLbmBAv7AAg:BacFKYeakfs:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/TLbmBAv7AAg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/8457568367370141825/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=8457568367370141825" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/8457568367370141825?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/8457568367370141825?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/TLbmBAv7AAg/chronoscope-gviz-api-and-gadget.html" title="Chronoscope GViz API and Gadget released" /><author><name>Timepedia</name><uri>http://www.blogger.com/profile/01408958797259838599</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="14869287118381606485" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2008/07/chronoscope-gviz-api-and-gadget.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEcBSHcycCp7ImA9WxdQEkg.&quot;"><id>tag:blogger.com,1999:blog-2515037436118935802.post-2422471157639155557</id><published>2008-06-11T23:05:00.000-07:00</published><updated>2008-06-12T00:34:19.998-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-12T00:34:19.998-07:00</app:edited><title>Google I/O GWT Extreme! Presentation video now online</title><content type="html">or, how I managed to render a million datapoints in Javascript in real time without crashing my browser. :)&lt;br /&gt;&lt;br /&gt;For those who missed it, you can now catch my GWT Extreme! presentation on YouTube courtesy of Google.&lt;br /&gt;&lt;br /&gt;&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/2ScPbu8ga1Q&amp;hl=en"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/2ScPbu8ga1Q&amp;hl=en" type="application/x-shockwave-flash" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;You can also view the slides directly here:&lt;br /&gt;&lt;div style="width:425px;text-align:left" id="__ss_463176"&gt;&lt;object style="margin:0px" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slideshare.net/swf/ssplayer2.swf?doc=gwtextreme-1213250644043195-8"/&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed src="http://static.slideshare.net/swf/ssplayer2.swf?doc=gwtextreme-1213250644043195-8" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="font-size:11px;font-family:tahoma,arial;height:26px;padding-top:2px;"&gt;&lt;a href="http://www.slideshare.net/?src=embed"&gt;&lt;img src="http://static.slideshare.net/swf/logo_embd.png" style="border:0px none;margin-bottom:-5px" alt="SlideShare"/&gt;&lt;/a&gt; | &lt;a href="http://www.slideshare.net/cromwellian/gwt-extreme?src=embed" title="View GWT Extreme! on SlideShare"&gt;View&lt;/a&gt; | &lt;a href="http://www.slideshare.net/upload?src=embed"&gt;Upload your own&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;I would highly recommend watching the following presentations first before watching mine:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.youtube.com/watch?v=vv2MnqP8Bmk"&gt;Surprisingly Rockin Javascript and DOM Programming&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.youtube.com/watch?v=nvti32k4xyU"&gt;Faster Than Possible Code: Deferred Binding&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.youtube.com/watch?v=cyfVHNAM80E"&gt;Resource Bundles and Linkers&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If you're interested in just seeing the demo of Chronoscope zooming and rendering over 1 million datapoints, skip to about 27 minutes into my video.&lt;br /&gt;&lt;br /&gt;-Ray&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2515037436118935802-2422471157639155557?l=timepedia.blogspot.com'/&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=vGgHEk_E5Y8:q88lf4XLn3E:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=vGgHEk_E5Y8:q88lf4XLn3E:dnMXMwOfBR0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=dnMXMwOfBR0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=vGgHEk_E5Y8:q88lf4XLn3E:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=vGgHEk_E5Y8:q88lf4XLn3E:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=vGgHEk_E5Y8:q88lf4XLn3E:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/timepedia?a=vGgHEk_E5Y8:q88lf4XLn3E:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/timepedia?i=vGgHEk_E5Y8:q88lf4XLn3E:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/timepedia/~4/vGgHEk_E5Y8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://timepedia.blogspot.com/feeds/2422471157639155557/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=2515037436118935802&amp;postID=2422471157639155557" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/2422471157639155557?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2515037436118935802/posts/default/2422471157639155557?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/blogspot/timepedia/~3/vGgHEk_E5Y8/google-io-gwt-extreme-presentation.html" title="Google I/O GWT Extreme! Presentation video now online" /><author><name>Timepedia</name><uri>http://www.blogger.com/profile/01408958797259838599</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="14869287118381606485" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total><feedburner:origLink>http://timepedia.blogspot.com/2008/06/google-io-gwt-extreme-presentation.html</feedburner:origLink></entry></feed>
