# Intro The Variable interface provides an uniform access model to data in Simantics. It includes * Tree-structured address space for accessing model **structure** and **properties** * Uniform access to model **configuration** and **state** Key use cases include * Browsing of the **model configuration and states** (see [[Model Browser]]) * **Manipulation of objects** (see [[Selection View]]) * Representation of **tabular data** (see [[Spreadsheets]]) Main functional requirements include representation of * **Structural models** with **procedural** features (see [[Structural]]) * **Runtime** data of solvers (see [[Experiments]]) * **Historical** data from experiments * **Ontological** data # Solution **Variable** is a tree-structured view into the Simantics data model. Each variable is either a **child** or a **property** and can further contain own children and properties. The difference between a child and a property is that a property contains a **value**. The variable space browsing methods are used to obtain * all children * children by name * all properties * properties by name * variable by **path** * **parent** variable Other services are * accessing (get/set) the value of a property variable * querying adapter interfaces A set of built-in properties is required for all variables. These properties have also dedicated interface methods. * **URI**, which is an unique string identifier and locator of the variable in the tree structure * **Parent**, which is the tree parent of the variable * **HasName**, which is a local identifier for the variable within its parent. Names are also used to create URIs. * **HasLabel**, which is a short textual representation of the variable * **hasStandardResource**, which returns the context resource in a **standard graph-based child variable** * **Represents**, which is a resource representing the variable **TODO** * **Type**, which returns a single type resource classifying the variable * **Role**, which tells whether the variable is a **child** or a **property** (TODO: could be deprecated) * **DATATYPE**, which returns the data type of a property variable. (TODO: should be HasDatatype) Other properties and the structure of the variable space is configured in the semantic graph or contributed by custom variable implementations. Variables can be located using an **URI**, which * Represents the path from **root variable** (Variables.getRootVariable) into the variable such that ** **var/xx** represents a getChild(unescaped(xx)) query from var ** **var#yy** represents a getProperty(unescaped(yy)) query from var ** the escape function is bidirectional (URIStringUtils.escape and URIStringUtils.unescape) * Is an **identifier** (two variables with the same URI are the same in the sense of Java Object.equals) * Is a random access identifier (by Variables.getVariable()) * Examples: ** http://www.acme.org/Projects/MyProject/MyModel/Configuration/DiagramN/PI_X#PI_MASS_FLOW ** http://www.acme.org/Projects/MyProject/MyModel/ExperimentConfiguration/RunName/DiagramN/PI_X#PI_MASS_FLOW#DATATYPE A common way of identifying a variable is by supplying a **base variable** and a **Relative Variable Identifier (RVI)**. * RVI represents the path from **base variable** into another variable * In textual RVI notation (Variable.browse()) ** **.** represents a getParent() query ** **/xx** represents a getChild(unescaped(xx)) query ** **#yy** represents a getProperty(unescaped(yy)) query * A literal RVI (Variable.getRVI(), RVI.resolve()) ** Does not need to depend on the names visible in the URI ** Is based on e.g. resource ids ** Survives export/import A **model** variable represents the root of a Simantics model * Model variables correspond directly to instances of SIMU.Model in the database ** Variable and resource URIs are the same * For all variables under a model, the model variable can be obtained using Variables.getModel() A **context** variable under a model provides a view into a **state** of the model * The **Type** property of a context variable is inheried from L0.RVIContext * A RVI obtained from e.g. model configuration can be used to access similarly identified data from different model states ** E.g. /DiagramX/ComponentY#PropertyZ can have different values in different contexts * The **configuration** context can be used to browse the structure and configuration values of the model * **Experiment run** contexts are used to monitor values from simulations or history The variable interface is bound to Simantics database **transactions**, but is not in any other way bound to the semantic data model, which allows variable implementations to represent arbitrary data models somehow related to Simantics models. All variable-based requests can be listened using standard Simantics database listening. Procedural children and variables are used with large data sets. E.g. query-based views can be exposed. Procedural properties also enable efficient slicing of arrays e.g. URI#Array_Property/0-99 = General assertions in the Variable model = * All variables except the **root** have a parent * Let p be the parent of v. Then v#URI equals p#URI + '/'|'#' + escape(v#HasName) * Iff v1#URI equals v2#URI, then v1 and v2 are equal in Java Object.equals sense ** Other identifications can be established by property values * A variable v2 equaling variable v can always be obtained by calling Variables.getVariable(v#URI) ** The obtained variable **need not be the same object** but **can be** ** Variables.getVariable can return also variables, which are not reachable by browsing (**TODO**) * All property variables have a value ** No child variable has a value ** The value of a property variable may be **null** ** The value of **DATATYPE** property can be null for property variables i.e. property values can be arbitrary Java objects * Variable.getProperty returns all the variables returned from Variable.browseProperties ** Variable.getProperty can return variables not returned by Variable.browseProperties * Variable.getChild returns all the variables returned from Variable.browseChildren ** Variable.getChild can return variables not returned by Variable.browseChildren * A variable can be part of at most one **model** * A variable can be part of at most one **context** * All values can be accessed using either Variable.getValue or Variable.getInterface(Accessor.class) * All properties retrieved using Variable.browseProperties shall be available using Variable.getProperty * No variable can have a name that begins with one or more dots ('.') due to '.' being a reserved character for browsing the variable address space towards the parent. In other words names matching the pattern **"^\.+.*$"** are forbidden. # Standard properties ## Connections * Connection point properties are classified with **http://www.simantics.org/Structural-1.0/ConnectionRelation** * The value of a connection point property is an object of class **org.simantics.structural2.variables.Connection** ~~~ public interface Connection { Collection getConnectionPoints(ReadGraph graph) throws DatabaseException; } ~~~ It is assumed that instances of **org.simantics.structural2.variables.Connection** have proper identities (equals/hashCode) based on flattened connections. The value of connection point properties can be **null**. This means that the connection point is not connected. ## String editing operations * **HasDisplayValue** is a String-valued property, which is a formatted and unit-converted representation of the property value. * **expression** is a String-valued property, which is an SCL-formula used to compute the value of the property. If the property can be computed using an expression, this property is always available and returns **null** if an expression has not been defined. * **validator** is a **org.simantics.utils.strings.StringInputValidator**-valued property. The validator is used for checking values to be written into a property. ~~~ public interface StringInputProblem { enum Severity { Error, Warning } String getDescription(); int getBegin(); int getEnd(); Severity getSeverity(); } public interface StringInputValidator { Collection validate(String input); } ~~~ * **valid** is a Boolean-valued property, which indicates whether the property contains a valid value. ## Property properties * **required** is a Boolean-valued property, which indicates that the property should contain a valid value * **default** is a Boolean-valued property, which indicates that the property value is a default value * **readOnly** is a Boolean-valued property, which indicates that the property value can not be written ## Complex datatypes * Record * All named fields are '/name' * Tuples are named after position e.g. '/11' * Union * Union does not show in URI * Array * Elements are named after position e.g. '/i-11' * Map * Items are named after key # Standard graph based variable implementation The standard child and property variables are * org.simantics.db.layer0.variable.StandardGraphChildVariable * org.simantics.db.layer0.variable.StandardGraphPropertyVariable Their implementation is based on the following interfaces ~~~ package org.simantics.db.layer0.variable; public interface VariableMap { Variable getVariable(ReadGraph graph, Variable context, String name) throws DatabaseException; // Must not modify collection in any way not possible with put-method. void getVariables(ReadGraph graph, Variable context, Map map) throws DatabaseException; } ~~~ ~~~ package org.simantics.db.layer0.variable; public interface ValueAccessor { Object getValue(ReadGraph graph, Variable context) throws DatabaseException; Object getValue(ReadGraph graph, Variable context, Binding binding) throws DatabaseException; void setValue(WriteGraph graph, Variable context, Object value) throws DatabaseException; void setValue(WriteGraph graph, Variable context, Object value, Binding binding) throws DatabaseException; } ~~~ Implementations of the above interfaces can be bound to instances and types in the database via the following standard properties ~~~ L0.Entity >-- L0.hasStandardResource ==> "Resource" -- L0.domainProperties ==> "VariableMap" -- L0.domainChildren ==> "VariableMap" -- L0.valueAccessor ==> "ValueAccessor" modifier = context.getPossiblePropertyValue(graph, Variables.INPUT_MODIFIER); if(modifier == null) modifier = VariableUtils.defaultInputModifier; try { modifier.apply(graph, context, value, Bindings.getBinding(value.getClass())); } catch (BindingConstructionException e) { throw new DatabaseException(e); } } @Override public void setValue(WriteGraph graph, Variable context, Object value, Binding binding) throws DatabaseException { StandardGraphPropertyVariable variable = (StandardGraphPropertyVariable)context; ValueAccessor propertyAccessor = getPossiblePropertyValueAccessor(graph, variable); if(propertyAccessor != null) { propertyAccessor.setValue(graph, context, value, binding); return; } Function4 modifier = context.getPossiblePropertyValue(graph, Variables.INPUT_MODIFIER); if(modifier == null) modifier = VariableUtils.defaultInputModifier; modifier.apply(graph, context, value, binding); } }; @SCLValue(type = "VariableMap") public static VariableMap standardChildDomainProperties = new VariableMap() { @Override public Variable getVariable(ReadGraph graph, Variable context, String name) throws DatabaseException { final StandardGraphChildVariable variable = (StandardGraphChildVariable)context; return getPossiblePropertyFromContext(graph, variable, variable.resource, name); } @Override public void getVariables(ReadGraph graph, Variable context, Map map) throws DatabaseException { StandardGraphChildVariable variable = (StandardGraphChildVariable)context; collectPropertiesFromContext(graph, variable, variable.resource, map); } }; @SCLValue(type = "VariableMap") public static VariableMap standardPropertyDomainProperties = new VariableMap() { @Override public Variable getVariable(ReadGraph graph, Variable context, String name) throws DatabaseException { StandardGraphPropertyVariable variable = (StandardGraphPropertyVariable)context; Resource literal = graph.getPossibleObject(variable.parentResource, variable.property); if(literal != null) { Variable result = getPossiblePropertyFromContext(graph, variable, literal, name); if(result != null) return result; } return getPossiblePropertyFromContext(graph, variable, variable.property, name); } @Override public void getVariables(ReadGraph graph, Variable context, Map map) throws DatabaseException { StandardGraphPropertyVariable variable = (StandardGraphPropertyVariable)context; collectPropertiesFromContext(graph, variable, variable.property, map); Resource literal = graph.getPossibleObject(variable.parentResource, variable.property); if(literal != null) collectPropertiesFromContext(graph, variable, literal, map); } }; @SCLValue(type = "VariableMap") public static VariableMap standardChildDomainChildren = new VariableMap() { @Override public Variable getVariable(ReadGraph graph, Variable context, String name) throws DatabaseException { StandardGraphChildVariable variable = (StandardGraphChildVariable)context; Map children = graph.syncRequest(new UnescapedChildMapOfResource(variable.resource)); Resource child = children.get(name); if(child == null) return null; return graph.getPossibleContextualAdapter(child, variable, Variable.class, Variable.class); } @Override public void getVariables(ReadGraph graph, Variable context, Map map) throws DatabaseException { StandardGraphChildVariable variable = (StandardGraphChildVariable)context; for(Map.Entry entry : graph.syncRequest(new UnescapedChildMapOfResource(variable.resource)).entrySet()) { String name = entry.getKey(); Resource child = entry.getValue(); Variable var = graph.getPossibleContextualAdapter(child, variable, Variable.class, Variable.class); if(var != null) { map.put(name, var); } else { System.err.println("No adapter for " + child + " in " + variable.getURI(graph)); } } } }; @SCLValue(type = "VariableMap") public static VariableMap standardPropertyDomainChildren = new VariableMap() { @Override public Variable getVariable(ReadGraph graph, Variable context, String name) throws DatabaseException { StandardGraphPropertyVariable variable = (StandardGraphPropertyVariable)context; Datatype dt = variable.getDatatype(graph); if (dt instanceof ArrayType) { ChildReference ref = getPossibleIndexReference(name); if (ref != null) return new SubliteralPropertyVariable(variable, ref); } return null; } @Override public void getVariables(ReadGraph graph, Variable context, Map map) throws DatabaseException { } }; ~~~ ## Informally Standard child modelling assumes a context resource res. * childResource : where (res, L0.ConsistsOf, childResource) * childVariable = graph.adaptContextual(childResource, this, Variable.class, Variable.class) Standard property modelling assumes a context resource res. * (predicate, object) : where (res, predicate, object) and (predicate browse entrypoint "./Out#sdf" * Use . and #-for browsing * How to browse parent? Explicit function * Example: (parent entrypoint).Out#sdf * Special operator for parents (binds stronger than . or #): * Example: !entrypoint.Out#sdf * Resolve entrypoints in the context * Example: Out#sdf * Local variable definitions may shadow context ## Experiment modelling The modelling of experiment run contexts and the value types of properties in states is still underway. # Refactoring * add Collection browseProperties(ReadGraph graph, String classification) throws DatabaseException; * add standard property **classifications**, which returns a set of strings * deprecate browseChildren, browseProperties => add get