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