<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss'><id>tag:blogger.com,1999:blog-5950537015062599287</id><updated>2009-12-11T11:05:39.737+11:00</updated><title type='text'>Kennard Consulting's Blog</title><subtitle type='html'>...what Kennard Consulting is working on (the non-confidential bits, anyway)</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default?start-index=26&amp;max-results=25'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>81</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-4777114702164745536</id><published>2009-12-08T14:33:00.017+11:00</published><updated>2009-12-09T12:41:46.384+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JBoss Cache'/><title type='text'>Newbie's Guide to using JBoss Cache in JBoss AS</title><content type='html'>&lt;img style="float:right; margin:0 0 10px 10px;border: 0;width: 128px; height: 128px;" src="http://2.bp.blogspot.com/__YNTBm_fS_I/Sx3OIXEJuII/AAAAAAAAAMw/A2RkbJaeWnM/s320/Community+Help.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5412708970194909314" /&gt;&lt;br /&gt;The promise of &lt;a href="http://www.jboss.org/jbosscache"&gt;JBoss Cache&lt;/a&gt; is that it makes distributed caching really easy. But it can be hard to figure out how to use it, because many of the docs are concerned with installation and configuration. Yet the promise of &lt;a href="http://www.jboss.org/jbossas"&gt;JBoss AS&lt;/a&gt; is that everything is &lt;em&gt;already&lt;/em&gt; installed, configured and integrated together, so how do you skip all that and get straight to The Good Stuff?&lt;br /&gt;&lt;br /&gt;So, this is the blog &lt;em&gt;I&lt;/em&gt; was looking for:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;If Today You Are Running JBoss AS And Have...&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;public class MyBeanOrControllerOrWhatever {&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;private final static Map MY_CACHE = new HashMap();&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public Object lookupSomething( Object key ) {&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Object value = &lt;strong&gt;MY_CACHE.get( key );&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( value == null ) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value = lookupUncached();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;MY_CACHE.put( key, value );&lt;/strong&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return value;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/tt&gt;&lt;/div&gt;&lt;em&gt;(ie. you are using a static Map for your cache - which is fine but will not distribute across VMs)&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;...Then Using JBoss Cache You Can Do&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;public class MyBeanOrControllerOrWhatever {&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public Object lookupSomething( Object key ) {&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;CacheManager cacheManager = CacheManagerLocator.getCacheManagerLocator().getCacheManager( null );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Cache myCache = cacheManager.getCache( "sfsb-cache", true );&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Fqn fqn = Fqn.fromString( MyBeanOrControllerOrWhatever.class.getName() );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Object value = &lt;strong&gt;myCache.get( fqn, key );&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( value == null ) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value = lookupUncached();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;myCache.put( fqn, key, value );&lt;/strong&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return value;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;Where:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;tt&gt;fqn&lt;/tt&gt; is a unique key in the JBoss Cache tree so that you don't conflict with other caches&lt;/li&gt; &lt;br /&gt; &lt;li&gt;&lt;tt&gt;sfsb-cache&lt;/tt&gt; is a pre-configured JBoss AS cache that replicates asynchronously and never expires&lt;/li&gt;&lt;br /&gt; &lt;li&gt;&lt;tt&gt;CacheManagerLocator&lt;/tt&gt; is in &lt;tt&gt;common/lib/jboss-ha-server-api.jar&lt;/tt&gt;&lt;/li&gt;&lt;br /&gt; &lt;li&gt;&lt;tt&gt;CacheManager&lt;/tt&gt; is in &lt;tt&gt;server/all/lib/jbosscache-core.jar&lt;/tt&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;You'll also need to edit &lt;tt&gt;server/all/deploy/cluster/jboss-cache-manager.sar/META-INF/jboss-cache-manager-jboss-beans.xml&lt;/tt&gt; to pre-start the cache:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;&amp;lt;!-- Start these caches as part of the start of this CacheManager --&amp;gt;&lt;br /&gt;&amp;lt;property name="eagerStartCaches"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;set&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;value&amp;gt;&lt;strong&gt;sfsb-cache&lt;/strong&gt;&amp;lt;/value&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/set&amp;gt;&lt;br /&gt;&amp;lt;/property&amp;gt;&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;This should be enough to get you working and get you excited. After a while you should probably look at configuring your own cache rather than hijacking &lt;tt&gt;sfsb-cache&lt;/tt&gt;, as that's not strictly meant for your own stuff. All this actually is in the JBoss Cache docs, just &lt;a href="http://www.jboss.org/file-access/default/members/jbossclustering/freezone/docs/cluster_guide/5.1/html/jbosscache.chapt.html#jbosscache-custom-deployment"&gt;waaaay at the back in Chapter 11&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Improvements welcome!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-4777114702164745536?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/4777114702164745536/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=4777114702164745536' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/4777114702164745536'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/4777114702164745536'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/12/newbies-guide-to-jboss-cache.html' title='Newbie&apos;s Guide to using JBoss Cache in JBoss AS'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/__YNTBm_fS_I/Sx3OIXEJuII/AAAAAAAAAMw/A2RkbJaeWnM/s72-c/Community+Help.png' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-6219799757646028839</id><published>2009-11-26T11:44:00.010+11:00</published><updated>2009-11-26T11:51:11.171+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Metawidget'/><title type='text'>Metawidget: King of the Hill</title><content type='html'>&lt;img style="MARGIN: 0px 0px 10px 10px; WIDTH: 297px; FLOAT: right; HEIGHT: 123px; border: 0" id="BLOGGER_PHOTO_ID_5408207982146337410" border="0" alt="" src="http://4.bp.blogspot.com/__YNTBm_fS_I/Sw3QgX9DuoI/AAAAAAAAAMo/pS4w39JEE5w/s320/logo.jpg" /&gt;&lt;br /&gt;&lt;div&gt;Nice article here (at least I assume it's nice, my German is even worse than Google Translate's):&lt;br /&gt;&lt;br /&gt;&lt;div align="center"&gt;&lt;a href="http://it-republik.de/jaxenter/artikel/Metawidget-King-of-the-Hill-2681.html"&gt;Metawidget: King of the Hill&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div align="center"&gt;&lt;/div&gt;&lt;/div&gt;My thanks to the author.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-6219799757646028839?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/6219799757646028839/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=6219799757646028839' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/6219799757646028839'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/6219799757646028839'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/11/metawidget-king-of-hill.html' title='Metawidget: King of the Hill'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/__YNTBm_fS_I/Sw3QgX9DuoI/AAAAAAAAAMo/pS4w39JEE5w/s72-c/logo.jpg' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-214483607930086771</id><published>2009-11-18T08:46:00.011+11:00</published><updated>2009-11-18T09:11:12.228+11:00</updated><title type='text'>We're Hiring!</title><content type='html'>Here's the job ad we're posting today. A chance to work from home, and work for me! What a dream job :)&lt;br /&gt;&lt;br /&gt;&lt;img style="float:right; margin:0 0 10px 10px;border: 0;width: 128px; height: 128px;" src="http://3.bp.blogspot.com/__YNTBm_fS_I/SwMcum-f5LI/AAAAAAAAAMQ/1bDL3YzNvD8/s320/folder_home.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5405195564836971698" /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Senior Java 5 (J2EE) Developer: $90,000 - $100,000 AUD&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Small, focused team&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Modern, well-crafted software&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Work in city or from home&lt;/li&gt;&lt;/ul&gt;&lt;strong&gt;A rare opportunity to join our small consultancy team.&lt;/strong&gt; We have recently received substantial investment from a leading Australian financial institution and we must grow to meet demand.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;You will work side-by-side with one of Australia's premier Java EE developers&lt;/strong&gt;, and be intimately involved in all design decisions.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;You will be a pragmatic programmer. Software development will be in your blood:&lt;/strong&gt; a natural instinct. You must be passionate about Open Source and Java EE. You will demonstrate advanced analytical skills; an eye for elegance and craftmanship; a passion for refactoring, unit testing, and delivering secure and robust code.&lt;br /&gt;&lt;br /&gt;Your skills will include many of the following:&lt;br /&gt;&lt;img style="float:right; margin:0 0 10px 10px;border: 0;width: 128px; height: 128px;" src="http://4.bp.blogspot.com/__YNTBm_fS_I/SwMdf4iTwYI/AAAAAAAAAMg/ylIYM1sRv08/s320/java.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5405196411364163970" /&gt;&lt;ul&gt;&lt;li&gt;JBoss&lt;/li&gt;&lt;br /&gt;&lt;li&gt;JSF/RichFaces&lt;/li&gt;&lt;br /&gt;&lt;li&gt;XSL (T, XPath and FO)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;EJB 3/JMS&lt;/li&gt;&lt;br /&gt;&lt;li&gt;JPA/SQL&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Metawidget&lt;/li&gt;&lt;br /&gt;&lt;li&gt;HtmlUnit&lt;/li&gt;&lt;br /&gt;&lt;li&gt;HTML/CSS&lt;/li&gt;&lt;/ul&gt;In addition, you will have excellent communication skills and be comfortable interacting with stakeholders at all levels.&lt;br /&gt;&lt;br /&gt;Work from home will be offered providing that you can demonstrate an appropriate working environment. &lt;strong&gt;You must be a citizen or permanent resident of Australia&lt;/strong&gt;.&lt;br /&gt;&lt;br /&gt;To help us sort applicants, include your CV and a 1 page overview highlighting your suitability for the role. Links to examples of your work (eg. web sites you have built, blogs you have written, Open Source contributions etc) will be highly regarded.&lt;br /&gt;&lt;br /&gt;Please send your application to &lt;a href="mailto:jobs@kennardconsulting.com"&gt;jobs@kennardconsulting.com&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-214483607930086771?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/214483607930086771/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=214483607930086771' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/214483607930086771'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/214483607930086771'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/11/were-hiring.html' title='We&apos;re Hiring!'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/__YNTBm_fS_I/SwMcum-f5LI/AAAAAAAAAMQ/1bDL3YzNvD8/s72-c/folder_home.png' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-2521589955023424333</id><published>2009-11-11T10:06:00.012+11:00</published><updated>2009-11-11T11:14:05.341+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JSF'/><title type='text'>Safely manipulating the component tree with JSF 2</title><content type='html'>&lt;img style="float:right; margin:0 0 10px 10px;width: 128px; height: 128px;border:0" src="http://3.bp.blogspot.com/__YNTBm_fS_I/Svn_UHv7efI/AAAAAAAAAL4/Jgx7h1YCHTA/s320/cache.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402629949150165490" /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;The Problem&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;A common problem encountered by writers of dynamic JSF 1.x components (like &lt;a href="http://metawidget.org/"&gt;Metawidget&lt;/a&gt;) was &lt;strong&gt;"at what point in the lifecycle can I safely manipulate the component tree"&lt;/strong&gt;?&lt;br /&gt;&lt;br /&gt;For a page's initital GET request, there weren't many lifecycle methods you could hook into - you basically had &lt;tt&gt;encodeBegin&lt;/tt&gt;. For subsequent POST-backs there were a slew of hooks, including &lt;tt&gt;decode&lt;/tt&gt;, &lt;tt&gt;processValidators&lt;/tt&gt; and &lt;tt&gt;processUpdates&lt;/tt&gt;, but none were at exactly the right place in the lifecycle to do safe component tree manipulation. What would be 'exactly the right place'? Namely:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;the component tree should be fully realized (ie. all parents and children should be set)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;the component tree should not have been serialized yet&lt;/li&gt;&lt;/ol&gt;Because there was no 'official' place, writers of dynamic JSF 1.x components had to resort to all kinds of tricks. As &lt;a href="http://osdir.com/ml/java.facelets.user/2008-06/msg00050.html"&gt;Ken Paulson&lt;/a&gt; wrote "...people use constructors, bindings, action methods... people create their own renderer which dynamically adds children each request". To which &lt;a href="http://osdir.com/ml/java.facelets.user/2008-06/msg00044.html"&gt;Jacob Hookum&lt;/a&gt; opined "what's actually needed is post component tree creation hooks, providing the ability to then modify the component tree".&lt;br /&gt;&lt;br /&gt;&lt;img style="float:right; margin:0 0 10px 10px;width: 128px; height: 128px;border:0" src="http://2.bp.blogspot.com/__YNTBm_fS_I/Svn_UuXEjUI/AAAAAAAAAMI/pgdPlEl7pt4/s320/amor.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402629959514885442" /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;The Saviour&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Enter JSF 2. From the spec &lt;strong&gt;"System Events are introduced in version 2 of the specification and represent specific points in time for a JSF application"&lt;/strong&gt;. They are like the old PhaseEvents, but much more fine-grained. For the purposes of this blog, the SystemEvent we are interested in is PostAddToViewEvent.&lt;br /&gt;&lt;br /&gt;To use it, first &lt;em&gt;subscribe&lt;/em&gt; to the event in your UIComponent's constructor...&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;public class UITestComponent&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;extends UIComponentBase&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;implements SystemEventListener {&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public UITestComponent() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FacesContext ctx = FacesContext.getCurrentInstance();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ctx.getViewRoot().subscribeToViewEvent(PostAddToViewEvent.class, this);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;...then, when it gets fired, you can safely manipulate the component tree ...&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;public void processEvent( SystemEvent event ) throws AbortProcessingException {&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;FacesContext ctx = FacesContext.getCurrentInstance();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;UIComponent dynamicallyGenerated = ctx.getApplication().createComponent( "javax.faces.HtmlInputText" );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;getChildren().add( dynamicallyGenerated );&lt;br /&gt;}&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;&lt;img style="float:right; margin:0 0 10px 10px;width: 128px; height: 128px;border:0" src="http://2.bp.blogspot.com/__YNTBm_fS_I/Svn_UUgl8iI/AAAAAAAAAMA/FnrnxANDltI/s320/clanbomber.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402629952575500834" /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;The Gotchas&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;While working through this solution, there were a number of gotchas I encountered. Huge thanks to &lt;a href="https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=1383"&gt;Ryan Lubke&lt;/a&gt; for holding my hand through them:&lt;br /&gt;&lt;br /&gt;First, you should use &lt;tt&gt;subscribeToViewEvent&lt;/tt&gt;, not &lt;tt&gt;subscribeToEvent&lt;/tt&gt;. The latter will mean the event subscription itself get serialized into the component tree, so you'll keep doubling up subscribers every time the page refreshes.&lt;br /&gt;&lt;br /&gt;Second, you must hook into &lt;em&gt;UIViewRoot's&lt;/em&gt; PostAddToViewEvent, not UITestComponent's. This is because, at the time UITestComponent's PostAddToViewEvent is fired, its children will not be initialized yet.&lt;br /&gt;&lt;br /&gt;Third, you cannot use the &lt;tt&gt;@ListenerFor&lt;/tt&gt; annotation to hook into SystemEvents from a UIComponent. This is because UIComponent implements ComponentSystemEventListener, and &lt;tt&gt;@ListenerFor&lt;/tt&gt; is designed to ignore the SystemEventListener interface for any class that implements ComponentSystemEventListener.&lt;br /&gt;&lt;br /&gt;&lt;img style="float:right; margin:0 0 10px 10px;width: 128px; height: 128px;border:0" src="http://3.bp.blogspot.com/__YNTBm_fS_I/Svn_T4sAuBI/AAAAAAAAALw/W4D9ViHz34E/s320/alert.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402629945107200018" /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;The Ugly&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There is, unfortunately, still a problem for certain types of dynamic component.&lt;br /&gt;&lt;br /&gt;If your component, in its &lt;tt&gt;processEvent&lt;/tt&gt; method, dynamically creates not just simple HtmlInputText components but nested components that are themselves dynamic, we have a problem. This is because the 'official' way to safely manipulate the component tree is through SystemEvents, but &lt;strong&gt;there is no official way to safely manipulate the SystemEvent list&lt;/strong&gt;.&lt;br /&gt;&lt;br /&gt;In the current release of the &lt;a href="https://javaserverfaces.dev.java.net"&gt;JSF Reference Implementation&lt;/a&gt; (2.0.1), attempting to dynamically create a component that in turn tries to subscribe to a SystemEvent will get you a ConcurrentModificationException. In future releases, there will not be an exception but the newly added SystemEvent will simply not fire, so your newly added dynamic component will never get an opportunity to populate itself.&lt;br /&gt;&lt;br /&gt;This new issue is &lt;a href="https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=1402"&gt;being tracked by the JSF RI team&lt;/a&gt;. Until it is resolved, JSF 2 &lt;em&gt;can&lt;/em&gt; safely create dynamic components (a big step forward), but it &lt;em&gt;cannot&lt;/em&gt; safely create dynamic components that themselves create dynamic components.&lt;br /&gt;&lt;br /&gt;Suggestions welcome!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-2521589955023424333?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/2521589955023424333/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=2521589955023424333' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/2521589955023424333'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/2521589955023424333'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/11/safely-manipulating-component-tree-with.html' title='Safely manipulating the component tree with JSF 2'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/__YNTBm_fS_I/Svn_UHv7efI/AAAAAAAAAL4/Jgx7h1YCHTA/s72-c/cache.png' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-1331721575502622330</id><published>2009-10-30T13:38:00.008+11:00</published><updated>2009-11-03T20:41:34.988+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='XSL-FO'/><title type='text'>XSL-FO number-columns-spanned="remainder"</title><content type='html'>Every so often when developing some XSL-FO I get the dreaded...&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;column-number or number of cells in the row overflows the number of fo:table-column specified for the table&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;...these tend to be a nightmare to track down, and very fiddly to fix, because often you need to add a &lt;tt&gt;number-columns-spanned&lt;/tt&gt; attribute to one of the &lt;tt&gt;fo:table-cell&lt;/tt&gt; elements, and in a dynamic world you have to figure out exactly how many cells to span. Agh!&lt;br /&gt;&lt;br /&gt;It'd be much easier if XSL-FO let you just say &lt;tt&gt;number-columns-spanned="remainder"&lt;/tt&gt;. So today I wrote a little XSLT that you can put &lt;em&gt;after&lt;/em&gt; your XSL-FO and &lt;em&gt;before&lt;/em&gt; you try to serialize it (with something like &lt;a href="http://xmlgraphics.apache.org/fop"&gt;Apache FOP&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;&amp;lt;?xml version="1.0" encoding="utf-8" ?&amp;gt;&lt;br /&gt;&amp;lt;xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;xmlns:fo="http://www.w3.org/1999/XSL/Format"&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&amp;lt;!-- number-columns-spanned: remainder --&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:template match="fo:table-cell[@number-columns-spanned = 'remainder']" mode="#all"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:copy&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:attribute name="number-columns-spanned"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:variable name="number-columns-in-table" as="xs:integer"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:value-of select="count(ancestor::fo:table[1]/fo:table-column)"/&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/xsl:variable&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:variable name="number-columns-in-row" as="xs:integer"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:value-of select="count(preceding-sibling::fo:table-cell[not(@number-columns-spanned)]) + count(following-sibling::fo:table-cell[not(@number-columns-spanned)])"/&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/xsl:variable&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:variable name="number-spanned-columns-in-row" as="xs:integer"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:value-of select="sum(preceding-sibling::fo:table-cell[@number-columns-spanned ne 'remainder']/@number-columns-spanned) + sum(following-sibling::fo:table-cell[@number-columns-spanned ne 'remainder']/@number-columns-spanned)"/&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/xsl:variable&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:value-of select="$number-columns-in-table - $number-columns-in-row - $number-spanned-columns-in-row"/&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/xsl:attribute&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:apply-templates select="@*[not(local-name() = 'number-columns-spanned')]|node()"/&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/xsl:copy&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/xsl:template&amp;gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!-- identity transform --&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:template match="@*|node()"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:copy&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xsl:apply-templates select="@*|node()"/&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/xsl:copy&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&amp;gt;&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;I should've done it years ago. Note unfortunately it won't work if you've got other rows that are using &lt;tt&gt;number-rows-spanned&lt;/tt&gt;. Improvements welcome!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-1331721575502622330?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/1331721575502622330/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=1331721575502622330' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/1331721575502622330'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/1331721575502622330'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/10/xsl-fo-number-columns-spannedremainder.html' title='XSL-FO number-columns-spanned=&quot;remainder&quot;'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-700220814167818430</id><published>2009-10-15T18:30:00.009+11:00</published><updated>2009-10-21T14:28:12.058+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Metawidget'/><title type='text'>User Interface Generator: Metawidget v0.85</title><content type='html'>&lt;a href="http://metawidget.org"&gt;Metawidget&lt;/a&gt; v0.85 is now available! This release has something for everyone:&lt;br /&gt;&lt;br /&gt;For first time users, we have &lt;a href="http://metawidget.org/doc/reference/en/pdf/metawidget.pdf"&gt;updated and improved the User Guide&lt;/a&gt;. For intermediate users, we have &lt;a href="http://metawidget.org/doc/reference/en/htmlsingle/metawidget.html#chapter-architecture"&gt;greatly expanded the architecture documentation&lt;/a&gt;, with lots more diagrams and detailed explanations. And for advanced users, we have substantially refactored the internals, including &lt;a href="http://metawidget.org/doc/reference/en/htmlsingle/metawidget.html#section-architecture-widgetprocessors"&gt;introducing new pluggable WidgetProcessors&lt;/a&gt; to handle more demanding requirements. Please note there are unfortunately some breaking changes, as covered in the &lt;a href="http://kennardconsulting.blogspot.com/2009/09/metawidget-08-to-085-migration-guide.html"&gt;Migration Guide&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In addition we have improved our support for RichFaces (now includes &lt;a href="http://metawidget.org/doc/reference/en/htmlsingle/metawidget.html#section-widgetbuilders-richfaces"&gt;SuggestionBox&lt;/a&gt;, &lt;a href="http://metawidget.org/doc/reference/en/htmlsingle/metawidget.html#section-layouts-web-faces-richfaces"&gt;TabPanel and RichPanel&lt;/a&gt;), ExtGWT (now includes Slider), Swing (now includes &lt;a href="http://metawidget.org/doc/reference/en/html/ch07.html#section-layouts-desktop-swing-gridbag"&gt;configurable labels&lt;/a&gt;), and have added &lt;a href="http://metawidget.org/xsd"&gt;XML schemas for every component&lt;/a&gt; and &lt;a href="http://metawidget.org/doc/coverage/index.html"&gt;lots more unit tests&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Special thanks to Girolamo Violante and Illya Yalovyy for their help with this release!&lt;br /&gt;&lt;br /&gt;As always, the best place to start is the Reference Documentation:&lt;br /&gt;&lt;br /&gt;&lt;center&gt;&lt;a href="http://metawidget.org/doc/reference/en/pdf/metawidget.pdf"&gt;http://metawidget.org/doc/reference/en/pdf/metawidget.pdf&lt;br /&gt;&lt;/a&gt;&lt;/center&gt;&lt;br /&gt;Your continued feedback is invaluable to us. Please download it and let us know what you think.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-700220814167818430?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/700220814167818430/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=700220814167818430' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/700220814167818430'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/700220814167818430'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/10/user-interface-generator-metawidget.html' title='User Interface Generator: Metawidget v0.85'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-6885260780664882107</id><published>2009-10-02T19:00:00.004+10:00</published><updated>2009-10-21T11:51:19.068+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Metawidget'/><title type='text'>Metawidget 0.8 to 0.85 Migration Guide</title><content type='html'>The next release of Metawidget (v0.85, due in late October) will represent a significant refactoring of the Metawidget internals, aimed at increasing API clarity and consistency, code reuse, type safety and extensibility - all in preparation for our 1.0 release. As such, there will be a number of breaking changes.&lt;br /&gt;&lt;br /&gt;We apologise for this disruption and provide this Migration Guide to help in moving from v0.8 to v0.85. All of the existing documentation and examples have already been migrated, as a reference point.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;WidgetProcessors&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The big new change is the concept of a 'widget processor'. These are lightweight, pluggable classes who can 'process' a widget just after its building (by the WidgetBuilder) and before it is laid out (by the Layout).&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/__YNTBm_fS_I/SsHf111dF8I/AAAAAAAAALE/uk9pYt36ZpU/s1600-h/architecture.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 342px;" src="http://4.bp.blogspot.com/__YNTBm_fS_I/SsHf111dF8I/AAAAAAAAALE/uk9pYt36ZpU/s400/architecture.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5386832745389168578" /&gt;&lt;/a&gt;&lt;br /&gt;The interface is simply:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;W processWidget( W widget, String elementName, Map&amp;lt;String, String&amp;gt; attributes, M metawidget )&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;This is great because:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;It allows you to plug-in arbitrary processing of a widget without touching the Metawidget class itself. For example you could write a widget processor to add a tooltip to every component:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;JComponent processWidget( JComponent widget, String elementName, Map&lt;String, String&gt; attributes, SwingMetawidget metawidget ) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;widget.setToolTipText( attributes.get( NAME ));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return widget;&lt;br /&gt;}&lt;/tt&gt;&lt;/div&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;It provides more ways to identify and process a widget than &lt;tt&gt;getComponent( String... names )&lt;/tt&gt;, for situations where you don't know the component's name in advance&lt;/li&gt;&lt;br /&gt;&lt;li&gt;It allows you to attach Event Handlers as inner classes that have connections to their parent class:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;final Object someObject = ...;&lt;br /&gt;    &lt;br /&gt;metawidget.addWidgetProcessor( new BaseWidgetProcessor() {&lt;br /&gt;JComponent processWidget( JComponent widget, String elementName, Map&amp;lt;String, String&amp;gt; attributes, SwingMetawidget metawidget ) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;...decide whether to attach event handler...&lt;br /&gt;  &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;widget.add( new AbstractAction() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public void actionPerformed( ActionEvent event ) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;someObject.doSomething();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;br /&gt;}&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;&lt;li&gt;Standardizing this concept allows us to refactor a lot of previously ad-hoc functionality. For example all binding and validation functionality are now implemented as widget processors:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/__YNTBm_fS_I/SsHrUDgdAUI/AAAAAAAAALM/duxvS2Xs8uc/s1600-h/architecture-widgetprocessors.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 270px;" src="http://1.bp.blogspot.com/__YNTBm_fS_I/SsHrUDgdAUI/AAAAAAAAALM/duxvS2Xs8uc/s400/architecture-widgetprocessors.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5386845359083159874" /&gt;&lt;/a&gt;&lt;br /&gt;They can be chained together either programmatically or in &lt;tt&gt;metawidget.xml&lt;/tt&gt;:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;&amp;lt;widgetProcessors&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;list&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;processor:requiredAttributeProcessor /&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;processor:immediateAttributeProcessor /&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;processor:standardBindingProcessor /&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;processor:readableIdProcessor /&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;processor:labelProcessor /&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;processor:standardValidatorProcessor /&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;processor:standardConverterProcessor /&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/list&amp;gt;&lt;br /&gt;&amp;lt;/widgetProcessors&amp;gt;&lt;br /&gt;&lt;/tt&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Refactored save(), rebind(), validate()&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;As mentioned, binding and validation have now been refactored into WidgetProcessors. This has the advantage of moving methods such as save() and validate() out of the Metawidget (where they weren't always applicable) and into the responsible WidgetProcessor (where they will always be applicable, by definition).&lt;br /&gt;&lt;br /&gt;This unfortunately means a slightly more cumbersome API. Instead of...&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;metawidget.save()&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;...you now have to do...&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;metawidget.getWidgetProcessor( BeansBindingProcessor.class ).save( metawidget )&lt;/tt&gt;&lt;/div&gt;...however we think the tradeoff is worth it.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;metawidget.xml now supports (and mostly uses) &amp;lt;array&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;ConfigReader now natively supports an &amp;lt;array&amp;gt; element. This allows it to natively call vararg methods, removing the need for some of the overloaded methods in the &lt;tt&gt;xxxConfig&lt;/tt&gt; classes.&lt;br /&gt;&lt;br /&gt;Most current usages of &amp;lt;list&amp;gt; should be replaced with &amp;lt;array&amp;gt;. Lists (and Sets) are still supported for your custom use-cases.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Refactored setCreateHiddenFields&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;JSF hidden field creation has also been refactored into a WidgetProcessor. For example:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;&amp;lt;widgetProcessors&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;list&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;hiddenFieldProcessor /&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/list&amp;gt;&lt;br /&gt;&amp;lt;/widgetProcessors&amp;gt;&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Layouts must now be immutable&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Layouts are now a standardized part of the Metawidget pipeline. They must implement the new &lt;tt&gt;org.metawidget.layout.iface.Layout&lt;/tt&gt; interface, and must be threadsafe and immutable just like Inspectors, WidgetBuilders and WidgetProcessors. Custom Layouts that are currently thread-unsafe or mutable need to be refactored in a couple of ways:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Configuration settings, that do not change over the lifetime of the Layout and would previously have been retrieved via &lt;tt&gt;metawidget.getParameter&lt;/tt&gt; (and possibly saved into member variables), should now be configured by an external &lt;tt&gt;xxxConfig&lt;/tt&gt; class. For example:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;public GridBagLayout() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;this( new GridBagLayoutConfig() );&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public GridBagLayout( GridBagLayoutConfig config ) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;mNumberOfColumns = config.getNumberOfColumns();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;mLabelAlignment = config.getLabelAlignment();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;mSectionStyle = config.getSectionStyle();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;mLabelSuffix = config.getLabelSuffix();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;mRequiredAlignment = config.getRequiredAlignment();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;mRequiredText = config.getRequiredText();&lt;br /&gt;}&lt;br /&gt;&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;...and configured either programmatically or in &lt;tt&gt;metawidget.xml&lt;/tt&gt;:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;&amp;lt;layout&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;htmlTableLayout xmlns="java:org.metawidget.jsp.tagext.html.layout" config="HtmlTableLayoutConfig"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;tableStyle&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;string&amp;gt;aTableStyle&amp;lt;/string&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/tableStyle&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/htmlTableLayout&amp;gt;&lt;br /&gt;&amp;lt;/layout&amp;gt;&lt;/tt&gt;&lt;/div&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Internal state that changes during layout and was stored in member variables should now be stored in the parent Metawidget. For example:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;State state = (State) metawidget.getClientProperty( GridBagLayout.class );&lt;br /&gt;state.currentRow++;&lt;/tt&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;No more metawidget.setParameter or m:param&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The previously (type unsafe) &lt;tt&gt;metawidget.setParameter&lt;/tt&gt; methods and &lt;tt&gt;m:param&lt;/tt&gt; tags have now been removed, in favour of typesafe configuration objects on the WidgetProcessors and Layouts. So instead of...&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;metawidget.setPropertyBindingClass( BeansBinding.class )&lt;br /&gt;metawidget.setParameter( "updateStrategy", UpdateStrategy.READ )&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;...you now do...&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;metawidget.addWidgetProcessor( new BeansBindingProcessor(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;new BeansBindingProcessorConfig()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.setUpdateStrategy( UpdateStrategy.READ )));&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;...or equivalent in &lt;tt&gt;metawidget.xml&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;ConfigReader Moved&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;ConfigReader has now been moved out of &lt;tt&gt;org.metawidget.inspector&lt;/tt&gt; and into the new &lt;tt&gt;org.metawidget.config&lt;/tt&gt;. This reflects it outgrowing its roots as a purely Inspector-focussed mechanism, into a general purpose way to configure Metawidgets.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;XSDs&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Existing XSDs have now been moved under &lt;tt&gt;http://metawidget.org/xsd&lt;/tt&gt;. In addition, all the XSDs for the Inspectors, WidgetBuilders etc have been generated.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;PropertyStyles and ActionStyles are now objects&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;ConfigReader is now smart enough to reuse &lt;tt&gt;PropertyStyle&lt;/tt&gt; and &lt;tt&gt;ActionStyles&lt;/tt&gt; instances between &lt;tt&gt;Inspector&lt;/tt&gt;s. This means they are now specified on the &lt;tt&gt;Inspector&lt;/tt&gt; as an object, not their class. This opens the door to people writing their own, configrable property styles if they need to.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Ant Build&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The formerly monolithic build.xml has been split into several smaller ones. The original (/build.xml) now only builds Metawidget itself. There is a separate /examples/build.xml (builds examples), /test/build.xml (runs unit tests), /src/doc/build.xml (builds documentation) and /src/web/build.xml (builds Web site).&lt;br /&gt;&lt;br /&gt;Similarly, the lib folder has now been split into /lib, /examples/lib and /test/lib.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-6885260780664882107?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/6885260780664882107/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=6885260780664882107' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/6885260780664882107'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/6885260780664882107'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/09/metawidget-08-to-085-migration-guide.html' title='Metawidget 0.8 to 0.85 Migration Guide'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/__YNTBm_fS_I/SsHf111dF8I/AAAAAAAAALE/uk9pYt36ZpU/s72-c/architecture.png' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-4914377640874936051</id><published>2009-10-02T14:24:00.016+10:00</published><updated>2009-10-05T15:16:31.232+11:00</updated><title type='text'>Java Puzzler: enforcing whether a class overrides equals/hashCode</title><content type='html'>&lt;img style="float:right; margin:0 0 10px 10px;width: 128px; height: 128px; border: 0" src="http://3.bp.blogspot.com/__YNTBm_fS_I/SsWWQsjNsPI/AAAAAAAAALU/0x1XWOpOtVw/s400/ktip.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5387877742799597810" /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;The Problem&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's say I'm writing a Java class that requires &lt;em&gt;users&lt;/em&gt; of my class override &lt;tt&gt;equals&lt;/tt&gt; and &lt;tt&gt;hashCode&lt;/tt&gt; on their objects. I'd be in good company: lots of classes do this, not least the &lt;tt&gt;java.util.Collection&lt;/tt&gt; classes like &lt;tt&gt;List&lt;/tt&gt; and &lt;tt&gt;Set&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;This important requirement is generally only recorded in the JavaDoc, with a stern warning that &lt;strong&gt;Bad Things Will Happen&lt;/strong&gt; if you forget. But the requirement is &lt;em&gt;not&lt;/em&gt; enforced at runtime, much less compile time.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;The Question&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;What if I wanted to enforce it? What if I wanted to write a class like &lt;tt&gt;HashMap&lt;/tt&gt; that made sure anything you &lt;tt&gt;put&lt;/tt&gt; in it overrode &lt;tt&gt;equals&lt;/tt&gt; and &lt;tt&gt;hashCode&lt;/tt&gt;? What if I'm prepared to sacrifice a little performance and/or code complexity to do this? What are my choices? Can I do it at runtime? Better yet, can I do it at compile-time?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;The Answer?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I don't know a great answer. I don't know of any libraries that do. I'd love some feedback on helping eliminate this important category of subtle bugs.&lt;br /&gt;&lt;br /&gt;To get the ball rolling, this is what I'm thinking of doing in &lt;a href="http://metawidget.org"&gt;Metawidget&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;Class&lt;?&gt; classToTest = objectToCache.getClass();&lt;br /&gt;Object dummy1 = classToTest.newInstance();&lt;br /&gt;Object dummy2 = classToTest.newInstance();&lt;br /&gt;&lt;br /&gt;if ( !dummy1.equals( dummy2 ))&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new Exception( classToTest + " does not override .equals(), so cannot be reliably cached" );&lt;br /&gt;&lt;br /&gt;if ( dummy1.hashCode() != dummy2.hashCode() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new Exception( classToTest + " does not override .hashCode(), so cannot be reliably cached" );&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;This approach takes advantage of the fact that, given your class has internal state, two brand new instances should always have equivalent state, but the default &lt;tt&gt;Object.equals&lt;/tt&gt; (which uses &lt;tt&gt;==&lt;/tt&gt;) will return false. It's not perfect. It'll work if you write a POJO and forget to override &lt;tt&gt;equals&lt;/tt&gt;. But it won't work if your superclass overrides &lt;tt&gt;equals&lt;/tt&gt; but your subclass, which adds some more internal state, forgets to. But it's a start.&lt;br /&gt;&lt;br /&gt;Note that, annoyingly, this doesn't seem to work:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;class.getDeclaredMethod( "hashCode" )&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;Public methods like &lt;tt&gt;hashCode&lt;/tt&gt; and &lt;tt&gt;equals&lt;/tt&gt; are &lt;em&gt;always&lt;/em&gt; considered 'declared methods', even if the class doesn't actually declare them. Similarly:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;class.getDeclaredMethod( "hashCode" ).getDeclaringClass()&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;Always returns the subclass name, even if the subclass doesn't override &lt;tt&gt;hashCode&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;Suggestions welcome!&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Update&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Thanks to everyone for the really helpful comments. The first thing to note is that...&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;class.getDeclaredMethod( "hashCode" ).getDeclaringClass()&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;...&lt;em&gt;does&lt;/em&gt; actually work. I don't know why it didn't seem to when I tried it originally. Evidentally I am an idiot!&lt;br /&gt;&lt;br /&gt;Anyway, here's what's currently going into Metawidget, based on everyone's feedback:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;tt&gt;Class&amp;lt;?&amp;gt; configClass = configToStoreUnder.getClass();&lt;br /&gt;&lt;br /&gt;// Hard error&lt;br /&gt;&lt;br /&gt;// equals&lt;br /&gt;&lt;br /&gt;Class&amp;lt;?&amp;gt; equalsDeclaringClass = configClass.getMethod( "equals", Object.class ).getDeclaringClass();&lt;br /&gt;&lt;br /&gt;if ( Object.class.equals( equalsDeclaringClass ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new Exception( configClass + " does not override .equals(), so cannot cache reliably" );&lt;br /&gt;&lt;br /&gt;// hashCode&lt;br /&gt;&lt;br /&gt;Class&amp;lt;?&amp;gt; hashCodeDeclaringClass = configClass.getMethod( "hashCode" ).getDeclaringClass();&lt;br /&gt;&lt;br /&gt;if ( Object.class.equals( hashCodeDeclaringClass ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new Exception( configClass + " does not override .hashCode(), so cannot cache reliably" );&lt;br /&gt;&lt;br /&gt;if ( !equalsDeclaringClass.equals( hashCodeDeclaringClass ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new Exception( equalsDeclaringClass + " implements .equals(), but .hashCode() is implemented by " + hashCodeDeclaringClass + ", so cannot cache reliably" );&lt;br /&gt;&lt;br /&gt;if ( !configClass.equals( equalsDeclaringClass ) )&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Soft warning&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Note: only show this if the configClass appears to have its own 'state'.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Base this assumption on whether it declares any methods. We don't want to&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;// use .getDeclaredFields because that requires a security manager&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;// check of checkMemberAccess(Member.DECLARED), whereas we may only have&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;// checkMemberAccess(Member.PUBLIC) permission&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( Method declaredMethod : configClass.getMethods() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( configClass.equals( declaredMethod.getDeclaringClass() ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LOG.warn( configClass + " does not override .equals() (only its super" + equalsDeclaringClass + " does), so may not be cached reliably" );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Note: not necessary to do !configClass.equals( hashCodeDeclaringClass ),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;// as will already have thrown an Exception from&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;// !equalsDeclaringClass.equals( hashCodeDeclaringClass ) if that's the case&lt;br /&gt;}&lt;/tt&gt;&lt;/div&gt;&lt;br /&gt;Improvements welcome!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-4914377640874936051?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/4914377640874936051/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=4914377640874936051' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/4914377640874936051'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/4914377640874936051'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/10/java-puzzler-enforcing-whether-class.html' title='Java Puzzler: enforcing whether a class overrides equals/hashCode'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/__YNTBm_fS_I/SsWWQsjNsPI/AAAAAAAAALU/0x1XWOpOtVw/s72-c/ktip.png' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-6720603368018101845</id><published>2009-09-17T09:57:00.013+10:00</published><updated>2009-10-21T14:53:34.909+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Metawidget'/><title type='text'>Metawidget Elevator Pitch</title><content type='html'>&lt;a href="http://1.bp.blogspot.com/__YNTBm_fS_I/St6Fs4jpBqI/AAAAAAAAALk/p7fIEQzG-aU/s1600-h/elevator-pitch.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 134px; height: 320px;" src="http://1.bp.blogspot.com/__YNTBm_fS_I/St6Fs4jpBqI/AAAAAAAAALk/p7fIEQzG-aU/s320/elevator-pitch.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5394896409779046050" /&gt;&lt;/a&gt;&lt;br /&gt;I've been having fun lately drawing an &lt;a href="http://en.wikipedia.org/wiki/Elevator_pitch"&gt;Elevator Pitch&lt;/a&gt; for &lt;a href="http://metawidget.org"&gt;Metawidget&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I was inspired by the style of the &lt;a href="http://www.google.com/googlebooks/chrome"&gt;Google Chrome Book&lt;/a&gt;, though clearly I'm no &lt;a href="http://www.scottmccloud.com/"&gt;Scott McCloud&lt;/a&gt;!&lt;br /&gt;&lt;br /&gt;Check it out here: &lt;a href="http://metawidget.org/elevator.html"&gt;http://metawidget.org/elevator.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;As always, your feedback is much appreciated.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-6720603368018101845?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/6720603368018101845/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=6720603368018101845' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/6720603368018101845'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/6720603368018101845'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/09/metawidget-elevator-pitch.html' title='Metawidget Elevator Pitch'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/__YNTBm_fS_I/St6Fs4jpBqI/AAAAAAAAALk/p7fIEQzG-aU/s72-c/elevator-pitch.jpg' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-3686470678329997020</id><published>2009-08-11T14:45:00.010+10:00</published><updated>2009-08-13T08:43:35.299+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='GWT'/><category scheme='http://www.blogger.com/atom/ns#' term='Metawidget'/><title type='text'>GWT Metawidget takes a walk on the client side</title><content type='html'>The latest release of &lt;a href="http://metawidget.org"&gt;Metawidget&lt;/a&gt; upgrades our &lt;a href="http://code.google.com/webtoolkit"&gt;GWT&lt;/a&gt; support to 1.7 and includes a new example of running pure client-side GWT:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/__YNTBm_fS_I/SoD3vc35kaI/AAAAAAAAAKs/0d-86BlQFoM/s1600-h/web-clientside.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 251px;" src="http://1.bp.blogspot.com/__YNTBm_fS_I/SoD3vc35kaI/AAAAAAAAAKs/0d-86BlQFoM/s320/web-clientside.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5368563150402195874" /&gt;&lt;/a&gt;By default, GwtMetawidget inspects business objects server-side. This is because &lt;a href="http://kennardconsulting.blogspot.com/2008/06/gwt-and-metawidget-part-1-reflection.html"&gt;client-side JavaScript does not support reflections or annotations&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;However if you don't need reflections or annotations, and have your own way of retrieving inspection results, you can plug in your own Inspector and keep everything client-side. This example retrieves inspection results from a textarea and generates the UI.&lt;br /&gt;&lt;br /&gt;Download the example &lt;a href="http://metawidget.org/download.html"&gt;here&lt;/a&gt;. More documentation can be found &lt;a href="http://metawidget.org/doc/reference/en/html/ch01s03.html#section-introduction-part3-gwt-clientside"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-3686470678329997020?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/3686470678329997020/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=3686470678329997020' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/3686470678329997020'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/3686470678329997020'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/08/metawidget-takes-walk-on-client-side.html' title='GWT Metawidget takes a walk on the client side'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/__YNTBm_fS_I/SoD3vc35kaI/AAAAAAAAAKs/0d-86BlQFoM/s72-c/web-clientside.jpg' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-377856801661972516</id><published>2009-08-11T14:37:00.008+10:00</published><updated>2009-08-11T15:13:38.400+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Metawidget'/><category scheme='http://www.blogger.com/atom/ns#' term='ICEfaces'/><title type='text'>Metawidget on ICE</title><content type='html'>The latest release of &lt;a href="http://metawidget.org"&gt;Metawidget&lt;/a&gt; includes support for the &lt;a href="http://icefaces.org"&gt;ICEfaces component library&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;Metawidget's philosophy of not 'owning' the UI, of integrating with &lt;em&gt;existing&lt;/em&gt; UI frameworks and component libraries, means it can easily take advantage of awesome component libraries such as ICEfaces and all the AJAX-goodness they provide.&lt;br /&gt;&lt;br /&gt;The Metawidget distribution includes an example of Metawidget and ICEfaces working together to deliver a rich, AJAX-driven UI with minimal code:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/__YNTBm_fS_I/SoD2RqVkCDI/AAAAAAAAAKk/HmYQFIy1w0k/s1600-h/web-penguincolony.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 206px;" src="http://1.bp.blogspot.com/__YNTBm_fS_I/SoD2RqVkCDI/AAAAAAAAAKk/HmYQFIy1w0k/s320/web-penguincolony.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5368561539108571186" /&gt;&lt;/a&gt;&lt;br /&gt;All the input boxes and command buttons in the screenshot are generated at runtime by Metawidget, and update dynamically using AJAX.&lt;br /&gt;&lt;br /&gt;Download the example &lt;a href="http://metawidget.org/download.html"&gt;here&lt;/a&gt;. More documentation can be found &lt;a href="http://metawidget.sourceforge.net/doc/reference/en/html/ch01s03.html#section-introduction-part3-penguincolony"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-377856801661972516?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/377856801661972516/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=377856801661972516' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/377856801661972516'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/377856801661972516'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/08/metawidget-on-ice.html' title='Metawidget on ICE'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/__YNTBm_fS_I/SoD2RqVkCDI/AAAAAAAAAKk/HmYQFIy1w0k/s72-c/web-penguincolony.jpg' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-8521536643587425693</id><published>2009-08-11T14:29:00.007+10:00</published><updated>2009-08-11T14:48:24.161+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Metawidget'/><title type='text'>AJAX User Interface Generator: Metawidget v0.8</title><content type='html'>&lt;a href="http://metawidget.org"&gt;Metawidget&lt;/a&gt; v0.8 is now available. This release includes:&lt;ul&gt;&lt;li&gt;&lt;a href="http://kennardconsulting.blogspot.com/2009/08/metawidget-on-ice.html"&gt;ICEfaces support and demo&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;&lt;a href="http://kennardconsulting.blogspot.com/2009/08/metawidget-takes-walk-on-client-side.html"&gt;GWT client-side demo (and upgrade to GWT 1.7)&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;&lt;a href="http://metawidget.org/doc/reference/en/html/ch05s02.html#section-inspectors-beanvalidation"&gt;Bean Validation (JSR 303) support&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;&lt;a href="http://metawidget.org/doc/reference/en/html/ch05s02.html#section-inspectors-oval"&gt;OVal support&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;&lt;a href="http://metawidget.org/doc/reference/en/html/ch04s02.html#section-widgetbuilders-tomahawk"&gt;Tomahawk support&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;&lt;a href="http://metawidget.org/doc/reference/en/html/ch04s02.html#section-widgetbuilders-extgwt"&gt;ExtGWT support&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;More documentation&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;Bug fixes; and&lt;/li&gt;&lt;br /&gt;   &lt;li&gt;More unit tests&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;Special thanks to &lt;a href="http://blog.icefaces.org/blojsom/blog/default/Ted%20Goddard"&gt;Ted Goddard&lt;/a&gt; for his help with this release!&lt;br /&gt;&lt;br /&gt;As always, the best place to start is the Reference Documentation:&lt;br /&gt;&lt;br /&gt;&lt;div align="center"&gt;&lt;a href="http://metawidget.org/doc/reference/en/pdf/metawidget.pdf"&gt;http://metawidget.org/doc/reference/en/pdf/metawidget.pdf&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Your continued feedback is invaluable to us. Please download it and let us know what you think.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-8521536643587425693?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/8521536643587425693/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=8521536643587425693' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/8521536643587425693'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/8521536643587425693'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/08/ajax-user-interface-generator.html' title='AJAX User Interface Generator: Metawidget v0.8'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-3081979082334075655</id><published>2009-08-03T10:56:00.013+10:00</published><updated>2009-08-03T17:50:46.039+10:00</updated><title type='text'>Geek mid-life crisis</title><content type='html'>I recently completed my 'geek mid-life crisis' and joined the ranks of those who attempt to recapture their youth and relive by-gone days by building their own arcade cabinet.&lt;br /&gt;&lt;br /&gt;I was inspired by watching &lt;a href="http://en.wikipedia.org/wiki/The_King_of_Kong"&gt;The King of Kong&lt;/a&gt; and considered purchasing &lt;a href="http://www.mygamesroom.com.au/item_challenger.html"&gt;a professional cabinet&lt;/a&gt;, but installing something bigger than a fridge freezer in the living room is a tough sell for a family home: I needed something more slimline and less obtrusive, which is when I came across the &lt;a href="http://forum.arcadecontrols.com/index.php?topic=89163.0"&gt;awesome job this guy had done&lt;/a&gt;, so I decided to have a go myself.&lt;br /&gt;&lt;br /&gt;Here are the initial plans (they changed a little bit during construction):&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/__YNTBm_fS_I/SnY2OpwEG7I/AAAAAAAAAJ0/IQe4k95-dSI/s1600-h/plans.gif"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 233px;" src="http://3.bp.blogspot.com/__YNTBm_fS_I/SnY2OpwEG7I/AAAAAAAAAJ0/IQe4k95-dSI/s320/plans.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5365535631412042674" /&gt;&lt;/a&gt;&lt;br /&gt;The work in progress (this was my first time with a &lt;a href="http://en.wikipedia.org/wiki/Router_(woodworking)"&gt;router&lt;/a&gt;, or even a drill for that matter, so I asked &lt;a href="http://www.homeimprovementpages.com.au/professional/13022"&gt;a grown up to help me&lt;/a&gt;):&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/__YNTBm_fS_I/SnY2YdIB43I/AAAAAAAAAJ8/3BxUIf9cl5k/s1600-h/routing.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 213px;" src="http://3.bp.blogspot.com/__YNTBm_fS_I/SnY2YdIB43I/AAAAAAAAAJ8/3BxUIf9cl5k/s320/routing.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5365535799821591410" /&gt;&lt;/a&gt;&lt;br /&gt;And the finished product (a few months later):&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/__YNTBm_fS_I/SnY8LT2XRGI/AAAAAAAAAKE/bZX-rvcIWxU/s1600-h/arcade1.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 214px; height: 320px;" src="http://1.bp.blogspot.com/__YNTBm_fS_I/SnY8LT2XRGI/AAAAAAAAAKE/bZX-rvcIWxU/s320/arcade1.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5365542171063043170" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/__YNTBm_fS_I/SnY8LoGETDI/AAAAAAAAAKM/vr3OA-_sMd0/s1600-h/arcade2.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 214px; height: 320px;" src="http://3.bp.blogspot.com/__YNTBm_fS_I/SnY8LoGETDI/AAAAAAAAAKM/vr3OA-_sMd0/s320/arcade2.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5365542176497617970" /&gt;&lt;/a&gt;&lt;br /&gt;Costs (in Aussie dollars):&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Joystick and Encoder board (incl shipping) - $203.85 - &lt;a href="http://replayarcade.com.au"&gt;Replay Arcade&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;20" L200P LCD monitor - $172 (incl shipping) - &lt;a href="http://ebay.com.au"&gt;eBay&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Coin buttons - $18 (incl shipping) - &lt;a href="http://ozstick.com.au"&gt;OzStick&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Wood - $120 - &lt;a href="http://www.billsboardfactory.com.au"&gt;Bill's boards&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Artwork - $170.50 (incl shipping) - &lt;a href="http://mamemarquees.com"&gt;MAME Marquees&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;1L Black paint - $30&lt;/li&gt;&lt;br /&gt;&lt;li&gt;6mm laminated glass - $40 - &lt;a href="http://www.jbglass.com.au"&gt;JB Glass&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;2 perspex sheets - $20&lt;/li&gt;&lt;br /&gt;&lt;li&gt;2.8GHz 1GB P4 PC - free (a local school was getting rid of one)&lt;/li&gt;&lt;/ul&gt;All up about $800 Aussie dollars, plus a bunch of sweat and tears and, yes, even blood (I cut myself a couple times). Sincere thanks to the many people who either helped or were inconvenienced during this hair-brained project!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-3081979082334075655?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/3081979082334075655/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=3081979082334075655' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/3081979082334075655'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/3081979082334075655'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/08/geek-mid-life-crisis.html' title='Geek mid-life crisis'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/__YNTBm_fS_I/SnY2OpwEG7I/AAAAAAAAAJ0/IQe4k95-dSI/s72-c/plans.gif' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-7090385561656605116</id><published>2009-08-01T11:29:00.005+10:00</published><updated>2009-12-11T11:05:39.743+11:00</updated><title type='text'>Home office</title><content type='html'>I thought I'd post a couple snaps of where I've been spending my working day for the last 18 months:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/__YNTBm_fS_I/SnY9gh4o40I/AAAAAAAAAKU/Rfz31EFL66U/s1600-h/office.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 214px; height: 320px;" src="http://3.bp.blogspot.com/__YNTBm_fS_I/SnY9gh4o40I/AAAAAAAAAKU/Rfz31EFL66U/s320/office.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5365543635119563586" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/__YNTBm_fS_I/SnY9gyoXtfI/AAAAAAAAAKc/4ckPShONeOQ/s1600-h/screens.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 213px;" src="http://3.bp.blogspot.com/__YNTBm_fS_I/SnY9gyoXtfI/AAAAAAAAAKc/4ckPShONeOQ/s320/screens.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5365543639614731762" /&gt;&lt;/a&gt;I'm a big advocate of multiple screens: two is definitely better than one; after a while with two you start thinking you need a third; and if you go three you need 2 video cards so you may as well have four! What do I use four screens for?&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Far left:&lt;/strong&gt; e-mail, reading PDFs of specifications etc&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Middle left:&lt;/strong&gt; running version of whatever application I'm developing&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Middle right:&lt;/strong&gt; my IDE, spread over two screens, with this screen being a &lt;em&gt;full screen of source code&lt;/em&gt; (bliss :)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Far right:&lt;/strong&gt; the rest of my IDE, including server console, debugging tree, folder navigator etc&lt;/li&gt;&lt;/ul&gt;It took a little while to get used to, but now I'd never want to go back.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-7090385561656605116?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/7090385561656605116/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=7090385561656605116' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/7090385561656605116'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/7090385561656605116'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/08/home-office.html' title='Home office'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/__YNTBm_fS_I/SnY9gh4o40I/AAAAAAAAAKU/Rfz31EFL66U/s72-c/office.jpg' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-2272840061966243119</id><published>2009-07-17T08:37:00.010+10:00</published><updated>2009-07-27T13:30:22.843+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Metawidget (Technical Stuff)'/><title type='text'>On the value of reflection</title><content type='html'>The research methodologies of &lt;a href="http://en.wikipedia.org/wiki/Action_research"&gt;Action Research&lt;/a&gt; and &lt;a href="http://en.wikipedia.org/wiki/Reflective_practice"&gt;Reflective Practice&lt;/a&gt; instruct that 'reflections' (as in &lt;a href="http://en.wikipedia.org/wiki/Contemplation"&gt;contemplation&lt;/a&gt;, not as in &lt;a href="http://en.wikipedia.org/wiki/Type_introspection"&gt;type introspection&lt;/a&gt;) from a previous software development phase should drive the planning for the next phase (much like the industry methodology of &lt;a href="http://en.wikipedia.org/wiki/Iterative_and_incremental_development"&gt;Iterative Development&lt;/a&gt;). It is perhaps worthwhile to reinforce the value of this reflection. After all, reflection is expensive. Conducting experiments, interviews and case studies consumes valuable time and resources, and it is legitimate to question whether its benefits outweigh its cost.&lt;br /&gt;&lt;br /&gt;One of the most important factors in software development is scope: deciding what to include and what to leave out. &lt;a href="http://en.wikipedia.org/wiki/Scope_creep"&gt;Scope creep&lt;/a&gt; and &lt;a href="http://en.wikipedia.org/wiki/Software_bloat"&gt;feature bloat&lt;/a&gt; are recognised risks, impacting development costs and release schedules. Good architects carefully apply rules of thumb: every design decision should 'carry its own weight', and strive to 'kill several birds with one stone'. But an implicit difficulty in evaluating this is knowing what the 'birds' are. Once out of its initial planning phases, software development has a tendency to lurch from immediate issue to immediate issue, dealing with each new requirement as it arises. &lt;strong&gt;Considering new requirements in isolation invariably means the burden of large-scale redesign to satisfy any one requirement will seem onerous: a smaller-scale, less impactful alternative will always seem the better option&lt;/strong&gt;. Reflection, on the other hand, allows the practitioner to consider many weeks worth of problems in a holistic light: he can see all the birds at once, and an approach that once seemed over-engineered now appears justified. Surfacing all the issues at the same time clears a path forward that otherwise would have seemed prohibitive.&lt;br /&gt;&lt;br /&gt;This phenomena is analogous to neural networks. While progressing to solve a given problem, a neural network may get trapped, still short of the best solution, in a &lt;a href="http://en.wikipedia.org/wiki/Maxima_and_minima"&gt;local minima&lt;/a&gt;. The local minima itself does not represent the best answer, but none of the immediate ways out of the minima are enough of an improvement to overcome the walls of the valley. It takes a combined push, a sort of disruptive excitation, to escape the trough so that a better solution can be found.&lt;br /&gt;&lt;br /&gt;So much for the theory - is it demonstrable in practice? Here I will give personal testimony. One of the themes from &lt;a href="http://metawidget.org"&gt;Metawidget&lt;/a&gt;'s alpha cycle reflections was support for 1-to-M relations. In itself, this seemed a corner case: difficult to support within the current architecture without a slippery slope of requirements around sorting, pagination and summary-to-detail navigation. Another theme was support for third-party UI components. The most challenging case study indicated this would have improved adoption, though it was not a primary factor. A third issue was around supporting the SWT library: the current design of 'return null to render nothing, return a dummy Metawidget to trigger nesting' was backwards for SWT's purposes, though this was being worked around in a sub-optimal way.&lt;br /&gt;&lt;br /&gt;Individually, none of these requirements seemed enough to justify a significant reworking of the widget creation subsystem. Indeed, the theme of 1-to-M relations gnawed at me for months with no obvious solution within the existing architecture. It was only reframing it within the context of the additional requirements of 'supporting third-party components' and 'turning widget creation inside out' that a new path presented itself (see &lt;a href="http://metawidget.sourceforge.net/doc/reference/en/html/ch02s03.html"&gt;WidgetBuilders&lt;/a&gt;). Looking back, I realise I was probably especially resistant to seeing this path because it was in an area I had &lt;a href="http://kennardconsulting.blogspot.com/2007/12/metawidget-and-widget-builders-good.html"&gt;already considered and decided against&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In summary, I have found explicit reflection to be an enlightening and worthwhile use of a project's time. It is easy to skip this phase in the heady rush of pumping out release and release, but when one takes the time to properly pause for breath important insights can be gained.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-2272840061966243119?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/2272840061966243119/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=2272840061966243119' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/2272840061966243119'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/2272840061966243119'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/07/on-value-of-reflection.html' title='On the value of reflection'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-1757152905158890692</id><published>2009-07-10T15:36:00.003+10:00</published><updated>2009-07-10T17:18:13.670+10:00</updated><title type='text'>WikidPad: does just what it says on the tin</title><content type='html'>Just a quick shout out to &lt;a href="http://wikidpad.sourceforge.net"&gt;WikidPad&lt;/a&gt;. It's always great when you find a piece of software that 'just works', in just the way you expect it to, and does just what you want it to.&lt;br /&gt;&lt;br /&gt;I've been looking for a good place to file away all those random bits of knowledge you accumulate during a day, but are not suitable for public consumption (either because they're confidential, or not properly formatted, or whatever) and having a personal, standalone Wiki on my desktop is just perfect!&lt;br /&gt;&lt;br /&gt;Thanks guys!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-1757152905158890692?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/1757152905158890692/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=1757152905158890692' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/1757152905158890692'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/1757152905158890692'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/07/wikipad-does-just-what-it-says-on-tin.html' title='WikidPad: does just what it says on the tin'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-539885035303427747</id><published>2009-06-01T11:03:00.010+10:00</published><updated>2009-07-03T13:44:26.785+10:00</updated><title type='text'>HtmlUnit: listening to their customers</title><content type='html'>I'm delighted to say I received an e-mail from the &lt;a href="http://htmlunit.sourceforge.net"&gt;HtmlUnit&lt;/a&gt; team this morning that a couple of the &lt;a href="http://kennardconsulting.blogspot.com/2009/03/htmlunit-vs-httpunit.html"&gt;requests I made&lt;/a&gt; have been incorporated into their next build. Thanks guys! Such prompt attention and turn-around really reinforces my confidence in the decision to switch.&lt;br /&gt;&lt;br /&gt;They've added &lt;strong&gt;getAnchorByText&lt;/strong&gt; and &lt;strong&gt;getOptionByText&lt;/strong&gt;. This means I can remove some of the code from my ad hoc &lt;strong&gt;HtmlUnitUtils&lt;/strong&gt; class. Of course, there are still more it'd be nice to see. I include here the whole of my utils class so that they may pick away at it for anything else they may want to incorporate.&lt;br /&gt;&lt;br /&gt;Naturally I don't expect it all, or even most. But whatever they may add is awesome as it means less code for me to maintain!&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;package com.kennardconsulting.core.util;&lt;br /&gt;&lt;br /&gt;import java.io.ByteArrayInputStream;&lt;br /&gt;import java.io.ByteArrayOutputStream;&lt;br /&gt;import java.io.File;&lt;br /&gt;import java.io.FileOutputStream;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.InputStream;&lt;br /&gt;import java.net.URL;&lt;br /&gt;import java.util.Arrays;&lt;br /&gt;import java.util.List;&lt;br /&gt;&lt;br /&gt;import org.metawidget.util.CollectionUtils;&lt;br /&gt;import org.metawidget.util.simple.StringUtils;&lt;br /&gt;import org.w3c.dom.Node;&lt;br /&gt;import org.w3c.dom.NodeList;&lt;br /&gt;&lt;br /&gt;import com.gargoylesoftware.htmlunit.Page;&lt;br /&gt;import com.gargoylesoftware.htmlunit.WebWindow;&lt;br /&gt;import com.gargoylesoftware.htmlunit.html.HtmlAnchor;&lt;br /&gt;import com.gargoylesoftware.htmlunit.html.HtmlElement;&lt;br /&gt;import com.gargoylesoftware.htmlunit.html.HtmlFileInput;&lt;br /&gt;import com.gargoylesoftware.htmlunit.html.HtmlForm;&lt;br /&gt;import com.gargoylesoftware.htmlunit.html.HtmlOption;&lt;br /&gt;import com.gargoylesoftware.htmlunit.html.HtmlPage;&lt;br /&gt;import com.gargoylesoftware.htmlunit.html.HtmlRadioButtonInput;&lt;br /&gt;import com.gargoylesoftware.htmlunit.html.HtmlSelect;&lt;br /&gt;import com.gargoylesoftware.htmlunit.html.HtmlTable;&lt;br /&gt;import com.kennardconsulting.core.enumeration.FileFormat;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Utilities for working with HtmlUnit.&lt;br /&gt; */&lt;br /&gt;&lt;br /&gt;public final class HtmlUnitUtils&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Public statics&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static HtmlAnchor getLink( HtmlPage response, String text )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;HtmlAnchor&amp;gt; links = getLinks( response, text );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( links.isEmpty() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;No such link with exact text of '&amp;quot; + text + &amp;quot;'&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( links.size() &amp;gt; 1 )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;More than one link with exact text of '&amp;quot; + text + &amp;quot;'&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return links.get( 0 );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static List&amp;lt;HtmlAnchor&amp;gt; getLinks( HtmlPage response, String text )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return getLinks( response, text, false );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static List&amp;lt;HtmlAnchor&amp;gt; getLinks( HtmlPage response, String text, boolean contains )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;HtmlAnchor&amp;gt; anchors = CollectionUtils.newArrayList();&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( HtmlAnchor anchor : response.getAnchors() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String anchorText = anchor.asText();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;anchorText = anchorText.replaceAll( &amp;quot;\r&amp;quot;, &amp;quot;&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( contains )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( !anchorText.contains( text ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( !anchorText.equals( text ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;anchors.add( anchor );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return anchors;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@SuppressWarnings( &amp;quot;unchecked&amp;quot; )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static &amp;lt;T extends HtmlElement&amp;gt; T getInputByNameEndingWith( HtmlForm form, String nameEndingWith )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( HtmlElement element : form.getHtmlElementsByTagName( &amp;quot;input&amp;quot; ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String elementName = element.getAttribute( &amp;quot;name&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( elementName == null )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( elementName.endsWith( nameEndingWith ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return (T) element;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return null;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static HtmlOption getSelectedOption( HtmlForm form, String selectName )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return getSelectedOption( form.getSelectByName( selectName ) );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static HtmlOption getSelectedOption( HtmlSelect select )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;HtmlOption&amp;gt; selectedOptions = select.getSelectedOptions();&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( selectedOptions.isEmpty() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return null;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( selectedOptions.size() &amp;gt; 1 )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;'&amp;quot; + select.getNameAttribute() + &amp;quot;' has more than one option selected&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return selectedOptions.get( 0 );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static String setSelectedOption( HtmlForm form, String selectName, String option )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return setSelectedOption( form.getSelectByName( selectName ), option );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static String setSelectedOption( HtmlForm form, String selectName, String option, boolean allowOnlyOne )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return setSelectedOption( form.getSelectByName( selectName ), option, allowOnlyOne );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static String setSelectedOption( HtmlSelect select, String option )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return setSelectedOption( select, option, true );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static String setSelectedOption( HtmlSelect select, String option, boolean allowOnlyOne )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String selectedValue = null;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( HtmlOption htmlOption : select.getOptions() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String htmlOptionText = htmlOption.asText();&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Special support for trimming off &amp;#160;, which we use for indenting select options&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;htmlOptionText = htmlOptionText.trim();&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( htmlOptionText.equals( option ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( selectedValue != null )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;Select '&amp;quot; + select.getNameAttribute() + &amp;quot;' has more than one '&amp;quot; + option + &amp;quot;'&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;selectedValue = htmlOption.getValueAttribute();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;htmlOption.setSelected( true );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( !allowOnlyOne )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( selectedValue == null )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;Select '&amp;quot; + select.getNameAttribute() + &amp;quot;' does not contain '&amp;quot; + option + &amp;quot;'&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return selectedValue;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static String setSelectedOptionValue( HtmlForm form, String selectName, String option )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return setSelectedOptionValue( form.getSelectByName( selectName ), option );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static String setSelectedOptionValue( HtmlSelect select, String optionValue )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String selectedValue = null;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( HtmlOption htmlOption : select.getOptions() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( htmlOption.getValueAttribute().equals( optionValue ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( selectedValue != null )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;Select '&amp;quot; + select.getNameAttribute() + &amp;quot;' has more than one '&amp;quot; + optionValue + &amp;quot;'&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;selectedValue = htmlOption.getValueAttribute();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;htmlOption.setSelected( true );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( selectedValue == null )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;Select '&amp;quot; + select.getNameAttribute() + &amp;quot;' does not contain value '&amp;quot; + optionValue + &amp;quot;'&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return selectedValue;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static boolean hasOption( HtmlForm form, String selectName, boolean selected, String... options )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int found = 0;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( HtmlOption htmlOption : form.getSelectByName( selectName ).getOptions() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( String option : options )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( htmlOption.asText().equals( option ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( selected &amp;&amp; !htmlOption.isSelected() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;found++;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return ( found == options.length );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static boolean hasOptionValue( HtmlForm form, String selectName, boolean selected, String... options )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int found = 0;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( HtmlOption htmlOption : form.getSelectByName( selectName ).getOptions() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( String option : options )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( htmlOption.getValueAttribute().equals( option ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( selected &amp;&amp; !htmlOption.isSelected() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;found++;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return ( found == options.length );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static String getOptionValue( HtmlForm form, String selectName, String option )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( HtmlOption htmlOption : form.getSelectByName( selectName ).getOptions() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( htmlOption.asText().equals( option ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return htmlOption.getValueAttribute();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;No option with text '&amp;quot; + option + &amp;quot;' found&amp;quot; );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static void setSelectedRadio( HtmlForm form, String radioName, String value )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;boolean selectedOne = false;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( HtmlRadioButtonInput htmlRadioButtonInput : form.getRadioButtonsByName( radioName ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( !htmlRadioButtonInput.getValueAttribute().trim().equals( value ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( selectedOne )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;Radio button group '&amp;quot; + radioName + &amp;quot;' has more than one '&amp;quot; + value + &amp;quot;'&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;selectedOne = true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;htmlRadioButtonInput.setChecked( true );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( !selectedOne )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;Radio button group '&amp;quot; + radioName + &amp;quot;' has no option '&amp;quot; + value + &amp;quot;'&amp;quot; );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static boolean hasRadio( HtmlForm form, String radioName, String value )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( HtmlRadioButtonInput htmlRadioButtonInput : form.getRadioButtonsByName( radioName ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( htmlRadioButtonInput.getValueAttribute().trim().equals( value ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static String getSelectedRadioValue( HtmlForm form, String radioName )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;HtmlRadioButtonInput radioButtonInputSelected = null;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( HtmlRadioButtonInput htmlRadioButtonInput : form.getRadioButtonsByName( radioName ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( !htmlRadioButtonInput.isChecked() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( radioButtonInputSelected != null )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;Radio button group '&amp;quot; + radioName + &amp;quot;' has more than one selected&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;radioButtonInputSelected = htmlRadioButtonInput;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( radioButtonInputSelected == null )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return null;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return radioButtonInputSelected.getValueAttribute();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@SuppressWarnings( &amp;quot;unchecked&amp;quot; )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static &amp;lt;E extends HtmlElement&amp;gt; E getElementByAttribute( HtmlPage page, String elementName, String attributeName, String attributeValue )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return (E) getElementByAttribute( page.getDocumentElement(), elementName, attributeName, attributeValue );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static &amp;lt;E extends HtmlElement&amp;gt; E getElementByAttribute( HtmlElement element, String elementName, String attributeName, String attributeValue )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;E&amp;gt; elements = element.getElementsByAttribute( elementName, attributeName, attributeValue );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( elements.isEmpty() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return null;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( elements.size() &amp;gt; 1 )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;More than one &amp;quot; + elementName + &amp;quot; with &amp;quot; + attributeName + &amp;quot; of '&amp;quot; + attributeValue + &amp;quot;'&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return elements.get( 0 );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static &amp;lt;E extends HtmlElement&amp;gt; E getElementByAttributeContaining( HtmlPage page, String elementName, String attributeName, String attributeValueContained )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;E&amp;gt; elements = getElementsByAttributeContaining( page, elementName, attributeName, attributeValueContained );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( elements.isEmpty() )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return null;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( elements.size() &amp;gt; 1 )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;More than one &amp;quot; + elementName + &amp;quot; with &amp;quot; + attributeName + &amp;quot; containing '&amp;quot; + attributeValueContained + &amp;quot;': &amp;quot; + CollectionUtils.toString( elements ) );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return elements.get( 0 );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @return the elements, in the order they are declared in the HTML.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; */&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@SuppressWarnings( &amp;quot;unchecked&amp;quot; )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static &amp;lt;E extends HtmlElement&amp;gt; List&amp;lt;E&amp;gt; getElementsByAttributeContaining( HtmlPage page, String elementName, String attributeName, String attributeValueContained )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;E&amp;gt; toReturn = CollectionUtils.newArrayList();&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;NodeList nodeList = page.getElementsByTagName( elementName );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for ( int loop = 0, length = nodeList.getLength(); loop &amp;lt; length; loop++ )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Node node = nodeList.item( loop );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Node nodeValue = node.getAttributes().getNamedItem( attributeName );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( nodeValue == null )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( nodeValue.getNodeValue().contains( attributeValueContained ) )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;toReturn.add( (E) node );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return toReturn;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@SuppressWarnings(&amp;quot;unchecked&amp;quot;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static &amp;lt;T extends Page&amp;gt; T waitForAjax( T page )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WebWindow window = page.getEnclosingWindow();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;window.getThreadManager().joinAll( 10000 );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return (T) window.getEnclosedPage();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static void setUpload( HtmlForm form, String uploadName, String url )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setUpload( form, uploadName, CoreStringUtils.substringAfterLast( url, StringUtils.SEPARATOR_FORWARD_SLASH ), url );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static void setUpload( HtmlForm form, String uploadName, String name, String url )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setUpload( form, uploadName, name, new URL( url ).openStream() );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;catch ( IOException e )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( e );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static void setUpload( HtmlForm form, String uploadName, String name, InputStream streamIn )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;HtmlFileInput fileInput = form.getInputByName( uploadName );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ByteArrayOutputStream streamOut = new ByteArrayOutputStream();&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IOUtils.streamBetween( streamIn, streamOut );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;catch ( IOException e )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( e );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fileInput.setValueAttribute( name );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fileInput.setData( streamOut.toByteArray() );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static HtmlTable getTableStartingWith( HtmlPage page, String startingWith )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;NodeList tables = page.getElementsByTagName( &amp;quot;table&amp;quot; );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for( int loop = 0, length = tables.getLength(); loop &amp;lt; length; loop++ )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;HtmlTable table = (HtmlTable) tables.item( loop );&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( table.asText().trim().startsWith( startingWith ))&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return table;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new RuntimeException( &amp;quot;No table starting with '&amp;quot; + startingWith + &amp;quot;'&amp;quot; );&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Private constructor&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;private HtmlUnitUtils()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Can never be called&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-539885035303427747?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/539885035303427747/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=539885035303427747' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/539885035303427747'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/539885035303427747'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/06/htmlunit-listening-to-their-customers.html' title='HtmlUnit: listening to their customers'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-8377887171097187286</id><published>2009-05-26T21:53:00.021+10:00</published><updated>2009-05-30T13:55:44.845+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Seam'/><category scheme='http://www.blogger.com/atom/ns#' term='Metawidget'/><title type='text'>Metawidget and Seam: saying goodbye to boilerplate code</title><content type='html'>This blog is to celebrate the inclusion of the &lt;a href="http://metawidget.org/" target="_blank"&gt;Metawidget&lt;/a&gt; examples in the upcoming release of &lt;a href="http://seamframework.org/" target="_blank"&gt;Seam&lt;/a&gt;. What is Metawidget? It's a 'smart User Interface widget' that populates itself, at runtime, with UI components to match the properties of your business objects.&lt;br /&gt;&lt;br /&gt;If you think of a typical Seam stack, you may have JPA at the bottom, then EJB, then Seam itself, then JSF, then maybe RichFaces to add a bit of polish. But there is still a gap at the very top - a gap that leads to a lot of 'boilerplate code'. This is where Metawidget comes in.&lt;br /&gt;&lt;br /&gt;Let me try and convey it visually using the image below. On the left are the complete contents of the &lt;strong&gt;book.xhtml&lt;/strong&gt; file from the Seam Groovy Booking example. On the right are the complete contents from the new Seam &lt;em&gt;Metawidget&lt;/em&gt; Groovy Booking example. The red boxes and lines highlight the chunks of boilerplate that have been replaced:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/__YNTBm_fS_I/ShvcMTzhWHI/AAAAAAAAAJk/I89Ftbnlyhs/s1600-h/book.xhtml.png"&gt;&lt;img id="BLOGGER_PHOTO_ID_5340103887210764402" style="DISPLAY: block; MARGIN: 0px auto 10px; WIDTH: 283px; CURSOR: hand; HEIGHT: 320px; TEXT-ALIGN: center" alt="" src="http://4.bp.blogspot.com/__YNTBm_fS_I/ShvcMTzhWHI/AAAAAAAAAJk/I89Ftbnlyhs/s320/book.xhtml.png" border="0" /&gt;&lt;/a&gt;The original file is 177 lines long. The Metawidget equivalent is 52 lines. That represents a 70% reduction in code, on top of the considerable reductions Seam already affords when developing enterprise applications. &lt;strong&gt;This ability to retrofit existing UIs, integrating with existing front-end and back-end technologies, is unique to Metawidget&lt;/strong&gt;.&lt;br /&gt;&lt;br /&gt;Now I want to be completely fair here - I'm a developer, not a marketing guy! You also have to add a few extra annotations to your business classes, add the Metawidget JAR to your project and create a little metawidget.xml file. If we compare all the source files that change:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/__YNTBm_fS_I/ShzRCOl3ybI/AAAAAAAAAJs/mOlq7K_0uVk/s1600-h/filesystem.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 117px;" src="http://2.bp.blogspot.com/__YNTBm_fS_I/ShzRCOl3ybI/AAAAAAAAAJs/mOlq7K_0uVk/s320/filesystem.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5340373094361450930" /&gt;&lt;/a&gt;&lt;br /&gt;By comparing file sizes, we see the overall code reduction is around 20%. We could allow that in such a small example the size of metawidget.xml is more significant than it should be: for apps with hundreds of screens, the impact of 2,789 bytes of metawidget.xml will be negligible. So if we compare file sizes without including metawidget.xml, we see the overall code reduction is around 40%. If you repeat this exercise with the Seam Metawidget DVD Store example (also included in the Seam distribution) the overall reduction is around 30%.&lt;br /&gt;&lt;br /&gt;Of course, on top of all that, lines of code is never a wondeful metric for comparing implementations. But anyway, hopefully you get the idea. Still, if you want a soundbite: &lt;strong&gt;Metawidget can save you up to 40% of your UI code&lt;/strong&gt;.&lt;br /&gt;&lt;br /&gt;My deepest thanks to the Seam guys, especially &lt;a href="http://www.mojavelinux.com" target="_blank"&gt;Dan Allen&lt;/a&gt;, for all their help integrating Metawidget into the Seam 2.1.2.GA build.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-8377887171097187286?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/8377887171097187286/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=8377887171097187286' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/8377887171097187286'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/8377887171097187286'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/05/metawidget-and-seam-saying-goodbye-to.html' title='Metawidget and Seam: saying goodbye to boilerplate code'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/__YNTBm_fS_I/ShvcMTzhWHI/AAAAAAAAAJk/I89Ftbnlyhs/s72-c/book.xhtml.png' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-8260121514770436500</id><published>2009-05-11T14:33:00.012+10:00</published><updated>2009-10-19T17:30:06.758+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Metawidget'/><title type='text'>Dynamic User Interface Generator: Metawidget v0.75</title><content type='html'>&lt;strong style="color: red"&gt;Update: the APIs shown in this blog entry have changed slightly in newer releases of Metawidget. Specifically metawidget.xml uses &amp;lt;array&amp;gt; instead of &amp;lt;list&amp;gt;. Please download the latest documentation from &lt;a href="http://metawidget.org"&gt;http://metawidget.org&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.metawidget.org/"&gt;Metawidget&lt;/a&gt; v0.75 is now available. This release includes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Pluggable widget libraries&lt;/li&gt;&lt;li&gt;SwingX support&lt;/li&gt;&lt;li&gt;DisplayTag support&lt;/li&gt;&lt;li&gt;Improved documentation&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;'Pluggable widget libraries' represents a significant refactoring of the widget generation code, intended to:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;simplify support of third party libraries, including mixing multiple third party libraries in the same application&lt;/li&gt;&lt;li&gt;pave the way for supporting Collections&lt;/li&gt;&lt;li&gt;pave the way for supporting some more UI toolkits (ie. SWT)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;It is also, unfortunately, a breaking change. Sorry!&lt;/p&gt;&lt;p&gt;&lt;span style="font-size:130%;"&gt;Migration Guide&lt;/span&gt;&lt;/p&gt;&lt;p&gt;To migrate from v0.7 to v0.75:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Change #1: inspector-config.xml is now metawidget.xml&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The role of inspector-config.xml has been expanded from configuring pluggable inspectors to configuring pluggable inspectors &lt;em&gt;and&lt;/em&gt; widget builders. It is also now a general configuration mechanism for all aspects of your Metawidget, such as default CSS settings etc.&lt;/p&gt;&lt;p&gt;You will need to refactor &lt;strong&gt;inspector-config.xml&lt;/strong&gt; files of the form...&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&amp;lt;inspector-config&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;compositeInspector xmlns="org.metawidget.inspector.composite"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;myinspector&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;...&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/myinspector&amp;gt;&lt;br /&gt;&amp;lt;/inspector-config&amp;gt;&lt;/div&gt;...to be &lt;strong&gt;metawidget.xml&lt;/strong&gt; files of the form...&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&amp;lt;metawidget&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;swingMetawidget xmlns="org.metawidget.swing"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;inspector&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;compositeInspector xmlns="org.metawidget.inspector.composite"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;list&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;myinspector&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;...&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/myinspector&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/list&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/compositeInspector&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/inspector&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/swingMetawidget&amp;gt;&lt;br /&gt;&amp;lt;/metawidget&amp;gt;&lt;/div&gt;The main differences with this new XML format are:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;it is now concerned with the top-level Metawidget, not just the inspectors inside it. This means you can also configure other Metawidget properties (see below)&lt;/li&gt;&lt;li&gt;method values must now be wrapped with their type (ie. &amp;lt;list&amp;gt;) - this allows us to support configuring multi-value methods such as setParameter&lt;/li&gt;&lt;li&gt;Full documentation can be found &lt;a href="http://metawidget.sourceforge.net/doc/reference/en/html/ch02s05.html"&gt;here&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;Use &lt;strong&gt;metawidget.setConfig&lt;/strong&gt; to set this new format, instead of &lt;strong&gt;metawidget.setInspectorConfig&lt;/strong&gt;.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Change #2: Metawidget.buildWidget is now WidgetBuilder.buildWidget&lt;/strong&gt;&lt;br /&gt;&lt;p&gt;If you had previously extended Metawidget to add support for a third party widget, you'll need to refactor your code into a WidgetBuilder. WidgetBuilders can be configured programmatically, or with the new metawidget.xml:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&amp;lt;metawidget&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;swingMetawidget xmlns="org.metawidget.swing"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;widgetBuilder&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;&lt;strong&gt;compositeWidgetBuilder xmlns="org.metawidget.widgetbuilder.composite"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;list&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;myWidgetBuilder /&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;swingWidgetBuilder /&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/list&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/compositeWidgetBuilder&amp;gt;&lt;/strong&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/widgetBuilder&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;inspector&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;...&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/inspector&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/swingMetawidget&amp;gt;&lt;br /&gt;&amp;lt;/metawidget&amp;gt;&lt;/div&gt;WidgetBuilders higher in the list get called first. If they return null the next WidgetBuilder in the list will be called. If all WidgetBuilders return null the parent Metawidget will instantiate a nested Metawidget.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Change #3: RichFacesMetawidget has been removed&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;To use JBoss RichFaces, you now use a regular UIMetawidget with a RichFacesWidgetBuilder. Full documentation can be found &lt;a href="http://metawidget.org/doc/reference/en/html/ch01s02.html#section-introduction-part2-web-richfaces"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Thanks!&lt;/span&gt;&lt;br /&gt;We apologise for the disruption these changes will cause, but strongly believe they will make Metawidget a better product for our v1.0 release. All documentation and examples have already been migrated.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-8260121514770436500?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/8260121514770436500/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=8260121514770436500' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/8260121514770436500'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/8260121514770436500'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/05/dynamic-user-interface-generator.html' title='Dynamic User Interface Generator: Metawidget v0.75'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-9194081334025519766</id><published>2009-03-03T10:58:00.009+11:00</published><updated>2009-03-03T12:38:50.267+11:00</updated><title type='text'>HtmlUnit vs HttpUnit</title><content type='html'>Today I bid a fond farewell to &lt;a href="http://httpunit.sourceforge.net/"&gt;HttpUnit&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I've been using HttpUnit for my black-box testing for about 3 years, and I really like it. However its JavaScript support just hasn't kept up with our needs, and it seems &lt;a href="http://htmlunit.sourceforge.net/"&gt;HtmlUnit&lt;/a&gt; has a much more active community around it.&lt;br /&gt;&lt;br /&gt;I converted about 6,000 lines of test scripts in about 3 days. I thought the HtmlUnit folks (if not the HttpUnit folks) might be interested in what I experienced.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;First, the FUD&lt;/span&gt;&lt;br /&gt;HttpUnit is a great project, and very similar to HtmlUnit.&lt;br /&gt;&lt;br /&gt;Blog entries like &lt;a href="http://daniel.gredler.net/2007/10/04/htmlunit-vs-httpunit"&gt;this&lt;/a&gt; (from an HtmlUnit guy) paint an inaccurate picture saying that HttpUnit is 'fairly low-level, modeling web interactions at something approaching the HTTP request and response level' whilst HtmlUnit is 'more high-level than HttpUnit’s, modeling web interaction in terms of the documents and interface elements which the user interacts with'. It then gives an HttpUnit example using requests and responses, and an HtmlUnit example using forms and input controls.&lt;br /&gt;&lt;br /&gt;But the examples are not comparing apples to apples. HttpUnit does forms (and input controls, and tables, and JavaScript) too - and in almost exactly the same way as HtmlUnit. I'm not saying this is deliberate deception, but I think such a comparison is unfair and may even be detrimental to HtmlUnit because developers may be more reluctant to 'make the switch' if they perceive the APIs are very different.&lt;br /&gt;&lt;br /&gt;In fact, the API methods are almost 1-to-1 identical. In coverting about 6,000 lines of code here's what I found:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;With HttpUnit: &lt;strong&gt;6,198 lines&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;With HtmlUnit: &lt;strong&gt;6,285 lines&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt;That's less than a 1% difference - not really a difference of 'high level' versus 'low level'.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Next, the Good&lt;br /&gt;&lt;/span&gt;The HtmlUnit API definitely feels nicer.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;There's some neat &lt;em&gt;'public &amp;lt;I&amp;gt; I getInputByName'&lt;/em&gt; code that saves a lot of casting - I've stolen this idea for the next release of &lt;a href="http://metawidget.org/"&gt;Metawidget&lt;/a&gt;&lt;/li&gt;&lt;li&gt;I love the &lt;em&gt;asText&lt;/em&gt; and &lt;em&gt;asXml&lt;/em&gt; methods which do a lot of parsing for you&lt;/li&gt;&lt;li&gt;HttpUnit used to silently submit forms using 'null' if the button you asked it to submit didn't exist (eg. &lt;em&gt;form.submit( form.getSubmitButton( 'not-there' ))).&lt;/em&gt; HtmlUnit doesn't do this&lt;/li&gt;&lt;li&gt;You can set file upload boxes just like regular text boxes. HttpUnit required you to wade through some &lt;em&gt;form.getRequest&lt;/em&gt; goo&lt;/li&gt;&lt;li&gt;I love that things like &lt;em&gt;HtmlTextInput&lt;/em&gt; are implemented as direct extensions of the internal DOM, rather than some parallel heirarchy&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-size:130%;"&gt;Finally, the Bad&lt;/span&gt;&lt;br /&gt;There are some things from the HttpUnit API I missed:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Locating anchors in HtmlUnit is very fiddly. You have &lt;em&gt;page.getAnchorByName&lt;/em&gt; and &lt;em&gt;page.getAnchorByHref&lt;/em&gt;. But this is black box testing - it's meant to test the 'user experience'. And the user never gets to see either an anchor's &lt;em&gt;name&lt;/em&gt; or its &lt;em&gt;href&lt;/em&gt;. HttpUnit had a &lt;em&gt;response.getLinkWith&lt;/em&gt; method that located an anchor by its 'innerHTML' or 'what the user actually sees'. This would be very helpful?&lt;/li&gt;&lt;li&gt;Choosing options from select boxes in HtmlUnit is similarly fiddly. You have &lt;em&gt;select.setSelectedAttribute(optionValue) &lt;/em&gt;but again this is keying off something the user never sees. I'd really like a &lt;em&gt;select.setSelectedAttribute(innerHTML)&lt;/em&gt; so I can simulate choosing what the user chooses?&lt;/li&gt;&lt;li&gt;HtmlUnit warns that use of the script type 'text/javascript' is obsolete, and maybe it is (as of about 2007). But that's a pretty recent change. If you try &lt;em&gt;&amp;lt;script/&amp;gt;&lt;/em&gt; in the &lt;a href="http://validator.w3.org/"&gt;W3C validator&lt;/a&gt; it still suggests using 'text/javascript', and older browsers will want to see it. So this seems a very noisy warning to have on by default?&lt;/li&gt;&lt;li&gt;There doesn't seem a good equivalent to HttpUnit's &lt;em&gt;form.getParameterNames?&lt;/em&gt;&lt;/li&gt;&lt;/ul&gt;Overall, though, there's really no bad: HtmlUnit is the better product. Certainly its JavaScript support seems much better. I guess the only bad is that interest around HttpUnit has waned and it's therefore no longer a close competitor. Competiton is good, incentive to improve is good, and could have only made both products better.&lt;br /&gt;&lt;br /&gt;Many thanks to both the HttpUnit and the HtmlUnit teams for all their hard work and contributions to the community!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-9194081334025519766?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/9194081334025519766/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=9194081334025519766' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/9194081334025519766'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/9194081334025519766'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/03/htmlunit-vs-httpunit.html' title='HtmlUnit vs HttpUnit'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-2289664523656604649</id><published>2009-02-12T17:00:00.000+11:00</published><updated>2009-02-13T17:03:21.346+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Metawidget'/><title type='text'>Declarative UI: Metawidget v0.7</title><content type='html'>&lt;a href="http://www.metawidget.org/"&gt;Metawidget&lt;/a&gt; v0.7 is now available. This release includes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Pluggable action bindings for GWT and Swing&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Pluggable Swing validation (including JGoodies Validator)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;MigLayout support&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Scala support&lt;/li&gt;&lt;br /&gt;&lt;li&gt;OSGi support&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Upgraded support for Android (1.0 R2) and Seam (2.1.1.GA)&lt;br /&gt;&lt;li&gt;Fluent API for configuring Inspectors programmatically&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;Special thanks to Stefan Ackermann, Ivaylo Kovatchev and Renato Garcia for their help with this release!&lt;br /&gt;&lt;br /&gt;As always, the best place to start is the Reference Documentation:&lt;br /&gt;&lt;br /&gt;&lt;div align="center"&gt;&lt;a href="http://metawidget.org/doc/reference/en/pdf/metawidget.pdf"&gt;http://metawidget.org/doc/reference/en/pdf/metawidget.pdf&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Your continued feedback is invaluable to us. Please download it and let us know what you think.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-2289664523656604649?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/2289664523656604649/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=2289664523656604649' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/2289664523656604649'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/2289664523656604649'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/02/declarative-ui-metawidget-v07.html' title='Declarative UI: Metawidget v0.7'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-2928738325606668672</id><published>2009-02-12T11:22:00.010+11:00</published><updated>2009-04-03T21:24:09.680+11:00</updated><title type='text'>UrlEncodedQueryString now Open Sourced</title><content type='html'>I just &lt;a href="https://urlencodedquerystring.dev.java.net/"&gt;Open Sourced my UrlEncodedQueryString implementation&lt;/a&gt; over at java.net.&lt;br /&gt;&lt;br /&gt;This is a project I worked on with Sun in response to &lt;a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6306820"&gt;this RFE&lt;/a&gt;, but that ultimately never made it into the JDK. I still use it extensively in my own projects, however, and find it very useful.&lt;br /&gt;&lt;br /&gt;A recent posting in the RFE's comments section prompted me to dust it off and give it a good home. Enjoy!&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Overview: Represents a www-form-urlencoded query string&lt;/h4&gt;An instance of this class represents a query string encoded using the &lt;span style="font-family:Courier;"&gt;www-form-urlencoded&lt;/span&gt; encoding scheme, as defined by &lt;a href="http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4.1"&gt;HTML 4.01 Specification: application/x-www-form-urlencoded&lt;/a&gt;, and &lt;a href="http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2.2"&gt;HTML 4.01 Specification: Ampersands in URI attribute values&lt;/a&gt;. This is a common encoding scheme of the query component of a URI, though the &lt;a href="http://www.ietf.org/rfc/rfc2396.txt"&gt;RFC 2396 URI specification&lt;/a&gt; itself does not define a specific format for the query component.&lt;br /&gt;&lt;br /&gt;This class provides static methods for creating UrlEncodedQueryString instances by parsing URI and string forms. Methods for creating, retrieving, updating and deleting the parameters on a query string, and methods for applying the query string back to an existing URI.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Encoding and decoding&lt;/h4&gt;UrlEncodedQueryString automatically encodes and decodes parameter names and values to and from &lt;span style="font-family:Courier;"&gt;www-form-urlencoded&lt;/span&gt; encoding by using &lt;span style="font-family:Courier;"&gt;java.net.URLEncoder&lt;/span&gt; and &lt;span style="font-family:Courier;"&gt;java.net.URLDecoder&lt;/span&gt;, which follow the &lt;a href="http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars"&gt;HTML 4.01 Specification: Non-ASCII characters in URI attribute values&lt;/a&gt; recommendation.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Multivalued parameters&lt;/h4&gt;Often, parameter names are unique across the name/value pairs of a &lt;span style="font-family:Courier;"&gt;www-form-urlencoded&lt;/span&gt; query string. However, it is permitted for the same parameter name to appear in multiple name/value pairs, denoting that a single parameter has multiple values. This less common use case can lead to ambiguity when adding parameters - is the 'add' a 'replace' (of an existing parameter, if one with the same name already exists) or an 'append' (potentially creating a multivalued parameter, if one with the same name already exists)?&lt;br /&gt;&lt;br /&gt;This requirement significantly shapes the &lt;span style="font-family:Courier;"&gt;UrlEncodedQueryString&lt;/span&gt; API. In particular there are:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-family:Courier;"&gt;set&lt;/span&gt; methods for setting a parameter, potentially replacing an existing value &lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;span style="font-family:Courier;"&gt;append&lt;/span&gt; methods for adding a parameter, potentially creating a multivalued parameter &lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;span style="font-family:Courier;"&gt;get&lt;/span&gt; methods for returning a single value, even if the parameter has multiple values &lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;span style="font-family:Courier;"&gt;getValues&lt;/span&gt; methods for returning multiple values &lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;h4&gt;Retrieving parameters&lt;/h4&gt;UrlEncodedQueryString can be used to parse and retrieve parameters from a query string by passing either a URI or a query string to its constructor:&lt;br /&gt;&lt;div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; MARGIN-TOP: 10pt; PADDING-LEFT: 5px; FONT-SIZE: 8pt; PADDING-BOTTOM: 5px; BORDER-LEFT: #cccccc 1px solid; PADDING-TOP: 5px; BORDER-BOTTOM: #cccccc 1px solid; FONT-FAMILY: Courier; BACKGROUND-COLOR: #eeeeee"&gt;URI uri = new URI("http://java.sun.com?forum=2");&lt;br /&gt;UrlEncodedQueryString queryString = new UrlEncodedQueryString(uri);&lt;br /&gt;System.out.println(queryString.get("forum"));&lt;/div&gt;&lt;br /&gt;&lt;h4&gt;Modifying parameters&lt;/h4&gt;UrlEncodedQueryString can be used to set, append or remove parameters from a query string:&lt;br /&gt;&lt;div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; MARGIN-TOP: 10pt; PADDING-LEFT: 5px; FONT-SIZE: 8pt; PADDING-BOTTOM: 5px; BORDER-LEFT: #cccccc 1px solid; PADDING-TOP: 5px; BORDER-BOTTOM: #cccccc 1px solid; FONT-FAMILY: Courier; BACKGROUND-COLOR: #eeeeee"&gt;URI uri = new URI("/forum/article.jsp?id=2&amp;amp;para=4");&lt;br /&gt;UrlEncodedQueryString queryString = new UrlEncodedQueryString(uri);&lt;br /&gt;queryString.set("id", 3);&lt;br /&gt;queryString.remove("para");&lt;br /&gt;System.out.println(queryString);&lt;/div&gt;When modifying parameters, the ordering of existing parameters is maintained. Parameters are &lt;span style="font-family:Courier;"&gt;set&lt;/span&gt; and &lt;span style="font-family:Courier;"&gt;removed&lt;/span&gt; in-place, while &lt;span style="font-family:Courier;"&gt;appended&lt;/span&gt; parameters are added to the end of the query string.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Applying the Query&lt;/h4&gt;UrlEncodedQueryString can be used to apply a modified query string back to a URI, creating a new URI:&lt;br /&gt;&lt;div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; MARGIN-TOP: 10pt; PADDING-LEFT: 5px; FONT-SIZE: 8pt; PADDING-BOTTOM: 5px; BORDER-LEFT: #cccccc 1px solid; PADDING-TOP: 5px; BORDER-BOTTOM: #cccccc 1px solid; FONT-FAMILY: Courier; BACKGROUND-COLOR: #eeeeee"&gt;URI uri = new URI("/forum/article.jsp?id=2");&lt;br /&gt;UrlEncodedQueryString queryString = new UrlEncodedQueryString(uri);&lt;br /&gt;queryString.set("id", 3);&lt;br /&gt;uri = queryString.apply(uri);&lt;/div&gt;When reconstructing query strings, there are two valid separator parameters defined by the W3C (ampersand "&amp;amp;" and semicolon ";"), with ampersand being the most common. The &lt;span style="font-family:Courier;"&gt;apply&lt;/span&gt; and &lt;span style="font-family:Courier;"&gt;toString&lt;/span&gt; methods both default to using an ampersand, with overloaded forms for using a semicolon.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Thread Safety&lt;/h4&gt;This implementation is not synchronized. If multiple threads access a query string concurrently, and at least one of the threads modifies the query string, it must be synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the query string.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;JavaDoc&lt;/h4&gt;For more information, &lt;a href="https://urlencodedquerystring.dev.java.net/files/documents/10018/126470/UrlEncodedQueryString.htm"&gt;see the JavaDoc&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-2928738325606668672?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/2928738325606668672/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=2928738325606668672' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/2928738325606668672'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/2928738325606668672'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/02/urlencodedquerystring-now-open-sourced.html' title='UrlEncodedQueryString now Open Sourced'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-1841230792841869080</id><published>2009-01-30T15:17:00.002+11:00</published><updated>2009-01-30T15:25:40.910+11:00</updated><title type='text'>It's the Length Of The Compile/Debug Cycle, Stupid (with apologies to Bill Clinton)</title><content type='html'>More than anything, I blog this to remind myself of this, because I seem to forget it more often than I should:&lt;br /&gt;&lt;br /&gt;&lt;em&gt;"The single most important factor in the quality of an individual's software development is the length of the compile/debug cycle"&lt;/em&gt;&lt;br /&gt;&lt;em&gt;&lt;/em&gt;&lt;br /&gt;I don't know how many times I think to myself 'I &lt;em&gt;could&lt;/em&gt; set up a proper environment in which to develop and test this change, but it's such a little thing I'll just fudge it'. Like I use the Ant build instead of the IDE build. Or I redeploy the EAR instead of setting up hot class replacement.&lt;br /&gt;&lt;br /&gt;And every single time the 'little thing' requires far more compile/debug cycles than I expected, so the job either:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;takes way longer than it should&lt;/li&gt;&lt;li&gt;gets done sloppily because you can't stomach umpteen more compile/debug cycles to tweak every last little pixel alignment&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;And every single time I wish I'd spent that little bit of extra time, upfront, to set up my environment efficiently.&lt;/p&gt;&lt;p&gt;This principle extends to the build/upload cycle too. If I can reduce the amount of stuff I have to re-upload to the server when I make a change, I can reduce the amount of time I have to get distracted and go surf &lt;a href="http://slashdot.org/"&gt;slashdot&lt;/a&gt;, thereby 'context switching' my brain and slowing me down even more.&lt;/p&gt;&lt;p&gt;Anything that reduces that compile/debug cycle is gold. We've all known this for a long time, but I think we all often forget it again too.&lt;/p&gt;&lt;p&gt;Gah!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-1841230792841869080?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/1841230792841869080/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=1841230792841869080' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/1841230792841869080'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/1841230792841869080'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2009/01/its-length-of-compiledebug-cycle-stupid.html' title='It&apos;s the Length Of The Compile/Debug Cycle, Stupid (with apologies to Bill Clinton)'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-2524320302314307368</id><published>2008-12-24T10:29:00.014+11:00</published><updated>2008-12-24T16:36:32.042+11:00</updated><title type='text'>New Features in Eclipse 3.5</title><content type='html'>&lt;div&gt;In an attempt to attract younger developers, the next version of Eclipse is to include &lt;a href="http://www.steampowered.com/status/l4d/"&gt;Unlockable Achievements&lt;/a&gt;:&lt;/div&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/__YNTBm_fS_I/SVF0mK1Va0I/AAAAAAAAAI4/0dDJKL55RSc/s1600-h/Achievements.png"&gt;&lt;img id="BLOGGER_PHOTO_ID_5283132036974209858" style="WIDTH: 640px; CURSOR: hand" alt="" src="http://1.bp.blogspot.com/__YNTBm_fS_I/SVF0mK1Va0I/AAAAAAAAAI4/0dDJKL55RSc/s640/Achievements.png" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;p&gt;Beta testers are encouraged to submit their own Achievement ideas. Suggestions include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Anally Retentive:&lt;/strong&gt; remove all warnings from your code. Yes, even those ones about serialVersionUID&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Saintly Patience:&lt;/strong&gt; use &lt;a href="http://java.sun.com/j2se/1.5.0/docs/api/java/math/BigDecimal.html"&gt;BigDecimal&lt;/a&gt; for more than 5 minutes without wishing for operator overloading in Java&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Ant Fan:&lt;/strong&gt; start an Ant build and watch it finish without visiting Slashdot in between&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-2524320302314307368?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/2524320302314307368/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=2524320302314307368' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/2524320302314307368'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/2524320302314307368'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2008/12/new-features-in-eclipse-35.html' title='New Features in Eclipse 3.5'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/__YNTBm_fS_I/SVF0mK1Va0I/AAAAAAAAAI4/0dDJKL55RSc/s72-c/Achievements.png' height='72' width='72'/><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5950537015062599287.post-7233196141234928412</id><published>2008-12-04T11:40:00.004+11:00</published><updated>2008-12-04T11:56:20.085+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Metawidget'/><category scheme='http://www.blogger.com/atom/ns#' term='Groovy'/><title type='text'>Better Than Free: Metawidget v0.65</title><content type='html'>There was a time, years ago, when just having a $0 price tag on a piece of software would have sparked people's interest. Nowadays, the success of Open Source has meant that &lt;em&gt;free is not enough&lt;/em&gt;.&lt;br /&gt;&lt;br /&gt;For a new software project to be noticed, it doesn't just have to be free: it has to be well documented, well tested, have plenty of working examples, an attractive Web site and high quality code. And even &lt;em&gt;then&lt;/em&gt; the competition is fierce: developers are busy people, and have very limited time to evaluate new technologies.&lt;br /&gt;&lt;br /&gt;So the big feature in the new release of &lt;a href="http://metawidget.org/"&gt;Metawidget&lt;/a&gt;, aside from a number of upgrades and enhancements (such as Commons Validator support), is a &lt;a href="http://metawidget.org/live-demo"&gt;Live Demo&lt;/a&gt; that lets you immediately get in and start playing and coding with Metawidget. I'm hoping it's a great way to let people try it without having to download, unzip and read through the usual distribution.&lt;br /&gt;&lt;br /&gt;The Live Demo uses an enhanced version of the &lt;a href="http://groovy.org/"&gt;Groovy 1.6 Console Applet&lt;/a&gt; to give you a fully working Java-like scripting environment. Everything is set up for you just to click &lt;strong&gt;Run&lt;/strong&gt;. You can then fiddle with the code, even import your own business model classes on to the CLASSPATH to see how Metawidget renders them.&lt;br /&gt;&lt;br /&gt;You can find the Live Demo here:&lt;br /&gt;&lt;br /&gt;&lt;center&gt;&lt;a href="http://metawidget.org/live-demo"&gt;http://metawidget.org/live-demo&lt;/a&gt;&lt;/center&gt;&lt;br /&gt;&lt;br /&gt;Also, special thanks to Gerardo Diazcorujo and Ryan Cornia for their help testing this release. Your continued feedback is invaluable to us. Please let us know what you think.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5950537015062599287-7233196141234928412?l=kennardconsulting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kennardconsulting.blogspot.com/feeds/7233196141234928412/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=5950537015062599287&amp;postID=7233196141234928412' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/7233196141234928412'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5950537015062599287/posts/default/7233196141234928412'/><link rel='alternate' type='text/html' href='http://kennardconsulting.blogspot.com/2008/12/better-than-free-metawidget-v065.html' title='Better Than Free: Metawidget v0.65'/><author><name>Richard</name><uri>http://www.blogger.com/profile/11191015489042575122</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='06360320725558590365'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>1</thr:total></entry></feed>