Adept Open Source Library

This is a blog to provide in-depth information on the Adept open source library - the core of which is a Java object database component. In addition there is lots of Java code and solutions for many development problems.

http://marringtons.com

Wednesday, April 20, 2005

String Utilities

The Strings class in Java is very special. Not only can instances be created by putting double-quotes around text, but it is allowed to use special overloaded operators (such as plus-sign) that the reset of us can't implement without reverting to C++. Hmmm, I sound bitter. Well, only slightly - after all we sometimes have to live with the tools we are given.

The String class is also final - which means you cannot add functionality. This is a shame because it doesn't do everything it could to make life easy. The Adept library contains a com.marringtons.string.Strings class of static methods for providing tools to make strings a little easier to work with.

There are methods to make string tests easier - even if the reference is null. There are methods to split strings and others to join them again. There's a method to create an array of strings from a collection of objects and another to create the array from a regular expression matcher. Lastly, there is a method to order strings by length (Strings.sortDescendingByLength).

Null Strings

It's not uncommon to have a empty string as either a null reference or a reference to a string with no content. This is not a problem with Java 1.5 and above, but in earlier code the tests all looked unwieldy and there was a risk of NullPointerException occurring - unchecked to the interface.
if (name != null  &&  name.length() > 0)
  name = askForName();

 

Strings provides a more readable alternative:
if (Strings.isEmpty( name))
  name = askForName();

if (Strings.isNotEmpty( name))
  sayHello( name);

 

The other related problem is the good old toString() object. When it is called explicitly it gets very upset if the object is null. Strings to the rescue:
Object response = getResponse();

if (response != null)
  name = response.toString();
else
  name = "";

// ... is the same as ...

name = Strings.toString( response);

 

Splitting Strings

Java.util.regex.Pattern has a split method that separates a string into an array of strings based of a separator pattern. Strings provides some convenience methods for comma separated and OO named lists. The adept library uses comma separated variables (csv) from the properties file and from HTTP forms. The OO version is often used to map form data to classes as part of form processing.
String[] parts = Strings.split( "ab, cd, ef");
parts = Strings.splitJava( "Java.util.regex.Pattern");

 

Joining Strings

Another common function is to create a string from component parts - joining them.
String[] parts = { "a", "b", "c", "d" }
string = Strings.join( parts); // a, b, c, d
string = Strings.join( parts, "."); // a.b.c.d
string = Strings.join( parts, ".", 1); // b.c.d
string = Strings.join( parts, ".", 1, 3); // b.c

 

There is also a version of the second method that takes a list for when the string array is not yet an array.

String Arrays

It's not uncommon to collect a list of strings - without knowing how many there will be - and then return an array of them.
public String[] listArchive() throws IOException
  {
    ArrayList files = new ArrayList();
    Enumeration enumeration = zipFile.entries();
    while (enumeration.hasMoreElements())
      files.add( ((ZipEntry) enumeration.nextElement()).getName());
    return Strings.toArray( files);
  }

 

There is also a method for generating a string array given a string and regular expression.
Pattern pattern = Pattern.compile( "(\\d+)\\D+(\\d+)");
Matcher matcher = pattern.matcher( "123<==>456");
check( matcher.matches());
String[] strings = Strings.toArray( matcher);
check( strings.length == 2);
check( strings[0].equals( "123"));
check( strings[1].equals( "456"));

 

Wednesday, April 13, 2005

Map Initialisation

Java provides a dictionary-like facility called a Map of which the most popular version is HashMap. They are great for keeping name/value pairs and are used heavily in almost all programs where type-safe naming is too restrictive. Most interpretive languages such as Perl or Python provide methods to initialise a dictionary in code, but native Java has no such built-in tool.

No fear, it's easy to build - and the Adept library has a Map helper class in the util package with two static load() methods. One to create a HashMap and another where the map is provided so that you can use another variant such as a LinkedHashMap. Both methods use arrays to provide the information to load the map.

Examples

Map fieldTranslation = Maps.load( new Object[]
  {
    "integer", "integer",
    "aLong", "anotherLong",
  });

Map pronounce = Maps.load( new LinkedHashMap(),
  new Object[]
    {
      "a", "aye",
      "b", "bee",
      "c", "see",
      "d", "dee",
      ...
    });

 

Sets

Sometimes maps are used as sets - being a collection of keys without values. The library class com.marringtons.util.Maps has two methods for working with map sets.
FileReader fileReader = new FileReader();
fileReader.setArchive(
    "com/marringtons/util/system.resourceTest.zip");
String[] files = fileReader.listArchive();

Map map = Maps.loadSet( files);
assertTrue( map.containsKey(
    "level1/level2/test in zip.txt"));
assertTrue( map.containsKey(
    "META-INF/PATH-BASE.TXT"));
assertTrue( map.containsKey(
    "META-INF/PROPERTIES.TXT"));
assertTrue( map.containsKey(
    "level1/system.properties.txt"));

String[] files = fileReader.listArchive();
Arrays.sort( files);
Map map = Maps.loadSet( new LinkedHashMap(), files);

 

Thursday, April 07, 2005

Transactions and the Adept Object Database

In the normal course of events you may read in some data, mark it for update, change it then commit it. The Adept Object Database system will queue it for writing to the database as soon as possible - typically in the next tenth of a second or so.

Now, it's far easier to design software by compartmentalising functionality. This not only means keeping disparate functions in different places, but also limiting how much they need know about each other.

Let's take an example form that the user enters for an application. It could include address, contact and application specific details. The processing code would look something like:

user = createUser( form.userName);
addAddress( user, form.addressFields);
addContact( user, form.email);
addApplication( user, form.applicationDetails);

 

Now, what happens if the application includes a date of birth and that shows that the applicant is under-age. Sure, we'll output and tell the applicant, but what to do with the data? We now have user, address and contact details that are of no use without an application. We could write a delete routine to remove the information already added. This wouldn't be a bad solution - particularly if you needed a delete routine anyway for maintenance. It would be better, however, if the inconsistent or unnecessary data never saw the database. This is what transactions are for.
try
  {
    Transaction.start();
    user = createUser( form.userName);
    addAddress( user, form.addressFields);
    addContact( user, form.email);
    addApplication( user, form.applicationDetails);
 Transaction.end();
  }
catch (Throwable throwable)
  { Transaction.abort(); }

 

Note that once a transaction starts, it must be completed or aborted - otherwise the persistence is never achieved.

Nesting Transactions

The Adept Transaction system supports full transaction nesting. On every start() a new nesting level is created. An abort() will drop all items updated since the last start() while an end() will add them to the next higher transaction group. An end() for the highest transaction group will have all items within the transaction queued for a commit. A global endAll() or abortAll() will process all nesting levels at once.

The Adept Development System

If you are working inside of the Adept Development System the connection manager does this all for you. It maintains a transaction across each conversation. You only need to call transaction management explicitly if you have a group of transactions that need to be controlled - but not effect other outer transactions if they need to be aborted. The end of the conversation can use endAll() or abortAll() to clear the nested transaction stack if needed.

In short, ignore transaction management as it's all done for you behind the scenes. Throw something if you want all database writes to be rolled back.