SpreadsheetCells with Circular References support iterations.
[simantics/platform.git] / bundles / org.simantics.spreadsheet.graph / src / org / simantics / spreadsheet / graph / SpreadsheetBook.java
1 package org.simantics.spreadsheet.graph;\r
2 \r
3 import java.io.Serializable;\r
4 import java.util.ArrayList;\r
5 import java.util.Arrays;\r
6 import java.util.Collection;\r
7 import java.util.Collections;\r
8 import java.util.HashMap;\r
9 import java.util.List;\r
10 import java.util.Map;\r
11 import java.util.Optional;\r
12 \r
13 import org.simantics.databoard.binding.mutable.Variant;\r
14 import org.simantics.db.exception.DatabaseException;\r
15 import org.simantics.db.layer0.StandardEngine;\r
16 import org.simantics.spreadsheet.graph.formula.SpreadsheetEvaluationEnvironment;\r
17 import org.simantics.spreadsheet.graph.synchronization.LineNodeUpdater;\r
18 import org.simantics.spreadsheet.graph.synchronization.LineUpdater;\r
19 import org.simantics.spreadsheet.graph.synchronization.NullUpdater;\r
20 import org.simantics.spreadsheet.graph.synchronization.SheetLineComponent;\r
21 import org.simantics.spreadsheet.graph.synchronization.StyleUpdater;\r
22 import org.simantics.structural.synchronization.base.ComponentFactory;\r
23 import org.simantics.structural.synchronization.base.MappingBase;\r
24 import org.simantics.structural.synchronization.base.ModuleUpdaterBase;\r
25 import org.simantics.structural.synchronization.base.ModuleUpdaterFactoryBase;\r
26 import org.simantics.structural.synchronization.base.Solver;\r
27 import org.simantics.structural.synchronization.base.SolverNameUtil;\r
28 \r
29 import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;\r
30 import it.unimi.dsi.fastutil.longs.AbstractLongList;\r
31 import it.unimi.dsi.fastutil.longs.AbstractLongSet;\r
32 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;\r
33 import it.unimi.dsi.fastutil.longs.LongArraySet;\r
34 import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;\r
35 \r
36 public class SpreadsheetBook implements SpreadsheetElement<SpreadsheetElement, SpreadsheetElement>, StandardEngine<SheetNode>, Serializable, SheetNode<SpreadsheetEngine, SheetNode>, Solver, SolverNameUtil, ComponentFactory<SheetLineComponent>, ModuleUpdaterFactoryBase<SheetLineComponent> {\r
37 \r
38         private static final long serialVersionUID = 7417208688311691396L;\r
39         \r
40         public Serializable NotAvailableError = new Serializable() {};\r
41         \r
42         public Long2ObjectOpenHashMap<AbstractLongSet> referenceMap = new Long2ObjectOpenHashMap<>();\r
43         \r
44         private Int2ObjectArrayMap<SpreadsheetStyle> styles = new Int2ObjectArrayMap<>();\r
45 //      public ObjectArrayList<SpreadsheetStyle> styles = new ObjectArrayList<SpreadsheetStyle>();\r
46         public ArrayList<SpreadsheetEngine> sheets = new ArrayList<SpreadsheetEngine>();\r
47         \r
48         private SpreadsheetMapping mapping;\r
49         \r
50         \r
51         private int idCounter = 1;\r
52         \r
53         public SpreadsheetBook() {\r
54                 getNewId(this);\r
55                 mapping = new SpreadsheetMapping(new SheetLineComponent(""));\r
56         }\r
57         \r
58         public Map<Integer, SpreadsheetElement> children = new HashMap<>();\r
59 \r
60         private boolean iterationEnabled;\r
61 \r
62         public int getNewId(SpreadsheetElement element) {\r
63                 int result = idCounter++;\r
64                 children.put(result, element);\r
65                 return result; \r
66         }\r
67         \r
68         public long getEngineIndex(SpreadsheetEngine engine) {\r
69                 for(int i=0;i<sheets.size();i++)\r
70                         if(sheets.get(i) == engine) return i;\r
71                 throw new IllegalStateException("Did not find sheet " + engine.getName());\r
72         }\r
73         \r
74         public void registerReferences(long cell, AbstractLongList refs) {\r
75                 for(int i=0;i<refs.size();i++) {\r
76                         long key = refs.getLong(i);\r
77                         AbstractLongSet set = referenceMap.get(key);\r
78                         if(set == null) {\r
79                                 set = new LongArraySet();\r
80                                 referenceMap.put(key, set);\r
81                         }\r
82                         if(set.size() == 5) {\r
83                                 AbstractLongSet newSet = new LongLinkedOpenHashSet();\r
84                                 newSet.addAll(set);\r
85                                 set = newSet;\r
86                                 referenceMap.put(key, set);\r
87                         }\r
88                         set.add(cell);\r
89                 }\r
90         }\r
91         \r
92         @Override\r
93         public Object getValue(SheetNode node) {\r
94                 if(node instanceof SpreadsheetCellContent) {\r
95                         try {\r
96                                 SpreadsheetCellContent scc = (SpreadsheetCellContent)node;\r
97                                 Object content = scc.cell.evaluate(SpreadsheetEvaluationEnvironment.getInstance(this));\r
98                                 if(content == null) return Variant.ofInstance("");\r
99                                 if(content instanceof Variant) return content;\r
100                                 else return Variant.ofInstance(content);\r
101                         } catch (Throwable t) {\r
102                                 t.printStackTrace();\r
103                                 return Variant.ofInstance(t.toString());\r
104                         }\r
105                 } else if (node instanceof SpreadsheetCellContentExpression) {\r
106                         SpreadsheetCellContentExpression scce = (SpreadsheetCellContentExpression)node;\r
107                         if (scce.cell.content instanceof SpreadsheetFormula) {\r
108                         SpreadsheetFormula formula = (SpreadsheetFormula)scce.cell.content; \r
109                         return formula.expression;\r
110                         } else if (scce.cell.content instanceof SpreadsheetSCLConstant) {\r
111                             SpreadsheetSCLConstant sclConstant = (SpreadsheetSCLConstant) scce.cell.content;\r
112                             return "=" + sclConstant.expression;\r
113                         } else {\r
114                             System.out.println("Broken SpreadsheetCellContentExpression possibly due to overwriting an existing expression with a constant or something else (current content is " + scce.cell.content + ")");\r
115                             if (scce.cell.content instanceof Variant) {\r
116                                 return scce.cell.content;\r
117                             } else {\r
118                                 return Variant.ofInstance(scce.cell.content);\r
119                             }\r
120                         }\r
121                 } else if (node instanceof SpreadsheetTypeNode) {\r
122                         SpreadsheetTypeNode stn = (SpreadsheetTypeNode)node;\r
123                         return stn.uri;\r
124                 } else if (node instanceof SpreadsheetCellStyle) {\r
125                     int styleId = ((SpreadsheetCellStyle) node).cell.style;\r
126                     SpreadsheetStyle style = getStyle(styleId);\r
127                     if (style == null) {\r
128                         style = SpreadsheetStyle.empty();\r
129                         if (styleId != style.getStyleId())\r
130                             new Exception("different style ids!" + styleId + "  " + style.getStyleId()).printStackTrace();\r
131                         addStyle(style);\r
132                     }\r
133                     return style;\r
134                 } else if (node instanceof SpreadsheetCellEditable) {\r
135                     boolean editable = ((SpreadsheetCellEditable) node).editable();\r
136                     return editable;\r
137                 }\r
138                 return null;\r
139         }\r
140 \r
141         @Override\r
142         public void setValue(SheetNode node, Object value) {\r
143         }\r
144 \r
145         @Override\r
146         public String getName(SheetNode node) {\r
147                 return node.getName();\r
148         }\r
149 \r
150         @Override\r
151         public Map<String, SheetNode> getChildren(SheetNode node) {\r
152                 return node.getChildren();\r
153         }\r
154 \r
155         @Override\r
156         public Map<String, SheetNode> getProperties(SheetNode node) {\r
157                 return node.getProperties();\r
158         }\r
159 \r
160         @Override\r
161         public String getName() {\r
162                 return "";\r
163         }\r
164 \r
165         @Override\r
166         public Map<String, SpreadsheetEngine> getChildren() {\r
167                 Map<String,SpreadsheetEngine> result = new HashMap<String,SpreadsheetEngine>();\r
168                 for(SpreadsheetEngine engine : sheets)\r
169                         result.put(engine.getName(), engine);\r
170                 return result;\r
171         }\r
172 \r
173         @Override\r
174         public Map<String, SheetNode> getProperties() {\r
175                 return Collections.emptyMap();\r
176         }\r
177         \r
178         public SpreadsheetCell get(String sheet, int row, int column) {\r
179                 SpreadsheetEngine engine = getEngine(sheet);\r
180                 if(engine == null) return null;\r
181                 SpreadsheetLine line = engine.getLine(row);\r
182                 if(line == null) return null;\r
183                 if(line.cells.size() <= column) return null;\r
184                 return line.cells.get(column);\r
185         }\r
186 \r
187         public SpreadsheetCell get(SpreadsheetEngine engine, int row, int column) {\r
188                 SpreadsheetLine line = engine.getLine(row);\r
189                 if(line == null) return null;\r
190                 if(line.cells.size() <= column) return null;\r
191                 return line.cells.get(column);\r
192         }\r
193 \r
194         public SpreadsheetEngine getEngine(String sheet) {\r
195                 for(SpreadsheetEngine engine : sheets)\r
196                         if(sheet.equals(engine.getName())) return engine;\r
197                 return null;\r
198         }\r
199 \r
200         @Override\r
201         public ModuleUpdaterBase<SheetLineComponent> createUpdater(String id) throws DatabaseException {\r
202                 if("http://www.simantics.org/Spreadsheet-1.2/Line".equals(id))\r
203                         return new LineUpdater(id);\r
204                 else if("http://www.simantics.org/Spreadsheet-1.2/LineNode".equals(id))\r
205                         return new LineNodeUpdater(id);\r
206                 else if ("http://www.simantics.org/Spreadsheet-1.2/Style".equals(id))\r
207                     return new StyleUpdater(id);\r
208                 else if("http://www.simantics.org/Spreadsheet-1.2/Lines".equals(id))\r
209                         return new NullUpdater(id);\r
210                 else if("http://www.simantics.org/Spreadsheet-1.2/Spreadsheet".equals(id))\r
211                         return new NullUpdater(id);\r
212                 else if("http://www.simantics.org/Spreadsheet-1.2/Book".equals(id))\r
213                         return new NullUpdater(id);\r
214                 else\r
215                         throw new IllegalStateException("createUpdater " + id);\r
216         }\r
217 \r
218         @Override\r
219         public SheetLineComponent create(String uid) {\r
220                 return new SheetLineComponent(uid);\r
221         }\r
222 \r
223         @Override\r
224         public String getFreshName(String parentName, String name) {\r
225                 return parentName + "/" + name;\r
226         }\r
227 \r
228         @Override\r
229         public String ensureNameIsVariationOf(String parentName, int id, String name) {\r
230             if (parentName.isEmpty())\r
231                 return name;\r
232                 return parentName + "/" + name;\r
233         }\r
234 \r
235         final static int COMP_ROOT_POS = "COMP_ROOT/".length();\r
236         \r
237         @Override\r
238         public int getId(String name) {\r
239                 \r
240                 if("COMP_ROOT".equals(name)) return 1;\r
241 \r
242                 String path = name.substring(COMP_ROOT_POS);\r
243                 String[] parts = path.split("/");\r
244                 Object o = resolve(parts, 0);\r
245                 if(o instanceof SpreadsheetLines) {\r
246                         return ((SpreadsheetLines)o).getId();\r
247                 } else if(o instanceof SpreadsheetLine) {\r
248                         return ((SpreadsheetLine)o).getId();\r
249                 } else if(o instanceof SpreadsheetEngine) {\r
250                         return ((SpreadsheetEngine)o).getId();\r
251                 } else if (o instanceof SpreadsheetStyle) {\r
252                     return ((SpreadsheetStyle) o).getId();\r
253                 }\r
254                 \r
255                 throw new IllegalStateException("Resolved object for parts " + Arrays.toString(parts) + " is not the right type! It is " + o);\r
256                 \r
257         }\r
258         \r
259         Object resolve(String[] parts, int index) {\r
260                 String part = parts[index];\r
261                 if (part.startsWith("Style")) {\r
262                     for (SpreadsheetStyle style : styles.values()) {\r
263                         if (style.name.equals(part)) {\r
264                             return style;\r
265                         }\r
266                     }\r
267                 }\r
268                 SpreadsheetEngine engine = getEngine(part);\r
269                 if(engine == null) return 0;\r
270                 if(index == parts.length-1) return engine;\r
271                 else return engine.resolve(parts, index+1);\r
272         }\r
273 \r
274         @Override\r
275         public String getName(int id) {\r
276                 if(id == -2) return "http://www.simantics.org/Spreadsheet-1.2/Book";\r
277                 else if(id == -3) return "http://www.simantics.org/Spreadsheet-1.2/Spreadsheet";\r
278                 else if(id == -4) return "http://www.simantics.org/Spreadsheet-1.2/Lines";\r
279                 else if(id == -5)\r
280                     return "http://www.simantics.org/Spreadsheet-1.2/LineNode";\r
281                 else if (id == -6)\r
282                     return "http://www.simantics.org/Spreadsheet-1.2/Line";\r
283                 else if(id == -7)\r
284                     return "http://www.simantics.org/Spreadsheet-1.2/Style";\r
285                 else return "" + id;\r
286         }\r
287 \r
288         @Override\r
289         public int getModuleType(int id) {\r
290                 Serializable s = children.get(id);\r
291                 if(s instanceof SpreadsheetBook) return -2;\r
292                 else if(s instanceof SpreadsheetEngine) return -3;\r
293                 else if(s instanceof SpreadsheetLines) {\r
294                         if("Lines".equals(((SpreadsheetLines) s).getName()))\r
295                                 return -4;\r
296                         else\r
297                                 return -5;\r
298                 }\r
299                 else if(s instanceof SpreadsheetLine)\r
300                     return -6;\r
301                 else if (s instanceof SpreadsheetStyle)\r
302                     return -7;\r
303                 else throw new IllegalStateException();\r
304         }\r
305 \r
306         @Override\r
307         public void remove(int id) {\r
308             \r
309             SpreadsheetElement child = children.get(id);\r
310             Optional<SpreadsheetElement> parent = child.getParent();\r
311             \r
312             if (parent.isPresent()) {\r
313                 parent.get().remove(child);\r
314                 children.remove(id);\r
315             }\r
316         }\r
317 \r
318         @Override\r
319         public void addSubprocess(String name) {\r
320                 ensureSubprocess(name);\r
321         }\r
322         \r
323         public <T> T ensureSubprocess(String name) {\r
324                 String[] parts = name.split("/");\r
325                 if(parts.length == 2) {\r
326                         SpreadsheetEngine engine = getEngine(parts[1]);\r
327                         if(engine == null) {\r
328                                 engine = new SpreadsheetEngine(this, parts[1]);\r
329                                 sheets.add(engine);\r
330                         }\r
331                         return (T)engine;\r
332                 } else if (parts.length > 2) {\r
333                         SpreadsheetEngine engine = getEngine(parts[1]);\r
334                         return (T)engine.ensureSubprocess(parts, 2);\r
335                 }\r
336                 throw new IllegalStateException();\r
337         }\r
338         \r
339 \r
340         @Override\r
341         public void includeSubprocess(String parentName, String subprocessName) {\r
342                 // Nop\r
343         }\r
344 \r
345         @Override\r
346         public <T> T getConcreteSolver() {\r
347                 return (T)this;\r
348         }\r
349         \r
350         @Override\r
351         public void accept(SpreadsheetVisitor v) {\r
352                 v.visit(this);\r
353         }\r
354         \r
355         public List<SpreadsheetCell> invalidate(SpreadsheetCell cell) {\r
356                 ArrayList<SpreadsheetCell> result = new ArrayList<>();\r
357                 result.add(cell);\r
358                 cell.invalidate();\r
359                 long refKey = cell.makeReferenceKey();\r
360                 AbstractLongSet refs = referenceMap.remove(refKey);\r
361                 if(refs == null) return result;\r
362                 for(long ref : refs) {\r
363                         long sheet = ref >> 40;\r
364                         long row = (ref >> 20) & 0xFFFFF;\r
365                         long col = (ref) & 0xFFFFF;\r
366                         SpreadsheetCell referer = get(sheets.get((int)sheet), (int)row, (int)col);\r
367                         invalidate(referer);\r
368                         result.add(referer);\r
369                 }\r
370                 return result;\r
371         }\r
372 \r
373     public void addStyle(SpreadsheetStyle style) {\r
374         if (style.name == null) {\r
375             new Exception("Trying to add style to book without name!!").printStackTrace();\r
376             return;\r
377         }\r
378         style.setSynchronizationId(getNewId(style));\r
379         styles.put(style.getStyleId(), style);\r
380     }\r
381 \r
382     public SpreadsheetStyle getStyle(int styleId) {\r
383         return styles.get(styleId);\r
384     }\r
385 \r
386     public MappingBase<SheetLineComponent> getMapping() {\r
387         return mapping;\r
388     }\r
389 \r
390     @Override\r
391     public Optional<SpreadsheetElement> getParent() {\r
392         return Optional.empty();\r
393     }\r
394 \r
395     @Override\r
396     public Collection<SpreadsheetElement> getSpreadsheetChildren() {\r
397         return children.values();\r
398     }\r
399 \r
400     @Override\r
401     public void remove(SpreadsheetElement child) {\r
402         // TODO Auto-generated method stub\r
403         \r
404     }\r
405 \r
406         public void setIterationEnabled(boolean value) {\r
407                 this.iterationEnabled = value;\r
408         }\r
409         \r
410         public boolean isIterationEnabled() {\r
411                 return iterationEnabled;\r
412         }\r
413 \r
414 }\r