--- /dev/null
+package org.simantics.spreadsheet.solver;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.simantics.databoard.binding.mutable.Variant;
+import org.simantics.spreadsheet.ExternalRef;
+import org.simantics.spreadsheet.SpreadsheetCellStyle;
+import org.simantics.spreadsheet.SpreadsheetVisitor;
+import org.simantics.spreadsheet.Spreadsheets;
+import org.simantics.spreadsheet.solver.formula.CellValueVisitor;
+import org.simantics.spreadsheet.solver.formula.FormulaError2;
+import org.simantics.spreadsheet.solver.formula.SpreadsheetEvaluationEnvironment;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstValue;
+
+@SuppressWarnings("rawtypes")
+public class SpreadsheetCell implements SpreadsheetElement, SheetNode {
+
+ private static final long serialVersionUID = 6616793596542239339L;
+
+ public static SpreadsheetCell EMPTY;
+
+ static {
+ EMPTY = new SpreadsheetCell(null, -1);
+ EMPTY.setContent("");
+ EMPTY.setStyle(SpreadsheetStyle.empty().getStyleId());
+ }
+
+ private boolean inProgress = false;
+ private int iterations = 0;
+
+ final private SpreadsheetLine line;
+ final private int column;
+ int style;
+ private Object content;
+ final private Map<String, SheetNode> properties;
+
+ public SpreadsheetCell(SpreadsheetLine line, int column) {
+ this.properties = createProperties();
+ this.line = line;
+ this.column = column;
+ }
+
+ //All SpreadsheetCells have these properties - create them when object is created
+ private Map<String, SheetNode> createProperties() {
+ Map<String, SheetNode> p = new HashMap<>();
+ p.put("typeURI", new SpreadsheetTypeNode(Spreadsheets.CELL_TYPE_URI));
+ p.put("content", new SpreadsheetCellContent(this));
+ p.put("style", new SpreadsheetCellStyle(this));
+ p.put("editable", new SpreadsheetCellEditable(this));
+ return p;
+ }
+
+ public boolean hasExpression() {
+ return content instanceof SpreadsheetFormula || content instanceof SpreadsheetSCLConstant;
+ }
+
+ public void setContent(Object newContent) {
+ this.content = newContent;
+ }
+
+ public int getColumn() {
+ return column;
+ }
+
+ @Override
+ public String getName() {
+ return Spreadsheets.cellName(line.row, column);
+ }
+
+ @Override
+ public Map<?, ?> getChildren() {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public Map<String, SheetNode> getProperties() {
+ return properties;
+ }
+
+ public SpreadsheetBook getBook() {
+ return line.getEngine().getBook();
+ }
+
+ public SpreadsheetEngine getEngine() {
+ return line.getEngine();
+ }
+
+ public <T> T evaluate(SpreadsheetEvaluationEnvironment env) {
+ return evaluate(env, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T evaluate(SpreadsheetEvaluationEnvironment env, CellValueVisitor caller) {
+ if(caller != null)
+ caller.addReference(makeReferenceKey());
+ if(content instanceof SpreadsheetFormula) {
+ SpreadsheetFormula f = (SpreadsheetFormula)content;
+ if(f.result == null) {
+ CellValueVisitor visitor = new CellValueVisitor(env, this);
+ AstValue value = ((SpreadsheetFormula)content).value;
+ if(this.inProgress == true) this.iterations++;
+
+ if(!env.getBook().isIterationEnabled()){
+ if(this.inProgress == false){
+ this.inProgress = true;
+ f.result = value.accept(visitor);
+ }
+ else f.result = FormulaError2.CIRCULAR_REF.getString();
+ }
+ else if(this.iterations<env.iterationLimit){
+ this.inProgress = true;
+ f.result = value.accept(visitor);
+ }
+ else {
+ if(f.result==null)
+ f.result = 0.0;
+ }
+ env.getBook().registerReferences(makeReferenceKey(), visitor.getReferences());
+ }
+ this.inProgress = false;
+ this.iterations = 0;
+ return (T)f.result;
+ } else if (content instanceof SpreadsheetSCLConstant) {
+ SpreadsheetSCLConstant sclConstant = (SpreadsheetSCLConstant) content;
+ Object c = sclConstant.getContent();
+ if(c instanceof Variant) {
+ Variant v = (Variant)c;
+ return (T) c;
+ } else if (c instanceof ExternalRef) {
+ ExternalRefData erd = env.getBook().getExternalRefValue(makeReferenceKey(), (ExternalRef)c);
+ return (T)erd;
+ } else {
+ throw new IllegalStateException("Unsupported content " + c);
+ }
+ } else {
+ this.inProgress = false;
+ return (T)content;
+ }
+ }
+
+ public long makeReferenceKey() {
+ SpreadsheetBook book = getBook();
+ SpreadsheetEngine engine = getEngine();
+ long engineIndex = book.getEngineIndex(engine);
+ long row = line.row;
+ long col = column;
+ return (engineIndex << 40) + (row << 20) + col;
+ }
+
+ public void invalidate() {
+ getEngine().rangeCache = null;
+ if(content instanceof SpreadsheetFormula) {
+ SpreadsheetFormula f = (SpreadsheetFormula)content;
+ f.result = null;
+ }
+ }
+
+ @Override
+ public void accept(SpreadsheetVisitor v) {
+ v.visit(this);
+ }
+
+ @Override
+ public Optional<SpreadsheetElement> getParent() {
+ return Optional.of(line);
+ }
+
+ @Override
+ public List<SpreadsheetElement> getSpreadsheetChildren() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void remove(SpreadsheetElement child) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + column;
+ result = prime * result + ((line == null) ? 0 : line.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SpreadsheetCell other = (SpreadsheetCell) obj;
+ if (column != other.column)
+ return false;
+ if (line == null) {
+ if (other.line != null)
+ return false;
+ } else if (!line.equals(other.line))
+ return false;
+ return true;
+ }
+
+ public void setStyle(int styleId) {
+ this.style = styleId;
+ }
+
+ public int getStyle() {
+ return style;
+ }
+
+ public Object getContent() {
+ return content;
+ }
+
+ public static SpreadsheetCell empty(SpreadsheetLine line, int column) {
+ SpreadsheetCell cell = new SpreadsheetCell(line, column);
+ cell.setContent("");
+ cell.setStyle(SpreadsheetStyle.empty().getStyleId());
+ return cell;
+ }
+
+}