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}