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