svn commit: r904592 - in /ofbiz/trunk/framework/base/src/org/ofbiz/base: test/BaseUnitTests.java util/string/FlexibleStringExpander.java

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r904592 - in /ofbiz/trunk/framework/base/src/org/ofbiz/base: test/BaseUnitTests.java util/string/FlexibleStringExpander.java

adrianc
Author: adrianc
Date: Fri Jan 29 19:03:43 2010
New Revision: 904592

URL: http://svn.apache.org/viewvc?rev=904592&view=rev
Log:
FlexibleStringExpander.java improvements and new unit tests. The main purpose of the change is to reduce the memory footprint of non-cached instances (more than 78,000 at this time).

Two small functional changes:

1. Elminated the deprecated "env." syntax support.
2. Added support for runtime expression nesting.

Modified:
    ofbiz/trunk/framework/base/src/org/ofbiz/base/test/BaseUnitTests.java
    ofbiz/trunk/framework/base/src/org/ofbiz/base/util/string/FlexibleStringExpander.java

Modified: ofbiz/trunk/framework/base/src/org/ofbiz/base/test/BaseUnitTests.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/src/org/ofbiz/base/test/BaseUnitTests.java?rev=904592&r1=904591&r2=904592&view=diff
==============================================================================
--- ofbiz/trunk/framework/base/src/org/ofbiz/base/test/BaseUnitTests.java (original)
+++ ofbiz/trunk/framework/base/src/org/ofbiz/base/test/BaseUnitTests.java Fri Jan 29 19:03:43 2010
@@ -18,6 +18,8 @@
  *******************************************************************************/
 package org.ofbiz.base.test;
 
+import java.util.HashMap;
+import java.util.Map;
 import junit.framework.TestCase;
 
 import org.ofbiz.base.conversion.*;
@@ -25,6 +27,7 @@
 import org.ofbiz.base.util.Debug;
 import org.ofbiz.base.util.UtilFormatOut;
 import org.ofbiz.base.util.UtilValidate;
+import org.ofbiz.base.util.string.FlexibleStringExpander;
 
 public class BaseUnitTests extends TestCase {
 
@@ -99,6 +102,32 @@
         } catch (IllegalArgumentException e) {}
     }
 
+    public void testFlexibleStringExpander() {
+        FlexibleStringExpander fse = FlexibleStringExpander.getInstance(null);
+        assertTrue("null FlexibleStringExpander", fse.isEmpty());
+        String compare = "Hello World!";
+        fse = FlexibleStringExpander.getInstance(compare);
+        assertEquals("null context", compare, fse.expandString(null));
+        Map<String, Object> testMap = new HashMap<String, Object>();
+        testMap.put("var", "World");
+        fse = FlexibleStringExpander.getInstance("Hello ${var}!");
+        assertTrue("simple replacement", compare.equals(fse.expandString(testMap)));
+        testMap.put("nested", "Hello ${var}");
+        fse = FlexibleStringExpander.getInstance("${nested}!");
+        assertTrue("hidden (runtime) nested replacement", compare.equals(fse.expandString(testMap)));
+        fse = FlexibleStringExpander.getInstance("${'Hello ${var}'}!");
+        assertTrue("visible nested replacement", compare.equals(fse.expandString(testMap)));
+        fse = FlexibleStringExpander.getInstance("${bsh:return \"Hello \" + var + \"!\";}");
+        assertTrue("bsh: script", compare.equals(fse.expandString(testMap)));
+        fse = FlexibleStringExpander.getInstance("${groovy:return \"Hello \" + var + \"!\";}");
+        assertTrue("groovy: script", compare.equals(fse.expandString(testMap)));
+        // It is assumed the UEL library will have its own unit tests, but we
+        // will do one UEL expression test to check the UEL integration.
+        testMap.put("testMap", testMap);
+        fse = FlexibleStringExpander.getInstance("Hello ${testMap.var}!");
+        assertTrue("UEL integration", compare.equals(fse.expandString(testMap)));
+    }
+
     public void testDateTimeConverters() {
         // Source class = java.util.Date
         java.util.Date utilDate = new java.util.Date();

Modified: ofbiz/trunk/framework/base/src/org/ofbiz/base/util/string/FlexibleStringExpander.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/src/org/ofbiz/base/util/string/FlexibleStringExpander.java?rev=904592&r1=904591&r2=904592&view=diff
==============================================================================
--- ofbiz/trunk/framework/base/src/org/ofbiz/base/util/string/FlexibleStringExpander.java (original)
+++ ofbiz/trunk/framework/base/src/org/ofbiz/base/util/string/FlexibleStringExpander.java Fri Jan 29 19:03:43 2010
@@ -21,7 +21,6 @@
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.TimeZone;
@@ -51,126 +50,13 @@
  * nested expressions.</p>
  */
 @SuppressWarnings("serial")
-public class FlexibleStringExpander implements Serializable {
+public abstract class FlexibleStringExpander implements Serializable {
 
     public static final String module = FlexibleStringExpander.class.getName();
     public static final String openBracket = "${";
     public static final String closeBracket = "}";
     protected static final UtilCache<String, FlexibleStringExpander> exprCache = UtilCache.createUtilCache("flexibleStringExpander.ExpressionCache");
-    protected static final FlexibleStringExpander nullExpr = new FlexibleStringExpander(null);
-    protected final String orig;
-    protected final List<StrElem> strElems;
-    protected int hint = 20;
-
-    /**
-     * @param original
-     */
-    protected FlexibleStringExpander(String original) {
-        this.orig = original;
-        if (original != null && original.contains(openBracket)) {
-            this.strElems = getStrElems(original);
-            if (original.length() > this.hint) {
-                this.hint = original.length();
-            }
-        } else {
-            this.strElems = null;
-        }
-    }
-
-    public boolean isEmpty() {
-        return this.orig == null || this.orig.length() == 0;
-    }
-
-    public String getOriginal() {
-        return this.orig;
-    }
-
-    /** This expands the pre-parsed String given the context passed in. A
-     * null context argument will return the original String.
-     * @param context A context Map containing the variable values
-     * @return The original String expanded by replacing varaible place holders.
-     */
-    public String expandString(Map<String, ? extends Object> context) {
-        return this.expandString(context, null, null);
-    }
-
-    /** This expands the pre-parsed String given the context passed in. A
-     * null context argument will return the original String.
-     * @param context A context Map containing the variable values
-     * @param locale the current set locale
-     * @return The original String expanded by replacing varaible place holders.
-     */
-    public String expandString(Map<String, ? extends Object> context, Locale locale) {
-        return this.expandString(context, null, locale);
-    }
-
-    /** This expands the pre-parsed String given the context passed in. A
-     * null context argument will return the original String.
-     * @param context A context Map containing the variable values
-     * @param timeZone the current set time zone
-     * @param locale the current set locale
-     * @return The original String expanded by replacing varaible place holders.
-     */
-    public String expandString(Map<String, ? extends Object> context, TimeZone timeZone, Locale locale) {
-        if (this.strElems == null || context == null) {
-            return this.orig == null ? "" : this.orig;
-        }
-        if (locale == null) {
-            locale = (Locale) context.get("locale");
-            if (locale == null && context.containsKey("autoUserLogin")) {
-                Map<String, Object> autoUserLogin = UtilGenerics.cast(context.get("autoUserLogin"));
-                locale = UtilMisc.ensureLocale(autoUserLogin.get("lastLocale"));
-            }
-            if (locale == null) {
-                locale = Locale.getDefault();
-            }
-        }
-        if (timeZone == null) {
-            timeZone = (TimeZone) context.get("timeZone");
-            if (timeZone == null && context.containsKey("autoUserLogin")) {
-                Map<String, String> autoUserLogin = UtilGenerics.cast(context.get("autoUserLogin"));
-                timeZone = UtilDateTime.toTimeZone(autoUserLogin.get("lastTimeZone"));
-            }
-            if (timeZone == null) {
-                timeZone = TimeZone.getDefault();
-            }
-        }
-        StringBuilder buffer = new StringBuilder(this.hint);
-        for (StrElem elem : this.strElems) {
-            elem.append(buffer, context, timeZone, locale);
-        }
-        if (buffer.length() > this.hint) {
-            synchronized(this) {
-                this.hint = buffer.length();
-            }
-        }
-        return buffer.toString();
-    }
-
-    /** Returns a FlexibleStringExpander instance.
-     * @param original The original String expression
-     * @return A FlexibleStringExpander instance
-     */
-    public static FlexibleStringExpander getInstance(String original) {
-        if (UtilValidate.isEmpty(original)) {
-            return nullExpr;
-        }
-        // Remove the next three lines to cache all expressions
-        if (!original.contains(openBracket)) {
-            return new FlexibleStringExpander(original);
-        }
-        FlexibleStringExpander fse = exprCache.get(original);
-        if (fse == null) {
-            synchronized (exprCache) {
-                fse = exprCache.get(original);
-                if (fse == null) {
-                    fse = new FlexibleStringExpander(original);
-                    exprCache.put(original, fse);
-                }
-            }
-        }
-        return fse;
-    }
+    protected static final FlexibleStringExpander nullExpr = new ConstElem("");
 
     /** Does on-the-fly parsing and expansion of the original String using
      * variable values from the passed context. A null context argument will
@@ -209,21 +95,48 @@
         return fse.expandString(context, timeZone, locale);
     }
 
+    /** Returns a FlexibleStringExpander instance.
+     * @param original The original String expression
+     * @return A FlexibleStringExpander instance
+     */
+    public static FlexibleStringExpander getInstance(String original) {
+        if (UtilValidate.isEmpty(original)) {
+            return nullExpr;
+        }
+        // Remove the next three lines to cache all expressions
+        if (!original.contains(openBracket)) {
+            return new ConstElem(original);
+        }
+        FlexibleStringExpander fse = exprCache.get(original);
+        if (fse == null) {
+            synchronized (exprCache) {
+                FlexibleStringExpander[] strElems = getStrElems(original);
+                if (strElems.length == 1) {
+                    fse = strElems[0];
+                } else {
+                    fse = new Elements(original, strElems);
+                }
+                exprCache.put(original, fse);
+            }
+        }
+        return fse;
+    }
+
     /** Protected helper method.
      * @param original
      * @return a list of parsed string elements
      */
-    protected static List<StrElem> getStrElems(String original) {
+    protected static FlexibleStringExpander[] getStrElems(String original) {
         if (UtilValidate.isEmpty(original)) {
             return null;
         }
         int origLen = original.length();
-        ArrayList<StrElem> strElems = new ArrayList<StrElem>();
+        ArrayList<FlexibleStringExpander> strElems = new ArrayList<FlexibleStringExpander>();
         int start = original.indexOf(openBracket);
         if (start == -1) {
             strElems.add(new ConstElem(original));
             strElems.trimToSize();
-            return strElems;
+            return strElems.toArray(new FlexibleStringExpander[strElems.size()]);
         }
         int currentInd = 0;
         int end = -1;
@@ -279,31 +192,82 @@
             strElems.add(new ConstElem(original.substring(currentInd)));
         }
         strElems.trimToSize();
-        return strElems;
+        return strElems.toArray(new FlexibleStringExpander[strElems.size()]);
     }
 
-    protected static interface StrElem extends Serializable {
-        public void append(StringBuilder buffer, Map<String, ? extends Object> context, TimeZone timeZone, Locale locale);
+    // Note: a character array is used instead of a String to
+    // keep the memory footprint small.
+    protected final char[] orig;
+    protected int hint = 20;
+
+    protected FlexibleStringExpander(String original) {
+        this.orig = original.toCharArray();
+    }
+
+    protected abstract void append(StringBuilder buffer, Map<String, ? extends Object> context, TimeZone timeZone, Locale locale);
+
+    public String expandString(Map<String, ? extends Object> context) {
+        return this.expandString(context, null, null);
     }
 
-    protected static class ConstElem implements StrElem {
-        protected final String str;
-        protected ConstElem(String value) {
-            this.str = value.intern();
+    public String expandString(Map<String, ? extends Object> context, Locale locale) {
+        return this.expandString(context, null, locale);
+    }
+
+    public String expandString(Map<String, ? extends Object> context, TimeZone timeZone, Locale locale) {
+        if (context == null) {
+            return this.toString();
         }
-        public void append(StringBuilder buffer, Map<String, ? extends Object> context, TimeZone timeZone, Locale locale) {
-            buffer.append(this.str);
+        if (locale == null) {
+            locale = (Locale) context.get("locale");
+            if (locale == null && context.containsKey("autoUserLogin")) {
+                Map<String, Object> autoUserLogin = UtilGenerics.cast(context.get("autoUserLogin"));
+                locale = UtilMisc.ensureLocale(autoUserLogin.get("lastLocale"));
+            }
+            if (locale == null) {
+                locale = Locale.getDefault();
+            }
         }
+        if (timeZone == null) {
+            timeZone = (TimeZone) context.get("timeZone");
+            if (timeZone == null && context.containsKey("autoUserLogin")) {
+                Map<String, String> autoUserLogin = UtilGenerics.cast(context.get("autoUserLogin"));
+                timeZone = UtilDateTime.toTimeZone(autoUserLogin.get("lastTimeZone"));
+            }
+            if (timeZone == null) {
+                timeZone = TimeZone.getDefault();
+            }
+        }
+        StringBuilder buffer = new StringBuilder(this.hint);
+        this.append(buffer, context, timeZone, locale);
+        if (buffer.length() > this.hint) {
+            synchronized(this) {
+                this.hint = buffer.length();
+            }
+        }
+        return buffer.toString();
+    }
+
+    public String getOriginal() {
+        return new String(this.orig);
+    }
+
+    public boolean isEmpty() {
+        return this.orig == null || this.orig.length == 0;
+    }
+
+    @Override
+    public String toString() {
+        return new String(this.orig);
     }
 
-    protected static class BshElem implements StrElem {
-        protected final String str;
-        protected BshElem(String scriptlet) {
-            this.str = scriptlet;
+    protected static class BshElem extends FlexibleStringExpander {
+        protected BshElem(String original) {
+            super(original);
         }
         public void append(StringBuilder buffer, Map<String, ? extends Object> context, TimeZone timeZone, Locale locale) {
             try {
-                Object obj = BshUtil.eval(this.str, UtilMisc.makeMapWritable(context));
+                Object obj = BshUtil.eval(new String(this.orig), UtilMisc.makeMapWritable(context));
                 if (obj != null) {
                     try {
                         buffer.append(ObjectType.simpleTypeConvert(obj, "String", null, timeZone, locale, true));
@@ -312,58 +276,42 @@
                     }
                 } else {
                     if (Debug.verboseOn()) {
-                        Debug.logVerbose("BSH scriptlet evaluated to null [" + this.str + "], got no return so inserting nothing.", module);
+                        Debug.logVerbose("BSH scriptlet evaluated to null [" + this + "], got no return so inserting nothing.", module);
                     }
                 }
             } catch (EvalError e) {
-                Debug.logWarning(e, "Error evaluating BSH scriptlet [" + this.str + "], inserting nothing; error was: " + e, module);
+                Debug.logWarning(e, "Error evaluating BSH scriptlet [" + this + "], inserting nothing; error was: " + e, module);
             }
         }
     }
 
-    protected static class GroovyElem implements StrElem {
-        protected final String originalString;
-        protected final Class<?> parsedScript;
-        protected GroovyElem(String script) {
-            this.originalString = script;
-            this.parsedScript = GroovyUtil.parseClass(script);
+    protected static class ConstElem extends FlexibleStringExpander {
+        protected ConstElem(String original) {
+            super(original);
         }
+        @Override
         public void append(StringBuilder buffer, Map<String, ? extends Object> context, TimeZone timeZone, Locale locale) {
-            try {
-                // this approach will re-parse the script each time: Object obj = GroovyUtil.eval(this.str, UtilMisc.makeMapWritable(context));
-                Object obj = InvokerHelper.createScript(this.parsedScript, GroovyUtil.getBinding(context)).run();
-                if (obj != null) {
-                    try {
-                        buffer.append(ObjectType.simpleTypeConvert(obj, "String", null, timeZone, locale, true));
-                    } catch (Exception e) {
-                        buffer.append(obj);
-                    }
-                } else {
-                    if (Debug.verboseOn()) {
-                        Debug.logVerbose("Groovy scriptlet evaluated to null [" + this.originalString + "], got no return so inserting nothing.", module);
-                    }
-                }
-            } catch (CompilationFailedException e) {
-                Debug.logWarning(e, "Error evaluating Groovy scriptlet [" + this.originalString + "], inserting nothing; error was: " + e, module);
-            } catch (Exception e) {
-                // handle other things, like the groovy.lang.MissingPropertyException
-                Debug.logWarning(e, "Error evaluating Groovy scriptlet [" + this.originalString + "], inserting nothing; error was: " + e, module);
-            }
+            buffer.append(this.orig);
+        }
+        @Override
+        public String expandString(Map<String, ? extends Object> context, TimeZone timeZone, Locale locale) {
+            return new String(this.orig);
         }
     }
 
-    protected static class CurrElem implements StrElem {
-        protected final String valueStr;
+    protected static class CurrElem extends FlexibleStringExpander {
+        protected final char[] valueStr;
         protected final FlexibleStringExpander codeExpr;
         protected CurrElem(String original) {
+            super(original);
             int currencyPos = original.indexOf("?currency(");
             int closeParen = original.indexOf(")", currencyPos + 10);
             this.codeExpr = FlexibleStringExpander.getInstance(original.substring(currencyPos + 10, closeParen));
-            this.valueStr = openBracket + original.substring(0, currencyPos) + closeBracket;
+            this.valueStr = openBracket.concat(original.substring(0, currencyPos)).concat(closeBracket).toCharArray();
         }
         public void append(StringBuilder buffer, Map<String, ? extends Object> context, TimeZone timeZone, Locale locale) {
             try {
-                Object obj = UelUtil.evaluate(context, this.valueStr);
+                Object obj = UelUtil.evaluate(context, new String(this.valueStr));
                 if (obj != null) {
                     String currencyCode = this.codeExpr.expandString(context, timeZone, locale);
                     buffer.append(UtilFormatOut.formatCurrency(new BigDecimal(obj.toString()), currencyCode, locale));
@@ -378,33 +326,67 @@
         }
     }
 
-    protected static class NestedVarElem implements StrElem {
-        protected final List<StrElem> strElems;
-        protected int hint = 20;
+    protected static class Elements extends FlexibleStringExpander {
+        protected final FlexibleStringExpander[] childElems;
+        protected Elements(String original, FlexibleStringExpander[] childElems) {
+            super(original);
+            this.childElems = childElems;
+        }
+        public void append(StringBuilder buffer, Map<String, ? extends Object> context, TimeZone timeZone, Locale locale) {
+            for (FlexibleStringExpander child : this.childElems) {
+                child.append(buffer, context, timeZone, locale);
+            }
+        }
+    }
+
+    protected static class GroovyElem extends FlexibleStringExpander {
+        protected final Class<?> parsedScript;
+        protected GroovyElem(String script) {
+            super(script);
+            this.parsedScript = GroovyUtil.parseClass(script);
+        }
+        public void append(StringBuilder buffer, Map<String, ? extends Object> context, TimeZone timeZone, Locale locale) {
+            try {
+                Object obj = InvokerHelper.createScript(this.parsedScript, GroovyUtil.getBinding(context)).run();
+                if (obj != null) {
+                    try {
+                        buffer.append(ObjectType.simpleTypeConvert(obj, "String", null, timeZone, locale, true));
+                    } catch (Exception e) {
+                        buffer.append(obj);
+                    }
+                } else {
+                    if (Debug.verboseOn()) {
+                        Debug.logVerbose("Groovy scriptlet evaluated to null [" + this + "], got no return so inserting nothing.", module);
+                    }
+                }
+            } catch (CompilationFailedException e) {
+                Debug.logWarning(e, "Error evaluating Groovy scriptlet [" + this + "], inserting nothing; error was: " + e, module);
+            } catch (Exception e) {
+                // handle other things, like the groovy.lang.MissingPropertyException
+                Debug.logWarning(e, "Error evaluating Groovy scriptlet [" + this + "], inserting nothing; error was: " + e, module);
+            }
+        }
+    }
+
+    protected static class NestedVarElem extends FlexibleStringExpander {
+        protected final FlexibleStringExpander[] childElems;
         protected NestedVarElem(String original) {
-            this.strElems = getStrElems(original);
+            super(original);
+            this.childElems = getStrElems(original);
             if (original.length() > this.hint) {
                 this.hint = original.length();
             }
         }
         public void append(StringBuilder buffer, Map<String, ? extends Object> context, TimeZone timeZone, Locale locale) {
-            if (strElems == null) {
-                return;
-            }
             StringBuilder expr = new StringBuilder(this.hint);
-            for (StrElem elem : this.strElems) {
-                elem.append(expr, context, timeZone, locale);
+            for (FlexibleStringExpander child : this.childElems) {
+                child.append(expr, context, timeZone, locale);
             }
             if (expr.length() == 0) {
                 return;
             }
-            if (expr.length() > this.hint) {
-                synchronized(this) {
-                    this.hint = expr.length();
-                }
-            }
             try {
-                Object obj = UelUtil.evaluate(context, openBracket + expr.toString() + closeBracket);
+                Object obj = UelUtil.evaluate(context, openBracket.concat(expr.toString()).concat(closeBracket));
                 if (obj != null) {
                     buffer.append((String) ObjectType.simpleTypeConvert(obj, "String", null, timeZone, locale, false));
                 }
@@ -418,36 +400,37 @@
         }
     }
 
-    protected static class VarElem implements StrElem {
-        protected final String original;
-        protected final String bracketedOriginal;
+    protected static class VarElem extends FlexibleStringExpander {
+        protected final char[] bracketedOriginal;
         protected VarElem(String original) {
-            this.original = original;
-            this.bracketedOriginal = openBracket + UelUtil.prepareExpression(original) + closeBracket;
+            super(original);
+            this.bracketedOriginal = openBracket.concat(UelUtil.prepareExpression(original)).concat(closeBracket).toCharArray();
         }
         public void append(StringBuilder buffer, Map<String, ? extends Object> context, TimeZone timeZone, Locale locale) {
             Object obj = null;
             try {
-                obj = UelUtil.evaluate(context, this.bracketedOriginal);
+                obj = UelUtil.evaluate(context, new String(this.bracketedOriginal));
             } catch (PropertyNotFoundException e) {
                 if (Debug.verboseOn()) {
-                    Debug.logVerbose("Error evaluating expression " + this.original + ": " + e, module);
+                    Debug.logVerbose("Error evaluating expression " + this + ": " + e, module);
                 }
             } catch (Exception e) {
-                Debug.logError("Error evaluating expression " + this.original + ": " + e, module);
-            }
-            if (obj == null) {
-                if (this.original.startsWith("env.")) {
-                    Debug.logWarning("${env...} expression syntax deprecated, use ${sys:getProperty(String)} instead", module);
-                    obj = System.getProperty(this.original.substring(4));
-                }
+                Debug.logError("Error evaluating expression " + this + ": " + e, module);
             }
             if (obj != null) {
                 try {
+                    // Check for runtime nesting
+                    String str = (String) obj;
+                    if (str.contains(openBracket)) {
+                        FlexibleStringExpander fse = FlexibleStringExpander.getInstance(str);
+                        fse.append(buffer, context, timeZone, locale);
+                        return;
+                    }
+                } catch (ClassCastException e) {}
+                try {
                     buffer.append((String) ObjectType.simpleTypeConvert(obj, "String", null, timeZone, locale, false));
                 } catch (Exception e) {}
             }
         }
     }
-
 }