com.google.common.css.SourceCodeLocation Maven / Gradle / Ivy
Show all versions of closure-stylesheets Show documentation
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.css;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Ints;
import javax.annotation.Nullable;
/**
* A location in source code. A location is a sequence of adjacent characters
* that usually represent a token or a larger language construct.
*
* Error messages represent the most common use of this class, as an error
* message usually relates to a source code location. The location related to
* some messages might not be known; this class has a special value that
* represents an "unknown" location.
*
*
Character sequences this class points to can have 0 length. If that is the
* case, the actual location is that "point" between two source code characters.
* This usually means that something happens from that point onwards, or that an
* error has been detected at that point but there is no information regarding
* the actual token that caused the error.
*
*
Instances of this class are immutable.
*
*/
public class SourceCodeLocation implements Comparable {
/**
* This describes a point in a string. A point is the location between two
* characters and is indicated by the character index of the immediately
* following character. For example, in the string "abc", point 0 refers to
* the location immediately before the 'a' and point 2 to the location before
* the 'c'.
*
* For convenience we also store the line number of the point (the line
* that contains the character following the point) and the index in that
* line. Both of these indices start at 1. The first line of a file is line 1
* and the point before its first character has index 1 on line 1.
*
*
The exact definition of lines depends on the language conventions and is
* best left for the parser to handle. If you want to display the text at that
* location, either use the character index or use the line number and the
* index in the line together with lines as they were split by the parser.
*
*
It might happen that the source code point for something is not known.
* This is modeled by a point with the special value -1 for the character
* index. The line and the index on the line must be 0 in this case.
*
*
Instances of this class are immutable.
*/
@VisibleForTesting
public static class SourceCodePoint implements Comparable {
/**
* The index of the character immediately after the source code point.
* Indices start at 0; -1 means the location is not known.
*/
private final int characterIndex;
/**
* The number of the line that contains the character at characterIndex.
* Numbers start at 1; 0 means the location is not known.
*/
private final int lineNumber;
/**
* The index in the line identified by lineNumber of the character at
* characterIndex. Numbers start at 1; 0 means the location is not known.
*/
private final int indexInLine;
SourceCodePoint(int characterIndex, int lineNumber, int indexInLine) {
this.lineNumber = lineNumber;
this.indexInLine = indexInLine;
this.characterIndex = characterIndex;
if (!hasValidKnownCoordinates()
&& !hasValidUnknownCoordinates()) {
throw new IllegalArgumentException(
String.format(
"The location passed "
+ "(lineNumber %d, indexInLine %d, characterIndex %d) "
+ "is not valid.",
lineNumber, indexInLine, characterIndex));
}
if (!hasPlausibleCoordinates()) {
throw new IllegalArgumentException(
String.format(
"The location passed "
+ "(lineNumber %d, indexInLine %d, characterIndex %d) "
+ "is not plausible.",
lineNumber, indexInLine, characterIndex));
}
}
SourceCodePoint(SourceCodePoint that) {
this(that.characterIndex, that.lineNumber, that.indexInLine);
}
boolean hasValidKnownCoordinates() {
return lineNumber >= 1 && indexInLine >= 1 && characterIndex >= 0;
}
boolean hasValidUnknownCoordinates() {
return characterIndex == -1 && lineNumber == 0 && indexInLine == 0;
}
boolean hasPlausibleCoordinates() {
return characterIndex >= lineNumber - 1 + indexInLine - 1;
}
int getCharacterIndex() {
return characterIndex;
}
int getLineNumber() {
return lineNumber;
}
int getIndexInLine() {
return indexInLine;
}
boolean isUnknown() {
return characterIndex == -1;
}
@Override
public boolean equals(@Nullable Object o) {
if (o == null) {
return false;
}
if (!(o instanceof SourceCodePoint)) {
return false;
}
SourceCodePoint other = (SourceCodePoint) o;
boolean areEqual = this.characterIndex == other.characterIndex;
if (areEqual) {
Preconditions.checkState((this.lineNumber == other.lineNumber)
&& (this.indexInLine == other.indexInLine),
"Character indexes are equal but line numbers and indexes within " +
"the line do not match.");
} else {
Preconditions.checkState((this.lineNumber != other.lineNumber)
|| (this.indexInLine != other.indexInLine),
"Line numbers and indexes within the line match but character " +
"indexes are not equal");
}
return areEqual;
}
@Override
public int hashCode() {
return characterIndex;
}
@Override
public int compareTo(SourceCodePoint o) {
Preconditions.checkNotNull(o);
return Ints.compare(this.characterIndex, o.characterIndex);
}
}
private static final SourceCode UNKNOWN_SOURCE_CODE =
new SourceCode("unknown", "");
/**
* Returns an unknown location.
*/
public static SourceCodeLocation getUnknownLocation() {
SourceCodeLocation result = new SourceCodeLocation(
UNKNOWN_SOURCE_CODE,
-1 /* beginCharacterIndex */,
0 /* beginLineNumber */,
0 /* beginIndexInLine */,
-1 /* endCharacterindex */,
0 /* endLineNumber */,
0 /* endIndexInLine */);
Preconditions.checkState(result.isUnknown());
Preconditions.checkState(result.begin.hasValidUnknownCoordinates());
Preconditions.checkState(result.end.hasValidUnknownCoordinates());
return result;
}
private final SourceCode sourceCode;
/**
* The sequence starts at the character immediately following the begin point.
*/
private final SourceCodePoint begin;
/**
* The sequence ends at the character immediately before the end point. The
* character immediately after the end point (the one indicated by the end's
* {@link SourceCodePoint#characterIndex}) is not part of the sequence. The
* empty sequence's begin point and end point are the same:
* {@code begin.equals(end)}.
*/
private final SourceCodePoint end;
@VisibleForTesting
public SourceCodeLocation(SourceCode sourceCode, int beginCharacterIndex,
int beginLineNumber, int beginIndexInLine, int endCharacterIndex,
int endLineNumber, int endIndexInLine) {
Preconditions.checkNotNull(sourceCode);
this.sourceCode = sourceCode;
this.begin = new SourceCodePoint(beginCharacterIndex, beginLineNumber,
beginIndexInLine);
this.end = new SourceCodePoint(endCharacterIndex, endLineNumber,
endIndexInLine);
Preconditions.checkArgument(begin.compareTo(end) <= 0,
"Beginning location must come before the end location.");
}
public SourceCode getSourceCode() {
return sourceCode;
}
public boolean isUnknown() {
Preconditions.checkState(begin.isUnknown() == end.isUnknown());
return begin.isUnknown();
}
public int getBeginCharacterIndex() {
return begin.getCharacterIndex();
}
public int getBeginLineNumber() {
return begin.getLineNumber();
}
public int getBeginIndexInLine() {
return begin.getIndexInLine();
}
public int getEndCharacterIndex() {
return end.getCharacterIndex();
}
public int getEndLineNumber() {
return end.getLineNumber();
}
public int getEndIndexInLine() {
return end.getIndexInLine();
}
public int getCharacterIndex() {
return getBeginCharacterIndex();
}
public int getLineNumber() {
return getBeginLineNumber();
}
public int getIndexInLine() {
return getBeginIndexInLine();
}
public SourceCodePoint getBegin() {
return begin;
}
public SourceCodePoint getEnd() {
return end;
}
@Override
public boolean equals(@Nullable Object o) {
if (o == null) {
return false;
}
if (!(o instanceof SourceCodeLocation)) {
return false;
}
SourceCodeLocation other = (SourceCodeLocation) o;
return sourceCode == other.sourceCode && begin.equals(other.begin)
&& end.equals(other.end);
}
@Override
public int hashCode() {
return sourceCode.hashCode() ^ begin.hashCode() ^ (end.hashCode() << 16);
}
/**
* Comparison and ordering of locations for source code in different
* input files is supported because we don't always preserve
* locations during AST mutations and yet we still want to be able
* to sort error reports, doing the best job we can for the errors
* that have known locations. For the semantics of this method, see
* {@link Comparable#compareTo(Object)}.
*/
@Override
public int compareTo(SourceCodeLocation o) {
Preconditions.checkNotNull(o);
if (sourceCode != o.sourceCode) {
if (sourceCode == null) {
return -1;
} else if (o.sourceCode == null) {
return 1;
} else {
return sourceCode.hashCode() - o.sourceCode.hashCode();
}
}
int startPointsComparison = begin.compareTo(o.begin);
if (startPointsComparison != 0) {
return startPointsComparison;
}
return end.compareTo(o.end);
}
}