Pronto Property Messages

One of the nicest parts of developing JSP based web applications is the simplicity in making the front-end locale specific. Most of the MVC frameworks I have used (proprietary, Struts and Spring) have a set of tags that allow the developer to embed a key into the JSP and then at runtime that key will be replaced by the user or application’s locale specific value that is associated with the key.

This is all pretty bog standard stuff. The majority frameworks allow the keys to be in Property file format (key=value), some allow an XML variant format and some also can connect to an RDBMS database for theirs. The problem I find is that if I am coding a pretty caption/label intensive page is that I spend most of my time to-ing and fro-ing between the JSP and the property file. This is a bind and can also lead to some “features” creeping in where I don’t add a property in the file; thus causing a crash when I test. Most of these frameworks allow the developer to add a default along with a key in the JSP, alleviating the crashing, but that then means you have to go through the pages looking for these defaults before UAT/Production.

Rather than whine ad infinitum about this I’ve come up with a little utility that prevents all the to-ing and fro-ing, ensures that every key has a default and a value. I’ve coded it to work with Spring MVC (my MVC de jour), but with a little tweaking it will work with the majority of property based messages. There is a simple rule (mainly because it’s a hack and its not as robust as it could be); All messages need to be coded with a default (which should be specifc to the message), and that default must be prefixed with an exclamation mark:


<spring:message code="ui.label.example" text="!An Example" />

So once you have littered your JSP with messages like above you need to run a parser against the JSP that will output a property file which can then be popped in your classpath for your web application to read. The application takes two parameters; the root directory of your JSP and the name/path of the property file you wish to create. Thats about it, so here is the code you will need:

package com.marshbourdon.utilities.spring;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class ProntoMessages {

	/**
	 * Default constructor.
	 */
	public ProntoMessages() {

		super();
	}

	public static void main(String args[]) {

		ProntoMessages pronto = new ProntoMessages();

		try {

			pronto.compile(args[0], args[1]);
		}
		catch (Exception e) {

			e.printStackTrace();
		}
	}

	@SuppressWarnings("unchecked")
	private void compile(String sourceDirectoryName, String defaultFileName) throws Exception {

		File sourceDirectory = new File(sourceDirectoryName);

		if (!sourceDirectory.exists())
			throw new FileNotFoundException("The provided Source Directory does not exist!");

		if (!sourceDirectory.isDirectory())
			throw new FileNotFoundException("The provided Source Directory is not a Directory!");

		if (!sourceDirectory.canRead())
			throw new FileNotFoundException("The provided Source Directory can not be read!");

		List  resourceItems = new ArrayList ();

		// Recurse the Source Directory
		recurseDirectory(sourceDirectory, resourceItems);

		// Take the Resource Items, and order them alphabetically, by key
		try {

			Comparator comparator = new Comparator() {

				public int compare(Object object1, Object object2) {

					ResourceItem resourceItem1 = (ResourceItem) object1;
					ResourceItem resourceItem2 = (ResourceItem) object2;

					return resourceItem1.getKey().compareTo(resourceItem2.getKey());
				}
			};

			if (resourceItems != null)
				Collections.sort(resourceItems, comparator);
		}
		catch (Exception exception) {
			exception.printStackTrace();
		}

		FileOutputStream fos = null;
		PrintStream ps = null;

		try {

			fos = new FileOutputStream(defaultFileName);
			ps = new PrintStream(fos);

			for (ResourceItem resourceItem : resourceItems) {

				StringBuilder sb = new StringBuilder();
				
				sb.append(resourceItem.getKey());
				sb.append("=");
				sb.append(resourceItem.getValue());

				ps.println(sb.toString());
			}
		}
		catch (RuntimeException e) {

			e.printStackTrace();
		}
		finally {
			
			ps.close();
			fos.close();
		}
	}

	private void recurseDirectory(File sourceDirectory, List  resourceItems) {

		FileFilter filter = new FileFilter() {

			public boolean accept(File file) {

				// We'll neexd to parse these files
				if (file.getName().toLowerCase().endsWith(".jsp"))
					return true;

				// We'll need to recurse these directories
				if (file.isDirectory())
					return true;

				return false;
			}
		};

		for (File file : sourceDirectory.listFiles(filter)) {

			if (file.isDirectory())
				recurseDirectory(file, resourceItems);
			else {

				try {

					IndexOfReader reader = new IndexOfReader(new FileReader(file), "spring:message");

					String line = null;

					while ((line = reader.readLine()) != null) {

						int positionTagStart = line.indexOf("<spring:message");
						int positionTagEnd = line.indexOf("/>", positionTagStart);

						int positionCodeStart = line.indexOf("code=", positionTagStart);
						int positionCodeQuoteStart = line.indexOf("\"", positionCodeStart) + 1;
						int positionCodeQuoteEnd = line.indexOf("\"", positionCodeQuoteStart);

						int positionTextStart = line.indexOf("text=", positionTagStart);
						int positionTextQuoteStart = line.indexOf("\"!", positionTextStart) + 2;
						int positionTextQuoteEnd = line.indexOf("\"", positionTextQuoteStart);

						if (positionCodeQuoteEnd < positionTagEnd && positionTextQuoteEnd < positionTagEnd) {

							String code = line.substring(positionCodeQuoteStart, positionCodeQuoteEnd);
							String text = line.substring(positionTextQuoteStart, positionTextQuoteEnd);

							addResource(resourceItems, new ResourceItem(code, text));
						}
					}

					reader.close();
				}
				catch (FileNotFoundException exception) {

					exception.printStackTrace();
				}
				catch (IOException exception) {

					exception.printStackTrace();
				}

			}
		}
	}

	private void addResource(List  resourceItems, ResourceItem resourceItem) {

		boolean unique = true;
		
		for (ResourceItem item : resourceItems) {
			
			if (item.getKey().equals(resourceItem.getKey())) {
				
				unique = false;
				break;
			}
		}
		
		if (unique)
			resourceItems.add(resourceItem);
	}
	
	private class ResourceItem {

		private String key;
		private String value;

		public ResourceItem() {

			key = null;
			value = null;
		}

		public ResourceItem(String key, String value) {

			super();
			this.key = key;
			this.value = value;
		}

		public String getKey() {

			return key;
		}

		public void setKey(String key) {

			this.key = key;
		}

		public String getValue() {

			return value;
		}

		public void setValue(String value) {

			this.value = value;
		}

	}

	private class IndexOfReader extends BufferedReader {

		private String criteria;

		public IndexOfReader(Reader reader, String criteria) {

			super(reader);
			this.criteria = criteria;
		}

		@Override
		public String readLine() throws IOException {

			String line = null;
			
			do {
				
				line = super.readLine();
			}
			while ((line != null) && line.indexOf(criteria) == -1);
			
			if (line != null)
				return line.trim();
			
			return line;
		}

	}
	
}