Initial model implementation

This commit is contained in:
Imbus 2024-06-03 19:21:42 +02:00
parent 7879178b1a
commit 4b47ea8d59
7 changed files with 360 additions and 0 deletions

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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<String, Cell> 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());
}
}
}

View file

@ -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<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.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<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.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);
}
}
}

View file

@ -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<Entry<String, Cell>> set) {
for (Entry<String, Cell> entry : set) {
print(entry.getKey());
print('=');
Cell cell = entry.getValue();
if (cell instanceof CommentCell) {
println("#" + cell.displayValue());
} else {
println(cell.displayValue());
}
}
flush();
close();
}
}