]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.spreadsheet/src/org/simantics/spreadsheet/solver/SpreadsheetBook.java
Spreadsheet changes
[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         System.err.println("SpreadsheetBook, context is " + context);
68         mapping = new SpreadsheetMapping(new SheetLineComponent(""));
69         this.context = context;
70     }
71
72     public Map<Integer, SpreadsheetElement> children = new HashMap<>();
73
74     private boolean iterationEnabled;
75
76     public int getNewId(SpreadsheetElement element) {
77         int result = idCounter++;
78         children.put(result, element);
79         return result; 
80     }
81
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());
86     }
87
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);
92             if(set == null) {
93                 set = new LongArraySet();
94                 referenceMap.put(key, set);
95             }
96             if(set.size() == 5) {
97                 AbstractLongSet newSet = new LongLinkedOpenHashSet();
98                 newSet.addAll(set);
99                 set = newSet;
100                 referenceMap.put(key, set);
101             }
102             set.add(cell);
103         }
104     }
105
106     @Override
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());
113     }
114
115     @Override
116     public Object getEngineValue(SheetNode node) {
117         if(node instanceof SpreadsheetCellContent) {
118             try {
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) {
125                 t.printStackTrace();
126                 return Variant.ofInstance(t.toString());
127             }
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;
136             } else {
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;
140                 } else {
141                     return Variant.ofInstance(scce.cell.content);
142                 }
143             }
144         } else if (node instanceof SpreadsheetTypeNode) {
145             SpreadsheetTypeNode stn = (SpreadsheetTypeNode)node;
146             return stn.uri;
147         } else if (node instanceof SpreadsheetCellStyle) {
148             int styleId = ((SpreadsheetCellStyle) node).cell.style;
149             SpreadsheetStyle style = getStyle(styleId);
150             if (style == null) {
151                 style = SpreadsheetStyle.empty();
152                 if (styleId != style.getStyleId())
153                     new Exception("different style ids!" + styleId + "  " + style.getStyleId()).printStackTrace();
154                 addStyle(style);
155             }
156             return style;
157         } else if (node instanceof SpreadsheetCellEditable) {
158             boolean editable = ((SpreadsheetCellEditable) node).editable();
159             return editable;
160         }
161         return null;
162     }
163
164     @Override
165     public void setEngineValue(SheetNode node, Object value) {
166     }
167
168     @Override
169     public String getName(SheetNode node) {
170         return node.getName();
171     }
172
173     @SuppressWarnings("unchecked")
174     @Override
175     public Map<String, SheetNode> getChildren(SheetNode node) {
176         return node.getChildren();
177     }
178
179     @SuppressWarnings("unchecked")
180     @Override
181     public Map<String, SheetNode> getProperties(SheetNode node) {
182         return node.getProperties();
183     }
184
185     @Override
186     public String getName() {
187         return "";
188     }
189
190     @Override
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);
195         return result;
196     }
197
198     @Override
199     public Map<String, SheetNode> getProperties() {
200         return Collections.emptyMap();
201     }
202
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);
210     }
211
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);
217     }
218
219     public SpreadsheetEngine getEngine(String sheet) {
220         for(SpreadsheetEngine engine : sheets)
221             if(sheet.equals(engine.getName())) return engine;
222         return null;
223     }
224
225     @Override
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);
239         else
240             throw new IllegalStateException("createUpdater " + id);
241     }
242
243     @Override
244     public SheetLineComponent create(String uid) {
245         return new SheetLineComponent(uid);
246     }
247
248     @Override
249     public String getFreshName(String parentName, String name) {
250         return parentName + "/" + name;
251     }
252
253     @Override
254     public String ensureNameIsVariationOf(String parentName, int id, String name) {
255         if (parentName.isEmpty())
256             return name;
257         return parentName + "/" + name;
258     }
259
260     final static int COMP_ROOT_POS = "COMP_ROOT/".length();
261
262     @Override
263     public int getId(String name) {
264
265         if("COMP_ROOT".equals(name)) return 1;
266
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();
278         }
279
280         throw new IllegalStateException("Resolved object for parts " + Arrays.toString(parts) + " is not the right type! It is " + o);
281
282     }
283
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)) {
289                     return style;
290                 }
291             }
292         }
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);
297     }
298
299     @Override
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";
304         else if(id == -5)
305             return "http://www.simantics.org/Spreadsheet-1.2/LineNode";
306         else if (id == -6)
307             return "http://www.simantics.org/Spreadsheet-1.2/Line";
308         else if(id == -7)
309             return "http://www.simantics.org/Spreadsheet-1.2/Style";
310         else return "" + id;
311     }
312
313     @Override
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()))
320                 return -4;
321             else
322                 return -5;
323         }
324         else if(s instanceof SpreadsheetLine)
325             return -6;
326         else if (s instanceof SpreadsheetStyle)
327             return -7;
328         else throw new IllegalStateException();
329     }
330
331     @SuppressWarnings("unchecked")
332     @Override
333     public void remove(int id) {
334
335         SpreadsheetElement child = children.get(id);
336         Optional<SpreadsheetElement> parent = child.getParent();
337
338         if (parent.isPresent()) {
339             parent.get().remove(child);
340             children.remove(id);
341         }
342     }
343
344     @Override
345     public void addSubprocess(String name, String subprocessType) {
346         ensureSubprocess(name);
347     }
348
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]);
354             if(engine == null) {
355                 engine = new SpreadsheetEngine(this, parts[1]);
356                 sheets.add(engine);
357             }
358             return (T)engine;
359         } else if (parts.length > 2) {
360             SpreadsheetEngine engine = getEngine(parts[1]);
361             return (T)engine.ensureSubprocess(parts, 2);
362         }
363         throw new IllegalStateException();
364     }
365
366
367     @Override
368     public void includeSubprocess(String parentName, String subprocessName) {
369         // Nop
370     }
371
372     @SuppressWarnings("unchecked")
373     @Override
374     public <T> T getConcreteSolver() {
375         return (T)this;
376     }
377
378     @Override
379     public void accept(SpreadsheetVisitor v) {
380         v.visit(this);
381     }
382
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);
388     }
389     
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<>();
393         result.add(cell);
394         cell.invalidate();
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));
401         }
402         return result;
403     }
404
405     @Deprecated
406     public List<SpreadsheetCell> invalidateShallow(SpreadsheetCell cell) {
407         ArrayList<SpreadsheetCell> result = new ArrayList<>();
408         result.add(cell);
409         cell.invalidate();
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);
415             invalidate(referer);
416             result.add(referer);
417         }
418         return result;
419     }
420
421     public void addStyle(SpreadsheetStyle style) {
422         if (style.name == null) {
423             new Exception("Trying to add style to book without name!!").printStackTrace();
424             return;
425         }
426         style.setSynchronizationId(getNewId(style));
427         styles.put(style.getStyleId(), style);
428     }
429
430     public SpreadsheetStyle getStyle(int styleId) {
431         return styles.get(styleId);
432     }
433
434     public MappingBase<SheetLineComponent> getMapping() {
435         return mapping;
436     }
437
438     @Override
439     public Optional<SpreadsheetElement> getParent() {
440         return Optional.empty();
441     }
442
443     @Override
444     public Collection<SpreadsheetElement> getSpreadsheetChildren() {
445         return children.values();
446     }
447
448     @Override
449     public void remove(SpreadsheetElement child) {
450         // TODO Auto-generated method stub
451
452     }
453
454     public void setIterationEnabled(boolean value) {
455         this.iterationEnabled = value;
456     }
457
458     public boolean isIterationEnabled() {
459         return iterationEnabled;
460     }
461
462     static Variant DEFAULT = Variant.ofInstance("Pending external reference");
463
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() {
469                 
470                 boolean isDisposed = false;
471                 
472                 @Override
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)) {
478                             value = newVariant;
479                             fireChanges(invalidate(cell));
480                             return;
481                         }
482                     }
483                     isDisposed = true;
484                 }
485                 @Override
486                 public boolean isDisposed() {
487                     if(isDisposed)
488                         return true;
489                     return SpreadsheetBook.this.isDisposed();
490                 }
491             });
492         }
493     }
494     
495     private Map<ExternalRef,ExternalRefData> externalRefMap = new HashMap<>();
496
497     void registerListening(long referenceKey, ExternalRef ref) {
498         ExternalRefData data = externalRefMap.get(ref);
499         if(data == null) {
500             data = new ExternalRefData(referenceKey, ref);
501             externalRefMap.put(ref, data);
502         } else {
503             // Already registered
504         }
505     }
506
507     Variant getExternalRefValue(long referenceKey, ExternalRef ref) {
508         System.err.println("getExternalRefValue " + ref);
509         ExternalRefData data = externalRefMap.get(ref);
510         if(data == null) {
511             registerListening(referenceKey, ref);
512             return DEFAULT;
513         }
514         return data.value;
515     }
516     
517     public boolean isDisposed() {
518         return disposed;
519     }
520
521     public static interface SpreadsheetBookListener {
522         void cellsChanged(Collection<SpreadsheetCell> cells);
523     }
524     
525     public void registerListener(SpreadsheetBookListener listener) {
526         listeners.add(listener);
527     }
528     
529     private transient ArrayList<SpreadsheetBookListener> listeners = new ArrayList<>();
530     
531     public void fireChanges(Collection<SpreadsheetCell> cells) {
532         for(SpreadsheetBookListener listener : listeners)
533             listener.cellsChanged(cells);
534     }
535     
536 }