1 package org.simantics.spreadsheet.solver;
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.Collection;
7 import java.util.Collections;
8 import java.util.HashMap;
9 import java.util.HashSet;
10 import java.util.List;
12 import java.util.Optional;
15 import org.simantics.databoard.Bindings;
16 import org.simantics.databoard.binding.Binding;
17 import org.simantics.databoard.binding.mutable.Variant;
18 import org.simantics.simulator.toolkit.StandardNodeManagerSupport;
19 import org.simantics.simulator.variable.exceptions.NodeManagerException;
20 import org.simantics.spreadsheet.ExternalRef;
21 import org.simantics.spreadsheet.ExternalRef.ExternalRefListener;
22 import org.simantics.spreadsheet.SpreadsheetCellStyle;
23 import org.simantics.spreadsheet.SpreadsheetVisitor;
24 import org.simantics.spreadsheet.solver.formula.SpreadsheetEvaluationEnvironment;
25 import org.simantics.spreadsheet.synchronization.LineNodeUpdater;
26 import org.simantics.spreadsheet.synchronization.LineUpdater;
27 import org.simantics.spreadsheet.synchronization.NullUpdater;
28 import org.simantics.spreadsheet.synchronization.StyleUpdater;
29 import org.simantics.structural.synchronization.base.ModuleUpdaterBase;
30 import org.simantics.structural.synchronization.base.ModuleUpdaterFactoryBase;
31 import org.simantics.structural.synchronization.base.SolverNameUtil;
32 import org.simantics.structural.synchronization.utils.ComponentFactory;
33 import org.simantics.structural.synchronization.utils.MappingBase;
34 import org.simantics.structural.synchronization.utils.Solver;
36 import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
37 import it.unimi.dsi.fastutil.longs.AbstractLongList;
38 import it.unimi.dsi.fastutil.longs.AbstractLongSet;
39 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
40 import it.unimi.dsi.fastutil.longs.LongArraySet;
41 import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
43 @SuppressWarnings("rawtypes")
44 public class SpreadsheetBook implements StandardNodeManagerSupport<SheetNode>, SpreadsheetElement<SpreadsheetElement, SpreadsheetElement>, Serializable, SheetNode<SpreadsheetEngine, SheetNode>, Solver, SolverNameUtil, ComponentFactory<SheetLineComponent>, ModuleUpdaterFactoryBase<SheetLineComponent> {
46 private static final long serialVersionUID = 7417208688311691396L;
48 public Serializable NotAvailableError = new Serializable() {
49 private static final long serialVersionUID = 2535371785498129460L;
52 public Long2ObjectOpenHashMap<AbstractLongSet> referenceMap = new Long2ObjectOpenHashMap<>();
54 private Int2ObjectArrayMap<SpreadsheetStyle> styles = new Int2ObjectArrayMap<>();
55 public ArrayList<SpreadsheetEngine> sheets = new ArrayList<SpreadsheetEngine>();
57 private SpreadsheetMapping mapping;
59 private String context;
61 private int idCounter = 1;
63 private transient boolean disposed = false;
65 public SpreadsheetBook(String context) {
67 System.err.println("SpreadsheetBook, context is " + context);
68 mapping = new SpreadsheetMapping(new SheetLineComponent(""));
69 this.context = context;
72 public Map<Integer, SpreadsheetElement> children = new HashMap<>();
74 private boolean iterationEnabled;
76 public int getNewId(SpreadsheetElement element) {
77 int result = idCounter++;
78 children.put(result, element);
82 public long getEngineIndex(SpreadsheetEngine engine) {
83 for(int i=0;i<sheets.size();i++)
84 if(sheets.get(i) == engine) return i;
85 throw new IllegalStateException("Did not find sheet " + engine.getName());
88 public void registerReferences(long cell, AbstractLongList refs) {
89 for(int i=0;i<refs.size();i++) {
90 long key = refs.getLong(i);
91 AbstractLongSet set = referenceMap.get(key);
93 set = new LongArraySet();
94 referenceMap.put(key, set);
97 AbstractLongSet newSet = new LongLinkedOpenHashSet();
100 referenceMap.put(key, set);
107 public Binding getEngineBinding(SheetNode node) throws NodeManagerException {
108 Object value = getEngineValue(node);
109 if(value instanceof Variant) return Bindings.VARIANT;
110 if(value instanceof String) return Bindings.STRING;
111 if(value instanceof Boolean) return Bindings.BOOLEAN;
112 else return Bindings.getBindingUnchecked(value.getClass());
116 public Object getEngineValue(SheetNode node) {
117 if(node instanceof SpreadsheetCellContent) {
119 SpreadsheetCellContent scc = (SpreadsheetCellContent)node;
120 Object content = scc.cell.evaluate(SpreadsheetEvaluationEnvironment.getInstance(this));
121 if(content == null) return Variant.ofInstance("");
122 if(content instanceof Variant) return content;
123 else return Variant.ofInstance(content);
124 } catch (Throwable t) {
126 return Variant.ofInstance(t.toString());
128 } else if (node instanceof SpreadsheetCellContentExpression) {
129 SpreadsheetCellContentExpression scce = (SpreadsheetCellContentExpression)node;
130 if (scce.cell.content instanceof SpreadsheetFormula) {
131 SpreadsheetFormula formula = (SpreadsheetFormula)scce.cell.content;
132 return formula.expression;
133 } else if (scce.cell.content instanceof SpreadsheetSCLConstant) {
134 SpreadsheetSCLConstant sclConstant = (SpreadsheetSCLConstant) scce.cell.content;
135 return "=" + sclConstant.expression;
137 System.out.println("Broken SpreadsheetCellContentExpression possibly due to overwriting an existing expression with a constant or something else (current content is " + scce.cell.content + ")");
138 if (scce.cell.content instanceof Variant) {
139 return scce.cell.content;
141 return Variant.ofInstance(scce.cell.content);
144 } else if (node instanceof SpreadsheetTypeNode) {
145 SpreadsheetTypeNode stn = (SpreadsheetTypeNode)node;
147 } else if (node instanceof SpreadsheetCellStyle) {
148 int styleId = ((SpreadsheetCellStyle) node).cell.style;
149 SpreadsheetStyle style = getStyle(styleId);
151 style = SpreadsheetStyle.empty();
152 if (styleId != style.getStyleId())
153 new Exception("different style ids!" + styleId + " " + style.getStyleId()).printStackTrace();
157 } else if (node instanceof SpreadsheetCellEditable) {
158 boolean editable = ((SpreadsheetCellEditable) node).editable();
165 public void setEngineValue(SheetNode node, Object value) {
169 public String getName(SheetNode node) {
170 return node.getName();
173 @SuppressWarnings("unchecked")
175 public Map<String, SheetNode> getChildren(SheetNode node) {
176 return node.getChildren();
179 @SuppressWarnings("unchecked")
181 public Map<String, SheetNode> getProperties(SheetNode node) {
182 return node.getProperties();
186 public String getName() {
191 public Map<String, SpreadsheetEngine> getChildren() {
192 Map<String,SpreadsheetEngine> result = new HashMap<String,SpreadsheetEngine>();
193 for(SpreadsheetEngine engine : sheets)
194 result.put(engine.getName(), engine);
199 public Map<String, SheetNode> getProperties() {
200 return Collections.emptyMap();
203 public SpreadsheetCell get(String sheet, int row, int column) {
204 SpreadsheetEngine engine = getEngine(sheet);
205 if(engine == null) return null;
206 SpreadsheetLine line = engine.getLine(row);
207 if(line == null) return null;
208 if(line.cells.size() <= column) return null;
209 return line.cells.get(column);
212 public SpreadsheetCell get(SpreadsheetEngine engine, int row, int column) {
213 SpreadsheetLine line = engine.getLine(row);
214 if(line == null) return null;
215 if(line.cells.size() <= column) return null;
216 return line.cells.get(column);
219 public SpreadsheetEngine getEngine(String sheet) {
220 for(SpreadsheetEngine engine : sheets)
221 if(sheet.equals(engine.getName())) return engine;
226 public ModuleUpdaterBase<SheetLineComponent> createUpdater(String id) throws Exception {
227 if("http://www.simantics.org/Spreadsheet-1.2/Line".equals(id))
228 return new LineUpdater(id);
229 else if("http://www.simantics.org/Spreadsheet-1.2/LineNode".equals(id))
230 return new LineNodeUpdater(id);
231 else if ("http://www.simantics.org/Spreadsheet-1.2/Style".equals(id))
232 return new StyleUpdater(id);
233 else if("http://www.simantics.org/Spreadsheet-1.2/Lines".equals(id))
234 return new NullUpdater(id);
235 else if("http://www.simantics.org/Spreadsheet-1.2/Spreadsheet".equals(id))
236 return new NullUpdater(id);
237 else if("http://www.simantics.org/Spreadsheet-1.2/Book".equals(id))
238 return new NullUpdater(id);
240 throw new IllegalStateException("createUpdater " + id);
244 public SheetLineComponent create(String uid) {
245 return new SheetLineComponent(uid);
249 public String getFreshName(String parentName, String name) {
250 return parentName + "/" + name;
254 public String ensureNameIsVariationOf(String parentName, int id, String name) {
255 if (parentName.isEmpty())
257 return parentName + "/" + name;
260 final static int COMP_ROOT_POS = "COMP_ROOT/".length();
263 public int getId(String name) {
265 if("COMP_ROOT".equals(name)) return 1;
267 String path = name.substring(COMP_ROOT_POS);
268 String[] parts = path.split("/");
269 Object o = resolve(parts, 0);
270 if(o instanceof SpreadsheetLines) {
271 return ((SpreadsheetLines)o).getId();
272 } else if(o instanceof SpreadsheetLine) {
273 return ((SpreadsheetLine)o).getId();
274 } else if(o instanceof SpreadsheetEngine) {
275 return ((SpreadsheetEngine)o).getId();
276 } else if (o instanceof SpreadsheetStyle) {
277 return ((SpreadsheetStyle) o).getId();
280 throw new IllegalStateException("Resolved object for parts " + Arrays.toString(parts) + " is not the right type! It is " + o);
284 Object resolve(String[] parts, int index) {
285 String part = parts[index];
286 if (part.startsWith("Style")) {
287 for (SpreadsheetStyle style : styles.values()) {
288 if (style.name.equals(part)) {
293 SpreadsheetEngine engine = getEngine(part);
294 if(engine == null) return 0;
295 if(index == parts.length-1) return engine;
296 else return engine.resolve(parts, index+1);
300 public String getName(int id) {
301 if(id == -2) return "http://www.simantics.org/Spreadsheet-1.2/Book";
302 else if(id == -3) return "http://www.simantics.org/Spreadsheet-1.2/Spreadsheet";
303 else if(id == -4) return "http://www.simantics.org/Spreadsheet-1.2/Lines";
305 return "http://www.simantics.org/Spreadsheet-1.2/LineNode";
307 return "http://www.simantics.org/Spreadsheet-1.2/Line";
309 return "http://www.simantics.org/Spreadsheet-1.2/Style";
314 public int getModuleType(int id) {
315 Serializable s = children.get(id);
316 if(s instanceof SpreadsheetBook) return -2;
317 else if(s instanceof SpreadsheetEngine) return -3;
318 else if(s instanceof SpreadsheetLines) {
319 if("Lines".equals(((SpreadsheetLines) s).getName()))
324 else if(s instanceof SpreadsheetLine)
326 else if (s instanceof SpreadsheetStyle)
328 else throw new IllegalStateException();
331 @SuppressWarnings("unchecked")
333 public void remove(int id) {
335 SpreadsheetElement child = children.get(id);
336 Optional<SpreadsheetElement> parent = child.getParent();
338 if (parent.isPresent()) {
339 parent.get().remove(child);
345 public void addSubprocess(String name, String subprocessType) {
346 ensureSubprocess(name);
349 @SuppressWarnings("unchecked")
350 public <T> T ensureSubprocess(String name) {
351 String[] parts = name.split("/");
352 if(parts.length == 2) {
353 SpreadsheetEngine engine = getEngine(parts[1]);
355 engine = new SpreadsheetEngine(this, parts[1]);
359 } else if (parts.length > 2) {
360 SpreadsheetEngine engine = getEngine(parts[1]);
361 return (T)engine.ensureSubprocess(parts, 2);
363 throw new IllegalStateException();
368 public void includeSubprocess(String parentName, String subprocessName) {
372 @SuppressWarnings("unchecked")
374 public <T> T getConcreteSolver() {
379 public void accept(SpreadsheetVisitor v) {
383 private SpreadsheetCell cellByReferenceKey(long ref) {
384 long sheet = ref >> 40;
385 long row = (ref >> 20) & 0xFFFFF;
386 long col = (ref) & 0xFFFFF;
387 return get(sheets.get((int)sheet), (int)row, (int)col);
390 //Recursively find all SpreadsheetCells, invalidate them and return them all as a set
391 public Set<SpreadsheetCell> invalidate(SpreadsheetCell cell) {
392 Set<SpreadsheetCell> result = new HashSet<>();
395 long refKey = cell.makeReferenceKey();
396 AbstractLongSet refs = referenceMap.remove(refKey);
397 if(refs == null) return result;
398 for(long ref : refs) {
399 SpreadsheetCell referer = cellByReferenceKey(ref);
400 result.addAll(invalidate(referer));
406 public List<SpreadsheetCell> invalidateShallow(SpreadsheetCell cell) {
407 ArrayList<SpreadsheetCell> result = new ArrayList<>();
410 long refKey = cell.makeReferenceKey();
411 AbstractLongSet refs = referenceMap.remove(refKey);
412 if(refs == null) return result;
413 for(long ref : refs) {
414 SpreadsheetCell referer = cellByReferenceKey(ref);
421 public void addStyle(SpreadsheetStyle style) {
422 if (style.name == null) {
423 new Exception("Trying to add style to book without name!!").printStackTrace();
426 style.setSynchronizationId(getNewId(style));
427 styles.put(style.getStyleId(), style);
430 public SpreadsheetStyle getStyle(int styleId) {
431 return styles.get(styleId);
434 public MappingBase<SheetLineComponent> getMapping() {
439 public Optional<SpreadsheetElement> getParent() {
440 return Optional.empty();
444 public Collection<SpreadsheetElement> getSpreadsheetChildren() {
445 return children.values();
449 public void remove(SpreadsheetElement child) {
450 // TODO Auto-generated method stub
454 public void setIterationEnabled(boolean value) {
455 this.iterationEnabled = value;
458 public boolean isIterationEnabled() {
459 return iterationEnabled;
462 static Variant DEFAULT = Variant.ofInstance("Pending external reference");
464 class ExternalRefData {
465 private Variant value = DEFAULT;
466 public ExternalRefData(long referenceKey, ExternalRef ref) {
467 System.err.println("listen to " + ref);
468 ref.listen(context, new ExternalRefListener() {
470 boolean isDisposed = false;
473 public void newValue(Variant newVariant) {
474 SpreadsheetCell cell = cellByReferenceKey(referenceKey);
475 if(cell.content instanceof SpreadsheetSCLConstant) {
476 SpreadsheetSCLConstant ssc = (SpreadsheetSCLConstant)cell.content;
477 if(ssc.content.equals(ref)) {
479 fireChanges(invalidate(cell));
486 public boolean isDisposed() {
489 return SpreadsheetBook.this.isDisposed();
495 private Map<ExternalRef,ExternalRefData> externalRefMap = new HashMap<>();
497 void registerListening(long referenceKey, ExternalRef ref) {
498 ExternalRefData data = externalRefMap.get(ref);
500 data = new ExternalRefData(referenceKey, ref);
501 externalRefMap.put(ref, data);
503 // Already registered
507 Variant getExternalRefValue(long referenceKey, ExternalRef ref) {
508 System.err.println("getExternalRefValue " + ref);
509 ExternalRefData data = externalRefMap.get(ref);
511 registerListening(referenceKey, ref);
517 public boolean isDisposed() {
521 public static interface SpreadsheetBookListener {
522 void cellsChanged(Collection<SpreadsheetCell> cells);
525 public void registerListener(SpreadsheetBookListener listener) {
526 listeners.add(listener);
529 private transient ArrayList<SpreadsheetBookListener> listeners = new ArrayList<>();
531 public void fireChanges(Collection<SpreadsheetCell> cells) {
532 for(SpreadsheetBookListener listener : listeners)
533 listener.cellsChanged(cells);