/* 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 java.io.IOException; import java.net.URL; import java.util.*; import org.jetbrains.annotations.Nullable; import org.languagetool.AnalyzedSentence; import org.languagetool.AnalyzedTokenReadings; import org.languagetool.JLanguageTool; import org.languagetool.Language; import org.languagetool.rules.patterns.PatternToken; import org.languagetool.tagging.disambiguation.rules.DisambiguationPatternRule; /** * Abstract rule class. A Rule describes a language error and can test whether a * given pre-analyzed text contains that error using the {@link Rule#match(AnalyzedSentence)} * method. * * <p>Rules are created whenever a {@link JLanguageTool} or * a {@link org.languagetool.MultiThreadedJLanguageTool} object is created. * As these objects are not thread-safe, this can happen often. Rules should thus * make sure that their initialization works fast. For example, if a rule needs * to load data from disk, it should store it in a static variable to make sure * the loading happens only once. * * @author Daniel Naber */ public abstract class Rule { protected final ResourceBundle messages; private List<CorrectExample> correctExamples = new ArrayList<>(); private List<IncorrectExample> incorrectExamples = new ArrayList<>(); private List<ErrorTriggeringExample> errorTriggeringExamples = new ArrayList<>(); private ITSIssueType locQualityIssueType = ITSIssueType.Uncategorized; private Category category; private URL url; private boolean defaultOff; public Rule() { this(null); } /** * Called by rules that require a translation of their messages. */ public Rule(ResourceBundle messages) { this.messages = messages; if (messages != null) { setCategory(Categories.MISC.getCategory(messages)); // the default, sub classes may overwrite this } else { setCategory(new Category(CategoryIds.MISC, "Misc")); } } /** * A string used to identify the rule in e.g. configuration files. * This string is supposed to be unique and to stay the same in all upcoming * versions of LanguageTool. It's supposed to contain only the characters {@code A-Z} * and the underscore. */ public abstract String getId(); /** * A short description of the error this rule can detect, usually in the language of the text * that is checked. */ public abstract String getDescription(); /** * Check whether the given sentence matches this error rule, i.e. whether it * contains the error detected by this rule. Note that the order in which * this method is called is not always guaranteed, i.e. the sentence order in the * text may be different than the order in which you get the sentences (this may be the * case when LanguageTool is used as a LibreOffice/OpenOffice add-on, for example). * * @param sentence a pre-analyzed sentence * @return an array of {@link RuleMatch} objects */ public abstract RuleMatch[] match(AnalyzedSentence sentence) throws IOException; /** * Overwrite this to avoid false alarms by ignoring these patterns - * note that your {@link #match(AnalyzedSentence)} method needs to * call {@link #getSentenceWithImmunization} for this to be used * and you need to check {@link AnalyzedTokenReadings#isImmunized()} * @since 3.1 */ public List<DisambiguationPatternRule> getAntiPatterns() { return Collections.emptyList(); } /** * To be called from {@link #match(AnalyzedSentence)} for rules that want * {@link #getAntiPatterns()} to be considered. * @since 3.1 */ protected AnalyzedSentence getSentenceWithImmunization(AnalyzedSentence sentence) { if (!getAntiPatterns().isEmpty()) { //we need a copy of the sentence, not reference to the old one AnalyzedSentence immunizedSentence = sentence.copy(sentence); for (DisambiguationPatternRule patternRule : getAntiPatterns()) { try { immunizedSentence = patternRule.replace(immunizedSentence); } catch (IOException e) { throw new RuntimeException(e); } } return immunizedSentence; } return sentence; } /** * Helper for implementing {@link #getAntiPatterns()}. * @since 3.1 */ protected List<DisambiguationPatternRule> makeAntiPatterns(List<List<PatternToken>> patternList, Language language) { List<DisambiguationPatternRule> rules = new ArrayList<>(); for (List<PatternToken> patternTokens : patternList) { rules.add(new DisambiguationPatternRule("INTERNAL_ANTIPATTERN", "(no description)", language, patternTokens, null, null, DisambiguationPatternRule.DisambiguatorAction.IMMUNIZE)); } return Collections.unmodifiableList(rules); } /** * Whether this rule can be used for text in the given language. * Since LanguageTool 2.6, this also works {@link org.languagetool.rules.patterns.PatternRule}s * (before, it used to always return {@code false} for those). */ public boolean supportsLanguage(Language language) { try { List<Class<? extends Rule>> relevantRuleClasses = new ArrayList<>(); List<Rule> relevantRules = language.getRelevantRules(JLanguageTool.getMessageBundle()); for (Rule relevantRule : relevantRules) { relevantRuleClasses.add(relevantRule.getClass()); } return relevantRuleClasses.contains(this.getClass()); } catch (IOException e) { throw new RuntimeException(e); } } /** * Whether this is a spelling rule that uses a dictionary. * Rules that return {@code true} here are basically rules that work like * a simple hunspell-like spellchecker: they check words without considering * the words' context. * @since 2.5 */ public boolean isDictionaryBasedSpellingRule() { return false; } /** * Whether this rule should be forced to be used in LO/OO extension. * Rules that return {@code true} will be enabled always in LO/OO extension * regardless of other options like isDictionaryBasedSpellingRule(). * @since 2.6 */ public boolean useInOffice() { return false; } /** * Set the examples that are correct and thus do not trigger the rule. */ public final void setCorrectExamples(List<CorrectExample> correctExamples) { this.correctExamples = Objects.requireNonNull(correctExamples); } /** * Get example sentences that are correct and thus will not match this rule. */ public final List<CorrectExample> getCorrectExamples() { return Collections.unmodifiableList(correctExamples); } /** * Set the examples that are incorrect and thus do trigger the rule. */ public final void setIncorrectExamples(List<IncorrectExample> incorrectExamples) { this.incorrectExamples = Objects.requireNonNull(incorrectExamples); } /** * Get example sentences that are incorrect and thus will match this rule. */ public final List<IncorrectExample> getIncorrectExamples() { return Collections.unmodifiableList(incorrectExamples); } /** * Set the examples that are correct but still trigger the rule due to an issue with the rule. * @since 3.5 */ public final void setErrorTriggeringExamples(List<ErrorTriggeringExample> examples) { this.errorTriggeringExamples = Objects.requireNonNull(examples); } /** * Get the examples that are correct but still trigger the rule due to an issue with the rule. * @since 3.5 */ public final List<ErrorTriggeringExample> getErrorTriggeringExamples() { return Collections.unmodifiableList(this.errorTriggeringExamples); } /** * @return a category (never null since LT 3.4) */ public final Category getCategory() { return category; } public final void setCategory(Category category) { this.category = Objects.requireNonNull(category, "category cannot be null"); } protected final RuleMatch[] toRuleMatchArray(List<RuleMatch> ruleMatches) { return ruleMatches.toArray(new RuleMatch[ruleMatches.size()]); } /** * Checks whether the rule has been turned off by default by the rule author. * @return True if the rule is turned off by default. */ public final boolean isDefaultOff() { return defaultOff; } /** * Turns the rule off by default. */ public final void setDefaultOff() { defaultOff = true; } /** * Turns the rule on by default. */ public final void setDefaultOn() { defaultOff = false; } /** * An optional URL describing the rule match in more detail. Typically points to a dictionary or grammar website * with explanations and examples. Will return {@code null} for rules that have no URL. * @since 1.8 */ @Nullable public URL getUrl() { return url; } /** * @since 1.8 * @see #getUrl() */ public void setUrl(URL url) { this.url = url; } /** * Returns the Localization Quality Issue Type, as defined * at <a href="http://www.w3.org/International/multilingualweb/lt/drafts/its20/its20.html#lqissue-typevalues" * >http://www.w3.org/International/multilingualweb/lt/drafts/its20/its20.html#lqissue-typevalues</a>. * * <p>Note that not all languages nor all rules actually map yet to a type yet. In those * cases, <tt>uncategorized</tt> is returned. * * @return the Localization Quality Issue Type - <tt>uncategorized</tt> if no type has been assigned * @since 2.5 */ public ITSIssueType getLocQualityIssueType() { return locQualityIssueType; } /** * Set the Localization Quality Issue Type. * @see #getLocQualityIssueType() * @since 2.5 */ public void setLocQualityIssueType(ITSIssueType locQualityIssueType) { this.locQualityIssueType = Objects.requireNonNull(locQualityIssueType); } /** * Convenience method to add a pair of sentences: an incorrect sentence and the same sentence * with the error corrected. * @since 2.5 */ protected void addExamplePair(IncorrectExample incorrectSentence, CorrectExample correctSentence) { incorrectExamples.add(incorrectSentence); correctExamples.add(correctSentence); } }