]> gerrit.simantics Code Review - simantics/sysdyn.git/commitdiff
Unit validation for Sysdyn functions: refactoring (refs #4320).
authormiettinen <miettinen@ac1ea38d-2e2b-0410-8846-a27921b304fc>
Fri, 31 Jan 2014 13:45:45 +0000 (13:45 +0000)
committermiettinen <miettinen@ac1ea38d-2e2b-0410-8846-a27921b304fc>
Fri, 31 Jan 2014 13:45:45 +0000 (13:45 +0000)
git-svn-id: https://www.simantics.org/svn/simantics/sysdyn/trunk@28747 ac1ea38d-2e2b-0410-8846-a27921b304fc

org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/FunctionCall.java
org.simantics.sysdyn/src/org/simantics/sysdyn/utils/Function.java

index 4f73e165981143faf21b33c8f8840ccd4cd8183c..eb07940d90b337aad4d00c54a664ee73c57e768a 100644 (file)
  *******************************************************************************/\r
 package org.simantics.sysdyn.unitParser.nodes;\r
 \r
-import java.io.StringReader;\r
 import java.util.ArrayList;\r
 import java.util.HashMap;\r
 \r
-import org.simantics.sysdyn.unitParser.ParseException;\r
 import org.simantics.sysdyn.unitParser.UnitCheckingException;\r
 import org.simantics.sysdyn.unitParser.UnitCheckingNode;\r
-import org.simantics.sysdyn.unitParser.UnitParser;\r
 import org.simantics.sysdyn.unitParser.nodes.UnitResult.UnitType;\r
 import org.simantics.sysdyn.utils.Function;\r
 import org.simantics.sysdyn.utils.Function.Input;\r
 import org.simantics.sysdyn.utils.Function.Output;\r
-import org.simantics.sysdyn.utils.Function.Parameter;\r
 import org.simantics.utils.datastructures.Pair;\r
 \r
 /**\r
@@ -41,7 +37,6 @@ public class FunctionCall extends UnitCheckingNode {
 \r
     @Override\r
     public UnitResult getUnits(HashMap<String, String> units, ArrayList<Function> functions, boolean allowEquivalents) throws UnitCheckingException {\r
-        UnitResult result = new UnitResult(allowEquivalents);\r
         UnitCheckingNode functionLabelNode = ((UnitCheckingNode)jjtGetChild(0));\r
         String functionLabel = functionLabelNode.printNode();\r
         \r
@@ -56,186 +51,67 @@ public class FunctionCall extends UnitCheckingNode {
                        ArrayList<Input> inputs = f.getInputList();\r
                        // Get arguments from parser.\r
                        ArrayList<Pair<UnitResult, String>> argumentUnits = getArgumentsOf(functionArgumentsNode, units, functions, allowEquivalents);\r
-                       \r
-                       //HashMap<String, String> correspondencies = getReplacementUnitCorrespondencies(inputs, argumentUnits);\r
-\r
-                       // If the function input parameter units are OK, set the output unit accordingly.  \r
-                       if (matchInputs(inputs, argumentUnits, f, functions, allowEquivalents, units)) {\r
-                               Output o = f.getOutputList().get(0); // Support only single return value functions.\r
-                               result = getUnitResult(units, result, inputs, argumentUnits, f, functions, allowEquivalents, o);\r
-                               return result;\r
+                       // Get correspondent actual units for unit templates in inputs.\r
+                       HashMap<String, String> correspondences = getReplacementUnitCorrespondences(inputs, argumentUnits);\r
+\r
+                       try {\r
+                               // If the function input parameter units are OK, set the output unit accordingly.  \r
+                               if (f.areArgumentUnitsValid(argumentUnits, correspondences, functions, allowEquivalents, units)) {\r
+                                       Output o = f.getOutputList().get(0); // Support only single return value functions.\r
+                                       return o.getUnitResult(units, f, functions, allowEquivalents, correspondences);\r
+                               }\r
+                               \r
+                               // Create the exception when we first time arrive here, since the functions\r
+                               // are sorted so that scalar functions are before vector ones and there are\r
+                               // a few duplicates, so this way the exception message is more likely useful.\r
+                               if (u == null)\r
+                                       u = getException(functionLabelNode, inputs, argumentUnits);\r
+                               // There may be similarly named functions, so if one doesn't succeed, go through the rest of the list anyway.\r
+                       } catch (UnitCheckingException e) {\r
+                               u = e; // Override the general exception with a more specific one.\r
                        }\r
-                       \r
-                       // Create the exception when we first time arrive here, since the functions\r
-                       // are sorted so that scalar functions are before vector ones and there are\r
-                       // a few duplicates, so this way the exception message is more likely useful.\r
-                       if (u == null)\r
-                               u = getException(functionLabelNode, inputs, argumentUnits);\r
-                       // There may be similarly named functions, so if one doesn't succeed, go through the rest of the list anyway.\r
                }\r
         }\r
         \r
         if (u == null) {\r
-               result.setUnitType(UnitType.ANY); // The function was not found.\r
+               UnitResult result = new UnitResult(allowEquivalents);\r
+            result.setUnitType(UnitType.ANY); // The function was not found.\r
                return result;\r
         }\r
         \r
         throw u;\r
     }\r
 \r
-       private static UnitResult getUnitResult(\r
-                       HashMap<String, String> units, \r
-                       UnitResult result, \r
-                       ArrayList<Input> inputs, \r
-                       ArrayList<Pair<UnitResult,String>> argumentUnits, \r
-                       Function f,\r
-                       ArrayList<Function> functions,\r
-                       boolean allowEquivalents,\r
-                       Parameter p) throws UnitCheckingException {\r
-               \r
-               if (Parameter.ANY.equals(p.unit)) {\r
-                       result.setUnitType(UnitType.ANY);\r
-               } else if ("1".equals(p.unit)) { // TODO: see if this should be changed to something else\r
-                       result.setUnitType(UnitType.SCALAR);\r
-               } else {\r
-                       // Replace TIME with actual time unit.\r
-                       String timeUnit = units.get("time");\r
-                       String timeReplaced = p.unit.replace(Parameter.TIME, timeUnit);\r
-\r
-                       // Replace 'p, 'q, etc. in output with units from actual inputs.\r
-                       HashMap<String, String> correspondencies = getReplacementUnitCorrespondencies(inputs, argumentUnits);\r
-                       String correspondenciesReplaced = replaceCorrespondencies(f, timeReplaced, correspondencies);\r
-\r
-                       try {\r
-                               StringReader outputReader = new StringReader(correspondenciesReplaced);\r
-                               UnitParser outputParser = new UnitParser(outputReader);\r
-                               UnitCheckingNode output;\r
-                               output = (UnitCheckingNode) outputParser.expr();\r
-                               outputReader.close();\r
-                   result.appendResult(output.getUnits(null, functions, allowEquivalents));\r
-                               result.setUnitType(UnitType.NORMAL);\r
-                       } catch (ParseException e) {\r
-                               e.printStackTrace();\r
-                       } catch (UnitCheckingException e) {\r
-                               e.printStackTrace();\r
-                       }\r
-                       \r
-               }\r
-               \r
-        return result;\r
-       }\r
-\r
-       private static String replaceCorrespondencies(Function f, String original,\r
-                       HashMap<String, String> correspondencies) throws UnitCheckingException {\r
-               int index;\r
-               String ret = new String(original);\r
-               // Go through the unit as long as there are part of form 'p, 'q, etc. and replace them.\r
-               while ((index = ret.indexOf('\'')) >= 0) {\r
-                       String replaced = ret.substring(index, index + 2); // The replaced units are always of length 2.\r
-                       try {\r
-                               ret = ret.replace(replaced, correspondencies.get(replaced));\r
-                       } catch (NullPointerException npe) {\r
-                               throw new UnitCheckingException("Function " + f.getName() + " output unit could not be determined. Replacement unit " \r
-                                               + replaced + " not found in input unit definitions.");\r
-                       }\r
-               }\r
-               return ret;\r
-       }\r
-\r
-       private static HashMap<String, String> getReplacementUnitCorrespondencies(\r
+       private static HashMap<String, String> getReplacementUnitCorrespondences(\r
                        ArrayList<Input> inputs, ArrayList<Pair<UnitResult, String>> argumentUnits) {\r
                // TODO: General case. Currently each 'p, 'q etc. must exist as an atomic unit.\r
-               HashMap<String, String> correspondencies = new HashMap<String, String>();\r
+               HashMap<String, String> correspondences = new HashMap<String, String>();\r
                for (int i = 0; i < argumentUnits.size(); ++i) {\r
                        if (argumentUnits.get(i).second != null) { // named arguments\r
                                for (Input input : inputs) {\r
                                        if (input.name.equals(argumentUnits.get(i).second)) {\r
-                                               addPossibleCorrespondence(correspondencies, input, argumentUnits.get(i).first);\r
+                                               addPossibleCorrespondence(correspondences, input, argumentUnits.get(i).first);\r
                                        }\r
                                }\r
                        } else if (inputs.size() > i) {\r
-                               addPossibleCorrespondence(correspondencies, inputs.get(i), argumentUnits.get(i).first);\r
+                               addPossibleCorrespondence(correspondences, inputs.get(i), argumentUnits.get(i).first);\r
                        }\r
                }\r
                // If there is no mapping between the actual and defined inputs (e.g. 'p refers to both m and s),\r
                // then return the latest correspondence. The users of this functions currently assume such behavior.\r
-               return correspondencies;\r
+               return correspondences;\r
        }\r
 \r
-       private static void addPossibleCorrespondence(HashMap<String, String> correspondencies,\r
+       private static void addPossibleCorrespondence(HashMap<String, String> correspondences,\r
                        Input input, UnitResult argumentUnit) {\r
                if (input.unit.matches("'[a-zA-Z]")) {\r
                        String fullUnit = argumentUnit.getCleanFullUnit();\r
                        if ("".equals(fullUnit))\r
                                fullUnit = "1";\r
-                       correspondencies.put(input.unit, fullUnit);\r
+                       correspondences.put(input.unit, fullUnit);\r
                }\r
        }\r
 \r
-       private boolean matchInputs(ArrayList<Input> inputs,\r
-                       ArrayList<Pair<UnitResult, String>> argumentUnits, Function f, ArrayList<Function> functions, boolean allowEquivalents, HashMap<String, String> units) throws UnitCheckingException {\r
-               ArrayList<Boolean> inputMatches = new ArrayList<Boolean>();\r
-               for (int i = 0; i < inputs.size(); ++i)\r
-                       inputMatches.add(Boolean.FALSE);\r
-               \r
-               for (int i = 0; i < argumentUnits.size(); ++i) { // Go through all _arguments_\r
-                       if (inputs.size() == 0)\r
-                               return false; // No inputs expected but got at least one\r
-                       \r
-                       if (argumentUnits.get(i).second != null) { // Named argument\r
-                               boolean found = false;\r
-                               for (int j = 0; j < inputs.size(); ++j) {\r
-                                       Input namedInput = inputs.get(j);\r
-                                       if (namedInput.name.equals(argumentUnits.get(i).second)) {\r
-                                               // Match input unit to argument unit\r
-                                               UnitResult inputUnit = new UnitResult(allowEquivalents);\r
-                                               inputUnit = getUnitResult(units, inputUnit, inputs, argumentUnits, f, functions, allowEquivalents, namedInput);\r
-                                               if (!inputUnit.equals(argumentUnits.get(i).first))\r
-                                                       return false;\r
-                                               inputMatches.set(j, Boolean.TRUE);\r
-                                               found = true;\r
-                                               break;\r
-                                       }\r
-                               }\r
-                               if (!found) {\r
-                                       throw new UnitCheckingException("Undefined input argument " + argumentUnits.get(i).second \r
-                                                       + " used in function " + f.getName() + ".");\r
-                               }\r
-                       } else { // Position argument\r
-                               if (i >= inputs.size()) { // Test for variable length argument\r
-                                       // Assume there can be only one variable length input and its in the end.\r
-                                       // And that there cannot be any optional arguments before.\r
-                                       if (inputs.get(inputs.size() - 1).variableLength) {\r
-                                               // Match input unit to argument unit\r
-                                               UnitResult inputUnit = new UnitResult(allowEquivalents);\r
-                                               inputUnit = getUnitResult(units, inputUnit, inputs, argumentUnits, f, functions, allowEquivalents, inputs.get(inputs.size() - 1));\r
-                                               if (!inputUnit.equals(argumentUnits.get(i).first))\r
-                                                       return false;\r
-                                               // The corresponding _input_ has already been gone through, no need to set true.\r
-                                       } else {\r
-                                               return false;\r
-                                       }\r
-                               } else {\r
-                                       // Match input unit to argument unit\r
-                                       UnitResult inputUnit = new UnitResult(allowEquivalents);\r
-                                       inputUnit = getUnitResult(units, inputUnit, inputs, argumentUnits, f, functions, allowEquivalents, inputs.get(i));\r
-                                       if (!inputUnit.equals(argumentUnits.get(i).first))\r
-                                               return false;\r
-                                       inputMatches.set(i, Boolean.TRUE);\r
-                               }\r
-                       }\r
-               }\r
-               \r
-               // See if some of the required inputs has not been defined. \r
-               for (int i = 0; i < inputs.size(); ++i) {\r
-                       if (!inputMatches.get(i) && !inputs.get(i).optional) {\r
-                               return false;\r
-                       }\r
-               }\r
-               \r
-               return true;\r
-       }\r
-\r
        /**\r
         * Get the exception (text) shown to user.\r
         * @param functionLabelNode\r
@@ -243,7 +119,9 @@ public class FunctionCall extends UnitCheckingNode {
         * @param argumentUnits\r
         * @return\r
         */\r
-       private UnitCheckingException getException(UnitCheckingNode functionLabelNode, ArrayList<Input> inputs, ArrayList<Pair<UnitResult,String>> argumentUnits) {\r
+       private static UnitCheckingException getException(UnitCheckingNode functionLabelNode, \r
+                       ArrayList<Input> inputs, \r
+                       ArrayList<Pair<UnitResult, String>> argumentUnits) {\r
        String exception = new String("Function arguments do not have correct units. Expected " + functionLabelNode.printNode() + "(");\r
                \r
        for (int i = 0; i < inputs.size(); ++i) {\r
@@ -274,7 +152,7 @@ public class FunctionCall extends UnitCheckingNode {
                return new UnitCheckingException(exception);\r
        }\r
 \r
-    private ArrayList<Pair<UnitResult, String>> getArgumentsOf(\r
+    private static ArrayList<Pair<UnitResult, String>> getArgumentsOf(\r
                        UnitCheckingNode functionArgumentsNode, \r
                        HashMap<String, String> units, \r
                        ArrayList<Function> functions, \r
@@ -298,7 +176,7 @@ public class FunctionCall extends UnitCheckingNode {
                return argumentUnits;\r
        }\r
 \r
-       private ArrayList<Pair<UnitResult, String>> getNamedArgumentsOf(\r
+       private static ArrayList<Pair<UnitResult, String>> getNamedArgumentsOf(\r
                        UnitCheckingNode namedArgumentsNode, \r
                        HashMap<String, String> units, \r
                        ArrayList<Function> functions, \r
index 90d202399ad1ae8567c5b50bc2269298d4d98ebb..8fb00e5aee18ab985076234487a47506ccf16443 100644 (file)
@@ -1,7 +1,9 @@
 package org.simantics.sysdyn.utils;\r
 \r
+import java.io.StringReader;\r
 import java.util.ArrayList;\r
 import java.util.Collection;\r
+import java.util.HashMap;\r
 \r
 import org.simantics.databoard.Bindings;\r
 import org.simantics.db.ReadGraph;\r
@@ -13,7 +15,14 @@ import org.simantics.db.exception.DatabaseException;
 import org.simantics.db.request.Read;\r
 import org.simantics.layer0.Layer0;\r
 import org.simantics.sysdyn.SysdynResource;\r
+import org.simantics.sysdyn.unitParser.ParseException;\r
+import org.simantics.sysdyn.unitParser.UnitCheckingException;\r
+import org.simantics.sysdyn.unitParser.UnitCheckingNode;\r
+import org.simantics.sysdyn.unitParser.UnitParser;\r
+import org.simantics.sysdyn.unitParser.nodes.UnitResult;\r
+import org.simantics.sysdyn.unitParser.nodes.UnitResult.UnitType;\r
 import org.simantics.ui.SimanticsUI;\r
+import org.simantics.utils.datastructures.Pair;\r
 \r
 \r
 /**\r
@@ -35,6 +44,61 @@ public class Function implements Comparable<Function>{
        public static final String TIME = "TIME";\r
        public String name;\r
        public String unit = ANY;\r
+       \r
+       public UnitResult getUnitResult(\r
+                       HashMap<String, String> units, \r
+                       Function f,\r
+                       ArrayList<Function> functions,\r
+                       boolean allowEquivalents,\r
+                       HashMap<String, String> correspondences) throws UnitCheckingException {\r
+               \r
+               UnitResult result = new UnitResult(allowEquivalents);\r
+                       if (Parameter.ANY.equals(this.unit)) {\r
+                       result.setUnitType(UnitType.ANY);\r
+               } else if ("1".equals(this.unit)) { // TODO: see if this should be changed to something else\r
+                       result.setUnitType(UnitType.SCALAR);\r
+               } else {\r
+                       // Replace TIME with actual time unit.\r
+                       String timeUnit = units.get("time");\r
+                       String timeReplaced = this.unit.replace(Parameter.TIME, timeUnit);\r
+\r
+                       // Replace 'p, 'q, etc. in output with units from actual inputs.\r
+                       String correspondencesReplaced = replaceCorrespondences(f, timeReplaced, correspondences);\r
+\r
+                       try {\r
+                               StringReader outputReader = new StringReader(correspondencesReplaced);\r
+                               UnitParser outputParser = new UnitParser(outputReader);\r
+                               UnitCheckingNode output;\r
+                               output = (UnitCheckingNode) outputParser.expr();\r
+                               outputReader.close();\r
+                   result.appendResult(output.getUnits(null, functions, allowEquivalents));\r
+                               result.setUnitType(UnitType.NORMAL);\r
+                       } catch (ParseException e) {\r
+                               e.printStackTrace();\r
+                       } catch (UnitCheckingException e) {\r
+                               e.printStackTrace();\r
+                       }\r
+               }\r
+               \r
+            return result;\r
+       }\r
+       \r
+       private static String replaceCorrespondences(Function f, String original,\r
+                       HashMap<String, String> correspondences) throws UnitCheckingException {\r
+               int index;\r
+               String ret = new String(original);\r
+               // Go through the unit as long as there are part of form 'p, 'q, etc. and replace them.\r
+               while ((index = ret.indexOf('\'')) >= 0) {\r
+                       String replaced = ret.substring(index, index + 2); // The replaced units are always of length 2.\r
+                       try {\r
+                               ret = ret.replace(replaced, correspondences.get(replaced));\r
+                       } catch (NullPointerException npe) {\r
+                               throw new UnitCheckingException("Function " + f.getName() + " output unit could not be determined. Replacement unit " \r
+                                               + replaced + " not found in input unit definitions.");\r
+                       }\r
+               }\r
+               return ret;\r
+       }\r
     }\r
 \r
     public static class Input extends Parameter {\r
@@ -418,4 +482,79 @@ public class Function implements Comparable<Function>{
         return description.replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\n", "<BR>");\r
     }\r
 \r
+    /**\r
+     * Determine if the units of the given arguments are valid\r
+     * @param argumentUnits validated arguments\r
+     * @param correspondences mapping from templates to actual arguments\r
+     * @param functions all functions available\r
+     * @param allowEquivalents true iff equivalent units are used (e.g. $ == dollar)\r
+     * @param units evm\r
+     * @return true iff argument units are valid\r
+     * @throws UnitCheckingException if \r
+     */\r
+    public boolean areArgumentUnitsValid(ArrayList<Pair<UnitResult, String>> argumentUnits, \r
+                       HashMap<String, String> correspondences,\r
+                       ArrayList<Function> functions, \r
+                       boolean allowEquivalents, \r
+                       HashMap<String, \r
+                       String> units) throws UnitCheckingException {\r
+               ArrayList<Boolean> inputMatches = new ArrayList<Boolean>(); // Table for matching inputs.\r
+               for (int i = 0; i < inputList.size(); ++i)\r
+                       inputMatches.add(Boolean.FALSE);\r
+               \r
+               for (int i = 0; i < argumentUnits.size(); ++i) { // Go through all _arguments_\r
+                       if (inputList.size() == 0)\r
+                               return false; // No inputs expected but got at least one\r
+                       \r
+                       if (argumentUnits.get(i).second != null) { // Named argument\r
+                               boolean found = false;\r
+                               for (int j = 0; j < inputList.size(); ++j) {\r
+                                       Input namedInput = inputList.get(j);\r
+                                       if (namedInput.name.equals(argumentUnits.get(i).second)) {\r
+                                               // Match input unit to argument unit\r
+                                               UnitResult inputUnit = namedInput.getUnitResult(units, this, functions, allowEquivalents, correspondences);\r
+                                               if (!inputUnit.equals(argumentUnits.get(i).first))\r
+                                                       return false;\r
+                                               inputMatches.set(j, Boolean.TRUE);\r
+                                               found = true;\r
+                                               break;\r
+                                       }\r
+                               }\r
+                               if (!found) {\r
+                                       throw new UnitCheckingException("Undefined input argument " + argumentUnits.get(i).second \r
+                                                       + " used in function " + this.getName() + ".");\r
+                               }\r
+                       } else { // Position argument\r
+                               if (i >= inputList.size()) { // Test for variable length argument\r
+                                       // Assume there can be only one variable length input and its in the end.\r
+                                       // And that there cannot be any optional arguments before.\r
+                                       Input input = inputList.get(inputList.size() - 1);\r
+                                       if (input.variableLength) {\r
+                                               // Match input unit to argument unit\r
+                                               UnitResult inputUnit = input.getUnitResult(units, this, functions, allowEquivalents, correspondences);\r
+                                               if (!inputUnit.equals(argumentUnits.get(i).first))\r
+                                                       return false;\r
+                                               // The corresponding _input_ has already been gone through, no need to set true.\r
+                                       } else {\r
+                                               return false;\r
+                                       }\r
+                               } else {\r
+                                       // Match input unit to argument unit\r
+                                       UnitResult inputUnit = inputList.get(i).getUnitResult(units, this, functions, allowEquivalents, correspondences);\r
+                                       if (!inputUnit.equals(argumentUnits.get(i).first))\r
+                                               return false;\r
+                                       inputMatches.set(i, Boolean.TRUE);\r
+                               }\r
+                       }\r
+               }\r
+               \r
+               // See if some of the required inputs has not been defined. \r
+               for (int i = 0; i < inputList.size(); ++i) {\r
+                       if (!inputMatches.get(i) && !inputList.get(i).optional) {\r
+                               return false;\r
+                       }\r
+               }\r
+               \r
+               return true;\r
+       }\r
 }\r