org.languagetool.rules.RuleMatch Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of languagetool-core Show documentation
Show all versions of languagetool-core Show documentation
LanguageTool is an Open Source proofreading software for English, French, German, Polish, Romanian, and more than 20 other languages. It finds many errors that a simple spell checker cannot detect like mixing up there/their and it detects some grammar problems.
/* LanguageTool, a natural language style checker
* Copyright (C) 2005 Daniel Naber (http://www.danielnaber.de)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package org.languagetool.rules;
import org.jetbrains.annotations.Nullable;
import org.languagetool.*;
import org.languagetool.tools.StringTools;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Information about an error rule that matches text and the position of the match.
* See {@link org.languagetool.tools.ContextTools} for displaying errors in their original text context.
*
* @author Daniel Naber
*/
public class RuleMatch implements Comparable {
private static final Pattern SUGGESTION_PATTERN = Pattern.compile("(.*?) ");
/**
* Unlike {@link Category}, this is specific to a RuleMatch, not to a rule.
* It is mainly used for selecting the underline color in clients.
* Note: this is experimental and might change soon (types might be added, deleted or renamed
* without deprecating them first)
* @since 4.3
*/
@Experimental
public enum Type {
/** Spelling errors, typically red. */
UnknownWord,
/** Style errors, typically light blue. */
Hint,
/** Other errors (including grammar), typically yellow/orange. */
Other
}
private final Rule rule;
private final OffsetPosition offsetPosition;
private final String message;
private final String shortMessage; // used e.g. for OOo/LO context menu
private final AnalyzedSentence sentence;
private LinePosition linePosition = new LinePosition(-1, -1);
private ColumnPosition columnPosition = new ColumnPosition(-1, -1);
private List suggestedReplacements = new ArrayList<>();
private URL url;
private List synonymsFor = null;
private Type type = Type.Other;
/**
* Creates a RuleMatch object, taking the rule that triggered
* this match, position of the match and an explanation message.
* This message is scanned for <suggestion>...</suggestion>
* to get suggested fixes for the problem detected by this rule.
* @deprecated use a constructor that also takes an {@code AnalyzedSentence} parameter (deprecated since 4.0)
*/
public RuleMatch(Rule rule, int fromPos, int toPos, String message) {
this(rule, fromPos, toPos, message, null, false, null);
}
/**
* Creates a RuleMatch object, taking the rule that triggered
* this match, position of the match and an explanation message.
* This message is scanned for <suggestion>...</suggestion>
* to get suggested fixes for the problem detected by this rule.
* @since 4.0
*/
public RuleMatch(Rule rule, AnalyzedSentence sentence, int fromPos, int toPos, String message) {
this(rule, sentence, fromPos, toPos, message, null, false, null);
}
/**
* Creates a RuleMatch object, taking the rule that triggered
* this match, position of the match and an explanation message.
* This message is scanned for <suggestion>...</suggestion>
* to get suggested fixes for the problem detected by this rule.
* @deprecated use a constructor that also takes an {@code AnalyzedSentence} parameter (deprecated since 4.0)
* @param shortMessage used for example in OpenOffice/LibreOffice's context menu
*/
public RuleMatch(Rule rule, int fromPos, int toPos, String message, String shortMessage) {
this(rule, fromPos, toPos, message, shortMessage, false, null);
}
/**
* Creates a RuleMatch object, taking the rule that triggered
* this match, position of the match and an explanation message.
* This message is scanned for <suggestion>...</suggestion>
* to get suggested fixes for the problem detected by this rule.
*
* @param shortMessage used for example in OpenOffice/LibreOffice's context menu
* @since 4.0
*/
public RuleMatch(Rule rule, AnalyzedSentence sentence, int fromPos, int toPos, String message, String shortMessage) {
this(rule, sentence, fromPos, toPos, message, shortMessage, false, null);
}
/**
* @deprecated use a constructor that also takes an {@code AnalyzedSentence} parameter (deprecated since 4.0)
*/
public RuleMatch(Rule rule, int fromPos, int toPos, String message, String shortMessage,
boolean startWithUppercase, String suggestionsOutMsg) {
this(rule, null, fromPos, toPos, message, shortMessage, startWithUppercase, suggestionsOutMsg);
}
/**
* Creates a RuleMatch object, taking the rule that triggered
* this match, position of the match and an explanation message.
* This message is scanned for <suggestion>...</suggestion>
* to get suggested fixes for the problem detected by this rule.
*
* @param fromPos error start position in original text
* @param toPos error end position in original text
* @param shortMessage used for example in OpenOffice/LibreOffice's context menu (may be null)
* @param startWithUppercase whether the original text at the position
* of the match starts with an uppercase character
* @since 4.0
*/
public RuleMatch(Rule rule, AnalyzedSentence sentence, int fromPos, int toPos, String message, String shortMessage,
boolean startWithUppercase, String suggestionsOutMsg) {
this.rule = Objects.requireNonNull(rule);
if (toPos <= fromPos) {
throw new RuntimeException("fromPos (" + fromPos + ") must be less than toPos (" + toPos + ")");
}
this.offsetPosition = new OffsetPosition(fromPos, toPos);
this.message = Objects.requireNonNull(message);
this.shortMessage = shortMessage;
// extract suggestion from ... in message:
Matcher matcher = SUGGESTION_PATTERN.matcher(message + suggestionsOutMsg);
int pos = 0;
while (matcher.find(pos)) {
pos = matcher.end();
String replacement = matcher.group(1);
if (startWithUppercase) {
replacement = StringTools.uppercaseFirstChar(replacement);
}
if (!suggestedReplacements.contains(replacement)) {
suggestedReplacements.add(replacement);
}
}
this.sentence = sentence;
}
public Rule getRule() {
return rule;
}
/**
* Set the line number in which the match occurs (zero-based).
*/
public void setLine(int fromLine) {
linePosition = new LinePosition(fromLine, linePosition.getEnd());
}
/**
* Get the line number in which the match occurs (zero-based).
* @deprecated rely on the character-based {@link #getFromPos()} instead (deprecated since 3.4)
*/
public int getLine() {
return linePosition.getStart();
}
/**
* Set the line number in which the match ends (zero-based).
*/
public void setEndLine(int endLine) {
linePosition = new LinePosition(linePosition.getStart(), endLine);
}
/**
* Get the line number in which the match ends (zero-based).
* @deprecated rely on {@link #getToPos()} instead (deprecated since 3.4)
*/
public int getEndLine() {
return linePosition.getEnd();
}
/**
* Set the column number in which the match occurs (zero-based).
* @deprecated (deprecated since 3.5)
*/
public void setColumn(int column) {
this.columnPosition = new ColumnPosition(column, columnPosition.getEnd());
}
/**
* Get the column number in which the match occurs (zero-based).
* @deprecated rely on the character-based {@link #getFromPos()} instead (deprecated since 3.4)
*/
public int getColumn() {
return columnPosition.getStart();
}
/**
* Set the column number in which the match ends (zero-based).
* @deprecated (deprecated since 3.5)
*/
public void setEndColumn(int endColumn) {
this.columnPosition = new ColumnPosition(columnPosition.getStart(), endColumn);
}
/**
* Get the column number in which the match ends (zero-based).
* @deprecated rely on {@link #getToPos()} instead (deprecated since 3.4)
*/
public int getEndColumn() {
return columnPosition.getEnd();
}
/**
* Position of the start of the error (in characters, zero-based, relative to the original input text).
*/
public int getFromPos() {
return offsetPosition.getStart();
}
/**
* Position of the end of the error (in characters, zero-based, relative to the original input text).
*/
public int getToPos() {
return offsetPosition.getEnd();
}
/**
* A human-readable explanation describing the error. This may contain
* one or more corrections marked up with <suggestion>...</suggestion>.
* @see #getSuggestedReplacements()
* @see #getShortMessage()
*/
public String getMessage() {
return message;
}
/**
* A shorter human-readable explanation describing the error or an empty string
* if no such explanation is available.
* @see #getMessage()
*/
@ApiCleanupNeeded("Should return an Optional")
public String getShortMessage() {
if (shortMessage == null) {
return ""; // just because this is what we have documented
}
return shortMessage;
}
/**
* @see #getSuggestedReplacements()
*/
public void setSuggestedReplacement(String replacement) {
Objects.requireNonNull(replacement, "replacement may be empty but not null");
List replacements = new ArrayList<>();
replacements.add(replacement);
setSuggestedReplacements(replacements);
}
/**
* @see #getSuggestedReplacements()
*/
public void setSuggestedReplacements(List replacements) {
this.suggestedReplacements = Objects.requireNonNull(replacements, "replacements may be empty but not null");
}
/**
* The text fragments which might be an appropriate fix for the problem. One
* of these fragments can be used to replace the old text between {@link #getFromPos()}
* to {@link #getToPos()}.
* @return unmodifiable list of String objects or an empty List
*/
public List getSuggestedReplacements() {
return Collections.unmodifiableList(suggestedReplacements);
}
/**
* A URL that points to a more detailed error description or {@code null}.
* Note that the {@link Rule} itself might also have an URL, which is usually
* a less specific one than this. This one will overwrite the rule's URL in
* the JSON output.
* @since 4.0
*/
@Nullable
public URL getUrl() {
return url;
}
/** @since 4.0 */
public void setUrl(URL url) {
this.url = url;
}
/** @since 4.0 */
public AnalyzedSentence getSentence() {
return sentence;
}
/**
* set lemmas from token for that synonyms should be suggested
* (only used by office extension)
* @since 4.3
*/
public void setSynonymsFor(AnalyzedTokenReadings token) {
if(token != null) {
List readings = token.getReadings();
synonymsFor = new ArrayList();
for (AnalyzedToken reading : readings) {
if (reading.getLemma() != null) {
synonymsFor.add(reading.getLemma());
}
}
String sToken = token.getToken();
if(synonymsFor.size() == 0) {
synonymsFor.add(sToken);
} else {
for (int i = 0; i < synonymsFor.size(); i++) {
if (sToken.equals(synonymsFor.get(i))) {
synonymsFor.remove(i);
synonymsFor.add(0, sToken);
}
}
}
}
}
/**
* set words for that synonyms should be suggested
* (only used by office extension)
* @since 4.3
*/
public void setSynonymsFor(List words) {
synonymsFor = words;
}
/**
* get all words for that synonyms should be suggested
* (only used by office extension)
* @since 4.3
*/
public List getSynonymsFor() {
return synonymsFor;
}
/**
* @since 4.3
*/
@Experimental
public void setType(Type type) {
this.type = Objects.requireNonNull(type);
}
/**
* @since 4.3
*/
@Experimental
public Type getType() {
return this.type;
}
@Override
public String toString() {
return rule.getId() + ":" + offsetPosition + ":" + message;
}
/** Compare by start position. */
@Override
public int compareTo(RuleMatch other) {
Objects.requireNonNull(other);
return Integer.compare(getFromPos(), other.getFromPos());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RuleMatch other = (RuleMatch) o;
return Objects.equals(rule.getId(), other.rule.getId())
&& Objects.equals(offsetPosition, other.offsetPosition)
&& Objects.equals(message, other.message)
&& Objects.equals(suggestedReplacements, other.suggestedReplacements)
&& Objects.equals(sentence, other.sentence)
&& Objects.equals(type, other.type)
&& Objects.equals(synonymsFor, other.synonymsFor);
}
@Override
public int hashCode() {
return Objects.hash(rule.getId(), offsetPosition, message, suggestedReplacements, sentence, type);
}
static class OffsetPosition extends MatchPosition {
OffsetPosition(int start, int end) {
super(start, end);
}
}
static class LinePosition extends MatchPosition {
LinePosition(int start, int end) {
super(start, end);
}
}
static class ColumnPosition extends MatchPosition {
ColumnPosition(int start, int end) {
super(start, end);
}
}
}