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 if (changeFlags == null) {
186 synchronizer.fullSynchronization(configuration, handler);
189 TObjectIntIterator<Variable> iter = changeFlags.iterator();
191 Variable row = iter.key();
193 Variable rowParent = row.getParent(graph);
194 while (!rowParent.equals(configuration)) {
195 changeFlags.put(rowParent, 1);
196 rowParent = rowParent.getParent(graph);
199 changeFlags.put(configuration, 1);
201 synchronizer.partialSynchronization(configuration, handler, changeFlags);
204 realm.getNodeManager().fireNodeListeners();
205 return handler.getDidChanges();
209 public static Variable findCell(ReadGraph graph, Variable run, String reference) throws DatabaseException {
211 int pos = reference.indexOf("!");
212 String sheetName = reference.substring(0, pos);
213 String cellName = reference.substring(pos+1);
215 String sessionName = run.getParent(graph).getURI(graph);
216 StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
217 SpreadsheetBook book = realm.getEngine();
218 SpreadsheetEngine engine = book.getEngine(sheetName);
219 if(engine == null) return null;
221 Range r = Spreadsheets.decodeCellAbsolute(cellName);
222 SpreadsheetLine line = engine.getLine(r.startRow);
223 if(line == null) return null;
225 String path = line.getPath();
226 if(path == null) return null;
228 Variable lineVariable = run.browse(graph, path);
229 if(lineVariable==null) return null;
231 return lineVariable.getChild(graph, cellName);
237 public static List<Variable> possibleConfigurationCellVariables(ReadGraph graph, Variable sheet, Range range) throws DatabaseException {
238 List<Variable> rowVariables = possibleConfigurationLineVariables(graph, sheet, range);
239 List<Variable> result = new ArrayList<>();
240 for (Variable variable : rowVariables) {
241 Collection<Variable> children = variable.getChildren(graph);
242 for (Variable child : children) {
243 if (variableInRange(graph, child, range)) {
251 public static Map<Integer, Resource> possibleConfigurationLineResources(ReadGraph graph, Variable sheet, Range range) throws DatabaseException {
252 Variable lines = sheet.getPossibleChild(graph, "Lines");
254 throw new DatabaseException("Invalid input variable " + sheet.getURI(graph));
255 Resource linesR = lines.getRepresents(graph);
256 BTree bt = new BTree(graph, linesR);
257 List<Tuple2> tuples = bt.searchRangeBTree(graph, Variant.ofInstance(range.startRow), Variant.ofInstance(range.endRow));
258 Map<Integer, Resource> result = new HashMap<>(tuples.size());
259 for (Tuple2 tuple : tuples) {
260 Integer lineNumber = (Integer)((Variant)tuple.c0).getValue();
261 Resource resource = (Resource)tuple.c1;
262 result.put(lineNumber, resource);
267 public static List<Variable> possibleConfigurationLineVariables(ReadGraph graph, Variable sheet, Range range) throws DatabaseException {
268 Map<Integer, Resource> rows = possibleConfigurationLineResources(graph, sheet, range);
269 List<Variable> result = new ArrayList<>(rows.size());
270 for (Resource row: rows.values()) {
271 Variable lineVar = Variables.getPossibleVariable(graph, row);
278 public static List<Variable> possibleRunLineVariables(ReadGraph graph, Variable sheetRun, Range range) throws DatabaseException {
280 Variable run = sheetRun.getParent(graph);
282 String sheetName = sheetRun.getName(graph);
283 String sessionName = run.getParent(graph).getURI(graph);
285 StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
286 SpreadsheetBook book = realm.getEngine();
288 SpreadsheetEngine engine = book.getEngine(sheetName);
289 if(engine == null) return null;
291 List<Variable> result = new ArrayList<>();
293 int end = range.endRow < engine.lines.getMaxRow() ? range.endRow : engine.lines.getMaxRow();
294 for (int i = range.startRow; i <= end; i++) {
295 SpreadsheetLine line = engine.getLine(i);
299 String path = line.getPath();
300 path = line.getPath();
304 Variable lineVariable = run.browse(graph, path);
305 if(lineVariable==null)
307 result.add(lineVariable);
313 public static List<Variable> possibleRunCellVariables(ReadGraph graph, Variable sheetRun, Range range) throws DatabaseException {
314 List<Variable> runLineVariable = possibleRunLineVariables(graph, sheetRun, range);
315 List<Variable> result = new ArrayList<>();
316 for (Variable variable : runLineVariable) {
317 // System.out.println("line: " + variable.getURI(graph));
318 for (Variable child : variable.getChildren(graph)) {
319 // System.out.print("cell : " + child.getURI(graph));
320 if (variableInRange(graph, child, range)) {
328 private static boolean variableInRange(ReadGraph graph, Variable child, Range range) throws DatabaseException {
329 String name = child.getName(graph);
330 Range childRange = Spreadsheets.decodeCellAbsolute(name);
331 // System.out.print(" and range " + childRange);
332 if (childRange != null && range.contains(childRange)) {
333 // System.out.println(" => range.contains(childRange) = true");
336 // System.out.println();
340 public static Map<Integer, Resource> createConfigurationLineResources(WriteGraph graph, Variable sheet, Range range) throws DatabaseException {
341 Layer0 L0 = Layer0.getInstance(graph);
342 SpreadsheetResource SHEET = SpreadsheetResource.getInstance(graph);
344 Variable lines = sheet.getPossibleChild(graph, "Lines");
346 throw new DatabaseException("Invalid input variable " + sheet.getURI(graph));
347 Resource linesR = lines.getRepresents(graph);
348 BTree bt = new BTree(graph, linesR);
350 Map<Integer, Resource> result = new HashMap<>();
351 for (int lineNumber = range.startRow; lineNumber <= range.endRow; lineNumber++) {
352 Resource line = graph.newResource();
353 graph.claim(line, L0.InstanceOf, null, SHEET.Line);
354 graph.claimLiteral(line, L0.HasName, L0.NameOf, L0.String, "Row" + lineNumber, Bindings.STRING);
355 bt.insertBTree(graph, Variant.ofInstance(lineNumber), line);
356 result.put(lineNumber, line);
361 public static List<Variable> getOrCreateConfigurationCellVariables(WriteGraph graph, Variable sheet, Range range) throws DatabaseException {
363 List<Variable> rows = possibleConfigurationLineVariables(graph, sheet, range);
364 if (rows.isEmpty()) {
365 createConfigurationLineResources(graph, sheet, range);
366 rows = possibleConfigurationLineVariables(graph, sheet, range);
369 List<Variable> cells = possibleConfigurationCellVariables(graph, sheet, range);
370 if (cells.isEmpty()) {
371 Iterator<Variable> rowIterator = rows.iterator();
372 for (int rowNumber = range.startRow; rowNumber <= range.endRow; rowNumber++) {
373 Variable row = rowIterator.next();
374 for (int colNumber = range.startColumn; colNumber <= range.endColumn; colNumber++) {
375 String location = Spreadsheets.cellName(rowNumber, colNumber);
376 defaultCreateCell(graph, row, location, new Variant(Bindings.STRING, ""));
381 cells = possibleConfigurationCellVariables(graph, sheet, range);
383 throw new DatabaseException("Unexpected problem while creating spreadsheet cell at '" + range + "'");
388 private static void defaultCreateCell(WriteGraph graph, Variable parent, String location, Variant value) throws DatabaseException {
390 Layer0 L0 = Layer0.getInstance(graph);
391 SpreadsheetResource SHEET = SpreadsheetResource.getInstance(graph);
392 Resource container = parent.getRepresents(graph);
394 Resource cell = graph.newResource();
395 graph.claim(cell, L0.InstanceOf, null, SHEET.TextCell);
396 graph.addLiteral(cell, L0.HasName, L0.NameOf, L0.String, location, Bindings.STRING);
397 graph.addLiteral(cell, SHEET.Cell_content, SHEET.Cell_content_Inverse, L0.Variant, value, Bindings.VARIANT);
398 graph.claim(cell, L0.PartOf, container);
400 Resource book = Variables.getContext(graph, parent).getRepresents(graph);
403 Collection<Resource> objects = graph.sync(new ObjectsWithType(book, L0.ConsistsOf, SHEET.Style));
405 int styleId = SpreadsheetStyle.empty().getStyleId();
406 Resource style = null;
407 for (Resource possibleStyle : objects) {
408 int possibleStyleId = graph.getRelatedValue2(possibleStyle, SHEET.Style_id, Bindings.INTEGER);
409 if (possibleStyleId == styleId) {
410 style = possibleStyle;
416 style = graph.newResource();
417 graph.claim(style, L0.InstanceOf, null, SHEET.Style);
418 graph.claim(style, L0.PartOf, book);
420 int id = objects.size();
421 graph.claimLiteral(style, L0.HasName, "Style_" + id);
422 graph.claimLiteral(style, SHEET.Style_id, styleId, Bindings.INTEGER);
424 graph.claim(cell, SHEET.Cell_HasStyle, style);
425 Layer0Utils.addCommentMetadata(graph, "Created cell on location " + location + " with value " + value.toString());
428 public static Resource createStyle(WriteGraph graph, Resource book, SpreadsheetStyle sstyle) throws DatabaseException {
429 Layer0 L0 = Layer0.getInstance(graph);
430 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
431 Resource style = graph.newResource();
432 graph.claim(style, L0.InstanceOf, null, SR.Style);
433 graph.claim(style, L0.PartOf, book);
435 int styleId = sstyle.getStyleId();
436 String styleName = sstyle.name;
438 graph.claimLiteral(style, L0.HasName, styleName);
439 //System.err.println("CREATING STYLE " + styleName + " WITH ID: " + styleId);
440 graph.claimLiteral(style, SR.Style_id, styleId, Bindings.INTEGER);
442 DatatypeResource DATATYPES = DatatypeResource.getInstance(graph);
443 if (sstyle.foreground != null)
444 graph.claimLiteral(style, SR.Cell_foreground, DATATYPES.RGB_Integer, sstyle.foreground, RGB.Integer.BINDING);
445 if (sstyle.background != null)
446 graph.claimLiteral(style, SR.Cell_background, DATATYPES.RGB_Integer, sstyle.background, RGB.Integer.BINDING);
447 if (sstyle.align != -1)
448 graph.claimLiteral(style, SR.Cell_align, sstyle.align, Bindings.INTEGER);
449 if (sstyle.font != null)
450 graph.claimLiteral(style, SR.Cell_font, DATATYPES.Font, sstyle.font, Font.BINDING);
451 if (sstyle.border != -1)
452 graph.claimLiteral(style, SR.Cell_border, sstyle.border);
453 if (sstyle.formatString != null && !sstyle.formatString.isEmpty())
454 graph.claimLiteral(style, SR.Cell_formatString, sstyle.formatString, Bindings.STRING);
455 if (sstyle.formatIndex != -1)
456 graph.claimLiteral(style, SR.Cell_formatIndex, sstyle.formatIndex, Bindings.INTEGER);
461 public static Resource createBook(WriteGraph graph, Resource parent, String name) throws DatabaseException {
462 Layer0 L0 = Layer0.getInstance(graph);
463 SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
464 Resource book = graph.newResource();
465 graph.claim(book, L0.InstanceOf, SR.Book);
466 graph.claimLiteral(book, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);
467 graph.claim(parent, L0.ConsistsOf, book);
472 public static Variable constructAndInitializeRunVariable(WriteGraph graph, Resource root) throws DatabaseException {
473 Variable run = SpreadsheetUtils.getBookVariable(graph, root);
474 SpreadsheetGraphUtils.fullSynchronization(graph, run);
475 SpreadsheetGraphUtils.evaluateAll(graph, run);
476 SpreadsheetGraphUtils.saveInitialCondition(graph, run, root, "Initial");
480 public static Variant extRefVariable(ReadGraph graph, Variable var) throws DatabaseException {
481 return new Variant(Bindings.VOID, new ExternalRefVariable(graph, var));
484 static class ExternalRefVariable implements ExternalRef {
486 final private String uri;
488 public ExternalRefVariable(ReadGraph graph, Variable variable) throws DatabaseException {
489 this.uri = variable.getURI(graph);
493 public void listen(Object context, ExternalRefListener listener) {
494 Simantics.getSession().asyncRequest(new UnaryRead<String, Variant>(uri) {
497 public Variant perform(ReadGraph graph) throws DatabaseException {
498 Variable variable = Variables.getVariable(graph, parameter);
499 return variable.getVariantValue(graph);
502 }, new Listener<Variant>() {
505 public void execute(Variant result) {
506 listener.newValue(result);
510 public void exception(Throwable t) {
511 LOGGER.error("Error while evaluating variable value", t);
515 public boolean isDisposed() {
516 return listener.isDisposed();
524 public static Variant extRefActiveVariable(ReadGraph graph, Variable var) throws DatabaseException {
525 return new Variant(Bindings.VOID, new ExternalRefActiveVariable(graph, var));
528 static class ExternalRefActiveVariable implements ExternalRef {
530 final private String uri;
532 public ExternalRefActiveVariable(ReadGraph graph, Variable variable) throws DatabaseException {
533 this.uri = variable.getURI(graph);
537 public void listen(Object context, ExternalRefListener listener) {
538 Simantics.getSession().asyncRequest(new BinaryRead<String, String, Variant>((String)context, uri) {
541 public Variant perform(ReadGraph graph) throws DatabaseException {
542 Variable contextVariable = Variables.getVariable(graph, parameter);
543 Variable configVariable = Variables.getVariable(graph, parameter2);
544 Variable activeVariable = Variables.switchPossibleContext(graph, configVariable, contextVariable.getRepresents(graph));
545 return activeVariable.getVariantValue(graph);
547 }, new Listener<Variant>() {
550 public void execute(Variant result) {
551 listener.newValue(result);
555 public void exception(Throwable t) {
556 LOGGER.error("Error while evaluating variable value", t);
560 public boolean isDisposed() {
561 return listener.isDisposed();