]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.network.ui/src/org/simantics/district/network/ui/function/Functions.java
Fixed regressions in box selection and element color function validation
[simantics/district.git] / org.simantics.district.network.ui / src / org / simantics / district / network / ui / function / Functions.java
1 package org.simantics.district.network.ui.function;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collection;
6 import java.util.Collections;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Set;
12 import java.util.stream.Collectors;
13
14 import org.eclipse.jface.dialogs.Dialog;
15 import org.eclipse.jface.layout.GridDataFactory;
16 import org.eclipse.swt.SWT;
17 import org.eclipse.swt.events.SelectionAdapter;
18 import org.eclipse.swt.events.SelectionEvent;
19 import org.eclipse.swt.layout.GridData;
20 import org.eclipse.swt.layout.GridLayout;
21 import org.eclipse.swt.widgets.Combo;
22 import org.eclipse.swt.widgets.Composite;
23 import org.eclipse.swt.widgets.Control;
24 import org.eclipse.swt.widgets.Group;
25 import org.eclipse.swt.widgets.Label;
26 import org.eclipse.swt.widgets.Shell;
27 import org.eclipse.ui.PlatformUI;
28 import org.eclipse.ui.dialogs.SelectionStatusDialog;
29 import org.simantics.NameLabelUtil;
30 import org.simantics.Simantics;
31 import org.simantics.browsing.ui.common.modifiers.EnumeratedValue;
32 import org.simantics.browsing.ui.common.modifiers.Enumeration;
33 import org.simantics.browsing.ui.graph.impl.GraphEnumerationModifier;
34 import org.simantics.databoard.Bindings;
35 import org.simantics.db.ReadGraph;
36 import org.simantics.db.Resource;
37 import org.simantics.db.Session;
38 import org.simantics.db.WriteGraph;
39 import org.simantics.db.common.request.IndexRoot;
40 import org.simantics.db.common.request.ObjectsWithType;
41 import org.simantics.db.common.request.ReadRequest;
42 import org.simantics.db.common.request.WriteRequest;
43 import org.simantics.db.exception.DatabaseException;
44 import org.simantics.db.exception.RuntimeDatabaseException;
45 import org.simantics.db.exception.ServiceException;
46 import org.simantics.db.layer0.QueryIndexUtils;
47 import org.simantics.db.layer0.util.Layer0Utils;
48 import org.simantics.db.layer0.variable.Variable;
49 import org.simantics.db.layer0.variable.Variables;
50 import org.simantics.db.layer0.variable.Variables.Role;
51 import org.simantics.db.procedure.Procedure;
52 import org.simantics.district.network.ontology.DistrictNetworkResource;
53 import org.simantics.layer0.Layer0;
54 import org.simantics.modeling.ModelingResources;
55 import org.simantics.modeling.adapters.NewCompositeActionFactory;
56 import org.simantics.modeling.typicals.TypicalUtil;
57 import org.simantics.operation.Layer0X;
58 import org.simantics.scl.compiler.commands.CommandSession;
59 import org.simantics.scl.compiler.commands.CommandSessionImportEntry;
60 import org.simantics.scl.compiler.errors.CompilationError;
61 import org.simantics.scl.osgi.SCLOsgi;
62 import org.simantics.scl.reflection.annotations.SCLValue;
63 import org.simantics.scl.runtime.SCLContext;
64 import org.simantics.scl.runtime.function.Function1;
65 import org.simantics.scl.runtime.function.FunctionImpl1;
66 import org.simantics.scl.runtime.reporting.SCLReportingHandler;
67 import org.simantics.ui.workbench.action.DefaultActions;
68 import org.simantics.utils.ui.SWTUtils;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
71
72 public class Functions {
73
74     private static final Logger LOGGER = LoggerFactory.getLogger(Functions.class);
75     
76     private Functions() {
77     }
78
79     private static class HasMappingEnumerationModifier extends GraphEnumerationModifier {
80
81         public HasMappingEnumerationModifier(Session session, Resource subject, Resource relation, Enumeration<Resource> enumeration, Resource value) {
82             super(session, subject, relation, enumeration, value);
83         }
84
85     }
86
87     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
88     public static Object defaultEdgeMappingModifier(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException {
89         Resource diagram = resolveElement(graph, context);
90         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
91         return baseMappingModifier(graph, diagram, DN.EdgeDefaultMapping, DN.Mapping_EdgeMapping, context);
92     }
93     
94     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
95     public static Object defaultVertexMappingModifier(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException {
96         System.out.println(graph.getURI(resource));
97         System.out.println(context.getURI(graph));
98         
99         Resource diagram = resolveElement(graph, context);
100         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
101         return baseMappingModifier(graph, diagram, DN.VertexDefaultMapping, DN.Mapping_VertexMapping, context);
102     }
103     
104     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
105     public static Object mappingModifier(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException {
106         
107         Resource element = resolveElement(graph, context);
108         Resource mappingType = resolveMappingType(graph, element);
109         return baseMappingModifier(graph, element, DistrictNetworkResource.getInstance(graph).HasMapping, mappingType, context);
110     }
111
112     public static Map<String, Resource> getVertexMappings(ReadGraph graph, Resource resource) throws DatabaseException {
113         Map<String, Resource> second = getNetworkMappingsByType(graph, resource , DistrictNetworkResource.getInstance(graph).Mapping_VertexMapping);
114         return second;
115     }
116
117     public static Map<String, Resource> getEdgeMappings(ReadGraph graph, Resource resource) throws DatabaseException {
118         Map<String, Resource> second = getNetworkMappingsByType(graph, resource , DistrictNetworkResource.getInstance(graph).Mapping_EdgeMapping);
119         return second;
120     }
121     
122     public static Map<String, Resource> getCRSs(ReadGraph graph, Resource resource) throws DatabaseException {
123         Map<String, Resource> result = getNetworkMappingsByType(graph, resource, DistrictNetworkResource.getInstance(graph).SpatialRefSystem);
124         return result;
125         
126     }
127
128     public static Map<String, Resource> getNetworkMappingsByType(ReadGraph graph, Resource element, Resource mappingType) throws DatabaseException {
129         Resource indexRoot = graph.sync(new IndexRoot(element));
130         List<Resource> mappings = QueryIndexUtils.searchByType(graph, indexRoot, mappingType);
131         Map<String, Resource> result = new HashMap<>(mappings.size());
132         Layer0 L0 = Layer0.getInstance(graph);
133         mappings.forEach(mapping -> {
134             try {
135                 String name = graph.getRelatedValue2(mapping, L0.HasName);
136                 Resource existing = result.put(name, mapping);
137                 if (existing != null) {
138                     LOGGER.warn("Duplicate mapping name! {} {} and existing is {}", name, mapping, existing);
139                 }
140             } catch (DatabaseException e) {
141                 e.printStackTrace();
142             }
143         });
144         return result;
145     }
146     
147     private static Object baseMappingModifier(ReadGraph graph, Resource element, Resource property, Resource mappingType, Variable context) throws DatabaseException {
148         Resource indexRoot = graph.sync(new IndexRoot(element));
149         List<Resource> mappings = QueryIndexUtils.searchByType(graph, indexRoot, mappingType);
150         Enumeration<Resource> enums = Enumeration
151                 .make(mappings.stream().map(m -> createEnumeratedValue(graph, m)).collect(Collectors.toList()));
152         
153         Resource currentMapping = graph.getSingleObject(element, property);
154         
155         return new HasMappingEnumerationModifier(Simantics.getSession(), element, property, enums, currentMapping);
156     }
157     
158     private static Resource resolveMappingType(ReadGraph graph, Resource element) throws DatabaseException {
159         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
160         if (graph.isInstanceOf(element, DN.Edge))
161             return DN.Mapping_EdgeMapping;
162         else if (graph.isInstanceOf(element, DN.Vertex))
163             return DN.Mapping_VertexMapping;
164         throw new IllegalStateException("No mapping type found for element " + element + " : " + graph.getPossibleURI(element));
165     }
166
167     private static Resource resolveElement(ReadGraph graph, Variable variable) throws DatabaseException {
168         Role role = variable.getPossibleRole(graph);
169         if (role.equals(Role.PROPERTY))
170             return resolveElement(graph, variable.getParent(graph));
171         else
172             return variable.getRepresents(graph);
173     }
174
175     private static EnumeratedValue<Resource> createEnumeratedValue(ReadGraph graph, Resource resource) {
176         try {
177             String label = NameLabelUtil.modalName(graph, resource);
178             return new EnumeratedValue<Resource>(label, resource);
179         } catch (DatabaseException e) {
180             throw new RuntimeDatabaseException(e);
181         }
182     }
183     
184     @SCLValue(type = "ReadGraph -> Resource -> a -> b")
185     public static Object enumerationValues(ReadGraph graph, Resource resource, Object context) throws DatabaseException {
186         Variable var = (Variable) context;
187         System.out.println(graph.getURI(resource));
188         System.out.println(var.getURI(graph));
189         return Collections.emptyList();
190     }
191     
192     @SCLValue(type = "ReadGraph -> Resource -> a -> b")
193     public static Object convertToValue(ReadGraph graph, Resource resource, Object context) throws DatabaseException {
194         Layer0 L0 = Layer0.getInstance(graph);
195         String label = graph.getPossibleRelatedValue2(resource, L0.HasLabel, Bindings.STRING);
196         if (label == null)
197             label = graph.getRelatedValue(resource, L0.HasName, Bindings.STRING);
198         return label;
199     }
200     
201     
202     @SCLValue(type = "Resource -> String -> Resource -> Resource")
203     public static Resource compositeInstantiator(final Resource compositeType, final String defaultName, final Resource target) throws DatabaseException {
204         
205         return TypicalUtil.syncExec(procedure -> {
206             if (!SWTUtils.asyncExec(PlatformUI.getWorkbench().getDisplay(), () -> {
207                 try {
208                     queryInitialValuesAndCreateComposite(compositeType, target, defaultName, procedure);
209                 } catch (Throwable t) {
210                     procedure.exception(t);
211                 }
212             })) {
213                 procedure.execute(null);
214             }
215         });
216     }
217
218     private static class DefaultMappingsDialog extends SelectionStatusDialog {
219
220         private Combo vertexMappingCombo;
221         private Combo edgeMappingCombo;
222         private Combo crsCombo;
223         private Composite composite;
224         
225         private Resource configuration;
226         private Map<String, Resource> vertexMappings = new HashMap<>();
227         private Map<String, Resource> edgeMappings = new HashMap<>();
228         private Map<String, Resource> composites = new HashMap<>();
229         private Map<String, Resource> crss = new HashMap<>();
230         
231         private Resource defaultVertexMapping;
232         private Resource defaultEdgeMapping;
233         private Resource defaultCRS;
234         
235         private Combo compositeMappingCombo;
236         private Combo componentMappingCombo;
237
238         protected DefaultMappingsDialog(Shell parentShell, Resource configuration) {
239             super(parentShell);
240             this.configuration = configuration;
241             setTitle("Select mappings for new DN diagram");
242         }
243
244         public Resource getDefaultVertexMapping() {
245             return defaultVertexMapping;
246         }
247
248         public Resource getDefaultEdgeMapping() {
249             return defaultEdgeMapping;
250         }
251
252         @Override
253         protected Control createDialogArea(Composite parent) {
254             composite = (Composite) super.createDialogArea(parent);
255             
256             createMappingsGroup(composite);
257             createExistingCompositeGroup(composite);
258             createCRSSettingsGroup(composite);
259             
260             // compute default values
261             Simantics.getSession().asyncRequest(new ReadRequest() {
262
263                 @Override
264                 public void run(ReadGraph graph) throws DatabaseException {
265                     
266                     vertexMappings = getVertexMappings(graph, configuration);
267                     edgeMappings = getEdgeMappings(graph, configuration);
268                     
269                     composites = getComposites(graph, configuration);
270                   
271                     crss = getCRSs(graph, configuration);
272                     
273                     composite.getDisplay().asyncExec(() -> {
274                         
275                         vertexMappingCombo.setItems(vertexMappings.keySet().toArray(new String[vertexMappings.size()]));
276                         edgeMappingCombo.setItems(edgeMappings.keySet().toArray(new String[edgeMappings.size()]));
277                         
278                         crsCombo.setItems(crss.keySet().toArray(new String[crss.size()]));
279                         
280                         compositeMappingCombo.setItems(composites.keySet().toArray(new String[composites.size()]));
281                         vertexMappingCombo.select(0);
282                         edgeMappingCombo.select(0);
283                         
284                         crsCombo.select(0);
285                         
286                         if (!composites.isEmpty())
287                             compositeMappingCombo.select(0);
288                     }); 
289                     
290                 }
291             });
292             return composite;
293         }
294         
295         protected Map<String, Resource> getComposites(ReadGraph graph, Resource element) throws DatabaseException {
296             List<Resource> nonDistrictComposites = composites.values().stream().filter(comp -> {
297                 try {
298                     return !graph.isInstanceOf(comp, DistrictNetworkResource.getInstance(graph).Composite);
299                 } catch (ServiceException e1) {
300                     LOGGER.error("Could not check if composite " + comp + " is instanceOf DistrictNetwork.composite");
301                     return false;
302                 }
303             }).collect(Collectors.toList());
304             Map<String, Resource> result = new HashMap<>(nonDistrictComposites.size());
305             Layer0 L0 = Layer0.getInstance(graph);
306             nonDistrictComposites.forEach(mapping -> {
307                 try {
308                     String name = graph.getRelatedValue2(mapping, L0.HasName);
309                     result.put(name, mapping);
310                 } catch (DatabaseException e) {
311                     LOGGER.error("Could not read name of " + mapping, e);
312                 }
313             });
314             return result;
315         }
316
317         private void createMappingsGroup(Composite parent) {
318             Group group= new Group(parent, SWT.NONE);
319             group.setFont(parent.getFont());
320             group.setText("Default mappings");
321             GridDataFactory.fillDefaults().grab(true, false).applyTo(group);
322             group.setLayout(new GridLayout(1, false));
323             
324             Composite cmposite = new Composite(group, SWT.NONE);
325             cmposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
326             cmposite.setLayout(new GridLayout(2, false));
327             
328             Label vertexMappingLabel = new Label(cmposite, SWT.NONE);
329             vertexMappingLabel.setText("Default vertex mapping");
330
331             vertexMappingCombo = new Combo(cmposite, SWT.READ_ONLY | SWT.BORDER);
332             GridDataFactory.fillDefaults().grab(true, false).applyTo(vertexMappingCombo);
333             
334             Label edgeMappingLabel = new Label(cmposite, SWT.NONE);
335             edgeMappingLabel.setText("Default edge mapping");
336
337             edgeMappingCombo = new Combo(cmposite, SWT.READ_ONLY | SWT.BORDER);
338             GridDataFactory.fillDefaults().grab(true, false).applyTo(edgeMappingCombo);
339         }
340         
341         private void createExistingCompositeGroup(Composite parent) {
342             Group group= new Group(parent, SWT.NONE);
343             group.setFont(parent.getFont());
344             group.setText("Mapped composite");
345             GridDataFactory.fillDefaults().grab(true, false).applyTo(group);
346             group.setLayout(new GridLayout(1, false));
347             
348             Composite cmposite = new Composite(group, SWT.NONE);
349             cmposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
350             cmposite.setLayout(new GridLayout(2, false));
351             
352             Label compositeMappingLabel = new Label(cmposite, SWT.NONE);
353             compositeMappingLabel.setText("Select composite");
354
355             compositeMappingCombo = new Combo(cmposite, SWT.READ_ONLY | SWT.BORDER);
356             GridDataFactory.fillDefaults().grab(true, false).applyTo(compositeMappingCombo);
357             compositeMappingCombo.addSelectionListener(new SelectionAdapter() {
358                 
359                 @Override
360                 public void widgetSelected(SelectionEvent e) {
361                     super.widgetSelected(e);
362                     recalculateMappapleComponents();
363                 }
364             });
365             
366             Label compojnentMappingLabel = new Label(cmposite, SWT.NONE);
367             compojnentMappingLabel.setText("Select component");
368             
369             componentMappingCombo = new Combo(cmposite, SWT.READ_ONLY | SWT.BORDER);
370             GridDataFactory.fillDefaults().grab(true, false).applyTo(componentMappingCombo);
371         }
372         
373         protected void recalculateMappapleComponents() {
374             Simantics.getSession().asyncRequest(new ReadRequest() {
375                 
376                 @Override
377                 public void run(ReadGraph graph) throws DatabaseException {
378                     
379                     
380                     composite.getDisplay().asyncExec(() -> {
381                         
382                     }); 
383                 }
384             });
385         }
386
387         private void createCRSSettingsGroup(Composite parent) {
388             Group group= new Group(parent, SWT.NONE);
389             group.setFont(parent.getFont());
390             group.setText("CRS settings");
391             GridDataFactory.fillDefaults().grab(true, false).applyTo(group);
392             group.setLayout(new GridLayout(1, false));
393             
394             Composite cmposite = new Composite(group, SWT.NONE);
395             cmposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
396             cmposite.setLayout(new GridLayout(2, false));
397             
398             Label vertexMappingLabel = new Label(cmposite, SWT.NONE);
399             vertexMappingLabel.setText("Default CRS");
400
401             crsCombo = new Combo(cmposite, SWT.READ_ONLY | SWT.BORDER);
402             GridData textData = new GridData(SWT.FILL, SWT.CENTER, true, false);
403             crsCombo.setLayoutData(textData);
404         }
405         
406
407         @Override
408         protected void computeResult() {
409             defaultVertexMapping = vertexMappings.get(vertexMappingCombo.getItem(vertexMappingCombo.getSelectionIndex()));
410             defaultEdgeMapping = edgeMappings.get(edgeMappingCombo.getItem(edgeMappingCombo.getSelectionIndex()));
411             defaultCRS = crss.get(crsCombo.getItem(crsCombo.getSelectionIndex()));
412         }
413
414         public Resource getCRS() {
415             return defaultCRS;
416         }
417         
418     }
419     
420     private static void queryInitialValuesAndCreateComposite(final Resource compositeType, final Resource target,
421             String defaultName, final Procedure<Resource> procedure) {
422         DefaultMappingsDialog dialog = new DefaultMappingsDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), target);
423
424         if (dialog.open() != Dialog.OK) {
425             procedure.execute(null);
426             return;
427         }
428         Simantics.getSession().asyncRequest(
429                 NewCompositeActionFactory.createCompositeRequest(target, defaultName, compositeType),
430                 new Procedure<Resource>() {
431                     @Override
432                     public void execute(Resource composite) {
433                         Simantics.getSession().asyncRequest(new WriteRequest() {
434                             
435                             @Override
436                             public void perform(WriteGraph graph) throws DatabaseException {
437                                 DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
438                                 Resource diagram = graph.getSingleObject(composite, ModelingResources.getInstance(graph).CompositeToDiagram);
439                                 graph.claim(diagram, DN.EdgeDefaultMapping, dialog.getDefaultEdgeMapping());
440                                 graph.claim(diagram, DN.VertexDefaultMapping, dialog.getDefaultVertexMapping());
441                                 graph.claim(diagram, DN.HasSpatialRefSystem, dialog.getCRS());
442                                 
443                                 // Generated name prefix from composite name
444                                 String compositeName = graph.getRelatedValue2(composite, Layer0.getInstance(graph).HasName, Bindings.STRING);
445                                 graph.claimLiteral(diagram, Layer0X.getInstance(graph).HasGeneratedNamePrefix, "N" + compositeName.substring(compositeName.length() - 1, compositeName.length()));
446                             }
447                         });
448                         DefaultActions.asyncPerformDefaultAction(Simantics.getSession(), composite, false, false, true);
449                         procedure.execute(composite);
450                     }
451
452                     @Override
453                     public void exception(Throwable t) {
454                         LOGGER.error("Failed to create composite, see exception for details.", t);
455                         procedure.exception(t);
456                     }
457                 });
458     }
459
460     public static Collection<Resource> getDistrictDiagrams(ReadGraph graph) throws DatabaseException {
461         Layer0 L0 = Layer0.getInstance(graph);
462         Collection<Resource> indexRoots = graph.sync(new ObjectsWithType(Simantics.getProjectResource(), L0.ConsistsOf, L0.IndexRoot));
463         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
464         Set<Resource> results = new HashSet<>();
465         for (Resource indexRoot : indexRoots) {
466             Collection<Resource> diagrams = QueryIndexUtils.searchByType(graph, indexRoot, DN.Diagram);
467             results.addAll(diagrams);
468         }
469         return results;
470     }
471
472     private static List<String> listInstanceNames(ReadGraph graph, Variable context, Resource type) throws DatabaseException {
473         Resource indexRoot = Variables.getIndexRoot(graph, context);
474         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
475         List<Resource> properties = QueryIndexUtils.searchByType(graph, indexRoot, DN.Vertex_ScaleProperty);
476         return properties.stream()
477                 .map(m -> createEnumeratedValue(graph, m))
478                 .map(EnumeratedValue::getName)
479                 .collect(Collectors.toList());
480     }
481
482     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
483     public static Object edgeThicknessPropertyEnumerationValues(ReadGraph graph, Resource resource, Variable context) throws DatabaseException {
484         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
485         return listInstanceNames(graph, context, DN.Edge_ThicknessProperty);
486     }
487
488     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
489     public static Object nodeScalePropertyEnumerationValues(ReadGraph graph, Resource resource, Variable context) throws DatabaseException {
490         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
491         return listInstanceNames(graph, context, DN.Vertex_ScaleProperty);
492     }
493
494     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
495     public static Object edgeThicknessPropertyModifier(ReadGraph graph, Resource resource, Variable context) throws DatabaseException {
496         Resource diagram = resolveElement(graph, context);
497         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
498         return baseMappingModifier(graph, diagram, DN.Diagram_edgeThicknessProperty, DN.Edge_ThicknessProperty, context);
499     }
500
501     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
502     public static Object nodeScalePropertyModifier(ReadGraph graph, Resource resource, Variable context) throws DatabaseException {
503         Resource diagram = resolveElement(graph, context);
504         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
505         return baseMappingModifier(graph, diagram, DN.Diagram_nodeScaleProperty, DN.Vertex_ScaleProperty, context);
506     }
507
508     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
509     public static Function1<Resource, Double> hasDiameterValue(ReadGraph graph, Resource resource, Variable context) throws DatabaseException {
510         return directPropertyValueFunction(DistrictNetworkResource.getInstance(graph).Edge_HasDiameter, 0);
511     }
512
513     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
514     public static Function1<Resource, Double> hasNominalMassFlowValue(ReadGraph graph, Resource resource, Variable context) throws DatabaseException {
515         return directPropertyValueFunction(DistrictNetworkResource.getInstance(graph).Edge_HasNominalMassFlow, 0);
516     }
517
518     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
519     public static Function1<Resource, Double> hasNominalSupplyPressure(ReadGraph graph, Resource resource, Variable context) throws DatabaseException {
520         return directPropertyValueFunction(DistrictNetworkResource.getInstance(graph).Vertex_HasSupplyPressure, 0);
521     }
522
523     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
524     public static Function1<Resource, Double> hasElevation(ReadGraph graph, Resource resource, Variable context) throws DatabaseException {
525         return directPropertyValueFunction(DistrictNetworkResource.getInstance(graph).Vertex_HasElevation, 0);
526     }
527
528     private static final Function1<Resource, Double> ONE = new FunctionImpl1<Resource, Double>() {
529         private final Double ONE = 1.0;
530         @Override
531         public Double apply(Resource edge) {
532             return ONE;
533         }
534         @Override
535         public String toString() {
536             return "1";
537         }
538     };
539
540     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
541     public static Function1<Resource, Double> constantOne(ReadGraph graph, Resource resource, Variable context) throws DatabaseException {
542         return ONE;
543     }
544
545     private static Function1<Resource, Double> directPropertyValueFunction(Resource property, double defaultValue) throws DatabaseException {
546         Double def = defaultValue;
547         return new FunctionImpl1<Resource, Double>() {
548             @Override
549             public Double apply(Resource edge) {
550                 ReadGraph graph = (ReadGraph) SCLContext.getCurrent().get("graph");
551                 try {
552                     Double d = graph.getPossibleRelatedValue(edge, property, Bindings.DOUBLE);
553                     return d != null ? d : def;
554                 } catch (DatabaseException e) {
555                     LOGGER.error("Failed to evaluate property value", e);
556                     return def;
557                 }
558             }
559         };
560     }
561
562     private static class RangeValidator implements Function1<String, String> {
563         private double min;
564         private double max;
565         public RangeValidator(double min, double max) {
566             this.min = min;
567             this.max = max;
568         }
569         @Override
570         public String apply(String s) {
571             try {
572                 double d = Double.parseDouble(s);
573                 if (d < min)
574                     return "Value must be greater than or equal to " + min;
575                 if (d > max)
576                     return "Value must be less than or equal to " + max;
577                 return null;
578             } catch (NumberFormatException e) {
579                 return "Specified value is not a number";
580             }
581         }
582     }
583
584     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
585     public static Object hueValidator(ReadGraph graph, Resource r, Variable context) throws DatabaseException {
586         return new RangeValidator(0, 360);
587     }
588
589     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
590     public static Object saturationValidator(ReadGraph graph, Resource r, Variable context) throws DatabaseException {
591         return new RangeValidator(0, 100);
592     }
593
594     @SCLValue(type = "ReadGraph -> Resource -> Variable -> b")
595     public static Object brightnessValidator(ReadGraph graph, Resource r, Variable context) throws DatabaseException {
596         String importEntry = null;
597         Resource root = Variables.getPossibleIndexRoot(graph, context);
598         if (root != null) {
599             Resource sclmain = Layer0Utils.getPossibleChild(graph, root, "SCLMain");
600             if (sclmain != null) {
601                 importEntry = graph.getPossibleURI(sclmain);
602             }
603         }
604         SCLContext ctx = SCLContext.getCurrent();
605         Object oldGraph = ctx.put("graph", graph);
606         try {
607             return new BrightnessExpressionValidator(
608                     importEntry != null
609                     ? Arrays.asList(importEntry)
610                     : Collections.emptyList());
611         } finally {
612             ctx.put("graph", oldGraph);
613         }
614     }
615
616     private static class BrightnessExpressionValidator implements Function1<String, String> {
617         private CommandSession session;
618
619         public BrightnessExpressionValidator(List<String> importEntries) {
620             this.session = new CommandSession(SCLOsgi.MODULE_REPOSITORY, SCLReportingHandler.DEFAULT);
621             this.session.setImportEntries(imports(importEntries));
622         }
623
624         private ArrayList<CommandSessionImportEntry> imports(List<String> entries) {
625             ArrayList<CommandSessionImportEntry> result = new ArrayList<>();
626             entries.stream().map(CommandSessionImportEntry::new).forEach(result::add);
627             if (entries.isEmpty())
628                 result.add(new CommandSessionImportEntry("Simantics/District/SCLMain"));
629             return result;
630         }
631
632         @Override
633         public String apply(String s) {
634             s = s.trim();
635             if (!s.startsWith("="))
636                 return "Expression expected, must start with '='";
637             CompilationError[] errors = session.validate(s.substring(1));
638             if(errors.length == 0)
639                 return null;
640             return errors[0].description;
641         }
642     }
643
644 }