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