The discussion about checked/unchecked exceptions is almost as old as Java. While we all have a point in your stance towards this, maybe we are looking at the problem from the wrong angle. Manuel Woelker wrote an article which concentrates on the receiver of the exception, the user, and how exceptions should behave to help the user: Exceptions From a User’s Perspective
Friday, January 30, 2009
Wednesday, November 26, 2008
Navigating SharePoint Folders With Axis2
I've just written some test code to get a list of items in a SharePoint folder with Apache Axis2 and since this was "not so easy", I'll share my insights here.
First, you need Axis2. If you're using Maven2, put this in your pom.xml:
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-kernel</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-adb</artifactId>
<version>1.4.1</version>
</dependency>
Next stop: Setting up NTLM authorization.
import org.apache.axis2.transport.http.HttpTransportProperties;
import org.apache.commons.httpclient.auth.AuthPolicy;
HttpTransportProperties.Authenticator auth = new
HttpTransportProperties.Authenticator();
auth.setUsername ("username");
auth.setPassword "password");
auth.setDomain ("ntdom");
auth.setHost ("host.domain.com");
List authPrefs = new ArrayList (1);
authPrefs.add (AuthPolicy.NTLM);
auth.setAuthSchemes (authPrefs);
This should be the username/password you're using to login to the NT domain "ntdom" on the NT domain server "host.domain.com". Often, this server is the same as the SharePoint server you want to connect to.
If the SharePoint server is somewhere outside your intranet, you may need to specify a proxy:
HttpTransportProperties.ProxyProperties proxyProperties =
new HttpTransportProperties.ProxyProperties();
proxyProperties.setProxyName ("your.proxy.com");
proxyProperties.setProxyPort (8888);
You can get these values from your Internet browser.
If there are several SharePoint "sites" on the server, set site to the relative URL of the site you want to connect to. Otherwise, leave site empty. If you have no idea what I'm talking about, browse the SharePoint server in Internet Explorer. In the location bar, you'll see an URL like this: https://sp.company.com/projects/demo/Documents2/Forms/AllItems.aspx?RootFolder=%2fprojects%2fdemo%2fDocument2%2f&FolderCTID=&View=%7b18698D80%2dE081%2d4BBE%2d96EB%2d73BA839230B9%7d. Scary, huh? Let's take it apart:
https:// = the protocol,
sp.company.com = The server name (with domain),
projects/demo = The "site" name
Documents2 = A "list" stored on the site "projects/demo"
/Forms/AllItems.aspx?RootFolder=... is stuff to make IE happy. Ignore it.
So in out example, we have to set site to:
String site = "/projects/demo";
Mind the leading slash!
To verify that this is correct, replace "/Documents2/Forms/" and anything beyond with "/_vti_bin/Lists.asmx?WSDL". That should return the WSDL definition for this site. Save the result as "sharepoint.wsdl" (File menu, "Save as..."). Install Axis2, open a command prompt in the directory where you saved the WSDL file and run this command (don't forget to replace the Java package name):
%AXIS2_HOME%\bin\WSDL2Java -uri sharepoint.wsdl -p java.package.name -d adb -s
This will create a "src" directory with the Java package and a single file "ListsStub.java". Copy it into your Maven2 project.
Now, we can get a list of the lists on the site:
ListsStub lists = new ListsStub
("https://sp.company.com"+site+"/_vti_bin/Lists.asmx");
lists._getServiceClient ().getOptions ()
.setProperty (HTTPConstants.AUTHENTICATE, auth);
If you need a proxy, specify it here:
options.setProperty (HTTPConstants.HTTP_PROTOCOL_VERSION,
HTTPConstants.HEADER_PROTOCOL_10);
options.setProperty (HTTPConstants.PROXY, proxyProperties);
We need to reduce the HTTP protocol version to 1.0 because most proxies don't allow to send multiple requests over a single connection. If you want to speed things up, you can try to comment out this line but be prepared to see it fail afterwards.
Okay. The plumbing is in place. Now we query the server for the lists it has:
String liste = "Documents2";
String document2ID;
{
ListsStub.GetListCollection req = new ListsStub.GetListCollection();
ListsStub.GetListCollectionResponse res = lists.GetListCollection (req);
displayResult (req, res);
document2ID = getIDByTitle (res, liste);
}
This downloads all lists defined on the server and searches for the one we need. If you're in doubt what the name of the list might be: Check the bread crumbs in the blue part in the intern explorer. The first two items are the title of the site and the list you're currently in.
displayResult() is the usual XML dump code:
private void displayResult (GetListCollection req,
GetListCollectionResponse res)
{
System.out.println ("Result OK: "
+res.localGetListCollectionResultTracker);
OMElement root = res.getGetListCollectionResult ()
.getExtraElement ();
dump (System.out, root, 0);
}
private void dump (PrintStream out, OMElement e, int indent)
{
indent(out, indent);
out.print (e.getLocalName ());
for (Iterator iter = e.getAllAttributes (); iter.hasNext (); )
{
OMAttribute attr = (OMAttribute)iter.next ();
out.print (" ");
out.print (attr.getLocalName ());
out.print ("=\"");
out.print (attr.getAttributeValue ());
out.print ("\"");
}
out.println ();
for (Iterator iter = e.getChildElements (); iter.hasNext (); )
{
OMElement child = (OMElement)iter.next ();
dump (out, child, indent+1);
}
}
private void indent (PrintStream out, int indent)
{
for (int i=0; i<indent; i++)
out.print (" ");
}
We also need getIDByTitle() to search for the ID of a SparePoint list:
private String getIDByTitle (GetListCollectionResponse res, String title)
{
OMElement root = res.getGetListCollectionResult ().getExtraElement ();
QName qnameTitle = new QName ("Title");
QName qnameID = new QName ("ID");
for (Iterator iter = root.getChildrenWithLocalName ("List"); iter.hasNext (); )
{
OMElement list = (OMElement)iter.next ();
if (title.equals (list.getAttributeValue (qnameTitle)))
return list.getAttributeValue (qnameID);
}
return null;
}
With that, we can finally list the items in a folder:
{
String dir = "folder/subfolder";
ListsStub.GetListItems req
= new ListsStub.GetListItems ();
req.setListName (document2ID);
QueryOptions_type1 query
= new QueryOptions_type1 ();
OMFactory fac = OMAbstractFactory.getOMFactory();
OMElement root = fac.createOMElement (
new QName("", "QueryOptions"));
query.setExtraElement (root);
OMElement folder = fac.createOMElement (
new QName("", "Folder"));
root.addChild (folder);
folder.setText (liste+"/"+dir); // <--!!
req.setQueryOptions (query);
GetListItemsResponse res = lists.GetListItems (req);
displayResult (req, res);
}
The important bits here are: To list the items in a folder, you must include the name of the list in the "Folder" element! For reference, this is the XML which actually sent to the server:
<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
<soapenv:Body>
<ns1:GetListItems xmlns:ns1="http://schemas.microsoft.com/sharepoint/soap/">
<ns1:listName>{12AF2346-CCA1-486D-BE3C-82223DEC3F42}</ns1:listName>
<ns1:queryOptions>
<QueryOptions>
<Folder>Documents2/folder/subfolder</Folder>
</QueryOptions>
</ns1:queryOptions>
</ns1:GetListItems>
</soapenv:Body>
</soapenv:Envelope>
If the folder name is not correct, you'll get a list of all files and folders that the SharePoint server can find anywhere. The folder names can be found in the bread crumbs. The first two items are the site and the list name, respectively, followed by the folder names.
The last missing piece is displayResult() for the items:
private void displayResult (GetListItems req,
GetListItemsResponse res)
{
System.out.println ("Result OK: "
+res.localGetListItemsResultTracker);
OMElement root = res.getGetListItemsResult ()
.getExtraElement ();
dump (System.out, root, 0);
}
If you run this code and you see the exception "unable to find valid certification path to requested target", this article will help.
If the SharePoint server returns an error, you'll see "detail unsupported element in SOAPFault element". I haven't found a way to work around this bug in Axis2. Try to set the log level of "org.apache.axis2" to "DEBUG" and you'll see what the SharePoint server sent back (not that it will help in most of the cases ...)
Links: GetListItems on MSDN, How to configure Axis2 to support Basic, NTLM and Proxy authentication?, Java to SharePoint Integration - Part I (old, for Java 1.4)
Good luck!
Wednesday, November 19, 2008
Testing the Impossible: Rules of Thumb
When people say "we can't test that", they usually mean "... with a reasonable effort". They say "we can't test that because it's using a database" or "we can't test the layout of the UI" or "to test this, we need information which is buried in private fields of that class".
And they are always wrong. You can test everything. Usually with a reasonable effort. But often, you need to take a step back and do the unusual. Some examples.
So your app is pumping lots of data into a database. You can't test the database. You'd need to scrap it for every test run and build it from scratch which would take hours or at least ages. Okay. Don't test the database. Test how you use it. You're not looking for bugs in the database, you're looking for bugs in your code. Saying "but some bugs might get away" is just a lame excuse.
Here is what you need to do: Identify independent objects (which need no other objects stored in the database). Write tests for those. Put the test data for them in an in-memory database. HSQLDB and Derby are your friends. If you must, use your production database but make the schema configurable. Scrap the tables before the test and load them from clean template tables.
So you need some really spiffy SQL extensions? Put them in an isolated place and test them without everything else against the real database. You need to test that searching a huge amount of data works? Put that data in a static test database. Switch database connections during the tests. Can't? Make that damn connection provider configurable at runtime! Can't? Sure you can. If everything else fails, get the source with JAD, compile that into an independent jar and force that as the first thing into the classpath when you run your tests. Use a custom classloader if you must.
While this is not perfect, it will allow you to learn how to test. How to test your work. Testing is always different just like every program is different. Allow yourself to make mistakes and to learn from them. Tackle the harder problems after the easier ones. Make the tests help you learn.
So you have this very complex user interface. Which you can't test. Let alone starting the app takes ten minutes and the UI changes all the time and ... Okay. Stop the whining. Your program is running on a computer and for same inputs, a computer should return the same outputs, right? Or did you just build a big random number generator? Something to challenge the Infinite Improbability Drive? No? Then you can test it. Follow me.
First, cut the code that does something from the code that connects said code to the UI. As a first simple step, we'll just assume that pressing a button will actually invoke your method. If this fails for some reason, that reason can't be very hard to find, so we can safely ignore these simple bugs for now.
After this change, you have the code that does stuff at the scruff. Now, you can write tests for it. Reduce entanglement. Keep separate issues separate. A friend of mine builds all his code around a central event service. Service providers register themselves and other parts of the code send events to do stuff. It costs a bit performance but it makes testing as easy as overwriting an existing service provider with a mock up.
Your software needs an insanely complex remote server? How about replacing this with a small proxy that always returns the same answers? Or at least fakes something that looks close enough to a real answer to make your code work (or fail when you're testing the error handling).
And if you need data that some stubborn object won't reveal, use the source, Luke (download the source and edit the offender to make the field public, remove "final" from all files, add a getter or make it protected and extend the class in the tests). If everything else fails, turn to java.lang.reflect.Field.setAccessible(true).
If you're using C/C++, always invoke methods via a trampoline: Put a pointer somewhere which contains the function to call and always use that pointer instead of the real function. Use header files and macros so no human can tell the difference. In your tests, bend those pointers. The Amiga did it in 1985. #ifdef is your friend.
If you're using some other language, put the test code in comments and have a self-written preprocessor create two versions that you can compile and run.
If all else fails, switch to Python.
Tuesday, November 18, 2008
Testing the Impossible: JavaScript in a Web Page
How do you run JUnit tests on JavaScript in a web page? Impossible?
Here is what you need: First, get a copy of Rhino (at least 1.6R7). Then, save a copy of the JavaScript code at the bottom as "env.js". And here is the setup code for the JUnit test:
Context cx;
Global scope;
public void setupContext () throws IllegalAccessException,
InstantiationException, InvocationTargetException
{
cx = Context.enter();
scope = new Global();
scope.init (cx);
addScript(cx, scope, new File ("html/env.js"));
File f = new File ("html/demo.html");
cx.evaluateString(scope,
"window.location = '"+f.toURL()+"';\n" +
"", "<"+getName ()+">", 1, null);
}
public void addScript (Context cx, Scriptable scope, File file) throws IOException
{
Reader in = new FileReader (file);
cx.evaluateReader(scope, in, file.getAbsolutePath(), 1, null);
}
This will load "demo.html" into the browser simulation. The problem here: The loading is asynchronous (just like in a real browser). Now what? We need synchronization:
import org.mozilla.javascript.ScriptableObject;
public class JSJSynchronize extends ScriptableObject
{
public Object data;
public Object lock = new Object ();
public JSJSynchronize()
{
}
@Override
public String getClassName ()
{
return "JSJSynchronize";
}
public Object jsGet_data()
{
synchronized (lock)
{
try
{
lock.wait ();
}
catch (InterruptedException e)
{
throw new RuntimeException ("Should not happen", e);
}
return data;
}
}
public void jsSet_data(Object data)
{
synchronized (lock)
{
this.data = data;
lock.notify ();
}
}
public Object getData()
{
synchronized (lock)
{
try
{
lock.wait ();
}
catch (InterruptedException e)
{
throw new RuntimeException ("Should not happen", e);
}
return data;
}
}
public void setData(Object data)
{
synchronized (lock)
{
this.data = data;
lock.notify ();
}
}
}
With this code and "window.onload", we can wait for the html to load:
JSJSynchronize jsjSynchronize;
ScriptableObject.defineClass(scope, JSJSynchronize.class);
jsjSynchronize = (JSJSynchronize)cx.newObject (scope, "JSJSynchronize");
scope.put("jsjSynchronize", scope, jsjSynchronize);
cx.evaluateString(scope,
"window.location = '"+f.toURL()+"';\n" +
"window.onload = function(){\n" +
" print('Window loaded');\n" +
" jsjSynchronize.data = window;\n" +
"};\n" +
"", "<"+getName ()+">", 1, null);
ScriptableObject window = (ScriptableObject)jsjSynchronize.getData();
System.out.println ("window="+window);
ScriptableObject document = (ScriptableObject)scope.get ("document", scope);
System.out.println ("document="+document);
System.out.println ("document.forms="+document.get ("forms", document));
ScriptableObject navigator = (ScriptableObject)scope.get ("navigator", scope);
System.out.println ("navigator="+navigator);
System.out.println ("navigator.location="+navigator.get ("location", navigator));
// I've been too lazy to parse the HTML for the scripts:
addScript(cx, scope, new File ("src/main/webapp/script/prototype.js"));
Slightly modified version of env.js, original by John Resig (original code):
/*
* Simulated browser environment for Rhino
* By John Resig <http://ejohn.org/>
* Copyright 2007 John Resig, under the MIT License
* http://jqueryjs.googlecode.com/svn/trunk/jquery/build/runtest/
* Revision 5251
*/
// The window Object
var window = this;
// generic enumeration
Function.prototype.forEach = function(object, block, context) {
for (var key in object) {
if (typeof this.prototype[key] == "undefined") {
block.call(context, object[key], key, object);
}
}
};
// globally resolve forEach enumeration
var forEach = function(object, block, context) {
if (object) {
var resolve = Object; // default
if (object instanceof Function) {
// functions have a "length" property
resolve = Function;
} else if (object.forEach instanceof Function) {
// the object implements a custom forEach method so use that
object.forEach(block, context);
return;
} else if (typeof object.length == "number") {
// the object is array-like
resolve = Array;
}
resolve.forEach(object, block, context);
}
};
function collectForms(document) {
var result = document.body.getElementsByTagName('form');
//print('collectForms');
document.forms = result;
for (var i=0; i<result.length; i++) {
var f = result[i];
f.name = f.attributes['name'];
//print('Form '+f.name);
document[f.name] = f;
f.elements = f.getElementsByTagName('input');
for(var j=0; j<f.elements.length; j++) {
var e = f.elements[j];
var attr = e.attributes;
//forEach(attr, print);
e.type = attr['type'];
e.name = attr['name'];
e.className = attr['class'];
f[e.name] = e;
//print(' Input '+e.name);
}
}
}
(function(){
// Browser Navigator
window.navigator = {
get userAgent(){
return "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3";
},
get appVersion(){
return "Mozilla/5.0";
}
};
var curLocation = (new java.io.File("./")).toURL();
window.__defineSetter__("location", function(url){
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onreadystatechange = function(){
curLocation = new java.net.URL( curLocation, url );
window.document = xhr.responseXML;
collectForms(window.document);
var event = document.createEvent();
event.initEvent("load");
window.dispatchEvent( event );
};
xhr.send();
});
window.__defineGetter__("location", function(url){
return {
get protocol(){
return curLocation.getProtocol() + ":";
},
get href(){
return curLocation.toString();
},
toString: function(){
return this.href;
}
};
});
// Timers
var timers = [];
window.setTimeout = function(fn, time){
var num;
return num = setInterval(function(){
fn();
clearInterval(num);
}, time);
};
window.setInterval = function(fn, time){
var num = timers.length;
timers[num] = new java.lang.Thread(new java.lang.Runnable({
run: function(){
while (true){
java.lang.Thread.currentThread().sleep(time);
fn();
}
}
}));
timers[num].start();
return num;
};
window.clearInterval = function(num){
if ( timers[num] ) {
timers[num].stop();
delete timers[num];
}
};
// Window Events
var events = [{}];
window.addEventListener = function(type, fn){
if ( !this.uuid || this == window ) {
this.uuid = events.length;
events[this.uuid] = {};
}
if ( !events[this.uuid][type] )
events[this.uuid][type] = [];
if ( events[this.uuid][type].indexOf( fn ) < 0 )
events[this.uuid][type].push( fn );
};
window.removeEventListener = function(type, fn){
if ( !this.uuid || this == window ) {
this.uuid = events.length;
events[this.uuid] = {};
}
if ( !events[this.uuid][type] )
events[this.uuid][type] = [];
events[this.uuid][type] =
events[this.uuid][type].filter(function(f){
return f != fn;
});
};
window.dispatchEvent = function(event){
if ( event.type ) {
if ( this.uuid && events[this.uuid][event.type] ) {
var self = this;
events[this.uuid][event.type].forEach(function(fn){
fn.call( self, event );
});
}
if ( this["on" + event.type] )
this["on" + event.type].call( self, event );
}
};
// DOM Document
window.DOMDocument = function(file){
this._file = file;
var factory = Packages.javax.xml.parsers.DocumentBuilderFactory.newInstance();
factory.setValidating(false);
this._dom = factory.newDocumentBuilder().parse(file);
if ( !obj_nodes.containsKey( this._dom ) )
obj_nodes.put( this._dom, this );
};
DOMDocument.prototype = {
createTextNode: function(text){
return makeNode( this._dom.createTextNode(
text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")) );
},
createElement: function(name){
return makeNode( this._dom.createElement(name.toLowerCase()) );
},
getElementsByTagName: function(name){
return new DOMNodeList( this._dom.getElementsByTagName(
name.toLowerCase()) );
},
getElementById: function(id){
var elems = this._dom.getElementsByTagName("*");
for ( var i = 0; i < elems.length; i++ ) {
var elem = elems.item(i);
if ( elem.getAttribute("id") == id )
return makeNode(elem);
}
return null;
},
get body(){
return this.getElementsByTagName("body")[0];
},
get documentElement(){
return makeNode( this._dom.getDocumentElement() );
},
get ownerDocument(){
return null;
},
addEventListener: window.addEventListener,
removeEventListener: window.removeEventListener,
dispatchEvent: window.dispatchEvent,
get nodeName() {
return "#document";
},
importNode: function(node, deep){
return makeNode( this._dom.importNode(node._dom, deep) );
},
toString: function(){
return "Document" + (typeof this._file == "string" ?
": " + this._file : "");
},
get innerHTML(){
return this.documentElement.outerHTML;
},
get defaultView(){
return {
getComputedStyle: function(elem){
return {
getPropertyValue: function(prop){
prop = prop.replace(/\-(\w)/g,function(m,c){
return c.toUpperCase();
});
var val = elem.style[prop];
if ( prop == "opacity" && val == "" )
val = "1";
return val;
}
};
}
};
},
createEvent: function(){
return {
type: "",
initEvent: function(type){
this.type = type;
}
};
}
};
function getDocument(node){
return obj_nodes.get(node);
}
// DOM NodeList
window.DOMNodeList = function(list){
this._dom = list;
this.length = list.getLength();
for ( var i = 0; i < this.length; i++ ) {
var node = list.item(i);
this[i] = makeNode( node );
}
};
DOMNodeList.prototype = {
toString: function(){
return "[ " +
Array.prototype.join.call( this, ", " ) + " ]";
},
get outerHTML(){
return Array.prototype.map.call(
this, function(node){return node.outerHTML;}).join('');
}
};
// DOM Node
window.DOMNode = function(node){
this._dom = node;
};
DOMNode.prototype = {
get nodeType(){
return this._dom.getNodeType();
},
get nodeValue(){
return this._dom.getNodeValue();
},
get nodeName() {
return this._dom.getNodeName();
},
cloneNode: function(deep){
return makeNode( this._dom.cloneNode(deep) );
},
get ownerDocument(){
return getDocument( this._dom.ownerDocument );
},
get documentElement(){
return makeNode( this._dom.documentElement );
},
get parentNode() {
return makeNode( this._dom.getParentNode() );
},
get nextSibling() {
return makeNode( this._dom.getNextSibling() );
},
get previousSibling() {
return makeNode( this._dom.getPreviousSibling() );
},
toString: function(){
return '"' + this.nodeValue + '"';
},
get outerHTML(){
return this.nodeValue;
}
};
// DOM Element
window.DOMElement = function(elem){
this._dom = elem;
this.style = {
get opacity(){ return this._opacity; },
set opacity(val){ this._opacity = val + ""; }
};
// Load CSS info
var styles = (this.getAttribute("style") || "").split(/\s*;\s*/);
for ( var i = 0; i < styles.length; i++ ) {
var style = styles[i].split(/\s*:\s*/);
if ( style.length == 2 )
this.style[ style[0] ] = style[1];
}
};
DOMElement.prototype = extend( new DOMNode(), {
get nodeName(){
return this.tagName.toUpperCase();
},
get tagName(){
return this._dom.getTagName();
},
toString: function(){
return "<" + this.tagName + (this.id ? "#" + this.id : "" ) + ">";
},
get outerHTML(){
var ret = "<" + this.tagName, attr = this.attributes;
for ( var i in attr )
ret += " " + i + "='" + attr[i] + "'";
if ( this.childNodes.length || this.nodeName == "SCRIPT" )
ret += ">" + this.childNodes.outerHTML +
"</" + this.tagName + ">";
else
ret += "/>";
return ret;
},
get attributes(){
var attr = {}, attrs = this._dom.getAttributes();
for ( var i = 0; i < attrs.getLength(); i++ )
attr[ attrs.item(i).nodeName ] = attrs.item(i).nodeValue;
return attr;
},
get innerHTML(){
return this.childNodes.outerHTML;
},
set innerHTML(html){
html = html.replace(/<\/?([A-Z]+)/g, function(m){
return m.toLowerCase();
});
var nodes = this.ownerDocument.importNode(
new DOMDocument( new java.io.ByteArrayInputStream(
(new java.lang.String("<wrap>" + html + "</wrap>"))
.getBytes("UTF8"))).documentElement, true).childNodes;
while (this.firstChild)
this.removeChild( this.firstChild );
for ( var i = 0; i < nodes.length; i++ )
this.appendChild( nodes[i] );
},
get textContent(){
return nav(this.childNodes);
function nav(nodes){
var str = "";
for ( var i = 0; i < nodes.length; i++ )
if ( nodes[i].nodeType == 3 )
str += nodes[i].nodeValue;
else if ( nodes[i].nodeType == 1 )
str += nav(nodes[i].childNodes);
return str;
}
},
set textContent(text){
while (this.firstChild)
this.removeChild( this.firstChild );
this.appendChild( this.ownerDocument.createTextNode(text));
},
style: {},
clientHeight: 0,
clientWidth: 0,
offsetHeight: 0,
offsetWidth: 0,
get disabled() {
var val = this.getAttribute("disabled");
return val != "false" && !!val;
},
set disabled(val) { return this.setAttribute("disabled",val); },
get checked() {
var val = this.getAttribute("checked");
return val != "false" && !!val;
},
set checked(val) { return this.setAttribute("checked",val); },
get selected() {
if ( !this._selectDone ) {
this._selectDone = true;
if ( this.nodeName == "OPTION" && !this.parentNode.getAttribute("multiple") ) {
var opt = this.parentNode.getElementsByTagName("option");
if ( this == opt[0] ) {
var select = true;
for ( var i = 1; i < opt.length; i++ )
if ( opt[i].selected ) {
select = false;
break;
}
if ( select )
this.selected = true;
}
}
}
var val = this.getAttribute("selected");
return val != "false" && !!val;
},
set selected(val) { return this.setAttribute("selected",val); },
get className() { return this.getAttribute("class") || ""; },
set className(val) {
if (typeof val != 'string') { val = "" + val; }
return this.setAttribute("class",
val.replace(/(^\s*|\s*$)/g,""));
},
get type() { return this.getAttribute("type") || ""; },
set type(val) { return this.setAttribute("type",val); },
get value() { return this.getAttribute("value") || ""; },
set value(val) { return this.setAttribute("value",val); },
get src() { return this.getAttribute("src") || ""; },
set src(val) { return this.setAttribute("src",val); },
get id() { return this.getAttribute("id") || ""; },
set id(val) { return this.setAttribute("id",val); },
getAttribute: function(name){
return this._dom.hasAttribute(name) ?
new String( this._dom.getAttribute(name) ) :
null;
},
setAttribute: function(name,value){
this._dom.setAttribute(name,value);
},
removeAttribute: function(name){
this._dom.removeAttribute(name);
},
get childNodes(){
return new DOMNodeList( this._dom.getChildNodes() );
},
get firstChild(){
return makeNode( this._dom.getFirstChild() );
},
get lastChild(){
return makeNode( this._dom.getLastChild() );
},
appendChild: function(node){
this._dom.appendChild( node._dom );
},
insertBefore: function(node,before){
this._dom.insertBefore( node._dom, before ? before._dom : before );
},
removeChild: function(node){
this._dom.removeChild( node._dom );
},
getElementsByTagName: DOMDocument.prototype.getElementsByTagName,
addEventListener: window.addEventListener,
removeEventListener: window.removeEventListener,
dispatchEvent: window.dispatchEvent,
click: function(){
var event = document.createEvent();
event.initEvent("click");
this.dispatchEvent(event);
},
submit: function(){
var event = document.createEvent();
event.initEvent("submit");
this.dispatchEvent(event);
},
focus: function(){
var event = document.createEvent();
event.initEvent("focus");
this.dispatchEvent(event);
},
blur: function(){
var event = document.createEvent();
event.initEvent("blur");
this.dispatchEvent(event);
},
get elements(){
return this.getElementsByTagName("*");
},
get contentWindow(){
return this.nodeName == "IFRAME" ? {
document: this.contentDocument
} : null;
},
get contentDocument(){
if ( this.nodeName == "IFRAME" ) {
if ( !this._doc )
this._doc = new DOMDocument(
new java.io.ByteArrayInputStream((new java.lang.String(
"<html><head><title></title></head><body></body></html>"))
.getBytes("UTF8")));
return this._doc;
} else
return null;
}
});
// Helper method for extending one object with another
function extend(a,b) {
for ( var i in b ) {
var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
if ( g || s ) {
if ( g )
a.__defineGetter__(i, g);
if ( s )
a.__defineSetter__(i, s);
} else
a[i] = b[i];
}
return a;
}
// Helper method for generating the right
// DOM objects based upon the type
var obj_nodes = new java.util.HashMap();
function makeNode(node){
if ( node ) {
if ( !obj_nodes.containsKey( node ) )
obj_nodes.put( node, node.getNodeType() ==
Packages.org.w3c.dom.Node.ELEMENT_NODE ?
new DOMElement( node ) : new DOMNode( node ) );
return obj_nodes.get(node);
} else
return null;
}
// XMLHttpRequest
// Originally implemented by Yehuda Katz
window.XMLHttpRequest = function(){
this.headers = {};
this.responseHeaders = {};
};
XMLHttpRequest.prototype = {
open: function(method, url, async, user, password){
this.readyState = 1;
if (async)
this.async = true;
this.method = method || "GET";
this.url = url;
this.onreadystatechange();
},
setRequestHeader: function(header, value){
this.headers[header] = value;
},
getResponseHeader: function(header){ },
send: function(data){
var self = this;
function makeRequest(){
var url = new java.net.URL(curLocation, self.url);
if ( url.getProtocol() == "file" ) {
if ( self.method == "PUT" ) {
var out = new java.io.FileWriter(
new java.io.File( new java.net.URI( url.toString() ) ) ),
text = new java.lang.String( data || "" );
out.write( text, 0, text.length() );
out.flush();
out.close();
} else if ( self.method == "DELETE" ) {
var file = new java.io.File( new java.net.URI( url.toString() ) );
file["delete"]();
} else {
var connection = url.openConnection();
connection.connect();
handleResponse();
}
} else {
var connection = url.openConnection();
connection.setRequestMethod( self.method );
// Add headers to Java connection
for (var header in self.headers)
connection.addRequestProperty(header, self.headers[header]);
connection.connect();
// Stick the response headers into responseHeaders
for (var i = 0; ; i++) {
var headerName = connection.getHeaderFieldKey(i);
var headerValue = connection.getHeaderField(i);
if (!headerName && !headerValue) break;
if (headerName)
self.responseHeaders[headerName] = headerValue;
}
handleResponse();
}
function handleResponse(){
self.readyState = 4;
self.status = parseInt(connection.responseCode) || undefined;
self.statusText = connection.responseMessage || "";
var stream = new java.io.InputStreamReader(connection.getInputStream()),
buffer = new java.io.BufferedReader(stream), line;
while ((line = buffer.readLine()) != null)
self.responseText += line;
self.responseXML = null;
if ( self.responseText.match(/^\s*</) ) {
//try {
self.responseXML = new DOMDocument(
new java.io.ByteArrayInputStream(
(new java.lang.String(
self.responseText)).getBytes("UTF8")));
//} catch(e) {
//}
}
}
self.onreadystatechange();
}
if (this.async)
(new java.lang.Thread(new java.lang.Runnable({
run: makeRequest
}))).start();
else
makeRequest();
},
abort: function(){},
onreadystatechange: function(){},
getResponseHeader: function(header){
if (this.readyState < 3)
throw new Error("INVALID_STATE_ERR");
else {
var returnedHeaders = [];
for (var rHeader in this.responseHeaders) {
if (rHeader.match(new Regexp(header, "i")))
returnedHeaders.push(this.responseHeaders[rHeader]);
}
if (returnedHeaders.length)
return returnedHeaders.join(", ");
}
return null;
},
getAllResponseHeaders: function(header){
if (this.readyState < 3)
throw new Error("INVALID_STATE_ERR");
else {
var returnedHeaders = [];
for (var header in this.responseHeaders)
returnedHeaders.push( header + ": " + this.responseHeaders[header] );
return returnedHeaders.join("\r\n");
}
},
async: true,
readyState: 0,
responseText: "",
status: 0
};
})();
Tuesday, November 11, 2008
Testing the Impossible: User Dialogs
How do you test a user dialog like "Do you really want to quit?"
This code usually looks like this:
public void quit () {
if (!MessageDialog.ask (getShell(),
"Really quit?",
"Do you really want to quit?"
))
return;
... quit ...
}
The solution is simple:
public void quit () {
if (askToQuit ())
return;
... quit ...
}
/** For tests */
protected boolean askToQuit () {
... ask your question here ...
}
In test cases, you can now extend the class, and override askToQuit:
public boolean askToQuitWasCalled = false;
public boolean askToQuitResult = true;
protected boolean askToQuit () {
askToQuitWasCalled = true;
return askToQuitResult;
}
Now, you can find out if the question would be asked and you can verify that the code behaves correctly depending on the answer. Tests that just want to quit won't need to do anything special to get the desired behavior.
The same applies to more complex dialogs: Refactor them to put their data into an intermediate structure which you can mock during the tests. That means to copy the data if the dialog is a black box but that's a small price to be paid for being able to test modal user dialogs.
Lesson: You don't want to test the dialog, you want to test whether it is opened at the right place, under the right circumstances and if the result is processes correctly.
Sunday, August 31, 2008
Wednesday, August 27, 2008
Text Editor Component and JADS
While working on DecentXML (1.2 due this weekend), I've had those other two things that were bugging me. One is that there is no high-quality, open-source framework with algorithms and data structures. I'm not talking about java.lang.Collections, I'm talking about red-black trees, interval trees, gap buffers, things like that. Powerful data structures you need to build complex software.
Welcome the "Java Algorithm and Data Structure" project - jads. I haven't started opened a project page on SourceForge or Google Code, yet, but I'll probably do that this weekend.
Based on that, I'm working on a versatile text editor component for Java software. The final editor will work with user interfaces implemented in Swing, SWT and Qt. It's an extensible framework where you can easily replace parts with your own code to get the special features you need. I currently have a demo running which can display text, which allows scrolling and where you can do some basic editing. Nothing fancy but it's coming alone nicely.
If you want to hear more about these projects, post a comment or drop me a mail.
Monday, August 04, 2008
Four harmful Java idioms, and how NOT to fix them
In his article "Four harmful Java idioms, and how to fix them", John O'Hanley writes about how to make Java more maintainable. He picks four common patterns and gives tips how to fix them ... or not. Follow me.
Names
The first idea is to prefix names with a letter giving a hint what they mean: Is "start" a method? A field? A parameter? The goal is to make the code more readable to humans.
Unfortunately, this doesn't work. The human brain doesn't read letters, it reads words. So "fStart" (meaning a field with the name "start") is rejected by the brain because it's not a word. This triggers the conscious analysis which John tries to avoid! Which is why modern IDEs use color to tell you what something is: The brain can decode color and words in independent parts - unconsciously.
Packaging Convention
Next, he moves on how to split code into packages. Currently, we use a "package by layer" scheme, meaning all DB code goes into one package and the model code into another and UI layer in a third, etc. He proposes to use a "by-feature" packaging with the litmus test "you should be able to delete a feature by deleting a single directory, without leaving behind any cruft".
Uhm. When have you ever written any code where you could remove a feature just by deleting a class? This sounds nice and simple but it's fails Einstein's litmus test: "Make it as simple as possible but not more simple". Even if you have a plug-in based software like Eclipse, this doesn't work because there are still references outside (otherwise, your plug-in wouldn't be able to do anything).
Also, to keep a feature as isolated from everything else as possible (which is a good thing), you need to copy a lot of code into the feature which would otherwise reside elsewhere, neatly packed up in its own package. Really just a limitation of Java where you can't tell the compiler to generate boiler plate code for you. Still, you need to cut code in such a way that it reduces dependencies, not increases them. Therefore, a general rule won't cut (or maybe it will cut: you).
Immutables
John quotes: "'Classes should be immutable unless there is a very good reason for making them mutable,' says Bloch.". And later: "From a practical perspective, many widely-used frameworks require application programmers to use JavaBeans (or something similar) to model database records. This is deeply unfortunate, because it doesn't allow programmers to take advantage of the many positive qualities of immutable objects."
From a practical perspective, immutable objects are dead weight. Applications are all about changing something. I read data from the database, I modify it, I write it back. I rarely read, display and forget about something. Yes, immutables have advantages because they can be shared between threads but that's their only advantage.
Just think about this: You must modify data from the database. So you read the data into an immutable. How to modify it, now? Obviously, you need a method to change it. If you prefer setters, the "setter" must return a copy. So you need to copy the object for every single change. If you want to get a feeling for that, try to do math with BigDecimal. Okay, after the copy you can write the copy back to the database. Question: How do you notify everyone else who might have a (now stale) copy of the old immutable? There are no listeners; immutables can't have listeners. Duh. Driving this to the extreme, lists wouldn't offer methods to add or remove items; or rather they would return new copies of themselves after every add/remove operation.
Sorry, no sale. I can't add money to my cash register. It's immutable.
And a colleague just introduced me to another great concept: Constructors which require values for all fields. The class in question has 95 fields. This idea has the following flaws: a) No matter how big your screen, you can't fit the call onto it. b) After argument #10, you lost track and you can't see anymore which value goes into which argument. Now imagine you have to remove a field. How do you find the right one in this mega-call?
No, nothing beats the no-arg constructor plus a list of setters, all costs considered.
Private members
John proposes to move private members to the end of the class. Here, I agree. I'd even put them close to the getter and setter so that a lot of stuff that belongs together is together.
In todays IDEs with their superb code navigation (I can't really believe there was a time before the F3 key), this doesn't matter much, though.
Conclusion: Think about it, but don't bother.
Friday, August 01, 2008
Wednesday, July 30, 2008
Update to DecentXML
I've updated my XML parser. The tests now cover 97.7% of the code (well, actually 100% of the code which can be executed; there are a couple of exceptions which will never be thrown but I still have to handle them) and there are classes to read XML from InputStream and Reader sources (including encoding detection).
The XMLInputStreamReader class can be used standalone, if you ever want to read an XML file with the correct encoding.
You can download the sources and report issues in the new Google Code project I've created.
Tuesday, July 29, 2008
A Decent XML Parser
Since there isn't one, I've started writing one myself. Main features:
- Allows 100% round-tripping, even for weird whitespace between attributes in elements
- Suitable for building editors and filters which want to preserve the original file layout
- Error messages have line and column information
- Easy to reuse
- XML 1.0 compatible
You can download the latest sources here as a Maven 2 project.
Monday, July 28, 2008
DSLs: Introducing Slang
Did you ever ask for a more compact way to express something in your favorite programming language? Say hello to DSL (Domain Specific Language). A DSL is a slang, a compact way to say what you want. When two astronauts talk, they use slang. They need to get information across and presto. "Over" instead of "I'll now clear the frequency so you can start talking." And when these guys do it, there's no reason for us not to.
Here is an artical on Java World which gives some nice examples how to create a slang in Java and in Groovy. Pizza-lovers of the world, eat your heart out.
Monday, July 21, 2008
Toying With Swing
I've been toying with Java Swing (the UI which comes with Java in case you're wondering) a bit lately to determine which UI to use for my ePen project. I'll post a longer article about my findings in the next few days but for now, just a few links I've collected:
From LegHumped:
- JTextField with background image (as in the location field in your browser)
- Word-wrap for JLabels
- Close a JFrame with Escape
- Utility methods for tree nodes
Then, there is the JFC Swing FAQ and of course the Java Swing Trail by Sun.
I've been looking for a good source on editor components. Swing Hacks looked promising but it seems to only scratch the surface like the rest.
Friday, July 11, 2008
Fastest Way to Collecting Objects in a String
The fastest way to collect a list of objects in a String in Java:
StringBuilder buffer = new StringBuilder ();
String delim = "";
for (Object o: list)
{
buffer.append (delim);
delim = ", "; // Avoid if(); assignment is very fast!
buffer.append (o);
}
buffer.toString ();
Tuesday, July 08, 2008
MQSeries 2045: MQRC_OPTION_NOT_VALID_FOR_TYPE
While doing some work with MQSeries, I got an error "MQJE001: Completion Code 2, Reason 2045" in MQQueueManager.accessQueue() which translates to "MQRC_OPTION_NOT_VALID_FOR_TYPE". Hm. Hey, IBM, how about adding real error messages to your products instead of having people look up odd codes in tables?
Anyway, the error means that I'm trying to open a queue for output which doesn't support this. For example, remote queues can't be opened with the option MQC.MQOO_OUTPUT. Other queues don't allow to read from them, i.e. you have to get rid of MQC.MQOO_INPUT_AS_Q_DEF in the openOptions.
Thursday, June 26, 2008
Jazoon: The Closures Controversy
If you ever wanted to know if it is possible to go through 60 information-packed slides in 50 minutes: Yes, it is. I've been there and Joshua pulled it off in from of about 100 people, so it's not a delusion of mine, either.
In his talk, Joshua presented a host of reasons why the BGGA proposal is a really bad idea. The key information here is "BGGA". We all want closures and Joshua is no exception, it's just that the BGGA proposal is like Generics on steroids and if you can't wrap your brain about Generics wildcards, then you won't understand BGGA closures as well.
The code in the proposal doesn't look too bad at first glance:
{int x, int y => x+y}
Unless you try to use an array. If you did "int[] x", you'd get an error because closures are based on generics and generics can't handle arrays or primitives.
But the next example is better to understand why BGGA will add a new level of hell to Java which will be worse than generics:
{int,String=>Number throws IOException} xyzzy;
is translated into
interface Function1{ // system-generated
R invoke(int x1, A2 x2) throws E;
}
Function1 xyzzy;
Doesn't bother you because you're never going to see this? Well, think again because if you try something that the generics type system doesn't understand, you'll get a generics error message like this one:
NewtonWithClosures.java:26: invoke(capture#418 of ? super {double => double}) in {capture#418 of ? super {double => double} => capture#928 of ? extends {double => double}} cannot be applied to (double)
The reason for this is that the closures are implemented using generics and without any high level support in the compiler so the compiler can't generate a more useful error message. And this was a simple example. We all know how quickly generics get message and for me, that means that any implementation of any new feature that is based on generics is a threat to the future of Java.
Again, I'm for closures and I use them as often as I can in Groovy but the current proposal is just a new way to make Java harder to use. Why don't they simply change the compiler to allow to access field and method objects (from java.lang.reflect) via the class? Like so:
collect(list, Math.class.min)
If we could use Method objects like first class citizens (instead of via the horrible reflection API with it's horde of checked exceptions), we could use Method objects as simple closures. Then, all we would need is a set of util classes for single mutable primitives and we're done. Sure, the syntax wouldn't be as compact but every normal Java developer would be able to understand the concept and how it's supposed to be applied in a few hours.
If you care, here is a link to the whole set of arguments by Joshua Bloch. Read it, keeping in mind that Joshua is pro closure, he just wants to avoid a second generics debacle.
Jazzon: Weblog in 15 minutes II
I wish I could link that presentation because it was the best I've seen in the whole show, both visually and how it was presented. So here is the link to the abstract. There is something to be learned by this talk for anyone who wants to demo a software: One human being can't talk and use a computer at the same time. While David explained things, Bertrand wrote the code. Thumbs up!
And in this case, the visual candy didn't distract from the fact that these guys were really showing off something that ought to have an impact. They showed agile web development with Apache Sling.
A sling gives you range and power and Apache Sling does just that. With just a few pieces of JavaScript added to an existing static HTML web site, they built a blog in roughly 10 minutes. Impressive. I've got to try this out myself, probably this weekend.
Jazoon: Thursday Keynote
Thursday's keynote was "Effective Java Reloaded" by Joshua Bloch where he presented a few key points from his new book.
The examples showed how to replace bit fields (or rather int constants which are used a bit fields) with EnumSet and EnumMap, and how to do lazy initialization for various cases effectively and correctly. If you're a Java programmer who does more than "Hello World", this book is a must have because it explains not only how to do things but how to do things elegantly and why.