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.Variable;
34 import org.simantics.db.layer0.variable.Variables;
35 import org.simantics.db.procedure.Listener;
36 import org.simantics.db.service.ClusteringSupport;
37 import org.simantics.layer0.Layer0;
38 import org.simantics.scl.runtime.tuple.Tuple2;
39 import org.simantics.simulator.toolkit.StandardRealm;
40 import org.simantics.spreadsheet.ExternalRef;
41 import org.simantics.spreadsheet.Range;
42 import org.simantics.spreadsheet.Spreadsheets;
43 import org.simantics.spreadsheet.graph.synchronization.SpreadsheetSynchronizationEventHandler;
44 import org.simantics.spreadsheet.resource.SpreadsheetResource;
45 import org.simantics.spreadsheet.solver.SheetNode;
46 import org.simantics.spreadsheet.solver.SpreadsheetBook;
47 import org.simantics.spreadsheet.solver.SpreadsheetEngine;
48 import org.simantics.spreadsheet.solver.SpreadsheetLine;
49 import org.simantics.spreadsheet.solver.SpreadsheetStyle;
50 import org.simantics.spreadsheet.util.SpreadsheetUtils;
51 import org.simantics.structural.synchronization.client.Synchronizer;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
55 import gnu.trove.iterator.TObjectIntIterator;
56 import gnu.trove.map.hash.TObjectIntHashMap;
58 public class SpreadsheetGraphUtils {
60 private static final Logger LOGGER = LoggerFactory.getLogger(SpreadsheetGraphUtils.class);
62 public static File extractInitialCondition(ReadGraph graph, Resource ic) throws DatabaseException, IOException {
64 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
66 File temp = Simantics.getTempfile("excel","ic");
68 LiteralFileUtil.copyRandomAccessBinaryToFile(graph, ic, SR.InitialCondition_bytes, temp);
69 if (temp.length() == 0)
70 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.");
76 public static RandomAccessBinary getOrCreateRandomAccessBinary(WriteGraph graph, Resource initialCondition) throws DatabaseException, IOException {
78 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
80 // We put snapshot literals in their own clusters for now just to be safe
81 Resource literal = graph.getPossibleObject(initialCondition, SR.InitialCondition_bytes);
82 if (literal != null) {
83 RandomAccessBinary rab = graph.getRandomAccessBinary(literal);
85 rab.removeBytes(rab.length(), RandomAccessBinary.ByteSide.Right);
88 Layer0 L0 = Layer0.getInstance(graph);
89 ClusteringSupport cs = graph.getService(ClusteringSupport.class);
90 literal = graph.newResource(cs.createCluster());
91 graph.claim(literal, L0.InstanceOf, null, L0.ByteArray);
92 graph.claim(initialCondition, SR.InitialCondition_bytes, SR.InitialCondition_bytes_Inverse, literal);
93 return graph.createRandomAccessBinary(literal, Bindings.BYTE_ARRAY.type(), null);
97 public static Resource saveInitialCondition(WriteGraph graph, Variable run, Resource container, String name) throws DatabaseException {
99 String sessionName = run.getParent(graph).getURI(graph);
101 Resource bookResource = run.getRepresents(graph);
103 StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
104 SpreadsheetBook book = realm.getEngine();
108 File temp = Simantics.getTempfile("excel", "ic");
109 System.err.println("Saving initial condition to " + temp.getAbsolutePath());
111 FileOutputStream fileOut = new FileOutputStream(temp);
112 ObjectOutputStream out = new ObjectOutputStream(fileOut);
113 out.writeObject(book);
117 Layer0 L0 = Layer0.getInstance(graph);
118 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
119 Resource ic = graph.newResource();
120 graph.claim(ic, L0.InstanceOf, SR.InitialCondition);
121 graph.addLiteral(ic, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);
123 RandomAccessBinary rab = getOrCreateRandomAccessBinary(graph, ic);
124 LiteralFileUtil.copyRandomAccessBinaryFromFile(temp, rab);
126 graph.claim(container, L0.ConsistsOf, L0.PartOf, ic);
128 graph.deny(bookResource, SR.HasInitialCondition);
129 graph.claim(bookResource, SR.HasInitialCondition, ic);
130 graph.claim(ic, SR.InitialCondition_ConditionOf, bookResource);
132 setDefaultInitialConditionForBook(graph, bookResource, ic);
136 } catch (IOException e) {
138 throw new DatabaseException(e);
143 public static void setDefaultInitialConditionForBook(WriteGraph graph, Resource book, Resource ic) throws ServiceException {
144 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
145 graph.deny(book, SR.Book_HasDefaultInitialCondition);
146 graph.claim(ic, SR.InitialCondition_DefaultConditionOf, book);
149 public static void evaluateAll(ReadGraph graph, Variable run) throws DatabaseException {
151 String sessionName = run.getParent(graph).getURI(graph);
152 StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
153 SpreadsheetBook book = realm.getEngine();
154 book.accept(new EvaluateAll(book));
158 public static void invalidateAll(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 InvalidateAll());
164 realm.getNodeManager().refreshVariables();
168 public static boolean fullSynchronization(ReadGraph graph, Variable run) throws DatabaseException {
169 return partialSynchronization(graph, run, null);
172 public static boolean partialSynchronization(ReadGraph graph, Variable run, TObjectIntHashMap<Variable> changeFlags) throws DatabaseException {
174 Synchronizer synchronizer = new Synchronizer(graph);
175 String sessionName = run.getParent(graph).getURI(graph);
177 Resource bookResource = run.getRepresents(graph);
178 Variable configuration = Variables.getVariable(graph, bookResource);
180 StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
181 SpreadsheetBook book = realm.getEngine();
183 SpreadsheetSynchronizationEventHandler handler = new SpreadsheetSynchronizationEventHandler(graph, book);
185 // System.err.println("sessionName : " + sessionName);
186 // System.err.println("bookResource : " + graph.getURI(bookResource));
187 // System.err.println("configuration : " + configuration.getURI(graph));
188 // System.err.println("realm : " + realm);
189 // System.err.println("book : " + book);
191 if (changeFlags == null) {
192 synchronizer.fullSynchronization(configuration, handler);
195 TObjectIntIterator<Variable> iter = changeFlags.iterator();
197 Variable row = iter.key();
199 Variable rowParent = row.getParent(graph);
200 while (!rowParent.equals(configuration)) {
201 changeFlags.put(rowParent, 1);
202 rowParent = rowParent.getParent(graph);
205 changeFlags.put(configuration, 1);
207 synchronizer.partialSynchronization(configuration, handler, changeFlags);
210 // book.accept(new InvalidateAll());
211 // realm.getNodeManager().refreshVariables();
212 // mapping.currentRevision = synchronizer.getHeadRevisionId();
213 // mapping.setTrustUids(true);
215 // QueryControl qc = g.getService(QueryControl.class);
217 // TimeLogger.log("Finished full synchronization");
218 realm.getNodeManager().fireNodeListeners();
219 return handler.getDidChanges();
223 public static Variable findCell(ReadGraph graph, Variable run, String reference) throws DatabaseException {
225 int pos = reference.indexOf("!");
226 String sheetName = reference.substring(0, pos);
227 String cellName = reference.substring(pos+1);
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;
235 Range r = Spreadsheets.decodeCellAbsolute(cellName);
236 SpreadsheetLine line = engine.getLine(r.startRow);
237 if(line == null) return null;
239 String path = line.getPath();
240 if(path == null) return null;
242 Variable lineVariable = run.browse(graph, path);
243 if(lineVariable==null) return null;
245 return lineVariable.getChild(graph, cellName);
251 public static List<Variable> possibleConfigurationCellVariables(ReadGraph graph, Variable sheet, Range range) throws DatabaseException {
252 List<Variable> rowVariables = possibleConfigurationLineVariables(graph, sheet, range);
253 List<Variable> result = new ArrayList<>();
254 for (Variable variable : rowVariables) {
255 Collection<Variable> children = variable.getChildren(graph);
256 for (Variable child : children) {
257 if (variableInRange(graph, child, range)) {
265 public static Map<Integer, Resource> possibleConfigurationLineResources(ReadGraph graph, Variable sheet, Range range) throws DatabaseException {
266 Variable lines = sheet.getPossibleChild(graph, "Lines");
268 throw new DatabaseException("Invalid input variable " + sheet.getURI(graph));
269 Resource linesR = lines.getRepresents(graph);
270 BTree bt = new BTree(graph, linesR);
271 List<Tuple2> tuples = bt.searchRangeBTree(graph, Variant.ofInstance(range.startRow), Variant.ofInstance(range.endRow));
272 Map<Integer, Resource> result = new HashMap<>(tuples.size());
273 for (Tuple2 tuple : tuples) {
274 Integer lineNumber = (Integer)((Variant)tuple.c0).getValue();
275 Resource resource = (Resource)tuple.c1;
276 result.put(lineNumber, resource);
281 public static List<Variable> possibleConfigurationLineVariables(ReadGraph graph, Variable sheet, Range range) throws DatabaseException {
282 Map<Integer, Resource> rows = possibleConfigurationLineResources(graph, sheet, range);
283 List<Variable> result = new ArrayList<>(rows.size());
284 for (Resource row: rows.values()) {
285 Variable lineVar = Variables.getPossibleVariable(graph, row);
292 public static List<Variable> possibleRunLineVariables(ReadGraph graph, Variable sheetRun, Range range) throws DatabaseException {
294 Variable run = sheetRun.getParent(graph);
296 String sheetName = sheetRun.getName(graph);
297 String sessionName = run.getParent(graph).getURI(graph);
299 StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
300 SpreadsheetBook book = realm.getEngine();
302 SpreadsheetEngine engine = book.getEngine(sheetName);
303 if(engine == null) return null;
305 List<Variable> result = new ArrayList<>();
307 int end = range.endRow < engine.lines.getMaxRow() ? range.endRow : engine.lines.getMaxRow();
308 for (int i = range.startRow; i <= end; i++) {
309 SpreadsheetLine line = engine.getLine(i);
313 String path = line.getPath();
314 path = line.getPath();
318 Variable lineVariable = run.browse(graph, path);
319 if(lineVariable==null)
321 result.add(lineVariable);
327 public static List<Variable> possibleRunCellVariables(ReadGraph graph, Variable sheetRun, Range range) throws DatabaseException {
328 List<Variable> runLineVariable = possibleRunLineVariables(graph, sheetRun, range);
329 List<Variable> result = new ArrayList<>();
330 for (Variable variable : runLineVariable) {
331 // System.out.println("line: " + variable.getURI(graph));
332 for (Variable child : variable.getChildren(graph)) {
333 // System.out.print("cell : " + child.getURI(graph));
334 if (variableInRange(graph, child, range)) {
342 private static boolean variableInRange(ReadGraph graph, Variable child, Range range) throws DatabaseException {
343 String name = child.getName(graph);
344 Range childRange = Spreadsheets.decodeCellAbsolute(name);
345 // System.out.print(" and range " + childRange);
346 if (childRange != null && range.contains(childRange)) {
347 // System.out.println(" => range.contains(childRange) = true");
350 // System.out.println();
354 public static Map<Integer, Resource> createConfigurationLineResources(WriteGraph graph, Variable sheet, Range range) throws DatabaseException {
355 Layer0 L0 = Layer0.getInstance(graph);
356 SpreadsheetResource SHEET = SpreadsheetResource.getInstance(graph);
358 Variable lines = sheet.getPossibleChild(graph, "Lines");
360 throw new DatabaseException("Invalid input variable " + sheet.getURI(graph));
361 Resource linesR = lines.getRepresents(graph);
362 BTree bt = new BTree(graph, linesR);
364 Map<Integer, Resource> result = new HashMap<>();
365 for (int lineNumber = range.startRow; lineNumber <= range.endRow; lineNumber++) {
366 Resource line = graph.newResource();
367 graph.claim(line, L0.InstanceOf, null, SHEET.Line);
368 graph.claimLiteral(line, L0.HasName, L0.NameOf, L0.String, "Row" + lineNumber, Bindings.STRING);
369 bt.insertBTree(graph, Variant.ofInstance(lineNumber), line);
370 result.put(lineNumber, line);
375 public static List<Variable> getOrCreateConfigurationCellVariables(WriteGraph graph, Variable sheet, Range range) throws DatabaseException {
377 List<Variable> rows = possibleConfigurationLineVariables(graph, sheet, range);
378 if (rows.isEmpty()) {
379 createConfigurationLineResources(graph, sheet, range);
380 rows = possibleConfigurationLineVariables(graph, sheet, range);
383 List<Variable> cells = possibleConfigurationCellVariables(graph, sheet, range);
384 if (cells.isEmpty()) {
385 Iterator<Variable> rowIterator = rows.iterator();
386 for (int rowNumber = range.startRow; rowNumber <= range.endRow; rowNumber++) {
387 Variable row = rowIterator.next();
388 for (int colNumber = range.startColumn; colNumber <= range.endColumn; colNumber++) {
389 String location = Spreadsheets.cellName(rowNumber, colNumber);
390 defaultCreateCell(graph, row, location, new Variant(Bindings.STRING, ""));
395 cells = possibleConfigurationCellVariables(graph, sheet, range);
397 throw new DatabaseException("Unexpected problem while creating spreadsheet cell at '" + range + "'");
402 private static void defaultCreateCell(WriteGraph graph, Variable parent, String location, Variant value) throws DatabaseException {
404 Layer0 L0 = Layer0.getInstance(graph);
405 SpreadsheetResource SHEET = SpreadsheetResource.getInstance(graph);
406 Resource container = parent.getRepresents(graph);
408 Resource cell = graph.newResource();
409 graph.claim(cell, L0.InstanceOf, null, SHEET.TextCell);
410 graph.addLiteral(cell, L0.HasName, L0.NameOf, L0.String, location, Bindings.STRING);
411 graph.addLiteral(cell, SHEET.Cell_content, SHEET.Cell_content_Inverse, L0.Variant, value, Bindings.VARIANT);
412 graph.claim(cell, L0.PartOf, container);
414 Resource book = Variables.getContext(graph, parent).getRepresents(graph);
417 Collection<Resource> objects = graph.sync(new ObjectsWithType(book, L0.ConsistsOf, SHEET.Style));
419 int styleId = SpreadsheetStyle.empty().getStyleId();
420 Resource style = null;
421 for (Resource possibleStyle : objects) {
422 int possibleStyleId = graph.getRelatedValue2(possibleStyle, SHEET.Style_id, Bindings.INTEGER);
423 if (possibleStyleId == styleId) {
424 style = possibleStyle;
430 style = graph.newResource();
431 graph.claim(style, L0.InstanceOf, null, SHEET.Style);
432 graph.claim(style, L0.PartOf, book);
434 int id = objects.size();
435 graph.claimLiteral(style, L0.HasName, "Style_" + id);
436 graph.claimLiteral(style, SHEET.Style_id, styleId, Bindings.INTEGER);
438 graph.claim(cell, SHEET.Cell_HasStyle, style);
439 Layer0Utils.addCommentMetadata(graph, "Created cell on location " + location + " with value " + value.toString());
442 public static Resource createStyle(WriteGraph graph, Resource book, SpreadsheetStyle sstyle) throws DatabaseException {
443 Layer0 L0 = Layer0.getInstance(graph);
444 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
445 Resource style = graph.newResource();
446 graph.claim(style, L0.InstanceOf, null, SR.Style);
447 graph.claim(style, L0.PartOf, book);
449 int styleId = sstyle.getStyleId();
450 String styleName = sstyle.name;
452 graph.claimLiteral(style, L0.HasName, styleName);
453 //System.err.println("CREATING STYLE " + styleName + " WITH ID: " + styleId);
454 graph.claimLiteral(style, SR.Style_id, styleId, Bindings.INTEGER);
456 DatatypeResource DATATYPES = DatatypeResource.getInstance(graph);
457 if (sstyle.foreground != null)
458 graph.claimLiteral(style, SR.Cell_foreground, DATATYPES.RGB_Integer, sstyle.foreground, RGB.Integer.BINDING);
459 if (sstyle.background != null)
460 graph.claimLiteral(style, SR.Cell_background, DATATYPES.RGB_Integer, sstyle.background, RGB.Integer.BINDING);
461 if (sstyle.align != -1)
462 graph.claimLiteral(style, SR.Cell_align, sstyle.align, Bindings.INTEGER);
463 if (sstyle.font != null)
464 graph.claimLiteral(style, SR.Cell_font, DATATYPES.Font, sstyle.font, Font.BINDING);
465 if (sstyle.border != -1)
466 graph.claimLiteral(style, SR.Cell_border, sstyle.border);
467 if (sstyle.formatString != null && !sstyle.formatString.isEmpty())
468 graph.claimLiteral(style, SR.Cell_formatString, sstyle.formatString, Bindings.STRING);
469 if (sstyle.formatIndex != -1)
470 graph.claimLiteral(style, SR.Cell_formatIndex, sstyle.formatIndex, Bindings.INTEGER);
475 public static Resource createBook(WriteGraph graph, Resource parent, String name) throws DatabaseException {
476 Layer0 L0 = Layer0.getInstance(graph);
477 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
478 Resource book = graph.newResource();
479 graph.claim(book, L0.InstanceOf, SR.Book);
480 graph.claimLiteral(book, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);
481 graph.claim(parent, L0.ConsistsOf, book);
486 public static Variable constructAndInitializeRunVariable(WriteGraph graph, Resource root) throws DatabaseException {
487 Variable run = SpreadsheetUtils.getBookVariable(graph, root);
488 SpreadsheetGraphUtils.fullSynchronization(graph, run);
489 SpreadsheetGraphUtils.evaluateAll(graph, run);
490 SpreadsheetGraphUtils.saveInitialCondition(graph, run, root, "Initial");
494 public static Variant extRefVariable(ReadGraph graph, Variable var) throws DatabaseException {
495 System.err.println("extRefVariable " + var.getURI(graph));
496 return new Variant(Bindings.VOID, new ExternalRefVariable(graph, var));
499 static class ExternalRefVariable implements ExternalRef {
501 final private String uri;
503 public ExternalRefVariable(ReadGraph graph, Variable variable) throws DatabaseException {
504 this.uri = variable.getURI(graph);
508 public void listen(Object context, ExternalRefListener listener) {
509 System.err.println("listen " + listener);
510 Simantics.getSession().asyncRequest(new UnaryRead<String, Variant>(uri) {
513 public Variant perform(ReadGraph graph) throws DatabaseException {
514 Variable variable = Variables.getVariable(graph, parameter);
515 System.err.println("ExternalRef value for " + variable.getURI(graph));
516 return variable.getVariantValue(graph);
519 }, new Listener<Variant>() {
522 public void execute(Variant result) {
523 System.err.println("execute " + result);
524 listener.newValue(result);
528 public void exception(Throwable t) {
529 LOGGER.error("Error while evaluating variable value", t);
533 public boolean isDisposed() {
534 return listener.isDisposed();
542 public static Variant extRefActiveVariable(ReadGraph graph, Variable var) throws DatabaseException {
543 System.err.println("extRefActiveVariable " + var.getURI(graph));
544 return new Variant(Bindings.VOID, new ExternalRefActiveVariable(graph, var));
547 static class ExternalRefActiveVariable implements ExternalRef {
549 final private String uri;
551 public ExternalRefActiveVariable(ReadGraph graph, Variable variable) throws DatabaseException {
552 this.uri = variable.getURI(graph);
556 public void listen(Object context, ExternalRefListener listener) {
557 System.err.println("listen " + context + " " + listener);
558 Simantics.getSession().asyncRequest(new BinaryRead<String, String, Variant>((String)context, uri) {
561 public Variant perform(ReadGraph graph) throws DatabaseException {
562 Variable contextVariable = Variables.getVariable(graph, parameter);
563 System.err.println("extref1 " + contextVariable.getURI(graph));
564 Variable configVariable = Variables.getVariable(graph, parameter2);
565 System.err.println("extref2 " + configVariable.getURI(graph));
566 Variable activeVariable = Variables.switchPossibleContext(graph, configVariable, contextVariable.getRepresents(graph));
567 System.err.println("ExternalRef value for " + activeVariable.getURI(graph));
568 return activeVariable.getVariantValue(graph);
570 }, new Listener<Variant>() {
573 public void execute(Variant result) {
574 System.err.println("execute " + result);
575 listener.newValue(result);
579 public void exception(Throwable t) {
580 LOGGER.error("Error while evaluating variable value", t);
584 public boolean isDisposed() {
585 return listener.isDisposed();