001: /*
002: * Copyright (c) 2005 Einar Pehrson <einar@pehrson.nu>.
003: *
004: * This file is part of
005: * CleanSheets - a spreadsheet application for the Java platform.
006: *
007: * CleanSheets is free software; you can redistribute it and/or modify
008: * it under the terms of the GNU General Public License as published by
009: * the Free Software Foundation; either version 2 of the License, or
010: * (at your option) any later version.
011: *
012: * CleanSheets is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with CleanSheets; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: */
021: package csheets.core;
022:
023: import java.io.IOException;
024: import java.io.ObjectInputStream;
025: import java.io.ObjectOutputStream;
026: import java.util.ArrayList;
027: import java.util.HashMap;
028: import java.util.List;
029: import java.util.Map;
030: import java.util.SortedSet;
031: import java.util.TreeSet;
032:
033: import csheets.core.formula.Formula;
034: import csheets.core.formula.Reference;
035: import csheets.core.formula.compiler.FormulaCompilationException;
036: import csheets.core.formula.compiler.FormulaCompiler;
037: import csheets.core.formula.util.ReferenceTransposer;
038: import csheets.ext.CellExtension;
039: import csheets.ext.Extension;
040: import csheets.ext.ExtensionManager;
041:
042: /**
043: * The implementation of the <code>Cell</code> interface.
044: * @author Einar Pehrson
045: */
046: public class CellImpl implements Cell {
047:
048: /** The unique version identifier used for serialization */
049: private static final long serialVersionUID = 926673794084390673L;
050:
051: /** The spreadsheet to which the cell belongs */
052: private Spreadsheet spreadsheet;
053:
054: /** The address of the cell */
055: private Address address;
056:
057: /** The value of the cell */
058: private Value value = new Value();
059:
060: /** The content of the cell */
061: private String content = "";
062:
063: /** The cell's formula */
064: private Formula formula;
065:
066: /** The cell's precedents */
067: private SortedSet<Cell> precedents = new TreeSet<Cell>();
068:
069: /** The cell's dependents */
070: private SortedSet<Cell> dependents = new TreeSet<Cell>();
071:
072: /** The cell listeners that have been registered on the cell */
073: private transient List<CellListener> listeners = new ArrayList<CellListener>();
074:
075: /** The cell extensions that have been instantiated */
076: private transient Map<String, CellExtension> extensions = new HashMap<String, CellExtension>();
077:
078: /**
079: * Creates a new cell at the given address in the given spreadsheet.
080: * (not intended to be used directly).
081: * @see Spreadsheet#getCell(Address)
082: * @param spreadsheet the spreadsheet
083: * @param address the address of the cell
084: */
085: CellImpl(Spreadsheet spreadsheet, Address address) {
086: this .spreadsheet = spreadsheet;
087: this .address = address;
088: }
089:
090: /**
091: * Creates a new cell at the given address in the given spreadsheet,
092: * initialized with the given content (not intended to be used directly).
093: * @see Spreadsheet#getCell(Address)
094: * @param spreadsheet the spreadsheet
095: * @param address the address of the cell
096: * @param content the content of the cell
097: * @throws ExpressionSyntaxException if an incorrectly formatted formula was entered
098: */
099: CellImpl(Spreadsheet spreadsheet, Address address, String content)
100: throws FormulaCompilationException {
101: this (spreadsheet, address);
102: storeContent(content);
103: reevaluate();
104: }
105:
106: /*
107: * LOCATION
108: */
109:
110: public Spreadsheet getSpreadsheet() {
111: return spreadsheet;
112: }
113:
114: public Address getAddress() {
115: return address;
116: }
117:
118: /*
119: * VALUE
120: */
121:
122: public Value getValue() {
123: return value;
124: }
125:
126: /**
127: * Updates the cell's value, and fires an event if it changed.
128: */
129: private void reevaluate() {
130: Value oldValue = value;
131:
132: // Fetches the new value
133: Value newValue;
134: if (formula != null)
135: try {
136: newValue = formula.evaluate();
137: } catch (IllegalValueTypeException e) {
138: newValue = new Value(e);
139: }
140: else
141: newValue = Value.parseValue(content);
142:
143: // Stores value
144: value = newValue;
145:
146: // Checks for change
147: if (!newValue.equals(oldValue))
148: fireValueChanged();
149: }
150:
151: /**
152: * Notifies all registered listeners that the value of the cell changed.
153: */
154: private void fireValueChanged() {
155: for (CellListener listener : listeners)
156: listener.valueChanged(this );
157: for (CellExtension extension : extensions.values())
158: extension.valueChanged(this );
159:
160: // Notifies dependents of the changed value
161: for (Cell dependent : dependents) {
162: if (dependent instanceof CellImpl)
163: ((CellImpl) dependent).reevaluate();
164: }
165: }
166:
167: /*
168: * CONTENT
169: */
170:
171: public String getContent() {
172: return content;
173: }
174:
175: public Formula getFormula() {
176: return formula;
177: }
178:
179: public void clear() {
180: try {
181: setContent("");
182: } catch (FormulaCompilationException e) {
183: }
184: fireCellCleared();
185: }
186:
187: public void setContent(String content)
188: throws FormulaCompilationException {
189: if (!this .content.equals(content)) {
190: storeContent(content);
191: fireContentChanged();
192: reevaluate();
193: }
194: }
195:
196: /**
197: * Updates the cell's content, and registers dependencies.
198: * @param content the content to store
199: * @throws FormulaCompilationException if an incorrectly formatted formula was entered
200: */
201: private void storeContent(String content)
202: throws FormulaCompilationException {
203: // Parses formula
204: Formula formula = null;
205: if (content.length() > 1)
206: formula = FormulaCompiler.getInstance().compile(this ,
207: content);
208:
209: // Stores content and formula
210: this .content = content;
211: this .formula = formula;
212: updateDependencies();
213: }
214:
215: /**
216: * Updates the cell's dependencies.
217: */
218: private void updateDependencies() {
219: // Deregisters as dependent with each old precedent
220: for (Cell precedent : precedents)
221: ((CellImpl) precedent).removeDependent(this );
222: precedents.clear();
223:
224: if (formula != null)
225: // Registers as dependent with each new precedent
226: for (Reference reference : formula.getReferences())
227: for (Cell precedent : reference.getCells()) {
228: if (!this .equals(precedent)) {
229: precedents.add(precedent);
230: ((CellImpl) precedent).addDependent(this );
231: }
232: }
233: }
234:
235: /**
236: * Notifies all registered listeners that the content of the cell changed.
237: */
238: private void fireContentChanged() {
239: for (CellListener listener : listeners)
240: listener.contentChanged(this );
241: for (CellExtension extension : extensions.values())
242: extension.contentChanged(this );
243: }
244:
245: /**
246: * Notifies all registered listeners that the cell was cleared.
247: */
248: private void fireCellCleared() {
249: for (CellListener listener : listeners)
250: listener.cellCleared(this );
251: for (CellExtension extension : extensions.values())
252: extension.cellCleared(this );
253: }
254:
255: /*
256: * DEPENDENCIES
257: */
258:
259: public SortedSet<Cell> getPrecedents() {
260: return new TreeSet<Cell>(precedents);
261: }
262:
263: public SortedSet<Cell> getDependents() {
264: return new TreeSet<Cell>(dependents);
265: }
266:
267: /**
268: * Adds the given cell as a dependent of this cell, to be notified when its
269: * value changes.
270: * @param cell the dependent to add
271: */
272: private void addDependent(Cell cell) {
273: dependents.add(cell);
274: fireDependentsChanged();
275: }
276:
277: /**
278: * Removes the given cell as a dependent of this cell.
279: * @param cell the dependent to remove
280: */
281: private void removeDependent(Cell cell) {
282: dependents.remove(cell);
283: fireDependentsChanged();
284: }
285:
286: /**
287: * Notifies all registered listeners that the content of the cell changed.
288: */
289: private void fireDependentsChanged() {
290: for (CellListener listener : listeners)
291: listener.dependentsChanged(this );
292: for (CellExtension extension : extensions.values())
293: extension.dependentsChanged(this );
294: }
295:
296: /*
297: * CLIPBOARD
298: */
299:
300: public void copyFrom(Cell source) {
301: // Copies content
302: if (source.getFormula() == null)
303: try {
304: setContent(source.getContent());
305: } catch (FormulaCompilationException e) {
306: }
307: else {
308: // Copies and transposes formula
309: this .formula = new Formula(this , new ReferenceTransposer(
310: getAddress().getColumn()
311: - source.getAddress().getColumn(),
312: getAddress().getRow()
313: - source.getAddress().getRow())
314: .getExpression(source.getFormula().getExpression()));
315: this .content = source.getContent().charAt(0)
316: + formula.toString();
317: updateDependencies();
318: fireContentChanged();
319: reevaluate();
320: }
321: fireCellCopied(source);
322: }
323:
324: public void moveFrom(Cell source) {
325: // Change the address of the source cell
326: // Remove the target cell from the spreadsheet
327: // Flag the target cell as overwritten!
328:
329: // fireCellCopied(source);
330: }
331:
332: /**
333: * Notifies all registered listeners that the cell was copied (or moved).
334: * @param source the cell from which data was copied
335: */
336: private void fireCellCopied(Cell source) {
337: for (CellListener listener : listeners)
338: listener.cellCopied(this , source);
339: for (CellExtension extension : extensions.values())
340: extension.cellCopied(this , source);
341: }
342:
343: /*
344: * EVENT HANDLING
345: */
346:
347: public void addCellListener(CellListener listener) {
348: listeners.add(listener);
349: }
350:
351: public void removeCellListener(CellListener listener) {
352: listeners.remove(listener);
353: }
354:
355: public CellListener[] getCellListeners() {
356: return listeners.toArray(new CellListener[listeners.size()]);
357: }
358:
359: /*
360: * EXTENSIONS
361: */
362:
363: public Cell getExtension(String name) {
364: // Looks for an existing cell extension
365: CellExtension extension = extensions.get(name);
366: if (extension == null) {
367: // Creates a new cell extension
368: Extension x = ExtensionManager.getInstance().getExtension(
369: name);
370: if (x != null) {
371: extension = x.extend(this );
372: if (extension != null)
373: extensions.put(name, extension);
374: }
375: }
376: return extension;
377: }
378:
379: /*
380: * GENERAL
381: */
382:
383: /**
384: * Compares this cell with the specified cell for order,
385: * by comparing their addresses.
386: * @param cell the cell to be compared
387: * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
388: */
389: public int compareTo(Cell cell) {
390: if (spreadsheet != cell.getSpreadsheet())
391: return -1;
392: else
393: return address.compareTo(cell.getAddress());
394: }
395:
396: /**
397: * Returns a string representation of the cell.
398: * @return the cell's content
399: */
400: public String toString() {
401: return address.toString();
402: }
403:
404: /**
405: * Customizes deserialization by recreating the listener list and by catching
406: * exceptions when extensions are not found.
407: * @param stream the object input stream from which the object is to be read
408: * @throws IOException If any of the usual Input/Output related exceptions occur
409: * @throws ClassNotFoundException If the class of a serialized object cannot be found.
410: */
411: private void readObject(ObjectInputStream stream)
412: throws IOException, ClassNotFoundException {
413: stream.defaultReadObject();
414: listeners = new ArrayList<CellListener>();
415:
416: // Reads extensions
417: extensions = new HashMap<String, CellExtension>();
418: int extCount = stream.readInt();
419: for (int i = 0; i < extCount; i++) {
420: try {
421: CellExtension extension = (CellExtension) stream
422: .readObject();
423: extensions.put(extension.getName(), extension);
424: } catch (ClassNotFoundException e) {
425: }
426: }
427: }
428:
429: /**
430: * Customizes serialization by writing extensions separately.
431: * @param stream the object output stream to which the object is to be written
432: * @throws IOException If any of the usual Input/Output related exceptions occur
433: */
434: private void writeObject(ObjectOutputStream stream)
435: throws IOException {
436: stream.defaultWriteObject();
437:
438: // Writes extensions
439: stream.writeInt(extensions.size());
440: for (CellExtension extension : extensions.values())
441: stream.writeObject(extension);
442: }
443: }
|