]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.spreadsheet/src/org/simantics/spreadsheet/solver/SpreadsheetBook.java
8f6b9d80c676185989e4c1f0f82dd274f57c3ae4
[simantics/platform.git] / bundles / org.simantics.spreadsheet / src / org / simantics / spreadsheet / solver / SpreadsheetBook.java
1 package org.simantics.spreadsheet.solver;
2
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;
11 import java.util.Map;
12 import java.util.Optional;
13 import java.util.Set;
14
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;
35
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;
42
43 @SuppressWarnings("rawtypes")
44 public class SpreadsheetBook implements StandardNodeManagerSupport<SheetNode>, SpreadsheetElement<SpreadsheetElement, SpreadsheetElement>, Serializable, SheetNode<SpreadsheetEngine, SheetNode>, Solver, SolverNameUtil, ComponentFactory<SheetLineComponent>, ModuleUpdaterFactoryBase<SheetLineComponent> {
45     
46     private static final long serialVersionUID = 7417208688311691396L;
47
48     public Serializable NotAvailableError = new Serializable() {
49         private static final long serialVersionUID = 2535371785498129460L;
50     };
51
52     public Long2ObjectOpenHashMap<AbstractLongSet> referenceMap = new Long2ObjectOpenHashMap<>();
53
54     private Int2ObjectArrayMap<SpreadsheetStyle> styles = new Int2ObjectArrayMap<>();
55     public ArrayList<SpreadsheetEngine> sheets = new ArrayList<SpreadsheetEngine>();
56
57     private SpreadsheetMapping mapping;
58     
59     private String context;
60
61     private int idCounter = 1;
62     
63     private transient boolean disposed = false;
64
65     public SpreadsheetBook(String context) {
66         getNewId(this);
67         mapping = new SpreadsheetMapping(new SheetLineComponent(""));
68         this.context = context;
69     }
70
71     public Map<Integer, SpreadsheetElement> children = new HashMap<>();
72
73     private boolean iterationEnabled;
74
75     public int getNewId(SpreadsheetElement element) {
76         int result = idCounter++;
77         children.put(result, element);
78         return result; 
79     }
80
81     public long getEngineIndex(SpreadsheetEngine engine) {
82         for(int i=0;i<sheets.size();i++)
83             if(sheets.get(i) == engine) return i;
84         throw new IllegalStateException("Did not find sheet " + engine.getName());
85     }
86
87     public void registerReferences(long cell, AbstractLongList refs) {
88         for(int i=0;i<refs.size();i++) {
89             long key = refs.getLong(i);
90             AbstractLongSet set = referenceMap.get(key);
91             if(set == null) {
92                 set = new LongArraySet();
93                 referenceMap.put(key, set);
94             }
95             if(set.size() == 5) {
96                 AbstractLongSet newSet = new LongLinkedOpenHashSet();
97                 newSet.addAll(set);
98                 set = newSet;
99                 referenceMap.put(key, set);
100             }
101             set.add(cell);
102         }
103     }
104
105     @Override
106     public Binding getEngineBinding(SheetNode node) throws NodeManagerException {
107         Object value = getEngineValue(node);
108         if(value instanceof Variant) return Bindings.VARIANT;
109         if(value instanceof String) return Bindings.STRING;
110         if(value instanceof Boolean) return Bindings.BOOLEAN;
111         else return Bindings.getBindingUnchecked(value.getClass());
112     }
113
114     @Override
115     public Object getEngineValue(SheetNode node) {
116         if(node instanceof SpreadsheetCellContent) {
117             try {
118                 SpreadsheetCellContent scc = (SpreadsheetCellContent)node;
119                 Object content = scc.cell.evaluate(SpreadsheetEvaluationEnvironment.getInstance(this));
120                 if(content == null) return Variant.ofInstance("");
121                 if(content instanceof Variant) return content;
122                 else return Variant.ofInstance(content);
123             } catch (Throwable t) {
124                 t.printStackTrace();
125                 return Variant.ofInstance(t.toString());
126             }
127         } else if (node instanceof SpreadsheetCellContentExpression) {
128             SpreadsheetCellContentExpression scce = (SpreadsheetCellContentExpression)node;
129             if (scce.cell.content instanceof SpreadsheetFormula) {
130                 SpreadsheetFormula formula = (SpreadsheetFormula)scce.cell.content; 
131                 return formula.expression;
132             } else if (scce.cell.content instanceof SpreadsheetSCLConstant) {
133                 SpreadsheetSCLConstant sclConstant = (SpreadsheetSCLConstant) scce.cell.content;
134                 return "=" + sclConstant.expression;
135             } else {
136                 System.out.println("Broken SpreadsheetCellContentExpression possibly due to overwriting an existing expression with a constant or something else (current content is " + scce.cell.content + ")");
137                 if (scce.cell.content instanceof Variant) {
138                     return scce.cell.content;
139                 } else {
140                     return Variant.ofInstance(scce.cell.content);
141                 }
142             }
143         } else if (node instanceof SpreadsheetTypeNode) {
144             SpreadsheetTypeNode stn = (SpreadsheetTypeNode)node;
145             return stn.uri;
146         } else if (node instanceof SpreadsheetCellStyle) {
147             int styleId = ((SpreadsheetCellStyle) node).cell.style;
148             SpreadsheetStyle style = getStyle(styleId);
149             if (style == null) {
150                 style = SpreadsheetStyle.empty();
151                 if (styleId != style.getStyleId())
152                     new Exception("different style ids!" + styleId + "  " + style.getStyleId()).printStackTrace();
153                 addStyle(style);
154             }
155             return style;
156         } else if (node instanceof SpreadsheetCellEditable) {
157             boolean editable = ((SpreadsheetCellEditable) node).editable();
158             return editable;
159         }
160         return null;
161     }
162
163     @Override
164     public void setEngineValue(SheetNode node, Object value) {
165     }
166
167     @Override
168     public String getName(SheetNode node) {
169         return node.getName();
170     }
171
172     @SuppressWarnings("unchecked")
173     @Override
174     public Map<String, SheetNode> getChildren(SheetNode node) {
175         return node.getChildren();
176     }
177
178     @SuppressWarnings("unchecked")
179     @Override
180     public Map<String, SheetNode> getProperties(SheetNode node) {
181         return node.getProperties();
182     }
183
184     @Override
185     public String getName() {
186         return "";
187     }
188
189     @Override
190     public Map<String, SpreadsheetEngine> getChildren() {
191         Map<String,SpreadsheetEngine> result = new HashMap<String,SpreadsheetEngine>();
192         for(SpreadsheetEngine engine : sheets)
193             result.put(engine.getName(), engine);
194         return result;
195     }
196
197     @Override
198     public Map<String, SheetNode> getProperties() {
199         return Collections.emptyMap();
200     }
201
202     public SpreadsheetCell get(String sheet, int row, int column) {
203         SpreadsheetEngine engine = getEngine(sheet);
204         if(engine == null) return null;
205         SpreadsheetLine line = engine.getLine(row);
206         if(line == null) return null;
207         if(line.cells.size() <= column) return null;
208         return line.cells.get(column);
209     }
210
211     public SpreadsheetCell get(SpreadsheetEngine engine, int row, int column) {
212         SpreadsheetLine line = engine.getLine(row);
213         if(line == null) return null;
214         if(line.cells.size() <= column) return null;
215         return line.cells.get(column);
216     }
217
218     public SpreadsheetEngine getEngine(String sheet) {
219         for(SpreadsheetEngine engine : sheets)
220             if(sheet.equals(engine.getName())) return engine;
221         return null;
222     }
223
224     @Override
225     public ModuleUpdaterBase<SheetLineComponent> createUpdater(String id) throws Exception {
226         if("http://www.simantics.org/Spreadsheet-1.2/Line".equals(id))
227             return new LineUpdater(id);
228         else if("http://www.simantics.org/Spreadsheet-1.2/LineNode".equals(id))
229             return new LineNodeUpdater(id);
230         else if ("http://www.simantics.org/Spreadsheet-1.2/Style".equals(id))
231             return new StyleUpdater(id);
232         else if("http://www.simantics.org/Spreadsheet-1.2/Lines".equals(id))
233             return new NullUpdater(id);
234         else if("http://www.simantics.org/Spreadsheet-1.2/Spreadsheet".equals(id))
235             return new NullUpdater(id);
236         else if("http://www.simantics.org/Spreadsheet-1.2/Book".equals(id))
237             return new NullUpdater(id);
238         else
239             throw new IllegalStateException("createUpdater " + id);
240     }
241
242     @Override
243     public SheetLineComponent create(String uid) {
244         return new SheetLineComponent(uid);
245     }
246
247     @Override
248     public String getFreshName(String parentName, String name) {
249         return parentName + "/" + name;
250     }
251
252     @Override
253     public String ensureNameIsVariationOf(String parentName, int id, String name) {
254         if (parentName.isEmpty())
255             return name;
256         return parentName + "/" + name;
257     }
258
259     final static int COMP_ROOT_POS = "COMP_ROOT/".length();
260
261     @Override
262     public int getId(String name) {
263
264         if("COMP_ROOT".equals(name)) return 1;
265
266         String path = name.substring(COMP_ROOT_POS);
267         String[] parts = path.split("/");
268         Object o = resolve(parts, 0);
269         if(o instanceof SpreadsheetLines) {
270             return ((SpreadsheetLines)o).getId();
271         } else if(o instanceof SpreadsheetLine) {
272             return ((SpreadsheetLine)o).getId();
273         } else if(o instanceof SpreadsheetEngine) {
274             return ((SpreadsheetEngine)o).getId();
275         } else if (o instanceof SpreadsheetStyle) {
276             return ((SpreadsheetStyle) o).getId();
277         }
278
279         throw new IllegalStateException("Resolved object for parts " + Arrays.toString(parts) + " is not the right type! It is " + o);
280
281     }
282
283     Object resolve(String[] parts, int index) {
284         String part = parts[index];
285         if (part.startsWith("Style")) {
286             for (SpreadsheetStyle style : styles.values()) {
287                 if (style.name.equals(part)) {
288                     return style;
289                 }
290             }
291         }
292         SpreadsheetEngine engine = getEngine(part);
293         if(engine == null) return 0;
294         if(index == parts.length-1) return engine;
295         else return engine.resolve(parts, index+1);
296     }
297
298     @Override
299     public String getName(int id) {
300         if(id == -2) return "http://www.simantics.org/Spreadsheet-1.2/Book";
301         else if(id == -3) return "http://www.simantics.org/Spreadsheet-1.2/Spreadsheet";
302         else if(id == -4) return "http://www.simantics.org/Spreadsheet-1.2/Lines";
303         else if(id == -5)
304             return "http://www.simantics.org/Spreadsheet-1.2/LineNode";
305         else if (id == -6)
306             return "http://www.simantics.org/Spreadsheet-1.2/Line";
307         else if(id == -7)
308             return "http://www.simantics.org/Spreadsheet-1.2/Style";
309         else return "" + id;
310     }
311
312     @Override
313     public int getModuleType(int id) {
314         Serializable s = children.get(id);
315         if(s instanceof SpreadsheetBook) return -2;
316         else if(s instanceof SpreadsheetEngine) return -3;
317         else if(s instanceof SpreadsheetLines) {
318             if("Lines".equals(((SpreadsheetLines) s).getName()))
319                 return -4;
320             else
321                 return -5;
322         }
323         else if(s instanceof SpreadsheetLine)
324             return -6;
325         else if (s instanceof SpreadsheetStyle)
326             return -7;
327         else throw new IllegalStateException();
328     }
329
330     @SuppressWarnings("unchecked")
331     @Override
332     public void remove(int id) {
333
334         SpreadsheetElement child = children.get(id);
335         Optional<SpreadsheetElement> parent = child.getParent();
336
337         if (parent.isPresent()) {
338             parent.get().remove(child);
339             children.remove(id);
340         }
341     }
342
343     @Override
344     public void addSubprocess(String name, String subprocessType) {
345         ensureSubprocess(name);
346     }
347
348     @SuppressWarnings("unchecked")
349     public <T> T ensureSubprocess(String name) {
350         String[] parts = name.split("/");
351         if(parts.length == 2) {
352             SpreadsheetEngine engine = getEngine(parts[1]);
353             if(engine == null) {
354                 engine = new SpreadsheetEngine(this, parts[1]);
355                 sheets.add(engine);
356             }
357             return (T)engine;
358         } else if (parts.length > 2) {
359             SpreadsheetEngine engine = getEngine(parts[1]);
360             return (T)engine.ensureSubprocess(parts, 2);
361         }
362         throw new IllegalStateException();
363     }
364
365
366     @Override
367     public void includeSubprocess(String parentName, String subprocessName) {
368         // Nop
369     }
370
371     @SuppressWarnings("unchecked")
372     @Override
373     public <T> T getConcreteSolver() {
374         return (T)this;
375     }
376
377     @Override
378     public void accept(SpreadsheetVisitor v) {
379         v.visit(this);
380     }
381
382     private SpreadsheetCell cellByReferenceKey(long ref) {
383         long sheet = ref >> 40;
384         long row = (ref >> 20) & 0xFFFFF;
385         long col = (ref) & 0xFFFFF;
386         return  get(sheets.get((int)sheet), (int)row, (int)col);
387     }
388     
389     //Recursively find all SpreadsheetCells, invalidate them and return them all as a set
390     public Set<SpreadsheetCell> invalidate(SpreadsheetCell cell) {
391         Set<SpreadsheetCell> result = new HashSet<>();
392         result.add(cell);
393         cell.invalidate();
394         long refKey = cell.makeReferenceKey();
395         AbstractLongSet refs = referenceMap.remove(refKey);
396         if(refs == null) return result;
397         for(long ref : refs) {
398             SpreadsheetCell referer = cellByReferenceKey(ref);
399             result.addAll(invalidate(referer));
400         }
401         return result;
402     }
403
404     @Deprecated
405     public List<SpreadsheetCell> invalidateShallow(SpreadsheetCell cell) {
406         ArrayList<SpreadsheetCell> result = new ArrayList<>();
407         result.add(cell);
408         cell.invalidate();
409         long refKey = cell.makeReferenceKey();
410         AbstractLongSet refs = referenceMap.remove(refKey);
411         if(refs == null) return result;
412         for(long ref : refs) {
413             SpreadsheetCell referer = cellByReferenceKey(ref);
414             invalidate(referer);
415             result.add(referer);
416         }
417         return result;
418     }
419
420     public void addStyle(SpreadsheetStyle style) {
421         if (style.name == null) {
422             new Exception("Trying to add style to book without name!!").printStackTrace();
423             return;
424         }
425         style.setSynchronizationId(getNewId(style));
426         styles.put(style.getStyleId(), style);
427     }
428
429     public SpreadsheetStyle getStyle(int styleId) {
430         return styles.get(styleId);
431     }
432
433     public MappingBase<SheetLineComponent> getMapping() {
434         return mapping;
435     }
436
437     @Override
438     public Optional<SpreadsheetElement> getParent() {
439         return Optional.empty();
440     }
441
442     @Override
443     public Collection<SpreadsheetElement> getSpreadsheetChildren() {
444         return children.values();
445     }
446
447     @Override
448     public void remove(SpreadsheetElement child) {
449         // TODO Auto-generated method stub
450
451     }
452
453     public void setIterationEnabled(boolean value) {
454         this.iterationEnabled = value;
455     }
456
457     public boolean isIterationEnabled() {
458         return iterationEnabled;
459     }
460
461     static Variant DEFAULT = Variant.ofInstance("Pending external reference");
462
463     class ExternalRefData {
464         private Variant value = DEFAULT;
465         public ExternalRefData(long referenceKey, ExternalRef ref) {
466             ref.listen(context, new ExternalRefListener() {
467                 
468                 boolean isDisposed = false;
469                 
470                 @Override
471                 public void newValue(Variant newVariant) {
472                     SpreadsheetCell cell = cellByReferenceKey(referenceKey);
473                     if(cell.content instanceof SpreadsheetSCLConstant) {
474                         SpreadsheetSCLConstant ssc = (SpreadsheetSCLConstant)cell.content;
475                         if(ssc.content.equals(ref)) {
476                             value = newVariant;
477                             fireChanges(invalidate(cell));
478                             return;
479                         }
480                     }
481                     isDisposed = true;
482                 }
483                 @Override
484                 public boolean isDisposed() {
485                     if(isDisposed)
486                         return true;
487                     return SpreadsheetBook.this.isDisposed();
488                 }
489             });
490         }
491     }
492     
493     private Map<ExternalRef,ExternalRefData> externalRefMap = new HashMap<>();
494
495     void registerListening(long referenceKey, ExternalRef ref) {
496         ExternalRefData data = externalRefMap.get(ref);
497         if(data == null) {
498             data = new ExternalRefData(referenceKey, ref);
499             externalRefMap.put(ref, data);
500         } else {
501             // Already registered
502         }
503     }
504
505     Variant getExternalRefValue(long referenceKey, ExternalRef ref) {
506         ExternalRefData data = externalRefMap.get(ref);
507         if(data == null) {
508             registerListening(referenceKey, ref);
509             return DEFAULT;
510         }
511         return data.value;
512     }
513     
514     public boolean isDisposed() {
515         return disposed;
516     }
517
518     public static interface SpreadsheetBookListener {
519         void cellsChanged(Collection<SpreadsheetCell> cells);
520     }
521     
522     public void registerListener(SpreadsheetBookListener listener) {
523         listeners.add(listener);
524     }
525     
526     private transient ArrayList<SpreadsheetBookListener> listeners = new ArrayList<>();
527     
528     public void fireChanges(Collection<SpreadsheetCell> cells) {
529         for(SpreadsheetBookListener listener : listeners)
530             listener.cellsChanged(cells);
531     }
532     
533 }