+package org.simantics.spreadsheet.solver;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.binding.Binding;
+import org.simantics.databoard.binding.mutable.Variant;
+import org.simantics.simulator.toolkit.StandardNodeManagerSupport;
+import org.simantics.simulator.variable.exceptions.NodeManagerException;
+import org.simantics.spreadsheet.ExternalRef;
+import org.simantics.spreadsheet.SpreadsheetCellStyle;
+import org.simantics.spreadsheet.SpreadsheetVisitor;
+import org.simantics.spreadsheet.solver.formula.SpreadsheetEvaluationEnvironment;
+import org.simantics.spreadsheet.synchronization.LineNodeUpdater;
+import org.simantics.spreadsheet.synchronization.LineUpdater;
+import org.simantics.spreadsheet.synchronization.NullUpdater;
+import org.simantics.spreadsheet.synchronization.StyleUpdater;
+import org.simantics.structural.synchronization.base.ModuleUpdaterBase;
+import org.simantics.structural.synchronization.base.ModuleUpdaterFactoryBase;
+import org.simantics.structural.synchronization.base.SolverNameUtil;
+import org.simantics.structural.synchronization.utils.ComponentFactory;
+import org.simantics.structural.synchronization.utils.MappingBase;
+import org.simantics.structural.synchronization.utils.Solver;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import it.unimi.dsi.fastutil.longs.AbstractLongList;
+import it.unimi.dsi.fastutil.longs.AbstractLongSet;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.LongArraySet;
+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
+
+@SuppressWarnings("rawtypes")
+public class SpreadsheetBook implements StandardNodeManagerSupport<SheetNode>, SpreadsheetElement<SpreadsheetElement, SpreadsheetElement>, Serializable, SheetNode<SpreadsheetEngine, SheetNode>, Solver, SolverNameUtil, ComponentFactory<SheetLineComponent>, ModuleUpdaterFactoryBase<SheetLineComponent> {
+
+ private static final long serialVersionUID = 7417208688311691396L;
+
+ public Serializable NotAvailableError = new Serializable() {
+ private static final long serialVersionUID = 2535371785498129460L;
+ };
+
+ public Long2ObjectOpenHashMap<AbstractLongSet> referenceMap = new Long2ObjectOpenHashMap<>();
+
+ private Int2ObjectArrayMap<SpreadsheetStyle> styles = new Int2ObjectArrayMap<>();
+ public ArrayList<SpreadsheetEngine> sheets = new ArrayList<SpreadsheetEngine>();
+
+ private SpreadsheetMapping mapping;
+
+ String context;
+
+ private int idCounter = 1;
+
+ private transient boolean disposed = false;
+
+ public SpreadsheetBook(String context) {
+ getNewId(this);
+ mapping = new SpreadsheetMapping(new SheetLineComponent(""));
+ this.context = context;
+ }
+
+ public Map<Integer, SpreadsheetElement> children = new HashMap<>();
+
+ private boolean iterationEnabled;
+
+ public int getNewId(SpreadsheetElement element) {
+ int result = idCounter++;
+ children.put(result, element);
+ return result;
+ }
+
+ public long getEngineIndex(SpreadsheetEngine engine) {
+ for(int i=0;i<sheets.size();i++)
+ if(sheets.get(i) == engine) return i;
+ throw new IllegalStateException("Did not find sheet " + engine.getName());
+ }
+
+ public void registerReferences(long cell, AbstractLongList refs) {
+ for(int i=0;i<refs.size();i++) {
+ long key = refs.getLong(i);
+ AbstractLongSet set = referenceMap.get(key);
+ if(set == null) {
+ set = new LongArraySet();
+ referenceMap.put(key, set);
+ }
+ if(set.size() == 5) {
+ AbstractLongSet newSet = new LongLinkedOpenHashSet();
+ newSet.addAll(set);
+ set = newSet;
+ referenceMap.put(key, set);
+ }
+ set.add(cell);
+ }
+ }
+
+ @Override
+ public Binding getEngineBinding(SheetNode node) throws NodeManagerException {
+ Object value = getEngineValue(node);
+ if(value instanceof Variant) return Bindings.VARIANT;
+ if(value instanceof String) return Bindings.STRING;
+ if(value instanceof Boolean) return Bindings.BOOLEAN;
+ else return Bindings.getBindingUnchecked(value.getClass());
+ }
+
+ @Override
+ public Object getEngineValue(SheetNode node) {
+ if(node instanceof SpreadsheetCellContent) {
+ try {
+ SpreadsheetCellContent scc = (SpreadsheetCellContent)node;
+ Object content = scc.cell.evaluate(SpreadsheetEvaluationEnvironment.getInstance(this));
+ if(content == null) return Variant.ofInstance("");
+ if(content instanceof Variant) return content;
+ if(content instanceof ExternalRefData) return ((ExternalRefData)content).getContent();
+ else return Variant.ofInstance(content);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ return Variant.ofInstance(t.toString());
+ }
+ } else if (node instanceof SpreadsheetCellContentExpression) {
+ SpreadsheetCellContentExpression scce = (SpreadsheetCellContentExpression)node;
+ if (scce.cell.getContent() instanceof SpreadsheetFormula) {
+ SpreadsheetFormula formula = (SpreadsheetFormula)scce.cell.getContent();
+ return formula.expression;
+ } else if (scce.cell.getContent() instanceof SpreadsheetSCLConstant) {
+ SpreadsheetSCLConstant sclConstant = (SpreadsheetSCLConstant) scce.cell.getContent();
+ return "=" + sclConstant.getExpression();
+ } else {
+ System.out.println("Broken SpreadsheetCellContentExpression possibly due to overwriting an existing expression with a constant or something else (current content is " + scce.cell.getContent() + ")");
+ if (scce.cell.getContent() instanceof Variant) {
+ return scce.cell.getContent();
+ } else {
+ return Variant.ofInstance(scce.cell.getContent());
+ }
+ }
+ } else if (node instanceof SpreadsheetTypeNode) {
+ SpreadsheetTypeNode stn = (SpreadsheetTypeNode)node;
+ return stn.uri;
+ } else if (node instanceof SpreadsheetCellStyle) {
+ int styleId = ((SpreadsheetCellStyle) node).cell.style;
+ SpreadsheetStyle style = getStyle(styleId);
+ if (style == null) {
+ style = SpreadsheetStyle.empty();
+ if (styleId != style.getStyleId())
+ new Exception("different style ids!" + styleId + " " + style.getStyleId()).printStackTrace();
+ addStyle(style);
+ }
+ return style;
+ } else if (node instanceof SpreadsheetCellEditable) {
+ boolean editable = ((SpreadsheetCellEditable) node).editable();
+ return editable;
+ }
+ return null;
+ }
+
+ @Override
+ public void setEngineValue(SheetNode node, Object value) {
+
+ SpreadsheetCellContent scc = (SpreadsheetCellContent)node;
+
+ Object content = scc.cell.evaluate(SpreadsheetEvaluationEnvironment.getInstance(this));
+ System.err.println("content2: " + content);
+ if (content instanceof Variant) {
+ scc.cell.setContent(value);
+ } else if (content instanceof ExternalRefData) {
+ ExternalRefData erd = (ExternalRefData)content;
+ erd.getRef().modify(context, (Variant)value);
+ } else {
+ throw new IllegalStateException("Unable to set cell value");
+ }
+
+ }
+
+ @Override
+ public String getName(SheetNode node) {
+ return node.getName();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Map<String, SheetNode> getChildren(SheetNode node) {
+ return node.getChildren();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Map<String, SheetNode> getProperties(SheetNode node) {
+ return node.getProperties();
+ }
+
+ @Override
+ public String getName() {
+ return "";
+ }
+
+ @Override
+ public Map<String, SpreadsheetEngine> getChildren() {
+ Map<String,SpreadsheetEngine> result = new HashMap<String,SpreadsheetEngine>();
+ for(SpreadsheetEngine engine : sheets)
+ result.put(engine.getName(), engine);
+ return result;
+ }
+
+ @Override
+ public Map<String, SheetNode> getProperties() {
+ return Collections.emptyMap();
+ }
+
+ public SpreadsheetCell get(String sheet, int row, int column) {
+ SpreadsheetEngine engine = getEngine(sheet);
+ if(engine == null) return null;
+ SpreadsheetLine line = engine.getLine(row);
+ if(line == null) return null;
+ if(line.cells.size() <= column) return null;
+ return line.cells.get(column);
+ }
+
+ public SpreadsheetCell get(SpreadsheetEngine engine, int row, int column) {
+ SpreadsheetLine line = engine.getLine(row);
+ if(line == null) return null;
+ if(line.cells.size() <= column) return null;
+ return line.cells.get(column);
+ }
+
+ public SpreadsheetEngine getEngine(String sheet) {
+ for(SpreadsheetEngine engine : sheets)
+ if(sheet.equals(engine.getName())) return engine;
+ return null;
+ }
+
+ @Override
+ public ModuleUpdaterBase<SheetLineComponent> createUpdater(String id) throws Exception {
+ if("http://www.simantics.org/Spreadsheet-1.2/Line".equals(id))
+ return new LineUpdater(id);
+ else if("http://www.simantics.org/Spreadsheet-1.2/LineNode".equals(id))
+ return new LineNodeUpdater(id);
+ else if ("http://www.simantics.org/Spreadsheet-1.2/Style".equals(id))
+ return new StyleUpdater(id);
+ else if("http://www.simantics.org/Spreadsheet-1.2/Lines".equals(id))
+ return new NullUpdater(id);
+ else if("http://www.simantics.org/Spreadsheet-1.2/Spreadsheet".equals(id))
+ return new NullUpdater(id);
+ else if("http://www.simantics.org/Spreadsheet-1.2/Book".equals(id))
+ return new NullUpdater(id);
+ else
+ throw new IllegalStateException("createUpdater " + id);
+ }
+
+ @Override
+ public SheetLineComponent create(String uid) {
+ return new SheetLineComponent(uid);
+ }
+
+ @Override
+ public String getFreshName(String parentName, String name) {
+ return parentName + "/" + name;
+ }
+
+ @Override
+ public String ensureNameIsVariationOf(String parentName, int id, String name) {
+ if (parentName.isEmpty())
+ return name;
+ return parentName + "/" + name;
+ }
+
+ final static int COMP_ROOT_POS = "COMP_ROOT/".length();
+
+ @Override
+ public int getId(String name) {
+
+ if("COMP_ROOT".equals(name)) return 1;
+
+ String path = name.substring(COMP_ROOT_POS);
+ String[] parts = path.split("/");
+ Object o = resolve(parts, 0);
+ if(o instanceof SpreadsheetLines) {
+ return ((SpreadsheetLines)o).getId();
+ } else if(o instanceof SpreadsheetLine) {
+ return ((SpreadsheetLine)o).getId();
+ } else if(o instanceof SpreadsheetEngine) {
+ return ((SpreadsheetEngine)o).getId();
+ } else if (o instanceof SpreadsheetStyle) {
+ return ((SpreadsheetStyle) o).getId();
+ }
+
+ throw new IllegalStateException("Resolved object for parts " + Arrays.toString(parts) + " is not the right type! It is " + o);
+
+ }
+
+ Object resolve(String[] parts, int index) {
+ String part = parts[index];
+ if (part.startsWith("Style")) {
+ for (SpreadsheetStyle style : styles.values()) {
+ if (style.name.equals(part)) {
+ return style;
+ }
+ }
+ }
+ SpreadsheetEngine engine = getEngine(part);
+ if(engine == null) return 0;
+ if(index == parts.length-1) return engine;
+ else return engine.resolve(parts, index+1);
+ }
+
+ @Override
+ public String getName(int id) {
+ if(id == -2) return "http://www.simantics.org/Spreadsheet-1.2/Book";
+ else if(id == -3) return "http://www.simantics.org/Spreadsheet-1.2/Spreadsheet";
+ else if(id == -4) return "http://www.simantics.org/Spreadsheet-1.2/Lines";
+ else if(id == -5)
+ return "http://www.simantics.org/Spreadsheet-1.2/LineNode";
+ else if (id == -6)
+ return "http://www.simantics.org/Spreadsheet-1.2/Line";
+ else if(id == -7)
+ return "http://www.simantics.org/Spreadsheet-1.2/Style";
+ else return "" + id;
+ }
+
+ @Override
+ public int getModuleType(int id) {
+ Serializable s = children.get(id);
+ if(s instanceof SpreadsheetBook) return -2;
+ else if(s instanceof SpreadsheetEngine) return -3;
+ else if(s instanceof SpreadsheetLines) {
+ if("Lines".equals(((SpreadsheetLines) s).getName()))
+ return -4;
+ else
+ return -5;
+ }
+ else if(s instanceof SpreadsheetLine)
+ return -6;
+ else if (s instanceof SpreadsheetStyle)
+ return -7;
+ else throw new IllegalStateException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void remove(int id) {
+
+ SpreadsheetElement child = children.get(id);
+ Optional<SpreadsheetElement> parent = child.getParent();
+
+ if (parent.isPresent()) {
+ parent.get().remove(child);
+ children.remove(id);
+ }
+ }
+
+ @Override
+ public void addSubprocess(String name, String subprocessType) {
+ ensureSubprocess(name);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T ensureSubprocess(String name) {
+ String[] parts = name.split("/");
+ if(parts.length == 2) {
+ SpreadsheetEngine engine = getEngine(parts[1]);
+ if(engine == null) {
+ engine = new SpreadsheetEngine(this, parts[1]);
+ sheets.add(engine);
+ }
+ return (T)engine;
+ } else if (parts.length > 2) {
+ SpreadsheetEngine engine = getEngine(parts[1]);
+ return (T)engine.ensureSubprocess(parts, 2);
+ }
+ throw new IllegalStateException();
+ }
+
+
+ @Override
+ public void includeSubprocess(String parentName, String subprocessName) {
+ // Nop
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T getConcreteSolver() {
+ return (T)this;
+ }
+
+ @Override
+ public void accept(SpreadsheetVisitor v) {
+ v.visit(this);
+ }
+
+ SpreadsheetCell cellByReferenceKey(long ref) {
+ long sheet = ref >> 40;
+ long row = (ref >> 20) & 0xFFFFF;
+ long col = (ref) & 0xFFFFF;
+ return get(sheets.get((int)sheet), (int)row, (int)col);
+ }
+
+ //Recursively find all SpreadsheetCells, invalidate them and return them all as a set
+ public Set<SpreadsheetCell> invalidate(SpreadsheetCell cell) {
+ Set<SpreadsheetCell> result = new HashSet<>();
+ result.add(cell);
+ cell.invalidate();
+ long refKey = cell.makeReferenceKey();
+ AbstractLongSet refs = referenceMap.remove(refKey);
+ if(refs == null) return result;
+ for(long ref : refs) {
+ SpreadsheetCell referer = cellByReferenceKey(ref);
+ result.addAll(invalidate(referer));
+ }
+ return result;
+ }
+
+ @Deprecated
+ public List<SpreadsheetCell> invalidateShallow(SpreadsheetCell cell) {
+ ArrayList<SpreadsheetCell> result = new ArrayList<>();
+ result.add(cell);
+ cell.invalidate();
+ long refKey = cell.makeReferenceKey();
+ AbstractLongSet refs = referenceMap.remove(refKey);
+ if(refs == null) return result;
+ for(long ref : refs) {
+ SpreadsheetCell referer = cellByReferenceKey(ref);
+ invalidate(referer);
+ result.add(referer);
+ }
+ return result;
+ }
+
+ public void addStyle(SpreadsheetStyle style) {
+ if (style.name == null) {
+ new Exception("Trying to add style to book without name!!").printStackTrace();
+ return;
+ }
+ style.setSynchronizationId(getNewId(style));
+ styles.put(style.getStyleId(), style);
+ }
+
+ public SpreadsheetStyle getStyle(int styleId) {
+ return styles.get(styleId);
+ }
+
+ public MappingBase<SheetLineComponent> getMapping() {
+ return mapping;
+ }
+
+ @Override
+ public Optional<SpreadsheetElement> getParent() {
+ return Optional.empty();
+ }
+
+ @Override
+ public Collection<SpreadsheetElement> getSpreadsheetChildren() {
+ return children.values();
+ }
+
+ @Override
+ public void remove(SpreadsheetElement child) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setIterationEnabled(boolean value) {
+ this.iterationEnabled = value;
+ }
+
+ public boolean isIterationEnabled() {
+ return iterationEnabled;
+ }
+
+ static Variant DEFAULT_VALUE = Variant.ofInstance("Pending external reference");
+
+ private Map<ExternalRef,ExternalRefData> externalRefMap = new HashMap<>();
+
+ void registerListening(long referenceKey, ExternalRef ref) {
+ ExternalRefData data = externalRefMap.get(ref);
+ if(data == null) {
+ data = new ExternalRefData(this, referenceKey, ref);
+ externalRefMap.put(ref, data);
+ } else {
+ // Already registered
+ }
+ }
+
+ ExternalRefData getExternalRefValue(long referenceKey, ExternalRef ref) {
+ ExternalRefData data = externalRefMap.get(ref);
+ if(data == null) {
+ registerListening(referenceKey, ref);
+ return new ExternalRefData(this, referenceKey, new ExternalRefConstant(DEFAULT_VALUE));
+ }
+ return data;
+ }
+
+ public boolean isDisposed() {
+ return disposed;
+ }
+
+ public static interface SpreadsheetBookListener {
+ void cellsChanged(Collection<SpreadsheetCell> cells);
+ }
+
+ public void registerListener(SpreadsheetBookListener listener) {
+ if(listeners == null)
+ listeners = new ArrayList<>();
+ listeners.add(listener);
+ }
+
+ private transient ArrayList<SpreadsheetBookListener> listeners = new ArrayList<>();
+
+ public void fireChanges(Collection<SpreadsheetCell> cells) {
+ for(SpreadsheetBookListener listener : listeners)
+ listener.cellsChanged(cells);
+ }
+
+}