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