Initial model implementation
This commit is contained in:
parent
7879178b1a
commit
4b47ea8d59
7 changed files with 360 additions and 0 deletions
22
app/src/main/java/xl/model/BombCell.java
Normal file
22
app/src/main/java/xl/model/BombCell.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
app/src/main/java/xl/model/Cell.java
Normal file
11
app/src/main/java/xl/model/Cell.java
Normal 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();
|
||||||
|
}
|
21
app/src/main/java/xl/model/CommentCell.java
Normal file
21
app/src/main/java/xl/model/CommentCell.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
26
app/src/main/java/xl/model/ExpressionCell.java
Normal file
26
app/src/main/java/xl/model/ExpressionCell.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
41
app/src/main/java/xl/model/XLBufferedReader.java
Normal file
41
app/src/main/java/xl/model/XLBufferedReader.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
208
app/src/main/java/xl/model/XLModel.java
Normal file
208
app/src/main/java/xl/model/XLModel.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
app/src/main/java/xl/model/XLPrintStream.java
Normal file
31
app/src/main/java/xl/model/XLPrintStream.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue