]> gerrit.simantics Code Review - simantics/platform.git/blob - docs/Developer/Database/Variable.md
Optimise performance of job scheduling in QueryListening
[simantics/platform.git] / docs / Developer / Database / Variable.md
1 # Intro\r
2 \r
3 The Variable interface provides an uniform access model to data in Simantics. It includes\r
4 \r
5 * Tree-structured address space for accessing model **structure** and **properties**\r
6 * Uniform access to model **configuration** and **state**\r
7 \r
8 Key use cases include\r
9 \r
10 * Browsing of the **model configuration and states** (see [[Model Browser]])\r
11 * **Manipulation of objects** (see [[Selection View]])\r
12 * Representation of **tabular data** (see [[Spreadsheets]])\r
13 \r
14 Main functional requirements include representation of\r
15 \r
16 * **Structural models** with **procedural** features (see [[Structural]])\r
17 * **Runtime** data of solvers (see [[Experiments]])\r
18 * **Historical** data from experiments\r
19 * **Ontological** data\r
20 \r
21 # Solution\r
22 \r
23 **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**.\r
24 \r
25 The variable space browsing methods are used to obtain\r
26 \r
27 * all children\r
28 * children by name\r
29 * all properties\r
30 * properties by name\r
31 * variable by **path**\r
32 * **parent** variable\r
33 \r
34 Other services are\r
35 \r
36 * accessing (get/set) the value of a property variable\r
37 * querying adapter interfaces\r
38 \r
39 A set of built-in properties is required for all variables. These properties have also dedicated interface methods.\r
40 \r
41 * **URI**, which is an unique string identifier and locator of the variable in the tree structure\r
42 * **Parent**, which is the tree parent of the variable\r
43 * **HasName**, which is a local identifier for the variable within its parent. Names are also used to create URIs.\r
44 * **HasLabel**, which is a short textual representation of the variable\r
45 * **hasStandardResource**, which returns the context resource in a **standard graph-based child variable**\r
46 * **Represents**, which is a resource representing the variable **TODO**\r
47 * **Type**, which returns a single type resource classifying the variable\r
48 * **Role**, which tells whether the variable is a **child** or a **property** (TODO: could be deprecated)\r
49 * **DATATYPE**, which returns the data type of a property variable. (TODO: should be HasDatatype)\r
50 \r
51 Other properties and the structure of the variable space is configured in the semantic graph or contributed by custom variable implementations.\r
52 \r
53 Variables can be located using an **URI**, which\r
54 \r
55 * Represents the path from **root variable** (Variables.getRootVariable) into the variable such that\r
56 ** **var/xx** represents a getChild(unescaped(xx)) query from var\r
57 ** **var#yy** represents a getProperty(unescaped(yy)) query from var\r
58 ** the escape function is bidirectional (URIStringUtils.escape and URIStringUtils.unescape)\r
59 * Is an **identifier** (two variables with the same URI are the same in the sense of Java Object.equals)\r
60 * Is a random access identifier (by Variables.getVariable())\r
61 * Examples:  \r
62 ** http://www.acme.org/Projects/MyProject/MyModel/Configuration/DiagramN/PI_X#PI_MASS_FLOW\r
63 ** http://www.acme.org/Projects/MyProject/MyModel/ExperimentConfiguration/RunName/DiagramN/PI_X#PI_MASS_FLOW#DATATYPE\r
64 \r
65 A common way of identifying a variable is by supplying a **base variable** and a **Relative Variable Identifier (RVI)**.\r
66 \r
67 * RVI represents the path from **base variable** into another variable\r
68 * In textual RVI notation (Variable.browse())\r
69 ** **.** represents a getParent() query\r
70 ** **/xx** represents a getChild(unescaped(xx)) query\r
71 ** **#yy** represents a getProperty(unescaped(yy)) query\r
72 * A literal RVI (Variable.getRVI(), RVI.resolve())\r
73 ** Does not need to depend on the names visible in the URI\r
74 ** Is based on e.g. resource ids\r
75 ** Survives export/import\r
76 \r
77 A **model** variable represents the root of a Simantics model\r
78 * Model variables correspond directly to instances of SIMU.Model in the database\r
79 ** Variable and resource URIs are the same\r
80 * For all variables under a model, the model variable can be obtained using Variables.getModel()\r
81 \r
82 A **context** variable under a model provides a view into a **state** of the model\r
83 * The **Type** property of a context variable is inheried from L0.RVIContext\r
84 * A RVI obtained from e.g. model configuration can be used to access similarly identified data from different model states\r
85 ** E.g. /DiagramX/ComponentY#PropertyZ can have different values in different contexts\r
86 * The **configuration** context can be used to browse the structure and configuration values of the model\r
87 * **Experiment run** contexts are used to monitor values from simulations or history\r
88 \r
89 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.\r
90 \r
91 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\r
92 \r
93 = General assertions in the Variable model =\r
94 \r
95 * All variables except the **root** have a parent\r
96 * Let p be the parent of v. Then v#URI equals p#URI + '/'|'#' + escape(v#HasName)\r
97 * Iff v1#URI equals v2#URI, then v1 and v2 are equal in Java Object.equals sense\r
98 ** Other identifications can be established by property values\r
99 * A variable v2 equaling variable v can always be obtained by calling Variables.getVariable(v#URI)\r
100 ** The obtained variable **need not be the same object** but **can be**\r
101 ** Variables.getVariable can return also variables, which are not reachable by browsing (**TODO**)\r
102 * All property variables have a value\r
103 ** No child variable has a value\r
104 ** The value of a property variable may be **null**\r
105 ** The value of **DATATYPE** property can be null for property variables i.e. property values can be arbitrary Java objects\r
106 * Variable.getProperty returns all the variables returned from Variable.browseProperties\r
107 ** Variable.getProperty can return variables not returned by Variable.browseProperties\r
108 * Variable.getChild returns all the variables returned from Variable.browseChildren\r
109 ** Variable.getChild can return variables not returned by Variable.browseChildren\r
110 * A variable can be part of at most one **model**\r
111 * A variable can be part of at most one **context**\r
112 * All values can be accessed using either Variable.getValue or Variable.getInterface(Accessor.class)\r
113 * All properties retrieved using Variable.browseProperties shall be available using Variable.getProperty\r
114 * 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.\r
115 \r
116 # Standard properties\r
117 \r
118 ## Connections\r
119 \r
120 * Connection point properties are classified with **http://www.simantics.org/Structural-1.0/ConnectionRelation**\r
121 * The value of a connection point property is an object of class **org.simantics.structural2.variables.Connection**\r
122 \r
123 ~~~\r
124 \r
125 public interface Connection {\r
126 \r
127   Collection<Variable> getConnectionPoints(ReadGraph graph) throws DatabaseException;\r
128 \r
129 }\r
130 \r
131 ~~~\r
132 \r
133 It is assumed that instances of **org.simantics.structural2.variables.Connection** have proper identities (equals/hashCode) based on flattened connections.\r
134 \r
135 The value of connection point properties can be **null**. This means that the connection point is not connected.\r
136 \r
137 ## String editing operations\r
138 \r
139 * **HasDisplayValue** is a String-valued property, which is a formatted and unit-converted representation of the property value.\r
140 * **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.\r
141 * **validator** is a **org.simantics.utils.strings.StringInputValidator**-valued property. The validator is used for checking values to be written into a property.\r
142 \r
143 ~~~\r
144 \r
145 public interface StringInputProblem {\r
146 \r
147   enum Severity {\r
148     Error, Warning\r
149   }\r
150 \r
151   String getDescription();\r
152   int getBegin();\r
153   int getEnd();\r
154   Severity getSeverity();\r
155 \r
156 }\r
157 \r
158 public interface StringInputValidator {\r
159 \r
160   Collection<StringInputProblem> validate(String input);\r
161 \r
162 }\r
163 \r
164 ~~~\r
165 \r
166 * **valid** is a Boolean-valued property, which indicates whether the property contains a valid value.\r
167 \r
168 ## Property properties\r
169 \r
170 * **required** is a Boolean-valued property, which indicates that the property should contain a valid value\r
171 * **default** is a Boolean-valued property, which indicates that the property value is a default value\r
172 * **readOnly** is a Boolean-valued property, which indicates that the property value can not be written\r
173 \r
174 ## Complex datatypes\r
175 \r
176 * Record\r
177   * All named fields are '/name'\r
178   * Tuples are named after position e.g. '/11'\r
179 * Union\r
180   * Union does not show in URI\r
181 * Array\r
182   * Elements are named after position e.g. '/i-11'\r
183 * Map\r
184   * Items are named after key\r
185 \r
186 # Standard graph based variable implementation\r
187 \r
188 The standard child and property variables are\r
189 \r
190 * org.simantics.db.layer0.variable.StandardGraphChildVariable\r
191 * org.simantics.db.layer0.variable.StandardGraphPropertyVariable\r
192 \r
193 Their implementation is based on the following interfaces\r
194 \r
195 ~~~\r
196 \r
197 package org.simantics.db.layer0.variable;\r
198 \r
199 public interface VariableMap {\r
200     Variable getVariable(ReadGraph graph, Variable context, String name) throws DatabaseException;\r
201     // Must not modify collection in any way not possible with put-method.\r
202     void getVariables(ReadGraph graph, Variable context, Map<String, Variable> map) throws DatabaseException;\r
203 }\r
204 \r
205 ~~~\r
206 \r
207 ~~~\r
208 \r
209 package org.simantics.db.layer0.variable;\r
210 \r
211 public interface ValueAccessor {\r
212     Object getValue(ReadGraph graph, Variable context) throws DatabaseException;\r
213     Object getValue(ReadGraph graph, Variable context, Binding binding) throws DatabaseException;\r
214     void setValue(WriteGraph graph, Variable context, Object value) throws DatabaseException;\r
215     void setValue(WriteGraph graph, Variable context, Object value, Binding binding) throws DatabaseException;\r
216 }\r
217 \r
218 ~~~\r
219 \r
220 Implementations of the above interfaces can be bound to instances and types in the database via the following standard properties\r
221 \r
222 ~~~\r
223 \r
224 L0.Entity\r
225   >-- L0.hasStandardResource ==> "Resource" <R L0.HasProperty : L0.FunctionalRelation\r
226     L0.HasDescription "The backend resource for standard graph-based Variables."\r
227   >-- L0.domainProperties ==> "VariableMap" <R L0.HasProperty : L0.FunctionalRelation\r
228     L0.HasDescription "Retruns a map of all domain properties of the entity."\r
229   >-- L0.domainChildren ==> "VariableMap" <R L0.HasProperty : L0.FunctionalRelation\r
230     L0.HasDescription "Retruns a map of all domain children of the entity."\r
231   >-- L0.valueAccessor ==> "ValueAccessor" <R L0.HasProperty : L0.FunctionalRelation\r
232     L0.HasDescription "Returns an interface for accessing the value."\r
233   @L0.assert L0.hasStandardResource\r
234     L0.Functions.hasStandardResource : L0.Function\r
235   @L0.assert L0.domainProperties L0.Functions.standardChildDomainProperties\r
236   @L0.assert L0.domainChildren L0.Functions.standardChildDomainChildren\r
237 \r
238 L0.Value\r
239   @L0.assert L0.domainProperties L0.Functions.standardPropertyDomainProperties\r
240   @L0.assert L0.domainChildren L0.Functions.standardPropertyDomainChildren\r
241   @L0.assert L0.valueAccessor L0.Functions.standardValueAccessor\r
242 \r
243 ~~~\r
244 \r
245 The standard implementation is\r
246 \r
247 ~~~\r
248 \r
249 @SCLValue(type = "ValueAccessor")\r
250 public static ValueAccessor standardValueAccessor = new ValueAccessor() {\r
251 \r
252         @Override\r
253         public Object getValue(ReadGraph graph, Variable context) throws DatabaseException {\r
254                 StandardGraphPropertyVariable variable = (StandardGraphPropertyVariable)context;\r
255                 try {\r
256                         ValueAccessor propertyAccessor = getPossiblePropertyValueAccessor(graph, variable);\r
257                         if(propertyAccessor != null) return propertyAccessor.getValue(graph, context);\r
258                         if(graph.sync(new IsEnumeratedValue(variable.getRepresents(graph)))) {\r
259                                 Layer0 L0 = Layer0.getInstance(graph);\r
260                                 return graph.getRelatedValue2(variable.getRepresents(graph), L0.HasLabel);\r
261                         }\r
262                         if(variable.adapterClass != null) {\r
263                                 return graph.adaptRelated(variable.parentResource, variable.property, variable.adapterClass);\r
264                         } else {\r
265                                 return graph.getRelatedValue2(variable.parentResource, variable.property, variable);\r
266                         }\r
267                 } catch (NoSingleResultException e) {\r
268                         throw new MissingVariableValueException(variable.getPossibleURI(graph), e);\r
269                 } catch (DoesNotContainValueException e) {\r
270                         throw new MissingVariableValueException(variable.getPossibleURI(graph), e);\r
271                 } catch (DatabaseException e) {\r
272                         throw new MissingVariableValueException(variable.getPossibleURI(graph), e);\r
273                 }\r
274         }\r
275         @Override\r
276         public Object getValue(ReadGraph graph, Variable context, Binding binding) throws DatabaseException {\r
277                 StandardGraphPropertyVariable variable = (StandardGraphPropertyVariable)context;\r
278                 try {\r
279                         ValueAccessor propertyAccessor = getPossiblePropertyValueAccessor(graph, variable);\r
280                         if(propertyAccessor != null) return propertyAccessor.getValue(graph, context, binding);\r
281                         if(graph.sync(new IsEnumeratedValue(variable.getRepresents(graph)))) {\r
282                                 Layer0 L0 = Layer0.getInstance(graph);\r
283                                 return graph.getRelatedValue2(variable.getRepresents(graph), L0.HasLabel, binding);\r
284                         }\r
285                         if(variable.adapterClass != null) {\r
286                                 return graph.adaptRelated(variable.parentResource, variable.property, variable.adapterClass);\r
287                         } else {\r
288                                 return graph.getRelatedValue2(variable.parentResource, variable.property, variable, binding);\r
289                         }\r
290                 } catch (NoSingleResultException e) {\r
291                         throw new MissingVariableValueException(variable.getPossibleURI(graph));\r
292                 } catch (DoesNotContainValueException e) {\r
293                         throw new MissingVariableValueException(variable.getPossibleURI(graph));\r
294                 } catch (DatabaseException e) {\r
295                         throw new MissingVariableValueException(variable.getPossibleURI(graph));\r
296                 }\r
297         }\r
298         @Override\r
299         public void setValue(WriteGraph graph, Variable context, Object value) throws DatabaseException {\r
300                 StandardGraphPropertyVariable variable = (StandardGraphPropertyVariable)context;\r
301                 ValueAccessor propertyAccessor = getPossiblePropertyValueAccessor(graph, variable);\r
302                 if(propertyAccessor != null) {\r
303                         propertyAccessor.setValue(graph, context, value);\r
304                         return;\r
305                 }\r
306                 Function4<WriteGraph, Variable, Object, Object, String> modifier = context.getPossiblePropertyValue(graph, Variables.INPUT_MODIFIER);\r
307                 if(modifier == null) modifier = VariableUtils.defaultInputModifier;\r
308                 try {\r
309                         modifier.apply(graph, context, value, Bindings.getBinding(value.getClass()));\r
310                 } catch (BindingConstructionException e) {\r
311                         throw new DatabaseException(e);\r
312                 }\r
313         }\r
314         @Override\r
315         public void setValue(WriteGraph graph, Variable context, Object value, Binding binding) throws DatabaseException {\r
316                 StandardGraphPropertyVariable variable = (StandardGraphPropertyVariable)context;\r
317                 ValueAccessor propertyAccessor = getPossiblePropertyValueAccessor(graph, variable);\r
318                 if(propertyAccessor != null) {\r
319                         propertyAccessor.setValue(graph, context, value, binding);\r
320                         return;\r
321                 }\r
322                 Function4<WriteGraph, Variable, Object, Object, String> modifier = context.getPossiblePropertyValue(graph, Variables.INPUT_MODIFIER);\r
323                 if(modifier == null) modifier = VariableUtils.defaultInputModifier;\r
324                 modifier.apply(graph, context, value, binding);\r
325         }\r
326 };\r
327 \r
328 @SCLValue(type = "VariableMap")\r
329 public static VariableMap standardChildDomainProperties = new VariableMap() {\r
330 \r
331         @Override\r
332         public Variable getVariable(ReadGraph graph, Variable context, String name) throws DatabaseException {\r
333                 final StandardGraphChildVariable variable = (StandardGraphChildVariable)context;\r
334                 return getPossiblePropertyFromContext(graph, variable, variable.resource, name);\r
335         }\r
336         @Override\r
337         public void getVariables(ReadGraph graph, Variable context, Map<String, Variable> map) throws DatabaseException {\r
338                 StandardGraphChildVariable variable = (StandardGraphChildVariable)context;\r
339                 collectPropertiesFromContext(graph, variable, variable.resource, map);\r
340         }\r
341 \r
342 };\r
343 \r
344 @SCLValue(type = "VariableMap")\r
345 public static VariableMap standardPropertyDomainProperties = new VariableMap() {\r
346 \r
347         @Override\r
348         public Variable getVariable(ReadGraph graph, Variable context, String name) throws DatabaseException {\r
349                 StandardGraphPropertyVariable variable = (StandardGraphPropertyVariable)context;\r
350                 Resource literal = graph.getPossibleObject(variable.parentResource, variable.property);\r
351                 if(literal != null) {\r
352                         Variable result = getPossiblePropertyFromContext(graph, variable, literal, name);\r
353                         if(result != null) return result;\r
354                 }\r
355                 return getPossiblePropertyFromContext(graph, variable, variable.property, name);\r
356         }\r
357         @Override\r
358         public void getVariables(ReadGraph graph, Variable context, Map<String, Variable> map) throws DatabaseException {\r
359                 StandardGraphPropertyVariable variable = (StandardGraphPropertyVariable)context;\r
360                 collectPropertiesFromContext(graph, variable, variable.property, map);\r
361                 Resource literal = graph.getPossibleObject(variable.parentResource, variable.property);\r
362                 if(literal != null) collectPropertiesFromContext(graph, variable, literal, map);\r
363         }\r
364 \r
365 };\r
366 \r
367 @SCLValue(type = "VariableMap")\r
368 public static VariableMap standardChildDomainChildren = new VariableMap() {\r
369 \r
370         @Override\r
371         public Variable getVariable(ReadGraph graph, Variable context, String name) throws DatabaseException {\r
372                 StandardGraphChildVariable variable = (StandardGraphChildVariable)context;\r
373                 Map<String, Resource> children = graph.syncRequest(new UnescapedChildMapOfResource(variable.resource));\r
374                 Resource child = children.get(name);\r
375                 if(child == null) return null;\r
376                 return graph.getPossibleContextualAdapter(child, variable, Variable.class, Variable.class);\r
377         }\r
378         @Override\r
379         public void getVariables(ReadGraph graph, Variable context, Map<String, Variable> map) throws DatabaseException {\r
380                 StandardGraphChildVariable variable = (StandardGraphChildVariable)context;\r
381                 for(Map.Entry<String, Resource> entry : graph.syncRequest(new UnescapedChildMapOfResource(variable.resource)).entrySet()) {\r
382                         String name = entry.getKey();\r
383                         Resource child = entry.getValue();\r
384                         Variable var = graph.getPossibleContextualAdapter(child, variable, Variable.class, Variable.class);\r
385                         if(var != null) {\r
386                                 map.put(name, var);\r
387                         } else {\r
388                                 System.err.println("No adapter for " + child + " in " + variable.getURI(graph));\r
389                         }\r
390                 }\r
391         }\r
392 \r
393 };\r
394 \r
395 @SCLValue(type = "VariableMap")\r
396 public static VariableMap standardPropertyDomainChildren = new VariableMap() {\r
397 \r
398         @Override\r
399         public Variable getVariable(ReadGraph graph, Variable context, String name) throws DatabaseException {\r
400                 StandardGraphPropertyVariable variable = (StandardGraphPropertyVariable)context;\r
401                 Datatype dt = variable.getDatatype(graph);\r
402                 if (dt instanceof ArrayType) {\r
403                     ChildReference ref = getPossibleIndexReference(name);\r
404                     if (ref != null)\r
405                         return new SubliteralPropertyVariable(variable, ref);\r
406                 }\r
407                 return null;\r
408         }\r
409         @Override\r
410         public void getVariables(ReadGraph graph, Variable context, Map<String, Variable> map) throws DatabaseException {\r
411         }\r
412 \r
413 };\r
414 \r
415 ~~~\r
416 \r
417 ## Informally\r
418 \r
419 Standard child modelling assumes a context resource res.\r
420 \r
421 * childResource : where (res, L0.ConsistsOf, childResource)\r
422 * childVariable = graph.adaptContextual(childResource, this, Variable.class, Variable.class)\r
423 \r
424 Standard property modelling assumes a context resource res.\r
425 \r
426 * (predicate, object) : where (res, predicate, object) and (predicate <R L0.HasProperty)\r
427 * childProperty = graph.adaptContextual(object, ModelledVariablePropertyDescriptor(this, predicate), Variable.class)\r
428 \r
429 For property variables there are two context resources available, the associated value and the predicate. Properties are found from the value context first and then from the predicate context.\r
430 \r
431 The graph.adaptContextual step is an extension point for defining custom variable implementations contributed using the adapter (adapters.xml) mechanism.\r
432 \r
433 # Frequent cases\r
434 \r
435 1. Given variable v, obtain model (Resource)\r
436    call Variables.getModel(graph, v)\r
437 2. Given variable v, obtain context (Variable)\r
438    call Variables.getContext(graph, v)\r
439 3. Given Variable URI, obtain Variable\r
440    call Variables.getVariable(graph, uri)\r
441 4. Obtain string representation for value of Variable v\r
442    obtain value of v#HasDisplayValue\r
443 \r
444 to be continued...\r
445 \r
446 # Open issues\r
447 \r
448 ## Generic simulator support in Variable ==\r
449 \r
450 Current hypothesis is that we extends standard graph based implementation by a special interface, which serves the simulator (or other) data. The standard implementation then combines data from graph and simulator. The special interface is designed such that is supports custom synchronization and lazy operation and can also be directly utilized in remote simulators.\r
451 \r
452 ## Variable syntax in SCL\r
453 \r
454 * Implement variable interface methods as normal functions\r
455   * Example: <code>browse entrypoint "./Out#sdf"</code>\r
456 * Use . and #-for browsing\r
457   * How to browse parent? Explicit function\r
458   * Example: <code>(parent entrypoint).Out#sdf</code>\r
459   * Special operator for parents (binds stronger than . or #):\r
460   * Example: <code>!entrypoint.Out#sdf</code>\r
461 * Resolve entrypoints in the context\r
462   * Example: <code>Out#sdf</code>\r
463   * Local variable definitions may shadow context\r
464 \r
465 ## Experiment modelling\r
466 \r
467 The modelling of experiment run contexts and the value types of properties in states is still underway.\r
468 \r
469 # Refactoring\r
470 \r
471 * <strike>add Collection<Variable> browseProperties(ReadGraph graph, String classification) throws DatabaseException;</strike>\r
472 * add standard property **classifications**, which returns a set of strings\r
473 * <strike>deprecate browseChildren, browseProperties => add get</strike>\r