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.formula.lang;
022    
023    import java.text.ParseException;
024    import java.util.SortedSet;
025    import java.util.TreeSet;
026    import java.util.regex.Matcher;
027    import java.util.regex.Pattern;
028    
029    import csheets.core.Address;
030    import csheets.core.Cell;
031    import csheets.core.Spreadsheet;
032    import csheets.core.Value;
033    import csheets.core.formula.Reference;
034    import csheets.core.formula.util.ExpressionVisitor;
035    import csheets.core.formula.util.ExpressionVisitorException;
036    
037    /**
038     * A reference to a cell in a spreadsheet.
039     * @author Einar Pehrson
040     */
041    public 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    }