summaryrefslogtreecommitdiffstats
path: root/dcaedt_validator/checker/src/main/java/org/onap/sdc/dcae/checker/Data.java
diff options
context:
space:
mode:
Diffstat (limited to 'dcaedt_validator/checker/src/main/java/org/onap/sdc/dcae/checker/Data.java')
-rw-r--r--dcaedt_validator/checker/src/main/java/org/onap/sdc/dcae/checker/Data.java895
1 files changed, 895 insertions, 0 deletions
diff --git a/dcaedt_validator/checker/src/main/java/org/onap/sdc/dcae/checker/Data.java b/dcaedt_validator/checker/src/main/java/org/onap/sdc/dcae/checker/Data.java
new file mode 100644
index 0000000..70552bb
--- /dev/null
+++ b/dcaedt_validator/checker/src/main/java/org/onap/sdc/dcae/checker/Data.java
@@ -0,0 +1,895 @@
+package org.onap.sdc.dcae.checker;
+
+import java.util.Collection;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.EnumSet;
+
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import com.google.common.collect.Table;
+import com.google.common.collect.HashBasedTable;
+import org.onap.sdc.common.onaplog.OnapLoggerDebug;
+import org.onap.sdc.common.onaplog.OnapLoggerError;
+import org.onap.sdc.common.onaplog.Enums.LogLevel;
+
+/*
+ * String -- 'primitive tosca type' converters, used in verifying valuations
+ */
+public class Data {
+ private static OnapLoggerError errLogger = OnapLoggerError.getInstance();
+ private static OnapLoggerDebug debugLogger = OnapLoggerDebug.getInstance();
+
+ private Data() {
+ }
+
+ /*
+ */
+ @FunctionalInterface
+ public static interface Evaluator {
+
+ public boolean eval(Object theExpr, Map theDef, Checker.CheckContext theCtx);
+ }
+
+
+ /* data type processing */
+
+ private static Map<String,Type> typesByName = new HashMap<String,Type>();
+ static {
+ //CoreType.String.toString();
+ //CoreFunction.concat.toString();
+ //Constraint.equal.toString();
+ }
+
+
+ public static Data.Type typeByName(String theName) {
+ return typesByName.getOrDefault(theName, userType);
+ }
+/*
+ public static Evaluator getTypeEvaluator(Type theType) {
+ }
+*/
+
+ /* Needs a better name ?? RValue??
+ * This is not an rvalue (C def) per se but the construct who's instances
+ * yield rvalues. It is a construct that yields data, not the data (yield)
+ * itself.
+ */
+ public static interface Type {
+
+ public String name();
+
+ public Evaluator evaluator();
+
+ public Evaluator constraintsEvaluator();
+ }
+
+ /* generic placeholder
+ */
+ private static Type userType = new Type() {
+
+ public String name() {
+ return null;
+ }
+
+ public Evaluator evaluator() {
+ return Data::evalUser;
+ }
+
+ public Evaluator constraintsEvaluator() {
+ return Data::evalUserConstraints;
+ }
+ };
+
+
+ public static enum CoreType implements Type {
+
+ String("string",
+ (expr,def,ctx) -> expr != null && expr instanceof String,
+ Data::evalScalarConstraints),
+ Integer("integer",
+ (expr,def,ctx) -> Data.valueOf(ctx, expr, Integer.class),
+ Data::evalScalarConstraints),
+ Float("float",
+ (expr,def,ctx) -> Data.valueOf(ctx, expr, Double.class, Integer.class),
+ Data::evalScalarConstraints),
+ Boolean("boolean",
+ (expr,def,ctx) -> Data.valueOf(ctx, expr, Boolean.class),
+ Data::evalScalarConstraints),
+ Null("null",
+ (expr,def,ctx) -> expr.equals("null"),
+ null),
+ Timestamp("timestamp",
+ (expr,def,ctx) -> timestampRegex.matcher(expr.toString()).matches(),
+ null),
+ List("list", Data::evalList, Data::evalListConstraints),
+ Map("map", Data::evalMap, Data::evalMapConstraints),
+ Version("version",
+ (expr,def,ctx) -> versionRegex.matcher(expr.toString()).matches(),
+ null),
+ /* use a scanner and check that the upper bound is indeed greater than
+ * the lower bound */
+ Range("range",
+ (expr,def,ctx) -> { return rangeRegex.matcher(expr.toString()).matches();},
+ null ),
+ Size("scalar-unit.size",
+ (expr,def,ctx) -> sizeRegex.matcher(expr.toString()).matches(),
+ null),
+ Time("scalar-unit.time",
+ (expr,def,ctx) -> timeRegex.matcher(expr.toString()).matches(),
+ null),
+ Frequency("scalar-unit.frequency",
+ (expr,def,ctx) -> frequencyRegex.matcher(expr.toString()).matches(),
+ null);
+
+
+ private String toscaName;
+ private Evaluator valueEvaluator,
+ constraintsEvaluator;
+
+ private CoreType(String theName, Evaluator theValueEvaluator, Evaluator theConstraintsEvaluator) {
+ this.toscaName = theName;
+ this.valueEvaluator = theValueEvaluator;
+ this.constraintsEvaluator = theConstraintsEvaluator;
+
+ if (typesByName == null)
+ throw new RuntimeException("No type index available!");
+
+ typesByName.put(this.toscaName, this);
+ }
+
+ public String toString() {
+ return this.toscaName;
+ }
+
+ public Evaluator evaluator() {
+ return this.valueEvaluator;
+ }
+
+ public Evaluator constraintsEvaluator() {
+ return this.constraintsEvaluator;
+ }
+ }
+
+ private static Pattern timestampRegex = null,
+ versionRegex = null,
+ rangeRegex = null,
+ sizeRegex = null,
+ timeRegex = null,
+ frequencyRegex = null;
+
+ static {
+ try {
+ timestampRegex = Pattern.compile(
+ "\\p{Digit}+"); //?? where to find the definition
+
+ //<major_version>.<minor_version>[.<fix_version>[.<qualifier>[-<build_version]]]
+ versionRegex = Pattern.compile(
+ "\\p{Digit}+\\.\\p{Digit}+?(\\.\\p{Digit}+(\\.\\p{Alpha}+(\\-\\p{Digit}+))*)*");
+
+ rangeRegex = Pattern.compile(
+ "\\[[ ]*\\p{Digit}+(\\.\\p{Digit}+)?[ ]*\\,[ ]*(\\p{Digit}+(\\.\\p{Digit}+)?|UNBOUNDED)[ ]*\\]");
+
+ sizeRegex = Pattern.compile(
+ "\\p{Digit}+(\\.\\p{Digit}+)?[ ]*(B|kB|KiB|MB|MiB|GB|GiB|TB|TiB)");
+
+ timeRegex = Pattern.compile(
+ "\\p{Digit}+(\\.\\p{Digit}+)?[ ]*(d|h|m|s|ms|us|ns)");
+
+ frequencyRegex = Pattern.compile(
+ "\\p{Digit}+(\\.\\p{Digit}+)?[ ]*(Hz|kHz|MHz|GHz)");
+ }
+ catch (PatternSyntaxException psx) {
+ throw new RuntimeException("Bad patterns", psx);
+ }
+ }
+
+ /* */
+ public static boolean evalScalarConstraints(Object theVal,
+ Map theDef,
+ Checker.CheckContext theCtx) {
+ Data.Type type = typeByName((String)theDef.get("type"));
+ List<Map<String,Object>> constraints =
+ (List<Map<String,Object>>)theDef.get("constraints");
+ if (constraints == null) {
+ return true;
+ }
+
+ //check value against constraints
+ boolean res = true;
+ for (Map<String,Object> constraintDef: constraints) {
+ Map.Entry<String,Object> constraintEntry =
+ constraintDef.entrySet().iterator().next();
+ Data.Constraint constraint = constraintByName(constraintEntry.getKey());
+
+// the def passed here includes all constraints, not necessary! we can pass
+// simple constraintEntry.getValue()
+ Evaluator constraintEvaluator = getTypeConstraintEvaluator(type, constraint);
+ if (constraintEvaluator == null) {
+ debugLogger.log(LogLevel.DEBUG, Data.class.getName(), "No constant evaluator available for {}/{}", type, constraint);
+ continue;
+ }
+
+ if (!constraintEvaluator.eval(theVal, theDef, theCtx)) {
+ theCtx.addError("Value " + theVal + " failed constraint " + constraintEntry, null);
+ res = false;
+ }
+ }
+ return res;
+ }
+
+ /*
+ * It assumes the specification is complete, i.e. it contains a valid
+ * entry_schema section.
+ * TODO: check constraints, i.e. entrySchema.get("constraints")
+ */
+ public static boolean evalList(Object theVal,
+ Map theDef,
+ Checker.CheckContext theCtx) {
+ try {
+ return evalCollection((List)theVal, theDef, theCtx);
+ }
+ catch (ClassCastException ccx) {
+ theCtx.addError("Value " + theVal + " not a list", null);
+ return false;
+ }
+ }
+
+ public static boolean evalMap(Object theVal,
+ Map theDef,
+ Checker.CheckContext theCtx) {
+ try {
+ return evalCollection(((Map)theVal).values(), theDef, theCtx);
+ }
+ catch (ClassCastException ccx) {
+ theCtx.addError("Value " + theVal + " not a map", null);
+ return false;
+ }
+ }
+
+
+ /**
+ * The elements of a collection can be of a core type or user defined type.
+ */
+ private static boolean evalCollection(Collection theVals,
+ Map theDef,
+ Checker.CheckContext theCtx) {
+ Data.Type entryType = null;
+ Map entryTypeDef = (Map)theDef.get("entry_schema");
+ if (null != entryTypeDef)
+ entryType = typeByName((String)entryTypeDef.get("type"));
+
+ boolean res = true;
+ for (Object val: theVals) {
+ //check if the value is not a function call
+ Data.Function f = Data.function(val);
+ if (f != null &&
+ f.evaluator().eval(val, entryTypeDef, theCtx)) {
+ res = false;
+ }
+ else if (entryType != null &&
+ !entryType.evaluator().eval(val, entryTypeDef, theCtx)) {
+ res= false;
+ //the error should hav been reported by the particular evaluator
+ //theCtx.addError("Value " + val + " failed evaluation", null);
+ }
+ }
+ return res;
+ }
+
+ public static boolean evalListConstraints(Object theVal,
+ Map theDef,
+ Checker.CheckContext theCtx) {
+ return evalCollectionConstraints((List)theVal, theDef, theCtx);
+ }
+
+ public static boolean evalMapConstraints(Object theVal,
+ Map theDef,
+ Checker.CheckContext theCtx) {
+ return evalCollectionConstraints(((Map)theVal).values(), theDef, theCtx);
+ }
+
+ private static boolean evalCollectionConstraints(Collection theVals,
+ Map theDef,
+ Checker.CheckContext theCtx) {
+ //should check overall constraints
+
+ if (theVals == null)
+ return true;
+
+ Map entryTypeDef = (Map)theDef.get("entry_schema");
+ if (null == entryTypeDef)
+ return true;
+
+ String entryTypeName = (String)entryTypeDef.get("type");
+ Data.Type entryType = typeByName(entryTypeName);
+
+ boolean res = true;
+ for (Object val: theVals) {
+ Evaluator entryEvaluator = entryType.constraintsEvaluator();
+ if (entryEvaluator != null &&
+ !entryEvaluator.eval(val, entryTypeDef, theCtx)) {
+ res= false;
+ //the constraints evaluator should have already added an error, but it also adds some context
+ //theCtx.addError("Value " + val + " failed evaluation", null);
+ }
+ }
+ return res;
+ }
+
+ /*
+ * All required properties across the hierarchical defintion must be present
+ * TODO: The expr cannot contain any entry not specified in the type definition
+ */
+ public static boolean evalUser(Object theVal,
+ Map theDef,
+ Checker.CheckContext theCtx) {
+
+ boolean res = true;
+ Map val = (Map)theVal;
+ //must be done with respect to the super-type(s) definition
+ Iterator<Map.Entry> props = theCtx.catalog()
+ .facets(Construct.Data,
+ Facet.properties,
+ (String)theDef.get("type"));
+ while (props.hasNext()) {
+ Map.Entry propEntry = props.next();
+ Map propDef = (Map)propEntry.getValue();
+ Object propVal = val.get(propEntry.getKey());
+
+ if (propVal != null) {
+ Data.Type propType = typeByName((String)propDef.get("type"));
+
+ if (!propType.evaluator().eval(propVal, propDef, theCtx)) {
+ res= false;
+ //the constraints evaluator should have already added an error
+ //theCtx.addError("Property " + propEntry.getKey() + " failed evaluation for " + propVal, null);
+ }
+ }
+ }
+ return res;
+ }
+
+ public static boolean evalUserConstraints(Object theVal,
+ Map theDef,
+ Checker.CheckContext theCtx) {
+ boolean res = true;
+ Map val = (Map)theVal;
+ Iterator<Map.Entry> props = theCtx.catalog()
+ .facets(Construct.Data,
+ Facet.properties,
+ (String)theDef.get("type"));
+ while (props.hasNext()) {
+ Map.Entry propEntry = props.next();
+ Map propDef = (Map)propEntry.getValue();
+ Object propVal = val.get(propEntry.getKey());
+
+ if (propVal != null) {
+ Data.Type propType = typeByName((String)propDef.get("type"));
+
+ if (propType.constraintsEvaluator() != null &&
+ !propType.constraintsEvaluator().eval(propVal, propDef, theCtx)) {
+ res= false;
+ //the constraints evaluator should have already added an error
+ //theCtx.addError("Property " + propEntry.getKey() + " failed evaluation for " + propVal, null);
+ }
+ }
+ else {
+ if (Boolean.TRUE == (Boolean)propDef.getOrDefault("required", Boolean.FALSE) &&
+ !propDef.containsKey("default")) {
+ theCtx.addError("Property " + propEntry.getKey() + " failed 'required' constraint; definition is " + propDef, null);
+ res = false;
+ }
+ }
+ }
+ return res;
+ }
+
+ private static boolean valueOf(Checker.CheckContext theCtx,
+ Object theExpr,
+ Class ... theTypes) {
+ for (Class type: theTypes) {
+ if (type.isAssignableFrom(theExpr.getClass())) {
+ return true;
+ }
+ }
+
+ theCtx.addError("Expression " + theExpr + " as " + theExpr.getClass().getName() + " is not compatible with any of required types: " + Arrays.toString(theTypes), null);
+ return false;
+ }
+
+/*
+ private static boolean valueOf(Class theTarget,
+ String theExpr,
+ Checker.CheckContext theCtx) {
+ try {
+ theTarget.getMethod("valueOf", new Class[] {String.class})
+ .invoke(null, theExpr);
+ return true;
+ }
+ catch (InvocationTargetException itx) {
+ theCtx.addError("Failed to parse " + theExpr + " as a " + theTarget.getName(), itx.getCause());
+ return false;
+ }
+ catch (Exception x) {
+ theCtx.addError("Failed to valueOf " + theExpr + " as a " + theTarget.getName(), x);
+ return false;
+ }
+ }
+*/
+
+ /*
+ * Function e(valuation)
+ * ?
+ * note to self : is there a more efficient way of retrieving a map's
+ * single entry? (without knowing the key)
+ *
+ * ! Function evaluators have to handle null definition (i.e. perform argument checking) so that
+ * we can use them in the context of collections with without entry_schemas
+ */
+
+ //just as Type but is it worth expressing this 'commonality'??
+
+ public static interface Function {
+
+ public String name();
+
+ public Evaluator evaluator();
+ }
+
+ /*
+ * This is a heuristic induced from the tosca specification .. it answers the
+ * question of wether the given expression is a function
+ */
+ public static Function function(Object theExpr) {
+ if (theExpr instanceof Map &&
+ ((Map)theExpr).size() == 1) {
+ try {
+ return Enum.valueOf(CoreFunction.class, functionName(theExpr));
+ }
+ catch (IllegalArgumentException iax) {
+ //no such function but we cannot really record an error as we only guessed the expression as being a function ..
+ debugLogger.log(LogLevel.DEBUG, Data.class.getName(), "Failed attempt to interpret {} as a function call", theExpr);
+ }
+ }
+
+ return null;
+ }
+
+ /*
+ */
+ public static String functionName(Object theExpr) {
+ return (String)
+ ((Map.Entry)
+ ((Map)theExpr).entrySet().iterator().next())
+ .getKey();
+ }
+
+ /*
+ */
+ public static Data.Function functionByName(String theName) {
+ return Enum.valueOf(CoreFunction.class, theName);
+ }
+
+ /*
+ */
+ public static enum CoreFunction implements Function {
+
+ concat(Data::evalConcat),
+ token(Data::evalToken),
+ get_input(Data::evalGetInput),
+ get_property(Data::evalGetProperty),
+ get_attribute(Data::evalGetAttribute),
+ get_operation_output((expr,def,ctx) -> true),
+ get_nodes_of_type(Data::evalGetNodesOfType),
+ get_artifact((expr,def,ctx) -> true);
+
+ private Evaluator evaluator;
+
+ private CoreFunction(Evaluator theEval) {
+ this.evaluator = theEval;
+ }
+
+ public Evaluator evaluator() {
+ return this.evaluator;
+ }
+ }
+
+ private static boolean evalConcat(
+ Object theVal, Map theDef, Checker.CheckContext theCtx) {
+ return true;
+ }
+
+ private static boolean evalToken(
+ Object theVal, Map theDef, Checker.CheckContext theCtx) {
+ return true;
+ }
+
+ private static boolean evalGetInput(
+ Object theVal, Map theDef, Checker.CheckContext theCtx) {
+ Map val = (Map)theVal;
+ Map.Entry entry = (Map.Entry)val.entrySet().iterator().next();
+
+ if (!(entry.getValue() instanceof String)) {
+ theCtx.addError("get_input: argument must be a String" ,null);
+ return false;
+ }
+
+ //check that an input with the given name exists and has a compatible type
+ Map inputDef = theCtx.catalog()
+ .getTemplate(theCtx.target(), Construct.Data, (String)entry.getValue());
+ if (inputDef == null) {
+ theCtx.addError("get_input: no such input " + entry.getValue(), null);
+ return false;
+ }
+
+ if (theDef == null)
+ return true;
+
+ //the output must be type compatible with the input
+ String targetType = (String)theDef.get("type");
+ if (targetType != null) {
+ String inputType = (String)inputDef.get("type");
+
+ if (!theCtx.catalog()
+ .isDerivedFrom(Construct.Data, inputType, targetType)) {
+ theCtx.addError("get_input: input type " + inputType + " is incompatible with the target type " + targetType, null);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /*
+ * Who's the smarty that decided to define optional arguments in between
+ * required ones ?!
+ * (factors the evaluation of get_attribute and get_property)
+ */
+ private static boolean evalGetData(
+ Object theVal, Map theDef,
+ EnumSet<Facet> theFacets, Checker.CheckContext theCtx) {
+
+ Map val = (Map)theVal;
+ Map.Entry entry = (Map.Entry)val.entrySet().iterator().next();
+
+ if (!(entry.getValue() instanceof List)) {
+ theCtx.addError("get_property: argument must be a List" ,null);
+ return false;
+ }
+
+ List args = (List)entry.getValue();
+ if (args.size() < 2) {
+ theCtx.addError("'get_property' has at least 2 arguments", null);
+ return false;
+ }
+
+ //the first argument is a node or relationship template
+ String tmpl = (String)args.get(0);
+ Construct tmplConstruct = null;
+ Map tmplSpec = null;
+
+ if ("SELF".equals(tmpl)) {
+ tmpl = theCtx.enclosingConstruct(Construct.Node);
+ if (tmpl == null) {
+ tmpl = theCtx.enclosingConstruct(Construct.Relationship);
+ if (tmpl == null) {
+ theCtx.addError("'get_property' invalid SELF reference: no node or relationship template in scope at " + theCtx.getPath(), null);
+ return false;
+ }
+ else {
+ tmplConstruct = Construct.Relationship;
+ }
+ }
+ else {
+ tmplConstruct = Construct.Node;
+ }
+ tmplSpec = theCtx.catalog().getTemplate(theCtx.target(), tmplConstruct, tmpl);
+ }
+ else if ("SOURCE".equals("tmpl")) {
+ //we are in the scope of a relationship template and this is the source node template.
+ tmpl = theCtx.enclosingConstruct(Construct.Relationship);
+ if (tmpl == null) {
+ theCtx.addError("'get_property' invalid SOURCE reference: no relationship template in scope at " + theCtx.getPath(), null);
+ return false;
+ }
+
+ return true;
+ }
+ else if ("TARGET".equals("tmpl")) {
+ //we are in the scope of a relationship template and this is the target node template.
+ tmpl = theCtx.enclosingConstruct(Construct.Relationship);
+ if (tmpl == null) {
+ theCtx.addError("'get_property' invalid TARGET reference: no relationship template in scope at " + theCtx.getPath(), null);
+ return false;
+ }
+
+ return true;
+ }
+ else if ("HOST".equals("tmpl")) {
+ tmpl = theCtx.enclosingConstruct(Construct.Node);
+ if (tmpl == null) {
+ theCtx.addError("'get_property' invalid HOST reference: no node template in scope at " + theCtx.getPath(), null);
+ return false;
+ }
+
+ return true;
+ }
+ else {
+ //try node template first
+ tmplSpec = theCtx.catalog().getTemplate(theCtx.target(), Construct.Node, tmpl);
+ if (tmplSpec == null) {
+ //try relationship
+ tmplSpec = theCtx.catalog().getTemplate(theCtx.target(), Construct.Relationship, tmpl);
+ if (tmplSpec == null) {
+ theCtx.addError("'get_data' invalid template reference '" + tmpl + "': no node or relationship template with this name", null);
+ return false;
+ }
+ else {
+ tmplConstruct = Construct.Relationship;
+ }
+ }
+ else {
+ tmplConstruct = Construct.Node;
+ }
+ }
+
+ int facetNameIndex = 1;
+ Construct facetConstruct = tmplConstruct; //who's construct the facet is supposed to belong to
+ Map facetConstructSpec = null;
+ String facetConstructType = null;
+
+ if (tmplConstruct.equals(Construct.Node) &&
+ args.size() > 2) {
+ //the second arg might be a capability or requirement name. If it is a
+ //capability than the third argument becomes a property of the
+ //coresponding capability type. If it is a requirement than the
+ //requirement definition indicates a capability who's type has a
+ //property with the name indicated in the third argument ..
+ //
+ //while the spec does not make it explicit this can only take place
+ //if the first argument turned out to be a node template (as relationship
+ //templates/types do not have capabilities/requirements
+ String secondArg = (String)args.get(1);
+ if ((facetConstructSpec = theCtx.catalog().getFacetDefinition(
+ tmplConstruct,
+ (String)tmplSpec.get("type"),
+ Facet.capabilities,
+ secondArg)) != null) {
+ facetNameIndex = 2;
+ facetConstruct = Construct.Capability;
+ facetConstructType = (String)facetConstructSpec.get("type");
+ }
+ else if ((facetConstructSpec = theCtx.catalog().getRequirementDefinition(
+ tmplConstruct,
+ (String)tmplSpec.get("type"),
+ secondArg)) != null) {
+ facetNameIndex = 2;
+ facetConstruct = Construct.Capability;
+
+ //find the specof the capability this requirement points to
+ //TODO: check, can the capability reference be anything else but a capability tyep?
+ facetConstructType = (String)facetConstructSpec.get("capability");
+ }
+ }
+ else {
+ //we'll attempt to handle it as a property of the node template
+ facetConstruct = Construct.Node;
+ facetConstructSpec = tmplSpec;
+ facetConstructType = (String)facetConstructSpec.get("type");
+ }
+
+ //validate the facet name
+ Map facetSpec = null;
+ {
+ String facetName = (String)args.get(facetNameIndex);
+ for (Facet facet: theFacets) {
+ facetSpec = theCtx.catalog()
+ .getFacetDefinition(
+ facetConstruct,
+ facetConstructType,
+ facet,
+ facetName);
+ if (facetSpec != null)
+ break;
+ }
+
+ if (facetSpec == null) {
+//TODO: not the greatest message if the call strated with a requirement ..
+ theCtx.addError("'get_data' invalid reference, '" + facetConstruct + "' " + facetConstructType + " has no " + theFacets + " with name " + facetName, null);
+ return false;
+ }
+ }
+
+ //the rest of the arguments have to resolve to a field of the property's
+ //data type; the propertySpec contains the type specification
+ for (int i = facetNameIndex + 1; i < args.size(); i++) {
+ }
+
+ return true;
+ }
+
+ /**/
+ private static boolean evalGetProperty(
+ Object theVal, Map theDef, Checker.CheckContext theCtx) {
+ return evalGetData(theVal, theDef, EnumSet.of(Facet.properties), theCtx);
+ }
+
+ /*
+ * get_property and get_attribute are identical, just operating on different
+ * facets, with one exception: there is an intrinsec attribute for every
+ * declared property.
+ */
+ private static boolean evalGetAttribute(
+ Object theVal, Map theDef, Checker.CheckContext theCtx) {
+ return evalGetData(theVal, theDef, EnumSet.of(Facet.attributes, Facet.properties), theCtx);
+ }
+
+ private static boolean evalGetNodesOfType(
+ Object theVal, Map theDef, Checker.CheckContext theCtx) {
+
+ Map val = (Map)theVal;
+ Map.Entry entry = (Map.Entry)val.entrySet().iterator().next();
+
+ if (!(entry.getValue() instanceof String)) {
+ theCtx.addError("get_nodes_of_type: argument must be a String", null);
+ return false;
+ }
+
+ String arg = (String)entry.getValue();
+
+ if (null == theCtx.catalog().getTypeDefinition(Construct.Node, arg)) {
+ theCtx.addError("get_nodes_of_type: no such node type " + arg, null);
+ return false;
+ }
+ else {
+ return true;
+ }
+ }
+
+ /* */
+ public static Constraint constraintByName(String theName) {
+ return Enum.valueOf(Constraint.class, theName);
+ }
+
+ /* */
+ public static Constraint constraint(Object theExpr) {
+ if (theExpr instanceof Map &&
+ ((Map)theExpr).size() == 1) {
+ return constraintByName(constraintName(theExpr));
+ }
+
+ return null;
+ }
+
+ /* */
+ public static String constraintName(Object theExpr) {
+ return (String)
+ ((Map.Entry)
+ ((Map)theExpr).entrySet().iterator().next())
+ .getKey();
+ }
+
+ private static Object getConstraintValue(Map theDef,
+ Constraint theConstraint) {
+ List<Map> constraints = (List<Map>)theDef.get("constraints");
+ if (null == constraints)
+ return null;
+
+ for(Map constraint: constraints) {
+ Object val = constraint.get(theConstraint.toString());
+ if (val != null)
+ return val;
+ }
+ return null;
+ }
+
+ public static enum Constraint {
+ equal,
+ greater_than,
+ greater_or_equal,
+ less_than,
+ less_or_equal,
+ in_range,
+ valid_values,
+ length,
+ min_length,
+ max_length,
+ pattern;
+ }
+
+
+ /* hold the constraint evaluators for pairs of type/constraint.
+ * If a pair is not present than the given constraint does not apply
+ * to the type.
+ */
+ private static Table<Type,Constraint,Evaluator> typeConstraintEvaluator =null;
+
+ public static Evaluator
+ getTypeConstraintEvaluator(Type theType, Constraint theConstraint) {
+ if (typeConstraintEvaluator == null) {
+ typeConstraintEvaluator = HashBasedTable.create();
+
+ typeConstraintEvaluator.put(CoreType.String, Constraint.equal,
+ (val,def,ctx) -> val.equals(getConstraintValue(def,Constraint.equal)));
+ typeConstraintEvaluator.put(CoreType.String, Constraint.valid_values,
+ (val,def,ctx) -> {
+ return ((List)getConstraintValue(def,Constraint.valid_values)).contains(val);
+ });
+ typeConstraintEvaluator.put(CoreType.String, Constraint.length,
+ (val,def,ctx) -> ((String)val).length() == ((Number)getConstraintValue(def,Constraint.length)).intValue());
+ typeConstraintEvaluator.put(CoreType.String, Constraint.min_length,
+ (val,def,ctx) -> ((String)val).length() >= ((Number)getConstraintValue(def,Constraint.min_length)).intValue());
+ typeConstraintEvaluator.put(CoreType.String, Constraint.max_length,
+ (val,def,ctx) -> ((String)val).length() <= ((Number)getConstraintValue(def,Constraint.max_length)).intValue());
+ typeConstraintEvaluator.put(CoreType.String, Constraint.pattern,
+ (val,def,ctx) -> Pattern.compile((String)getConstraintValue(def,Constraint.pattern))
+ .matcher((String)val)
+ .matches());
+
+ typeConstraintEvaluator.put(CoreType.Integer, Constraint.equal,
+ (val,def,ctx) -> ((Integer)val).compareTo((Integer)getConstraintValue(def,Constraint.equal)) == 0);
+ typeConstraintEvaluator.put(CoreType.Integer, Constraint.greater_than,
+ (val,def,ctx) -> ((Integer)val).compareTo((Integer)getConstraintValue(def,Constraint.greater_than)) > 0);
+ typeConstraintEvaluator.put(CoreType.Integer, Constraint.greater_or_equal,
+ (val,def,ctx) -> ((Integer)val).compareTo((Integer)getConstraintValue(def,Constraint.greater_or_equal)) >= 0);
+ typeConstraintEvaluator.put(CoreType.Integer, Constraint.less_than,
+ (val,def,ctx) -> ((Integer)val).compareTo((Integer)getConstraintValue(def,Constraint.less_than)) < 0);
+ typeConstraintEvaluator.put(CoreType.Integer, Constraint.less_or_equal,
+ (val,def,ctx) -> ((Integer)val).compareTo((Integer)getConstraintValue(def,Constraint.less_or_equal)) <= 0);
+ typeConstraintEvaluator.put(CoreType.Integer, Constraint.in_range,
+ (val,def,ctx) -> { List<Integer> range = (List<Integer>)getConstraintValue(def, Constraint.in_range);
+ return ((Integer)val).compareTo(range.get(0)) >= 0 &&
+ ((Integer)val).compareTo(range.get(1)) <= 0;
+ });
+ typeConstraintEvaluator.put(CoreType.Integer, Constraint.valid_values,
+ (val,def,ctx) -> ((List<Integer>)getConstraintValue(def, Constraint.valid_values)).contains((Integer)val));
+
+//yaml parser represents yaml floats as java Double and we are even more tolerant as many double values
+//get represented as ints and the parser will substitute an Integer
+ typeConstraintEvaluator.put(CoreType.Float, Constraint.equal,
+ (val,def,ctx) -> ((Number)val).doubleValue() == ((Number)getConstraintValue(def,Constraint.equal)).doubleValue());
+ typeConstraintEvaluator.put(CoreType.Float, Constraint.greater_than,
+ (val,def,ctx) -> ((Number)val).doubleValue() > ((Number)getConstraintValue(def,Constraint.greater_than)).doubleValue());
+ typeConstraintEvaluator.put(CoreType.Float, Constraint.greater_or_equal,
+ (val,def,ctx) -> ((Number)val).doubleValue() >= ((Number)getConstraintValue(def,Constraint.greater_or_equal)).doubleValue());
+ typeConstraintEvaluator.put(CoreType.Float, Constraint.less_than,
+ (val,def,ctx) -> ((Number)val).doubleValue() < ((Number)getConstraintValue(def,Constraint.less_than)).doubleValue());
+ typeConstraintEvaluator.put(CoreType.Float, Constraint.less_or_equal,
+ (val,def,ctx) -> ((Number)val).doubleValue() <= ((Number)getConstraintValue(def,Constraint.less_or_equal)).doubleValue());
+ typeConstraintEvaluator.put(CoreType.Float, Constraint.in_range,
+ (val,def,ctx) -> { List<Number> range = (List<Number>)getConstraintValue(def, Constraint.in_range);
+ return ((Number)val).doubleValue() >= range.get(0).doubleValue() &&
+ ((Number)val).doubleValue() <= range.get(1).doubleValue();
+ });
+ typeConstraintEvaluator.put(CoreType.Float, Constraint.valid_values,
+ (val,def,ctx) -> ((List<Number>)getConstraintValue(def, Constraint.valid_values)).contains((Number)val));
+ }
+
+ Evaluator eval = typeConstraintEvaluator.get(theType, theConstraint);
+
+ return eval == null ? (expr,def,ctx) -> true
+ : eval;
+ }
+
+
+ private static boolean stringValidValues(String theVal,
+ List<String> theValidValues,
+ Checker.CheckContext theCtx) {
+ if (!theValidValues.contains(theVal)) {
+ theCtx.addError("not a valid value: " + theVal + " not part of " + theValidValues, null);
+ return false;
+ }
+
+ return true;
+ }
+
+ public static final void main(String[] theArgs) {
+ Data.CoreType dt = Enum.valueOf(Data.CoreType.class, theArgs[0]);
+ debugLogger.log(LogLevel.DEBUG, Data.class.getName(), "{} > {}", theArgs[1], dt.evaluator().eval(theArgs[1], null, null));
+ }
+}