diff --git a/app/src/main/java/xl/model/BombCell.java b/app/src/main/java/xl/model/BombCell.java new file mode 100644 index 0000000..748a43f --- /dev/null +++ b/app/src/main/java/xl/model/BombCell.java @@ -0,0 +1,22 @@ +package xl.model; + +import xl.expr.Environment; +import xl.util.XLException; + +/** + * A class representing a cell with a bomb value. + * + * This is essentially a placeholder for an invalid cell reference. + */ +public class BombCell implements Cell { + public BombCell() {} + + public double cellValue(Environment e) { + throw XLException.RECURSIVECELL_ERROR; + } + + public String displayValue() { + throw XLException.RECURSIVECELL_ERROR; + } + +} diff --git a/app/src/main/java/xl/model/Cell.java b/app/src/main/java/xl/model/Cell.java new file mode 100644 index 0000000..a65781e --- /dev/null +++ b/app/src/main/java/xl/model/Cell.java @@ -0,0 +1,11 @@ +package xl.model; + +import xl.expr.Environment; + +/** + * Interface for a cell in the spreadsheet. + */ +public interface Cell { + public double cellValue(Environment e); + public String displayValue(); +} diff --git a/app/src/main/java/xl/model/CommentCell.java b/app/src/main/java/xl/model/CommentCell.java new file mode 100644 index 0000000..b9a9550 --- /dev/null +++ b/app/src/main/java/xl/model/CommentCell.java @@ -0,0 +1,21 @@ +package xl.model; + +import xl.expr.Environment; +/** + * A class representing a cell with a comment value. + */ +public class CommentCell implements Cell { + private String commentText; + + public CommentCell(String commentText) { + this.commentText = commentText; + } + + public double cellValue(Environment e) { + return 0; + } + + public String displayValue() { + return commentText; + } +} diff --git a/app/src/main/java/xl/model/ExpressionCell.java b/app/src/main/java/xl/model/ExpressionCell.java new file mode 100644 index 0000000..b720159 --- /dev/null +++ b/app/src/main/java/xl/model/ExpressionCell.java @@ -0,0 +1,26 @@ +package xl.model; + +import xl.expr.Environment; +import xl.expr.Expr; + +/** + * A class representing a cell with an expression value. + */ +public class ExpressionCell implements Cell { + private Expr expr; + + public ExpressionCell(Expr expr) { + this.expr = expr; + } + + public double cellValue(Environment env) { + return expr.value(env); + } + + public String displayValue() { + return expr.toString(); + } + +} + + diff --git a/app/src/main/java/xl/model/XLBufferedReader.java b/app/src/main/java/xl/model/XLBufferedReader.java new file mode 100644 index 0000000..63a6750 --- /dev/null +++ b/app/src/main/java/xl/model/XLBufferedReader.java @@ -0,0 +1,41 @@ +package xl.model; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.util.Map; + +import xl.expr.Expr; +import xl.expr.ExprParser; +import xl.util.XLException; + +/** + * Specialized BufferedReader for reading XL files. + */ +public class XLBufferedReader extends BufferedReader { + + public XLBufferedReader(String name) throws FileNotFoundException { + super(new FileReader(name)); + } + + public void load(Map map) { + try { + while (ready()) { + String string = readLine(); + int i = string.indexOf('='); + String address = string.substring(0, i); + String content = string.substring(i + 1); + + if(content.startsWith("#")){ + map.put(address, new CommentCell(content.substring(1))); + } else { + ExprParser parser = new ExprParser(); + Expr expression = parser.build(content); + map.put(address, new ExpressionCell(expression)); + } + } + } catch (Exception e) { + throw new XLException(e.getMessage()); + } + } +} diff --git a/app/src/main/java/xl/model/XLModel.java b/app/src/main/java/xl/model/XLModel.java new file mode 100644 index 0000000..d7bd4a7 --- /dev/null +++ b/app/src/main/java/xl/model/XLModel.java @@ -0,0 +1,208 @@ +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.CREATECELL_ERROR; + } + } + } + + 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.RECURSIVECELL_ERROR; + } + // Check if the cell is empty + if (map.containsKey(name)) { + return map.get(name).cellValue(this); + } else { + throw XLException.EMPTY_ERROR; + } + } + + public void addObserver(Observer o) { + observers.add(o); + } + + public void notifyObservers() { + for (Observer o : observers) { + o.update(this, null); + } + } +} diff --git a/app/src/main/java/xl/model/XLPrintStream.java b/app/src/main/java/xl/model/XLPrintStream.java new file mode 100644 index 0000000..3bbe6ca --- /dev/null +++ b/app/src/main/java/xl/model/XLPrintStream.java @@ -0,0 +1,31 @@ +package xl.model; + +import java.io.FileNotFoundException; +import java.io.PrintStream; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Specialized PrintStream for writing XL files. + */ +public class XLPrintStream extends PrintStream { + + public XLPrintStream(String fileName) throws FileNotFoundException { + super(fileName); + } + + public void save(Set> set) { + for (Entry entry : set) { + print(entry.getKey()); + print('='); + Cell cell = entry.getValue(); + if (cell instanceof CommentCell) { + println("#" + cell.displayValue()); + } else { + println(cell.displayValue()); + } + } + flush(); + close(); + } +}