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.Iterator;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.SortedSet;
032    import java.util.TreeSet;
033    
034    import csheets.core.formula.compiler.FormulaCompilationException;
035    import csheets.ext.Extension;
036    import csheets.ext.ExtensionManager;
037    import csheets.ext.SpreadsheetExtension;
038    
039    /**
040     * The implementation of the <code>Spreadsheet</code> interface.
041     * @author Einar Pehrson
042     */
043    public class SpreadsheetImpl implements Spreadsheet {
044    
045            /** The unique version identifier used for serialization */
046            private static final long serialVersionUID = 7010464744129096272L;
047    
048            /** The base of the titles of new spreadsheets */
049            public static final String BASE_TITLE = "Sheet ";
050    
051            /** The workbook to which the spreadsheet belongs */
052            private Workbook workbook;
053    
054            /** The cells that have been instantiated */
055            private SortedSet<Cell> cells = new TreeSet<Cell>();
056    
057            /** The title of the spreadsheet */
058            private String title;
059    
060            /** The number of columns in the spreadsheet */
061            private int columns = 0;
062    
063            /** The number of rows in the spreadsheet */
064            private int rows = 0;
065    
066            /** The cell listeners that have been registered on the cell */
067            private transient List<CellListener> cellListeners
068                    = new ArrayList<CellListener>();
069    
070            /** The cell listener that forwards events from all cells */
071            private transient CellListener eventForwarder = new EventForwarder();
072    
073            /** The spreadsheet extensions that have been instantiated */
074            private transient Map<String, SpreadsheetExtension> extensions = 
075                    new HashMap<String, SpreadsheetExtension>();
076    
077            /**
078             * Creates a new spreadsheet.
079             * @param workbook the workbook to which the spreadsheet belongs
080             * @param title the title of the spreadsheet
081             */
082            SpreadsheetImpl(Workbook workbook, String title) {
083                    this.workbook = workbook;
084                    this.title = title;
085            }
086    
087            /**
088             * Creates a new spreadsheet, in which cells are initialized with data from
089             * the given content matrix.
090             * @param workbook the workbook to which the spreadsheet belongs
091             * @param title the title of the spreadsheet
092             * @param content the contents of the cells in the spreadsheet
093             */
094            SpreadsheetImpl(Workbook workbook, String title, String[][] content) {
095                    this(workbook, title);
096                    rows = content.length;
097                    for (int row = 0; row < content.length; row++) {
098                            int columns = content[row].length;
099                            if (this.columns < columns)
100                                    this.columns = columns;
101                            for (int column = 0; column < columns; column++) {
102                                    try {
103                                            Cell cell = new CellImpl(this, new Address(column, row), content[row][column]);
104                                            cell.addCellListener(eventForwarder);
105                                            cells.add(cell);
106                                    } catch (FormulaCompilationException e) {}
107                            }
108                    }
109            }
110    
111    /*
112     * LOCATION
113     */
114    
115            public Workbook getWorkbook() {
116                    return workbook;
117            }
118    
119            public String getTitle() {
120                    return title;
121            }
122    
123            public void setTitle(String title) {
124                    this.title = title;
125                    // fireTitleChanged();
126            }
127    
128    /*
129     * DIMENSIONS
130     */
131    
132            public int getColumnCount() {
133                    return columns;
134            }
135    
136            public int getRowCount() {
137                    return rows;
138            }
139    
140    /*
141     * CELLS
142     */
143    
144            public Cell getCell(Address address) {
145                    // Updates spreadsheet dimensions
146                    if (address.getRow() > rows)
147                            rows = address.getRow();
148                    if (address.getColumn() > columns)
149                            columns = address.getColumn();
150    
151                    // Looks for a previously used cell with this address
152                    for (Cell cell : cells)
153                            if (address.equals(cell.getAddress()))
154                                    return cell;
155    
156                    // If the cell has never been requested, create a new one
157                    Cell cell = new CellImpl(this, address);
158                    cell.addCellListener(eventForwarder);
159                    cells.add(cell);
160                    return cell;
161            }
162    
163            public Cell getCell(int column, int row) {
164                    return getCell(new Address(column, row));
165            }
166    
167            public SortedSet<Cell> getCells(Address address1, Address address2) {
168                    // Sorts addresses
169                    if (address1.compareTo(address2) > 0) {
170                            Address tempAddress = address1;
171                            address1 = address2;
172                            address2 = tempAddress;
173                    }
174    
175                    // Builds the set
176                    SortedSet<Cell> cells = new TreeSet<Cell>();
177                    for (int column = address1.getColumn(); column <= address2.getColumn(); column++)
178                            for (int row = address1.getRow(); row <= address2.getRow(); row++)
179                                    cells.add(getCell(new Address(column, row)));
180    
181                    return cells;
182            }
183    
184            public Cell[] getColumn(int index) {
185                    Cell[] column = new Cell[rows];
186                    for (int row = 0; row < row; row++)
187                            column[row] = getCell(new Address(index, row));
188                    return column;
189            }
190    
191            public Cell[] getRow(int index) {
192                    Cell[] row = new Cell[columns];
193                    for (int column = 0; column < columns; column++)
194                            row[column] = getCell(new Address(column, index));
195                    return row;
196            }
197    
198            public Iterator<Cell> iterator() {
199                    return cells.iterator();
200            }
201    
202    /*
203     * EVENT HANDLING
204     */
205    
206            public void addCellListener(CellListener listener) {
207                    cellListeners.add(listener);
208            }
209    
210            public void removeCellListener(CellListener listener) {
211                    cellListeners.remove(listener);
212            }
213    
214            public CellListener[] getCellListeners() {
215                    return cellListeners.toArray(new CellListener[cellListeners.size()]);
216            }
217    
218            /**
219             * A cell listener that forwards events from all cells to registered listeners.
220             */
221            private class EventForwarder implements CellListener {
222    
223                    /**
224                     * Creates a new event forwarder.
225                     */
226                    public EventForwarder() {}
227    
228                    public void valueChanged(Cell cell) {
229                            for (CellListener listener : cellListeners)
230                                    listener.valueChanged(cell);
231                    }
232    
233                    public void contentChanged(Cell cell) {
234                            for (CellListener listener : cellListeners)
235                                    listener.contentChanged(cell);
236                    }
237    
238                    public void dependentsChanged(Cell cell) {
239                            for (CellListener listener : cellListeners)
240                                    listener.dependentsChanged(cell);
241                    }
242    
243                    public void cellCleared(Cell cell) {
244                            for (CellListener listener : cellListeners)
245                                    listener.cellCleared(cell);
246                    }
247    
248                    public void cellCopied(Cell cell, Cell source) {
249                            for (CellListener listener : cellListeners)
250                                    listener.cellCopied(cell, source);
251                    }
252            }
253    
254    /*
255     * EXTENSIONS
256     */
257    
258            public Spreadsheet getExtension(String name) {
259                    // Looks for an existing spreadsheet extension
260                    SpreadsheetExtension extension = extensions.get(name);
261                    if (extension == null) {
262                            // Creates a new spreadsheet extension
263                            Extension x = ExtensionManager.getInstance().getExtension(name);
264                            if (x != null) {
265                                    extension = x.extend(this);
266                                    if (extension != null)
267                                            extensions.put(name, extension);
268                            }
269                    }
270                    return extension;
271            }
272    
273    /*
274     * GENERAL
275     */
276    
277            /**
278             * Customizes deserialization by catching exceptions when extensions
279             * are not found.
280             * @param stream the object input stream from which the object is to be read
281             * @throws IOException If any of the usual Input/Output related exceptions occur
282             * @throws ClassNotFoundException If the class of a serialized object cannot be found.
283             */
284            private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
285                stream.defaultReadObject();
286    
287                    // Sets up event forwarder
288                    eventForwarder = new EventForwarder();
289                    for (Cell cell : cells)
290                            cell.addCellListener(eventForwarder);
291                    cellListeners = new ArrayList<CellListener>();
292    
293                    // Reads extensions
294                    extensions = new HashMap<String, SpreadsheetExtension>();
295                    int extCount = stream.readInt();
296                    for (int i = 0; i < extCount; i++) {
297                            try {
298                                    SpreadsheetExtension extension = (SpreadsheetExtension)stream.readObject();
299                                    extensions.put(extension.getName(), extension);
300                            } catch (ClassNotFoundException e) {
301                                    System.err.println(e);
302                            }
303                    }
304            }
305    
306            /**
307             * Customizes serialization, by writing extensions separately.
308             * @param stream the object output stream to which the object is to be written
309             * @throws IOException If any of the usual Input/Output related exceptions occur
310             */
311            private void writeObject(ObjectOutputStream stream) throws IOException {
312                    stream.defaultWriteObject();
313    
314                    // Writes extensions
315                    stream.writeInt(extensions.size());
316                    for (SpreadsheetExtension extension : extensions.values())
317                            stream.writeObject(extension);
318            }
319    }