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