diff --git a/eo-maven-plugin/src/main/java/org/eolang/maven/optimization/OptCached.java b/eo-maven-plugin/src/main/java/org/eolang/maven/optimization/OptCached.java index f808fbfc94..5f69e64983 100644 --- a/eo-maven-plugin/src/main/java/org/eolang/maven/optimization/OptCached.java +++ b/eo-maven-plugin/src/main/java/org/eolang/maven/optimization/OptCached.java @@ -41,12 +41,19 @@ * Returns already optimized XML if it's found in the cache. * * @since 0.28.11 - * @todo #2746:30min Use checksum, not time. + * @todo #2746:30min Fix caching mechanism in {@link OptCached}. Current + * The last modified time of the files between stages may be different, + * so it is not correct to do an equality comparison ({@code .equals(...)}). + * The last modification time of the file at the current stage + * must be less than or equal to the last modification time of file in cache at the next stage. * The following tests show that fetching from the cache doesn't work correctly: * - {@link OptCachedTest#returnsFromCacheCorrectProgram(Path path)}, * - {@link OptCachedTest#returnsFromCacheButTimesSaveAndExecuteDifferent(Path path)}. - * Need to fix the file validation from cache: using checksum, but not time. * Don't forget to enable the tests. + * @todo #2746:30min Unify caching mechanism on stages: parse, optimize, pull and so on. + * Current implementations of caching on parsing stage and optimize stages work differently. + * In ParseMojo we have condition {@code if (tojo.hasHash()) }, in OptimizeMojo or ShakeMojo we + * compare creation time of files. */ public final class OptCached implements Optimization { diff --git a/eo-parser/src/main/java/org/eolang/parser/EoSyntax.java b/eo-parser/src/main/java/org/eolang/parser/EoSyntax.java index 6200d5f8d1..825354b85f 100644 --- a/eo-parser/src/main/java/org/eolang/parser/EoSyntax.java +++ b/eo-parser/src/main/java/org/eolang/parser/EoSyntax.java @@ -26,6 +26,7 @@ import com.jcabi.log.Logger; import com.jcabi.xml.XML; import com.jcabi.xml.XMLDocument; +import com.yegor256.xsline.Xsline; import java.io.IOException; import java.util.List; import org.antlr.v4.runtime.CommonTokenStream; @@ -70,7 +71,7 @@ public EoSyntax(final String nme, final Input ipt) { } /** - * Compile it to XML and save. + * Compile it to XML. * *

No exception will be thrown if the syntax is invalid. In any case, XMIR will * be generated and saved. Read it in order to find the errors, @@ -92,10 +93,12 @@ public XML parsed() throws IOException { parser.addErrorListener(spy); final XeEoListener xel = new XeEoListener(this.name); new ParseTreeWalker().walk(xel, parser.program()); - final XML dom = new XMLDocument( - new Xembler( - new Directives(xel).append(spy) - ).domQuietly() + final XML dom = new Xsline(new StHash()).pass( + new XMLDocument( + new Xembler( + new Directives(xel).append(spy) + ).domQuietly() + ) ); new Schema(dom).check(); if (spy.size() == 0) { diff --git a/eo-parser/src/main/java/org/eolang/parser/PhiSyntax.java b/eo-parser/src/main/java/org/eolang/parser/PhiSyntax.java index c06438cdd0..79a7265632 100644 --- a/eo-parser/src/main/java/org/eolang/parser/PhiSyntax.java +++ b/eo-parser/src/main/java/org/eolang/parser/PhiSyntax.java @@ -26,6 +26,7 @@ import com.jcabi.log.Logger; import com.jcabi.xml.XML; import com.jcabi.xml.XMLDocument; +import com.yegor256.xsline.Xsline; import java.io.IOException; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; @@ -84,10 +85,12 @@ public XML parsed() throws IOException { parser.removeErrorListeners(); parser.addErrorListener(spy); new ParseTreeWalker().walk(xel, parser.program()); - final XML dom = new XMLDocument( - new Xembler( - new Directives(xel).append(spy) - ).domQuietly() + final XML dom = new Xsline(new StHash()).pass( + new XMLDocument( + new Xembler( + new Directives(xel).append(spy) + ).domQuietly() + ) ); new Schema(dom).check(); if (spy.size() == 0) { diff --git a/eo-parser/src/main/java/org/eolang/parser/StHash.java b/eo-parser/src/main/java/org/eolang/parser/StHash.java new file mode 100644 index 0000000000..671e1ebaf7 --- /dev/null +++ b/eo-parser/src/main/java/org/eolang/parser/StHash.java @@ -0,0 +1,119 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016-2023 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.xml.XML; +import com.jcabi.xml.XMLDocument; +import com.yegor256.xsline.StEnvelope; +import com.yegor256.xsline.StLambda; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import org.xembly.Directives; +import org.xembly.Xembler; + +/** + * Add attribute 'hash' in node 'program' in XML. + * Returns already XML with hash. + * + * @since 0.35.0 + * + * @todo #2764:30min Replace the hash code entry + * from the XML file node attribute to the file attribute. + * This is necessary to increase the speed of searching for a cached file. + * The hash code entry should be deleted from EoSyntax.java and PhiSyntax.java. + * Hash code should save to the file attribute in ParseMojo.java and UnphiMojo. + * Delete tests containsHash() from EoSyntaxTest.java and PhiSyntaxTest.java. + * @todo #2764:30min Is it possible set the hash function directly + * in XeEoListener during the parsing? Now hash code is generated in class StHash.java. + * Maybe it will be better generating hash code in class XeEoListener. + */ +public final class StHash extends StEnvelope { + + /** + * Returns XML with attribute 'hash'. + */ + public StHash() { + super( + new StLambda(( + position, + xml) -> + new XMLDocument( + new Xembler( + new Directives().xpath("//program").attr("hash", new Hash(xml).compute()) + ).apply(xml.node()) + ) + ) + ); + } + + /** + * Hash is generated by MD5 using node '/program/objects'. + * Returns program hash. + * + * @since 0.35.0 + */ + public static final class Hash { + + /** + * XML for which the hash code will be made. + */ + private final XML xml; + + /** + * The constructor. + * + * @param xml XML for which the hash code will be made. + */ + public Hash(final XML xml) { + this.xml = xml; + } + + /** + * Return program hash using node "/program/objects". + * + * @return String hash of this XML. + * @throws NoSuchAlgorithmException If fails. + */ + public String compute() throws NoSuchAlgorithmException { + final BigInteger number = new BigInteger( + 1, + MessageDigest + .getInstance("MD5") + .digest( + this.xml.nodes( + "/program/objects" + ) + .toString().getBytes() + ) + ); + final StringBuilder hash = new StringBuilder(number.toString(16)); + while (hash.length() < 32) { + hash.insert(0, "0"); + } + return hash.toString(); + } + } +} + diff --git a/eo-parser/src/main/resources/XMIR.xsd b/eo-parser/src/main/resources/XMIR.xsd index 2032196dc3..6a7e95ff67 100644 --- a/eo-parser/src/main/resources/XMIR.xsd +++ b/eo-parser/src/main/resources/XMIR.xsd @@ -113,6 +113,7 @@ SOFTWARE. + diff --git a/eo-parser/src/test/java/org/eolang/parser/EoSyntaxTest.java b/eo-parser/src/test/java/org/eolang/parser/EoSyntaxTest.java index 6757a7fe3d..a86dacbff3 100644 --- a/eo-parser/src/test/java/org/eolang/parser/EoSyntaxTest.java +++ b/eo-parser/src/test/java/org/eolang/parser/EoSyntaxTest.java @@ -47,6 +47,7 @@ * * @since 0.1 */ +@SuppressWarnings("PMD.TooManyMethods") final class EoSyntaxTest { @Test void parsesSimpleCode() throws Exception { @@ -175,6 +176,18 @@ void parsesDefinition() throws IOException { ); } + @Test + void containsHash() throws IOException { + MatcherAssert.assertThat( + "XML file should have 'hash' attribute on program node, but didn't", + new EoSyntax( + "test-it-5", + new InputOf("[v] > p\n f.write > @\n") + ).parsed(), + XhtmlMatchers.hasXPath("/program/@hash") + ); + } + @Test void parsesMethodCalls() throws IOException { MatcherAssert.assertThat( diff --git a/eo-parser/src/test/java/org/eolang/parser/PhiSyntaxTest.java b/eo-parser/src/test/java/org/eolang/parser/PhiSyntaxTest.java index 835c490614..4da109a7dd 100644 --- a/eo-parser/src/test/java/org/eolang/parser/PhiSyntaxTest.java +++ b/eo-parser/src/test/java/org/eolang/parser/PhiSyntaxTest.java @@ -46,4 +46,15 @@ void addsError() throws IOException { ) ); } + + @Test + void containsHash() throws IOException { + MatcherAssert.assertThat( + "Result XML must contain hash", + new PhiSyntax( + "empty ↦ Φ.org.eolang.bytes" + ).parsed(), + XhtmlMatchers.hasXPath("/program/@hash") + ); + } } diff --git a/eo-parser/src/test/java/org/eolang/parser/StHashTest.java b/eo-parser/src/test/java/org/eolang/parser/StHashTest.java new file mode 100644 index 0000000000..cebde70156 --- /dev/null +++ b/eo-parser/src/test/java/org/eolang/parser/StHashTest.java @@ -0,0 +1,77 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016-2023 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.Xsline; +import java.security.NoSuchAlgorithmException; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Test; +import org.xembly.Directives; +import org.xembly.Xembler; + +/** + * Test case for {@link StHash}. + * + * @since 0.35.0 + */ +final class StHashTest { + + @Test + void isHashInXml() { + MatcherAssert.assertThat( + "We should get XML, which has new attribute 'hash', but didn't", + new Xsline(new StHash()).pass(program()), + XhtmlMatchers.hasXPath("/program/@hash") + ); + } + + @Test + void checksHash() throws NoSuchAlgorithmException { + MatcherAssert.assertThat( + "We should get the same hash code, but didn't", + new Xsline(new StHash()).pass(program()), + XhtmlMatchers.hasXPath("/program/@hash", new StHash.Hash(program()).compute()) + ); + } + + /** + * Generates EO program for tests with specified time and context. + * @return XML representation of program. + */ + private static XML program() { + return new XMLDocument( + new Xembler( + new Directives() + .add("program") + .attr("name", "main") + .add("objects") + .attr("object", "10") + .up() + ).xmlQuietly() + ); + } +}