diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/Template.java b/common/source/java/ch/systemsx/cisd/common/utilities/Template.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ea484ada7d5fa83df69e9090d0d37265b787dae
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/Template.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.utilities;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A little template engine. Usage example:
+ * <pre>
+ * Template template = new Template("Hello ${name}!");
+ * template.bind("name", "world");
+ * String text = template.createText();
+ * </pre>
+ * The method {@link #bind(String, String)} throws an exception if the placeholder name is unknown.
+ * The method {@link #createText()} throws an exception if not all placeholders have been bound.
+ * <p>
+ * Since placeholder bindings change the state of an instance of this class there is method {@link #createFreshCopy()}
+ * which creates a copy without reparsing the template. Usage example:
+ * <pre>
+ * static final Template TEMPLATE = new Template("Hello ${name}!");
+ * 
+ * void doSomething()
+ * {
+ *   Template template = TEMPLATE.createFreshCopy();
+ *   template.bind("name", "world");
+ *   String text = template.createText();
+ * }
+ * </pre>
+ * 
+ * @author Franz-Josef Elmer
+ */
+public class Template
+{
+    private static final char PLACEHOLDER_ESCAPE_CHARACTER = '$';
+    private static final char PLACEHOLDER_START_CHARACTER = '{';
+    private static final char PLACEHOLDER_END_CHARACTER = '}';
+    
+    private static final String createPlaceholder(String variableName)
+    {
+        return PLACEHOLDER_ESCAPE_CHARACTER + (PLACEHOLDER_START_CHARACTER + variableName) + PLACEHOLDER_END_CHARACTER;
+    }
+    
+    private static interface IToken
+    {
+        public void appendTo(StringBuilder builder);
+    }
+    
+    private static final class PlainToken implements IToken
+    {
+        private final String plainText;
+        
+        PlainToken(String plainText)
+        {
+            assert plainText != null : "Unspecified plain text.";
+            this.plainText = plainText;
+        }
+        
+        public void appendTo(StringBuilder builder)
+        {
+            builder.append(plainText);
+        }
+    }
+    
+    private static final class VariableToken implements IToken
+    {
+        private final String variableName;
+        
+        private String value;
+        
+        VariableToken(String variablePlaceHolder)
+        {
+            assert variablePlaceHolder != null : "Unspecified variable place holder.";
+            this.variableName = variablePlaceHolder;
+        }
+
+        public void appendTo(StringBuilder builder)
+        {
+            builder.append(isBound() ? value : createPlaceholder(variableName));
+        }
+        
+        String getVariableName()
+        {
+            return variableName;
+        }
+
+        boolean isBound()
+        {
+            return value != null;
+        }
+        
+        void bind(String v)
+        {
+            this.value = v;
+        }
+    }
+ 
+    private static enum State
+    {
+        PLAIN()
+        {
+            @Override
+            State next(char character, TokenBuilder tokenBuilder)
+            {
+                if (character == PLACEHOLDER_ESCAPE_CHARACTER)
+                {
+                    return STARTING_PLACEHOLDER;
+                }
+                tokenBuilder.addCharacter(character);
+                return PLAIN;
+            }
+        },
+        
+        STARTING_PLACEHOLDER()
+        {
+            @Override
+            State next(char character, TokenBuilder tokenBuilder)
+            {
+                switch (character)
+                {
+                    case PLACEHOLDER_ESCAPE_CHARACTER: 
+                        tokenBuilder.addCharacter(PLACEHOLDER_ESCAPE_CHARACTER);
+                        return PLAIN;
+                    case PLACEHOLDER_START_CHARACTER:
+                        tokenBuilder.finishPlainToken();
+                        return PLACEHOLDER;
+                    default:
+                        tokenBuilder.addCharacter(PLACEHOLDER_ESCAPE_CHARACTER);
+                        tokenBuilder.addCharacter(character);
+                        return PLAIN;
+                }
+            }
+        },
+        
+        PLACEHOLDER()
+        {
+            @Override
+            State next(char character, TokenBuilder tokenBuilder)
+            {
+                if (character == PLACEHOLDER_END_CHARACTER)
+                {
+                    tokenBuilder.finishPlaceholder();
+                    return PLAIN;
+                }
+                tokenBuilder.addCharacter(character);
+                return PLACEHOLDER;
+            }
+        };
+        
+        abstract State next(char character, TokenBuilder tokenBuilder);
+    }
+    
+    private static final class TokenBuilder
+    {
+        private final Map<String, VariableToken> variableTokens;
+        private final List<IToken> tokens;
+        private final StringBuilder builder;
+
+        TokenBuilder(Map<String, VariableToken> variableTokens, List<IToken> tokens)
+        {
+            this.variableTokens = variableTokens;
+            this.tokens = tokens;
+            builder = new StringBuilder();
+        }
+
+        public void addCharacter(char character)
+        {
+            builder.append(character);
+        }
+        
+        public void finishPlainToken()
+        {
+            if (builder.length() > 0)
+            {
+                tokens.add(new PlainToken(builder.toString()));
+                builder.setLength(0);
+            }
+        }
+
+        public void finishPlaceholder()
+        {
+            String variableName = builder.toString();
+            if (variableName.length() == 0)
+            {
+                throw new IllegalArgumentException("Nameless placeholder " + createPlaceholder("") + " found.");
+            }
+            VariableToken token = variableTokens.get(variableName);
+            if (token == null)
+            {
+                token = new VariableToken(variableName);
+                variableTokens.put(variableName, token);
+            }
+            tokens.add(token);
+            builder.setLength(0);
+        }
+    }
+
+    private final Map<String, VariableToken> variableTokens;
+    private final List<IToken> tokens;
+    
+    /**
+     * Creates a new instance for the specified template.
+     *
+     * @throws IllegalArgumentException if some error occurred during parsing.
+     */
+    public Template(String template)
+    {
+        this(new LinkedHashMap<String, VariableToken>(), new ArrayList<IToken>());
+        assert template != null : "Unspecified template.";
+        
+        TokenBuilder tokenBuilder = new TokenBuilder(variableTokens, tokens);
+        State state = State.PLAIN;
+        for (int i = 0, n = template.length(); i < n; i++)
+        {
+            state = state.next(template.charAt(i), tokenBuilder);
+        }
+        if (state != State.PLAIN)
+        {
+            throw new IllegalArgumentException("Incomplete placeholder detected at the end.");
+        }
+        tokenBuilder.finishPlainToken();
+    }
+    
+    private Template(Map<String, VariableToken> variableTokens, List<IToken> tokens)
+    {
+        this.variableTokens = variableTokens;
+        this.tokens = tokens;
+    }
+    
+    /**
+     * Creates a copy of this template with no variable bindings.
+     */
+    public Template createFreshCopy()
+    {
+        LinkedHashMap<String, VariableToken> map = new LinkedHashMap<String, VariableToken>();
+        ArrayList<IToken> list = new ArrayList<IToken>();
+        for (IToken token : tokens)
+        {
+            if (token instanceof VariableToken)
+            {
+                String variableName = ((VariableToken) token).getVariableName();
+                VariableToken variableToken = new VariableToken(variableName);
+                map.put(variableName, variableToken);
+                list.add(variableToken);
+            } else
+            {
+                list.add(token);
+            }
+        }
+        return new Template(map, list);
+    }
+    
+
+    /**
+     * Binds the specified value to the specified placeholder name.
+     * 
+     * @throws IllegalArgumentException if placeholder is not known.
+     */
+    public void bind(String placeholderName, String value)
+    {
+        assert placeholderName != null : "Unspecified placeholder name.";
+        assert value != null : "Unspecified value for '" + placeholderName + "'";
+        
+        VariableToken variableToken = variableTokens.get(placeholderName);
+        if (variableToken == null)
+        {
+            throw new IllegalArgumentException("Unknown variable '" + placeholderName + "'."); 
+        }
+        variableToken.bind(value);
+    }
+    
+    /**
+     * Creates the text by using all placeholder bindings.
+     * 
+     * @throws IllegalStateException if not all placeholders have been bound.
+     */
+    public String createText()
+    {
+        return createText(true);
+    }
+    
+    /**
+     * Creates the text by using placeholder bindings.
+     * 
+     * @param complete If <code>true</code> an {@link IllegalStateException} will be thrown if not all
+     *      bindings are set.
+     */
+    public String createText(boolean complete)
+    {
+        if (complete)
+        {
+            assertAllVariablesAreBound();
+        }
+        StringBuilder builder = new StringBuilder();
+        for (IToken token : tokens)
+        {
+            token.appendTo(builder);
+        }
+        return builder.toString();
+    }
+
+    private void assertAllVariablesAreBound()
+    {
+        StringBuilder builder = new StringBuilder();
+        for (Map.Entry<String, VariableToken> entry : variableTokens.entrySet())
+        {
+            if (entry.getValue().isBound() == false)
+            {
+                builder.append(entry.getKey()).append(' ');
+            }
+        }
+        if (builder.length() > 0)
+        {
+            throw new IllegalStateException("The following variables are not bound: " + builder);
+        }
+    }
+}
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/TemplateTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/TemplateTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..abba46a01e37100df4c02c60e7e6c699f7128bf2
--- /dev/null
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/TemplateTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.utilities;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.fail;
+
+import org.testng.annotations.Test;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class TemplateTest
+{
+    @Test
+    public void testEmptyTemplate()
+    {
+        assertEquals("", new Template("").createText());
+    }
+    
+    @Test
+    public void testWithoutPlaceholders()
+    {
+        assertEquals("hello", new Template("hello").createText());
+    }
+    
+    @Test
+    public void testWithOnePlaceholder()
+    {
+        Template template = new Template("hello ${name}!");
+        template.bind("name", "world");
+        assertEquals("hello world!", template.createText());
+    }
+    
+    @Test
+    public void testWithTwiceTheSamePlaceholder()
+    {
+        Template template = new Template("hello ${name}${name}");
+        template.bind("name", "world");
+        assertEquals("hello worldworld", template.createText());
+    }
+    
+    @Test
+    public void testWithTwoPlaceholders()
+    {
+        Template template = new Template("hello ${name}, do you know ${name2}?");
+        template.bind("name", "world");
+        template.bind("name2", "Albert Einstein");
+        assertEquals("hello world, do you know Albert Einstein?", template.createText());
+    }
+    
+    @Test
+    public void testWithEscaping()
+    {
+        Template template = new Template("hello $${name}. I have 25$.");
+        assertEquals("hello ${name}. I have 25$.", template.createText());
+    }
+    
+    @Test
+    public void testNamelessPlaceholderInTemplate()
+    {
+        try
+        {
+            new Template("hello ${}");
+            fail("IllegalArgumentException expected.");
+        } catch (IllegalArgumentException e)
+        {
+            assertEquals("Nameless placeholder ${} found.", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void testUnfinishedPlaceholderInTemplate()
+    {
+        try
+        {
+            new Template("hello ${name");
+            fail("IllegalArgumentException expected.");
+        } catch (IllegalArgumentException e)
+        {
+            assertEquals("Incomplete placeholder detected at the end.", e.getMessage());
+        }
+        try
+        {
+            new Template("hello ${");
+            fail("IllegalArgumentException expected.");
+        } catch (IllegalArgumentException e)
+        {
+            assertEquals("Incomplete placeholder detected at the end.", e.getMessage());
+        }
+        try
+        {
+            new Template("hello $");
+            fail("IllegalArgumentException expected.");
+        } catch (IllegalArgumentException e)
+        {
+            assertEquals("Incomplete placeholder detected at the end.", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void testBindUnknownPlaceholder()
+    {
+        Template template = new Template("hello ${name}!");
+        try
+        {
+            template.bind("blabla", "blub");
+            fail("IllegalArgumentException expected.");
+        } catch (IllegalArgumentException e)
+        {
+            assertEquals("Unknown variable 'blabla'.", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void testIncompleteBinding()
+    {
+        Template template = new Template("${greeting} ${name}!");
+        try
+        {
+            template.createText(true);
+            fail("IllegalStateException expected");
+        } catch (IllegalStateException e)
+        {
+            assertEquals("The following variables are not bound: greeting name ", e.getMessage());
+        }
+        
+        template.bind("greeting", "hello");
+        assertEquals("hello ${name}!", template.createText(false));
+        try
+        {
+            template.createText(true);
+            fail("IllegalStateException expected");
+        } catch (IllegalStateException e)
+        {
+            assertEquals("The following variables are not bound: name ", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void testCreateFreshCopy()
+    {
+        Template template = new Template("hello ${name}!");
+        Template template1 = template.createFreshCopy();
+        template1.bind("name", "world");
+        assertEquals("hello world!", template1.createText());
+        assertEquals("hello ${name}!", template.createText(false));
+        
+        Template template2 = template.createFreshCopy();
+        template2.bind("name", "universe");
+        assertEquals("hello universe!", template2.createText());
+        assertEquals("hello world!", template1.createText());
+        assertEquals("hello ${name}!", template.createText(false));
+    }
+}