From a608aa2e0571afa76e1a862acc3764bf0b27c610 Mon Sep 17 00:00:00 2001 From: VasilevNStas Date: Sun, 9 Aug 2026 16:12:54 +0300 Subject: [PATCH 1/2] fix(#297): make vocabulary thread-safe and return test-not-verb --- src/main/java/org/eolang/lints/MonoLints.java | 24 +++++++++++++++---- src/main/java/org/eolang/lints/PkMono.java | 5 ---- .../java/org/eolang/lints/Vocabulary.java | 21 +++++++--------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/eolang/lints/MonoLints.java b/src/main/java/org/eolang/lints/MonoLints.java index 1ce9c4bb9..5c858ecf4 100644 --- a/src/main/java/org/eolang/lints/MonoLints.java +++ b/src/main/java/org/eolang/lints/MonoLints.java @@ -4,6 +4,7 @@ */ package org.eolang.lints; +import java.io.IOException; import java.util.List; import java.util.stream.Collectors; import org.cactoos.iterable.IterableEnvelope; @@ -24,11 +25,7 @@ final class MonoLints extends IterableEnvelope { private static final Iterable LINTS = new Shuffled<>( new Joined( new PkByXsl(), - List.of( - new LtAsciiOnly(), - new LtReservedName(), - new LtSyntaxVersion() - ) + MonoLints.mono() ) ); @@ -60,4 +57,21 @@ final class MonoLints extends IterableEnvelope { ) ); } + + /** + * Java-based lints. + * @return Java-based lints + */ + private static List mono() { + try { + return List.of( + new LtAsciiOnly(), + new LtReservedName(), + new LtSyntaxVersion(), + new LtTestNotVerb() + ); + } catch (final IOException ex) { + throw new IllegalArgumentException(ex); + } + } } diff --git a/src/main/java/org/eolang/lints/PkMono.java b/src/main/java/org/eolang/lints/PkMono.java index 6118095ff..6616374be 100644 --- a/src/main/java/org/eolang/lints/PkMono.java +++ b/src/main/java/org/eolang/lints/PkMono.java @@ -18,11 +18,6 @@ *

This class is thread-safe.

* * @since 0.23 - * @todo #297:35min Return `LtTestNotVerb` back. - * For some reason this lint produces errors in EO-to-Java Compiler. Check - * this - * issue for more details. We should return it in the fixed state, once we understand - * the root cause of the problem. */ @ThreadSafe final class PkMono extends IterableEnvelope { diff --git a/src/main/java/org/eolang/lints/Vocabulary.java b/src/main/java/org/eolang/lints/Vocabulary.java index c821fdb88..7280640ec 100644 --- a/src/main/java/org/eolang/lints/Vocabulary.java +++ b/src/main/java/org/eolang/lints/Vocabulary.java @@ -30,9 +30,9 @@ final class Vocabulary { private static final Pattern KEBAB = Pattern.compile("-"); /** - * Part-Of-Speech tagger. + * Part-Of-Speech taggers, one per thread. */ - private final POSTaggerME tagger; + private final ThreadLocal taggers; /** * Ctor. @@ -53,15 +53,12 @@ final class Vocabulary { * @param mdl Part-Of-Speech model */ Vocabulary(final POSModel mdl) { - this(new POSTaggerME(mdl)); - } - - /** - * Ctor. - * @param pos Part-Of-Speech tagger - */ - Vocabulary(final POSTaggerME pos) { - this.tagger = pos; + this.taggers = new ThreadLocal<>() { + @Override + protected POSTaggerME initialValue() { + return new POSTaggerME(mdl); + } + }; } /** @@ -75,7 +72,7 @@ final class Vocabulary { */ boolean isVerb(final String name) { return "VBZ".equals( - this.tagger.tag( + this.taggers.get().tag( Stream.concat( Stream.of("It"), Arrays.stream(Vocabulary.KEBAB.split(name)) From bfea148d782d9abc45184cea94f8ab77abf3b6d6 Mon Sep 17 00:00:00 2001 From: VasilevNStas Date: Thu, 13 Aug 2026 18:33:11 +0300 Subject: [PATCH 2/2] fix(#297): rethrow with context, thread-safe tagger per review --- .../lints/LtUnlintNonExistingDefect.java | 5 +- src/main/java/org/eolang/lints/MonoLints.java | 9 ++- src/main/java/org/eolang/lints/PkByXsl.java | 58 ++++++++++++------- .../java/org/eolang/lints/Vocabulary.java | 38 +++++++----- .../java/org/eolang/lints/VocabularyTest.java | 30 ++++++++++ 5 files changed, 98 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/eolang/lints/LtUnlintNonExistingDefect.java b/src/main/java/org/eolang/lints/LtUnlintNonExistingDefect.java index dede3a1ad..6dc98eb8d 100644 --- a/src/main/java/org/eolang/lints/LtUnlintNonExistingDefect.java +++ b/src/main/java/org/eolang/lints/LtUnlintNonExistingDefect.java @@ -93,7 +93,10 @@ private Map> existingDefects(final XML xmir) { try { return lint.defects(xmir).stream(); } catch (final IOException exception) { - throw new IllegalStateException(exception); + throw new IllegalStateException( + String.format("Failed to apply lint %s to XMIR", lint.name()), + exception + ); } } ).collect( diff --git a/src/main/java/org/eolang/lints/MonoLints.java b/src/main/java/org/eolang/lints/MonoLints.java index 5c858ecf4..d3dfd86a0 100644 --- a/src/main/java/org/eolang/lints/MonoLints.java +++ b/src/main/java/org/eolang/lints/MonoLints.java @@ -25,7 +25,7 @@ final class MonoLints extends IterableEnvelope { private static final Iterable LINTS = new Shuffled<>( new Joined( new PkByXsl(), - MonoLints.mono() + MonoLints.javaLints() ) ); @@ -62,7 +62,7 @@ final class MonoLints extends IterableEnvelope { * Java-based lints. * @return Java-based lints */ - private static List mono() { + private static List javaLints() { try { return List.of( new LtAsciiOnly(), @@ -71,7 +71,10 @@ private static List mono() { new LtTestNotVerb() ); } catch (final IOException ex) { - throw new IllegalArgumentException(ex); + throw new IllegalStateException( + "Failed to instantiate Java-based lints (ascii-only, reserved-name, test-not-verb)", + ex + ); } } } diff --git a/src/main/java/org/eolang/lints/PkByXsl.java b/src/main/java/org/eolang/lints/PkByXsl.java index 4d30a949b..76f839977 100644 --- a/src/main/java/org/eolang/lints/PkByXsl.java +++ b/src/main/java/org/eolang/lints/PkByXsl.java @@ -52,7 +52,6 @@ final class PkByXsl extends IterableEnvelope { * Load all lints once. * @return List of all lints */ - @SuppressWarnings("PMD.UnnecessaryLocalRule") private static List load() { try { return Arrays.stream( @@ -60,29 +59,44 @@ private static List load() { "classpath*:org/eolang/lints/**/*.xsl" ) ).map( - res -> { - try { - final String url = res.getURL().toString(); - final String xsl = url.replaceAll(".*org/eolang/lints/", "") - .replaceAll("\\.xsl$", ""); - return new LtByXsl( - new InputOf(res.getInputStream()), - new InputOf( - PkByXsl.XSL_PATTERN.matcher( - PkByXsl.LINTS_PATH.matcher(url).replaceAll("eolang/motives") - ).replaceAll(".md") - ), - new FxResource( - String.format("org/eolang/fixes/%s.xsl", xsl) - ) - ); - } catch (final IOException ex) { - throw new IllegalArgumentException(ex); - } - } + PkByXsl::lint ).collect(Collectors.toList()); } catch (final IOException ex) { - throw new IllegalArgumentException(ex); + throw new IllegalArgumentException( + "Failed to load XSL lints from the classpath", + ex + ); + } + } + + /** + * Build a lint for the given XSL resource. + * @param res Resource with XSL + * @return Lint + */ + private static Lint lint( + final io.github.secretx33.resourceresolver.Resource res) { + try { + final String url = res.getURL().toString(); + return new LtByXsl( + new InputOf(res.getInputStream()), + new InputOf( + PkByXsl.XSL_PATTERN.matcher( + PkByXsl.LINTS_PATH.matcher(url).replaceAll("eolang/motives") + ).replaceAll(".md") + ), + new FxResource( + String.format( + "org/eolang/fixes/%s.xsl", + url.replaceAll(".*org/eolang/lints/", "").replaceAll("\\.xsl$", "") + ) + ) + ); + } catch (final IOException ex) { + throw new IllegalArgumentException( + "Failed to build a fix for an XSL lint", + ex + ); } } } diff --git a/src/main/java/org/eolang/lints/Vocabulary.java b/src/main/java/org/eolang/lints/Vocabulary.java index 7280640ec..fd9794fff 100644 --- a/src/main/java/org/eolang/lints/Vocabulary.java +++ b/src/main/java/org/eolang/lints/Vocabulary.java @@ -30,9 +30,14 @@ final class Vocabulary { private static final Pattern KEBAB = Pattern.compile("-"); /** - * Part-Of-Speech taggers, one per thread. + * Part-Of-Speech tagger. */ - private final ThreadLocal taggers; + private final POSTaggerME taggers; + + /** + * Lock guarding the tagger. + */ + private final java.util.concurrent.locks.ReentrantLock lock; /** * Ctor. @@ -53,12 +58,8 @@ final class Vocabulary { * @param mdl Part-Of-Speech model */ Vocabulary(final POSModel mdl) { - this.taggers = new ThreadLocal<>() { - @Override - protected POSTaggerME initialValue() { - return new POSTaggerME(mdl); - } - }; + this.taggers = new POSTaggerME(mdl); + this.lock = new java.util.concurrent.locks.ReentrantLock(); } /** @@ -71,13 +72,18 @@ protected POSTaggerME initialValue() { * @return True if the first word is a VBZ-tagged verb */ boolean isVerb(final String name) { - return "VBZ".equals( - this.taggers.get().tag( - Stream.concat( - Stream.of("It"), - Arrays.stream(Vocabulary.KEBAB.split(name)) - ).map(s -> s.toLowerCase(Locale.ROOT)).toArray(String[]::new) - )[1] - ); + this.lock.lock(); + try { + return "VBZ".equals( + this.taggers.tag( + Stream.concat( + Stream.of("It"), + Arrays.stream(Vocabulary.KEBAB.split(name)) + ).map(s -> s.toLowerCase(Locale.ROOT)).toArray(String[]::new) + )[1] + ); + } finally { + this.lock.unlock(); + } } } diff --git a/src/test/java/org/eolang/lints/VocabularyTest.java b/src/test/java/org/eolang/lints/VocabularyTest.java index 05ce6c3e2..56e9a3b50 100644 --- a/src/test/java/org/eolang/lints/VocabularyTest.java +++ b/src/test/java/org/eolang/lints/VocabularyTest.java @@ -4,9 +4,11 @@ */ package org.eolang.lints; +import com.yegor256.Together; import java.io.IOException; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.params.ParameterizedTest; @@ -108,4 +110,32 @@ void recognizesVerbName(final String name) throws IOException { Matchers.is(true) ); } + + @Test + void worksFromMultipleThreads() throws Exception { + final Vocabulary vocab = new Vocabulary(); + MatcherAssert.assertThat( + "The shared Vocabulary must give the same answer in all threads", + new org.cactoos.set.SetOf<>( + new Together<>( + t -> vocab.isVerb("works-as-expected") + ) + ).size(), + Matchers.equalTo(1) + ); + } + + @Test + void givesConsistentAnswersAcrossThreads() throws Exception { + final Vocabulary vocab = new Vocabulary(); + MatcherAssert.assertThat( + "The shared Vocabulary must stay stable under load", + new org.cactoos.set.SetOf<>( + new Together<>( + t -> vocab.isVerb("works-as-expected") + ) + ).size(), + Matchers.equalTo(1) + ); + } }