001: // ============================================================================
002: // $Id: Cell.java,v 1.12 2006/01/08 00:52:25 davidahall Exp $
003: // Copyright (c) 2004-2005 David A. Hall
004: // ============================================================================
005: // The contents of this file are subject to the Common Development and
006: // Distribution License (CDDL), Version 1.0 (the License); you may not use this
007: // file except in compliance with the License. You should have received a copy
008: // of the the License along with this file: if not, a copy of the License is
009: // available from Sun Microsystems, Inc.
010: //
011: // http://www.sun.com/cddl/cddl.html
012: //
013: // From time to time, the license steward (initially Sun Microsystems, Inc.) may
014: // publish revised and/or new versions of the License. You may not use,
015: // distribute, or otherwise make this file available under subsequent versions
016: // of the License.
017: //
018: // Alternatively, the contents of this file may be used under the terms of the
019: // GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which
020: // case the provisions of the LGPL are applicable instead of those above. If you
021: // wish to allow use of your version of this file only under the terms of the
022: // LGPL, and not to allow others to use your version of this file under the
023: // terms of the CDDL, indicate your decision by deleting the provisions above
024: // and replace them with the notice and other provisions required by the LGPL.
025: // If you do not delete the provisions above, a recipient may use your version
026: // of this file under the terms of either the CDDL or the LGPL.
027: //
028: // This library is distributed in the hope that it will be useful,
029: // but WITHOUT ANY WARRANTY; without even the implied warranty of
030: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
031: // ============================================================================
032:
033: package net.sf.jga.swing.spreadsheet;
034:
035: import java.awt.Component;
036: import java.awt.Point;
037: import java.util.HashSet;
038: import java.util.Iterator;
039: import java.util.Observable;
040: import java.util.Observer;
041: import java.util.Set;
042: import java.util.logging.Level;
043: import java.util.logging.Logger;
044: import javax.swing.DefaultCellEditor;
045: import javax.swing.JTable;
046: import javax.swing.JTextField;
047: import javax.swing.table.TableCellEditor;
048: import javax.swing.table.TableCellRenderer;
049: import net.sf.jga.fn.AdaptorVisitor;
050: import net.sf.jga.fn.BinaryFunctor;
051: import net.sf.jga.fn.EvaluationException;
052: import net.sf.jga.fn.Generator;
053: import net.sf.jga.fn.UnaryFunctor;
054: import net.sf.jga.fn.Visitable;
055: import net.sf.jga.fn.adaptor.ApplyBinary;
056: import net.sf.jga.fn.adaptor.ApplyGenerator;
057: import net.sf.jga.fn.adaptor.ApplyUnary;
058: import net.sf.jga.fn.adaptor.Bind1st;
059: import net.sf.jga.fn.adaptor.Bind2nd;
060: import net.sf.jga.fn.adaptor.Bind;
061: import net.sf.jga.fn.adaptor.ChainBinary;
062: import net.sf.jga.fn.adaptor.ChainUnary;
063: import net.sf.jga.fn.adaptor.ComposeBinary;
064: import net.sf.jga.fn.adaptor.ComposeUnary;
065: import net.sf.jga.fn.adaptor.ConditionalBinary;
066: import net.sf.jga.fn.adaptor.ConditionalGenerator;
067: import net.sf.jga.fn.adaptor.ConditionalUnary;
068: import net.sf.jga.fn.adaptor.Constant;
069: import net.sf.jga.fn.adaptor.Distribute;
070: import net.sf.jga.fn.adaptor.Generate1st;
071: import net.sf.jga.fn.adaptor.Generate2nd;
072: import net.sf.jga.fn.adaptor.Generate;
073: import net.sf.jga.fn.adaptor.GenerateBinary;
074: import net.sf.jga.fn.adaptor.GenerateUnary;
075: import net.sf.jga.parser.FunctorParser;
076: import net.sf.jga.parser.ParseException;
077: import net.sf.jga.swing.GenericTableCellRenderer;
078: import net.sf.jga.util.Formattable;
079:
080: /**
081: * A single cell in spreadsheet. Encapsulates information about the generator
082: * that provides the value of the cell, the cell's formatting information, and
083: * the editor and/or renderer used to display/update the cell's contents.
084: * <p>
085: * Copyright © 2004-2005 David A. Hall
086: * @author <a href="mailto:davidahall@users.sf.net">David A. Hall</a>
087: */
088:
089: // Problem: this class can't be generic, as the type T can be changed if the
090: // user changes the formula to return a different type. An alternative is to
091: // allow this type to remain generic, and when the user enters a new formula,
092: // a new instance if generated. In this case, we'd need the new cell to be
093: // able to adopt the listener list of the old cell, and the listener list in
094: // the base class (Observable) is private.
095: // TODO: implement the strictTyping flag of the enclosing spreadsheet. When
096: // strictly typed, a cell may reject a value/formula if it is not of the type
097: // already set (the first value/formula assigned to an undefined cell will
098: // never be rejected if it is not of the default type, however)
099: public class Cell extends Observable implements Observer {
100:
101: private Spreadsheet _parent;
102:
103: // // the type that the cell holds: typechecking has to be implemented
104: // // in this class, as the table can represent a heterogenous collection
105: // // of cells
106: // private Class _type;
107:
108: // The name of the cell
109: private String _name;
110:
111: // caches the value held by the cell
112: private Object _value;
113:
114: // the functor that returns the value of the cell: in the simple case,
115: // it'll be a Constant<T>, but it can be an arbitrarily complex functor
116: private Generator _generator;
117:
118: // the formula that describes the value of the cell: this string is
119: // parsed to create the Generator
120: private String _formula;
121:
122: // The address of this cell (for the time being, rows & columns won't be
123: // inserted (we'll need to update the address of a cell or provide a map)
124: private Point _addr;
125:
126: // Flag that controls editablilty: we can make cells read-only
127: private boolean _editable;
128:
129: // Component used to render cells in the paint method
130: private TableCellRenderer _renderer;
131:
132: // Component used to edit cell values
133: private TableCellEditor _editor;
134:
135: // The last exception caught by the cell
136: private Throwable _exception;
137:
138: // The string shown on the UI when a cell encounters an error
139: private String _errMsg;
140:
141: // Implies that a dependency value is not of the expected type
142: static public final String CLASS_CAST_ERR = "### CLASS ###";
143:
144: // Implies that a formula encounters an error when evaluated
145: static public final String EVALUATION_ERR = "### EVAL ###";
146:
147: // Implies that a cell's formula depends directly or indirectly on itself
148: static public final String CIRCULAR_REF_ERR = "### CIRC ###";
149:
150: // Implies that a cell's formula encounted an unexpected null value when evaluated
151: static public final String NULL_POINTER_ERR = "### NULL ###";
152:
153: // Implies that a cell reference cannot be resolved
154: static public final String REFERENCE_ERR = "### REF ###";
155:
156: // Implies that a cell has been referenced before it is initialized
157: static public final String UNDEFINED_ERR = "### UNDEF ###";
158:
159: // Implies that a cell's formula cannot be parsed: nothing past the point in the
160: // string representation of the formula where the parser exception was thrown has
161: // been analyzed.
162: static public final String PARSER_ERR = "### PARSE ###";
163:
164: // The set of cells on which this cell depends.
165: private Set<Cell> _dependencies = new HashSet<Cell>();
166:
167: // TODO: move this into spreadsheet model (cell should only know about Generators:
168: // it would be up to the model to parse string formulas and set the cell's
169: // generator or error state)
170:
171: /**
172: * Builds a possibly editable cell at the given address, using the formula to build
173: * a generator to retrieve the value.
174: */
175: Cell(Spreadsheet parent, Point addr, String formula,
176: boolean editable) {
177: if (formula == null || formula.trim().length() <= 0)
178: throw new IllegalArgumentException(
179: "Formula must be supplied");
180:
181: // TODO: delete this temporary hack
182: // _type = Object.class;
183:
184: _parent = parent;
185: _addr = addr;
186: _editable = editable;
187: setFormula(formula);
188:
189: // if (isValid())
190: // _type = getParser().getReturnType();
191:
192: _renderer = new Renderer(); //GenericTableCellRenderer();
193: }
194:
195: /**
196: * Builds a read-only cell at the given address, using the generator to retrieve the
197: * value.
198: */
199: <T> Cell(Spreadsheet parent, Class<T> type, Point addr,
200: Generator<T> value) {
201: _parent = parent;
202: // _type = type;
203: _addr = addr;
204: _editable = false;
205: _renderer = new Renderer(); // GenericTableCellRenderer();
206:
207: setFormulaImpl(value);
208: }
209:
210: Cell(Spreadsheet parent, int row, int col) {
211: _parent = parent;
212: _addr = new Point(row, col);
213: _editable = true;
214: _errMsg = UNDEFINED_ERR;
215: // _type = parent.getDefaultType();
216: _value = parent.getDefaultCellValue();
217: }
218:
219: Spreadsheet getParent() {
220: return _parent;
221: }
222:
223: private Spreadsheet.Parser getParser() {
224: return _parent._parser;
225: }
226:
227: public String getName() {
228: return _name;
229: }
230:
231: void setName(String name) {
232: _name = name;
233: }
234:
235: /**
236: * Returns the type of values held by the cell.
237: */
238: // public Class getType() { return _type; }
239: public Class getType() {
240: return _value == null ? Object.class : _value.getClass();
241: }
242:
243: /**
244: * Returns the formula that describes the cell's contents.
245: */
246: String getFormula() {
247: return _formula;
248: }
249:
250: /**
251: * Returns true if the cell is editable, false if it is read-only.
252: */
253: public boolean isEditable() {
254: return _editable;
255: }
256:
257: /**
258: * Sets the cell as editable (or not).
259: */
260: public void setEditable(boolean b) {
261: _editable = b;
262: }
263:
264: /**
265: * Returns the address of the cell.
266: */
267: public Point getAddress() {
268: return _addr;
269: }
270:
271: /**
272: * Returns the renderer used to paint the cell
273: */
274: public TableCellRenderer getRenderer() {
275: return _renderer;
276: }
277:
278: /**
279: * Sets the component used to paint the cell
280: */
281: public void setRenderer(TableCellRenderer renderer) {
282: _renderer = renderer;
283: }
284:
285: /**
286: * Returns the component used to edit the cell
287: */
288: public TableCellEditor getEditor() {
289: if (_editor == null)
290: _editor = new Editor();
291:
292: return _editor;
293: }
294:
295: /**
296: * Sets the component used to edit the cell
297: */
298: public void setEditor(TableCellEditor editor) {
299: _editor = editor;
300: }
301:
302: /**
303: * Returns true if the cell's value can be retrieved without error
304: */
305: public boolean isValid() {
306: return _errMsg == null;
307: }
308:
309: // TODO: fix this: it's bad to use _errMsg in this way, as the first thing
310: // that setFormula does is clear this information. (It might be OK, but if
311: // so, its more of an accident than anything)
312:
313: /**
314: * Returns true if the cell's value is undefined
315: */
316: public boolean isUndefined() {
317: return _errMsg == UNDEFINED_ERR;
318: }
319:
320: /**
321: * Returns the textual description of the last error associated with cell.
322: * The string will be used by the UI. If the cell's value can be retrieved
323: * without error, then this field will be null.
324: */
325: public String getErrorMsg() {
326: return _errMsg;
327: }
328:
329: /**
330: * Puts the cell into the error display mode: the invalid formula is preserved
331: * as a string, and the error message is set
332: */
333: private void setError(Throwable t, String formula, String msg) {
334: Logger.global.log(Level.FINE, formula, t);
335: _exception = t;
336: _parent.setStatus(t.getMessage());
337:
338: // _type = String.class;
339: _formula = formula;
340: _errMsg = msg;
341:
342: _value = t;
343: notifyObservers();
344: }
345:
346: /**
347: * Puts the cell into the standard mode.
348: */
349: private void clearError() {
350: _exception = null;
351: _errMsg = null;
352: }
353:
354: /**
355: * Returns the value of the cell. If an exception is caught while
356: * evaluating the cell, then this will return null and the getErrorMsg()
357: * method will return a description of the problem.
358: */
359: public Object getValue() {
360: return _value;
361: }
362:
363: /**
364: * Evaluates the cell's formula and caches the result. This can cause the cell to enter
365: * an error state, if the forumula throws one of several exceptions.
366: */
367: private void setValue() {
368: try {
369: _value = _generator.gen();
370: clearError();
371: } catch (CircularReferenceException x) {
372: setError(x, _formula, CIRCULAR_REF_ERR);
373: } catch (InvalidReferenceException x) {
374: recover(x, _formula, REFERENCE_ERR);
375: } catch (NullPointerException x) {
376: setError(x, _formula, NULL_POINTER_ERR);
377: } catch (ClassCastException x) {
378: recover(x, _formula, CLASS_CAST_ERR);
379: } catch (EvaluationException x) {
380: recover(x, _formula, EVALUATION_ERR);
381: } catch (IllegalArgumentException x) {
382: recover(x, _formula, EVALUATION_ERR);
383: } catch (Exception x) {
384: setError(x, _formula, EVALUATION_ERR);
385: }
386: }
387:
388: /**
389: * Clears the contents of the cell, setting to the default type and value.
390: */
391:
392: public void clear() {
393: doClear(null, _parent.getDefaultCellValue());
394: }
395:
396: /**
397: * Clears the contents of the cell, and sets its state to an invalid state to
398: * force all dependant cells to reparse.
399: */
400:
401: void unlink() {
402: doClear(REFERENCE_ERR, new InvalidReferenceException("Cell("
403: + _addr.x + "," + _addr.y + ")"));
404: }
405:
406: private void doClear(String msg, Object value) {
407: unregister();
408:
409: _editable = true;
410: // _type = parent.getDefaultType();
411: _value = value;
412: _generator = null;
413: _formula = null;
414: _errMsg = msg;
415:
416: notifyObservers();
417: }
418:
419: /**
420: * Sets the contents of the cell to the given value.
421: */
422: public final void setValue(Object value) {
423: if (value instanceof Generator)
424: setFormula((Generator) value);
425:
426: // If the value we receive is the current exception, then we're at the end
427: // of an editing process that went bad. The Editor has set the cell's state
428: // to reflect the error, and we don't want to undo that here.
429: else if (value != _exception)
430: setFormula(new Constant(value));
431: }
432:
433: /**
434: * Sets the contents of the cell to the given formula.
435: */
436: public final void setFormula(String formula)
437: throws CircularReferenceException {
438: if (formula == null || "".equals(formula)) {
439: clear();
440: return;
441: }
442:
443: if (formula.startsWith("="))
444: formula = formula.substring(1);
445:
446: try {
447: Generator gen = getParser().parseGenerator(this , formula);
448: // _type = getParser().getReturnType();
449: setFormula(gen);
450: _formula = formula;
451: } catch (ParseException x) {
452: setError(x, formula, PARSER_ERR);
453: } catch (ClassCastException x) {
454: recover(x, formula, CLASS_CAST_ERR);
455: } catch (NullPointerException x) {
456: setError(x, formula, NULL_POINTER_ERR);
457: }
458: }
459:
460: // TODO: define a version of setFormula that the spreadsheet to use: it must do
461: // all of this and also set the _formula member to something meaningful
462:
463: /**
464: * Sets the contents of the cell to the given formula, notifying all observers of
465: * the change.
466: */
467: public final void setFormula(Generator formula) {
468: clearError();
469: unregister();
470: setFormulaImpl(formula);
471: notifyObservers();
472: }
473:
474: /**
475: * Checks the given formula to prevent circular dependencies, and sets the contents of the
476: * cell to the given formula. A circular reference causes the cell to enter an error state,
477: * and its old value will be retained.
478: */
479: private void setFormulaImpl(Generator formula) {
480: try {
481: _generator = register(formula);
482: setValue();
483: } catch (CircularReferenceException x) {
484: setError(x, formula.toString(), CIRCULAR_REF_ERR);
485: }
486: }
487:
488: /**
489: * Attempts one recovery from the given exception. The formula stored in the cell
490: * is reparsed, to allow recovery in cases where there is a type mismatch between the
491: * old formula functor and new values in input cells.
492: */
493: synchronized private void recover(Exception x, String formula,
494: String msg) {
495: if (_inRecovery) {
496: setError(x, formula, msg);
497: } else {
498: _inRecovery = true;
499: setFormula(_formula);
500: if (!isValid() && _errMsg.equals(PARSER_ERR)) {
501: setError(x, formula, msg);
502: }
503:
504: _inRecovery = false;
505: }
506: }
507:
508: // guards against re-entering the recover method
509: private boolean _inRecovery = false;
510:
511: /**
512: * Sets the format of the cell, if formatting is supported by the cell's
513: * renderer. If formatting is not supported, invoking this method will
514: * have no effect.
515: */
516: public <T> void setFormat(UnaryFunctor<T, String> formatter) {
517: if (_renderer instanceof Formattable)
518: ((Formattable) _renderer).setFormat(formatter);
519: }
520:
521: /**
522: * Returns a reference to the cell's formula.
523: */
524: public Generator getReference() {
525: return new CellReference(this );
526: }
527:
528: /**
529: * Returns true if this cell references the given cell, either directly or
530: * indirectly. Also returns true if this cell <i>is</i> the given cell.
531: */
532: public boolean references(Cell c) {
533: if (getAddress().equals(c.getAddress())) {
534: return true;
535: }
536:
537: for (Cell c1 : _dependencies) {
538: if (c1 == c) {
539: return true;
540: }
541: if (c1.references(c)) {
542: return true;
543: }
544: }
545:
546: return false;
547: }
548:
549: /**
550: * returns an iterator over all cells on which this cell directly depends
551: */
552: public final Iterator<Cell> dependsOn() {
553: return _dependencies.iterator();
554: }
555:
556: public String toString() {
557: StringBuffer buf = new StringBuffer(256);
558: buf.append("Cell(").append(_addr.x).append(",").append(_addr.y)
559: .append(") ");
560: if (_name != null) {
561: buf.append('"').append(_name).append('"').append(' ');
562: }
563: buf.append("[");
564: if (_exception != null)
565: buf.append(_exception.getMessage());
566: else if (_formula != null)
567: buf.append(_formula);
568: else
569: buf.append(_value);
570:
571: return buf.append("]").toString();
572: }
573:
574: // - - - - - - - - - - - -
575: // Observer Interface
576: // - - - - - - - - - - - -
577:
578: public void update(Observable observable, Object object) {
579: setValue();
580: notifyObservers();
581: }
582:
583: /**
584: * Triggers cells (and other observers) that this cell's contents have been
585: * changed.
586: */
587: public void notifyObservers() {
588: setChanged();
589: notifyObservers(_value);
590: clearChanged();
591: }
592:
593: // - - - - - - - - - - - -
594: // Registration details
595: // - - - - - - - - - - - -
596:
597: // Examines the current value generator, adding this as an observer to
598: // any cells on which this depends
599: private <T> Generator<T> register(Generator<T> formula)
600: throws CircularReferenceException {
601: formula.accept(new RegistrationVisitor());
602: return formula;
603: }
604:
605: // Examines the current value generator, removing this as an observer to
606: // any cells on which this depends.
607: private void unregister() {
608: if (_generator != null)
609: _generator.accept(new UnregistrationVisitor());
610:
611: _dependencies.clear();
612: }
613:
614: private class RegistrationVisitor extends AdaptorVisitor implements
615: CellReference.Visitor {
616: public void visit(CellReference host) {
617: Cell hostCell = host.getCell();
618:
619: // check for circular references
620: if (hostCell.references(Cell.this ))
621: throw new CircularReferenceException();
622:
623: host.register(Cell.this );
624: _dependencies.add(hostCell);
625: }
626: }
627:
628: private class UnregistrationVisitor extends AdaptorVisitor
629: implements CellReference.Visitor {
630: public void visit(CellReference host) {
631: host.unregister(Cell.this );
632: }
633: }
634:
635: // - - - - - - - - - - -
636: // Editor class
637: // - - - - - - - - - - -
638:
639: static class Editor extends DefaultCellEditor {
640:
641: static final long serialVersionUID = -9010644547311806213L;
642:
643: private Spreadsheet _sheet;
644: private Cell _cell;
645:
646: public Editor() {
647: super (new JTextField());
648: }
649:
650: public Object getCellEditorValue() {
651: // The superclass' editorComponent is the textField we passed at construction
652: // the super class returns the text fields contents. we'll parse it and pass
653: // the result
654: String formula = (String) super .getCellEditorValue();
655: if (formula.startsWith("="))
656: formula = formula.substring(1);
657:
658: try {
659: return _sheet._parser.parseGenerator(_cell, formula);
660: } catch (ParseException x) {
661: _cell.setError(x, formula, PARSER_ERR);
662: return x;
663: }
664: }
665:
666: public Component getTableCellEditorComponent(JTable table,
667: Object value, boolean isSelected, int row, int col) {
668: _sheet = (Spreadsheet) table;
669: _cell = _sheet.getCellAt(row, col);
670: return super .getTableCellEditorComponent(table,
671: _cell._formula, isSelected, row, col);
672: }
673: }
674:
675: // - - - - - - - - - - -
676: // Renderer class
677: // - - - - - - - - - - -
678:
679: static class Renderer extends GenericTableCellRenderer {
680:
681: public Component getTableCellRendererComponent(JTable table,
682: Object value, boolean isSelected, boolean hasFocus,
683: int row, int column) {
684: Spreadsheet sheet = (Spreadsheet) table;
685: Cell cell = sheet.getCellIfPresent(row, column);
686: boolean editable = (cell != null) ? cell.isEditable()
687: : sheet.isEditableByDefault();
688: return super .getTableCellRendererComponent(table, value,
689: isSelected && editable, hasFocus && editable, row,
690: column);
691: }
692: }
693: }
694:
695: // - - - - - - - - - - -
696: // Cell Reference class
697: // - - - - - - - - - - -
698:
699: class CellReference extends Generator {
700:
701: static final long serialVersionUID = -2552950462394369940L;
702:
703: private Cell _cell;
704:
705: public CellReference(Cell cell) {
706: _cell = cell;
707: }
708:
709: public Object gen() {
710: Object obj = _cell.getValue();
711: if (obj instanceof RuntimeException)
712: throw (RuntimeException) obj;
713:
714: return obj;
715: }
716:
717: public Cell getCell() {
718: return _cell;
719: }
720:
721: public void register(Observer obs) {
722: _cell.addObserver(obs);
723: }
724:
725: public void unregister(Observer obs) {
726: _cell.deleteObserver(obs);
727: }
728:
729: public String toString() {
730: return "CellReference(" + _cell + ")";
731: }
732:
733: public void accept(net.sf.jga.fn.Visitor v) {
734: if (v instanceof CellReference.Visitor)
735: ((CellReference.Visitor) v).visit(this );
736: else
737: v.visit(this );
738: }
739:
740: public interface Visitor extends net.sf.jga.fn.Visitor {
741: public void visit(CellReference host);
742: }
743: }
744:
745: class CircularReferenceException extends RuntimeException {
746: static final long serialVersionUID = -8670923266092229864L;
747:
748: public String getMessage() {
749: return "Circular Reference";
750: }
751: }
752:
753: class InvalidReferenceException extends RuntimeException {
754: public InvalidReferenceException(String msg) {
755: super(msg);
756: }
757: }
|