001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.common.text;
017
018import org.modeshape.common.annotation.Immutable;
019
020/**
021 * A class that represents the position of a particular character in terms of the lines and columns of a character sequence.
022 */
023@Immutable
024public final class Position {
025
026    /**
027     * The position is used when there is no content.
028     */
029    public static final Position EMPTY_CONTENT_POSITION = new Position(-1, 1, 0);
030
031    private final int line;
032    private final int column;
033    private final int indexInContent;
034
035    public Position( int indexInContent,
036                     int line,
037                     int column ) {
038        this.indexInContent = indexInContent < 0 ? -1 : indexInContent;
039        this.line = line;
040        this.column = column;
041
042        assert this.indexInContent >= -1;
043        assert this.line > 0;
044        assert this.column >= 0;
045        // make sure that negative index means an EMPTY_CONTENT_POSITION
046        assert this.indexInContent < 0 ? this.line == 1 && this.column == 0 : true;
047    }
048
049    /**
050     * Get the 0-based index of this position in the content character array.
051     * 
052     * @return the index; never negative except for the first position in an empty content.
053     */
054    public int getIndexInContent() {
055        return indexInContent;
056    }
057
058    /**
059     * Get the 1-based column number of the character.
060     * 
061     * @return the column number; always positive
062     */
063    public int getColumn() {
064        return column;
065    }
066
067    /**
068     * Get the 1-based line number of the character.
069     * 
070     * @return the line number; always positive
071     */
072    public int getLine() {
073        return line;
074    }
075
076    @Override
077    public int hashCode() {
078        return indexInContent;
079    }
080
081    @Override
082    public String toString() {
083        return "" + indexInContent + ':' + line + ':' + column;
084    }
085
086    /**
087     * Return a new position that is the addition of this position and that supplied.
088     * 
089     * @param position the position to add to this object; may not be null
090     * @return the combined position
091     */
092    public Position add( Position position ) {
093        if (this.getIndexInContent() < 0) {
094            return position.getIndexInContent() < 0 ? EMPTY_CONTENT_POSITION : position;
095        }
096
097        if (position.getIndexInContent() < 0) {
098            return this;
099        }
100
101        int index = this.getIndexInContent() + position.getIndexInContent();
102        int line = position.getLine() + this.getLine() - 1;
103        int column = this.getLine() == 1 ? this.getColumn() + position.getColumn() : this.getColumn();
104
105        return new Position(index, line, column);
106    }
107}