From 5ea3a7b0fbabee37dd13f16a948849f39bfbbe99 Mon Sep 17 00:00:00 2001
From: brinn <brinn>
Date: Thu, 15 Nov 2012 13:23:42 +0000
Subject: [PATCH] Add support for extracting placeholder metadata and for
 manipulating tokens.

SVN: 27634
---
 .../systemsx/cisd/common/string/Template.java | 338 +++++++++++++++++-
 .../cisd/common/string/TemplateTest.java      |  49 ++-
 2 files changed, 373 insertions(+), 14 deletions(-)

diff --git a/common/source/java/ch/systemsx/cisd/common/string/Template.java b/common/source/java/ch/systemsx/cisd/common/string/Template.java
index fccae3222d4..264a333eb5d 100644
--- a/common/source/java/ch/systemsx/cisd/common/string/Template.java
+++ b/common/source/java/ch/systemsx/cisd/common/string/Template.java
@@ -22,6 +22,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.commons.lang.StringUtils;
+
 /**
  * A little template engine. Usage example:
  * 
@@ -68,14 +70,82 @@ public class Template
                 + PLACEHOLDER_END_CHARACTER;
     }
 
-    private static interface IToken
+    public static interface IToken
     {
+        /**
+         * Append the token's value to the <var>builder</var>.
+         */
         public void appendTo(StringBuilder builder);
+
+        /**
+         * Returns <code>true</code> if this token is a variable token, <code>false</code>
+         * otherwise.
+         */
+        public boolean isVariable();
+
+        /**
+         * Returns the name of the variable for a variable token, or <code>null</code> otherwise.
+         */
+        public String tryGetName();
+
+        /**
+         * Returns the index of the variable for a variable token, or -1 otherwise.
+         */
+        public int getVariableIndex();
+
+        /**
+         * Returns the value of the token or <code>null</code> if this is a variable token whose
+         * value has not yet been set.
+         */
+        public String tryGetValue();
+
+        /**
+         * Returns the metadata of the token for a variable token, or <code>null</code> otherwise.
+         */
+        public String tryGetMetadata();
+
+        /**
+         * Sets the value of the token.
+         */
+        public void setValue(String value);
+
+        /**
+         * Sets the value of the token to a substring given by <var>start</var> and
+         * <code>value.length() - indexFromEnd</code> and add <var>prefix</var> and
+         * <var>suffix</var>.
+         */
+        public void setSubString(int start, int indexFromEnd, String prefix, String suffix);
+
+        /**
+         * Returns the prefix for a variable token, or <code>null</code> if no prefix has been set
+         * or it is not a variable token.
+         */
+        public String tryGetPrefix();
+
+        /**
+         * Returns the suffix for a variable token, or <code>null</code> if no prefix has been set
+         * or it is not a variable token.
+         */
+        public String tryGetSuffix();
+
+        /**
+         * Returns <code>false</code> for a variable token that is not yet bound, <code>true</code>
+         * otherwise.
+         */
+        public boolean isBound();
+
+        /**
+         * Binds the given <var>value</var> to a variable token.
+         * 
+         * @throw {@link UnsupportedOperationException} if this token is not a variable token.
+         */
+        public void bind(String value) throws UnsupportedOperationException;
+
     }
 
-    private static final class PlainToken implements IToken
+    public static final class PlainToken implements IToken
     {
-        private final String plainText;
+        private String plainText;
 
         PlainToken(String plainText)
         {
@@ -88,48 +158,181 @@ public class Template
         {
             builder.append(plainText);
         }
+
+        @Override
+        public String tryGetName()
+        {
+            return null;
+        }
+
+        @Override
+        public String tryGetMetadata()
+        {
+            return null;
+        }
+
+        @Override
+        public String tryGetValue()
+        {
+            return plainText;
+        }
+
+        @Override
+        public void setValue(String value)
+        {
+            this.plainText = value;
+        }
+
+        @Override
+        public void setSubString(int start, int indexFromEnd, String prefix, String suffix)
+        {
+            this.plainText =
+                    prefix + StringUtils.substring(plainText, start, StringUtils.length(plainText)
+                            - indexFromEnd) + suffix;
+        }
+
+        @Override
+        public String tryGetPrefix()
+        {
+            return null;
+        }
+
+        @Override
+        public String tryGetSuffix()
+        {
+            return null;
+        }
+
+        @Override
+        public boolean isVariable()
+        {
+            return false;
+        }
+
+        @Override
+        public int getVariableIndex()
+        {
+            return -1;
+        }
+
+        @Override
+        public boolean isBound()
+        {
+            return true;
+        }
+
+        @Override
+        public void bind(String value) throws UnsupportedOperationException
+        {
+            throw new UnsupportedOperationException();
+        }
+
     }
 
-    private static final class VariableToken implements IToken
+    public static final class VariableToken implements IToken
     {
         private final String variableName;
 
+        private final String variableMetadata;
+
         private final int variableIndex;
 
+        private String prefix;
+
+        private String suffix;
+
         private String value;
 
-        VariableToken(String variablePlaceHolder, int variableIndex)
+        VariableToken(String variablePlaceHolder, String variableMetadata, int variableIndex)
         {
             assert variablePlaceHolder != null : "Unspecified variable place holder.";
             this.variableName = variablePlaceHolder;
+            this.variableMetadata = StringUtils.isEmpty(variableMetadata) ? null : variableMetadata;
             this.variableIndex = variableIndex;
         }
 
         @Override
         public void appendTo(StringBuilder builder)
         {
+            if (prefix != null)
+            {
+                builder.append(prefix);
+            }
             builder.append(isBound() ? value : createPlaceholder(variableName));
+            if (suffix != null)
+            {
+                builder.append(suffix);
+            }
         }
 
-        String getVariableName()
+        @Override
+        public String tryGetName()
         {
             return variableName;
         }
 
-        boolean isBound()
+        @Override
+        public String tryGetMetadata()
+        {
+            return variableMetadata;
+        }
+
+        @Override
+        public String tryGetValue()
+        {
+            return ((prefix != null) ? prefix : "") + value + ((suffix != null) ? suffix : "");
+        }
+
+        @Override
+        public void setValue(String value)
+        {
+            bind(value);
+        }
+
+        @Override
+        public void setSubString(int start, int indexFromEnd, String prefix, String suffix)
+        {
+            bind(StringUtils.substring(value, start, StringUtils.length(value) - indexFromEnd));
+            this.prefix = prefix;
+            this.suffix = suffix;
+        }
+
+        @Override
+        public String tryGetPrefix()
+        {
+            return prefix;
+        }
+
+        @Override
+        public String tryGetSuffix()
+        {
+            return suffix;
+        }
+
+        @Override
+        public boolean isVariable()
+        {
+            return true;
+        }
+
+        @Override
+        public boolean isBound()
         {
             return value != null;
         }
 
-        void bind(String v)
+        @Override
+        public void bind(String v)
         {
             this.value = v;
         }
 
-        int getVariableIndex()
+        @Override
+        public int getVariableIndex()
         {
             return variableIndex;
         }
+
     }
 
     private static enum State
@@ -214,6 +417,7 @@ public class Template
                     tokenBuilder.finishPlaceholder();
                     return PLAIN;
                 }
+                tokenBuilder.addMetadataCharacter(character);
                 return PLACEHOLDER_METADATA;
             }
         };
@@ -229,6 +433,8 @@ public class Template
 
         private final StringBuilder builder;
 
+        private final StringBuilder metadataBuilder;
+
         private int index;
 
         TokenBuilder(Map<String, VariableToken> variableTokens, List<IToken> tokens)
@@ -236,6 +442,7 @@ public class Template
             this.variableTokens = variableTokens;
             this.tokens = tokens;
             builder = new StringBuilder();
+            metadataBuilder = new StringBuilder();
         }
 
         public void addCharacter(char character)
@@ -243,6 +450,11 @@ public class Template
             builder.append(character);
         }
 
+        public void addMetadataCharacter(char character)
+        {
+            metadataBuilder.append(character);
+        }
+
         public void finishPlainToken()
         {
             if (builder.length() > 0)
@@ -255,6 +467,7 @@ public class Template
         public void finishPlaceholder()
         {
             String variableName = builder.toString();
+            String variableMetadata = metadataBuilder.toString();
             if (variableName.length() == 0)
             {
                 throw new IllegalArgumentException("Nameless placeholder " + createPlaceholder("")
@@ -263,11 +476,12 @@ public class Template
             VariableToken token = variableTokens.get(variableName);
             if (token == null)
             {
-                token = new VariableToken(variableName, index++);
+                token = new VariableToken(variableName, variableMetadata, index++);
                 variableTokens.put(variableName, token);
             }
             tokens.add(token);
             builder.setLength(0);
+            metadataBuilder.setLength(0);
         }
     }
 
@@ -313,16 +527,17 @@ public class Template
         LinkedHashMap<String, VariableToken> map = new LinkedHashMap<String, VariableToken>();
         for (VariableToken variableToken : variableTokens.values())
         {
-            final String variableName = variableToken.getVariableName();
+            final String variableName = variableToken.tryGetName();
+            final String variableMetadata = variableToken.tryGetMetadata();
             final int variableIndex = variableToken.getVariableIndex();
-            map.put(variableName, new VariableToken(variableName, variableIndex));
+            map.put(variableName, new VariableToken(variableName, variableMetadata, variableIndex));
         }
         ArrayList<IToken> list = new ArrayList<IToken>();
         for (IToken token : tokens)
         {
             if (token instanceof VariableToken)
             {
-                list.add(map.get(((VariableToken) token).getVariableName()));
+                list.add(map.get(((VariableToken) token).tryGetName()));
             } else
             {
                 list.add(token);
@@ -339,6 +554,14 @@ public class Template
         return variableTokens.keySet();
     }
 
+    /**
+     * Returns all tokens of this template.
+     */
+    public List<IToken> getTokens()
+    {
+        return tokens;
+    }
+
     /**
      * Binds the specified value to the specified placeholder name.
      * 
@@ -387,6 +610,95 @@ public class Template
         return variableToken.getVariableIndex();
     }
 
+    /**
+     * Returns the metadata of the given <var>placeholderName</var>, or <code>null</code>, if the
+     * place holder name cannot be found in the template or there are no metadata for this
+     * placeholder.
+     */
+    public String tryGetMetadata(String placeholderName)
+    {
+        assert placeholderName != null : "Unspecified placeholder name.";
+        VariableToken variableToken = variableTokens.get(placeholderName);
+        if (variableToken == null)
+        {
+            return null;
+        }
+        return variableToken.tryGetMetadata();
+    }
+
+    /**
+     * Look through all tokens for variable tokens which are surrounded by plain text tokens which
+     * end / start with the given <var>oldOpeningBracket</var> /
+     * <var>oldClosingBracket</var>. If that is the case, replace these brackets with
+     * <var>newOpeningBracket</var> / <var>newClosingBracket</var>.
+     * 
+     * @return The variable tokens where brackets have been replaced.
+     */
+    public List<IToken> replaceBrackets(String oldOpeningBracket, String oldClosingBracket,
+            String newOpeningBracket, String newClosingBracket)
+    {
+        final List<IToken> tokensReplaced = new ArrayList<IToken>();
+        for (int i = 1; i < tokens.size() - 1; ++i)
+        {
+            if (tokens.get(i).isVariable() && tokenEndsWith(i - 1, oldOpeningBracket)
+                    && tokenStartsWith(i + 1, oldClosingBracket))
+            {
+                tokens.get(i - 1).setSubString(0, oldOpeningBracket.length(), "",
+                        newOpeningBracket);
+                tokens.get(i + 1).setSubString(oldClosingBracket.length(), 0,
+                        newClosingBracket, "");
+                tokensReplaced.add(tokens.get(i));
+            }
+        }
+        return tokensReplaced;
+    }
+
+    /**
+     * Returns the left neighbor token of the variable token given by <var>variableName</var>, or
+     * <code>null</code>, if this variable token is not found or doesn't have a left neighbor.
+     */
+    public IToken tryGetLeftNeighbor(String variableName)
+    {
+        for (int i = 1; i < tokens.size() - 1; ++i)
+        {
+            if (tokens.get(i).isVariable()
+                    && StringUtils.equals(tokens.get(i).tryGetName(), variableName))
+            {
+                return tokens.get(i - 1);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the right neighbor token of the variable token given by <var>variableName</var>, or
+     * <code>null</code>, if this variable token is not found or doesn't have a right neighbor.
+     */
+    public IToken tryGetRightNeighbor(String variableName)
+    {
+        for (int i = 1; i < tokens.size() - 1; ++i)
+        {
+            if (tokens.get(i).isVariable()
+                    && StringUtils.equals(tokens.get(i).tryGetName(), variableName))
+            {
+                return tokens.get(i + 1);
+            }
+        }
+        return null;
+    }
+
+    private boolean tokenStartsWith(int tokenInx, String text)
+    {
+        final IToken token = tokens.get(tokenInx);
+        return (token.isVariable() == false && StringUtils.startsWith(token.tryGetValue(), text));
+    }
+
+    private boolean tokenEndsWith(int tokenInx, String text)
+    {
+        final IToken token = tokens.get(tokenInx);
+        return (token.isVariable() == false && StringUtils.endsWith(token.tryGetValue(), text));
+    }
+
     /**
      * Creates the text by using all placeholder bindings.
      * 
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/string/TemplateTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/string/TemplateTest.java
index 78e27c91538..c2770bba7b0 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/string/TemplateTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/string/TemplateTest.java
@@ -17,13 +17,16 @@
 package ch.systemsx.cisd.common.string;
 
 import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.fail;
 
+import java.util.List;
 import java.util.Set;
 
+import org.apache.commons.lang.StringUtils;
 import org.testng.annotations.Test;
 
-import ch.systemsx.cisd.common.string.Template;
+import ch.systemsx.cisd.common.string.Template.IToken;
 
 /**
  * Test cases for the {@link Template}.
@@ -53,11 +56,55 @@ public class TemplateTest
         assertEquals("hello world!", template.createText());
     }
 
+    @Test
+    public void testWithOnePlaceholderManipulateTokens()
+    {
+        Template template = new Template("hello ${name}!");
+        assertEquals(0, template.tryGetIndex("name"));
+        for (IToken token : template.getTokens())
+        {
+            if (token.isVariable())
+            {
+                token.setSubString(0, 0, "'", "'");
+            } else
+            {
+                if (StringUtils.endsWith(token.tryGetValue(), " "))
+                {
+                    token.setValue(token.tryGetValue().substring(0, token.tryGetValue().length() - 1));
+                }
+            }
+        }
+        template.bind("name", "world");
+        assertEquals("hello'world'!", template.createText());
+    }
+
+    @Test
+    public void testWithTwoPlaceholdersReplaceBrackets()
+    {
+        Template template = new Template("hello (${name}) (${name2}) {${name3}}");
+        assertEquals(0, template.tryGetIndex("name"));
+        assertEquals(1, template.tryGetIndex("name2"));
+        final List<IToken> replaced1 = template.replaceBrackets("(",  ")",  "[", "]");
+        assertEquals(2, replaced1.size());
+        assertEquals("name", replaced1.get(0).tryGetName());
+        assertEquals("name2", replaced1.get(1).tryGetName());
+        final List<IToken> replaced2 = template.replaceBrackets("{",  "}",  "", "");
+        assertEquals(1, replaced2.size());
+        assertEquals("name3", replaced2.get(0).tryGetName());
+        template.bind("name", "world");
+        template.bind("name2", "universe");
+        template.bind("name3", "???");
+        assertEquals("hello [world] [universe] ???", template.createText());
+    }
+
     @Test
     public void testWithPlaceholderMetadata()
     {
         Template template =
                 new Template("hello ${name::some metadata} - welcome to ${CISD:shortcut}!");
+        assertEquals("some metadata", template.tryGetMetadata("name"));
+        assertNull(template.tryGetMetadata("CISD"));
+        assertNull(template.tryGetMetadata("CISD:shortcut"));
         template.bind("name", "world");
         template.bind("CISD:shortcut", "Center for Sciences and Databases");
         assertEquals("hello world - welcome to Center for Sciences and Databases!",
-- 
GitLab