Solutions

January 14th, 2009

It occurs to me that when there are problems that we can do 3 different things to address those problems.

  1. Fix the problem Fixing the problem involves changing the problem so that it does not exist any more. In the case of a flat tire, if you remove the tire and put on a new tire then the problem is solved. You have fixed it. Another example if a bug in software. The developer will write a unit test to identify the bug, then change some code and then test to make sure that he has fixed the bug. Hopefully the bug is no longer there.
  2. Hide the problem This happens when you cannot fix the problem so you fix it by fixing the symptoms. The easiest example of this is the common cold. You cannot fix (cure) a cold, but you can help with the symptoms. So when you get a cold you buy some drugs to get rid of the cough and the stuffy nose, you “soldier on” to work looking completely ok, and spread your germs around. There is many examples of this in the software industry, and I am at risk of “geeking” out on you. So the example that is more relevant to me is when I notice the words on my screen getting fuzzy because I should put on my glasses, I just increase the font size. Soon I will have to get a bigger monitor (again).
  3. Change your perspective This situation is employed the most by politicians. They know they cannot fix the problems or even address the symptoms, all they can do is try to make you feel better. “Yes we know the fuel is expensive, but lucky you are not living in the next town/state/country, they are paying twice as much for their fuel”. Suddenly we all feel better about our fuel and nothing has changed. Another example “Person 1: I hate the way that XP crashes sometimes. Person 2: You are lucky you are not using Vista”

So these are the three ways to address things in your life/work that are not working the way you like. Try to fix them, try to hide them and if all else fails change the way you look at them.

Automatic Wrapper Generator for Java

January 8th, 2009

Writing Java programs these days is impossible without using 3rd party APIs. 3rd party APIs is you include a useful JAR that someone else created into your program. If you are lucky the API is consistant, and documented. Some common 3rd party tools are Log4J. JUnit, Hibernate, Spring, Wicket the list goes on and on.

It is considered good coding practice to create a delegation layer between your code and the 3rd party libraries. This is especially true of libraries that are still bleeding edge.

The delegation layer allows many advantages:

  • Allows us to swap out one 3rd party library for another without influencing the code.
  • Include additional validation at the interface layer
  • Fix/Allow for bugs in the 3rd party API

The downside is the time it takes coding for these. IMHO there is no substitute for automatic code generation. Unfortunately, every 3rd party API is different so code generators tend to be specific. With no generic code generators available on the internet (that I could find), I went and wrote my own (specific for this project).

In a recent project I wanted to create this delegation layer for the web services for Rally (www.rallydev.com). Rally have been producing different version of their software, and it plays havoc trying to refactor the code. After the first change in package name I decided to create the following code.

The Rally web services has a good hierarchical class design, and I had to create code that would mimic that structure and be the easiest to generate. It also was array based rather than collections, so I did not have to search for Collection elements.

The following code is not pretty, but it did the job, it is not pretty. Please feel free to extract the useful elements

package ...;
import ...;
public class GenerateWrappersMain {
    private static final String NL = System.getProperty ( "line.separator" );
	
    private static Properties context = new Properties ();
	
    private static final Logger log = Logger.getLogger ( GenerateWrappersMain.class.getSimpleName () );
	
    private static Writer outFile;
	
    /**
     * @param args
     * @throws Exception
     */
    public static void main ( String [] args ) throws Exception {
	
        context.put ( "DESTINATION", ".\\src\\main\\java" );
        context.put ( "OVERWRITE", true );
	
        Collection<String> exclusionMethods = new ArrayList<String> ();
        exclusionMethods.add ( "equals" );
        exclusionMethods.add ( "hashCode" );
	
        String packageName = "com.rallydev.webservice.domain";
        Collection<String> delegateClassList = getListOfClassesForThisProject ( packageName.replaceAll ( "\\.", "\\\\" ) );
        // One Off - delegateClassList.add (
        // "com.rallydev.webservice.service.RallyService" );
	
        String delegatePackage = "com.mycompany.wrapper.rally.webservices";
	
        for ( String className : delegateClassList ) {
            Class<?> clazz = null;
            try {
                clazz = Class.forName ( className );
            }
            catch ( ClassNotFoundException e ) {
                log.debug ( "Class not found: " + className );
                continue;
            }
            outFile = setUpDirAndFileForClass ( delegatePackage + "." + clazz.getSimpleName () );
	
            println ( "package " + delegatePackage + ";" );
	
            print ( "public class " + clazz.getSimpleName () );
            Class<?> superClass = clazz.getSuperclass ();
            boolean hasSuperClass = ( superClass != null && !"java.lang.Object".equals ( superClass.getName () ) );
            boolean superClassIsDelegate = false;
            String superClassName = "";
            if ( hasSuperClass ) {
                superClassIsDelegate = delegateClassList.contains ( superClass.getName () );
                superClassName = ( superClassIsDelegate ) ? superClass.getSimpleName () : superClass.getName ();
                print ( " extends " + superClassName );
            }
            println ( " {" );
	
            println ( "    private " + clazz.getName () + " d = null;" );
            println ( "    public " + clazz.getSimpleName () + " ( Object d ) {" );
            if ( hasSuperClass && superClassIsDelegate ) {
                println ( "        super ( d );" );
            }
            println ( "        if ( d instanceof " + clazz.getName () + " ) {" );
            println ( "            this.d = (" + clazz.getName () + ")d;" );
            println ( "        }" );
            println ( "        else {" );
            println ( "            throw new ClassCastException ( \"Attempt to cast \" + d.getClass().getName() + \" to " + clazz.getName () + "\");" );
            println ( "        }" );
            println ( "    }" );
            println ( "    public " + clazz.getName () + " getDelegate ( ) {" );
            println ( "        return this.d;" );
            println ( "    }" );
            println ( "    public static " + clazz.getSimpleName () + " [] toArray ( " + clazz.getName () + "[] arg ) {" );
            println ( "        " + clazz.getSimpleName () + " [] ret = new " + clazz.getSimpleName () + " [arg.length];" );
            println ( "        for ( int i=0; i<arg.length; i++ ) {" );
            println ( "            try {" );
            println ( "                Class<?> c = Class.forName ( \"" + delegatePackage + ".\" + arg[i].getClass().getSimpleName() );" );
            println ( "                java.lang.reflect.Constructor<?> con = c.getConstructor ( Object.class );" );
            println ( "                ret[i] = (" + clazz.getSimpleName () + ")con.newInstance ( arg[i] );" );
            println ( "            }" );
            println ( "            catch ( Exception e ) {" );
            println ( "                ret[i] = new " + clazz.getSimpleName () + " ( arg[i] );" );
            println ( "            }" );
            println ( "        }" );
            println ( "        return ret;" );
            println ( "    }" );
            println ( "    public static " + clazz.getName () + " [] fromArray ( " + clazz.getSimpleName () + "[] arg ) {" );
            println ( "        " + clazz.getName () + " [] ret = new " + clazz.getName () + " [arg.length];" );
            println ( "        for ( int i=0; i<arg.length; i++ ) {" );
            println ( "            ret[i] = arg[i].getDelegate ();" );
            println ( "        }" );
            println ( "        return ret;" );
            println ( "    }" );
	
            Method [] methods = clazz.getDeclaredMethods ();
            for ( Method m : methods ) {
                int modifiers = m.getModifiers ();
                if ( Modifier.isStatic ( modifiers ) ) continue;
                if ( !Modifier.isPublic ( modifiers ) ) continue;
                if ( exclusionMethods.contains ( m.getName () ) ) continue;
	
                print ( "    public " );
                Class<?> returnTypeClass = m.getReturnType ();
                boolean isThereReturnType = returnTypeClass != Void.TYPE;
                boolean isReturnTypeAnArray = returnTypeClass.isArray ();
                if ( isReturnTypeAnArray ) returnTypeClass = returnTypeClass.getComponentType ();
                boolean isReturnTypeInDelegateList = delegateClassList.contains ( returnTypeClass.getName () );
	
                if ( isReturnTypeInDelegateList ) {
                    print ( returnTypeClass.getSimpleName () );
                }
                else {
                    print ( returnTypeClass.getName () );
                }
                if ( isReturnTypeAnArray ) print ( " []" );
	
                print ( " " + m.getName () + " ( " );
	
                Class<?> [] paramTypes = m.getParameterTypes ();
                int argCnt = 0;
                for ( Class<?> c : paramTypes ) {
	
                    boolean isParamTypeAnArray = c.isArray ();
                    if ( isParamTypeAnArray ) c = c.getComponentType ();
                    boolean isParamDelegate = delegateClassList.contains ( c.getName () );
	
                    if ( argCnt > 0 ) print ( ", " );
                    print ( ( isParamDelegate ) ? c.getSimpleName () : c.getName () );
                    if ( isParamTypeAnArray ) print ( " []" );
                    print ( " arg" + argCnt++ );
                }
                print ( " )" );
                argCnt = 0;
                for ( Class<?> ex : m.getExceptionTypes () ) {
                    print ( ( argCnt == 0 ) ? " throws " : ", " );
                    print ( ex.getName () );
                    argCnt++;
                }
                println ( " { " );
	
                String indent = "        ";
                if ( isThereReturnType ) {
                    print ( indent + "return " );
                    indent = "";
                    if ( isReturnTypeInDelegateList ) {
                        if ( isReturnTypeAnArray ) {
                            print ( returnTypeClass.getSimpleName () + ".toArray ( " );
                        }
                        else {
                            print ( "new " + returnTypeClass.getSimpleName () + " ( " );
                        }
                    }
                }
                print ( indent + "d." + m.getName () + " ( " );
                argCnt = 0;
                for ( Class<?> c : paramTypes ) {
	
                    boolean isParamTypeAnArray = c.isArray ();
                    if ( isParamTypeAnArray ) c = c.getComponentType ();
                    boolean isParamDelegate = delegateClassList.contains ( c.getName () );
	
                    if ( argCnt > 0 ) print ( ", " );
                    if ( isParamDelegate ) {
                        if ( isParamTypeAnArray ) {
                            print ( c.getSimpleName () + ".fromArray ( arg" + argCnt + " )" );
                        }
                        else {
                            print ( "arg" + argCnt + ".getDelegate ()" );
                        }
                    }
                    else {
                        print ( "arg" + argCnt );
                    }
                    argCnt++;
                }
                print ( " )" );
                if ( isThereReturnType && isReturnTypeInDelegateList ) {
                    print ( " )" );
                }
                println ( ";" );
                println ( "    }" );
            }
            println ( " }" );
            outFile.close ();
        }
    }
	
    private static void print ( String s ) throws IOException {
        outFile.write ( s );
        System.out.print ( s );
    }
	
    private static void println ( String s ) throws IOException {
        print ( s );
        print ( NL );
    }
	
    private static String location = ".\\target\\classes\\";
	
    private static final String DOT_CLASS = ".class";
	
    private static Collection<String> getListOfClassesForThisProject ( final String packageName ) {
        Collection<String> listOfClasses = new ArrayList<String> ();
	
        Collection<File> files = listFiles ( new File ( location ), new FilenameFilter () {
            public boolean accept ( File dir, String name ) {
                String dirName = dir.getPath ();
                return ( dirName.endsWith ( packageName ) && name.indexOf ( '$' ) == -1 && !name.endsWith ( "Test.class" )
                        && !name.endsWith ( "AllTests.class" ) && name.endsWith ( DOT_CLASS ) );
            }
        }, true );
        for ( File f : files ) {
            String className = f.getPath ();
            log.trace ( "ClassName: " + className );
            listOfClasses.add ( className.substring ( location.length (), className.length () - 6 ).replaceAll ( "\\\\", "." ) );
        }
        return listOfClasses;
    }
	
    private static Collection<File> listFiles ( File directory, FilenameFilter filter, boolean recurse ) {
        Collection<File> files = new ArrayList<File> ();
        File [] entries = directory.listFiles ();
        if ( entries != null ) {
            for ( File entry : entries ) {
                if ( filter == null || filter.accept ( directory, entry.getName () ) ) {
                    files.add ( entry );
                }
                if ( recurse && entry.isDirectory () ) {
                    files.addAll ( listFiles ( entry, filter, recurse ) );
                }
            }
        }
        return files;
    }
	
    private static Writer setUpDirAndFileForClass ( String clazz ) throws IOException {
        String [] clazzComponents = clazz.split ( "\\." );
        StringBuilder fileNameBuilder = new StringBuilder ( (String)context.get ( "DESTINATION" ) );
        List<String> directoriesToSetup = new ArrayList<String> ();
	
        directoriesToSetup.add ( fileNameBuilder.toString () );
	
        for ( int i = 0; i < clazzComponents.length; i++ ) {
            fileNameBuilder.append ( "\\" ).append ( clazzComponents[i] );
            if ( i < clazzComponents.length - 1 ) {
                directoriesToSetup.add ( fileNameBuilder.toString () );
            }
            else {
                fileNameBuilder.append ( ".java" );
            }
        }
        String fileName = fileNameBuilder.toString ();
        if ( !(Boolean)context.get ( "OVERWRITE" ) && new File ( fileName ).exists () ) {
            log.info ( "Overwrite flag is false and file (" + fileName + ") exists. skipping..." );
            return ( null );
        }
	
        for ( String d : directoriesToSetup )
            checkAndCreateDir ( d );
        return ( new FileWriter ( fileName ) );
    }
	
    private static void checkAndCreateDir ( String dirName ) {
        File dir = new File ( dirName );
        if ( !dir.exists () ) dir.mkdir ();
    }
	
}
	

Using HashMap as Model in Wicket

January 7th, 2009

In Apache Wicket once you understand the Model structure, almost everything else is easy (Except for finding the documentation).

The PropertyModel and CompoundPropertyModel do not really cut it when it comes to handling a model that is a HashMap. I wrote a MapModel which takes care of this problem.

package ...;
import java.util.Map;
import org.apache.wicket.model.IModel;
public class MapModel<K, V> implements IModel<V> {
    private static final long serialVersionUID = 1L;
    private Map<K, V> map;
    private K key;
    public MapModel ( Map<K, V> map, K key ) {
        this.map = map;
        this.key = key;
    }
    @Override
    public V getObject () {
        return map.get ( key );
    }
    @Override
    public void setObject ( V arg0 ) {
        map.put ( key, arg0 );
    }
    @Override
    public void detach () {
        // nothing to do
    }
}

In this example ConfigSettings extends HashMap

...
        ConfigSettings configSettings = getDevToolsSession ().getConfigSettings ();
        RepeatingView rv = new RepeatingView ( "row" );
        add ( rv );
        for ( String key : configSettings.keySet () ) {
            WebMarkupContainer parent = new WebMarkupContainer ( rv.newChildId () );
            rv.add ( parent );
            parent.add ( new Label ( "name", key ) );
            parent.add ( new AjaxEditableLabel<String> ( "value", new MapModel<String,String> ( configSettings, key ) ) );
        }
...

Just for completeness the html for the above

...
<wicket:extend>
    <table>
        <tr wicket:id="row">
            <td wicket:id="name">Name</td><td wicket:id="value">value</td>
        </tr>
    </table>
</wicket:extend>
	
...

Perceptions in the Software Industry - Some Rambling from my contractor days

February 26th, 2004

Even though the software industry is compared to other service industries, it remains unique. It is unique in its scale, costs and problems.

As a software developer I am often faced with the task of producing a quote for writing and implementation of a software product. This usually takes the form of a web site that is specialized in doing a very unique and singular task. When I offer a quote in the order of $5000 for this site I am faced with expressions of horror. You would think that I am asking for their first born.

The client then tells me that I am overcharging, and that he will get (word “buy” deliberately omitted) a copy of Microsoft FrontPage or any one of a dozen HTML editors and then create it for himself. After a couple of months of letting him try I usually find a web site that does not do what he asked, or no web site (nothing at all), or someone else has done the job. I have learned that these sorts of clients are usually too embarrassed to come back to me, or they end up settling for a less costly solution delivered by the “Hack-a-Web” company.

What should I do? Lower my prices. I have tried this, but it has become increasingly difficult to convince my landlord that they should cut my rent just because I am having trouble getting paid for my software development. Maybe I should ignore the customer’s requirements and tell him what he is going to get. This attitude is what gave the computer industry the stigma on the 70’s and 80’s as a bunch of Nerds telling us (badly) how we should run our business.

These days when I deliver a quote I am squinting, ready for the barrage of complaints about how their entire desktop computer and software together costs less than my software development fees. This is often followed by something derogatory about programmers in general. I used to start explaining the differences between individual software development and shrink-wrap software, noting that if they could have bought shrink-wrap I would have suggested it. I found this to be a waste of time and effort.

More recently I have started itemizing my quotations and breaking them up into programming effort, QA effort, Documentation and Training. This has worked very well since the clients figure that they can save themselves over half the costs if they develop their own documentation and do their own testing. I hear the collective nodding from half the audience. From the other half I am hearing “Danger Will Robinson – Danger!!” This is dangerous, so I must also tell you that I always get a signature that the client is going against my better judgment. These sorts of jobs usually lead to more work anyway (he says with a sly grin).

So are programmers getting worse at QA or is the industry not paying for the QA? You just need to look at the NASA budget and ask yourself, “is industry prepared to pay for software quality or are they going to accept bugs that you can fix later”?

So what is the problem with paying for computer software? I know many people who go to grocery shops and when they take products from the shelves, they expect to pay for those products before they leave the shop. I know the same people are happy to have the computer software written for them, use it, copy it, give it to their friends, make some money on it, copy it again, charge their other friends for a copy, and then completely resent the fact that they are being charged for it.

Is the answer is in tangibility? You don’t mind paying for a car because the car exists in a physical form. That sounds fair, but what about a loaf of bread? That is only tangible for a little while? But then a loaf of bread is much cheaper than a car. Maybe tangibility is the key to charging?

When a software designer successfully creates his program you don’t notice it. He creates a masterpiece of software so that you never even know of its complexity. The software designer succeeds at his job when you do not even notice that your job is being done by software. So there is almost zero tangibility here. Is there a global belief that since I cannot see the software it is not worth anything? Maybe this is the key to the success of Microsoft. More support problems that exists with the software the more it is worth. Can we form a relationship, the more the software costs, the more problems that people will have and therefore raise the visibility and therefore its perceived value? Let’s quietly consider some of the software giants that come with a big price tag. The public has allowed those companies to charge what they like because of the (nightmare) support issues that come with running the software. (cough oracle cough) I am sure that some names come to mind.

So let’s assume that there are some good programmers in the world that are building software to make our life easier. Effectively; the less tangible/visible the software, the more successful the programmer. Then it follows that the more successful the programmer, the poorer the programmer. Hmmm, this does not work, to be good at my job I have to introduce more problems into the software. Is this some sort of conspiracy that I have stumbled on, or is it just the perception issues of the general public?

Hmmm, perception issues… When we ask a contractor come into our house and remodel our bathroom, he will give a price for the completed job. He then goes to work and every day make lots of noise and dust and then comes out of the working space filthy and almost beaten by the pesky bathroom. We praise the contractor to our friends and say “what a good job he is doing”. If he came to work each day in a suit, made no noise or mess and did not get dirty we would be suspicious that he is not working. There is not much opportunity to get dirty or be seen to be working hard when you write software.

With the advent of reality shows we are seeing the “bad contractor” that is taking shortcuts on work. They are producing crummy results and making family life a living hell. These contractors are trying to give the insisting client a “better deal” because apparently Contractor B offered it at that price. The contractor is in a bidding war against a fictitious character that forces him to lower prices. Soon society faces the concept that you cannot do the job that the market is expecting to pay. The Good contractor from the Reality show then comes to the rescue and installs the Best of the best, with highly skilled employees, state of the art equipment and materials that come gold plated. This Good Contractor is now doing TV shows because he is unable to get a job in a market that is so tight and cannot afford him.

So spare a thought for your programmers. They do their job well so that you do not see their work, but are eased through your life by their programming subroutines they have placed in the items that surround you. And give a thought to how these people afford to eat and live.