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.SpreadsheetCellStyle;
22 import org.simantics.spreadsheet.SpreadsheetVisitor;
23 import org.simantics.spreadsheet.solver.formula.SpreadsheetEvaluationEnvironment;
24 import org.simantics.spreadsheet.synchronization.LineNodeUpdater;
25 import org.simantics.spreadsheet.synchronization.LineUpdater;
26 import org.simantics.spreadsheet.synchronization.NullUpdater;
27 import org.simantics.spreadsheet.synchronization.StyleUpdater;
28 import org.simantics.structural.synchronization.base.ModuleUpdaterBase;
29 import org.simantics.structural.synchronization.base.ModuleUpdaterFactoryBase;
30 import org.simantics.structural.synchronization.base.SolverNameUtil;
31 import org.simantics.structural.synchronization.utils.ComponentFactory;
32 import org.simantics.structural.synchronization.utils.MappingBase;
33 import org.simantics.structural.synchronization.utils.Solver;
35 import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
36 import it.unimi.dsi.fastutil.longs.AbstractLongList;
37 import it.unimi.dsi.fastutil.longs.AbstractLongSet;
38 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
39 import it.unimi.dsi.fastutil.longs.LongArraySet;
40 import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
42 @SuppressWarnings("rawtypes")
43 public class SpreadsheetBook implements StandardNodeManagerSupport<SheetNode>, SpreadsheetElement<SpreadsheetElement, SpreadsheetElement>, Serializable, SheetNode<SpreadsheetEngine, SheetNode>, Solver, SolverNameUtil, ComponentFactory<SheetLineComponent>, ModuleUpdaterFactoryBase<SheetLineComponent> {
45 private static final long serialVersionUID = 7417208688311691396L;
47 public Serializable NotAvailableError = new Serializable() {
48 private static final long serialVersionUID = 2535371785498129460L;
51 public Long2ObjectOpenHashMap<AbstractLongSet> referenceMap = new Long2ObjectOpenHashMap<>();
53 private Int2ObjectArrayMap<SpreadsheetStyle> styles = new Int2ObjectArrayMap<>();
54 public ArrayList<SpreadsheetEngine> sheets = new ArrayList<SpreadsheetEngine>();
56 private SpreadsheetMapping mapping;
60 private int idCounter = 1;
62 private transient boolean disposed = false;
64 public SpreadsheetBook(String context) {
66 mapping = new SpreadsheetMapping(new SheetLineComponent(""));
67 this.context = context;
70 public Map<Integer, SpreadsheetElement> children = new HashMap<>();
72 private boolean iterationEnabled;
74 public int getNewId(SpreadsheetElement element) {
75 int result = idCounter++;
76 children.put(result, element);
80 public long getEngineIndex(SpreadsheetEngine engine) {
81 for(int i=0;i<sheets.size();i++)
82 if(sheets.get(i) == engine) return i;
83 throw new IllegalStateException("Did not find sheet " + engine.getName());
86 public void registerReferences(long cell, AbstractLongList refs) {
87 for(int i=0;i<refs.size();i++) {
88 long key = refs.getLong(i);
89 AbstractLongSet set = referenceMap.get(key);
91 set = new LongArraySet();
92 referenceMap.put(key, set);
95 AbstractLongSet newSet = new LongLinkedOpenHashSet();
98 referenceMap.put(key, set);
105 public Binding getEngineBinding(SheetNode node) throws NodeManagerException {
106 Object value = getEngineValue(node);
107 if(value instanceof Variant) return Bindings.VARIANT;
108 if(value instanceof String) return Bindings.STRING;
109 if(value instanceof Boolean) return Bindings.BOOLEAN;
110 else return Bindings.getBindingUnchecked(value.getClass());
114 public Object getEngineValue(SheetNode node) {
115 if(node instanceof SpreadsheetCellContent) {
117 SpreadsheetCellContent scc = (SpreadsheetCellContent)node;
118 Object content = scc.cell.evaluate(SpreadsheetEvaluationEnvironment.getInstance(this));
119 if(content == null) return Variant.ofInstance("");
120 if(content instanceof Variant) return content;
121 if(content instanceof ExternalRefData) return ((ExternalRefData)content).getContent();
122 else return Variant.ofInstance(content);
123 } catch (Throwable t) {
125 return Variant.ofInstance(t.toString());
127 } else if (node instanceof SpreadsheetCellContentExpression) {
128 SpreadsheetCellContentExpression scce = (SpreadsheetCellContentExpression)node;
129 if (scce.cell.getContent() instanceof SpreadsheetFormula) {
130 SpreadsheetFormula formula = (SpreadsheetFormula)scce.cell.getContent();
131 return formula.expression;
132 } else if (scce.cell.getContent() instanceof SpreadsheetSCLConstant) {
133 SpreadsheetSCLConstant sclConstant = (SpreadsheetSCLConstant) scce.cell.getContent();
134 return "=" + sclConstant.getExpression();
136 System.out.println("Broken SpreadsheetCellContentExpression possibly due to overwriting an existing expression with a constant or something else (current content is " + scce.cell.getContent() + ")");
137 if (scce.cell.getContent() instanceof Variant) {
138 return scce.cell.getContent();
140 return Variant.ofInstance(scce.cell.getContent());
143 } else if (node instanceof SpreadsheetTypeNode) {
144 SpreadsheetTypeNode stn = (SpreadsheetTypeNode)node;
146 } else if (node instanceof SpreadsheetCellStyle) {
147 int styleId = ((SpreadsheetCellStyle) node).cell.style;
148 SpreadsheetStyle style = getStyle(styleId);
150 style = SpreadsheetStyle.empty();
151 if (styleId != style.getStyleId())
152 new Exception("different style ids!" + styleId + " " + style.getStyleId()).printStackTrace();
156 } else if (node instanceof SpreadsheetCellEditable) {
157 boolean editable = ((SpreadsheetCellEditable) node).editable();
164 public void setEngineValue(SheetNode node, Object value) {
166 SpreadsheetCellContent scc = (SpreadsheetCellContent)node;
168 Object content = scc.cell.evaluate(SpreadsheetEvaluationEnvironment.getInstance(this));
169 System.err.println("content2: " + content);
170 if (content instanceof Variant) {
171 scc.cell.setContent(value);
172 } else if (content instanceof ExternalRefData) {
173 ExternalRefData erd = (ExternalRefData)content;
174 erd.getRef().modify(context, (Variant)value);
176 throw new IllegalStateException("Unable to set cell value");
182 public String getName(SheetNode node) {
183 return node.getName();
186 @SuppressWarnings("unchecked")
188 public Map<String, SheetNode> getChildren(SheetNode node) {
189 return node.getChildren();
192 @SuppressWarnings("unchecked")
194 public Map<String, SheetNode> getProperties(SheetNode node) {
195 return node.getProperties();
199 public String getName() {
204 public Map<String, SpreadsheetEngine> getChildren() {
205 Map<String,SpreadsheetEngine> result = new HashMap<String,SpreadsheetEngine>();
206 for(SpreadsheetEngine engine : sheets)
207 result.put(engine.getName(), engine);
212 public Map<String, SheetNode> getProperties() {
213 return Collections.emptyMap();
216 public SpreadsheetCell get(String sheet, int row, int column) {
217 SpreadsheetEngine engine = getEngine(sheet);
218 if(engine == null) return null;
219 SpreadsheetLine line = engine.getLine(row);
220 if(line == null) return null;
221 if(line.cells.size() <= column) return null;
222 return line.cells.get(column);
225 public SpreadsheetCell get(SpreadsheetEngine engine, int row, int column) {
226 SpreadsheetLine line = engine.getLine(row);
227 if(line == null) return null;
228 if(line.cells.size() <= column) return null;
229 return line.cells.get(column);
232 public SpreadsheetEngine getEngine(String sheet) {
233 for(SpreadsheetEngine engine : sheets)
234 if(sheet.equals(engine.getName())) return engine;
239 public ModuleUpdaterBase<SheetLineComponent> createUpdater(String id) throws Exception {
240 if("http://www.simantics.org/Spreadsheet-1.2/Line".equals(id))
241 return new LineUpdater(id);
242 else if("http://www.simantics.org/Spreadsheet-1.2/LineNode".equals(id))
243 return new LineNodeUpdater(id);
244 else if ("http://www.simantics.org/Spreadsheet-1.2/Style".equals(id))
245 return new StyleUpdater(id);
246 else if("http://www.simantics.org/Spreadsheet-1.2/Lines".equals(id))
247 return new NullUpdater(id);
248 else if("http://www.simantics.org/Spreadsheet-1.2/Spreadsheet".equals(id))
249 return new NullUpdater(id);
250 else if("http://www.simantics.org/Spreadsheet-1.2/Book".equals(id))
251 return new NullUpdater(id);
253 throw new IllegalStateException("createUpdater " + id);
257 public SheetLineComponent create(String uid) {
258 return new SheetLineComponent(uid);
262 public String getFreshName(String parentName, String name) {
263 return parentName + "/" + name;
267 public String ensureNameIsVariationOf(String parentName, int id, String name) {
268 if (parentName.isEmpty())
270 return parentName + "/" + name;
273 final static int COMP_ROOT_POS = "COMP_ROOT/".length();
276 public int getId(String name) {
278 if("COMP_ROOT".equals(name)) return 1;
280 String path = name.substring(COMP_ROOT_POS);
281 String[] parts = path.split("/");
282 Object o = resolve(parts, 0);
283 if(o instanceof SpreadsheetLines) {
284 return ((SpreadsheetLines)o).getId();
285 } else if(o instanceof SpreadsheetLine) {
286 return ((SpreadsheetLine)o).getId();
287 } else if(o instanceof SpreadsheetEngine) {
288 return ((SpreadsheetEngine)o).getId();
289 } else if (o instanceof SpreadsheetStyle) {
290 return ((SpreadsheetStyle) o).getId();
293 throw new IllegalStateException("Resolved object for parts " + Arrays.toString(parts) + " is not the right type! It is " + o);
297 Object resolve(String[] parts, int index) {
298 String part = parts[index];
299 if (part.startsWith("Style")) {
300 for (SpreadsheetStyle style : styles.values()) {
301 if (style.name.equals(part)) {
306 SpreadsheetEngine engine = getEngine(part);
307 if(engine == null) return 0;
308 if(index == parts.length-1) return engine;
309 else return engine.resolve(parts, index+1);
313 public String getName(int id) {
314 if(id == -2) return "http://www.simantics.org/Spreadsheet-1.2/Book";
315 else if(id == -3) return "http://www.simantics.org/Spreadsheet-1.2/Spreadsheet";
316 else if(id == -4) return "http://www.simantics.org/Spreadsheet-1.2/Lines";
318 return "http://www.simantics.org/Spreadsheet-1.2/LineNode";
320 return "http://www.simantics.org/Spreadsheet-1.2/Line";
322 return "http://www.simantics.org/Spreadsheet-1.2/Style";
327 public int getModuleType(int id) {
328 Serializable s = children.get(id);
329 if(s instanceof SpreadsheetBook) return -2;
330 else if(s instanceof SpreadsheetEngine) return -3;
331 else if(s instanceof SpreadsheetLines) {
332 if("Lines".equals(((SpreadsheetLines) s).getName()))
337 else if(s instanceof SpreadsheetLine)
339 else if (s instanceof SpreadsheetStyle)
341 else throw new IllegalStateException();
344 @SuppressWarnings("unchecked")
346 public void remove(int id) {
348 SpreadsheetElement child = children.get(id);
349 Optional<SpreadsheetElement> parent = child.getParent();
351 if (parent.isPresent()) {
352 parent.get().remove(child);
358 public void addSubprocess(String name, String subprocessType) {
359 ensureSubprocess(name);
362 @SuppressWarnings("unchecked")
363 public <T> T ensureSubprocess(String name) {
364 String[] parts = name.split("/");
365 if(parts.length == 2) {
366 SpreadsheetEngine engine = getEngine(parts[1]);
368 engine = new SpreadsheetEngine(this, parts[1]);
372 } else if (parts.length > 2) {
373 SpreadsheetEngine engine = getEngine(parts[1]);
374 return (T)engine.ensureSubprocess(parts, 2);
376 throw new IllegalStateException();
381 public void includeSubprocess(String parentName, String subprocessName) {
385 @SuppressWarnings("unchecked")
387 public <T> T getConcreteSolver() {
392 public void accept(SpreadsheetVisitor v) {
396 SpreadsheetCell cellByReferenceKey(long ref) {
397 long sheet = ref >> 40;
398 long row = (ref >> 20) & 0xFFFFF;
399 long col = (ref) & 0xFFFFF;
400 return get(sheets.get((int)sheet), (int)row, (int)col);
403 //Recursively find all SpreadsheetCells, invalidate them and return them all as a set
404 public Set<SpreadsheetCell> invalidate(SpreadsheetCell cell) {
405 Set<SpreadsheetCell> result = new HashSet<>();
408 long refKey = cell.makeReferenceKey();
409 AbstractLongSet refs = referenceMap.remove(refKey);
410 if(refs == null) return result;
411 for(long ref : refs) {
412 SpreadsheetCell referer = cellByReferenceKey(ref);
413 result.addAll(invalidate(referer));
419 public List<SpreadsheetCell> invalidateShallow(SpreadsheetCell cell) {
420 ArrayList<SpreadsheetCell> result = new ArrayList<>();
423 long refKey = cell.makeReferenceKey();
424 AbstractLongSet refs = referenceMap.remove(refKey);
425 if(refs == null) return result;
426 for(long ref : refs) {
427 SpreadsheetCell referer = cellByReferenceKey(ref);
434 public void addStyle(SpreadsheetStyle style) {
435 if (style.name == null) {
436 new Exception("Trying to add style to book without name!!").printStackTrace();
439 style.setSynchronizationId(getNewId(style));
440 styles.put(style.getStyleId(), style);
443 public SpreadsheetStyle getStyle(int styleId) {
444 return styles.get(styleId);
447 public MappingBase<SheetLineComponent> getMapping() {
452 public Optional<SpreadsheetElement> getParent() {
453 return Optional.empty();
457 public Collection<SpreadsheetElement> getSpreadsheetChildren() {
458 return children.values();
462 public void remove(SpreadsheetElement child) {
463 // TODO Auto-generated method stub
467 public void setIterationEnabled(boolean value) {
468 this.iterationEnabled = value;
471 public boolean isIterationEnabled() {
472 return iterationEnabled;
475 static Variant DEFAULT_VALUE = Variant.ofInstance("Pending external reference");
477 private Map<ExternalRef,ExternalRefData> externalRefMap = new HashMap<>();
479 void registerListening(long referenceKey, ExternalRef ref) {
480 ExternalRefData data = externalRefMap.get(ref);
482 data = new ExternalRefData(this, referenceKey, ref);
483 externalRefMap.put(ref, data);
485 // Already registered
489 ExternalRefData getExternalRefValue(long referenceKey, ExternalRef ref) {
490 ExternalRefData data = externalRefMap.get(ref);
492 registerListening(referenceKey, ref);
493 return new ExternalRefData(this, referenceKey, new ExternalRefConstant(DEFAULT_VALUE));
498 public boolean isDisposed() {
502 public static interface SpreadsheetBookListener {
503 void cellsChanged(Collection<SpreadsheetCell> cells);
506 public void registerListener(SpreadsheetBookListener listener) {
507 if(listeners == null)
508 listeners = new ArrayList<>();
509 listeners.add(listener);
512 private transient ArrayList<SpreadsheetBookListener> listeners = new ArrayList<>();
514 public void fireChanges(Collection<SpreadsheetCell> cells) {
515 for(SpreadsheetBookListener listener : listeners)
516 listener.cellsChanged(cells);