From 10a006b7518752db16c9c85fb135b92a7b225436 Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Mon, 27 Jan 2025 11:07:32 +0300 Subject: [PATCH 1/5] feat(#3840): add a unit test for TrStepped --- .../java/org/eolang/parser/TrStepped.java | 2 +- .../java/org/eolang/parser/TrSteppedTest.java | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java diff --git a/eo-parser/src/main/java/org/eolang/parser/TrStepped.java b/eo-parser/src/main/java/org/eolang/parser/TrStepped.java index 20b1a337c87..4c8c3e62f00 100644 --- a/eo-parser/src/main/java/org/eolang/parser/TrStepped.java +++ b/eo-parser/src/main/java/org/eolang/parser/TrStepped.java @@ -37,7 +37,7 @@ import org.cactoos.text.TextOf; /** - * Trains that adds sheet names that were processed. + * Train that adds sheet names that were processed. * * @since 0.1 */ diff --git a/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java b/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java new file mode 100644 index 00000000000..8cacefbfe66 --- /dev/null +++ b/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java @@ -0,0 +1,59 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016-2025 Objectionary.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.eolang.parser; + +import com.jcabi.matchers.XhtmlMatchers; +import com.jcabi.xml.XML; +import com.jcabi.xml.XMLDocument; +import com.yegor256.xsline.Shift; +import com.yegor256.xsline.StClasspath; +import com.yegor256.xsline.TrDefault; +import com.yegor256.xsline.Xsline; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Test; + +/** + * Test cases for {@link TrStepped}. + * + * @since 0.51 + */ +final class TrSteppedTest { + + @Test + void addsSheetName() { + MatcherAssert.assertThat( + "We expect the sheet name to be added", + new Xsline( + new TrStepped( + new TrDefault().with( + new StClasspath("/org/eolang/parser/print/wrap-data.xsl")) + )).pass( + new XMLDocument("EO") + ).toString(), + XhtmlMatchers.hasXPath("/program/sheets/sheet[text()='wrap-data']") + ); + } + + +} \ No newline at end of file From 8c0fac828de6e8cfddd0a08db93eaea764abd8bf Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Mon, 27 Jan 2025 12:07:08 +0300 Subject: [PATCH 2/5] feat(#3840): identify concurrency issue --- .../java/org/eolang/parser/TrStepped.java | 9 +++- .../java/org/eolang/parser/TrSteppedTest.java | 48 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/eo-parser/src/main/java/org/eolang/parser/TrStepped.java b/eo-parser/src/main/java/org/eolang/parser/TrStepped.java index 4c8c3e62f00..67437ff7962 100644 --- a/eo-parser/src/main/java/org/eolang/parser/TrStepped.java +++ b/eo-parser/src/main/java/org/eolang/parser/TrStepped.java @@ -54,11 +54,16 @@ final class TrStepped extends TrEnvelope { ) ); + TrStepped(final Train train) { + this(train, TrStepped.STEPPED); + } + /** * Ctor. + * * @param train Original train */ - TrStepped(final Train train) { + TrStepped(final Train train, Scalar stepped) { super( new TrLambda( train, @@ -66,7 +71,7 @@ final class TrStepped extends TrEnvelope { shift, new StLambda( shift::uid, - (pos, xml) -> TrStepped.STEPPED.value() + (pos, xml) -> stepped.value() .with("step", pos) .with("sheet", shift.uid()) .transform(xml) diff --git a/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java b/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java index 8cacefbfe66..1d0f8cfb852 100644 --- a/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java +++ b/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java @@ -26,11 +26,24 @@ import com.jcabi.matchers.XhtmlMatchers; import com.jcabi.xml.XML; import com.jcabi.xml.XMLDocument; +import com.jcabi.xml.XSL; +import com.jcabi.xml.XSLDocument; +import com.yegor256.Together; import com.yegor256.xsline.Shift; import com.yegor256.xsline.StClasspath; import com.yegor256.xsline.TrDefault; +import com.yegor256.xsline.Train; import com.yegor256.xsline.Xsline; +import java.io.InputStream; +import java.net.URLClassLoader; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import org.cactoos.Input; +import org.cactoos.io.ResourceOf; +import org.cactoos.scalar.Sticky; +import org.cactoos.text.TextOf; import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; /** @@ -49,11 +62,44 @@ void addsSheetName() { new TrDefault().with( new StClasspath("/org/eolang/parser/print/wrap-data.xsl")) )).pass( - new XMLDocument("EO") + new XMLDocument("no") ).toString(), XhtmlMatchers.hasXPath("/program/sheets/sheet[text()='wrap-data']") ); } + @RepeatedTest(10) + void addsSheetNameConcurrently() { + final XML doc = new XMLDocument("yes"); + final CountDownLatch once = new CountDownLatch(1); + final Sticky loading = new Sticky<>( + () -> { + if (once.getCount() == 0) { + throw new IllegalStateException("Resource should be loaded only once"); + } + once.countDown(); + return new XSLDocument( + new TextOf( + () -> new ResourceOf("org/eolang/parser/_stepped.xsl").stream() + ).asString() + ); + } + ); + MatcherAssert.assertThat( + "We expect the sheet name to be added successfully in concurrent environment", + new Together<>( + i -> new Xsline( + new TrStepped( + new TrDefault().with( + new StClasspath("/org/eolang/parser/print/wrap-data.xsl") + ), + loading + ) + ).pass(doc).toString() + ).iterator().next(), + XhtmlMatchers.hasXPath("/program/sheets/sheet[text()='wrap-data']") + ); + } + } \ No newline at end of file From 795e16dac708f87c1075174c9d0bf343628d8c9a Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Mon, 27 Jan 2025 12:39:03 +0300 Subject: [PATCH 3/5] feat(#3840): add Once class --- .../java/org/eolang/parser/TrStepped.java | 37 +++++++++++++++++-- .../java/org/eolang/parser/TrSteppedTest.java | 29 ++++----------- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/eo-parser/src/main/java/org/eolang/parser/TrStepped.java b/eo-parser/src/main/java/org/eolang/parser/TrStepped.java index 67437ff7962..d2e8c1b09e2 100644 --- a/eo-parser/src/main/java/org/eolang/parser/TrStepped.java +++ b/eo-parser/src/main/java/org/eolang/parser/TrStepped.java @@ -31,6 +31,7 @@ import com.yegor256.xsline.TrEnvelope; import com.yegor256.xsline.TrLambda; import com.yegor256.xsline.Train; +import java.util.concurrent.CountDownLatch; import org.cactoos.Scalar; import org.cactoos.io.ResourceOf; import org.cactoos.scalar.Sticky; @@ -47,10 +48,12 @@ final class TrStepped extends TrEnvelope { * Apply changes to each XML after processing. */ private static final Scalar STEPPED = new Sticky<>( - () -> new XSLDocument( - new TextOf( - new ResourceOf("org/eolang/parser/_stepped.xsl") - ).asString() + new Once( + () -> new XSLDocument( + new TextOf( + new ResourceOf("org/eolang/parser/_stepped.xsl") + ).asString() + ) ) ); @@ -80,4 +83,30 @@ final class TrStepped extends TrEnvelope { ) ); } + + static class Once implements Scalar { + + private final Scalar origin; + private final CountDownLatch latch; + + Once(final Scalar origin) { + this(origin, new CountDownLatch(1)); + } + + private Once(final Scalar origin, final CountDownLatch latch) { + this.origin = origin; + this.latch = latch; + } + + @Override + public T value() throws Exception { + if (this.latch.getCount() < 1) { + throw new IllegalStateException( + String.format("Resource '%s' should be loaded only once", this.origin) + ); + } + this.latch.countDown(); + return this.origin.value(); + } + } } diff --git a/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java b/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java index 1d0f8cfb852..62b4f9c2405 100644 --- a/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java +++ b/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java @@ -32,13 +32,7 @@ import com.yegor256.xsline.Shift; import com.yegor256.xsline.StClasspath; import com.yegor256.xsline.TrDefault; -import com.yegor256.xsline.Train; import com.yegor256.xsline.Xsline; -import java.io.InputStream; -import java.net.URLClassLoader; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; -import org.cactoos.Input; import org.cactoos.io.ResourceOf; import org.cactoos.scalar.Sticky; import org.cactoos.text.TextOf; @@ -60,10 +54,10 @@ void addsSheetName() { new Xsline( new TrStepped( new TrDefault().with( - new StClasspath("/org/eolang/parser/print/wrap-data.xsl")) - )).pass( - new XMLDocument("no") - ).toString(), + new StClasspath("/org/eolang/parser/print/wrap-data.xsl") + ) + ) + ).pass(new XMLDocument("no")).toString(), XhtmlMatchers.hasXPath("/program/sheets/sheet[text()='wrap-data']") ); } @@ -71,19 +65,14 @@ void addsSheetName() { @RepeatedTest(10) void addsSheetNameConcurrently() { final XML doc = new XMLDocument("yes"); - final CountDownLatch once = new CountDownLatch(1); final Sticky loading = new Sticky<>( - () -> { - if (once.getCount() == 0) { - throw new IllegalStateException("Resource should be loaded only once"); - } - once.countDown(); - return new XSLDocument( + new TrStepped.Once( + () -> new XSLDocument( new TextOf( () -> new ResourceOf("org/eolang/parser/_stepped.xsl").stream() ).asString() - ); - } + ) + ) ); MatcherAssert.assertThat( "We expect the sheet name to be added successfully in concurrent environment", @@ -100,6 +89,4 @@ void addsSheetNameConcurrently() { XhtmlMatchers.hasXPath("/program/sheets/sheet[text()='wrap-data']") ); } - - } \ No newline at end of file From 77cdf87cb6edad6e1aa0c6b8f12ba9816e915b57 Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Mon, 27 Jan 2025 12:40:32 +0300 Subject: [PATCH 4/5] feat(#3840): sync access to the scalar --- eo-parser/src/main/java/org/eolang/parser/TrStepped.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eo-parser/src/main/java/org/eolang/parser/TrStepped.java b/eo-parser/src/main/java/org/eolang/parser/TrStepped.java index d2e8c1b09e2..f965f6d79ea 100644 --- a/eo-parser/src/main/java/org/eolang/parser/TrStepped.java +++ b/eo-parser/src/main/java/org/eolang/parser/TrStepped.java @@ -35,6 +35,7 @@ import org.cactoos.Scalar; import org.cactoos.io.ResourceOf; import org.cactoos.scalar.Sticky; +import org.cactoos.scalar.Synced; import org.cactoos.text.TextOf; /** @@ -74,7 +75,7 @@ final class TrStepped extends TrEnvelope { shift, new StLambda( shift::uid, - (pos, xml) -> stepped.value() + (pos, xml) -> new Synced<>(stepped).value() .with("step", pos) .with("sheet", shift.uid()) .transform(xml) From 6a85eb919ac5448b5685960be5c60c65a02f1eed Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Mon, 27 Jan 2025 12:43:50 +0300 Subject: [PATCH 5/5] feat(#3840): fix all the code offences --- .../java/org/eolang/parser/TrStepped.java | 34 +++++++++++++++++-- .../java/org/eolang/parser/TrSteppedTest.java | 2 +- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/eo-parser/src/main/java/org/eolang/parser/TrStepped.java b/eo-parser/src/main/java/org/eolang/parser/TrStepped.java index f965f6d79ea..01f4d70d365 100644 --- a/eo-parser/src/main/java/org/eolang/parser/TrStepped.java +++ b/eo-parser/src/main/java/org/eolang/parser/TrStepped.java @@ -58,6 +58,11 @@ final class TrStepped extends TrEnvelope { ) ); + /** + * Ctor. + * + * @param train Original train + */ TrStepped(final Train train) { this(train, TrStepped.STEPPED); } @@ -66,8 +71,9 @@ final class TrStepped extends TrEnvelope { * Ctor. * * @param train Original train + * @param stepped XSL to apply */ - TrStepped(final Train train, Scalar stepped) { + TrStepped(final Train train, final Scalar stepped) { super( new TrLambda( train, @@ -85,15 +91,39 @@ final class TrStepped extends TrEnvelope { ); } - static class Once implements Scalar { + /** + * Scalar that loads the value only once. + * + * @param Type of the value + * @since 0.51 + */ + static final class Once implements Scalar { + /** + * Origin scalar. + */ private final Scalar origin; + + /** + * Latch to count down. + */ private final CountDownLatch latch; + /** + * Ctor. + * + * @param origin Origin scalar + */ Once(final Scalar origin) { this(origin, new CountDownLatch(1)); } + /** + * Ctor. + * + * @param origin Origin scalar + * @param latch Latch to count down + */ private Once(final Scalar origin, final CountDownLatch latch) { this.origin = origin; this.latch = latch; diff --git a/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java b/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java index 62b4f9c2405..85c3fb95b65 100644 --- a/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java +++ b/eo-parser/src/test/java/org/eolang/parser/TrSteppedTest.java @@ -89,4 +89,4 @@ void addsSheetNameConcurrently() { XhtmlMatchers.hasXPath("/program/sheets/sheet[text()='wrap-data']") ); } -} \ No newline at end of file +}