Sunday, December 30, 2012

A Issue with Using db4o in Scala Programs

In the other article I showed that we can use db4o in Scala programs. I also mentioned that there are some limitations: db4o cannot correctly store Scala objects with fields of the type scala.collection.mutable.Map and scala.collection.mutable.Set. This article discuss this issue in detail.

The Scala class Programmer (Listing-1) has a field named skillMap whose type is scalca.collection.mutable.Map. The map is used to record the programmer's skills and respective skill levels.

Listing-1 

package tutorial.db4o

class Programmer (val name: String, var phone: String) {
    private val skillMap = scala.collection.mutable.Map[String, Int]()
    
    def skills = skillMap.keySet
    
    def hasSkill(skill : String) = skills.contains(skill)
    
    def skillLevel(skill: String) : Option[Int] = {
        if (hasSkill(skill)) Some(skillMap(skill))
        else None
    }
    
    def addSkill(skill: String, level: Int) {skillMap(skill) = level}
}

In the program in Listing-2, we create an instance of the Programmer class and store it in the db4o database. In the program in Listin-3, we retrieve the object stored in db4o, and print out the programer's name, phone number, skills and corresponding levels. However, the execution runs into an exception.

Listing-2 - A Program to store an object into db4o

package tutorial.db4o

import com.db4o.Db4oEmbedded
import Adapter._

object AddProgrammers extends App {
    // a connection to the tutorial (db4o) database
    val connection = Db4oEmbedded.openFile("tutorial.db4o")
    
    // create a Programmer instance and store it in the database
    
    val steve = new Programmer("Steve", "513-206-3276")
    steve.addSkill("Java", 6)
    steve.addSkill("Perl", 3)
    connection.store(steve)
    
    connection.commit()
    
    connection.close()
}

Listing-3 - A Program to retrieve an object from db4o

package tutorial.db4o

import com.db4o.Db4oEmbedded
import Adapter._
import com.db4o.query.Predicate

object ReadProgrammers extends App {
    // a connection to the tutorial (db4o) database
    val connection = Db4oEmbedded.openFile("tutorial.db4o")
    
    // Retrieve programmers with the name Steve from the database
    val rs = connection.query{programmer: Programmer => programmer.name == "Steve"}
        
    // Print out each programmer's name, phone number, and skills
    rs.foreach{programmer =>
        println("Programmer name: %s, phone number: %s, skills:".format(programmer.name, programmer.phone))
        programmer.skills.foreach{skill => println("    %s at level %d".format(skill, programmer.skillLevel(skill).get))}
    }
    
    connection.close()
}

Output of execution of the program in Listing-3:

Programmer name: Steve, phone number: 513-206-3276, skills: Exception in thread "main" java.lang.NullPointerException at scala.collection.mutable.HashTable$$anon$1.(HashTable.scala:159) at scala.collection.mutable.HashTable$class.entriesIterator(HashTable.scala:157) at scala.collection.mutable.HashMap.entriesIterator(HashMap.scala:45) at scala.collection.mutable.HashTable$class.foreachEntry(HashTable.scala:190) at scala.collection.mutable.HashMap.foreachEntry(HashMap.scala:45) at scala.collection.mutable.HashMap$$anon$1.foreach(HashMap.scala:99) at tutorial.db4o.ReadProgrammers$$anonfun$2.apply(ReadProgrammers.scala:17) at tutorial.db4o.ReadProgrammers$$anonfun$2.apply(ReadProgrammers.scala:15) at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59) at scala.collection.immutable.List.foreach(List.scala:76) at tutorial.db4o.ReadProgrammers$delayedInit$body.apply(ReadProgrammers.scala:15) at scala.Function0$class.apply$mcV$sp(Function0.scala:34) at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12) at scala.App$$anonfun$main$1.apply(App.scala:60) at scala.App$$anonfun$main$1.apply(App.scala:60) at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59) at scala.collection.immutable.List.foreach(List.scala:76) at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:30) at scala.App$class.main(App.scala:60) at tutorial.db4o.ReadProgrammers$.main(ReadProgrammers.scala:7) at tutorial.db4o.ReadProgrammers.main(ReadProgrammers.scala)

Clearly, the Programmer object was not stored correctly in db4o. If we replace the Scala Map by a Java Map (e.g. java.util.HashMap), the programs will work correctly. It is however a awkward work-around to use Java map instead of Scala map in a Scala program.

Sunday, December 23, 2012

Using db4o in Scala Programs

This is a tutorial on using db4o in Scala programs. db4o is an object database management system with both Java and .NET APIs. We can also use db4o in Scala programs (with some limitations) as shown in this tutorial. Scala has many advantages over Java, as also shown in this tutorial. The readers of this tutorial are not assumed to have any knowledge about db4o. However, readers who have experience with Java programs using db4o can appreciate the elegance of Scala over Java more. The readers are expected to have basic knowledge about both Java and Scala programming.

It is a good practice to manage library dependency using Maven. (If you use Eclipse, and are interested in using Maven to manage classpath in Eclipse, you may be interested in this article of mine on this subject.) To manage db4o library dependency using Maven, include the snapshot repository below in the pom.xml file:

<repository>
    <snapshots>
        <enabled>true</enabled>
        <updatePolicy>never</updatePolicy>
        <checksumPolicy>fail</checksumPolicy>
    </snapshots>
    <id>db4o</id>
    <name>DB4O</name>
    <url>http://source.db4o.com/maven</url>
    <layout>default</layout>
</repository>

Also include the following dependency on the db4o-full-java5.jar in the pom.xml:

<dependency>
    <groupId>com.db4o</groupId>
    <artifactId>db4o-full-java5</artifactId>
    <version>8.0-SNAPSHOT</version>
</dependency>

(This tutorial includes both Java and Scala programs. If you use Eclipse and want help on working with Eclipse projects mixing Java and Scala, you may be interested in this article of mine on this subject.)

A Java Program Using db4o 

This tutorial starts with a Java program using db4o, to highlight the advantages of Scala. In Listing 2, the Java program opens a db4o database and retrieves Programmer objects with name "Joe" from it. The Java class Programmer is in Listing 1. A programmer has a name and a phone number. The name is not mutable and the phone number is. The query is awkward because a Java method cannot take another method as parameter so we have to wrap the match method in an anonymous class extending the  Predicate class (Line 12 - 17). The shortcoming can be overcome in Scala programs since Scala is also a functional programming language and can take a function as function parameter.


Listing 1 - The Programmer Java Class 

package tutorial.db4o.java;

public class Programmer {
    private String name;
    public String phone;
    
    public Programmer(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }
    
    public String getName() {
        return name;
    }
}

Listing 2 - A Java Program Using db4o 

package tutorial.db4o.java;

import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.query.Predicate;

public class ReadProgrammer {
    public static void main(String[] args) {
        ObjectContainer connection = Db4oEmbedded.openFile("tutorial.db4o");
        
        ObjectSet<Programmer> rs = connection.query(new Predicate<Programmer>() {
            @Override
            public boolean match(Programmer programmer) {
                return programmer.getName().equals("Joe");
            }
        });

    }
}

Adding Scala Objects into db4o 

Now let's look into the Scala world. In Listing 3 is the Scala counterpart of the Java Programmer class. An instance of the Scala class Programmer also has a immutable name and a mutable phone number. It is obvious that the Scala version is much simpler.

Listing 3 - The Scala Class Programmer 

package tutorial.db4o

class Programmer (val name: String, var phone: String)

In Listing 4 is a Scala program that creates three instances of the Programmer class and adds them into the db4o database. A Java version will be similar. There is no advantage in Scala programs over their Java counterparts in regard to storing objects into a db4o database, and deleting them from it.

Listing 4 - Add Objects into a db4o Database
 
package tutorial.db4o

import com.db4o.Db4oEmbedded
import Adapter._

object AddProgrammers extends App {
    // a connection to the tutorial (DB4o) database
    val connection = Db4oEmbedded.openFile("tutorial.db4o")
    
    // create three Programmer instances and store them in the database
    connection.store(new Programmer("Steve", "513-206-3276"))
    connection.store(new Programmer("Bob", "513-376-2521"))
    connection.store(new Programmer("Joe", "513-536-8093"))
    connection.commit()
    
    connection.close()
}

Retrieving Objects from db4o

In Listing 5 is a Scala program that retrieves all programmer instances in the db4o database and prints out their name and phone number. Contrast this program with the one in Listing 2, we can see that the query in the Scala version (Line 11) is much simpler than their Java counterpart (Line 12 - 17). The query method in the Scala version takes an anonymous function as its parameter. The anonymous function, on its turn, takes a Programmer object as its parameter and returns a Boolean value. If it returns true, the programmer object will be included in the query results, or excluded from it if it returns false. In Listing 5, the anonymous function always returns true regardless the programmer object passed in since we want to retrieve all programmer objects in the db4o database.

Listing 5 - Retrieve Objects from a db4o Database 
 
package tutorial.db4o

import com.db4o.Db4oEmbedded
import Adapter._

object ReadProgrammers extends App {
    // a connection to the tutorial (db4o) database
    val connection = Db4oEmbedded.openFile("tutorial.db4o")
    
    // Retrieve all programmers from the database
    val rs = connection.query{programmer: Programmer => true}
    
    // Print out each programmer's name and phone number
    rs.foreach{programmer =>
        println("Programmer name: %s, phone number: %s".format(programmer.name, programmer.phone))
    }
    
    connection.close()
}

If we run the program in Listing 5 without running the program in Listing 4 before, it prints nothing, because there is no programmer object in the database. If we run the program in Listing 4, then run the program in Listing 5, it will print:

Programmer name: Joe, phone number: 513-536-8093
Programmer name: Bob, phone number: 513-376-2521
Programmer name: Steve, phone number: 513-206-3276

Updating Objects in db4o

In Listing 6 is a Scala program that at first retrieves programmer objects with name "Joe" from the db4o database, and changes their phone number from whatever to 513-111-1111.  

Listing 6 - Update Objects in a db4o Database

package tutorial.db4o

import com.db4o.Db4oEmbedded
import Adapter._

object UpdateProgrammers extends App {
    // a connection to the tutorial (db4o) database
    val connection = Db4oEmbedded.openFile("tutorial.db4o")
    
    // Retrieve programmers with name "Joe" from the database
    val rs = connection.query{programmer: Programmer => programmer.name == "Joe"}
    
    // Change the programs' phone number
    rs.foreach{programmer =>
        programmer.phone = "513-111-1111"
        connection.store(programmer)
    }
    connection.commit()
    
    connection.close()
}
After we run this program, if we run the program in Listing 5 again, the output will be:

Programmer name: Joe, phone number: 513-111-1111 
Programmer name: Bob, phone number: 513-376-2521 
Programmer name: Steve, phone number: 513-206-3276 

We can see phone number of Joe has been updated.

In db4o, the method to store a brand new object and the method to store a updated object is exactly the same one.

Deleting Objects from db4o 

In Listing 7 is a Scala program that deletes every programmer objects in the database.

Listing 7 - Delete Objects from a db4o Database 

package tutorial.db4o

import com.db4o.Db4oEmbedded
import Adapter._

object DeleteProgrammers extends App {
    // a connection to the tutorial (db4o) database
    val connection = Db4oEmbedded.openFile("tutorial.db4o")
    
    // Retrieve all programmers from the database
    val rs = connection.query{programmer: Programmer => true}
    
    // delete every programmers
    rs.foreach{programmer =>
        connection.delete(programmer)
    }
    connection.commit()
    
    connection.close()
}

The Adapter

The magic to simplify the query method is an adapter class shown in Listing 8.  It is this class that has the query method that takes a predicate (i.e. a function returning a Boolean value) rather than an anonymous class extending the Predicate class. In the company object we also defined a implicit method that converts (i.e. wraps) an ObjectContainer into an Adapter object. With this implicit method, and a import statement import Adapter._, we will be able to call the new version of query method on an ObjectContainer object, as shown in Listing 5, 6, 7 (The compiler will do the conversion behind the scene). These are the real power of the Scala programming language.

Listing 8 - An Adapter Class

package tutorial.db4o

import com.db4o.ObjectContainer
import com.db4o.query.Predicate

class Adapter (connection: ObjectContainer) {
    def query[T](predicate: T => Boolean) : List[T] = {
        var results : List[T] = List[T]()
        val objectSet = connection.query(new Predicate[T]() {
            override
            def `match`(entry: T) = {
                predicate(entry)
            }
        });
        
        while (objectSet.hasNext()) {
            results = objectSet.next :: results
        }
        
        results
    }
}

object Adapter {
    implicit def objectContainer2Adapter(connection: ObjectContainer) = new Adapter(connection)
}

Conclusion 

We can use db4o in Scala programs. The functional programming nature of Scala greatly simplifies the query API. There are, however, some limitations on storing Scala objects in db4o databases. My experience shows that db4o does not support Scala collections. In other words, if a Scala class has Scala Map and Set as fields, db4o will not be able to store them correctly. I am going to discuss more details in this regard in my next post on this subject.

Sunday, November 11, 2012

Overriding hashCode() in Java May Cause Adverse Effects

One important method of the java.lang.Object class is hashCode(). It is a popular practice to override the hashCode() method in classes that extend the Object class (All Java classes, directly or indirectly extend the Object class). Joshua Block, in his popular book Effective Java, advices Java programmers to "always override hashCode when you override equals" (Item 8, Effective Java, Second Edition). Joshua, however, did not mention the possible adverse effect of overriding the hashCode() method. This article demonstrates such adverse effects in an example.

Our example is a short program that manages student-tutor relationship between students and tutors. Students are represented by objects of the Student class, in Listing 1. Tutors are represented by objects of the Tutor class, in Listing 2. The main program is in Listing 3.

Listing 1


package hashcode.issue;

public class Student {
    public String firstName;
    public String lastName;
    public String phoneNumber;
    
    public Student(String firstName, String lastName, String phoneNumber) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.phoneNumber = phoneNumber;
    }
}

Listing 2

package hashcode.issue;

public class Tutor {
    public String firstName;
    public String lastName;
    public String phoneNumber;
    
    public Tutor(String firstName, String lastName, String phoneNumber) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.phoneNumber = phoneNumber;
    }
}

Listing 3

package hashcode.issue;

import java.util.HashMap;
import java.util.Map;

public class HashCodeIssue {
    private static Map<Student, Tutor"> relationMap = new HashMap<Student, Tutor>();

    public static void main(String[] args) {
        Student john = new Student("John", "Jones", "8945");    // Create a Student object named john
        Tutor tom = new Tutor("Tom", "Petzold", "4627");    // Create a Tutor object named tom
        
        relate(john, tom);    // record the student-tutor relationship between john and tom
        
        // do many other things ...
        
        Tutor johnsTutor = findTutor(john);    // find the tutor of john
        if (johnsTutor != null) {    // print out what we found
            System.out.printf("John's tutor is %s %s\n", johnsTutor.firstName, johnsTutor.lastName);
        } else {
            System.out.println("John does not have a tutor.");
        }
        
        
        // John changed phone number
        john.phoneNumber = "513-326-5489";
        
        johnsTutor = findTutor(john);    // find the tutor of john again
        if (johnsTutor != null) { // print out what we found
            System.out.printf("John's tutor is %s %s\n", johnsTutor.firstName, johnsTutor.lastName);
        } else {
            System.out.println("John does not have a tutor.");
        }

    }
    
    private static void relate(Student student, Tutor tutor) {
        relationMap.put(student, tutor);
    }
    
    private static Tutor findTutor(Student student) {
        return relationMap.get(student);
    }
}

The program maintains the student-tutor relationship by a HashMap, using Student objects as map keys and Tutor objects as map values. A Student object is mutable. One can change a student's first name, last name, and phone number. After we establish a student-tutor relationship between a Student object john and a Tutor object tom by calling the relate() method, we call the findTutor() method to find the tutor of john. We found it. Then we change phone number of john, and call the findTutor() method again to find the tutor. We found the tutor again.

Program execution output:

John's tutor is Tom Petzold 
John's tutor is Tom Petzold 

So far, so good.

Now we override the hashCode() method in the Student class, as in Listing 4 (Line 16 - 19). Then we run the program again. This time, the second call to the findTutor() method failed to find the tutor.

Program execution output:

John's tutor is Tom Petzold 
John does not have a tutor


Listing 4

package hashcode.issue;

import org.apache.commons.lang3.builder.HashCodeBuilder;

public class Student {
    public String firstName;
    public String lastName;
    public String phoneNumber;
    
    public Student(String firstName, String lastName, String phoneNumber) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.phoneNumber = phoneNumber;
    }
    
    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(firstName).append(lastName).append(phoneNumber).build();
    }
}

The failure is due to the fact that when we use a Student object as key in a HashMap, the map calls the hashCode() method on the Student object and uses the returned number to determine hash bucket where the corresponding value resides. In our case, we overrode the hashCode() method to return a  number that depends on value of the student's first name, last number, and phone number. When the student's phone number changes, so does its hash code.

The hashCode() method defined in the java.lang.Object class is different, it always return the same number for the same object. In other words, the returned hash code can be used as the object's identity. When we overrode the hashCode() method to return a number that depends on the object's state (in our example, the Student's first name, last name, and phone number), the hash code can no longer be used as the object's identity.

Conclusion
If an object is mutable and its hashCode() method returns a number depending on its state, do not use the object as hash map key.  In case that you want to use an object as hash map key, ensure at first that it is guaranteed that either the object is immutable or its hashCode() method will always return the same number.

Sunday, October 14, 2012

Deploying Scala Desktop Applications via Java Web Start

[Last update on 04/08/2013]

Java Web Start is a great technology to deploy Java applications. It can be used to deploy Scala applications as well. This tutorial shows how to do it.

The Example Application

Our example Scala application is a simple Scala Swing desktop GUI, shown as Listing 1. Compile it and package the produced class files in a jar file. Let's name the jar file scala-gui.jar. To compile and run it, we need two scala libraries: scala-library and scala-swing. In this tutorial, we use scala-library-2.9.2.jar and scala-swing-2.9.2.jar.

Listing 1 - The Example Application

package simple.scala.swing.gui

import scala.swing._

object HelloWorld extends SimpleGUIApplication {
    def top = new MainFrame {
        title = "Scala Swing"
        contents = new Label {
            text = "Hello World!"
        }
    }
}

When runs, the application shows a simple window with text "Hello World!" as its content, as in the figure below.

Figure 1 - Example Scala Application


Host Web Server

To deploy the Scala application via Java Web Start, we have to host it on a web server. In this tutorial, we use Tomcat as our web server.

In Tomcat, create a simple web application named scala-gui. The figure below shows the directory structure of the Tomcat home directory.

Figure 2 - Directory Structure of Tomcat Home





Library Jar Files

Sign scala-gui.jar, scala-library-2.9.2.jar, and scala-swing-2.9.2.jar with a private key. (If need, see the appendix later for the how-to). Copy the above three jar files to webapps/scala-gui/lib under the Tomcat home directory.

jnlp Files

Create two jnlp files - scala-gui.jnlp and lib.jnlp, and place them under webapps/scala-gui under the Tomcate home directory. Content of the two jnlp files are in Listing 2 & 3. The IP of our web server is 192.168.241.128.

Listing 2 - scala-gui.jnlp

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+"
    codebase="http://192.168.241.128/scala-gui/"
    href="scala-gui.jnlp">
    <information>
          <title>Scala GUI</title>
          <vendor>xxx</vendor>
          <homepage href="http://192.168.241.128/scala-gui/" />
          <description>A simple Scala Swing GUI application to demo Web Start Deployment</description>
    </information>

    <security>
        <all-permissions />
    </security>

    <resources>
        <jar href="lib/scala-gui.jar" />
        <extension href="lib.jnlp"/>
    </resources>
    
    <application-desc main-class="simple.scala.swing.gui.HelloWorld" />
</jnlp>
 
 
Listing 3 - lib.jnlp 

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+"
    codebase="http://192.168.241.128/scala-gui/"
    href="lib.jnlp">
    <information>
          <title>Scala GUI Library</title>
          <vendor>scala.org</vendor>
          <homepage href="http://192.168.241.128/scala-gui/" />
          <description>Scala language and Swing libraries</description>
    </information>

    <security>
        <all-permissions />
    </security>
    
    <resources>
        <jar href="lib/scala-library-2.9.2.jar" />
    </resources>
    
    <resources>
        <jar href="lib/scala-swing-2.9.2.jar" />
    </resources>

    <component-desc/>
</jnlp>

The Web Page 

Finally, create our web page for users to download the Scala application. It is a simple HTML file, as shown in Listing 4.  

Listing 4 - index.html 

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Scala GUI</title>
        
        <script src="http://www.java.com/js/deployJava.js"></script>
    </head>
    
    <body>
      <H1>Scala GUI</H1>
  
  Click this button to start the Scala GUI:
  <script>
                 var url = "http://192.168.241.128/scala-gui/scala-gui.jnlp";
          deployJava.createWebStartLaunchButtonEx(url, '1.7.0');
  </script>
    </body>
</html>


The web page will be shown in a web browser as in the figure below.

Figure 3 - Download Page of the Applicatio

When a user click the Launch button on the web page, if Java Web Start has been installed on the client machine, a dialog as in the figure below will be shown to the user.

Figure 4 - Open JNLP with Java Web Start


If the user takes the default option, i.e. "Open with Java(TM) Web Start Launcher", a Security Warning dialog will be shown next, as in Figure 5. It is because we signed the jar files with a self-signed certificate. If we had signed the jar files with a certificate signed by a certificate authority trusted by the user, there would not be such warning. If the user accept the unverified signature, Java Web Start will download the Scala application and run it on the user's machine, as shown in Figure 1.

Figure 5 - Security Warning

Troubleshooting

Java Web Start may log/trace many useful information that can be used to troubleshoot deployment problems.

To enable Java Web Start log/trace on a Windows machine, do the following:

  1. Go to the Control Panel, and click the Java Control Panel. On the Java Control Panel dialog, choose the Advanced tab. Under Setting / Debugging, check both Enable tracing and Enable logging, as shown in Figure 6.
  2. Find the deployment.properties file. (On a Windows 7 machine, it is under C:\users\${username}\AppData\LocalLow\Sun\Java\Deployment, where ${username} should be replaced by concrete username.) Make sure the deployment.properties file has the following configuration lines:
    • deployment.trace=true
    • deployment.trace.level=all

Figure 6 - Java Control Panel    





On a Windows 7 machine, the log and trace files can be found under C:\users\${username}\AppData\LocalLow\Sun\Java\Deployment\log.




Appendix - Using Java keytool & jarsigner


Generate Keys

The example below shows how to generate keys using Java keytool.

keytool -genkey -alias tedkey -keypass tedpass -dname "cn=Ted Gao, ou=, o=, l=Mason, s=Ohio, c=US" -keystore C:\web-start-tutorial\ted.keystore -storepass tedpass -validity 1825 -keyalg RSA -keysize 2048 -storetype JCEKS -v

The command above has to be in one line. It generates a pair of RSA private key and certificate with 2048 bit strength, and stores them in a keystore file named ted.keystore under C:\web-start-tutorial. Alias for this key pair is "tedkey". The alias is to be used to specify which key (pair) to use later. The keystore and the private key are protected with a password "tedpass". The password for the private key is required to read the private key; the password for the keystore is required to write to the keystore. They do not have to be the same password. In this example, we just use the same password for both.

Sign  Jars

The example below shows how to sign java files using Java jarsigner.

jarsigner -keystore C:\web-start-tutorial\ted.keystore -storepass tedpass -keypass tedpass -storetype JCEKS -verbose scala-gui.jar tedkey

The command above has to be in one line. It use a keystore named ted.keystore under C:\web-start-tutorial. Alias of the key to use is "tedkey". Both the private key and the keystore are protected by password "tedpass". The example above signs a jar file named scala-gui.jar.

Tuesday, June 12, 2012

Running Core JavaScript in Eclipse

With the Eclipse JavaScript Development Tools, one can create JavaScript projects, and create and edit JavaScript files. The JavaScript editor provides JavaScript syntax highlight and formatting. In this regard, the JavaScript Development Tools are pretty much like the Java Development Tools. However, unlike in the Java Development Tools, with which one can run the Java class selected in the Java Editor, one cannot simply run the JavaScript file selected in the JavaScript Editor. This article teaches, with a little "trick", one will be able to do so - almost.

Either make a jar file with the Java class in Listing 1 in it and name the jar file javascript-runner.jar, or download javascript-runner.jar from https://github.com/ted-gao/teds-code/downloads. Place javascript-runner.jar somewhere in your local file system, for example, C:\toolkit.

Listing 1

package coder.toolkit;

import java.io.FileReader;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class CoreJavaScriptRunner {

    /**
     * @param args - a single program argument is expected. It should be a full path
     * to the core JavaScript to run.
     */
    public static void main(String[] args) {        
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("js");
        
        try {
            FileReader reader = new FileReader(args[0]);
            engine.eval(reader);
            reader.close();
        } catch (Exception e) {
          e.printStackTrace();
        }
    }
}

From Eclipse Workbench, create an External Tools Configuration following the steps below:
  1. Click the menu item Run | External Tools | External Tools Configurations ...
  2. On the pop-up External Tools Configurations dialog, right click Program, then click New, as in Figure 1.
  3. Fill in the right pane of the External Tools Configurations dialog, as in Figure 2.
  4. Click the Apply button, and then the Close button to close the External Tools Configurations dialog.
Figure 1 - Create a New External Tool Configuration



Figure 2 - The Core JavaScript Runner


(Assume that you have already installed JavaScript Development Tools in Eclipse) Now You can create a JavaScript project and a JavaScript file in it. Open the JavaScript file with the JavaScript Editor, (if wanted, edit the script,) then click the External Tool button and select Core JavaScript Runner, as in Figure 3 below. Eclipse will run the selected JavaScript. And the output will be written to the Console.


Figure 3 - Run Core JavaScript


Figure 4 - An Example

Appendix - Create JavaScript Project in Eclipse


To create a JavaScript project in Eclipse, from the Workbench, follow the steps below:
  1. Click menu item File | New | Project ...
  2. On the pop-up New Project dialog appears, select JavaScript | JavaScript Project, and click the Next button.
  3. Fill in a project name, and click the Finish button.


Thursday, May 31, 2012

Exception Handling in Scala

For exception handling, both Scala and Java have similar try/catch/finally constructs.  In addition, exception handling in Scala can be done using the various member classes of the scala.util.control.Exception object, such as Catch, Try, Catcher, and Finally, in stead of the try/catch/finally construct. In this tutorial, we are going to see examples of both approaches.

try/catch/finally Construct

There is a small difference between Scala and Java's try/catch/finally constructs. In Java, one may have multiple catch clauses for a single try clause, each for a exception class (the Exception class itself or its subclass). In Scala, only one catch clause is needed, and multiple cases are used to handle multiple exception classes. Listing 1 below illustrate such exception handling in Scala.

Listing 1 - Example Using the try/catch/finally Construct

package exception.handling.examples

import scala.io.Source
import java.io.IOException
import java.io.FileNotFoundException

object TryClauseExample extends App {
    val fileName = "c:\\temp\\test.txt"
     
    val content =
    try {
        Source.fromFile(fileName).getLines.mkString("\n")
    } catch {
          case e: FileNotFoundException => throw new SystemError(e)
          case e: IOException => throw new SystemError(e)
    }
    
    println(content)
}

In Listing 1, the simple program reads a file from the file system, store the file content in a variable named content, and print content's value to the console. If a FileNotFoundException, or a more general IOException occurs, it wraps the exception into a custom excepton named SystemError (see Listing 2) and throws the SystemError exception. Since FileNotFoundException is not treated in any way different from the more general IOException, one case will be sufficient. We used two cases in the example just to show how to handle different Exception subclasses.

Listing 2 - SystemError

package exception.handling.examples

class SystemError(message: String, nestedException: Throwable) extends Exception(message, nestedException) {
    def this() = this("", null)
    
    def this(message: String) = this(message, null)
    
    def this(nestedException : Throwable) = this("", nestedException)
}

scala.util.control.Exception object and its Member Classes

It is not necessary to use the try/catch/finally construct to handle exceptions. The same thing can be done using various methods and nested classes of the singleton object scala.util.control.Exception. In this tutorial, we are just going to refer to this singleton object as the Exception singleton. The program in Listing 3 is equivalent to the program in Listing 1, but does not use the try/catch/finally construct.

Listing 3 - First Example Using the Catch Class 

package exception.handling.examples

import scala.util.control.Exception.catching
import java.io.IOException
import java.io.FileNotFoundException
import scala.io.Source

object CatchClassExample1 extends App {
    val fileName = "c:\\temp\\test.txt"
        
    val fileCatch = catching(classOf[FileNotFoundException], classOf[IOException]).withApply(e => throw new SystemError(e))
    
    val content = fileCatch{Source.fromFile(fileName).getLines.mkString("\n")}
    
    println(content)
}

The most important thing about the Exception singleton is its (nested) member class Catch. Every object of this class captures the exception handling logic usually captured by the catch (and optionally the finally) part of a try/catch/finally statement. In general, such exception logic are about:
  1. What exception, e.g. FileNotFoundException and IOException, to catch
  2. What to do when catching each of those exceptions, e.g. wrapping the exception in another exception and throwing the later, 
  3. Optionally, what to do always no mater whether an exception occurred or not, i.e. the body of a finally clause in a try/catch/finally statement
The Exception singleton has a method whose header is

catching[T](exceptions: Class[_]*): Catch[T]

 

This method returns a Catch object. The method parameter exceptions are the exception classes to be caught by the Catch object. In our example (Line 11 of Listing 3), calling the catching method and passing to it two exception classes (i.e. FileNotFoundException and IOException) returns a Catch object that will catch FileNotFoundException and IOException.

The Catch class has a method withApply, whose header is

withApply[U](f: (Throwable) ⇒ U): Catch[U] 

 

Calling this method on a Catch object will return another Catch object that will execute the anonymous function passed as argument to the withApply method, when a target exception is caught. In our example (Line 11 of Listing 3), variable fileCatch references a Catch object that will catch FileNotFoundException and IOException, and will throw a custom exception - SystemError - that wraps the caught exception.

The Catch class has a method apply, whose header is

apply[U >: T](body: ⇒ U): U

 

Calling the apply method, passing a code block as argument to the method, will execute the code block, like the case of a try clause in a try/catch/finally statement. If that code block throw any exception targeted by the Catch object, the Catch object will catch the exception and execute another code block - the one passed to it via calling its withApply method.

This explains why the two programs in Listing 3 and Listing 1 are equivalent.

Listing 4 - Second Example Using the Catch Class 

package exception.handling.examples

import scala.util.control.Exception.catching
import java.io.IOException
import java.io.FileNotFoundException
import scala.io.Source

object CatchClassExample1 extends App {
    val fileName = "c:\\temp\\test.txt"
        
    val fileCatch = catching(classOf[FileNotFoundException], classOf[IOException]).withApply(e => throw new SystemError(e))
    
    val content = fileCatch.opt{Source.fromFile(fileName).getLines.mkString("\n")}
    
    println(content.getOrElse("The file cannot be found or read"))
}

Listing 4 is slightly different from Listing 5. In Line 13, instead of the apply method, the opt method, of the Catch object is called. The opt method, like the apply method, takes a code block as parameter, and executes the code block. If calling the apply method with a certain code block as argument returns normally, i.e. not throwing exception, calling the opt method with the same code block as argument will return a Some object wrapping that normal result; if calling the apply method with a certain code block as argument throws an exception, calling the opt method with the same code block as argument will return None. The program in Listing 4 will print the file content to the console if the file can be found and read. If the file cannot be found or read, it will print string "The file cannot be found or read" to the console.


Listing 5 - Third Example Using the Catch Class 

package exception.handling.examples

import scala.util.control.Exception.catching
import java.io.IOException
import java.io.FileNotFoundException
import scala.io.Source

object CatchClassExample1 extends App {
    val fileName = "c:\\temp\\test.txt"
        
    val fileCatch = catching(classOf[FileNotFoundException], classOf[IOException]).withApply(e => throw new SystemError(e))
    
    val content = fileCatch.either{Source.fromFile(fileName).getLines.mkString("\n")}
    
    println(content.right.getOrElse("The file cannot be found or read"))
}

Listing 5 is also slightly different from Listing 3. In Line 13, instead of the apply method, the either method, of the Catch object is called. The either method, like the apply method, takes a code block as parameter, and executes the code block. If calling the apply method with a certain code block as argument returns normally, i.e. not throwing exception, calling the either method with the same code block as argument will return a Right object wrapping that normal result; if calling the apply method with a certain code block as argument throws an exception, calling the either method with the same code block as argument will return a Left object wrapping the exception. The program in Listing 5 will print the file content to the console if the file can be found and read. If the file cannot be found or read, it will print string "The file cannot be found or read" to the console.

Conclusion
 The exception handling with the Catch class has two advantages comparing with the traditional try/catch/finally construct approach:
  1. reusing exception handling logic. That is "Don't Repeat Yourself". A Catch object captures a certain exception handling logic. One can call the apply (or opt, or either) on the same Catch object many times, each time with different argument - code block equivalent to the body of a try clause. In the case of the try/catch/finally construct approach, when there are multiple try/catch/finally statements only differ in the try clause, the catch and finally clause still has to be duplicated for each try clause.
  2. Supporting Monad, with the opt and either methods of the Catch class
 The exception handling with the Catch class also has two disadvantages comparing with the traditional try/catch/finally construct approach:
  1. Unfamiliar to a large body of programmers, while almost every programmer with basic knowledge of Object-Oriented programming is familiar with the try/catch/finally construct.
  2. Scattering the exception handling logic about what exceptions to catch, what to do when catch them, and the code that throws those exception into different location in source code.When the gaps between those locations are large, likely the logic will be more difficult to understand than other wise. 
The first disadvantage can be overcome with a little training. In general the second disadvantage cannot be easily overcome. Programmers need to be cautious and weight the costs over benefits while choosing one approach over the other.

Sunday, April 29, 2012

Selenuim 2 (WebDriver 2) and The Trouble of Automatic Firefox Updates

One day all my Selenium-based tests suddenly broke with error message: "org.openqa.selenium.firefox.NotConnectedException: Unable to connect to host localhost on port 7055 after 45000 ms." That day my Firefox was automatically updated from 10 to 11. All tests were fine before the update. I guessed that it was the update that broke all the tests. The full error message is below (with a little modification removing security sensitive information):

[TestNG] Running:
  C:\Documents and Settings\ted-gao\Local Settings\Temp\testng-eclipse--811630090\testng-customsuite.xml

org.openqa.selenium.firefox.NotConnectedException: Unable to connect to host localhost on port 7055 after 45000 ms. Firefox console output:
*** LOG addons.xpi: startup
*** LOG addons.xpi: Skipping unavailable install location app-system-local
*** LOG addons.xpi: Skipping unavailable install location app-system-share
*** LOG addons.xpi: Ignoring file entry whose name is not a valid add-on ID: C:\DOCUME~1\TED-GAO\LOCALS~1\Temp\anonymous4693713743778448909webdriver-profile\extensions\webdriver-staging
*** LOG addons.xpi: checkForChanges
*** LOG addons.xpi: No changes found

    at org.openqa.selenium.firefox.internal.NewProfileExtensionConnection.start(NewProfileExtensionConnection.java:95)
    at org.openqa.selenium.firefox.FirefoxDriver.startClient(FirefoxDriver.java:147)
    at org.openqa.selenium.remote.RemoteWebDriver.<init>(RemoteWebDriver.java:75)
    at org.openqa.selenium.firefox.FirefoxDriver.<init>(FirefoxDriver.java:127)
    at org.openqa.selenium.firefox.FirefoxDriver.<init>(FirefoxDriver.java:82)
   (a few lines removed here)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:80)
    at org.testng.internal.Invoker.invokeConfigurationMethod(Invoker.java:543)
    at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:212)
    at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:140)
    at org.testng.internal.TestMethodWorker.invokeBeforeClassMethods(TestMethodWorker.java:175)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:107)
    at org.testng.TestRunner.privateRun(TestRunner.java:753)
    at org.testng.TestRunner.run(TestRunner.java:613)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:337)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:330)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:292)
    at org.testng.SuiteRunner.run(SuiteRunner.java:241)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:64)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:87)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1170)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1094)
    at org.testng.TestNG.run(TestNG.java:1006)
    at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:107)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:210)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:170)
FAILED CONFIGURATION: @BeforeClass setup
org.openqa.selenium.WebDriverException: Failed to connect to binary FirefoxBinary(D:\Program Files\Mozilla Firefox\firefox.exe) on port 7055; process output follows:
*** LOG addons.xpi: startup
*** LOG addons.xpi: Skipping unavailable install location app-system-local
*** LOG addons.xpi: Skipping unavailable install location app-system-share
*** LOG addons.xpi: Ignoring file entry whose name is not a valid add-on ID: C:\DOCUME~1\TED-GAO\LOCALS~1\Temp\anonymous4693713743778448909webdriver-profile\extensions\webdriver-staging
*** LOG addons.xpi: checkForChanges
*** LOG addons.xpi: No changes found

Build info: version: '2.14.0', revision: 'unknown', time: '2011-11-29 13:13:16'
System info: os.name: 'Windows XP', os.arch: 'x86', os.version: '5.1', java.version: '1.6.0_17'
Driver info: driver.version: FirefoxDriver
    at org.openqa.selenium.firefox.internal.NewProfileExtensionConnection.start(NewProfileExtensionConnection.java:107)
    at org.openqa.selenium.firefox.FirefoxDriver.startClient(FirefoxDriver.java:147)
    at org.openqa.selenium.remote.RemoteWebDriver.<init>(RemoteWebDriver.java:75)
    at org.openqa.selenium.firefox.FirefoxDriver.<init>(FirefoxDriver.java:127)
    at org.openqa.selenium.firefox.FirefoxDriver.<init>(FirefoxDriver.java:82)
    (a few lines removed here)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:80)
    at org.testng.internal.Invoker.invokeConfigurationMethod(Invoker.java:543)
    at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:212)
    at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:140)
    at org.testng.internal.TestMethodWorker.invokeBeforeClassMethods(TestMethodWorker.java:175)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:107)
    at org.testng.TestRunner.privateRun(TestRunner.java:753)
    at org.testng.TestRunner.run(TestRunner.java:613)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:337)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:330)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:292)
    at org.testng.SuiteRunner.run(SuiteRunner.java:241)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:64)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:87)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1170)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1094)
    at org.testng.TestNG.run(TestNG.java:1006)
    at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:107)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:210)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:170)
Caused by: org.openqa.selenium.firefox.NotConnectedException: Unable to connect to host localhost on port 7055 after 45000 ms. Firefox console output:
*** LOG addons.xpi: startup
*** LOG addons.xpi: Skipping unavailable install location app-system-local
*** LOG addons.xpi: Skipping unavailable install location app-system-share
*** LOG addons.xpi: Ignoring file entry whose name is not a valid add-on ID: C:\DOCUME~1\TED-GAO\LOCALS~1\Temp\anonymous4693713743778448909webdriver-profile\extensions\webdriver-staging
*** LOG addons.xpi: checkForChanges
*** LOG addons.xpi: No changes found

    at org.openqa.selenium.firefox.internal.NewProfileExtensionConnection.start(NewProfileExtensionConnection.java:95)
    ... 33 more

SKIPPED CONFIGURATION: @AfterMethod teardown
SKIPPED: testHomePage
SKIPPED: testBenefitPage
SKIPPED: testProviderLocatorPageFromSideMenu
SKIPPED: testProviderSearchByZip
SKIPPED: testProviderLocatorPageFromTopMenu
SKIPPED: testWellness

===============================================
    Default test
    Tests run: 6, Failures: 0, Skips: 6
    Configuration Failures: 1, Skips: 1
===============================================


===============================================
Default suite
Total tests run: 6, Failures: 0, Skips: 6
Configuration Failures: 1, Skips: 1
===============================================

[TestNG] Time taken by org.testng.reporters.SuiteHTMLReporter@2079451: 141 ms
[TestNG] Time taken by org.testng.reporters.EmailableReporter@207a59c: 31 ms
[TestNG] Time taken by [TestListenerAdapter] Passed:0 Failed:0 Skipped:0]: 16 ms
[TestNG] Time taken by org.testng.reporters.JUnitReportReporter@207a66b: 15 ms
[TestNG] Time taken by org.testng.reporters.XMLReporter@20795a3: 32 ms


Even though the error message might seem suggesting something else. The really reason was that the version of WebDriver that I was using (2.11.0) was not compatible with Firefox 11 (at that point of time, there was not any version of WebDriver that supported Firefox 11).

It was too much trouble to uninstall Firefox 11 and to reinstall Firefox 10. My solution is to copy the base directory of a Firefox 10 installation (and all sub-directories, etc) on another computer to my computer, and use the following code fragment to point to the specific Firefox binary:

FirefoxBinary binary = new FirefoxBinary(new File(firefoxBinaryPath));
WebDriver webDriver = new FirefoxDriver(binary, profile);

Where firefoxBinaryPath is a String representing the path to the Firefox binary executable; profile is a FirefoxProfile object. (My another post is about choosing Firefox profile for WebDriver.)

It works. All my Selenium-based tests work again.

To disable automatic Firefox updates, follow this article.

Saturday, March 17, 2012

Executing External Programs via Java Programs Using Plexus Common Utilities

This tutorial shows how for Java programs to execute external programs using the Plexus Common Utilities library. The Plexus Common Utilities library makes it easier for Java programs to execute external programs and shell commands, and open files such as Word documents, PDF documents, etc with appropriate programs, than with the Runtime interface and ProcessBuilder class in the standard Java API.

This JavaWorld articule, When Runtime.exec() won't by Michael C. Daconta discusses how to use the Runtime interface from the standard Java API in the right way to execute external programs and shell commands. This article, Execute an external program, discusses how to use the Runtime interface and ProcessBuilder class from the standard Java API to execute external programs and open PDF files, etc with appropriate programs. Interested readers can read the two articles to appreciate the convenience brought by the Plexus Common Utilities library in regard to executing external programs and shell commands, and opening files with appropriate programs.

In our first example (Listing 1), we execute a maven command from a Java program. The maven command is mvn clean install. It at first cleans a Maven project, build it, and install the artifact built into the local Maven repository. In the Java program, the maven command is specified via Line 18 and 19. The base directory of the Maven project is specified as the working directory of the execution via Line 20. The center of this example is Line 27, where the executeCommandLine() method is called with three arguments. The first argument, commandline is an object of the type org.codehaus.plexus.util.cli.CommandLine. The commandline object captures the command and working directory to execute the external program, plus arguments to the external program. The second and third arguments, out and err, are two object of the type org.codehaus.plexus.util.cli.CommandLineUtils.StringStreamConsumer. The output and error message of the external program go into out and err respectively. The executeCommandLine() method is overloaded. Readers may look up them via the Plexus Common Utilities API.

Listing 1 - Executing an external program

package plexus.examples;

import java.io.File;

import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;


public class ExternalProgramRunner {
    public static void main(String[] args) {
        String command = "D:\\maven\\bin\\mvn.bat";
        String workingDir = "D:\\examples\\plexus-example";
        String[] arguments = new String[]{"clean", "install"};
        
        Commandline commandline = new Commandline();
        commandline.setExecutable(command);
        commandline.addArguments(arguments);
        commandline.setWorkingDirectory(new File(workingDir));
        
        CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
        CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
        
        int exitCode;
        try {
            exitCode = CommandLineUtils.executeCommandLine(commandline, out, err);
        } catch (CommandLineException e) {
            e.printStackTrace();
        }
        
        String output = out.getOutput();
        if (!StringUtils.isEmpty(output)) {
            System.out.println(output);
        }
        
        String error = err.getOutput();
        if (!StringUtils.isEmpty(error)) {
            System.out.println(error);
        }
    }
}


Output of Listing 1 Execution

[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building Plexus Example 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ plexus-example ---
[INFO] Deleting D:\examples\plexus-example\target
[INFO]
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ plexus-example ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ plexus-example ---
[WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
[INFO] Compiling 2 source files to D:\examples\plexus-example\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ plexus-example ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\examples\plexus-example\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ plexus-example ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.7.1:test (default-test) @ plexus-example ---
[INFO] No tests to run.
[INFO] Surefire report directory: D:\examples\plexus-example\target\surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
There are no tests to run.

Results :

Tests run: 0, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- maven-jar-plugin:2.3.1:jar (default-jar) @ plexus-example ---
[INFO] Building jar: D:\examples\plexus-example\target\plexus-example-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:2.3.1:install (default-install) @ plexus-example ---
[INFO] Installing D:\examples\plexus-example\target\plexus-example-1.0-SNAPSHOT.jar to C:\users\ted\.m2\repository\ted-tutorial\plexus-example\1.0-SNAPSHOT\plexus-example-1.0-SNAPSHOT.jar
[INFO] Installing D:\examples\plexus-example\pom.xml to C:\users\ted\.m2\repository\ted-tutorial\plexus-example\1.0-SNAPSHOT\plexus-example-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.329s
[INFO] Finished at: Sun Mar 18 23:36:37 EDT 2012
[INFO] Final Memory: 8M/245M
[INFO] ------------------------------------------------------------------------


Listing 2 below shows executing a Windows shell command (i.e. dir) from a Java program (the Java program is executed on a Windows machine). The only differences between Listing 2 and 1 lie on Line 13-15.

Listing 2 - Executing a Shell Command

package plexus.examples;

import java.io.File;

import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;


public class ExternalProgramRunner {
    public static void main(String[] args) {
        String command = "dir";
        String workingDir = "D:\\examples\plexus-example";
        
        Commandline commandline = new Commandline();
        commandline.setExecutable(command);
        commandline.setWorkingDirectory(new File(workingDir));
        
        CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
        CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
        
        int exitCode;
        try {
            exitCode = CommandLineUtils.executeCommandLine(commandline, out, err);
        } catch (CommandLineException e) {
            e.printStackTrace();
        }
        
        String output = out.getOutput();
        if (!StringUtils.isEmpty(output)) {
            System.out.println(output);
        }
        
        String error = err.getOutput();
        if (!StringUtils.isEmpty(error)) {
            System.out.println(error);
        }
    }
}

Output of Listing 2 Execution

Directory of D:\\examples\plexus-example

03/18/2012 11:36 PM <DIR> .
03/18/2012 11:36 PM <DIR> ..
03/12/2012 05:34 PM 421 .classpath
03/12/2012 05:34 PM 506 .project
03/12/2012 05:34 PM <DIR> .settings
03/12/2012 05:33 PM 1,330 pom.xml
03/12/2012 05:12 PM <DIR> src
03/18/2012 11:36 PM <DIR> target
3 File(s) 2,257 bytes
5 Dir(s) 554,998,767,616 bytes free

Listing 3 below shows opening a PDF file with an appropriate program (i.e. Adobe Reader) from a Java program. In this example, a PDF file named plexus-guide.pdf is located under D:\docs. Note that nowhere is ever Adobe Reader is mentioned, nevertheless the installation path, in the Java program.Plexus Common Utilities just figures it out behind the scene. The only differences between Listing 3 and 1 lie on Line 13-15.

Listing 3 - Opening a PDF File with Adobe Reader

package plexus.examples;

import java.io.File;

import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;


public class ExternalProgramRunner {
    public static void main(String[] args) {
        String command = "plexus-guide.pdf";
        String workingDir = "D:\\docs";
        
        Commandline commandline = new Commandline();
        commandline.setExecutable(command);
        commandline.setWorkingDirectory(new File(workingDir));
        
        CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
        CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
        
        int exitCode;
        try {
            exitCode = CommandLineUtils.executeCommandLine(commandline, out, err);
        } catch (CommandLineException e) {
            e.printStackTrace();
        }
        
        String output = out.getOutput();
        if (!StringUtils.isEmpty(output)) {
            System.out.println(output);
        }
        
        String error = err.getOutput();
        if (!StringUtils.isEmpty(error)) {
            System.out.println(error);
        }
    }
}