KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sf > jga > swing > spreadsheet > Cell


1 // ============================================================================
2
// $Id: Cell.java,v 1.12 2006/01/08 00:52:25 davidahall Exp $
3
// Copyright (c) 2004-2005 David A. Hall
4
// ============================================================================
5
// The contents of this file are subject to the Common Development and
6
// Distribution License (CDDL), Version 1.0 (the License); you may not use this
7
// file except in compliance with the License. You should have received a copy
8
// of the the License along with this file: if not, a copy of the License is
9
// available from Sun Microsystems, Inc.
10
//
11
// http://www.sun.com/cddl/cddl.html
12
//
13
// From time to time, the license steward (initially Sun Microsystems, Inc.) may
14
// publish revised and/or new versions of the License. You may not use,
15
// distribute, or otherwise make this file available under subsequent versions
16
// of the License.
17
//
18
// Alternatively, the contents of this file may be used under the terms of the
19
// GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which
20
// case the provisions of the LGPL are applicable instead of those above. If you
21
// wish to allow use of your version of this file only under the terms of the
22
// LGPL, and not to allow others to use your version of this file under the
23
// terms of the CDDL, indicate your decision by deleting the provisions above
24
// and replace them with the notice and other provisions required by the LGPL.
25
// If you do not delete the provisions above, a recipient may use your version
26
// of this file under the terms of either the CDDL or the LGPL.
27
//
28
// This library is distributed in the hope that it will be useful,
29
// but WITHOUT ANY WARRANTY; without even the implied warranty of
30
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
31
// ============================================================================
32

33 package net.sf.jga.swing.spreadsheet;
34
35 import java.awt.Component JavaDoc;
36 import java.awt.Point JavaDoc;
37 import java.util.HashSet JavaDoc;
38 import java.util.Iterator JavaDoc;
39 import java.util.Observable JavaDoc;
40 import java.util.Observer JavaDoc;
41 import java.util.Set JavaDoc;
42 import java.util.logging.Level JavaDoc;
43 import java.util.logging.Logger JavaDoc;
44 import javax.swing.DefaultCellEditor JavaDoc;
45 import javax.swing.JTable JavaDoc;
46 import javax.swing.JTextField JavaDoc;
47 import javax.swing.table.TableCellEditor JavaDoc;
48 import javax.swing.table.TableCellRenderer JavaDoc;
49 import net.sf.jga.fn.AdaptorVisitor;
50 import net.sf.jga.fn.BinaryFunctor;
51 import net.sf.jga.fn.EvaluationException;
52 import net.sf.jga.fn.Generator;
53 import net.sf.jga.fn.UnaryFunctor;
54 import net.sf.jga.fn.Visitable;
55 import net.sf.jga.fn.adaptor.ApplyBinary;
56 import net.sf.jga.fn.adaptor.ApplyGenerator;
57 import net.sf.jga.fn.adaptor.ApplyUnary;
58 import net.sf.jga.fn.adaptor.Bind1st;
59 import net.sf.jga.fn.adaptor.Bind2nd;
60 import net.sf.jga.fn.adaptor.Bind;
61 import net.sf.jga.fn.adaptor.ChainBinary;
62 import net.sf.jga.fn.adaptor.ChainUnary;
63 import net.sf.jga.fn.adaptor.ComposeBinary;
64 import net.sf.jga.fn.adaptor.ComposeUnary;
65 import net.sf.jga.fn.adaptor.ConditionalBinary;
66 import net.sf.jga.fn.adaptor.ConditionalGenerator;
67 import net.sf.jga.fn.adaptor.ConditionalUnary;
68 import net.sf.jga.fn.adaptor.Constant;
69 import net.sf.jga.fn.adaptor.Distribute;
70 import net.sf.jga.fn.adaptor.Generate1st;
71 import net.sf.jga.fn.adaptor.Generate2nd;
72 import net.sf.jga.fn.adaptor.Generate;
73 import net.sf.jga.fn.adaptor.GenerateBinary;
74 import net.sf.jga.fn.adaptor.GenerateUnary;
75 import net.sf.jga.parser.FunctorParser;
76 import net.sf.jga.parser.ParseException;
77 import net.sf.jga.swing.GenericTableCellRenderer;
78 import net.sf.jga.util.Formattable;
79
80 /**
81  * A single cell in spreadsheet. Encapsulates information about the generator
82  * that provides the value of the cell, the cell's formatting information, and
83  * the editor and/or renderer used to display/update the cell's contents.
84  * <p>
85  * Copyright &copy; 2004-2005 David A. Hall
86  * @author <a HREF="mailto:davidahall@users.sf.net">David A. Hall</a>
87  */

88
89 // Problem: this class can't be generic, as the type T can be changed if the
90
// user changes the formula to return a different type. An alternative is to
91
// allow this type to remain generic, and when the user enters a new formula,
92
// a new instance if generated. In this case, we'd need the new cell to be
93
// able to adopt the listener list of the old cell, and the listener list in
94
// the base class (Observable) is private.
95

96 // TODO: implement the strictTyping flag of the enclosing spreadsheet. When
97
// strictly typed, a cell may reject a value/formula if it is not of the type
98
// already set (the first value/formula assigned to an undefined cell will
99
// never be rejected if it is not of the default type, however)
100

101 public class Cell extends Observable JavaDoc implements Observer JavaDoc {
102
103     private Spreadsheet _parent;
104     
105 // // the type that the cell holds: typechecking has to be implemented
106
// // in this class, as the table can represent a heterogenous collection
107
// // of cells
108
// private Class _type;
109

110     // The name of the cell
111
private String JavaDoc _name;
112     
113     // caches the value held by the cell
114
private Object JavaDoc _value;
115     
116     // the functor that returns the value of the cell: in the simple case,
117
// it'll be a Constant<T>, but it can be an arbitrarily complex functor
118
private Generator _generator;
119
120     // the formula that describes the value of the cell: this string is
121
// parsed to create the Generator
122
private String JavaDoc _formula;
123     
124     // The address of this cell (for the time being, rows & columns won't be
125
// inserted (we'll need to update the address of a cell or provide a map)
126
private Point JavaDoc _addr;
127
128     // Flag that controls editablilty: we can make cells read-only
129
private boolean _editable;
130
131     // Component used to render cells in the paint method
132
private TableCellRenderer JavaDoc _renderer;
133
134     // Component used to edit cell values
135
private TableCellEditor JavaDoc _editor;
136
137     // The last exception caught by the cell
138
private Throwable JavaDoc _exception;
139     
140     // The string shown on the UI when a cell encounters an error
141
private String JavaDoc _errMsg;
142
143     // Implies that a dependency value is not of the expected type
144
static public final String JavaDoc CLASS_CAST_ERR = "### CLASS ###";
145
146     // Implies that a formula encounters an error when evaluated
147
static public final String JavaDoc EVALUATION_ERR = "### EVAL ###";
148
149     // Implies that a cell's formula depends directly or indirectly on itself
150
static public final String JavaDoc CIRCULAR_REF_ERR = "### CIRC ###";
151
152     // Implies that a cell's formula encounted an unexpected null value when evaluated
153
static public final String JavaDoc NULL_POINTER_ERR = "### NULL ###";
154
155     // Implies that a cell reference cannot be resolved
156
static public final String JavaDoc REFERENCE_ERR = "### REF ###";
157
158     // Implies that a cell has been referenced before it is initialized
159
static public final String JavaDoc UNDEFINED_ERR = "### UNDEF ###";
160
161     // Implies that a cell's formula cannot be parsed: nothing past the point in the
162
// string representation of the formula where the parser exception was thrown has
163
// been analyzed.
164
static public final String JavaDoc PARSER_ERR = "### PARSE ###";
165
166     // The set of cells on which this cell depends.
167
private Set JavaDoc<Cell> _dependencies = new HashSet JavaDoc<Cell>();
168
169     // TODO: move this into spreadsheet model (cell should only know about Generators:
170
// it would be up to the model to parse string formulas and set the cell's
171
// generator or error state)
172

173     /**
174      * Builds a possibly editable cell at the given address, using the formula to build
175      * a generator to retrieve the value.
176      */

177     Cell(Spreadsheet parent, Point JavaDoc addr, String JavaDoc formula, boolean editable) {
178         if (formula == null || formula.trim().length() <= 0)
179             throw new IllegalArgumentException JavaDoc("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 JavaDoc(); //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 JavaDoc<T> type, Point JavaDoc addr, Generator<T> value) {
200         _parent = parent;
201 // _type = type;
202
_addr = addr;
203         _editable = false;
204         _renderer = new Renderer JavaDoc(); // GenericTableCellRenderer();
205

206         setFormulaImpl(value);
207     }
208
209     Cell(Spreadsheet parent, int row, int col) {
210         _parent = parent;
211         _addr = new Point JavaDoc(row, col);
212         _editable = true;
213         _errMsg = UNDEFINED_ERR;
214 // _type = parent.getDefaultType();
215
_value = parent.getDefaultCellValue();
216     }
217
218     Spreadsheet getParent() { return _parent; }
219
220     private Spreadsheet.Parser getParser() { return _parent._parser; }
221
222     public String JavaDoc getName() { return _name; }
223
224     
225     void setName(String JavaDoc name) { _name = name; }
226     
227     /**
228      * Returns the type of values held by the cell.
229      */

230 // public Class getType() { return _type; }
231
public Class JavaDoc getType() { return _value == null ? Object JavaDoc.class : _value.getClass(); }
232
233     /**
234      * Returns the formula that describes the cell's contents.
235      */

236     String JavaDoc getFormula() { return _formula; }
237
238     /**
239      * Returns true if the cell is editable, false if it is read-only.
240      */

241     public boolean isEditable() { return _editable; }
242
243
244     /**
245      * Sets the cell as editable (or not).
246      */

247     public void setEditable(boolean b) { _editable = b; }
248     
249     /**
250      * Returns the address of the cell.
251      */

252     public Point JavaDoc getAddress() { return _addr; }
253
254     /**
255      * Returns the renderer used to paint the cell
256      */

257     public TableCellRenderer JavaDoc getRenderer() { return _renderer; }
258
259     /**
260      * Sets the component used to paint the cell
261      */

262     public void setRenderer(TableCellRenderer JavaDoc renderer) {
263         _renderer = renderer;
264     }
265    
266     /**
267      * Returns the component used to edit the cell
268      */

269     public TableCellEditor JavaDoc getEditor() {
270         if (_editor == null)
271             _editor = new Editor JavaDoc();
272         
273         return _editor;
274     }
275     
276     /**
277      * Sets the component used to edit the cell
278      */

279     public void setEditor(TableCellEditor JavaDoc editor) {
280         _editor = editor;
281     }
282
283     /**
284      * Returns true if the cell's value can be retrieved without error
285      */

286     public boolean isValid () {
287         return _errMsg == null;
288     }
289
290     // TODO: fix this: it's bad to use _errMsg in this way, as the first thing
291
// that setFormula does is clear this information. (It might be OK, but if
292
// so, its more of an accident than anything)
293

294     /**
295      * Returns true if the cell's value is undefined
296      */

297     public boolean isUndefined () {
298         return _errMsg == UNDEFINED_ERR;
299     }
300
301     /**
302      * Returns the textual description of the last error associated with cell.
303      * The string will be used by the UI. If the cell's value can be retrieved
304      * without error, then this field will be null.
305      */

306     public String JavaDoc getErrorMsg() { return _errMsg; }
307
308     /**
309      * Puts the cell into the error display mode: the invalid formula is preserved
310      * as a string, and the error message is set
311      */

312     private void setError(Throwable JavaDoc t, String JavaDoc formula, String JavaDoc msg) {
313         Logger.global.log(Level.FINE, formula, t);
314         _exception = t;
315         _parent.setStatus(t.getMessage());
316
317 // _type = String.class;
318
_formula = formula;
319         _errMsg = msg;
320
321         _value = t;
322         notifyObservers();
323     }
324
325     /**
326      * Puts the cell into the standard mode.
327      */

328     private void clearError() {
329         _exception = null;
330         _errMsg = null;
331     }
332
333     /**
334      * Returns the value of the cell. If an exception is caught while
335      * evaluating the cell, then this will return null and the getErrorMsg()
336      * method will return a description of the problem.
337      */

338     public Object JavaDoc getValue() {
339         return _value;
340     }
341
342     
343     /**
344      * Evaluates the cell's formula and caches the result. This can cause the cell to enter
345      * an error state, if the forumula throws one of several exceptions.
346      */

347     private void setValue() {
348         try {
349             _value = _generator.gen();
350             clearError();
351         }
352         catch (CircularReferenceException x) { setError(x, _formula, CIRCULAR_REF_ERR); }
353         catch (InvalidReferenceException x) { recover (x, _formula, REFERENCE_ERR); }
354         catch (NullPointerException JavaDoc x) { setError(x, _formula, NULL_POINTER_ERR); }
355         catch (ClassCastException JavaDoc x) { recover (x, _formula, CLASS_CAST_ERR); }
356         catch (EvaluationException x) { recover (x, _formula, EVALUATION_ERR); }
357         catch (IllegalArgumentException JavaDoc x) { recover (x, _formula, EVALUATION_ERR); }
358         catch (Exception JavaDoc x) { setError(x, _formula, EVALUATION_ERR); }
359     }
360
361
362     /**
363      * Clears the contents of the cell, setting to the default type and value.
364      */

365
366     public void clear() {
367         doClear(null, _parent.getDefaultCellValue());
368     }
369
370     /**
371      * Clears the contents of the cell, and sets its state to an invalid state to
372      * force all dependant cells to reparse.
373      */

374
375     void unlink() {
376         doClear(REFERENCE_ERR, new InvalidReferenceException("Cell("+_addr.x+","+_addr.y+")"));
377     }
378
379     private void doClear(String JavaDoc msg, Object JavaDoc value) {
380         unregister();
381         
382         _editable = true;
383 // _type = parent.getDefaultType();
384
_value = value;
385         _generator = null;
386         _formula = null;
387         _errMsg = msg;
388         
389         notifyObservers();
390     }
391
392
393     /**
394      * Sets the contents of the cell to the given value.
395      */

396     public final void setValue(Object JavaDoc value) {
397         if (value instanceof Generator)
398             setFormula((Generator) value);
399
400         // If the value we receive is the current exception, then we're at the end
401
// of an editing process that went bad. The Editor has set the cell's state
402
// to reflect the error, and we don't want to undo that here.
403
else if (value != _exception)
404             setFormula(new Constant(value));
405     }
406     
407     /**
408      * Sets the contents of the cell to the given formula.
409      */

410     public final void setFormula(String JavaDoc formula) throws CircularReferenceException {
411         if (formula == null || "".equals(formula)) {
412             clear();
413             return;
414         }
415
416         if (formula.startsWith("="))
417             formula = formula.substring(1);
418      
419         try {
420             Generator gen = getParser().parseGenerator(this, formula);
421             // _type = getParser().getReturnType();
422
setFormula(gen);
423             _formula = formula;
424         }
425         catch (ParseException x) { setError(x, formula, PARSER_ERR); }
426         catch (ClassCastException JavaDoc x) { recover(x, formula, CLASS_CAST_ERR); }
427         catch (NullPointerException JavaDoc x) { setError(x, formula, NULL_POINTER_ERR); }
428     }
429
430     // TODO: define a version of setFormula that the spreadsheet to use: it must do
431
// all of this and also set the _formula member to something meaningful
432

433     /**
434      * Sets the contents of the cell to the given formula, notifying all observers of
435      * the change.
436      */

437     public final void setFormula(Generator formula) {
438         clearError();
439         unregister();
440         setFormulaImpl(formula);
441         notifyObservers();
442     }
443
444
445     /**
446      * Checks the given formula to prevent circular dependencies, and sets the contents of the
447      * cell to the given formula. A circular reference causes the cell to enter an error state,
448      * and its old value will be retained.
449      */

450     private void setFormulaImpl(Generator formula) {
451         try {
452             _generator = register(formula);
453             setValue();
454         }
455         catch (CircularReferenceException x) { setError(x, formula.toString(), CIRCULAR_REF_ERR); }
456     }
457     
458
459     /**
460      * Attempts one recovery from the given exception. The formula stored in the cell
461      * is reparsed, to allow recovery in cases where there is a type mismatch between the
462      * old formula functor and new values in input cells.
463      */

464     synchronized private void recover(Exception JavaDoc x, String JavaDoc formula, String JavaDoc msg) {
465         if (_inRecovery) {
466             setError(x, formula, msg);
467         }
468         else {
469             _inRecovery = true;
470             setFormula(_formula);
471             if (!isValid() && _errMsg.equals(PARSER_ERR)) {
472                 setError(x, formula, msg);
473             }
474             
475             _inRecovery = false;
476         }
477     }
478
479     // guards against re-entering the recover method
480
private boolean _inRecovery = false;
481
482     
483     /**
484      * Sets the format of the cell, if formatting is supported by the cell's
485      * renderer. If formatting is not supported, invoking this method will
486      * have no effect.
487      */

488     public <T> void setFormat(UnaryFunctor<T,String JavaDoc> formatter) {
489         if (_renderer instanceof Formattable)
490             ((Formattable) _renderer).setFormat(formatter);
491     }
492
493
494     /**
495      * Returns a reference to the cell's formula.
496      */

497     public Generator getReference() {
498         return new CellReference(this);
499     }
500
501     /**
502      * Returns true if this cell references the given cell, either directly or
503      * indirectly. Also returns true if this cell <i>is</i> the given cell.
504      */

505     public boolean references(Cell c) {
506         if (getAddress().equals(c.getAddress())) {
507             return true;
508         }
509
510         for (Cell c1 : _dependencies) {
511             if (c1 == c) {
512                 return true;
513             }
514             if (c1.references(c)) {
515                 return true;
516             }
517         }
518
519         return false;
520     }
521
522
523     /**
524      * returns an iterator over all cells on which this cell directly depends
525      */

526     public final Iterator JavaDoc<Cell> dependsOn() {
527         return _dependencies.iterator();
528     }
529     
530
531     public String JavaDoc toString() {
532         StringBuffer JavaDoc buf = new StringBuffer JavaDoc(256);
533         buf.append("Cell(").append(_addr.x).append(",").append(_addr.y).append(") ");
534         if (_name != null) {
535             buf.append('"').append(_name).append('"').append(' ');
536         }
537         buf.append("[");
538         if (_exception != null)
539             buf.append(_exception.getMessage());
540         else if (_formula != null)
541             buf.append(_formula);
542         else
543             buf.append(_value);
544
545         return buf.append("]").toString();
546     }
547
548
549     // - - - - - - - - - - - -
550
// Observer Interface
551
// - - - - - - - - - - - -
552

553     public void update(Observable JavaDoc observable, Object JavaDoc object) {
554         setValue();
555         notifyObservers();
556     }
557
558     /**
559      * Triggers cells (and other observers) that this cell's contents have been
560      * changed.
561      */

562     public void notifyObservers() {
563         setChanged();
564         notifyObservers(_value);
565         clearChanged();
566     }
567
568     // - - - - - - - - - - - -
569
// Registration details
570
// - - - - - - - - - - - -
571

572     // Examines the current value generator, adding this as an observer to
573
// any cells on which this depends
574
private <T> Generator<T> register(Generator<T> formula) throws CircularReferenceException {
575         formula.accept(new RegistrationVisitor());
576         return formula;
577     }
578
579     
580     // Examines the current value generator, removing this as an observer to
581
// any cells on which this depends.
582
private void unregister() {
583         if (_generator != null)
584             _generator.accept(new UnregistrationVisitor());
585         
586         _dependencies.clear();
587     }
588
589     private class RegistrationVisitor extends AdaptorVisitor
590         implements CellReference.Visitor
591     {
592         public void visit(CellReference host) {
593             Cell hostCell = host.getCell();
594             
595             // check for circular references
596
if (hostCell.references(Cell.this))
597                 throw new CircularReferenceException();
598
599             host.register(Cell.this);
600             _dependencies.add(hostCell);
601         }
602     }
603
604     private class UnregistrationVisitor extends AdaptorVisitor
605         implements CellReference.Visitor
606     {
607         public void visit(CellReference host) {
608             host.unregister(Cell.this);
609         }
610     }
611
612     // - - - - - - - - - - -
613
// Editor class
614
// - - - - - - - - - - -
615

616     static class Editor extends DefaultCellEditor JavaDoc {
617         
618         static final long serialVersionUID = -9010644547311806213L;
619
620         private Spreadsheet _sheet;
621         private Cell _cell;
622         
623         public Editor() {
624             super(new JTextField JavaDoc());
625         }
626      
627         public Object JavaDoc getCellEditorValue() {
628             // The superclass' editorComponent is the textField we passed at construction
629
// the super class returns the text fields contents. we'll parse it and pass
630
// the result
631
String JavaDoc formula = (String JavaDoc) super.getCellEditorValue();
632             if (formula.startsWith("="))
633                 formula = formula.substring(1);
634         
635             try {
636                 return _sheet._parser.parseGenerator(_cell, formula);
637             }
638             catch(ParseException x) {
639                 _cell.setError(x, formula, PARSER_ERR);
640                 return x;
641             }
642         }
643         
644         public Component JavaDoc getTableCellEditorComponent(JTable JavaDoc table, Object JavaDoc value, boolean isSelected,
645                                                      int row, int col)
646         {
647             _sheet = (Spreadsheet) table;
648             _cell = _sheet.getCellAt(row, col);
649             return super.getTableCellEditorComponent(table,_cell._formula,isSelected,row,col);
650         }
651     }
652
653
654     // - - - - - - - - - - -
655
// Renderer class
656
// - - - - - - - - - - -
657

658     static class Renderer extends GenericTableCellRenderer {
659         
660         public Component JavaDoc getTableCellRendererComponent(JTable JavaDoc table,Object JavaDoc value,boolean isSelected,
661                                                        boolean hasFocus, int row, int column)
662         {
663             Spreadsheet sheet = (Spreadsheet) table;
664             Cell cell = sheet.getCellIfPresent(row,column);
665             boolean editable = (cell != null) ? cell.isEditable() : sheet.isEditableByDefault();
666             return super.getTableCellRendererComponent(table, value, isSelected && editable,
667                                                        hasFocus && editable, row, column);
668         }
669     }
670 }
671
672 // - - - - - - - - - - -
673
// Cell Reference class
674
// - - - - - - - - - - -
675

676 class CellReference extends Generator {
677
678     static final long serialVersionUID = -2552950462394369940L;
679     
680     private Cell _cell;
681
682     public CellReference(Cell cell) { _cell = cell; }
683
684     public Object JavaDoc gen() {
685         Object JavaDoc obj = _cell.getValue();
686         if (obj instanceof RuntimeException JavaDoc)
687             throw (RuntimeException JavaDoc) obj;
688
689         return obj;
690     }
691
692     public Cell getCell() { return _cell; }
693
694     public void register(Observer JavaDoc obs) {
695         _cell.addObserver(obs);
696     }
697     
698     public void unregister(Observer JavaDoc obs) {
699         _cell.deleteObserver(obs);
700     }
701     
702     public String JavaDoc toString() { return "CellReference("+_cell+")"; }
703
704     public void accept(net.sf.jga.fn.Visitor v) {
705         if (v instanceof CellReference.Visitor)
706             ((CellReference.Visitor)v).visit(this);
707         else
708             v.visit(this);
709     }
710
711     public interface Visitor extends net.sf.jga.fn.Visitor {
712         public void visit(CellReference host);
713     }
714 }
715
716 class CircularReferenceException extends RuntimeException JavaDoc {
717     static final long serialVersionUID = -8670923266092229864L;
718     public String JavaDoc getMessage() { return "Circular Reference"; }
719 }
720
721 class InvalidReferenceException extends RuntimeException JavaDoc {
722     public InvalidReferenceException(String JavaDoc msg) { super(msg); }
723 }
724     
725
Popular Tags