@@ -298,7 +298,8 @@ public JLanguageTool(Language language, List<Language> altLanguages, Language mo
298298 GlobalConfig globalConfig , UserConfig userConfig , boolean inputLogging ) {
299299 this (language , altLanguages , motherTongue , cache , globalConfig , userConfig , true , false );
300300 }
301-
301+
302+
302303 /**
303304 * Create a JLanguageTool and setup the built-in rules for the
304305 * given language and false friend rules for the text language / mother tongue pair.
@@ -317,25 +318,51 @@ public JLanguageTool(Language language, List<Language> altLanguages, Language mo
317318 * @since 6.6
318319 */
319320 public JLanguageTool (Language language , List <Language > altLanguages , Language motherTongue , ResultCache cache , GlobalConfig globalConfig , UserConfig userConfig , boolean inputLogging , boolean withLanguageModel ) {
321+ this (language , altLanguages , motherTongue , cache , globalConfig , userConfig , inputLogging , withLanguageModel , null );
322+ }
323+
324+ /**
325+ * Create a JLanguageTool and setup the built-in rules for the
326+ * given language and false friend rules for the text language / mother tongue pair.
327+ *
328+ * @param language the language of the text to be checked
329+ * @param altLanguages The languages that are accepted as alternative languages - currently this means
330+ * words are accepted if they are in an alternative language and not similar to
331+ * a word from {@code language}. If there's a similar word in {@code language},
332+ * there will be an error of type {@link RuleMatch.Type#Hint} (EXPERIMENTAL)
333+ * @param motherTongue the user's mother tongue, used for false friend rules, or <code>null</code>.
334+ * The mother tongue may also be used as a source language for checking bilingual texts.
335+ * @param cache a cache to speed up checking if the same sentences get checked more than once,
336+ * e.g. when LT is running as a server and texts are re-checked due to changes
337+ * @param inputLogging allow inclusion of input in logs on exceptions
338+ * @param withLanguageModel will not call updateOptionalLanguageModelRules(null) if this is true
339+ * @param customRules rules to use for the JLanguageTool instance instead of initializing with the built-in ones, or null to use built-in rules
340+ * @since 6.6
341+ */
342+ public JLanguageTool (Language language , List <Language > altLanguages , Language motherTongue , ResultCache cache , GlobalConfig globalConfig , UserConfig userConfig , boolean inputLogging , boolean withLanguageModel , List <Rule > customRules ) {
320343 this .language = Objects .requireNonNull (language , "language cannot be null" );
321344 this .altLanguages = Objects .requireNonNull (altLanguages , "altLanguages cannot be null (but empty)" );
322345 this .motherTongue = motherTongue ;
323346 this .userConfig = Objects .requireNonNullElseGet (userConfig , UserConfig ::new );
324347 this .globalConfig = globalConfig ;
325- ResourceBundle messages = ResourceBundleTools .getMessageBundle (language );
326- builtinRules = getAllBuiltinRules (language , messages , userConfig , globalConfig );
327348 this .cleanOverlappingMatches = true ;
328- try {
329- activateDefaultPatternRules ();
330- if (!language .hasNGramFalseFriendRule (motherTongue )) {
331- // use the old false friends, which always match, not depending on context
332- activateDefaultFalseFriendRules ();
333- }
334- if (!withLanguageModel ) {
335- updateOptionalLanguageModelRules (null ); // start out with rules without language model
349+ ResourceBundle messages = ResourceBundleTools .getMessageBundle (language );
350+ if (customRules != null ) {
351+ builtinRules = new ArrayList <>(customRules );
352+ } else {
353+ builtinRules = getAllBuiltinRules (language , messages , userConfig , globalConfig );
354+ try {
355+ activateDefaultPatternRules ();
356+ if (!language .hasNGramFalseFriendRule (motherTongue )) {
357+ // use the old false friends, which always match, not depending on context
358+ activateDefaultFalseFriendRules ();
359+ }
360+ if (!withLanguageModel ) {
361+ updateOptionalLanguageModelRules (null ); // start out with rules without language model
362+ }
363+ } catch (Exception e ) {
364+ throw new RuntimeException ("Could not activate rules" , e );
336365 }
337- } catch (Exception e ) {
338- throw new RuntimeException ("Could not activate rules" , e );
339366 }
340367 this .cache = cache ;
341368 descProvider = new ShortDescriptionProvider ();
@@ -775,6 +802,20 @@ public void disableRules(List<String> ruleIds) {
775802 ruleSetCache .clear ();
776803 }
777804
805+ /**
806+ * Updates the rules for the system by replacing the user-defined rules with the provided set of rules.
807+ * Clears any existing user and built-in rules, as well as the cached rule set, before applying the new rules.
808+ *
809+ * @param rules a list of Rule objects to be set as the new user-defined rules
810+ * @since 6.8
811+ */
812+ public void setRules (List <Rule > rules ) {
813+ builtinRules .clear ();
814+ userRules .clear ();
815+ userRules .addAll (rules );
816+ ruleSetCache .clear ();
817+ }
818+
778819 /**
779820 * Disable the given rule category so the check methods like {@link #check(String)} won't use it.
780821 *
@@ -1038,9 +1079,15 @@ protected CheckResults checkInternal(AnnotatedText annotatedText, ParagraphHandl
10381079 }
10391080
10401081 protected CheckResults checkInternal (AnnotatedText annotatedText , ParagraphHandling paraMode , RuleMatchListener listener ,
1082+ Mode mode , Level level , @ NotNull Set <ToneTag > toneTags ,
1083+ @ Nullable Long textSessionID , List <String > sentences , List <AnalyzedSentence > analyzedSentences ) throws IOException {
1084+ RuleSet rules = getActiveRulesForLevelAndToneTags (level , toneTags );
1085+ return checkInternalWithCustomRules (rules , annotatedText , paraMode , listener , mode , level , toneTags , textSessionID , sentences , analyzedSentences );
1086+ }
1087+
1088+ public CheckResults checkInternalWithCustomRules (RuleSet rules , AnnotatedText annotatedText , ParagraphHandling paraMode , RuleMatchListener listener ,
10411089 Mode mode , Level level , @ NotNull Set <ToneTag > toneTags ,
10421090 @ Nullable Long textSessionID , List <String > sentences , List <AnalyzedSentence > analyzedSentences ) throws IOException {
1043- RuleSet rules = getActiveRulesForLevelAndToneTags (level , toneTags );
10441091 if (printStream != null ) {
10451092 printIfVerbose (rules .allRules ().size () + " rules activated for language " + language );
10461093 }
@@ -1304,7 +1351,7 @@ private RemoteRuleResult fetchResults(long deadlineStartNanos, Mode mode, Level
13041351 if (matches == null ) {
13051352 continue ;
13061353 }
1307- if (cache != null && result .isSuccess ()) {
1354+ if (cache != null && result .isSuccess () && result . adjustOffsets () ) {
13081355 // store in cache
13091356 InputSentence cacheKey = new InputSentence (
13101357 sentence , language , motherTongue , disabledRules , disabledRuleCategories ,
@@ -1318,8 +1365,10 @@ private RemoteRuleResult fetchResults(long deadlineStartNanos, Mode mode, Level
13181365 // clone matches before adjusting offsets
13191366 // match objects could be relevant to multiple (duplicate) sentences at different offsets
13201367 List <RuleMatch > adjustedMatches = matches .stream ().map (RuleMatch ::new ).collect (Collectors .toList ());
1321- for (RuleMatch match : adjustedMatches ) {
1322- adjustOffset (annotatedText , offset , match );
1368+ if (result .adjustOffsets ()) {
1369+ for (RuleMatch match : adjustedMatches ) {
1370+ adjustOffset (annotatedText , offset , match );
1371+ }
13231372 }
13241373 remoteMatches .addAll (adjustedMatches );
13251374 }
0 commit comments