]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.spreadsheet.graph/src/org/simantics/spreadsheet/graph/SpreadsheetGraphUtils.java
Adopt spreadsheet changes made in Balas development
[simantics/platform.git] / bundles / org.simantics.spreadsheet.graph / src / org / simantics / spreadsheet / graph / SpreadsheetGraphUtils.java
1 package org.simantics.spreadsheet.graph;
2
3 import java.io.File;
4 import java.io.FileNotFoundException;
5 import java.io.FileOutputStream;
6 import java.io.IOException;
7 import java.io.ObjectOutputStream;
8 import java.util.ArrayList;
9 import java.util.Collection;
10 import java.util.HashMap;
11 import java.util.Iterator;
12 import java.util.List;
13 import java.util.Map;
14
15 import org.simantics.Simantics;
16 import org.simantics.databoard.Bindings;
17 import org.simantics.databoard.binding.mutable.Variant;
18 import org.simantics.databoard.util.binary.RandomAccessBinary;
19 import org.simantics.datatypes.DatatypeResource;
20 import org.simantics.datatypes.literal.Font;
21 import org.simantics.datatypes.literal.RGB;
22 import org.simantics.datatypes.utils.BTree;
23 import org.simantics.db.ReadGraph;
24 import org.simantics.db.Resource;
25 import org.simantics.db.WriteGraph;
26 import org.simantics.db.common.request.BinaryRead;
27 import org.simantics.db.common.request.ObjectsWithType;
28 import org.simantics.db.common.request.UnaryRead;
29 import org.simantics.db.common.request.WriteRequest;
30 import org.simantics.db.common.utils.LiteralFileUtil;
31 import org.simantics.db.exception.DatabaseException;
32 import org.simantics.db.exception.ServiceException;
33 import org.simantics.db.layer0.util.Layer0Utils;
34 import org.simantics.db.layer0.variable.StandardGraphChildVariable;
35 import org.simantics.db.layer0.variable.Variable;
36 import org.simantics.db.layer0.variable.Variables;
37 import org.simantics.db.procedure.Listener;
38 import org.simantics.db.service.ClusteringSupport;
39 import org.simantics.layer0.Layer0;
40 import org.simantics.scl.compiler.commands.CommandSession;
41 import org.simantics.scl.runtime.SCLContext;
42 import org.simantics.scl.runtime.function.Function;
43 import org.simantics.scl.runtime.tuple.Tuple0;
44 import org.simantics.scl.runtime.tuple.Tuple2;
45 import org.simantics.simulator.toolkit.StandardRealm;
46 import org.simantics.spreadsheet.CellEditor;
47 import org.simantics.spreadsheet.ExternalRef;
48 import org.simantics.spreadsheet.OperationMode;
49 import org.simantics.spreadsheet.Range;
50 import org.simantics.spreadsheet.Spreadsheets;
51 import org.simantics.spreadsheet.Transaction;
52 import org.simantics.spreadsheet.graph.synchronization.SpreadsheetSynchronizationEventHandler;
53 import org.simantics.spreadsheet.resource.SpreadsheetResource;
54 import org.simantics.spreadsheet.solver.SheetNode;
55 import org.simantics.spreadsheet.solver.SpreadsheetBook;
56 import org.simantics.spreadsheet.solver.SpreadsheetCell;
57 import org.simantics.spreadsheet.solver.SpreadsheetEngine;
58 import org.simantics.spreadsheet.solver.SpreadsheetLine;
59 import org.simantics.spreadsheet.solver.SpreadsheetStyle;
60 import org.simantics.spreadsheet.util.SpreadsheetUtils;
61 import org.simantics.structural.synchronization.client.Synchronizer;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 import gnu.trove.iterator.TObjectIntIterator;
66 import gnu.trove.map.hash.TObjectIntHashMap;
67
68 public class SpreadsheetGraphUtils {
69
70     private static final Logger LOGGER = LoggerFactory.getLogger(SpreadsheetGraphUtils.class);
71
72     public static File extractInitialCondition(ReadGraph graph, Resource ic) throws DatabaseException, IOException {
73
74         SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
75
76         File temp = Simantics.getTempfile("excel","ic");
77
78         LiteralFileUtil.copyRandomAccessBinaryToFile(graph, ic, SR.InitialCondition_bytes, temp);
79         if (temp.length() == 0)
80             throw new FileNotFoundException("Snapshot file does not exist.\nThis seems to be a database bug that manifests as total loss of state file data.\nThis error prevents the program from crashing.");
81
82         return temp;
83         
84     }
85         
86     public static RandomAccessBinary getOrCreateRandomAccessBinary(WriteGraph graph, Resource initialCondition) throws DatabaseException, IOException {
87
88         SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
89         
90         // We put snapshot literals in their own clusters for now just to be safe
91         Resource literal = graph.getPossibleObject(initialCondition, SR.InitialCondition_bytes);
92         if (literal != null) {
93             RandomAccessBinary rab = graph.getRandomAccessBinary(literal);
94             rab.position(0);
95             rab.removeBytes(rab.length(), RandomAccessBinary.ByteSide.Right);
96             return rab;
97         } else {
98             Layer0 L0 = Layer0.getInstance(graph);
99             ClusteringSupport cs = graph.getService(ClusteringSupport.class);
100             literal = graph.newResource(cs.createCluster());
101             graph.claim(literal, L0.InstanceOf, null, L0.ByteArray);
102             graph.claim(initialCondition, SR.InitialCondition_bytes, SR.InitialCondition_bytes_Inverse, literal);
103             return graph.createRandomAccessBinary(literal, Bindings.BYTE_ARRAY.type(), null);
104         }
105     }
106
107     public static Resource saveInitialCondition(WriteGraph graph, Variable run, Resource container, String name) throws DatabaseException {
108
109                 String sessionName = run.getParent(graph).getURI(graph);
110
111                 Resource bookResource = run.getRepresents(graph);
112                 
113         StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
114         SpreadsheetBook book = realm.getEngine();
115
116         try {
117         
118                 File temp = Simantics.getTempfile("excel", "ic");
119                 System.err.println("Saving initial condition to " + temp.getAbsolutePath());
120                 
121                         FileOutputStream fileOut = new FileOutputStream(temp);
122                         ObjectOutputStream out = new ObjectOutputStream(fileOut);
123                         out.writeObject(book);
124                         out.close();
125                         fileOut.close();
126                         
127                         Layer0 L0 = Layer0.getInstance(graph);
128                 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
129                         Resource ic = graph.newResource();
130                         graph.claim(ic, L0.InstanceOf, SR.InitialCondition);
131                         graph.addLiteral(ic, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);
132                 
133                 RandomAccessBinary rab = getOrCreateRandomAccessBinary(graph, ic);
134                 LiteralFileUtil.copyRandomAccessBinaryFromFile(temp, rab);
135                 
136                         graph.claim(container, L0.ConsistsOf, L0.PartOf, ic);
137                         
138                         graph.deny(bookResource, SR.HasInitialCondition);
139                         graph.claim(bookResource, SR.HasInitialCondition, ic);
140                         graph.claim(ic, SR.InitialCondition_ConditionOf, bookResource);
141                         
142                         setDefaultInitialConditionForBook(graph, bookResource, ic);
143
144                 return ic;
145                 
146         } catch (IOException e) {
147                 
148                 throw new DatabaseException(e);
149                 
150         }
151     }
152     
153     public static void setDefaultInitialConditionForBook(WriteGraph graph, Resource book, Resource ic) throws ServiceException {
154         SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
155         graph.deny(book, SR.Book_HasDefaultInitialCondition);
156         graph.claim(ic, SR.InitialCondition_DefaultConditionOf, book);
157     }
158
159     public static void evaluateAll(ReadGraph graph, Variable run) throws DatabaseException {
160
161                 String sessionName = run.getParent(graph).getURI(graph);
162         StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
163         SpreadsheetBook book = realm.getEngine();
164         book.accept(new EvaluateAll(book));
165         
166     }
167
168     public static void invalidateAll(ReadGraph graph, Variable run) throws DatabaseException {
169
170                 String sessionName = run.getParent(graph).getURI(graph);
171         StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
172         SpreadsheetBook book = realm.getEngine();
173         book.accept(new InvalidateAll());
174         realm.getNodeManager().refreshVariables();
175         
176     }
177     
178     public static boolean fullSynchronization(ReadGraph graph, Variable run) throws DatabaseException {
179         return partialSynchronization(graph, run, null);
180     }
181
182     public static boolean partialSynchronization(ReadGraph graph, Variable run, TObjectIntHashMap<Variable> changeFlags) throws DatabaseException {
183
184         Synchronizer synchronizer = new Synchronizer(graph);
185                 String sessionName = run.getParent(graph).getURI(graph);
186                 
187         Resource bookResource = run.getRepresents(graph);
188         Variable configuration = Variables.getVariable(graph, bookResource);
189         
190         StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
191         SpreadsheetBook book = realm.getEngine();
192                         
193         SpreadsheetSynchronizationEventHandler handler = new SpreadsheetSynchronizationEventHandler(graph, book);
194         
195         if (changeFlags == null) {
196             synchronizer.fullSynchronization(configuration, handler);
197         } else {
198             
199             TObjectIntIterator<Variable> iter = changeFlags.iterator();
200             iter.advance();
201             Variable row = iter.key();
202             
203             Variable rowParent = row.getParent(graph);
204             while (!rowParent.equals(configuration)) {
205                 changeFlags.put(rowParent, 1);
206                 rowParent = rowParent.getParent(graph);
207             }
208             
209             changeFlags.put(configuration, 1);
210             
211             synchronizer.partialSynchronization(configuration, handler, changeFlags);
212         }
213         
214         realm.getNodeManager().fireNodeListeners();
215         return handler.getDidChanges();
216         
217     }
218
219     public static Variable findCell(ReadGraph graph, Variable run, String reference) throws DatabaseException {
220
221         int pos = reference.indexOf("!");
222         String sheetName = reference.substring(0, pos);
223         String cellName = reference.substring(pos+1);
224
225                 String sessionName = run.getParent(graph).getURI(graph);
226         StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
227         SpreadsheetBook book = realm.getEngine();
228         SpreadsheetEngine engine = book.getEngine(sheetName);
229         if(engine == null) return null;
230         
231         Range r = Spreadsheets.decodeCellAbsolute(cellName);
232         SpreadsheetLine line = engine.getLine(r.startRow);
233         if(line == null) return null;
234         
235         String path = line.getPath();
236         if(path == null) return null;
237         
238         Variable lineVariable = run.browse(graph, path);
239         if(lineVariable==null) return null;
240         
241         return lineVariable.getChild(graph, cellName);
242         
243     }
244
245     
246
247     public static List<Variable> possibleConfigurationCellVariables(ReadGraph graph, Variable sheet, Range range) throws DatabaseException {
248         List<Variable> rowVariables = possibleConfigurationLineVariables(graph, sheet, range);
249         List<Variable> result = new ArrayList<>();
250         for (Variable variable : rowVariables) {
251             Collection<Variable> children = variable.getChildren(graph);
252             for (Variable child : children) {
253                 if (variableInRange(graph, child, range)) {
254                     result.add(child);
255                 }
256             }
257         }
258         return result;
259     }
260     
261     public static Map<Integer, Resource> possibleConfigurationLineResources(ReadGraph graph, Variable sheet, Range range) throws DatabaseException {
262         Variable lines = sheet.getPossibleChild(graph, "Lines");
263         if (lines == null)
264             throw new DatabaseException("Invalid input variable " + sheet.getURI(graph));
265         Resource linesR = lines.getRepresents(graph);
266         BTree bt = new BTree(graph, linesR);
267         List<Tuple2> tuples = bt.searchRangeBTree(graph, Variant.ofInstance(range.startRow), Variant.ofInstance(range.endRow));
268         Map<Integer, Resource> result = new HashMap<>(tuples.size());
269         for (Tuple2 tuple : tuples) {
270             Integer lineNumber = (Integer)((Variant)tuple.c0).getValue();
271             Resource resource = (Resource)tuple.c1;
272             result.put(lineNumber, resource);
273         }
274         return result; 
275     }
276     
277     public static List<Variable> possibleConfigurationLineVariables(ReadGraph graph, Variable sheet, Range range) throws DatabaseException {
278         Map<Integer, Resource> rows = possibleConfigurationLineResources(graph, sheet, range);
279         List<Variable> result = new ArrayList<>(rows.size());
280         for (Resource row: rows.values()) {
281             Variable lineVar = Variables.getPossibleVariable(graph, row);
282             if (lineVar != null)
283                 result.add(lineVar);
284         }
285         return result;
286     }
287     
288     public static List<Variable> possibleRunLineVariables(ReadGraph graph, Variable sheetRun, Range range) throws DatabaseException {
289         
290         Variable run = sheetRun.getParent(graph);
291         
292         String sheetName = sheetRun.getName(graph);
293         String sessionName = run.getParent(graph).getURI(graph);
294         
295         StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
296         SpreadsheetBook book = realm.getEngine();
297         
298         SpreadsheetEngine engine = book.getEngine(sheetName);
299         if(engine == null) return null;
300         
301         List<Variable> result = new ArrayList<>();
302         
303         int end = range.endRow < engine.lines.getMaxRow() ? range.endRow : engine.lines.getMaxRow();
304         for (int i = range.startRow; i <= end; i++) {
305                 SpreadsheetLine line = engine.getLine(i);
306                 if(line == null)
307                         continue;
308                 
309                 String path = line.getPath();
310                 path = line.getPath();
311                 if(path == null)
312                         continue;
313                 
314                 Variable lineVariable = run.browse(graph, path);
315                 if(lineVariable==null)
316                         continue;
317                 result.add(lineVariable);
318         }
319     
320         return result;
321     }
322     
323     public static List<Variable> possibleRunCellVariables(ReadGraph graph, Variable sheetRun, Range range) throws DatabaseException {
324         List<Variable> runLineVariable = possibleRunLineVariables(graph, sheetRun, range);
325         List<Variable> result = new ArrayList<>();
326         for (Variable variable : runLineVariable) {
327 //              System.out.println("line: " + variable.getURI(graph));
328             for (Variable child : variable.getChildren(graph)) {
329 //              System.out.print("cell : " + child.getURI(graph));
330                 if (variableInRange(graph, child, range)) {
331                     result.add(child);
332                 }
333             }
334         }
335         return result;
336     }
337     
338     private static boolean variableInRange(ReadGraph graph, Variable child, Range range) throws DatabaseException {
339         String name = child.getName(graph);
340         Range childRange = Spreadsheets.decodeCellAbsolute(name);
341 //        System.out.print(" and range " + childRange);
342         if (childRange != null && range.contains(childRange)) {
343 //              System.out.println(" => range.contains(childRange) = true");
344             return true;
345         }
346 //        System.out.println();
347         return false;
348     }
349     
350     public static Map<Integer, Resource> createConfigurationLineResources(WriteGraph graph, Variable sheet, Range range) throws DatabaseException {
351         Layer0 L0 = Layer0.getInstance(graph);
352         SpreadsheetResource SHEET = SpreadsheetResource.getInstance(graph);
353         
354         Variable lines = sheet.getPossibleChild(graph, "Lines");
355         if (lines == null)
356             throw new DatabaseException("Invalid input variable " + sheet.getURI(graph));
357         Resource linesR = lines.getRepresents(graph);
358         BTree bt = new BTree(graph, linesR);
359         
360         Map<Integer, Resource> result = new HashMap<>();
361         for (int lineNumber = range.startRow; lineNumber <= range.endRow; lineNumber++) {
362             Resource line = graph.newResource();
363             graph.claim(line, L0.InstanceOf, null, SHEET.Line);
364             graph.claimLiteral(line, L0.HasName, L0.NameOf, L0.String, "Row" + lineNumber, Bindings.STRING);
365             bt.insertBTree(graph, Variant.ofInstance(lineNumber), line);
366             result.put(lineNumber, line);
367         }
368         return result;
369     }
370
371     public static List<Variable> getOrCreateConfigurationCellVariables(WriteGraph graph, Variable sheet, Range range) throws DatabaseException {
372         
373         List<Variable> rows = possibleConfigurationLineVariables(graph, sheet, range);
374         if (rows.isEmpty()) {
375             createConfigurationLineResources(graph, sheet, range);
376             rows = possibleConfigurationLineVariables(graph, sheet, range);
377         }
378         
379         List<Variable> cells = possibleConfigurationCellVariables(graph, sheet, range);
380         if (cells.isEmpty()) {
381             Iterator<Variable> rowIterator = rows.iterator();
382             for (int rowNumber = range.startRow; rowNumber <= range.endRow; rowNumber++) {
383                 Variable row = rowIterator.next();
384                 for (int colNumber = range.startColumn; colNumber <= range.endColumn; colNumber++) {
385                     String location = Spreadsheets.cellName(rowNumber, colNumber);
386                     defaultCreateCell(graph, row, location, new Variant(Bindings.STRING, ""));
387                 }
388             }
389         }
390         
391         cells = possibleConfigurationCellVariables(graph, sheet, range);
392         if(cells.isEmpty())
393             throw new DatabaseException("Unexpected problem while creating spreadsheet cell at '" + range + "'");
394         
395         return cells;
396     }
397     
398     private static void defaultCreateCell(WriteGraph graph, Variable parent, String location, Variant value) throws DatabaseException {
399
400         Layer0 L0 = Layer0.getInstance(graph);
401         SpreadsheetResource SHEET = SpreadsheetResource.getInstance(graph);
402         Resource container = parent.getRepresents(graph);
403         
404         Resource cell = graph.newResource();
405         graph.claim(cell, L0.InstanceOf, null, SHEET.TextCell);
406         graph.addLiteral(cell, L0.HasName, L0.NameOf, L0.String, location, Bindings.STRING);
407         graph.addLiteral(cell, SHEET.Cell_content, SHEET.Cell_content_Inverse, L0.Variant, value, Bindings.VARIANT);
408         graph.claim(cell, L0.PartOf, container);
409         
410         Resource book = Variables.getContext(graph, parent).getRepresents(graph);
411         
412         
413         Collection<Resource> objects = graph.sync(new ObjectsWithType(book, L0.ConsistsOf, SHEET.Style));
414         
415         int styleId = SpreadsheetStyle.empty().getStyleId();
416         Resource style = null;
417         for (Resource possibleStyle : objects) {
418             int possibleStyleId = graph.getRelatedValue2(possibleStyle, SHEET.Style_id, Bindings.INTEGER);
419             if (possibleStyleId == styleId) {
420                 style = possibleStyle;
421                 break;
422             }
423         }
424         
425         if (style == null) {
426             style = graph.newResource();
427             graph.claim(style, L0.InstanceOf, null, SHEET.Style);
428             graph.claim(style, L0.PartOf, book);
429             
430             int id = objects.size();
431             graph.claimLiteral(style, L0.HasName, "Style_" + id);
432             graph.claimLiteral(style, SHEET.Style_id, styleId, Bindings.INTEGER);
433         }
434         graph.claim(cell, SHEET.Cell_HasStyle, style);
435         Layer0Utils.addCommentMetadata(graph, "Created cell on location " + location + " with value " + value.toString());
436     }
437
438     public static Resource createStyle(WriteGraph graph, Resource book, SpreadsheetStyle sstyle) throws DatabaseException {
439         Layer0 L0 = Layer0.getInstance(graph);
440         SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
441         Resource style = graph.newResource();
442         graph.claim(style, L0.InstanceOf, null, SR.Style);
443         graph.claim(style, L0.PartOf, book);
444         
445         int styleId = sstyle.getStyleId();
446         String styleName = sstyle.name;
447         
448         graph.claimLiteral(style, L0.HasName, styleName);
449         //System.err.println("CREATING STYLE " + styleName + " WITH ID: " + styleId);
450         graph.claimLiteral(style, SR.Style_id, styleId, Bindings.INTEGER);
451         
452         DatatypeResource DATATYPES = DatatypeResource.getInstance(graph);
453         if (sstyle.foreground != null)
454             graph.claimLiteral(style, SR.Cell_foreground, DATATYPES.RGB_Integer, sstyle.foreground, RGB.Integer.BINDING);
455         if (sstyle.background != null)
456             graph.claimLiteral(style, SR.Cell_background, DATATYPES.RGB_Integer, sstyle.background, RGB.Integer.BINDING);
457         if (sstyle.align != -1)
458             graph.claimLiteral(style, SR.Cell_align, sstyle.align, Bindings.INTEGER);
459         if (sstyle.font != null)
460             graph.claimLiteral(style, SR.Cell_font, DATATYPES.Font, sstyle.font, Font.BINDING);
461         if (sstyle.border != -1)
462             graph.claimLiteral(style, SR.Cell_border, sstyle.border);
463         if (sstyle.formatString != null && !sstyle.formatString.isEmpty())
464             graph.claimLiteral(style, SR.Cell_formatString, sstyle.formatString, Bindings.STRING);
465         if (sstyle.formatIndex != -1)
466             graph.claimLiteral(style, SR.Cell_formatIndex, sstyle.formatIndex, Bindings.INTEGER);
467         
468         return style;
469     }
470     
471     public static Resource createBook(WriteGraph graph, Resource parent, String name) throws DatabaseException {
472         Layer0 L0 = Layer0.getInstance(graph);
473         SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
474         Resource book = graph.newResource();
475         graph.claim(book, L0.InstanceOf, SR.Book);
476         graph.claimLiteral(book, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);
477         graph.claim(parent, L0.ConsistsOf, book);
478         
479         return book;
480     }
481     
482     public static Variable constructAndInitializeRunVariable(WriteGraph graph, Resource root) throws DatabaseException {
483         Variable run = SpreadsheetUtils.getBookVariable(graph, root);
484         SpreadsheetGraphUtils.fullSynchronization(graph, run);
485         SpreadsheetGraphUtils.evaluateAll(graph, run);
486         SpreadsheetGraphUtils.saveInitialCondition(graph, run, root, "Initial");
487         return run;
488     }
489     
490     public static Variant extRefVariable(ReadGraph graph, Variable var) throws DatabaseException {
491         return new Variant(Bindings.VOID, new ExternalRefVariable(graph, var));
492     }
493     
494     static class ExternalRefVariable implements ExternalRef {
495
496         final private String uri;
497         
498         public ExternalRefVariable(ReadGraph graph, Variable variable) throws DatabaseException {
499             this.uri = variable.getURI(graph);
500         }
501         
502         @Override
503         public void listen(Object context, ExternalRefListener listener) {
504             Simantics.getSession().asyncRequest(new UnaryRead<String, Variant>(uri) {
505
506                 @Override
507                 public Variant perform(ReadGraph graph) throws DatabaseException {
508                     Variable variable = Variables.getVariable(graph, parameter);
509                     return variable.getVariantValue(graph);
510                 }
511                 
512             }, new Listener<Variant>() {
513
514                 @Override
515                 public void execute(Variant result) {
516                     listener.newValue(result);
517                 }
518
519                 @Override
520                 public void exception(Throwable t) {
521                     LOGGER.error("Error while evaluating variable value", t);
522                 }
523
524                 @Override
525                 public boolean isDisposed() {
526                     return listener.isDisposed();
527                 }
528                 
529             });
530         }
531         
532         @Override
533         public void modify(Object context, Variant newValue) {
534             
535             Simantics.getSession().asyncRequest(new WriteRequest() {
536
537                 @Override
538                 public void perform(WriteGraph graph) throws DatabaseException {
539                     Variable variable = Variables.getVariable(graph, uri);
540                     variable.setValue(graph, newValue);
541                 }
542             });
543             
544         }
545         
546     }
547
548     public static Variant extRefActiveVariable(ReadGraph graph, Variable var) throws DatabaseException {
549         return new Variant(Bindings.VOID, new ExternalRefActiveVariable(graph, var));
550     }
551     
552     static class ExternalRefActiveVariable implements ExternalRef {
553
554         final private String uri;
555         
556         public ExternalRefActiveVariable(ReadGraph graph, Variable variable) throws DatabaseException {
557             this.uri = variable.getURI(graph);
558         }
559         
560         @Override
561         public void listen(Object context, ExternalRefListener listener) {
562             Simantics.getSession().asyncRequest(new BinaryRead<String, String, Variant>((String)context, uri) {
563
564                 @Override
565                 public Variant perform(ReadGraph graph) throws DatabaseException {
566                     Variable contextVariable = Variables.getVariable(graph, parameter);
567                     Variable configVariable = Variables.getVariable(graph, parameter2);
568                     Variable activeVariable = Variables.switchPossibleContext(graph, configVariable, contextVariable.getRepresents(graph));
569                     if(activeVariable == null) return Variant.ofInstance("Could not resolve " + configVariable.getURI(graph) + " for " + contextVariable.getURI(graph));
570                     return activeVariable.getVariantValue(graph);
571                 }
572             }, new Listener<Variant>() {
573
574                 @Override
575                 public void execute(Variant result) {
576                     listener.newValue(result);
577                 }
578
579                 @Override
580                 public void exception(Throwable t) {
581                     LOGGER.error("Error while evaluating variable value, context = " + context + " uri=" + uri, t);
582                 }
583
584                 @Override
585                 public boolean isDisposed() {
586                     return listener.isDisposed();
587                 }
588                 
589             });
590         }
591         
592         @Override
593         public void modify(Object context, Variant newValue) {
594             
595             Simantics.getSession().asyncRequest(new WriteRequest() {
596
597                 @Override
598                 public void perform(WriteGraph graph) throws DatabaseException {
599                     Variable contextVariable = Variables.getVariable(graph, (String)context);
600                     Variable configVariable = Variables.getVariable(graph,uri);
601                     Variable activeVariable = Variables.switchPossibleContext(graph, configVariable, contextVariable.getRepresents(graph));
602                     if(activeVariable == null) return;
603                     activeVariable.setValue(graph, newValue.getValue(), newValue.getBinding());
604                 }
605             });
606             
607         }
608         
609     }
610     
611     public static CellEditor cellEditor(ReadGraph graph, Resource sheet) throws DatabaseException {
612         SpreadsheetResource SHEET = SpreadsheetResource.getInstance(graph);
613         Variable sheetVariable = Variables.getVariable(graph, sheet);
614         return sheetVariable.getPropertyValue(graph, SHEET.cellEditor);
615     }
616     
617     public static final String SPREADSHEET_TRANSACTION = "spreadsheetTransaction";
618
619     @SuppressWarnings({ "rawtypes", "unchecked" })
620     public static Object syncExec(CellEditor editor, OperationMode mode, Function fun) throws InterruptedException {
621         
622         Transaction tr = editor.startTransaction(mode);
623         
624         SCLContext context = SCLContext.getCurrent();
625         Transaction oldTransaction = (Transaction)context.put(SPREADSHEET_TRANSACTION, tr);
626         
627         Object result = null;
628         
629         try {
630
631             result = fun.apply(Tuple0.INSTANCE);
632             
633         } finally {
634             
635             tr.commit();
636             
637             context.put(SPREADSHEET_TRANSACTION, oldTransaction);
638             
639         }
640         
641         return result;
642         
643     }
644     
645     public static int cellColumn(ReadGraph graph, Variable cell) {
646         if(cell instanceof StandardGraphChildVariable) {
647             StandardGraphChildVariable sgcv = (StandardGraphChildVariable)cell;
648             SpreadsheetCell sc = (SpreadsheetCell)sgcv.node.node;
649             return sc.getColumn();
650         }
651         throw new IllegalStateException("Expected StandardGraphChildVariable, got " + cell.getClass().getName());
652     }
653     
654 }