From b2c2c84f0424f15c98cccb22b1446c893315af41 Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Fri, 29 Nov 2024 16:01:59 +0300 Subject: [PATCH 01/11] feat(#889): add a performance unit test --- .../representation/xmir/XmlInstruction.java | 8 ---- .../XmirRepresentationTest.java | 40 +++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/eolang/jeo/representation/xmir/XmlInstruction.java b/src/main/java/org/eolang/jeo/representation/xmir/XmlInstruction.java index de9062bc5..f41b76489 100644 --- a/src/main/java/org/eolang/jeo/representation/xmir/XmlInstruction.java +++ b/src/main/java/org/eolang/jeo/representation/xmir/XmlInstruction.java @@ -62,14 +62,6 @@ public final class XmlInstruction implements XmlBytecodeEntry { ); } - /** - * Constructor. - * @param xml XML string that represents an instruction. - */ - XmlInstruction(final String xml) { - this(new XmlNode(xml)); - } - /** * Constructor. * @param xmlnode Instruction node. diff --git a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java index 9514646f1..490c487da 100644 --- a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java +++ b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java @@ -23,6 +23,7 @@ */ package org.eolang.jeo.representation; +import com.jcabi.log.Logger; import com.jcabi.matchers.XhtmlMatchers; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -34,6 +35,7 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -153,4 +155,42 @@ void failsToOpenBrokenXmirRepresentationFromFile(@TempDir final Path dir) throws ) ); } + + /** + * This is a performance test, which is disabled by default. + * It is used to measure the performance of the conversion of the EO object + * into the bytecode representation and back. + * Timings before the optimization were: 21s, 23s, 21s, 21s (500 attempts) + */ + @Test + @Disabled + void convertsToXmirAndBack() { + final Bytecode before = new BytecodeProgram( + new BytecodeClass("org/eolang/foo/Math") + .helloWorldMethod() + ).bytecode(); + final int attempts = 500; + final long start = System.currentTimeMillis(); + for (int current = 0; current < attempts; ++current) { + final Bytecode actual = new XmirRepresentation( + new BytecodeRepresentation( + before + ).toEO() + ).toBytecode(); + MatcherAssert.assertThat( + String.format(XmirRepresentationTest.MESSAGE, before, actual), + actual, + Matchers.equalTo(before) + ); + } + final long end = System.currentTimeMillis(); + final long l = end - start; + Logger.info( + this, + "We made %d attempts to convert bytecode to xmir and back in %[ms]s", + attempts, + l + ); + + } } From 20ebb08abe21082661633b3f25ce35685757eb5d Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Fri, 29 Nov 2024 16:18:36 +0300 Subject: [PATCH 02/11] feat(#889): first optimization round --- .../jeo/representation/xmir/XmlNode.java | 35 +++++++++---------- .../XmirRepresentationTest.java | 3 +- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/eolang/jeo/representation/xmir/XmlNode.java b/src/main/java/org/eolang/jeo/representation/xmir/XmlNode.java index db430ff7b..6d78faba7 100644 --- a/src/main/java/org/eolang/jeo/representation/xmir/XmlNode.java +++ b/src/main/java/org/eolang/jeo/representation/xmir/XmlNode.java @@ -46,7 +46,7 @@ public final class XmlNode { /** * Parent node. */ - private final XML node; + private final Node node; /** * Constructor. @@ -56,19 +56,11 @@ public XmlNode(final String xml) { this(new XMLDocument(xml).node().getFirstChild()); } - /** - * Constructor. - * @param parent XML document - */ - public XmlNode(final Node parent) { - this(new XMLDocument(parent)); - } - /** * Constructor. * @param parent Parent node. */ - public XmlNode(final XML parent) { + public XmlNode(final Node parent) { this.node = parent; } @@ -76,7 +68,7 @@ public XmlNode(final XML parent) { public boolean equals(final Object obj) { final boolean res; if (obj instanceof XmlNode) { - res = this.node.equals(((XmlNode) obj).node); + res = new XMLDocument(this.node).equals(new XMLDocument(((XmlNode) obj).node)); } else { res = false; } @@ -106,7 +98,7 @@ public Stream children() { * @return Text content. */ public String text() { - return this.node.node().getTextContent(); + return this.node.getTextContent(); } /** @@ -116,7 +108,7 @@ public String text() { */ public Optional attribute(final String name) { final Optional result; - final NamedNodeMap attrs = this.node.node().getAttributes(); + final NamedNodeMap attrs = this.node.getAttributes(); if (attrs == null) { result = Optional.empty(); } else { @@ -140,7 +132,7 @@ XmlNode child(final String name) { * @return List of elements. */ List xpath(final String xpath) { - return this.node.xpath(xpath); + return new XMLDocument(this.node).xpath(xpath); } /** @@ -206,7 +198,7 @@ boolean hasAttribute(final String name, final String value) { * @return Class. */ XmlClass toClass() { - return new XmlClass(this.node.node()); + return new XmlClass(this.node); } /** @@ -233,9 +225,14 @@ XmlBytecodeEntry toEntry() { */ private Optional optchild(final String name) { Optional result = Optional.empty(); - final List nodes = this.node.nodes(name); - if (!nodes.isEmpty()) { - result = Optional.of(new XmlNode(nodes.get(0))); + final NodeList children = this.node.getChildNodes(); + final int length = children.getLength(); + for (int index = 0; index < length; ++index) { + final Node current = children.item(index); + if (current.getNodeName().equals(name)) { + result = Optional.of(new XmlNode(current)); + break; + } } return result; } @@ -260,7 +257,7 @@ private IllegalStateException notFound(final String name) { * @return Stream of class objects. */ private Stream objects() { - final NodeList children = this.node.node().getChildNodes(); + final NodeList children = this.node.getChildNodes(); final List res = new ArrayList<>(children.getLength()); for (int index = 0; index < children.getLength(); ++index) { final Node child = children.item(index); diff --git a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java index 490c487da..4440303d7 100644 --- a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java +++ b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java @@ -160,7 +160,8 @@ void failsToOpenBrokenXmirRepresentationFromFile(@TempDir final Path dir) throws * This is a performance test, which is disabled by default. * It is used to measure the performance of the conversion of the EO object * into the bytecode representation and back. - * Timings before the optimization were: 21s, 23s, 21s, 21s (500 attempts) + * 1) Timings before the optimization were: 21s, 23s, 21s, 21s (500 attempts) + * 2) `XMLDocument` -> `org.w3c.Node` optimization: 11s, 10s, 11s, 10s (500 attempts) */ @Test @Disabled From 08c1050f463baaf94f1a2d1de24b2fe54f8940f7 Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Fri, 29 Nov 2024 16:47:17 +0300 Subject: [PATCH 03/11] feat(#889): speed up DirectivesSeq --- .../directives/DirectivesSeq.java | 15 ++---- .../jeo/representation/xmir/XmlNode.java | 1 - .../XmirRepresentationTest.java | 3 +- .../directives/DirectivesSeqTest.java | 49 +++++++++++++++++++ 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/eolang/jeo/representation/directives/DirectivesSeq.java b/src/main/java/org/eolang/jeo/representation/directives/DirectivesSeq.java index acd00e918..09371752e 100644 --- a/src/main/java/org/eolang/jeo/representation/directives/DirectivesSeq.java +++ b/src/main/java/org/eolang/jeo/representation/directives/DirectivesSeq.java @@ -82,21 +82,16 @@ private DirectivesSeq(final String name, final Iterable... elements) @Override public Iterator iterator() { + final List all = this.stream() + .map(Directives::new) + .collect(Collectors.toList()); return new DirectivesJeoObject( - String.format("seq.of%d", this.size()), + String.format("seq.of%d", all.size()), this.name, - this.stream().map(Directives::new).collect(Collectors.toList()) + all ).iterator(); } - /** - * Size of the sequence. - * @return Size. - */ - private long size() { - return this.stream().count(); - } - /** * Stream of directives. * @return Stream of directives. diff --git a/src/main/java/org/eolang/jeo/representation/xmir/XmlNode.java b/src/main/java/org/eolang/jeo/representation/xmir/XmlNode.java index 6d78faba7..db3570ef0 100644 --- a/src/main/java/org/eolang/jeo/representation/xmir/XmlNode.java +++ b/src/main/java/org/eolang/jeo/representation/xmir/XmlNode.java @@ -23,7 +23,6 @@ */ package org.eolang.jeo.representation.xmir; -import com.jcabi.xml.XML; import com.jcabi.xml.XMLDocument; import java.util.ArrayList; import java.util.List; diff --git a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java index 4440303d7..8bbc219ed 100644 --- a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java +++ b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java @@ -185,12 +185,11 @@ void convertsToXmirAndBack() { ); } final long end = System.currentTimeMillis(); - final long l = end - start; Logger.info( this, "We made %d attempts to convert bytecode to xmir and back in %[ms]s", attempts, - l + end - start ); } diff --git a/src/test/java/org/eolang/jeo/representation/directives/DirectivesSeqTest.java b/src/test/java/org/eolang/jeo/representation/directives/DirectivesSeqTest.java index 7e30277eb..70f19a1bb 100644 --- a/src/test/java/org/eolang/jeo/representation/directives/DirectivesSeqTest.java +++ b/src/test/java/org/eolang/jeo/representation/directives/DirectivesSeqTest.java @@ -24,8 +24,13 @@ package org.eolang.jeo.representation.directives; import com.jcabi.matchers.XhtmlMatchers; +import java.util.stream.Stream; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.xembly.Directives; import org.xembly.ImpossibleModificationException; import org.xembly.Xembler; @@ -52,4 +57,48 @@ void convertsToNumberedSeq() throws ImpossibleModificationException { ) ); } + + @ParameterizedTest + @MethodSource("sequences") + void correctlyComputesSize( + final DirectivesSeq actual, final int expected + ) throws ImpossibleModificationException { + MatcherAssert.assertThat( + "The size of the sequence is not as expected", + new Xembler(actual).xml(), + XhtmlMatchers.hasXPath( + String.format( + "/o[contains(@base,'seq.of%d') and @name='@']", + expected + ) + ) + ); + } + + /** + * Sequences to test. + * @return Stream of arguments. + */ + private static Stream sequences() { + return Stream.of( + Arguments.of( + new DirectivesSeq( + new DirectivesValue("1"), new DirectivesValue("2") + ), + 2 + ), + Arguments.of(new DirectivesSeq(), 0), + Arguments.of(new DirectivesSeq(new Directives(), new Directives()), 0), + Arguments.of( + new DirectivesSeq( + new DirectivesValue("1"), new Directives(), + new Directives(), new DirectivesValue("4") + ), + 2 + ), + Arguments.of( + new DirectivesSeq(), 0 + ) + ); + } } From 5d14e2e5d10a1c8ef4a2fa1ee26c0e633321a8f8 Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Fri, 29 Nov 2024 17:22:08 +0300 Subject: [PATCH 04/11] feat(#889): proceed with XMLDocument removal --- src/main/java/org/eolang/jeo/Assembling.java | 11 +++-- .../representation/XmirRepresentation.java | 46 +++++++++++++++---- .../jeo/representation/xmir/XmlProgram.java | 2 +- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/eolang/jeo/Assembling.java b/src/main/java/org/eolang/jeo/Assembling.java index 2b10c07cf..8da16bc34 100644 --- a/src/main/java/org/eolang/jeo/Assembling.java +++ b/src/main/java/org/eolang/jeo/Assembling.java @@ -44,6 +44,11 @@ public final class Assembling implements Transformation { */ private final Path from; + /** + * XMIR representation. + */ + private final XmirRepresentation repr; + /** * Constructor. * @param target Target folder. @@ -52,6 +57,7 @@ public final class Assembling implements Transformation { Assembling(final Path target, final Path representation) { this.folder = target; this.from = representation; + this.repr = new XmirRepresentation(this.from); } @Override @@ -61,8 +67,7 @@ public Path source() { @Override public Path target() { - final XmirRepresentation repr = new XmirRepresentation(this.from); - final String name = new PrefixedName(repr.name()).decode(); + final String name = new PrefixedName(this.repr.name()).decode(); final String[] subpath = name.split("\\."); subpath[subpath.length - 1] = String.format("%s.class", subpath[subpath.length - 1]); return Paths.get(this.folder.toString(), subpath); @@ -70,6 +75,6 @@ public Path target() { @Override public byte[] transform() { - return new XmirRepresentation(this.from).toBytecode().bytes(); + return this.repr.toBytecode().bytes(); } } diff --git a/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java b/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java index d0b5be2e1..db7c56892 100644 --- a/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java +++ b/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java @@ -27,12 +27,18 @@ import com.jcabi.xml.XMLDocument; import java.io.FileNotFoundException; import java.nio.file.Path; +import java.util.Optional; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; import org.cactoos.scalar.Sticky; import org.cactoos.scalar.Synced; import org.cactoos.scalar.Unchecked; import org.eolang.jeo.representation.bytecode.Bytecode; import org.eolang.jeo.representation.xmir.XmlProgram; import org.eolang.parser.Schema; +import org.w3c.dom.Node; /** * Intermediate representation of a class files from XMIR. @@ -41,6 +47,11 @@ */ public final class XmirRepresentation { + /** + * XPath's factory. + */ + private static final XPathFactory FACTORY = XPathFactory.newInstance(); + /** * XML. */ @@ -94,17 +105,36 @@ private XmirRepresentation( /** * Retrieves class name from XMIR. + * This method intentionally uses classes from `org.w3c.dom` instead of `com.jcabi.xml` + * by performance reasons. * @return Class name. */ public String name() { - return new ClassName( - this.xml.value() - .xpath("/program/metas/meta/tail/text()") - .stream() - .findFirst() - .orElse(""), - this.xml.value().xpath("/program/@name").get(0) - ).full(); + final Node node = this.xml.value().node(); + final XPath xpath = XmirRepresentation.FACTORY.newXPath(); + try { + return new ClassName( + Optional.ofNullable( + ((Node) xpath.evaluate( + "/program/metas/meta/tail/text()", + node, + XPathConstants.NODE + )).getTextContent() + ).orElse(""), + String.valueOf( + xpath.evaluate( + "/program/@name", + node, + XPathConstants.STRING + ) + ) + ).full(); + } catch (final XPathExpressionException exception) { + throw new IllegalStateException( + String.format("Can't extract class name from the '%s' source", this.source), + exception + ); + } } /** diff --git a/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java b/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java index 88b3aff14..67db1c3e1 100644 --- a/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java +++ b/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java @@ -89,7 +89,7 @@ public XmlProgram(final XML xml) { * * @param root Root node. */ - private XmlProgram(final Node root) { + public XmlProgram(final Node root) { this.root = root; } From e469dcd1dcdfcaf70ebf3163e415864172393f99 Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Fri, 29 Nov 2024 17:44:30 +0300 Subject: [PATCH 05/11] feat(#889): add OptimizedSchema --- .../representation/XmirRepresentation.java | 36 +++++++++++++++++-- .../XmirRepresentationTest.java | 19 ++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java b/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java index db7c56892..f7452b6a2 100644 --- a/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java +++ b/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java @@ -28,16 +28,20 @@ import java.io.FileNotFoundException; import java.nio.file.Path; import java.util.Optional; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import org.cactoos.io.ResourceOf; import org.cactoos.scalar.Sticky; import org.cactoos.scalar.Synced; import org.cactoos.scalar.Unchecked; import org.eolang.jeo.representation.bytecode.Bytecode; import org.eolang.jeo.representation.xmir.XmlProgram; -import org.eolang.parser.Schema; import org.w3c.dom.Node; /** @@ -144,7 +148,7 @@ public String name() { public Bytecode toBytecode() { final XML xmir = this.xml.value(); try { - new Schema(xmir).check(); + new OptimizedSchema(xmir.node()).check(); return new XmlProgram(xmir).bytecode().bytecode(); } catch (final IllegalArgumentException exception) { throw new IllegalArgumentException( @@ -191,4 +195,32 @@ private static XML open(final Path path) { ); } } + + + private static class OptimizedSchema { + private final Node node; + + public OptimizedSchema(final Node node) { + this.node = node; + } + + public void check() { + SchemaFactory schemaFactory = SchemaFactory.newInstance( + "http://www.w3.org/2001/XMLSchema" + ); + try { + javax.xml.validation.Schema schema = schemaFactory.newSchema( + new StreamSource(new ResourceOf("XMIR.xsd").stream())); + final Validator validator = schema.newValidator(); + validator.validate(new DOMSource(this.node)); + } catch (final Exception exception) { + throw new IllegalStateException( + String.format( + "There are 1 XSD violation(s), see the log", exception.getMessage()), + exception + ); + } + + } + } } diff --git a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java index 8bbc219ed..a01225513 100644 --- a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java +++ b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java @@ -25,6 +25,8 @@ import com.jcabi.log.Logger; import com.jcabi.matchers.XhtmlMatchers; +import com.jcabi.xml.XML; +import com.jcabi.xml.XMLDocument; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -191,6 +193,23 @@ void convertsToXmirAndBack() { attempts, end - start ); + } + @Test + void throwsExceptionIfXmirIsInvalid() { + MatcherAssert.assertThat( + "We excpect that XSD violation message will be easily understandable by developers", + Assertions.assertThrows( + IllegalStateException.class, + () -> new XmirRepresentation( + new XMLDocument( + new BytecodeProgram( + new BytecodeClass("org/eolang/foo/Math") + ).xml().toString().replace("package", "") + ) + ).toBytecode() + ).getCause().getMessage(), + Matchers.containsString("There are XSD violations, see the log") + ); } } From 2207db0a548804436c492be993bac2852bce3bf4 Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Fri, 29 Nov 2024 18:03:06 +0300 Subject: [PATCH 06/11] feat(#889): try to optimize even further --- .../representation/XmirRepresentation.java | 62 +++++++++++-------- .../XmirRepresentationTest.java | 1 + 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java b/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java index f7452b6a2..3255c868e 100644 --- a/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java +++ b/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java @@ -24,14 +24,16 @@ package org.eolang.jeo.representation; import com.jcabi.xml.XML; -import com.jcabi.xml.XMLDocument; import java.io.FileNotFoundException; +import java.io.IOException; import java.nio.file.Path; import java.util.Optional; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; @@ -42,7 +44,9 @@ import org.cactoos.scalar.Unchecked; import org.eolang.jeo.representation.bytecode.Bytecode; import org.eolang.jeo.representation.xmir.XmlProgram; +import org.w3c.dom.Document; import org.w3c.dom.Node; +import org.xml.sax.SAXException; /** * Intermediate representation of a class files from XMIR. @@ -54,12 +58,17 @@ public final class XmirRepresentation { /** * XPath's factory. */ - private static final XPathFactory FACTORY = XPathFactory.newInstance(); + private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance(); + + /** + * XML document factory. + */ + private static final DocumentBuilderFactory DOC_FACTORY = DocumentBuilderFactory.newInstance(); /** * XML. */ - private final Unchecked xml; + private final Unchecked xml; /** * Source of the XML. @@ -79,7 +88,7 @@ public XmirRepresentation(final Path path) { * @param xml XML. */ public XmirRepresentation(final XML xml) { - this(xml, "Unknown"); + this(xml.node(), "Unknown"); } /** @@ -88,7 +97,7 @@ public XmirRepresentation(final XML xml) { * @param source Source of the XML. */ private XmirRepresentation( - final XML xml, + final Node xml, final String source ) { this(new Unchecked<>(() -> xml), source); @@ -100,7 +109,7 @@ private XmirRepresentation( * @param source Source of the XML. */ private XmirRepresentation( - final Unchecked xml, + final Unchecked xml, final String source ) { this.xml = xml; @@ -114,8 +123,8 @@ private XmirRepresentation( * @return Class name. */ public String name() { - final Node node = this.xml.value().node(); - final XPath xpath = XmirRepresentation.FACTORY.newXPath(); + final Node node = this.xml.value(); + final XPath xpath = XmirRepresentation.XPATH_FACTORY.newXPath(); try { return new ClassName( Optional.ofNullable( @@ -146,9 +155,9 @@ public String name() { * @return Array of bytes. */ public Bytecode toBytecode() { - final XML xmir = this.xml.value(); + final Node xmir = this.xml.value(); try { - new OptimizedSchema(xmir.node()).check(); + new OptimizedSchema(xmir).check(); return new XmlProgram(xmir).bytecode().bytecode(); } catch (final IllegalArgumentException exception) { throw new IllegalArgumentException( @@ -168,7 +177,7 @@ public Bytecode toBytecode() { * @param path Path to an XML file. * @return Lazy XML. */ - private static Unchecked fromFile(final Path path) { + private static Unchecked fromFile(final Path path) { return new Unchecked<>(new Synced<>(new Sticky<>(() -> XmirRepresentation.open(path)))); } @@ -177,15 +186,18 @@ private static Unchecked fromFile(final Path path) { * @param path Path to XML file. * @return XML. */ - private static XML open(final Path path) { + private static Node open(final Path path) { try { - return new XMLDocument(path.toFile()); + DocumentBuilder builder = XmirRepresentation.DOC_FACTORY.newDocumentBuilder(); + Document document = builder.parse(new java.io.FileInputStream(path.toFile())); + return document.getDocumentElement(); } catch (final FileNotFoundException exception) { throw new IllegalStateException( String.format("Can't find file '%s'", path), exception ); - } catch (final IllegalArgumentException broken) { + } catch (final IllegalArgumentException | ParserConfigurationException | IOException | + SAXException broken) { throw new IllegalStateException( String.format( "Can't parse XML from the file '%s'", @@ -199,24 +211,24 @@ private static XML open(final Path path) { private static class OptimizedSchema { private final Node node; + private final SchemaFactory factory; - public OptimizedSchema(final Node node) { + OptimizedSchema(final Node node) { this.node = node; + this.factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); } - public void check() { - SchemaFactory schemaFactory = SchemaFactory.newInstance( - "http://www.w3.org/2001/XMLSchema" - ); + void check() { try { - javax.xml.validation.Schema schema = schemaFactory.newSchema( - new StreamSource(new ResourceOf("XMIR.xsd").stream())); - final Validator validator = schema.newValidator(); - validator.validate(new DOMSource(this.node)); + this.factory.newSchema( + new StreamSource(new ResourceOf("XMIR.xsd").stream()) + ).newValidator().validate(new DOMSource(this.node)); } catch (final Exception exception) { throw new IllegalStateException( String.format( - "There are 1 XSD violation(s), see the log", exception.getMessage()), + "There are XSD violations, see the log", + exception.getMessage() + ), exception ); } diff --git a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java index a01225513..3d12ac46a 100644 --- a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java +++ b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java @@ -164,6 +164,7 @@ void failsToOpenBrokenXmirRepresentationFromFile(@TempDir final Path dir) throws * into the bytecode representation and back. * 1) Timings before the optimization were: 21s, 23s, 21s, 21s (500 attempts) * 2) `XMLDocument` -> `org.w3c.Node` optimization: 11s, 10s, 11s, 10s (500 attempts) + * 3) Remove `XMLDocument` from the `XmirRepresentation` constructor: 10s, 9s, 9s, 9s (500 attempts) */ @Test @Disabled From c676d996575c943b7db0661e841a56f8784ae8d0 Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Sun, 1 Dec 2024 17:51:19 +0300 Subject: [PATCH 07/11] feat(#889): fix the bug with nodes hierarchy --- .../org/eolang/jeo/representation/XmirRepresentation.java | 2 +- .../java/org/eolang/jeo/representation/xmir/XmlProgram.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java b/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java index 3255c868e..952f120fd 100644 --- a/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java +++ b/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java @@ -88,7 +88,7 @@ public XmirRepresentation(final Path path) { * @param xml XML. */ public XmirRepresentation(final XML xml) { - this(xml.node(), "Unknown"); + this(xml.node().getFirstChild(), "Unknown"); } /** diff --git a/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java b/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java index 67db1c3e1..c859836be 100644 --- a/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java +++ b/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java @@ -64,7 +64,7 @@ public XmlProgram(final String... lines) { * @param xml Raw XMIR. */ public XmlProgram(final XML xml) { - this(xml.node()); + this(xml.node().getFirstChild()); } /** @@ -80,7 +80,7 @@ public XmlProgram(final XML xml) { new DirectivesClass(name), new DirectivesMetas(name) ) ).xmlQuietly() - ).node() + ) ); } @@ -123,7 +123,7 @@ public BytecodeProgram bytecode() { */ private XmlClass top() { return new XmlNode(this.root) - .child(XmlProgram.PROGRAM) +// .child(XmlProgram.PROGRAM) .child("objects") .child("o") .toClass(); From 032e367721f501006db7f16c77df23897b8fd7c1 Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Sun, 1 Dec 2024 18:09:13 +0300 Subject: [PATCH 08/11] feat(#889): fix all the code offences --- .../representation/XmirRepresentation.java | 51 ++++++++++++++----- .../jeo/representation/xmir/XmlProgram.java | 5 -- .../XmirRepresentationTest.java | 3 +- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java b/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java index 952f120fd..b5e650fb2 100644 --- a/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java +++ b/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java @@ -28,9 +28,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.SchemaFactory; @@ -39,12 +37,12 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.cactoos.io.ResourceOf; +import org.cactoos.io.UncheckedInput; import org.cactoos.scalar.Sticky; import org.cactoos.scalar.Synced; import org.cactoos.scalar.Unchecked; import org.eolang.jeo.representation.bytecode.Bytecode; import org.eolang.jeo.representation.xmir.XmlProgram; -import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.SAXException; @@ -185,19 +183,21 @@ private static Unchecked fromFile(final Path path) { * Convert a path to XML. * @param path Path to XML file. * @return XML. + * @checkstyle IllegalCatchCheck (20 lines) */ + @SuppressWarnings("PMD.AvoidCatchingGenericException") private static Node open(final Path path) { try { - DocumentBuilder builder = XmirRepresentation.DOC_FACTORY.newDocumentBuilder(); - Document document = builder.parse(new java.io.FileInputStream(path.toFile())); - return document.getDocumentElement(); + return XmirRepresentation.DOC_FACTORY + .newDocumentBuilder() + .parse(path.toFile()) + .getDocumentElement(); } catch (final FileNotFoundException exception) { throw new IllegalStateException( String.format("Can't find file '%s'", path), exception ); - } catch (final IllegalArgumentException | ParserConfigurationException | IOException | - SAXException broken) { + } catch (final Exception broken) { throw new IllegalStateException( String.format( "Can't parse XML from the file '%s'", @@ -208,22 +208,48 @@ private static Node open(final Path path) { } } - + /** + * Optimized schema for XMIR. + * @since 0.6 + */ private static class OptimizedSchema { + /** + * Node. + */ private final Node node; + + /** + * Schema factory. + */ private final SchemaFactory factory; + /** + * Constructor. + * @param node Node. + */ OptimizedSchema(final Node node) { + this(node, SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema")); + } + + /** + * Constructor. + * @param node Node. + * @param factory Schema factory. + */ + private OptimizedSchema(final Node node, final SchemaFactory factory) { this.node = node; - this.factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); + this.factory = factory; } + /** + * Check the node. + */ void check() { try { this.factory.newSchema( - new StreamSource(new ResourceOf("XMIR.xsd").stream()) + new StreamSource(new UncheckedInput(new ResourceOf("XMIR.xsd")).stream()) ).newValidator().validate(new DOMSource(this.node)); - } catch (final Exception exception) { + } catch (final IOException | SAXException exception) { throw new IllegalStateException( String.format( "There are XSD violations, see the log", @@ -232,7 +258,6 @@ void check() { exception ); } - } } } diff --git a/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java b/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java index c859836be..b14858596 100644 --- a/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java +++ b/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java @@ -40,10 +40,6 @@ * @since 0.1 */ public final class XmlProgram { - /** - * Program node name. - */ - private static final String PROGRAM = "program"; /** * Root node. @@ -123,7 +119,6 @@ public BytecodeProgram bytecode() { */ private XmlClass top() { return new XmlNode(this.root) -// .child(XmlProgram.PROGRAM) .child("objects") .child("o") .toClass(); diff --git a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java index 3d12ac46a..edb4eeaeb 100644 --- a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java +++ b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java @@ -25,7 +25,6 @@ import com.jcabi.log.Logger; import com.jcabi.matchers.XhtmlMatchers; -import com.jcabi.xml.XML; import com.jcabi.xml.XMLDocument; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -164,7 +163,7 @@ void failsToOpenBrokenXmirRepresentationFromFile(@TempDir final Path dir) throws * into the bytecode representation and back. * 1) Timings before the optimization were: 21s, 23s, 21s, 21s (500 attempts) * 2) `XMLDocument` -> `org.w3c.Node` optimization: 11s, 10s, 11s, 10s (500 attempts) - * 3) Remove `XMLDocument` from the `XmirRepresentation` constructor: 10s, 9s, 9s, 9s (500 attempts) + * 3) Remove `XMLDocument` usage: 10s, 9s, 9s, 9s (500 attempts) */ @Test @Disabled From d1a294de30666f6985b49574ea9f67d1aae5a6ef Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Sun, 1 Dec 2024 18:13:10 +0300 Subject: [PATCH 09/11] feat(#889): fix the test naming --- .../eolang/jeo/representation/directives/DirectivesSeqTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/eolang/jeo/representation/directives/DirectivesSeqTest.java b/src/test/java/org/eolang/jeo/representation/directives/DirectivesSeqTest.java index 70f19a1bb..c83990e90 100644 --- a/src/test/java/org/eolang/jeo/representation/directives/DirectivesSeqTest.java +++ b/src/test/java/org/eolang/jeo/representation/directives/DirectivesSeqTest.java @@ -60,7 +60,7 @@ void convertsToNumberedSeq() throws ImpossibleModificationException { @ParameterizedTest @MethodSource("sequences") - void correctlyComputesSize( + void computesSize( final DirectivesSeq actual, final int expected ) throws ImpossibleModificationException { MatcherAssert.assertThat( From 69597ebf364a8a7305366d4693a74a9f1b4146af Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Sun, 1 Dec 2024 18:40:39 +0300 Subject: [PATCH 10/11] feat(#889): suppress some warnings --- .../org/eolang/jeo/representation/XmirRepresentationTest.java | 1 + .../eolang/jeo/representation/directives/DirectivesSeqTest.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java index edb4eeaeb..89a5bcd07 100644 --- a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java +++ b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java @@ -167,6 +167,7 @@ void failsToOpenBrokenXmirRepresentationFromFile(@TempDir final Path dir) throws */ @Test @Disabled + @SuppressWarnings("PMD.GuardLogStatement") void convertsToXmirAndBack() { final Bytecode before = new BytecodeProgram( new BytecodeClass("org/eolang/foo/Math") diff --git a/src/test/java/org/eolang/jeo/representation/directives/DirectivesSeqTest.java b/src/test/java/org/eolang/jeo/representation/directives/DirectivesSeqTest.java index c83990e90..a70898102 100644 --- a/src/test/java/org/eolang/jeo/representation/directives/DirectivesSeqTest.java +++ b/src/test/java/org/eolang/jeo/representation/directives/DirectivesSeqTest.java @@ -79,7 +79,7 @@ void computesSize( * Sequences to test. * @return Stream of arguments. */ - private static Stream sequences() { + static Stream sequences() { return Stream.of( Arguments.of( new DirectivesSeq( From 157b83be445e7a24a965baa7ceb4c860942185a9 Mon Sep 17 00:00:00 2001 From: volodya-lombrozo Date: Sun, 1 Dec 2024 20:58:42 +0300 Subject: [PATCH 11/11] feat(#889): add one more puzzle --- .../eolang/jeo/representation/XmirRepresentation.java | 6 ++++++ .../org/eolang/jeo/representation/xmir/XmlNode.java | 10 ++++++++-- .../org/eolang/jeo/representation/xmir/XmlProgram.java | 5 +++++ .../jeo/representation/XmirRepresentationTest.java | 3 --- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java b/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java index b5e650fb2..a3ca212da 100644 --- a/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java +++ b/src/main/java/org/eolang/jeo/representation/XmirRepresentation.java @@ -210,7 +210,13 @@ private static Node open(final Path path) { /** * Optimized schema for XMIR. + * It is an optimized version of {@link org.eolang.parser.Schema} class. * @since 0.6 + * @todo #889:30min Use the `Schema` class instead of `OptimizedSchema`. + * The `OptimizedSchema` class is a temporary solution to avoid the performance + * issues with the `Schema` class. We will be able to remove this class after + * the following issue is resolved: + * https://github.com/jcabi/jcabi-xml/issues/277 */ private static class OptimizedSchema { /** diff --git a/src/main/java/org/eolang/jeo/representation/xmir/XmlNode.java b/src/main/java/org/eolang/jeo/representation/xmir/XmlNode.java index db3570ef0..2ce640f66 100644 --- a/src/main/java/org/eolang/jeo/representation/xmir/XmlNode.java +++ b/src/main/java/org/eolang/jeo/representation/xmir/XmlNode.java @@ -43,7 +43,13 @@ public final class XmlNode { /** - * Parent node. + * XML node. + * Attention! + * Here we use the {@link Node} class instead of the {@link com.jcabi.xml.XML} + * by performance reasons. + * In some cases {@link Node} 10 times faster than {@link com.jcabi.xml.XML}. + * You can read more about it here: + * Optimization */ private final Node node; @@ -57,7 +63,7 @@ public XmlNode(final String xml) { /** * Constructor. - * @param parent Parent node. + * @param parent Xml node. */ public XmlNode(final Node parent) { this.node = parent; diff --git a/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java b/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java index b14858596..30afab904 100644 --- a/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java +++ b/src/main/java/org/eolang/jeo/representation/xmir/XmlProgram.java @@ -43,6 +43,11 @@ public final class XmlProgram { /** * Root node. + * Here we use the {@link Node} class instead of the {@link com.jcabi.xml.XML} + * by performance reasons. + * In some cases {@link Node} 10 times faster than {@link com.jcabi.xml.XML}. + * You can read more about it here: + * Optimization */ private final Node root; diff --git a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java index 89a5bcd07..a9ce9d879 100644 --- a/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java +++ b/src/test/java/org/eolang/jeo/representation/XmirRepresentationTest.java @@ -161,9 +161,6 @@ void failsToOpenBrokenXmirRepresentationFromFile(@TempDir final Path dir) throws * This is a performance test, which is disabled by default. * It is used to measure the performance of the conversion of the EO object * into the bytecode representation and back. - * 1) Timings before the optimization were: 21s, 23s, 21s, 21s (500 attempts) - * 2) `XMLDocument` -> `org.w3c.Node` optimization: 11s, 10s, 11s, 10s (500 attempts) - * 3) Remove `XMLDocument` usage: 10s, 9s, 9s, 9s (500 attempts) */ @Test @Disabled