1 package org.simantics.spreadsheet.graph;
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;
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.utils.LiteralFileUtil;
30 import org.simantics.db.exception.DatabaseException;
31 import org.simantics.db.exception.ServiceException;
32 import org.simantics.db.layer0.util.Layer0Utils;
33 import org.simantics.db.layer0.variable.StandardGraphChildVariable;
34 import org.simantics.db.layer0.variable.Variable;
35 import org.simantics.db.layer0.variable.Variables;
36 import org.simantics.db.procedure.Listener;
37 import org.simantics.db.service.ClusteringSupport;
38 import org.simantics.layer0.Layer0;
39 import org.simantics.scl.compiler.commands.CommandSession;
40 import org.simantics.scl.runtime.SCLContext;
41 import org.simantics.scl.runtime.function.Function;
42 import org.simantics.scl.runtime.tuple.Tuple0;
43 import org.simantics.scl.runtime.tuple.Tuple2;
44 import org.simantics.simulator.toolkit.StandardRealm;
45 import org.simantics.spreadsheet.CellEditor;
46 import org.simantics.spreadsheet.ExternalRef;
47 import org.simantics.spreadsheet.OperationMode;
48 import org.simantics.spreadsheet.Range;
49 import org.simantics.spreadsheet.Spreadsheets;
50 import org.simantics.spreadsheet.Transaction;
51 import org.simantics.spreadsheet.graph.synchronization.SpreadsheetSynchronizationEventHandler;
52 import org.simantics.spreadsheet.resource.SpreadsheetResource;
53 import org.simantics.spreadsheet.solver.SheetNode;
54 import org.simantics.spreadsheet.solver.SpreadsheetBook;
55 import org.simantics.spreadsheet.solver.SpreadsheetCell;
56 import org.simantics.spreadsheet.solver.SpreadsheetEngine;
57 import org.simantics.spreadsheet.solver.SpreadsheetLine;
58 import org.simantics.spreadsheet.solver.SpreadsheetStyle;
59 import org.simantics.spreadsheet.util.SpreadsheetUtils;
60 import org.simantics.structural.synchronization.client.Synchronizer;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
64 import gnu.trove.iterator.TObjectIntIterator;
65 import gnu.trove.map.hash.TObjectIntHashMap;
67 public class SpreadsheetGraphUtils {
69 private static final Logger LOGGER = LoggerFactory.getLogger(SpreadsheetGraphUtils.class);
71 public static File extractInitialCondition(ReadGraph graph, Resource ic) throws DatabaseException, IOException {
73 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
75 File temp = Simantics.getTempfile("excel","ic");
77 LiteralFileUtil.copyRandomAccessBinaryToFile(graph, ic, SR.InitialCondition_bytes, temp);
78 if (temp.length() == 0)
79 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 public static RandomAccessBinary getOrCreateRandomAccessBinary(WriteGraph graph, Resource initialCondition) throws DatabaseException, IOException {
87 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
89 // We put snapshot literals in their own clusters for now just to be safe
90 Resource literal = graph.getPossibleObject(initialCondition, SR.InitialCondition_bytes);
91 if (literal != null) {
92 RandomAccessBinary rab = graph.getRandomAccessBinary(literal);
94 rab.removeBytes(rab.length(), RandomAccessBinary.ByteSide.Right);
97 Layer0 L0 = Layer0.getInstance(graph);
98 ClusteringSupport cs = graph.getService(ClusteringSupport.class);
99 literal = graph.newResource(cs.createCluster());
100 graph.claim(literal, L0.InstanceOf, null, L0.ByteArray);
101 graph.claim(initialCondition, SR.InitialCondition_bytes, SR.InitialCondition_bytes_Inverse, literal);
102 return graph.createRandomAccessBinary(literal, Bindings.BYTE_ARRAY.type(), null);
106 public static Resource saveInitialCondition(WriteGraph graph, Variable run, Resource container, String name) throws DatabaseException {
108 String sessionName = run.getParent(graph).getURI(graph);
110 Resource bookResource = run.getRepresents(graph);
112 StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
113 SpreadsheetBook book = realm.getEngine();
117 File temp = Simantics.getTempfile("excel", "ic");
118 System.err.println("Saving initial condition to " + temp.getAbsolutePath());
120 FileOutputStream fileOut = new FileOutputStream(temp);
121 ObjectOutputStream out = new ObjectOutputStream(fileOut);
122 out.writeObject(book);
126 Layer0 L0 = Layer0.getInstance(graph);
127 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
128 Resource ic = graph.newResource();
129 graph.claim(ic, L0.InstanceOf, SR.InitialCondition);
130 graph.addLiteral(ic, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);
132 RandomAccessBinary rab = getOrCreateRandomAccessBinary(graph, ic);
133 LiteralFileUtil.copyRandomAccessBinaryFromFile(temp, rab);
135 graph.claim(container, L0.ConsistsOf, L0.PartOf, ic);
137 graph.deny(bookResource, SR.HasInitialCondition);
138 graph.claim(bookResource, SR.HasInitialCondition, ic);
139 graph.claim(ic, SR.InitialCondition_ConditionOf, bookResource);
141 setDefaultInitialConditionForBook(graph, bookResource, ic);
145 } catch (IOException e) {
147 throw new DatabaseException(e);
152 public static void setDefaultInitialConditionForBook(WriteGraph graph, Resource book, Resource ic) throws ServiceException {
153 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
154 graph.deny(book, SR.Book_HasDefaultInitialCondition);
155 graph.claim(ic, SR.InitialCondition_DefaultConditionOf, book);
158 public static void evaluateAll(ReadGraph graph, Variable run) throws DatabaseException {
160 String sessionName = run.getParent(graph).getURI(graph);
161 StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
162 SpreadsheetBook book = realm.getEngine();
163 book.accept(new EvaluateAll(book));
167 public static void invalidateAll(ReadGraph graph, Variable run) throws DatabaseException {
169 String sessionName = run.getParent(graph).getURI(graph);
170 StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
171 SpreadsheetBook book = realm.getEngine();
172 book.accept(new InvalidateAll());
173 realm.getNodeManager().refreshVariables();
177 public static boolean fullSynchronization(ReadGraph graph, Variable run) throws DatabaseException {
178 return partialSynchronization(graph, run, null);
181 public static boolean partialSynchronization(ReadGraph graph, Variable run, TObjectIntHashMap<Variable> changeFlags) throws DatabaseException {
183 Synchronizer synchronizer = new Synchronizer(graph);
184 String sessionName = run.getParent(graph).getURI(graph);
186 Resource bookResource = run.getRepresents(graph);
187 Variable configuration = Variables.getVariable(graph, bookResource);
189 StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
190 SpreadsheetBook book = realm.getEngine();
192 SpreadsheetSynchronizationEventHandler handler = new SpreadsheetSynchronizationEventHandler(graph, book);
194 if (changeFlags == null) {
195 synchronizer.fullSynchronization(configuration, handler);
198 TObjectIntIterator<Variable> iter = changeFlags.iterator();
200 Variable row = iter.key();
202 Variable rowParent = row.getParent(graph);
203 while (!rowParent.equals(configuration)) {
204 changeFlags.put(rowParent, 1);
205 rowParent = rowParent.getParent(graph);
208 changeFlags.put(configuration, 1);
210 synchronizer.partialSynchronization(configuration, handler, changeFlags);
213 realm.getNodeManager().fireNodeListeners();
214 return handler.getDidChanges();
218 public static Variable findCell(ReadGraph graph, Variable run, String reference) throws DatabaseException {
220 int pos = reference.indexOf("!");
221 String sheetName = reference.substring(0, pos);
222 String cellName = reference.substring(pos+1);
224 String sessionName = run.getParent(graph).getURI(graph);
225 StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
226 SpreadsheetBook book = realm.getEngine();
227 SpreadsheetEngine engine = book.getEngine(sheetName);
228 if(engine == null) return null;
230 Range r = Spreadsheets.decodeCellAbsolute(cellName);
231 SpreadsheetLine line = engine.getLine(r.startRow);
232 if(line == null) return null;
234 String path = line.getPath();
235 if(path == null) return null;
237 Variable lineVariable = run.browse(graph, path);
238 if(lineVariable==null) return null;
240 return lineVariable.getChild(graph, cellName);
246 public static List<Variable> possibleConfigurationCellVariables(ReadGraph graph, Variable sheet, Range range) throws DatabaseException {
247 List<Variable> rowVariables = possibleConfigurationLineVariables(graph, sheet, range);
248 List<Variable> result = new ArrayList<>();
249 for (Variable variable : rowVariables) {
250 Collection<Variable> children = variable.getChildren(graph);
251 for (Variable child : children) {
252 if (variableInRange(graph, child, range)) {
260 public static Map<Integer, Resource> possibleConfigurationLineResources(ReadGraph graph, Variable sheet, Range range) throws DatabaseException {
261 Variable lines = sheet.getPossibleChild(graph, "Lines");
263 throw new DatabaseException("Invalid input variable " + sheet.getURI(graph));
264 Resource linesR = lines.getRepresents(graph);
265 BTree bt = new BTree(graph, linesR);
266 List<Tuple2> tuples = bt.searchRangeBTree(graph, Variant.ofInstance(range.startRow), Variant.ofInstance(range.endRow));
267 Map<Integer, Resource> result = new HashMap<>(tuples.size());
268 for (Tuple2 tuple : tuples) {
269 Integer lineNumber = (Integer)((Variant)tuple.c0).getValue();
270 Resource resource = (Resource)tuple.c1;
271 result.put(lineNumber, resource);
276 public static List<Variable> possibleConfigurationLineVariables(ReadGraph graph, Variable sheet, Range range) throws DatabaseException {
277 Map<Integer, Resource> rows = possibleConfigurationLineResources(graph, sheet, range);
278 List<Variable> result = new ArrayList<>(rows.size());
279 for (Resource row: rows.values()) {
280 Variable lineVar = Variables.getPossibleVariable(graph, row);
287 public static List<Variable> possibleRunLineVariables(ReadGraph graph, Variable sheetRun, Range range) throws DatabaseException {
289 Variable run = sheetRun.getParent(graph);
291 String sheetName = sheetRun.getName(graph);
292 String sessionName = run.getParent(graph).getURI(graph);
294 StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
295 SpreadsheetBook book = realm.getEngine();
297 SpreadsheetEngine engine = book.getEngine(sheetName);
298 if(engine == null) return null;
300 List<Variable> result = new ArrayList<>();
302 int end = range.endRow < engine.lines.getMaxRow() ? range.endRow : engine.lines.getMaxRow();
303 for (int i = range.startRow; i <= end; i++) {
304 SpreadsheetLine line = engine.getLine(i);
308 String path = line.getPath();
309 path = line.getPath();
313 Variable lineVariable = run.browse(graph, path);
314 if(lineVariable==null)
316 result.add(lineVariable);
322 public static List<Variable> possibleRunCellVariables(ReadGraph graph, Variable sheetRun, Range range) throws DatabaseException {
323 List<Variable> runLineVariable = possibleRunLineVariables(graph, sheetRun, range);
324 List<Variable> result = new ArrayList<>();
325 for (Variable variable : runLineVariable) {
326 // System.out.println("line: " + variable.getURI(graph));
327 for (Variable child : variable.getChildren(graph)) {
328 // System.out.print("cell : " + child.getURI(graph));
329 if (variableInRange(graph, child, range)) {
337 private static boolean variableInRange(ReadGraph graph, Variable child, Range range) throws DatabaseException {
338 String name = child.getName(graph);
339 Range childRange = Spreadsheets.decodeCellAbsolute(name);
340 // System.out.print(" and range " + childRange);
341 if (childRange != null && range.contains(childRange)) {
342 // System.out.println(" => range.contains(childRange) = true");
345 // System.out.println();
349 public static Map<Integer, Resource> createConfigurationLineResources(WriteGraph graph, Variable sheet, Range range) throws DatabaseException {
350 Layer0 L0 = Layer0.getInstance(graph);
351 SpreadsheetResource SHEET = SpreadsheetResource.getInstance(graph);
353 Variable lines = sheet.getPossibleChild(graph, "Lines");
355 throw new DatabaseException("Invalid input variable " + sheet.getURI(graph));
356 Resource linesR = lines.getRepresents(graph);
357 BTree bt = new BTree(graph, linesR);
359 Map<Integer, Resource> result = new HashMap<>();
360 for (int lineNumber = range.startRow; lineNumber <= range.endRow; lineNumber++) {
361 Resource line = graph.newResource();
362 graph.claim(line, L0.InstanceOf, null, SHEET.Line);
363 graph.claimLiteral(line, L0.HasName, L0.NameOf, L0.String, "Row" + lineNumber, Bindings.STRING);
364 bt.insertBTree(graph, Variant.ofInstance(lineNumber), line);
365 result.put(lineNumber, line);
370 public static List<Variable> getOrCreateConfigurationCellVariables(WriteGraph graph, Variable sheet, Range range) throws DatabaseException {
372 List<Variable> rows = possibleConfigurationLineVariables(graph, sheet, range);
373 if (rows.isEmpty()) {
374 createConfigurationLineResources(graph, sheet, range);
375 rows = possibleConfigurationLineVariables(graph, sheet, range);
378 List<Variable> cells = possibleConfigurationCellVariables(graph, sheet, range);
379 if (cells.isEmpty()) {
380 Iterator<Variable> rowIterator = rows.iterator();
381 for (int rowNumber = range.startRow; rowNumber <= range.endRow; rowNumber++) {
382 Variable row = rowIterator.next();
383 for (int colNumber = range.startColumn; colNumber <= range.endColumn; colNumber++) {
384 String location = Spreadsheets.cellName(rowNumber, colNumber);
385 defaultCreateCell(graph, row, location, new Variant(Bindings.STRING, ""));
390 cells = possibleConfigurationCellVariables(graph, sheet, range);
392 throw new DatabaseException("Unexpected problem while creating spreadsheet cell at '" + range + "'");
397 private static void defaultCreateCell(WriteGraph graph, Variable parent, String location, Variant value) throws DatabaseException {
399 Layer0 L0 = Layer0.getInstance(graph);
400 SpreadsheetResource SHEET = SpreadsheetResource.getInstance(graph);
401 Resource container = parent.getRepresents(graph);
403 Resource cell = graph.newResource();
404 graph.claim(cell, L0.InstanceOf, null, SHEET.TextCell);
405 graph.addLiteral(cell, L0.HasName, L0.NameOf, L0.String, location, Bindings.STRING);
406 graph.addLiteral(cell, SHEET.Cell_content, SHEET.Cell_content_Inverse, L0.Variant, value, Bindings.VARIANT);
407 graph.claim(cell, L0.PartOf, container);
409 Resource book = Variables.getContext(graph, parent).getRepresents(graph);
412 Collection<Resource> objects = graph.sync(new ObjectsWithType(book, L0.ConsistsOf, SHEET.Style));
414 int styleId = SpreadsheetStyle.empty().getStyleId();
415 Resource style = null;
416 for (Resource possibleStyle : objects) {
417 int possibleStyleId = graph.getRelatedValue2(possibleStyle, SHEET.Style_id, Bindings.INTEGER);
418 if (possibleStyleId == styleId) {
419 style = possibleStyle;
425 style = graph.newResource();
426 graph.claim(style, L0.InstanceOf, null, SHEET.Style);
427 graph.claim(style, L0.PartOf, book);
429 int id = objects.size();
430 graph.claimLiteral(style, L0.HasName, "Style_" + id);
431 graph.claimLiteral(style, SHEET.Style_id, styleId, Bindings.INTEGER);
433 graph.claim(cell, SHEET.Cell_HasStyle, style);
434 Layer0Utils.addCommentMetadata(graph, "Created cell on location " + location + " with value " + value.toString());
437 public static Resource createStyle(WriteGraph graph, Resource book, SpreadsheetStyle sstyle) throws DatabaseException {
438 Layer0 L0 = Layer0.getInstance(graph);
439 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
440 Resource style = graph.newResource();
441 graph.claim(style, L0.InstanceOf, null, SR.Style);
442 graph.claim(style, L0.PartOf, book);
444 int styleId = sstyle.getStyleId();
445 String styleName = sstyle.name;
447 graph.claimLiteral(style, L0.HasName, styleName);
448 //System.err.println("CREATING STYLE " + styleName + " WITH ID: " + styleId);
449 graph.claimLiteral(style, SR.Style_id, styleId, Bindings.INTEGER);
451 DatatypeResource DATATYPES = DatatypeResource.getInstance(graph);
452 if (sstyle.foreground != null)
453 graph.claimLiteral(style, SR.Cell_foreground, DATATYPES.RGB_Integer, sstyle.foreground, RGB.Integer.BINDING);
454 if (sstyle.background != null)
455 graph.claimLiteral(style, SR.Cell_background, DATATYPES.RGB_Integer, sstyle.background, RGB.Integer.BINDING);
456 if (sstyle.align != -1)
457 graph.claimLiteral(style, SR.Cell_align, sstyle.align, Bindings.INTEGER);
458 if (sstyle.font != null)
459 graph.claimLiteral(style, SR.Cell_font, DATATYPES.Font, sstyle.font, Font.BINDING);
460 if (sstyle.border != -1)
461 graph.claimLiteral(style, SR.Cell_border, sstyle.border);
462 if (sstyle.formatString != null && !sstyle.formatString.isEmpty())
463 graph.claimLiteral(style, SR.Cell_formatString, sstyle.formatString, Bindings.STRING);
464 if (sstyle.formatIndex != -1)
465 graph.claimLiteral(style, SR.Cell_formatIndex, sstyle.formatIndex, Bindings.INTEGER);
470 public static Resource createBook(WriteGraph graph, Resource parent, String name) throws DatabaseException {
471 Layer0 L0 = Layer0.getInstance(graph);
472 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
473 Resource book = graph.newResource();
474 graph.claim(book, L0.InstanceOf, SR.Book);
475 graph.claimLiteral(book, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);
476 graph.claim(parent, L0.ConsistsOf, book);
481 public static Variable constructAndInitializeRunVariable(WriteGraph graph, Resource root) throws DatabaseException {
482 Variable run = SpreadsheetUtils.getBookVariable(graph, root);
483 SpreadsheetGraphUtils.fullSynchronization(graph, run);
484 SpreadsheetGraphUtils.evaluateAll(graph, run);
485 SpreadsheetGraphUtils.saveInitialCondition(graph, run, root, "Initial");
489 public static Variant extRefVariable(ReadGraph graph, Variable var) throws DatabaseException {
490 return new Variant(Bindings.VOID, new ExternalRefVariable(graph, var));
493 static class ExternalRefVariable implements ExternalRef {
495 final private String uri;
497 public ExternalRefVariable(ReadGraph graph, Variable variable) throws DatabaseException {
498 this.uri = variable.getURI(graph);
502 public void listen(Object context, ExternalRefListener listener) {
503 Simantics.getSession().asyncRequest(new UnaryRead<String, Variant>(uri) {
506 public Variant perform(ReadGraph graph) throws DatabaseException {
507 Variable variable = Variables.getVariable(graph, parameter);
508 return variable.getVariantValue(graph);
511 }, new Listener<Variant>() {
514 public void execute(Variant result) {
515 listener.newValue(result);
519 public void exception(Throwable t) {
520 LOGGER.error("Error while evaluating variable value", t);
524 public boolean isDisposed() {
525 return listener.isDisposed();
533 public static Variant extRefActiveVariable(ReadGraph graph, Variable var) throws DatabaseException {
534 return new Variant(Bindings.VOID, new ExternalRefActiveVariable(graph, var));
537 static class ExternalRefActiveVariable implements ExternalRef {
539 final private String uri;
541 public ExternalRefActiveVariable(ReadGraph graph, Variable variable) throws DatabaseException {
542 this.uri = variable.getURI(graph);
546 public void listen(Object context, ExternalRefListener listener) {
547 Simantics.getSession().asyncRequest(new BinaryRead<String, String, Variant>((String)context, uri) {
550 public Variant perform(ReadGraph graph) throws DatabaseException {
551 Variable contextVariable = Variables.getVariable(graph, parameter);
552 Variable configVariable = Variables.getVariable(graph, parameter2);
553 Variable activeVariable = Variables.switchPossibleContext(graph, configVariable, contextVariable.getRepresents(graph));
554 if(activeVariable == null) return Variant.ofInstance("Could not resolve " + configVariable.getURI(graph) + " for " + contextVariable.getURI(graph));
555 return activeVariable.getVariantValue(graph);
557 }, new Listener<Variant>() {
560 public void execute(Variant result) {
561 listener.newValue(result);
565 public void exception(Throwable t) {
566 LOGGER.error("Error while evaluating variable value, context = " + context + " uri=" + uri, t);
570 public boolean isDisposed() {
571 return listener.isDisposed();
579 public static CellEditor cellEditor(ReadGraph graph, Resource sheet) throws DatabaseException {
580 SpreadsheetResource SHEET = SpreadsheetResource.getInstance(graph);
581 Variable sheetVariable = Variables.getVariable(graph, sheet);
582 return sheetVariable.getPropertyValue(graph, SHEET.cellEditor);
585 public static final String SPREADSHEET_TRANSACTION = "spreadsheetTransaction";
587 @SuppressWarnings({ "rawtypes", "unchecked" })
588 public static Object syncExec(CellEditor editor, OperationMode mode, Function fun) throws InterruptedException {
590 Transaction tr = editor.startTransaction(mode);
592 SCLContext context = SCLContext.getCurrent();
593 Transaction oldTransaction = (Transaction)context.put(SPREADSHEET_TRANSACTION, tr);
595 Object result = null;
599 result = fun.apply(Tuple0.INSTANCE);
605 context.put(SPREADSHEET_TRANSACTION, oldTransaction);
613 public static int cellColumn(ReadGraph graph, Variable cell) {
614 if(cell instanceof StandardGraphChildVariable) {
615 StandardGraphChildVariable sgcv = (StandardGraphChildVariable)cell;
616 SpreadsheetCell sc = (SpreadsheetCell)sgcv.node.node;
617 return sc.getColumn();
619 throw new IllegalStateException("Expected StandardGraphChildVariable, got " + cell.getClass().getName());