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.formula.lang;
022
023import java.text.ParseException;
024import java.util.SortedSet;
025import java.util.TreeSet;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028
029import csheets.core.Address;
030import csheets.core.Cell;
031import csheets.core.Spreadsheet;
032import csheets.core.Value;
033import csheets.core.formula.Reference;
034import csheets.core.formula.util.ExpressionVisitor;
035import csheets.core.formula.util.ExpressionVisitorException;
036
037/**
038 * A reference to a cell in a spreadsheet.
039 * @author Einar Pehrson
040 */
041public class CellReference implements Reference {
042
043        /** The unique version identifier used for serialization */
044        private static final long serialVersionUID = -6600693551615086696L;
045
046        /**
047         * The regular expression pattern used to match cell references:
048         * (\\$??)([a-zA-Z]+)(\\$??)(\\d+)$")
049         */
050        private static final Pattern PATTERN = Pattern.compile(
051                "(\\$??)([a-zA-Z]+)(\\$??)(\\d+)$");
052
053        /** The string used to match the use of absolute references */
054        private static final String ABSOLUTE_OPERATOR = "$";
055
056        /** The cell to which the reference points */
057        private Cell cell;
058
059        /** If the column is denoted with an absolute reference */
060        private boolean columnAbsolute;
061
062        /** If the row is denoted with an absolute reference */
063        private boolean rowAbsolute;
064
065        /**
066         * Creates a new cell reference to the given address.
067         * By default, relative addressing is used.
068         * @param cell the cell to which the reference points
069         */
070        public CellReference(Cell cell) {
071                this(cell, false, false);
072        }
073
074        /**
075         * Creates a new cell reference to the given address, using the given
076         * reference mode.
077         * @param cell the cell to which the reference points
078         * @param columnAbsolute if the column is denoted with an absolute reference
079         * @param rowAbsolute if the column is denoted with an absolute reference
080         */
081        public CellReference(Cell cell, boolean columnAbsolute, boolean rowAbsolute) {
082                this.cell = cell;
083                this.columnAbsolute = columnAbsolute;
084                this.rowAbsolute = rowAbsolute;
085        }
086
087        /**
088         * Creates a new cell reference from a string matching the (@link #PATTERN).
089         * @param spreadsheet the spreadsheet of the cell
090         * @param reference a string representation of the reference
091         * @throws ParseException if the string did not match the pattern
092         */
093        public CellReference(Spreadsheet spreadsheet, String reference) throws ParseException {
094                // Matches the expression
095                Matcher matcher = PATTERN.matcher(reference);
096                if (matcher.matches()) {
097
098                        // Parses row and column indices
099                        int row = Integer.parseInt(matcher.group(4)) - 1;
100                        int column = -1;
101                        String columnStr = matcher.group(2).toUpperCase();
102                        for (int i = columnStr.length() - 1; i >= 0; i--)
103                                column += (columnStr.charAt(i) - Address.LOWEST_CHAR + 1)
104                                        * Math.pow(Address.HIGHEST_CHAR - Address.LOWEST_CHAR + 1,
105                                        columnStr.length() - (i + 1));
106
107                        // Stores members
108                        this.cell = spreadsheet.getCell(new Address(column, row));
109                        this.columnAbsolute = matcher.group(1).equals("$");
110                        this.rowAbsolute = matcher.group(3).equals("$");
111                } else
112                        throw new ParseException(reference, 0);
113        }
114
115        public Value evaluate() {
116                return cell.getValue();
117        }
118
119        public Object accept(ExpressionVisitor visitor) throws ExpressionVisitorException {
120                return visitor.visitReference(this);
121        }
122
123        /**
124         * Returns the cell to which the reference points.
125         * @return the cell to which the reference points
126         */
127        public Cell getCell() {
128                return cell;
129        }
130
131        public SortedSet<Cell> getCells() {
132                SortedSet<Cell> cells = new TreeSet<Cell>();
133                cells.add(cell);
134                return cells;
135        }
136
137        /**
138         * Returns whether the column is denoted with an absolute reference.
139         * @return true if the column is denoted with an absolute reference.
140         */
141        public boolean isColumnAbsolute() {
142                return columnAbsolute;
143        }
144
145        /**
146         * Returns whether the row is denoted with an absolute reference.
147         * @return true if the row is denoted with an absolute reference.
148         */
149        public boolean isRowAbsolute() {
150                return rowAbsolute;
151        }
152
153        /**
154         * Compares the cell reference with the given cell reference for order.
155         * @param reference the reference to be compared
156         * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
157         */
158        public int compareTo(Reference reference) {
159                Cell otherCell = reference.getCells().first();
160                int firstDiff = cell.compareTo(otherCell);
161                if (firstDiff != 0)
162                        return firstDiff;
163                else {
164                        if (reference instanceof CellReference) {
165                                // Handle reference modes?
166                                return -1;
167                        } else
168                                return -1;
169                }
170        }
171
172        /**
173         * Returns a string representation of the address of the cell reference 
174         * on the form "B22", composed of the letter of the column and number of
175         * the row that intersect to form the address.   
176         * @return a string representation of the address of the cell reference
177         */
178        public String toString() {
179                // Converts column
180                String columnStr = "";
181                for (int tempColumn = cell.getAddress().getColumn();
182                                tempColumn >= 0; tempColumn = tempColumn
183                                        / (Address.HIGHEST_CHAR - Address.LOWEST_CHAR + 1) - 1)
184                        columnStr = ((char)((char)(tempColumn % (Address.HIGHEST_CHAR
185                                - Address.LOWEST_CHAR + 1)) + Address.LOWEST_CHAR)) + columnStr;
186                if (columnAbsolute)
187                        columnStr = ABSOLUTE_OPERATOR + columnStr;
188
189                // Converts row
190                String rowStr = (rowAbsolute ? ABSOLUTE_OPERATOR : "")
191                        + (cell.getAddress().getRow() + 1);
192                return columnStr + rowStr;
193        }
194}