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 }