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 map; private String status; private String currentAddress; // The observers that are registered to be notified private List observers = new ArrayList<>(); public XLModel() { status = ""; currentAddress = "A1"; map = new java.util.HashMap(); } public String getCurrentAddress() { return currentAddress; } public void setCurrentAddress(String currentAddress) { this.currentAddress = currentAddress; } public Optional 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 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); } } }