001    /*
002     * Copyright (c) 2007 Mozilla Foundation
003     *
004     * Permission is hereby granted, free of charge, to any person obtaining a 
005     * copy of this software and associated documentation files (the "Software"), 
006     * to deal in the Software without restriction, including without limitation 
007     * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
008     * and/or sell copies of the Software, and to permit persons to whom the 
009     * Software is furnished to do so, subject to the following conditions:
010     *
011     * The above copyright notice and this permission notice shall be included in 
012     * all copies or substantial portions of the Software.
013     *
014     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
015     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
016     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
017     * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
018     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
019     * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
020     * DEALINGS IN THE SOFTWARE.
021     */
022    
023    package nu.validator.source;
024    
025    /**
026     * Immutable source location with zero-based indexes. 
027     * Cannot point to a line break.
028     * 
029     * @version $Id$
030     * @author hsivonen
031     */
032    public final class Location implements Comparable<Location>, Cloneable {
033    
034        private final SourceCode owner;
035        private final int line;
036        private final int column;
037        
038        /**
039         * @param line
040         * @param column
041         */
042        Location(final SourceCode owner, int line, int column) {
043            this.owner = owner;
044            if (line < 0) {
045                line = 0;
046                column = 0;
047            } else if (column < 0) {
048                line--;
049                if (line < 0) {
050                    line = 0;
051                    column = 0;                
052                } else {
053                    column = owner.getLine(line).getBufferLength();
054                }
055            }
056            this.line = line;
057            this.column = column;
058        }
059    
060        public int compareTo(Location o) {
061            if (this.line < o.line) {
062                return -1;
063            } else if (this.line > o.line) {
064                return 1;
065            } else {
066                if (this.column < o.column) {
067                    return -1;
068                } else if (this.column > o.column) {
069                    return 1;
070                } else {
071                    return 0;
072                }
073            }
074        }
075    
076        /**
077         * @see java.lang.Object#equals(java.lang.Object)
078         */
079        @Override
080        public boolean equals(Object obj) {
081            if (obj instanceof Location) {
082                Location loc = (Location) obj;
083                return this.line == loc.line && this.column == loc.column;
084            } else {
085                return false;
086            }
087        }
088    
089        /**
090         * Returns the column.
091         * 
092         * @return the column
093         */
094        public int getColumn() {
095            return column;
096        }
097    
098        /**
099         * Returns the line.
100         * 
101         * @return the line
102         */
103        public int getLine() {
104            return line;
105        }
106    
107        /**
108         * 
109         * @see java.lang.Object#hashCode()
110         */
111        @Override
112        public int hashCode() {
113            return (line << 16) + column;
114        }
115        
116        Location next() {
117            return step(1);
118        }
119        
120        Location prev() {
121            return step(-1);
122        }
123        
124        Location step(int offset) {
125            int newLine = line;
126            int newColumn = column;
127            if (offset > 0) {
128                for (int i = 0; i < offset; i++) {
129                    if (newLine == owner.getNumberOfLines()) {
130                        break;
131                    }
132                    newColumn++;
133                    Line sourceLine = owner.getLine(newLine);
134                    if (newColumn > sourceLine.getBufferLength()) {
135                        newLine++;
136                        newColumn = 0;
137                    }
138                }
139                return new Location(owner, newLine, newColumn);
140            } if (offset < 0) {
141                offset = -offset;
142                for (int i = 0; i < offset; i++) {
143                    if (newLine == 0 && newColumn == 0) {
144                        break;
145                    }
146                    newColumn--;
147                    if (newColumn == -1) {
148                        newLine--;
149                        newColumn = owner.getLine(newLine).getBufferLength();
150                    }
151                }            
152                return new Location(owner, newLine, newColumn);
153            } else {
154                return this;
155            }
156        }
157    
158        /**
159         * @see java.lang.Object#toString()
160         */
161        @Override
162        public String toString() {
163            return line + ", " + column;
164        }
165    }