svn commit: r1350214 - in /ofbiz/trunk/framework/minilang: dtd/simple-methods-v2.xsd src/org/ofbiz/minilang/method/otherops/Calculate.java

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

svn commit: r1350214 - in /ofbiz/trunk/framework/minilang: dtd/simple-methods-v2.xsd src/org/ofbiz/minilang/method/otherops/Calculate.java

adrianc
Author: adrianc
Date: Thu Jun 14 12:36:25 2012
New Revision: 1350214

URL: http://svn.apache.org/viewvc?rev=1350214&view=rev
Log:
Overhauled Mini-language <calculate> element.

The overhaul includes: removing unnecessary object creation, make the class thread-safe, add syntax validation, and misc code cleanups.

Modified:
    ofbiz/trunk/framework/minilang/dtd/simple-methods-v2.xsd
    ofbiz/trunk/framework/minilang/src/org/ofbiz/minilang/method/otherops/Calculate.java

Modified: ofbiz/trunk/framework/minilang/dtd/simple-methods-v2.xsd
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/minilang/dtd/simple-methods-v2.xsd?rev=1350214&r1=1350213&r2=1350214&view=diff
==============================================================================
--- ofbiz/trunk/framework/minilang/dtd/simple-methods-v2.xsd (original)
+++ ofbiz/trunk/framework/minilang/dtd/simple-methods-v2.xsd Thu Jun 14 12:36:25 2012
@@ -4284,141 +4284,187 @@ under the License.
     <xs:element name="calculate" substitutionGroup="OtherOperations">
         <xs:annotation>
             <xs:documentation>
-                The calculate tag performs the specified calculation and puts the result in an object in the field of the specified map (see the calculate element attribute descriptions).
-                The type of the object can be specified with the type attribute, defaults to Double.
-
-                The calculate tag can contain calcop and number tags, and the calcop tag can also contain these two tags to enable nested calculations.
-
-                The operator specifies the operation to perform on the given field and nested calcops and numbers.
-                It must be one of the following: get | add | subtract | multiply | divide | negative.
+                Performs an arithmetic calculation and puts the result in the specified field.
+                Deprecated - use the set element.
             </xs:documentation>
         </xs:annotation>
         <xs:complexType>
             <xs:choice minOccurs="0" maxOccurs="unbounded">
-                <xs:element ref="calcop">
-                    <xs:annotation>
-                        <xs:documentation>
-                            This tag is used to apply an operator in the calculation.
-                            It can have calcop and number tags nested under it, making it also act like a parenthesis.
-                            It has three attributes: operator, map-name, and field-name. Operator and field-name are required.
-                        </xs:documentation>
-                    </xs:annotation>
-                </xs:element>
-                <xs:element ref="number"/>
+                <xs:element ref="calcop" />
+                <xs:element ref="number" />
             </xs:choice>
-            <xs:attributeGroup ref="attlist.calculate"/>
+            <xs:attribute ref="field" />
+            <xs:attribute name="rounding-mode">
+                <xs:annotation>
+                    <xs:documentation>
+                        Rounding mode for BigDecimal calculation, primarily for divide operation.
+                        Defaults to "HalfEven".
+                    </xs:documentation>
+                </xs:annotation>
+                <xs:simpleType>
+                    <xs:restriction base="xs:token">
+                        <xs:enumeration value="Ceiling">
+                            <xs:annotation>
+                                <xs:documentation>
+                                    Round towards positive infinity.
+                                </xs:documentation>
+                            </xs:annotation>
+                        </xs:enumeration>
+                        <xs:enumeration value="Floor">
+                            <xs:annotation>
+                                <xs:documentation>
+                                    Round towards negative infinity.
+                                </xs:documentation>
+                            </xs:annotation>
+                        </xs:enumeration>
+                        <xs:enumeration value="Up">
+                            <xs:annotation>
+                                <xs:documentation>
+                                    Round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
+                                </xs:documentation>
+                            </xs:annotation>
+                        </xs:enumeration>
+                        <xs:enumeration value="Down">
+                            <xs:annotation>
+                                <xs:documentation>
+                                    Round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.
+                                </xs:documentation>
+                            </xs:annotation>
+                        </xs:enumeration>
+                        <xs:enumeration value="HalfUp">
+                            <xs:annotation>
+                                <xs:documentation>
+                                    Round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
+                                </xs:documentation>
+                            </xs:annotation>
+                        </xs:enumeration>
+                        <xs:enumeration value="HalfDown">
+                            <xs:annotation>
+                                <xs:documentation>
+                                    Round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.
+                                </xs:documentation>
+                            </xs:annotation>
+                        </xs:enumeration>
+                        <xs:enumeration value="HalfEven">
+                            <xs:annotation>
+                                <xs:documentation>
+                                    Round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor.
+                                </xs:documentation>
+                            </xs:annotation>
+                        </xs:enumeration>
+                        <xs:enumeration value="Unnecessary">
+                            <xs:annotation>
+                                <xs:documentation>
+                                    Asserts that the requested operation has an exact result, hence no rounding is necessary.
+                                </xs:documentation>
+                            </xs:annotation>
+                        </xs:enumeration>
+                        <xs:enumeration value="${roundingMode}">
+                            <xs:annotation>
+                                <xs:documentation>
+                                    Convention for variable name for this attribute, in cases where it is determined at run-time.
+                                </xs:documentation>
+                            </xs:annotation>
+                        </xs:enumeration>
+                    </xs:restriction>
+                </xs:simpleType>
+            </xs:attribute>
+            <xs:attribute type="xs:string" name="decimal-scale">
+                <xs:annotation>
+                    <xs:documentation>
+                        Initial scale to use for the internal BigDecimal. Defaults to "2".
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+            <xs:attribute type="xs:string" name="decimal-format">
+                <xs:annotation>
+                    <xs:documentation>
+                        Decimal format to use for conversion to string.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="type">
+                <xs:annotation>
+                    <xs:documentation>
+                        Data type of the calculation result. Defaults to "BigDecimal".
+                    </xs:documentation>
+                </xs:annotation>
+                <xs:simpleType>
+                    <xs:restriction base="xs:token">
+                        <xs:enumeration value="String" />
+                        <xs:enumeration value="Double" />
+                        <xs:enumeration value="Float" />
+                        <xs:enumeration value="Long" />
+                        <xs:enumeration value="Integer" />
+                        <xs:enumeration value="BigDecimal" />
+                    </xs:restriction>
+                </xs:simpleType>
+            </xs:attribute>
         </xs:complexType>
     </xs:element>
-    <xs:attributeGroup name="attlist.calculate">
-        <xs:attribute type="xs:string" name="field" use="required">
-            <xs:annotation><xs:documentation>The name (key) of the map (or env if map-name is empty) field to use.</xs:documentation></xs:annotation>
-        </xs:attribute>
-        <xs:attribute name="type" default="BigDecimal">
-            <xs:simpleType>
-                <xs:restriction base="xs:token">
-                    <xs:enumeration value="String"/>
-                    <xs:enumeration value="Double"/>
-                    <xs:enumeration value="Float"/>
-                    <xs:enumeration value="Long"/>
-                    <xs:enumeration value="Integer"/>
-                    <xs:enumeration value="BigDecimal"/>
-                </xs:restriction>
-            </xs:simpleType>
-        </xs:attribute>
-        <xs:attribute name="rounding-mode" default="HalfEven">
-            <xs:annotation>
-                <xs:documentation>
-                    Rounding mode for BigDecimal calculation, primarily for divide operation.
-                </xs:documentation>
-            </xs:annotation>
-            <xs:simpleType>
-                <xs:restriction base="xs:token">
-                    <xs:enumeration value="Ceiling"><xs:annotation><xs:documentation>Rounding mode to round towards positive infinity</xs:documentation></xs:annotation></xs:enumeration>
-                    <xs:enumeration value="Floor"><xs:annotation><xs:documentation>Rounding mode to round towards negative infinity</xs:documentation></xs:annotation></xs:enumeration>
-                    <xs:enumeration value="Up"><xs:annotation><xs:documentation>Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up</xs:documentation></xs:annotation></xs:enumeration>
-                    <xs:enumeration value="Down"><xs:annotation><xs:documentation>Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down</xs:documentation></xs:annotation></xs:enumeration>
-                    <xs:enumeration value="HalfUp"><xs:annotation><xs:documentation>Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up</xs:documentation></xs:annotation></xs:enumeration>
-                    <xs:enumeration value="HalfDown"><xs:annotation><xs:documentation>Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down</xs:documentation></xs:annotation></xs:enumeration>
-                    <xs:enumeration value="HalfEven"><xs:annotation><xs:documentation>Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor</xs:documentation></xs:annotation></xs:enumeration>
-                    <xs:enumeration value="Unnecessary"><xs:annotation><xs:documentation>Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary</xs:documentation></xs:annotation></xs:enumeration>
-                    <xs:enumeration value="${roundingMode}"><xs:annotation><xs:documentation>Convention for variable name for this attribute, in cases where it is determined at run-time.</xs:documentation></xs:annotation></xs:enumeration>
-                </xs:restriction>
-            </xs:simpleType>
-        </xs:attribute>
-        <xs:attribute type="xs:string" name="decimal-scale" default="2">
-            <xs:annotation><xs:documentation>Initial scale to use for the internal BigDecimal. Defaults to 2 for monetary calculations.</xs:documentation></xs:annotation>
-        </xs:attribute>
-        <xs:attribute type="xs:string" name="decimal-format"/>
-    </xs:attributeGroup>
     <xs:element name="calcop">
         <xs:annotation>
             <xs:documentation>
-                The calcop tag has an operator: get, add, subtract, multiply, divide, and negative.
+                A basic arithmetic operation.
+                Supports these operations: get, add, subtract, multiply, divide, and negate.
                 So add, subtract, multiply, and divide are just the basic arithmetic operations.
             </xs:documentation>
         </xs:annotation>
         <xs:complexType>
             <xs:choice minOccurs="0" maxOccurs="unbounded">
-                <xs:element ref="calcop"/>
-                <xs:element ref="number"/>
+                <xs:element ref="calcop" />
+                <xs:element ref="number" />
             </xs:choice>
-            <xs:attributeGroup ref="attlist.calcop"/>
+            <xs:attribute name="operator" use="required">
+                <xs:simpleType>
+                    <xs:restriction base="xs:token">
+                        <xs:enumeration value="get">
+                            <xs:annotation>
+                                <xs:documentation>
+                                    Gets the positive value.
+                                </xs:documentation>
+                            </xs:annotation>
+                        </xs:enumeration>
+                        <xs:enumeration value="add" />
+                        <xs:enumeration value="subtract" />
+                        <xs:enumeration value="multiply" />
+                        <xs:enumeration value="divide" />
+                        <xs:enumeration value="negative">
+                            <xs:annotation>
+                                <xs:documentation>
+                                    Negates the value
+                            </xs:documentation>
+                            </xs:annotation>
+                        </xs:enumeration>
+                    </xs:restriction>
+                </xs:simpleType>
+            </xs:attribute>
+            <xs:attribute type="xs:string" name="field">
+                <xs:annotation>
+                    <xs:documentation>
+                        The name of the field to use.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
         </xs:complexType>
     </xs:element>
-    <xs:attributeGroup name="attlist.calcop">
-        <xs:attribute name="operator" use="required">
-            <xs:simpleType>
-                <xs:restriction base="xs:token">
-                    <xs:enumeration value="get">
-                        <xs:annotation>
-                            <xs:documentation>
-                                Gets the positive value.
-                            </xs:documentation>
-                        </xs:annotation>
-                    </xs:enumeration>
-                    <xs:enumeration value="add"/>
-                    <xs:enumeration value="subtract"/>
-                    <xs:enumeration value="multiply"/>
-                    <xs:enumeration value="divide"/>
-                    <xs:enumeration value="negative">
-                        <xs:annotation>
-                            <xs:documentation>
-                                Negates the value
-                            </xs:documentation>
-                        </xs:annotation>
-                    </xs:enumeration>
-                </xs:restriction>
-            </xs:simpleType>
-        </xs:attribute>
-        <xs:attribute type="xs:string" name="field">
-            <xs:annotation>
-                <xs:documentation>
-                    The name (key) of the map field to use.
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-    </xs:attributeGroup>
     <xs:element name="number">
         <xs:annotation>
             <xs:documentation>
                 This is used to put a numeric constant (a number) into the calculation.
                 It has one attribute: value. This must be a properly formatted number or an error will result.
-                May use flexible string.
             </xs:documentation>
         </xs:annotation>
         <xs:complexType>
-            <xs:attributeGroup ref="attlist.number"/>
+            <xs:attribute type="xs:string" name="value" use="required">
+                <xs:annotation>
+                    <xs:documentation>
+                        Literal or flexible string.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
         </xs:complexType>
     </xs:element>
-    <xs:attributeGroup name="attlist.number">
-        <xs:attribute type="xs:string" name="value" use="required">
-            <xs:annotation>
-                <xs:documentation>
-                    Literal or flexible string.
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-    </xs:attributeGroup>
     <xs:element name="set-calendar" substitutionGroup="EnvOperations">
         <xs:annotation>
             <xs:documentation>
@@ -4427,13 +4473,6 @@ under the License.
         </xs:annotation>
         <xs:complexType>
             <xs:attribute ref="field" />
-            <xs:attribute type="xs:string" name="from-field">
-                <xs:annotation>
-                    <xs:documentation>
-                        Deprecated - use the from attribute.
-                    </xs:documentation>
-                </xs:annotation>
-            </xs:attribute>
             <xs:attribute type="xs:string" name="from">
                 <xs:annotation>
                     <xs:documentation>
@@ -4445,6 +4484,13 @@ under the License.
                     </xs:documentation>
                 </xs:annotation>
             </xs:attribute>
+            <xs:attribute type="xs:string" name="from-field">
+                <xs:annotation>
+                    <xs:documentation>
+                        Deprecated - use the from attribute.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
             <xs:attribute type="xs:string" name="value">
                 <xs:annotation>
                     <xs:documentation>
@@ -4455,19 +4501,19 @@ under the License.
                     </xs:documentation>
                 </xs:annotation>
             </xs:attribute>
-            <xs:attribute type="xs:string" name="default-value">
+            <xs:attribute type="xs:string" name="default">
                 <xs:annotation>
                     <xs:documentation>
-                        Deprecated - use the default attribute.
+                        A default value that is used when the from attribute evaluates to null or empty.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Optional. Attribute types: constant, ${expression}.
                     </xs:documentation>
                 </xs:annotation>
             </xs:attribute>
-            <xs:attribute type="xs:string" name="default">
+            <xs:attribute type="xs:string" name="default-value">
                 <xs:annotation>
                     <xs:documentation>
-                        A default value that is used when the from attribute evaluates to null or empty.
-                        &lt;br/&gt;&lt;br/&gt;
-                        Optional. Attribute types: constant, ${expression}.
+                        Deprecated - use the default attribute.
                     </xs:documentation>
                 </xs:annotation>
             </xs:attribute>

Modified: ofbiz/trunk/framework/minilang/src/org/ofbiz/minilang/method/otherops/Calculate.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/minilang/src/org/ofbiz/minilang/method/otherops/Calculate.java?rev=1350214&r1=1350213&r2=1350214&view=diff
==============================================================================
--- ofbiz/trunk/framework/minilang/src/org/ofbiz/minilang/method/otherops/Calculate.java (original)
+++ ofbiz/trunk/framework/minilang/src/org/ofbiz/minilang/method/otherops/Calculate.java Thu Jun 14 12:36:25 2012
@@ -22,26 +22,24 @@ import java.math.BigDecimal;
 import java.text.DecimalFormat;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 
-import javolution.util.FastMap;
-
-import org.ofbiz.base.util.Debug;
-import org.ofbiz.base.util.ObjectType;
-import org.ofbiz.base.util.UtilValidate;
 import org.ofbiz.base.util.UtilXml;
+import org.ofbiz.base.util.collections.FlexibleMapAccessor;
+import org.ofbiz.base.util.string.FlexibleStringExpander;
+import org.ofbiz.minilang.MiniLangElement;
 import org.ofbiz.minilang.MiniLangException;
+import org.ofbiz.minilang.MiniLangRuntimeException;
 import org.ofbiz.minilang.MiniLangUtil;
+import org.ofbiz.minilang.MiniLangValidate;
 import org.ofbiz.minilang.SimpleMethod;
-import org.ofbiz.minilang.method.ContextAccessor;
 import org.ofbiz.minilang.method.MethodContext;
 import org.ofbiz.minilang.method.MethodOperation;
 import org.w3c.dom.Element;
 
 /**
- * Calculates a result based on nested calcops.
+ * Implements the &lt;calculate&gt; element.
  */
-public class Calculate extends MethodOperation {
+public final class Calculate extends MethodOperation {
 
     public static final String module = Calculate.class.getName();
 
@@ -51,36 +49,40 @@ public class Calculate extends MethodOpe
     public static final int TYPE_INTEGER = 4;
     public static final int TYPE_STRING = 5;
     public static final int TYPE_BIG_DECIMAL = 6;
-    public static final BigDecimal ZERO = BigDecimal.ZERO;
 
-    Calculate.SubCalc calcops[];
-    String decimalFormatString;
-    String decimalScaleString;
-    ContextAccessor<Object> fieldAcsr;
-    ContextAccessor<Map<String, Object>> mapAcsr;
-    String roundingModeString;
-    String typeString;
+    private final Calculate.SubCalc calcops[];
+    private final FlexibleStringExpander decimalFormatFse;
+    private final FlexibleStringExpander decimalScaleFse;
+    private final FlexibleMapAccessor<Object> fieldFma;
+    private final FlexibleStringExpander roundingModeFse;
+    private final FlexibleStringExpander typeFse;
 
     public Calculate(Element element, SimpleMethod simpleMethod) throws MiniLangException {
         super(element, simpleMethod);
-        // the schema for this element now just has the "field" attribute, though the old "field-name" and "map-name" pair is still supported
-        this.fieldAcsr = new ContextAccessor<Object>(element.getAttribute("field"), element.getAttribute("field-name"));
-        this.mapAcsr = new ContextAccessor<Map<String, Object>>(element.getAttribute("map-name"));
-        decimalScaleString = element.getAttribute("decimal-scale");
-        decimalFormatString = element.getAttribute("decimal-format");
-        typeString = element.getAttribute("type");
-        roundingModeString = element.getAttribute("rounding-mode");
+        if (MiniLangValidate.validationOn()) {
+            MiniLangValidate.handleError("<calculate> element is deprecated (use <set>)", simpleMethod, element);
+            MiniLangValidate.attributeNames(simpleMethod, element, "field", "decimal-scale", "decimal-format", "rounding-mode", "type");
+            MiniLangValidate.requiredAttributes(simpleMethod, element, "field");
+            MiniLangValidate.expressionAttributes(simpleMethod, element, "field");
+            MiniLangValidate.childElements(simpleMethod, element, "calcop", "number");
+        }
+        this.fieldFma = FlexibleMapAccessor.getInstance(element.getAttribute("field"));
+        this.decimalFormatFse = FlexibleStringExpander.getInstance(element.getAttribute("decimal-format"));
+        this.decimalScaleFse = FlexibleStringExpander.getInstance(element.getAttribute("decimal-scale"));
+        this.roundingModeFse = FlexibleStringExpander.getInstance(element.getAttribute("rounding-mode"));
+        this.typeFse = FlexibleStringExpander.getInstance(element.getAttribute("type"));
         List<? extends Element> calcopElements = UtilXml.childElementList(element);
         calcops = new Calculate.SubCalc[calcopElements.size()];
         int i = 0;
         for (Element calcopElement : calcopElements) {
             String nodeName = calcopElement.getNodeName();
             if ("calcop".equals(nodeName)) {
-                calcops[i] = new Calculate.CalcOp(calcopElement);
+                calcops[i] = new CalcOp(calcopElement, simpleMethod);
             } else if ("number".equals(nodeName)) {
-                calcops[i] = new Calculate.NumberOp(calcopElement);
+                calcops[i] = new NumberOp(calcopElement, simpleMethod);
             } else {
-                Debug.logError("Error: calculate operation with type " + nodeName, module);
+                MiniLangValidate.handleError("Invalid calculate sub-element.", simpleMethod, calcopElement);
+                calcops[i] = new InvalidOp(calcopElement, simpleMethod);
             }
             i++;
         }
@@ -88,7 +90,7 @@ public class Calculate extends MethodOpe
 
     @Override
     public boolean exec(MethodContext methodContext) throws MiniLangException {
-        String typeString = methodContext.expandString(this.typeString);
+        String typeString = typeFse.expandString(methodContext.getEnvMap());
         int type;
         if ("Double".equals(typeString)) {
             type = Calculate.TYPE_DOUBLE;
@@ -105,7 +107,7 @@ public class Calculate extends MethodOpe
         } else {
             type = Calculate.TYPE_BIG_DECIMAL;
         }
-        String roundingModeString = methodContext.expandString(this.roundingModeString);
+        String roundingModeString = roundingModeFse.expandString(methodContext.getEnvMap());
         int roundingMode;
         if ("Ceiling".equals(roundingModeString)) {
             roundingMode = BigDecimal.ROUND_CEILING;
@@ -127,29 +129,21 @@ public class Calculate extends MethodOpe
             // default to HalfEven, reduce cumulative errors
             roundingMode = BigDecimal.ROUND_HALF_EVEN;
         }
-        String decimalScaleString = methodContext.expandString(this.decimalScaleString);
+        String decimalScaleString = decimalScaleFse.expandString(methodContext.getEnvMap());
         int decimalScale = 2;
-        if (UtilValidate.isNotEmpty(decimalScaleString)) {
+        if (!decimalScaleString.isEmpty()) {
             decimalScale = Integer.valueOf(decimalScaleString).intValue();
         }
-        String decimalFormatString = methodContext.expandString(this.decimalFormatString);
+        String decimalFormatString = decimalFormatFse.expandString(methodContext.getEnvMap());
         DecimalFormat df = null;
-        if (UtilValidate.isNotEmpty(decimalFormatString)) {
+        if (!decimalFormatString.isEmpty()) {
             df = new DecimalFormat(decimalFormatString);
         }
-        BigDecimal resultValue = ZERO;
-        resultValue = resultValue.setScale(decimalScale, roundingMode);
+        BigDecimal resultValue = BigDecimal.ZERO.setScale(decimalScale, roundingMode);
         for (Calculate.SubCalc calcop : calcops) {
             resultValue = resultValue.add(calcop.calcValue(methodContext, decimalScale, roundingMode));
-            // Debug.logInfo("main total so far: " + resultValue, module);
         }
         resultValue = resultValue.setScale(decimalScale, roundingMode);
-        /*
-         * the old thing that did conversion to string and back, may want to use somewhere sometime...: for now just doing the setScale above (before and after calc ops) try { resultValue = new
-         * BigDecimal(df.format(resultValue)); } catch (ParseException e) { String errorMessage = "Unable to format [" + formatString + "] result [" + resultValue + "]"; Debug.logError(e,
-         * errorMessage, module); if (methodContext.getMethodType() == MethodContext.EVENT) { methodContext.putEnv(simpleMethod.getEventErrorMessageName(), errorMessage); } else if
-         * (methodContext.getMethodType() == MethodContext.SERVICE) { methodContext.putEnv(simpleMethod.getServiceErrorMessageName(), errorMessage); } return false; }
-         */
         Object resultObj = null;
         switch (type) {
             case TYPE_DOUBLE:
@@ -168,7 +162,7 @@ public class Calculate extends MethodOpe
                 break;
             case TYPE_STRING:
                 // run the decimal-formatting
-                if (df != null && resultValue.compareTo(ZERO) > 0) {
+                if (df != null && resultValue.compareTo(BigDecimal.ZERO) > 0) {
                     resultObj = df.format(resultValue);
                 } else {
                     resultObj = resultValue.toString();
@@ -178,71 +172,90 @@ public class Calculate extends MethodOpe
                 resultObj = resultValue;
                 break;
         }
-
-        if (!mapAcsr.isEmpty()) {
-            Map<String, Object> toMap = mapAcsr.get(methodContext);
-            if (toMap == null) {
-                if (Debug.verboseOn())
-                    Debug.logVerbose("Map not found with name " + mapAcsr + ", creating new map", module);
-                toMap = FastMap.newInstance();
-                mapAcsr.put(methodContext, toMap);
-            }
-            fieldAcsr.put(toMap, resultObj, methodContext);
-        } else {
-            fieldAcsr.put(methodContext, resultObj);
-        }
-
+        fieldFma.put(methodContext.getEnvMap(), resultObj);
         return true;
     }
 
     @Override
     public String expandedString(MethodContext methodContext) {
-        // TODO: something more than a stub/dummy
-        return this.rawString();
+        return FlexibleStringExpander.expandString(toString(), methodContext.getEnvMap());
     }
 
     @Override
     public String rawString() {
-        // TODO: add all attributes and other info
-        return "<calculate field-name=\"" + this.fieldAcsr + "\" map-name=\"" + this.mapAcsr + "\"/>";
+        return toString();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("<set ");
+        sb.append("field=\"").append(this.fieldFma).append("\" ");
+        if (!this.roundingModeFse.isEmpty()) {
+            sb.append("rounding-mode=\"").append(this.roundingModeFse).append("\" ");
+        }
+        if (!this.decimalScaleFse.isEmpty()) {
+            sb.append("decimal-scale=\"").append(this.decimalScaleFse).append("\" ");
+        }
+        if (!this.decimalFormatFse.isEmpty()) {
+            sb.append("decimal-format=\"").append(this.decimalFormatFse).append("\" ");
+        }
+        if (!typeFse.isEmpty()) {
+            sb.append("type=\"").append(this.typeFse).append("\" ");
+        }
+        sb.append("/>");
+        return sb.toString();
     }
 
-    protected static class CalcOp implements SubCalc {
-        public static final int OPERATOR_ADD = 1;
-        public static final int OPERATOR_DIVIDE = 4;
-        public static final int OPERATOR_MULTIPLY = 3;
-        public static final int OPERATOR_NEGATIVE = 5;
-        public static final int OPERATOR_SUBTRACT = 2;
-
-        Calculate.SubCalc calcops[];
-        ContextAccessor<Object> fieldAcsr;
-        ContextAccessor<Map<String, ? extends Object>> mapAcsr;
-        String operatorStr;
-
-        public CalcOp(Element element) {
-            // the schema for this element now just has the "field" attribute, though the old "field-name" and "map-name" pair is still supported
-            this.fieldAcsr = new ContextAccessor<Object>(element.getAttribute("field"), element.getAttribute("field-name"));
-            this.mapAcsr = new ContextAccessor<Map<String, ? extends Object>>(element.getAttribute("map-name"));
-            operatorStr = element.getAttribute("operator");
+    /**
+     * Interface for &lt;calculate&gt; sub-element implementations.
+     */
+    public interface SubCalc {
+        BigDecimal calcValue(MethodContext methodContext, int scale, int roundingMode) throws MiniLangException;
+    }
+
+    /**
+     * Implements the &lt;calcop&gt; element.
+     */
+    public final class CalcOp extends MiniLangElement implements SubCalc {
+        private static final int OPERATOR_ADD = 1;
+        private static final int OPERATOR_DIVIDE = 4;
+        private static final int OPERATOR_MULTIPLY = 3;
+        private static final int OPERATOR_NEGATIVE = 5;
+        private static final int OPERATOR_SUBTRACT = 2;
+
+        private final Calculate.SubCalc calcops[];
+        private final FlexibleMapAccessor<Object> fieldFma;
+        private final FlexibleStringExpander operatorFse;
+
+        private CalcOp(Element element, SimpleMethod simpleMethod) throws MiniLangException {
+            super(element, simpleMethod);
+            if (MiniLangValidate.validationOn()) {
+                MiniLangValidate.attributeNames(simpleMethod, element, "field", "operator");
+                MiniLangValidate.requiredAttributes(simpleMethod, element, "field");
+                MiniLangValidate.expressionAttributes(simpleMethod, element, "field");
+                MiniLangValidate.childElements(simpleMethod, element, "calcop", "number");
+            }
+            this.fieldFma = FlexibleMapAccessor.getInstance(element.getAttribute("field"));
+            this.operatorFse = FlexibleStringExpander.getInstance(element.getAttribute("operator"));
             List<? extends Element> calcopElements = UtilXml.childElementList(element);
             calcops = new Calculate.SubCalc[calcopElements.size()];
             int i = 0;
-
             for (Element calcopElement : calcopElements) {
-                String nodeName = calcopElement.getNodeName();
                 if ("calcop".equals(calcopElement.getNodeName())) {
-                    calcops[i] = new Calculate.CalcOp(calcopElement);
+                    calcops[i] = new Calculate.CalcOp(calcopElement, simpleMethod);
                 } else if ("number".equals(calcopElement.getNodeName())) {
-                    calcops[i] = new Calculate.NumberOp(calcopElement);
+                    calcops[i] = new Calculate.NumberOp(calcopElement, simpleMethod);
                 } else {
-                    Debug.logError("Error: calculate operation unknown with type " + nodeName, module);
+                    MiniLangValidate.handleError("Invalid calculate sub-element.", simpleMethod, calcopElement);
+                    calcops[i] = new InvalidOp(calcopElement, simpleMethod);
                 }
                 i++;
             }
         }
 
-        public BigDecimal calcValue(MethodContext methodContext, int scale, int roundingMode) {
-            String operatorStr = methodContext.expandString(this.operatorStr);
+        @Override
+        public BigDecimal calcValue(MethodContext methodContext, int scale, int roundingMode) throws MiniLangException {
+            String operatorStr = operatorFse.expandString(methodContext.getEnvMap());
             int operator = CalcOp.OPERATOR_ADD;
             if ("get".equals(operatorStr)) {
                 operator = CalcOp.OPERATOR_ADD;
@@ -257,45 +270,26 @@ public class Calculate extends MethodOpe
             } else if ("negative".equals(operatorStr)) {
                 operator = CalcOp.OPERATOR_NEGATIVE;
             }
-            BigDecimal resultValue = ZERO;
-            resultValue = resultValue.setScale(scale, roundingMode);
+            BigDecimal resultValue = BigDecimal.ZERO.setScale(scale, roundingMode);
             boolean isFirst = true;
-            // if a fieldAcsr was specified, get the field from the map or result and use it as the initial value
-            if (!fieldAcsr.isEmpty()) {
-                Object fieldObj = null;
-                if (!mapAcsr.isEmpty()) {
-                    Map<String, ? extends Object> fromMap = mapAcsr.get(methodContext);
-                    if (fromMap == null) {
-                        if (Debug.verboseOn())
-                            Debug.logVerbose("Map not found with name " + mapAcsr + ", creating new map", module);
-                        fromMap = FastMap.newInstance();
-                        mapAcsr.put(methodContext, fromMap);
-                    }
-                    fieldObj = fieldAcsr.get(fromMap, methodContext);
-                } else {
-                    fieldObj = fieldAcsr.get(methodContext);
-                }
-                if (fieldObj != null) {
-                    if (fieldObj instanceof Double) {
-                        resultValue = new BigDecimal(((Double) fieldObj).doubleValue());
-                    } else if (fieldObj instanceof Long) {
-                        resultValue = BigDecimal.valueOf(((Long) fieldObj).longValue());
-                    } else if (fieldObj instanceof Float) {
-                        resultValue = new BigDecimal(((Float) fieldObj).floatValue());
-                    } else if (fieldObj instanceof Integer) {
-                        resultValue = BigDecimal.valueOf(((Integer) fieldObj).longValue());
-                    } else if (fieldObj instanceof String) {
-                        resultValue = new BigDecimal((String) fieldObj);
-                    } else if (fieldObj instanceof BigDecimal) {
-                        resultValue = (BigDecimal) fieldObj;
-                    }
-                    if (operator == OPERATOR_NEGATIVE)
-                        resultValue = resultValue.negate();
-                    isFirst = false;
-                } else {
-                    if (Debug.infoOn())
-                        Debug.logInfo("Field not found with field-name " + fieldAcsr + ", and map-name " + mapAcsr + "using a default of 0", module);
+            Object fieldObj = fieldFma.get(methodContext.getEnvMap());
+            if (fieldObj != null) {
+                if (fieldObj instanceof Double) {
+                    resultValue = new BigDecimal(((Double) fieldObj).doubleValue());
+                } else if (fieldObj instanceof Long) {
+                    resultValue = BigDecimal.valueOf(((Long) fieldObj).longValue());
+                } else if (fieldObj instanceof Float) {
+                    resultValue = new BigDecimal(((Float) fieldObj).floatValue());
+                } else if (fieldObj instanceof Integer) {
+                    resultValue = BigDecimal.valueOf(((Integer) fieldObj).longValue());
+                } else if (fieldObj instanceof String) {
+                    resultValue = new BigDecimal((String) fieldObj);
+                } else if (fieldObj instanceof BigDecimal) {
+                    resultValue = (BigDecimal) fieldObj;
                 }
+                if (operator == OPERATOR_NEGATIVE)
+                    resultValue = resultValue.negate();
+                isFirst = false;
             }
             for (SubCalc calcop : calcops) {
                 if (isFirst) {
@@ -325,42 +319,62 @@ public class Calculate extends MethodOpe
         }
     }
 
-    public static final class CalculateFactory implements Factory<Calculate> {
-        public Calculate createMethodOperation(Element element, SimpleMethod simpleMethod) throws MiniLangException {
-            return new Calculate(element, simpleMethod);
-        }
-
-        public String getName() {
-            return "calculate";
-        }
-    }
-
-    protected static class NumberOp implements SubCalc {
-        String valueStr;
-
-        public NumberOp(Element element) {
-            valueStr = element.getAttribute("value");
+    /**
+     * Implements the &lt;number&gt; element.
+     */
+    public final class NumberOp extends MiniLangElement implements SubCalc {
+
+        private final FlexibleStringExpander valueFse;
+
+        private NumberOp(Element element, SimpleMethod simpleMethod) throws MiniLangException {
+            super(element, simpleMethod);
+            if (MiniLangValidate.validationOn()) {
+                MiniLangValidate.attributeNames(simpleMethod, element, "value");
+                MiniLangValidate.requiredAttributes(simpleMethod, element, "value");
+                MiniLangValidate.noChildElements(simpleMethod, element);
+            }
+            valueFse = FlexibleStringExpander.getInstance(element.getAttribute("value"));
         }
 
-        public BigDecimal calcValue(MethodContext methodContext, int scale, int roundingMode) {
-            String valueStr = methodContext.expandString(this.valueStr);
+        @Override
+        public BigDecimal calcValue(MethodContext methodContext, int scale, int roundingMode) throws MiniLangException {
+            String valueStr = valueFse.expandString(methodContext.getEnvMap());
             Locale locale = methodContext.getLocale();
             if (locale == null)
                 locale = Locale.getDefault();
-            BigDecimal value;
             try {
-                BigDecimal parseVal = (BigDecimal) MiniLangUtil.convertType(valueStr, java.math.BigDecimal.class, locale, null, null);
-                value = parseVal.setScale(scale, roundingMode);
+                BigDecimal parsedVal = (BigDecimal) MiniLangUtil.convertType(valueStr, java.math.BigDecimal.class, locale, null, null);
+                return parsedVal.setScale(scale, roundingMode);
             } catch (Exception e) {
-                Debug.logError(e, "Could not parse the number string: " + valueStr, module);
-                throw new IllegalArgumentException("Could not parse the number string: " + valueStr);
+                throw new MiniLangRuntimeException("Exception thrown while parsing value attribute: " + e.getMessage(), this);
             }
-            return value;
         }
+    }
 
+    private final class InvalidOp extends MiniLangElement implements SubCalc {
+
+        private InvalidOp(Element element, SimpleMethod simpleMethod) throws MiniLangException {
+            super(element, simpleMethod);
+        }
+
+        @Override
+        public BigDecimal calcValue(MethodContext methodContext, int scale, int roundingMode) throws MiniLangException {
+            throw new MiniLangRuntimeException("Invalid calculate sub-element.", this);
+        }
     }
 
-    protected static interface SubCalc {
-        public BigDecimal calcValue(MethodContext methodContext, int scale, int roundingMode);
+    /**
+     * A factory for the &lt;calculate&gt; element.
+     */
+    public static final class CalculateFactory implements Factory<Calculate> {
+        @Override
+        public Calculate createMethodOperation(Element element, SimpleMethod simpleMethod) throws MiniLangException {
+            return new Calculate(element, simpleMethod);
+        }
+
+        @Override
+        public String getName() {
+            return "calculate";
+        }
     }
 }