208 lines
5.5 KiB
Java
208 lines
5.5 KiB
Java
package xl.model;
|
|
|
|
import java.io.IOException;
|
|
import java.util.*;
|
|
|
|
import xl.expr.Environment;
|
|
import xl.expr.ExprParser;
|
|
import xl.util.XLException;
|
|
|
|
public class XLModel extends Observable implements Environment {
|
|
private Map<String, Cell> map;
|
|
private String status;
|
|
private String currentAddress;
|
|
|
|
// The observers that are registered to be notified
|
|
private List<Observer> observers = new ArrayList<>();
|
|
|
|
public XLModel() {
|
|
status = "";
|
|
currentAddress = "A1";
|
|
map = new java.util.HashMap<String, Cell>();
|
|
}
|
|
|
|
public String getCurrentAddress() {
|
|
return currentAddress;
|
|
}
|
|
|
|
public void setCurrentAddress(String currentAddress) {
|
|
this.currentAddress = currentAddress;
|
|
}
|
|
|
|
public Optional<Cell> getCell(String address) {
|
|
return Optional.ofNullable(map.get(address));
|
|
}
|
|
|
|
public boolean remove(String address) {
|
|
// Save the existing cell at the specified address
|
|
Cell oldCell = map.get(address);
|
|
|
|
if (oldCell != null) {
|
|
map.remove(address);
|
|
|
|
// If the output is invalid, restore the old cell
|
|
if (hasWrongOutputs()) {
|
|
map.put(address, oldCell);
|
|
status = "Cannot remove cell";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void add(String address, String text) {
|
|
try {
|
|
createCell(address, text);
|
|
} catch (XLException e) {
|
|
status = "Error creating cell";
|
|
return;
|
|
}
|
|
|
|
// Tell observers that the model has changed
|
|
setChanged();
|
|
notifyObservers();
|
|
}
|
|
|
|
public String getStatus() {
|
|
return status;
|
|
}
|
|
|
|
public void clearStatus() {
|
|
status = "";
|
|
}
|
|
|
|
public void clearCell() {
|
|
if (remove(currentAddress)) {
|
|
notifyObservers();
|
|
}
|
|
}
|
|
|
|
public void clearAll() {
|
|
map.clear();
|
|
clearStatus();
|
|
setChanged();
|
|
notifyObservers();
|
|
}
|
|
|
|
public void load(String path) {
|
|
try (XLBufferedReader reader = new XLBufferedReader(path)) {
|
|
map.clear();
|
|
reader.load(map);
|
|
status = "";
|
|
} catch (IOException e) {
|
|
status = "Error loading file";
|
|
}
|
|
setChanged();
|
|
notifyObservers();
|
|
}
|
|
|
|
public void save(String path) {
|
|
try (XLPrintStream xlPrintStream = new XLPrintStream(path)) {
|
|
xlPrintStream.save(map.entrySet());
|
|
} catch (IOException e) {
|
|
status = "Error saving file";
|
|
}
|
|
}
|
|
|
|
public String getAddressValue(String address) {
|
|
if (map.containsKey(address)) {
|
|
Cell cell = map.get(address);
|
|
if (cell instanceof CommentCell) {
|
|
return cell.displayValue();
|
|
}
|
|
return Double.toString(cell.cellValue(this));
|
|
}
|
|
return "";
|
|
}
|
|
|
|
public String getAddressContent(String address) {
|
|
if (map.containsKey(address)) {
|
|
Cell cell = map.get(address);
|
|
if (cell instanceof CommentCell) {
|
|
return "#" + map.get(address).displayValue();
|
|
}
|
|
return map.get(address).displayValue();
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
public boolean containsKey(String address) {
|
|
return map.containsKey(address);
|
|
}
|
|
|
|
private void createCell(String address, String text) {
|
|
if (text.length() > 0 && text.charAt(0) == '#') {
|
|
map.put(address, new CommentCell(text.substring(1)));
|
|
} else {
|
|
ExprParser parser = new ExprParser();
|
|
try {
|
|
Cell newCell = new ExpressionCell(parser.build(text));
|
|
Cell oldCell = map.get(address);
|
|
BombCell bomb = new BombCell();
|
|
map.put(address, bomb);
|
|
if (hasCircularDependancy(newCell) || hasWrongOutputs()) {
|
|
if (oldCell != null) {
|
|
map.put(address, oldCell);
|
|
} else {
|
|
map.remove(address);
|
|
}
|
|
}
|
|
} catch (IOException e) {
|
|
throw XLException.ERROR_CREATECELL;
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean hasCircularDependancy(Cell cell) {
|
|
try {
|
|
cell.cellValue(this);
|
|
map.put(currentAddress, cell);
|
|
status = "";
|
|
} catch (XLException e) {
|
|
status = e.getMessage();
|
|
return true;
|
|
} catch (NullPointerException e) {
|
|
status = e.toString();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean hasWrongOutputs() {
|
|
for (Map.Entry<String, Cell> entry : map.entrySet()) {
|
|
try {
|
|
entry.getValue().cellValue(this);
|
|
} catch (Exception e) {
|
|
status = "cannot update";
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public double value(String name) {
|
|
// Check if the cell is referencing itself
|
|
if (currentAddress.equals(name)) {
|
|
throw XLException.ERROR_RECURSIVE;
|
|
}
|
|
// Check if the cell is empty
|
|
if (map.containsKey(name)) {
|
|
return map.get(name).cellValue(this);
|
|
} else {
|
|
throw XLException.ERROR_EMPTY_REF;
|
|
}
|
|
}
|
|
|
|
public void addObserver(Observer o) {
|
|
observers.add(o);
|
|
}
|
|
|
|
public void notifyObservers() {
|
|
for (Observer o : observers) {
|
|
o.update(this, null);
|
|
}
|
|
}
|
|
}
|