From 34312eee4dc9012779d13d059931b05c3d745bf4 Mon Sep 17 00:00:00 2001 From: Sheikah45 Date: Mon, 13 May 2024 19:02:34 -0400 Subject: [PATCH] Use new parser in processor --- README.md | 2 +- .../sheikah45/fx2j/parser/FxmlAttribute.java | 106 --- .../sheikah45/fx2j/parser/FxmlComponents.java | 8 +- .../sheikah45/fx2j/parser/FxmlElement.java | 102 --- .../sheikah45/fx2j/parser/FxmlNode.java | 20 - .../sheikah45/fx2j/parser/FxmlParser.java | 239 +++-- .../parser/FxmlProcessingInstruction.java | 9 +- .../parser/attribute/ControllerAttribute.java | 11 + .../attribute/EventHandlerAttribute.java | 14 + .../fx2j/parser/attribute/FxmlAttribute.java | 10 + .../fx2j/parser/attribute/IdAttribute.java | 11 + .../attribute/InstancePropertyAttribute.java | 17 + .../attribute/StaticPropertyAttribute.java | 20 + .../parser/element/ClassInstanceElement.java | 22 + .../fx2j/parser/element/ConstantElement.java | 19 + .../fx2j/parser/element/CopyElement.java | 14 + .../parser/element/DeclarationElement.java | 4 + .../fx2j/parser/element/DefineElement.java | 5 + .../fx2j/parser/element/FactoryElement.java | 19 + .../fx2j/parser/element/FxmlElement.java | 5 + .../fx2j/parser/element/IncludeElement.java | 13 + .../fx2j/parser/element/InstanceElement.java | 15 + .../element/InstancePropertyElement.java | 13 + .../fx2j/parser/element/ReferenceElement.java | 15 + .../fx2j/parser/element/RootElement.java | 14 + .../fx2j/parser/element/ScriptElement.java | 10 + .../fx2j/parser/element/ScriptSource.java | 22 + .../parser/element/StaticPropertyElement.java | 17 + .../fx2j/parser/element/ValueElement.java | 18 + .../fx2j/parser/property/FxmlProperty.java | 41 + .../sheikah45/fx2j/parser/property/Value.java | 73 ++ fx2j-parser/src/main/java/module-info.java | 3 + .../fx2j/parser/FxmlParserElementTest.java | 295 +++++-- .../parser/FxmlParserInvalidElementTest.java | 42 +- .../fx2j/parser/FxmlParserPropertyTest.java | 181 ++-- .../resources/element/invalid/define.fxml | 6 + .../element/invalid/empty-import.fxml | 2 + .../element/invalid/empty-language.fxml | 2 + .../element/invalid/non-declaration-root.fxml | 1 + .../element/invalid/property-elements.fxml | 6 + .../property-non-common-attribute.fxml | 4 + .../element/valid/empty-property.fxml | 4 + .../element/valid/multi-property-value.fxml | 8 + .../resources/element/valid/multi-text.fxml | 6 + .../valid/processing-instructions.fxml | 2 +- .../element/valid/property-attribute.fxml | 4 + .../element/valid/property-element.fxml | 6 + .../valid/{script.fxml => script-inline.fxml} | 0 .../element/valid/script-reference.fxml | 6 + .../{instance.fxml => single-property.fxml} | 0 .../test/resources/element/valid/static.fxml | 1 - .../test/resources/element/valid/text.fxml | 4 + .../property/event-handler-method.fxml | 1 - .../property/event-handler-reference.fxml | 1 - .../resources/property/event-handler.fxml | 1 - .../src/test/resources/property/static.fxml | 1 - fx2j-processor/build.gradle.kts | 1 + .../fx2j/processor/Fx2jProcessor.java | 4 +- .../fx2j/processor/FxmlProcessor.java | 51 +- .../internal/ObjectNodeProcessor.java | 832 +++++++++--------- .../internal/model/FxmlComponents.java | 9 - .../processor/internal/model/FxmlNode.java | 49 -- .../processor/internal/utils/FXMLUtils.java | 97 -- .../processor/internal/utils/StringUtils.java | 4 - fx2j-processor/src/main/java/module-info.java | 1 + .../fx2j/processor/utils/FXMLUtilsTest.java | 76 -- .../fx2j/processor/utils/StringUtilsTest.java | 20 - .../fxml/controller/controller-type.fxml | 2 +- .../src/test/resources/fxml/read/test.fxml | 2 +- 69 files changed, 1443 insertions(+), 1200 deletions(-) delete mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlAttribute.java delete mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlElement.java delete mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlNode.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/ControllerAttribute.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/EventHandlerAttribute.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/FxmlAttribute.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/IdAttribute.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/InstancePropertyAttribute.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/StaticPropertyAttribute.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ClassInstanceElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ConstantElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/CopyElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/DeclarationElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/DefineElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/FactoryElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/FxmlElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/IncludeElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/InstanceElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/InstancePropertyElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ReferenceElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/RootElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ScriptElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ScriptSource.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/StaticPropertyElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ValueElement.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/FxmlProperty.java create mode 100644 fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/Value.java create mode 100644 fx2j-parser/src/test/resources/element/invalid/define.fxml create mode 100644 fx2j-parser/src/test/resources/element/invalid/empty-import.fxml create mode 100644 fx2j-parser/src/test/resources/element/invalid/empty-language.fxml create mode 100644 fx2j-parser/src/test/resources/element/invalid/non-declaration-root.fxml create mode 100644 fx2j-parser/src/test/resources/element/invalid/property-elements.fxml create mode 100644 fx2j-parser/src/test/resources/element/invalid/property-non-common-attribute.fxml create mode 100644 fx2j-parser/src/test/resources/element/valid/empty-property.fxml create mode 100644 fx2j-parser/src/test/resources/element/valid/multi-property-value.fxml create mode 100644 fx2j-parser/src/test/resources/element/valid/multi-text.fxml create mode 100644 fx2j-parser/src/test/resources/element/valid/property-attribute.fxml create mode 100644 fx2j-parser/src/test/resources/element/valid/property-element.fxml rename fx2j-parser/src/test/resources/element/valid/{script.fxml => script-inline.fxml} (100%) create mode 100644 fx2j-parser/src/test/resources/element/valid/script-reference.fxml rename fx2j-parser/src/test/resources/element/valid/{instance.fxml => single-property.fxml} (100%) create mode 100644 fx2j-parser/src/test/resources/element/valid/text.fxml delete mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/FxmlComponents.java delete mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/FxmlNode.java delete mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/FXMLUtils.java delete mode 100644 fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/utils/FXMLUtilsTest.java diff --git a/README.md b/README.md index d2f9165..c448112 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ in a failure to compile the fxml file Additionally, the controller class must be fully specified at compile time to ensure proper processing. This can be done by using the fx:controller attribute in the root of the -view graph or by adding a directive which specifies the fully qualified +view graph or by adding a directive which specifies the fully qualified name of the upper bounds for the controller class ## Usage diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlAttribute.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlAttribute.java deleted file mode 100644 index 57ab839..0000000 --- a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlAttribute.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.github.sheikah45.fx2j.parser; - -import io.github.sheikah45.fx2j.parser.utils.StringUtils; - -import java.nio.file.Path; -import java.util.Objects; - -public sealed interface FxmlAttribute { - sealed interface Property extends FxmlAttribute { - record Instance(String property, Value value) implements Property { - public Instance { - if (StringUtils.isNullOrBlank(property)) { - throw new IllegalArgumentException("property cannot be blank or null"); - } - Objects.requireNonNull(value, "value cannot be null"); - } - } - record Static(String className, String property, Value value) implements Property { - public Static { - if (StringUtils.isNullOrBlank(className)) { - throw new IllegalArgumentException("className cannot be blank or null"); - } - if (StringUtils.isNullOrBlank(property)) { - throw new IllegalArgumentException("property cannot be blank or null"); - } - Objects.requireNonNull(value, "value cannot be null"); - } - } - - sealed interface Value {} - - record Empty() implements Value {} - record Literal(String value) implements Value { - public Literal { - if (StringUtils.isNullOrBlank(value)) { - throw new IllegalArgumentException("value cannot be blank or null"); - } - } - } - record Location(Path location) implements Value { - public Location { - Objects.requireNonNull(location, "location cannot be null"); - } - } - record Resource(String value) implements Value { - public Resource { - if (StringUtils.isNullOrBlank(value)) { - throw new IllegalArgumentException("value cannot be blank or null"); - } - } - } - record Reference(String value) implements Value { - public Reference { - if (StringUtils.isNullOrBlank(value)) { - throw new IllegalArgumentException("value cannot be blank or null"); - } - } - } - record Expression(String value) implements Value { - public Expression { - if (StringUtils.isNullOrBlank(value)) { - throw new IllegalArgumentException("value cannot be blank or null"); - } - } - } - } - record Id(String value) implements FxmlAttribute { - public Id { - if (StringUtils.isNullOrBlank(value)) { - throw new IllegalArgumentException("id cannot be blank or null"); - } - } - } - record Controller(String className) implements FxmlAttribute { - public Controller { - if (StringUtils.isNullOrBlank(className)) { - throw new IllegalArgumentException("className cannot be blank or null"); - } - } - } - record EventHandler(String eventName, Value value) implements FxmlAttribute { - sealed interface Value {} - - record Script(String value) implements Value { - public Script { - if (StringUtils.isNullOrBlank(value)) { - throw new IllegalArgumentException("value cannot be blank or null"); - } - } - } - record Method(String method) implements Value { - public Method { - if (StringUtils.isNullOrBlank(method)) { - throw new IllegalArgumentException("method cannot be blank or null"); - } - } - } - record Reference(String reference) implements Value { - public Reference { - if (StringUtils.isNullOrBlank(reference)) { - throw new IllegalArgumentException("reference cannot be blank or null"); - } - } - } - } -} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlComponents.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlComponents.java index e17ab6c..f90e608 100644 --- a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlComponents.java +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlComponents.java @@ -1,12 +1,14 @@ package io.github.sheikah45.fx2j.parser; +import io.github.sheikah45.fx2j.parser.element.DeclarationElement; + import java.util.List; import java.util.Objects; -public record FxmlComponents(FxmlNode rootNode, List processingInstructions) { +public record FxmlComponents(DeclarationElement rootNode, List rootProcessingInstructions) { public FxmlComponents { Objects.requireNonNull(rootNode, "rootNode cannot be null"); - Objects.requireNonNull(processingInstructions, "processingInstructions cannot be null"); - processingInstructions = List.copyOf(processingInstructions); + Objects.requireNonNull(rootProcessingInstructions, "rootProcessingInstructions cannot be null"); + rootProcessingInstructions = List.copyOf(rootProcessingInstructions); } } diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlElement.java deleted file mode 100644 index 62ef49f..0000000 --- a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlElement.java +++ /dev/null @@ -1,102 +0,0 @@ -package io.github.sheikah45.fx2j.parser; - -import io.github.sheikah45.fx2j.parser.utils.StringUtils; - -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.util.Objects; - -public sealed interface FxmlElement { - sealed interface Declaration extends FxmlElement { - record Class(String className) implements Declaration { - public Class { - if (StringUtils.isNullOrBlank(className)) { - throw new IllegalArgumentException("className cannot be blank or null"); - } - } - } - record Value(String className, String value) implements Declaration { - public Value { - if (StringUtils.isNullOrBlank(className)) { - throw new IllegalArgumentException("className cannot be blank or null"); - } - - if (StringUtils.isNullOrBlank(value)) { - throw new IllegalArgumentException("value cannot be blank or null"); - } - } - } - record Constant(String className, String value) implements Declaration { - public Constant { - if (StringUtils.isNullOrBlank(className)) { - throw new IllegalArgumentException("className cannot be blank or null"); - } - - if (StringUtils.isNullOrBlank(value)) { - throw new IllegalArgumentException("value cannot be blank or null"); - } - } - } - record Factory(String factoryClassName, String factoryMethod) implements Declaration { - public Factory { - if (StringUtils.isNullOrBlank(factoryClassName)) { - throw new IllegalArgumentException("factoryClassName cannot be blank or null"); - } - - if (StringUtils.isNullOrBlank(factoryMethod)) { - throw new IllegalArgumentException("factoryMethod cannot be blank or null"); - } - } - } - } - sealed interface Property extends FxmlElement { - record Instance(String property) implements Property { - public Instance { - if (StringUtils.isNullOrBlank(property)) { - throw new IllegalArgumentException("property cannot be blank or null"); - } - } - } - - record Static(String className, String property) implements Property { - public Static { - if (StringUtils.isNullOrBlank(className)) { - throw new IllegalArgumentException("className cannot be blank or null"); - } - if (StringUtils.isNullOrBlank(property)) { - throw new IllegalArgumentException("property cannot be blank or null"); - } - } - } - } - record Include(Path source, Path resources, Charset charset) implements FxmlElement { - public Include { - Objects.requireNonNull(source, "source cannot be null"); - Objects.requireNonNull(charset, "charset cannot be null"); - } - } - record Copy(String source) implements FxmlElement { - public Copy { - if (StringUtils.isNullOrBlank(source)) { - throw new IllegalArgumentException("source cannot be blank or null"); - } - } - } - record Reference(String source) implements FxmlElement { - public Reference { - if (StringUtils.isNullOrBlank(source)) { - throw new IllegalArgumentException("source cannot be blank or null"); - } - } - } - record Define() implements FxmlElement {} - record Script() implements FxmlElement {} - record Root(String type) implements FxmlElement { - public Root { - if (StringUtils.isNullOrBlank(type)) { - throw new IllegalArgumentException("type cannot be blank or null"); - } - } - } - -} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlNode.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlNode.java deleted file mode 100644 index 640a39c..0000000 --- a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlNode.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.sheikah45.fx2j.parser; - -import java.util.List; -import java.util.Objects; - - -public record FxmlNode(FxmlElement element, - List attributes, - String innerText, - List children) { - - public FxmlNode { - Objects.requireNonNull(element, "element cannot be null"); - Objects.requireNonNull(attributes, "attributes cannot be null"); - Objects.requireNonNull(innerText, "innerText cannot be null"); - Objects.requireNonNull(children, "children cannot be null"); - attributes = List.copyOf(attributes); - children = List.copyOf(children); - } -} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlParser.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlParser.java index 7e08954..a0fd187 100644 --- a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlParser.java +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlParser.java @@ -1,5 +1,28 @@ package io.github.sheikah45.fx2j.parser; +import io.github.sheikah45.fx2j.parser.attribute.ControllerAttribute; +import io.github.sheikah45.fx2j.parser.attribute.EventHandlerAttribute; +import io.github.sheikah45.fx2j.parser.attribute.FxmlAttribute; +import io.github.sheikah45.fx2j.parser.attribute.IdAttribute; +import io.github.sheikah45.fx2j.parser.attribute.InstancePropertyAttribute; +import io.github.sheikah45.fx2j.parser.attribute.StaticPropertyAttribute; +import io.github.sheikah45.fx2j.parser.element.ClassInstanceElement; +import io.github.sheikah45.fx2j.parser.element.ConstantElement; +import io.github.sheikah45.fx2j.parser.element.CopyElement; +import io.github.sheikah45.fx2j.parser.element.DeclarationElement; +import io.github.sheikah45.fx2j.parser.element.DefineElement; +import io.github.sheikah45.fx2j.parser.element.FactoryElement; +import io.github.sheikah45.fx2j.parser.element.FxmlElement; +import io.github.sheikah45.fx2j.parser.element.IncludeElement; +import io.github.sheikah45.fx2j.parser.element.InstanceElement; +import io.github.sheikah45.fx2j.parser.element.InstancePropertyElement; +import io.github.sheikah45.fx2j.parser.element.ReferenceElement; +import io.github.sheikah45.fx2j.parser.element.RootElement; +import io.github.sheikah45.fx2j.parser.element.ScriptElement; +import io.github.sheikah45.fx2j.parser.element.ScriptSource; +import io.github.sheikah45.fx2j.parser.element.StaticPropertyElement; +import io.github.sheikah45.fx2j.parser.element.ValueElement; +import io.github.sheikah45.fx2j.parser.property.Value; import io.github.sheikah45.fx2j.parser.utils.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -28,44 +51,96 @@ public static FxmlComponents readFxml(Path filePath) { try { DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse(filePath.toFile()); - return new FxmlComponents(populateElement(document.getDocumentElement()), - extractProcessingInstructions(document)); + Element element = document.getDocumentElement(); + FxmlElement fxmlElement = createFxmlElement(element); + if (!(fxmlElement instanceof DeclarationElement declarationElement)) { + throw new ParseException("Root element is not a new declaration of an instance of a class"); + } + return new FxmlComponents(declarationElement, extractProcessingInstructions(document)); } catch (Exception e) { throw new ParseException("Error parsing document", e); } } - private static FxmlNode populateElement(Element element) { - NamedNodeMap attributesNodeMap = element.getAttributes(); - FxmlElement fxmlElement = createFxmlElement(element); + private static String retrieveInnerText(Element element) { + NodeList childNodes = element.getChildNodes(); + int childrenLength = childNodes.getLength(); - int attrLength = attributesNodeMap.getLength(); + return IntStream.range(0, childrenLength) + .sorted() + .map(index -> childrenLength - index - 1) + .mapToObj(childNodes::item) + .filter(Text.class::isInstance) + .map(Text.class::cast) + .map(Text::getTextContent) + .filter(text -> !StringUtils.isNullOrBlank(text)) + .map(text -> text.replaceAll("\\s+", " ").strip()) + .findFirst() + .orElse(""); + } + + private static ClassInstanceElement.Content createContent(Element element) { + List attributes = createAttributes(element); + List children = createChildren(element); + String text = retrieveInnerText(element); + return new ClassInstanceElement.Content(attributes, children, createPropertyValue(text)); + } + + private static Value createPropertyValue(Element element) { + List values = new ArrayList<>(); - List attributes = IntStream.range(0, attrLength) - .mapToObj(attributesNodeMap::item) - .filter(item -> !item.getNodeName().startsWith("xmlns")) - .map(FxmlParser::createFxmlAttribute) - .toList(); + createAttributes(element).stream().map(attribute -> { + if (!(attribute instanceof FxmlAttribute.CommonAttribute commonAttribute)) { + throw new ParseException("property attribute contains a non common attribute"); + } + + return commonAttribute; + }).map(Value.Attribute::new).forEach(values::add); + + createChildren(element) + .stream() + .map(Value.Element::new) + .forEach(values::add); + + values.add(createPropertyValue(retrieveInnerText(element))); + + List populatedValues = values.stream() + .filter(value -> (!(value instanceof Value.Empty))) + .toList(); - if (!element.hasChildNodes()) { - return new FxmlNode(fxmlElement, attributes, "", List.of()); + if (populatedValues.isEmpty()) { + return new Value.Empty(); } + if (populatedValues.size() == 1) { + return populatedValues.getFirst(); + } + + return new Value.Multi(populatedValues); + } + + private static List createChildren(Element element) { NodeList childNodes = element.getChildNodes(); int childrenLength = childNodes.getLength(); - List children = new ArrayList<>(); - String innerText = ""; + List children = new ArrayList<>(); for (int i = 0; i < childrenLength; i++) { Node item = childNodes.item(i); - if (item instanceof Text text && !StringUtils.isNullOrBlank(text.getTextContent())) { - innerText = text.getTextContent().replaceAll("\\s+", " ").strip(); - } else if (item instanceof Element childElement) { - children.add(populateElement(childElement)); + if (item instanceof Element childElement) { + children.add(createFxmlElement(childElement)); } } + return children; + } - return new FxmlNode(fxmlElement, attributes, innerText, children); + private static List createAttributes(Element element) { + NamedNodeMap attributesNodeMap = element.getAttributes(); + int attrLength = attributesNodeMap.getLength(); + return IntStream.range(0, attrLength) + .mapToObj(attributesNodeMap::item) + .filter(item -> !item.getNodeName().startsWith("xmlns")) + .map(FxmlParser::createFxmlAttribute) + .toList(); } private static FxmlElement createFxmlElement(Element element) { @@ -74,19 +149,22 @@ private static FxmlElement createFxmlElement(Element element) { case "fx:reference" -> createReferenceElement(element); case "fx:copy" -> createCopyElement(element); case "fx:root" -> createRootElement(element); - case "fx:define" -> new FxmlElement.Define(); - case "fx:script" -> new FxmlElement.Script(); - case String tag when tag.matches("[a-z]\\w*") -> new FxmlElement.Property.Instance(tag); + case "fx:define" -> createDefineElement(element); + case "fx:script" -> createScriptElement(element); + case String tag when tag.matches("[a-z]\\w*") -> + new InstancePropertyElement(tag, createPropertyValue(element)); case String tag when tag.matches("(\\w*\\.)*[A-Z]\\w*\\.[a-z]\\w*") -> { + Value value = createPropertyValue(element); int separatorIndex = tag.lastIndexOf('.'); - yield new FxmlElement.Property.Static(tag.substring(0, separatorIndex), - tag.substring(separatorIndex + 1)); + String className = tag.substring(0, separatorIndex); + String property = tag.substring(separatorIndex + 1); + yield new StaticPropertyElement(className, property, value); } default -> createInstanceElement(element); }; } - private static FxmlElement.Declaration createInstanceElement(Element element) { + private static ClassInstanceElement createInstanceElement(Element element) { String className = element.getTagName(); String factory = removeAndGetValueIfPresent(element, "fx:factory").orElse(null); String value = removeAndGetValueIfPresent(element, "fx:value").orElse(null); @@ -95,96 +173,116 @@ private static FxmlElement.Declaration createInstanceElement(Element element) { throw new ParseException("Multiple initialization attributes specified: %s".formatted(element)); } + ClassInstanceElement.Content content = createContent(element); + if (factory != null) { - return new FxmlElement.Declaration.Factory(className, factory); + return new FactoryElement(className, factory, content); } if (value != null) { - return new FxmlElement.Declaration.Value(className, value); + return new ValueElement(className, createPropertyValue(value), content); } if (constant != null) { - return new FxmlElement.Declaration.Constant(className, constant); + return new ConstantElement(className, constant, content); } - return new FxmlElement.Declaration.Class(className); + return new InstanceElement(className, content); } - private static FxmlElement.Root createRootElement(Element element) { + private static RootElement createRootElement(Element element) { String type = removeAndGetValueIfPresent(element, "type").orElse(Object.class.getCanonicalName()); - return new FxmlElement.Root(type); + return new RootElement(type, createContent(element)); + } + + private static DefineElement createDefineElement(Element element) { + List children = createChildren(element).stream().map(child -> { + if (!(child instanceof ClassInstanceElement classInstanceElement)) { + throw new ParseException("define element contains a non class instance element"); + } + + return classInstanceElement; + }).toList(); + + return new DefineElement(children); } - private static FxmlElement.Copy createCopyElement(Element element) { + private static ScriptElement createScriptElement(Element element) { + return removeAndGetValueIfPresent(element, "source").map(Path::of).map(source -> { + Optional charset = removeAndGetValueIfPresent(element, "charset"); + return new ScriptElement(new ScriptSource.Reference(source, charset.map(Charset::forName).orElse(null))); + }).orElseGet(() -> new ScriptElement(new ScriptSource.Inline(retrieveInnerText(element)))); + } + + private static CopyElement createCopyElement(Element element) { String source = removeAndGetValueIfPresent(element, "source").orElseThrow( - () -> new ParseException( - "Source attribute not found in fx:reference element: %s".formatted(element))); - return new FxmlElement.Copy(source); + () -> new ParseException("Source attribute not found in fx:reference element: %s".formatted(element))); + return new CopyElement(source, createContent(element)); } - private static FxmlElement.Reference createReferenceElement(Element element) { + private static ReferenceElement createReferenceElement(Element element) { String source = removeAndGetValueIfPresent(element, "source").orElseThrow( - () -> new ParseException( - "Source attribute not found in fx:reference element: %s".formatted(element))); - return new FxmlElement.Reference(source); + () -> new ParseException("Source attribute not found in fx:reference element: %s".formatted(element))); + return new ReferenceElement(source, createContent(element)); } - private static FxmlElement.Include createIncludeElement(Element element) { + private static IncludeElement createIncludeElement(Element element) { Path source = removeAndGetValueIfPresent(element, "source").map(Path::of) - .orElseThrow( - () -> new ParseException( - "Source attribute not found in fx:include element: %s".formatted( - element))); + .orElseThrow(() -> new ParseException( + "Source attribute not found in fx:include element: %s".formatted( + element))); Path resources = removeAndGetValueIfPresent(element, "resources").map(Path::of).orElse(null); Charset charset = removeAndGetValueIfPresent(element, "charset").map(Charset::forName) .orElse(StandardCharsets.UTF_8); - return new FxmlElement.Include(source, resources, charset); + + return new IncludeElement(source, resources, charset, createContent(element)); } private static FxmlAttribute createFxmlAttribute(Node node) { return switch (node.getNodeName()) { - case "fx:id" -> new FxmlAttribute.Id(node.getNodeValue()); - case "fx:controller" -> new FxmlAttribute.Controller(node.getNodeValue()); + case "fx:id" -> new IdAttribute(node.getNodeValue()); + case "fx:controller" -> new ControllerAttribute(node.getNodeValue()); case String name when name.startsWith("on") -> - new FxmlAttribute.EventHandler(name, createEventHandlerValue(node.getNodeValue())); + new EventHandlerAttribute(name, createEventHandler(node.getNodeValue())); case String name when name.matches("(\\w*\\.)*[A-Z]\\w*\\.[a-z]\\w*") -> { int separatorIndex = name.lastIndexOf('.'); - yield new FxmlAttribute.Property.Static(name.substring(0, separatorIndex), - name.substring(separatorIndex + 1), - createPropertyValue(node.getNodeValue()) - ); + yield new StaticPropertyAttribute(name.substring(0, separatorIndex), name.substring(separatorIndex + 1), + createPropertyValue(node.getNodeValue())); } - case String name -> new FxmlAttribute.Property.Instance(name, createPropertyValue(node.getNodeValue())); + case String name -> new InstancePropertyAttribute(name, createPropertyValue(node.getNodeValue())); }; } - private static FxmlAttribute.Property.Value createPropertyValue(String value) { + private static Value.Single createPropertyValue(String value) { return switch (value) { - case String val when val.startsWith("@") -> new FxmlAttribute.Property.Location(Path.of(val.substring(1))); - case String val when val.startsWith("%") -> new FxmlAttribute.Property.Resource(val.substring(1)); - case String val when val.matches("\\$\\{.*}") -> - new FxmlAttribute.Property.Expression(val.substring(2, val.length() - 1)); - case String val when val.startsWith("$") -> new FxmlAttribute.Property.Reference(val.substring(1)); - case String val when val.startsWith("\\") -> new FxmlAttribute.Property.Literal(val.substring(1)); - case String val when val.isBlank() -> new FxmlAttribute.Property.Empty(); - case String val -> new FxmlAttribute.Property.Literal(val); + case String val when val.startsWith("@") -> new Value.Location(Path.of(val.substring(1))); + case String val when val.startsWith("%") -> new Value.Resource(val.substring(1)); + case String val when val.matches("\\$\\{.*}") -> new Value.Expression(val.substring(2, val.length() - 1)); + case String val when val.startsWith("$") -> new Value.Reference(val.substring(1)); + case String val when val.startsWith("\\") -> new Value.Literal(val.substring(1)); + case String val when val.isBlank() -> new Value.Empty(); + case String val -> new Value.Literal(val); }; } - private static FxmlAttribute.EventHandler.Value createEventHandlerValue(String value) { + private static Value.Handler createEventHandler(String value) { return switch (value) { - case String val when val.startsWith("#") -> new FxmlAttribute.EventHandler.Method(val.substring(1)); - case String val when val.startsWith("$") -> new FxmlAttribute.EventHandler.Reference(val.substring(1)); - case String val -> new FxmlAttribute.EventHandler.Script(val); + case String val when val.startsWith("#") -> new Value.Handler.Method(val.substring(1)); + case String val when val.startsWith("$") -> new Value.Handler.Reference(val.substring(1)); + case String val -> new Value.Handler.Script(val); }; } private static List extractProcessingInstructions(Document document) { + + NodeList childNodes = document.getChildNodes(); + int childrenLength = childNodes.getLength(); + List processingInstructions = new ArrayList<>(); - Node node = document.getFirstChild(); - while (node != null) { - if (node instanceof ProcessingInstruction processingInstruction) { + for (int i = 0; i < childrenLength; i++) { + Node item = childNodes.item(i); + if (item instanceof ProcessingInstruction processingInstruction) { processingInstructions.add(switch (processingInstruction.getTarget()) { case "import" -> new FxmlProcessingInstruction.Import(processingInstruction.getData()); case "language" -> new FxmlProcessingInstruction.Language(processingInstruction.getData()); @@ -193,7 +291,6 @@ private static List extractProcessingInstructions(Doc case String target -> new FxmlProcessingInstruction.Custom(target, processingInstruction.getData()); }); } - node = node.getNextSibling(); } return processingInstructions; diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlProcessingInstruction.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlProcessingInstruction.java index 00d85bc..9c93008 100644 --- a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlProcessingInstruction.java +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/FxmlProcessingInstruction.java @@ -2,11 +2,13 @@ import io.github.sheikah45.fx2j.parser.utils.StringUtils; +import java.util.Objects; + public sealed interface FxmlProcessingInstruction { - record Import(String className) implements FxmlProcessingInstruction { + record Import(String value) implements FxmlProcessingInstruction { public Import { - if (StringUtils.isNullOrBlank(className)) { - throw new IllegalArgumentException("className cannot be blank or null"); + if (StringUtils.isNullOrBlank(value)) { + throw new IllegalArgumentException("value cannot be blank or null"); } } } @@ -20,6 +22,7 @@ record Language(String language) implements FxmlProcessingInstruction { record Compile(boolean enabled) implements FxmlProcessingInstruction {} record Custom(String name, String value) implements FxmlProcessingInstruction { public Custom { + Objects.requireNonNull(value, "value cannot be null"); if (StringUtils.isNullOrBlank(name)) { throw new IllegalArgumentException("name cannot be blank or null"); } diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/ControllerAttribute.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/ControllerAttribute.java new file mode 100644 index 0000000..60e68d0 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/ControllerAttribute.java @@ -0,0 +1,11 @@ +package io.github.sheikah45.fx2j.parser.attribute; + +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +public record ControllerAttribute(String className) implements FxmlAttribute.FxAttribute { + public ControllerAttribute { + if (StringUtils.isNullOrBlank(className)) { + throw new IllegalArgumentException("className cannot be blank or null"); + } + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/EventHandlerAttribute.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/EventHandlerAttribute.java new file mode 100644 index 0000000..a1e267f --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/EventHandlerAttribute.java @@ -0,0 +1,14 @@ +package io.github.sheikah45.fx2j.parser.attribute; + +import io.github.sheikah45.fx2j.parser.property.FxmlProperty; +import io.github.sheikah45.fx2j.parser.property.Value; + +import java.util.Objects; + +public record EventHandlerAttribute(String eventName, Value.Handler handler) implements FxmlProperty.EventHandler, + FxmlAttribute.CommonAttribute { + public EventHandlerAttribute { + Objects.requireNonNull(eventName, "eventName cannot be null"); + Objects.requireNonNull(handler, "handler cannot be null"); + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/FxmlAttribute.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/FxmlAttribute.java new file mode 100644 index 0000000..271af81 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/FxmlAttribute.java @@ -0,0 +1,10 @@ +package io.github.sheikah45.fx2j.parser.attribute; + +public sealed interface FxmlAttribute { + + sealed interface FxAttribute extends FxmlAttribute permits ControllerAttribute, IdAttribute {} + + sealed interface CommonAttribute extends FxmlAttribute + permits EventHandlerAttribute, InstancePropertyAttribute, StaticPropertyAttribute {} + +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/IdAttribute.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/IdAttribute.java new file mode 100644 index 0000000..8fe5584 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/IdAttribute.java @@ -0,0 +1,11 @@ +package io.github.sheikah45.fx2j.parser.attribute; + +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +public record IdAttribute(String value) implements FxmlAttribute.FxAttribute { + public IdAttribute { + if (StringUtils.isNullOrBlank(value)) { + throw new IllegalArgumentException("id cannot be blank or null"); + } + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/InstancePropertyAttribute.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/InstancePropertyAttribute.java new file mode 100644 index 0000000..62f2b57 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/InstancePropertyAttribute.java @@ -0,0 +1,17 @@ +package io.github.sheikah45.fx2j.parser.attribute; + +import io.github.sheikah45.fx2j.parser.property.FxmlProperty; +import io.github.sheikah45.fx2j.parser.property.Value; +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +import java.util.Objects; + +public record InstancePropertyAttribute(String property, Value.Single value) implements FxmlProperty.Instance, + FxmlAttribute.CommonAttribute { + public InstancePropertyAttribute { + if (StringUtils.isNullOrBlank(property)) { + throw new IllegalArgumentException("property cannot be blank or null"); + } + Objects.requireNonNull(value, "value cannot be null"); + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/StaticPropertyAttribute.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/StaticPropertyAttribute.java new file mode 100644 index 0000000..d67c00a --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/attribute/StaticPropertyAttribute.java @@ -0,0 +1,20 @@ +package io.github.sheikah45.fx2j.parser.attribute; + +import io.github.sheikah45.fx2j.parser.property.FxmlProperty; +import io.github.sheikah45.fx2j.parser.property.Value; +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +import java.util.Objects; + +public record StaticPropertyAttribute(String className, String property, Value.Single value) + implements FxmlAttribute.CommonAttribute, FxmlProperty.Static { + public StaticPropertyAttribute { + if (StringUtils.isNullOrBlank(className)) { + throw new IllegalArgumentException("className cannot be blank or null"); + } + if (StringUtils.isNullOrBlank(property)) { + throw new IllegalArgumentException("property cannot be blank or null"); + } + Objects.requireNonNull(value, "value cannot be null"); + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ClassInstanceElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ClassInstanceElement.java new file mode 100644 index 0000000..c407d86 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ClassInstanceElement.java @@ -0,0 +1,22 @@ +package io.github.sheikah45.fx2j.parser.element; + +import io.github.sheikah45.fx2j.parser.attribute.FxmlAttribute; +import io.github.sheikah45.fx2j.parser.property.Value; + +import java.util.List; +import java.util.Objects; + +sealed public interface ClassInstanceElement extends FxmlElement + permits CopyElement, DeclarationElement, IncludeElement, ReferenceElement { + Content content(); + + record Content(List attributes, List children, Value.Single body) { + public Content { + Objects.requireNonNull(attributes, "attributes cannot be null"); + Objects.requireNonNull(children, "children cannot be null"); + Objects.requireNonNull(body, "text cannot be null"); + attributes = List.copyOf(attributes); + children = List.copyOf(children); + } + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ConstantElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ConstantElement.java new file mode 100644 index 0000000..538cb3e --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ConstantElement.java @@ -0,0 +1,19 @@ +package io.github.sheikah45.fx2j.parser.element; + +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +import java.util.Objects; + +public record ConstantElement(String className, String member, Content content) + implements DeclarationElement { + public ConstantElement { + Objects.requireNonNull(content, "content cannot be null"); + if (StringUtils.isNullOrBlank(className)) { + throw new IllegalArgumentException("className cannot be blank or null"); + } + + if (StringUtils.isNullOrBlank(member)) { + throw new IllegalArgumentException("member cannot be blank or null"); + } + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/CopyElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/CopyElement.java new file mode 100644 index 0000000..54a2bba --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/CopyElement.java @@ -0,0 +1,14 @@ +package io.github.sheikah45.fx2j.parser.element; + +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +import java.util.Objects; + +public record CopyElement(String source, Content content) implements ClassInstanceElement { + public CopyElement { + Objects.requireNonNull(content, "content cannot be null"); + if (StringUtils.isNullOrBlank(source)) { + throw new IllegalArgumentException("source cannot be blank or null"); + } + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/DeclarationElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/DeclarationElement.java new file mode 100644 index 0000000..ec01f39 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/DeclarationElement.java @@ -0,0 +1,4 @@ +package io.github.sheikah45.fx2j.parser.element; + +sealed public interface DeclarationElement extends ClassInstanceElement + permits InstanceElement, ConstantElement, FactoryElement, RootElement, ValueElement {} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/DefineElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/DefineElement.java new file mode 100644 index 0000000..d8a25e8 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/DefineElement.java @@ -0,0 +1,5 @@ +package io.github.sheikah45.fx2j.parser.element; + +import java.util.List; + +public record DefineElement(List children) implements FxmlElement {} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/FactoryElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/FactoryElement.java new file mode 100644 index 0000000..df1586c --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/FactoryElement.java @@ -0,0 +1,19 @@ +package io.github.sheikah45.fx2j.parser.element; + +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +import java.util.Objects; + +public record FactoryElement(String factoryClassName, String factoryMethod, Content content) + implements DeclarationElement { + public FactoryElement { + Objects.requireNonNull(content, "content cannot be null"); + if (StringUtils.isNullOrBlank(factoryClassName)) { + throw new IllegalArgumentException("factoryClassName cannot be blank or null"); + } + + if (StringUtils.isNullOrBlank(factoryMethod)) { + throw new IllegalArgumentException("factoryMethod cannot be blank or null"); + } + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/FxmlElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/FxmlElement.java new file mode 100644 index 0000000..39385ac --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/FxmlElement.java @@ -0,0 +1,5 @@ +package io.github.sheikah45.fx2j.parser.element; + +public sealed interface FxmlElement + permits ClassInstanceElement, DefineElement, InstancePropertyElement, ScriptElement, + StaticPropertyElement {} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/IncludeElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/IncludeElement.java new file mode 100644 index 0000000..933e4ec --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/IncludeElement.java @@ -0,0 +1,13 @@ +package io.github.sheikah45.fx2j.parser.element; + +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Objects; + +public record IncludeElement(Path source, Path resources, Charset charset, Content content) + implements ClassInstanceElement { + public IncludeElement { + Objects.requireNonNull(source, "source cannot be null"); + Objects.requireNonNull(content, "content cannot be null"); + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/InstanceElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/InstanceElement.java new file mode 100644 index 0000000..a9562a1 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/InstanceElement.java @@ -0,0 +1,15 @@ +package io.github.sheikah45.fx2j.parser.element; + +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +import java.util.Objects; + +public record InstanceElement(String className, Content content) + implements DeclarationElement { + public InstanceElement { + Objects.requireNonNull(content, "content cannot be null"); + if (StringUtils.isNullOrBlank(className)) { + throw new IllegalArgumentException("className cannot be blank or null"); + } + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/InstancePropertyElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/InstancePropertyElement.java new file mode 100644 index 0000000..91d7d5e --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/InstancePropertyElement.java @@ -0,0 +1,13 @@ +package io.github.sheikah45.fx2j.parser.element; + +import io.github.sheikah45.fx2j.parser.property.FxmlProperty; +import io.github.sheikah45.fx2j.parser.property.Value; +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +public record InstancePropertyElement(String property, Value value) implements FxmlElement, FxmlProperty.Instance { + public InstancePropertyElement { + if (StringUtils.isNullOrBlank(property)) { + throw new IllegalArgumentException("property cannot be blank or null"); + } + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ReferenceElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ReferenceElement.java new file mode 100644 index 0000000..72479f2 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ReferenceElement.java @@ -0,0 +1,15 @@ +package io.github.sheikah45.fx2j.parser.element; + +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +import java.util.Objects; + +public record ReferenceElement(String source, Content content) + implements ClassInstanceElement { + public ReferenceElement { + Objects.requireNonNull(content, "content cannot be null"); + if (StringUtils.isNullOrBlank(source)) { + throw new IllegalArgumentException("source cannot be blank or null"); + } + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/RootElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/RootElement.java new file mode 100644 index 0000000..748e768 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/RootElement.java @@ -0,0 +1,14 @@ +package io.github.sheikah45.fx2j.parser.element; + +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +import java.util.Objects; + +public record RootElement(String type, Content content) implements DeclarationElement { + public RootElement { + Objects.requireNonNull(content, "content cannot be null"); + if (StringUtils.isNullOrBlank(type)) { + throw new IllegalArgumentException("type cannot be blank or null"); + } + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ScriptElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ScriptElement.java new file mode 100644 index 0000000..ec44e13 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ScriptElement.java @@ -0,0 +1,10 @@ +package io.github.sheikah45.fx2j.parser.element; + +import java.util.Objects; + +public record ScriptElement(ScriptSource source) implements FxmlElement { + + public ScriptElement { + Objects.requireNonNull(source, "content cannot be null"); + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ScriptSource.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ScriptSource.java new file mode 100644 index 0000000..71a76a2 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ScriptSource.java @@ -0,0 +1,22 @@ +package io.github.sheikah45.fx2j.parser.element; + +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Objects; + +public sealed interface ScriptSource { + record Inline(String value) implements ScriptSource { + public Inline { + if (StringUtils.isNullOrBlank(value)) { + throw new IllegalArgumentException("source cannot be blank or null"); + } + } + } + record Reference(Path source, Charset charset) implements ScriptSource { + public Reference { + Objects.requireNonNull(source, "source cannot be null"); + } + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/StaticPropertyElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/StaticPropertyElement.java new file mode 100644 index 0000000..55cdb71 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/StaticPropertyElement.java @@ -0,0 +1,17 @@ +package io.github.sheikah45.fx2j.parser.element; + +import io.github.sheikah45.fx2j.parser.property.FxmlProperty; +import io.github.sheikah45.fx2j.parser.property.Value; +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +public record StaticPropertyElement(String className, String property, Value value) + implements FxmlElement, FxmlProperty.Static { + public StaticPropertyElement { + if (StringUtils.isNullOrBlank(className)) { + throw new IllegalArgumentException("className cannot be blank or null"); + } + if (StringUtils.isNullOrBlank(property)) { + throw new IllegalArgumentException("property cannot be blank or null"); + } + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ValueElement.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ValueElement.java new file mode 100644 index 0000000..768d662 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/element/ValueElement.java @@ -0,0 +1,18 @@ +package io.github.sheikah45.fx2j.parser.element; + +import io.github.sheikah45.fx2j.parser.property.Value; +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +import java.util.Objects; + +public record ValueElement(String className, Value.Single value, Content content) implements + DeclarationElement { + public ValueElement { + Objects.requireNonNull(content, "content cannot be null"); + if (StringUtils.isNullOrBlank(className)) { + throw new IllegalArgumentException("className cannot be blank or null"); + } + + Objects.requireNonNull(value, "value cannot be null"); + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/FxmlProperty.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/FxmlProperty.java new file mode 100644 index 0000000..cd2c9a0 --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/FxmlProperty.java @@ -0,0 +1,41 @@ +package io.github.sheikah45.fx2j.parser.property; + +import io.github.sheikah45.fx2j.parser.attribute.EventHandlerAttribute; +import io.github.sheikah45.fx2j.parser.attribute.InstancePropertyAttribute; +import io.github.sheikah45.fx2j.parser.attribute.StaticPropertyAttribute; +import io.github.sheikah45.fx2j.parser.element.InstancePropertyElement; +import io.github.sheikah45.fx2j.parser.element.StaticPropertyElement; + +public sealed interface FxmlProperty { + + sealed interface Static extends FxmlProperty permits StaticPropertyAttribute, StaticPropertyElement { + String className(); + + String property(); + + Value value(); + } + + sealed interface Instance extends FxmlProperty + permits InstancePropertyAttribute, InstancePropertyElement, EventHandler { + String property(); + + Value value(); + } + + sealed interface EventHandler extends Instance permits EventHandlerAttribute { + @Override + default String property() { + return eventName(); + } + + @Override + default Value value() { + return handler(); + } + + Value.Handler handler(); + + String eventName(); + } +} diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/Value.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/Value.java new file mode 100644 index 0000000..6b78b1d --- /dev/null +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/Value.java @@ -0,0 +1,73 @@ +package io.github.sheikah45.fx2j.parser.property; + +import io.github.sheikah45.fx2j.parser.attribute.FxmlAttribute; +import io.github.sheikah45.fx2j.parser.element.FxmlElement; +import io.github.sheikah45.fx2j.parser.utils.StringUtils; + +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; + +sealed public interface Value permits Value.Handler, Value.Multi, Value.Single { + + + sealed interface Single extends Value {} + + sealed interface Handler extends Value {} + record Multi(List values) implements Value { + public Multi { + Objects.requireNonNull(values, "values cannot be null"); + values = List.copyOf(values); + } + } + record Empty() implements Single, Handler {} + record Expression(String value) implements Single { + public Expression { + if (StringUtils.isNullOrBlank(value)) { + throw new IllegalArgumentException("value cannot be blank or null"); + } + } + } + record Literal(String value) implements Single { + public Literal { + if (StringUtils.isNullOrBlank(value)) { + throw new IllegalArgumentException("value cannot be blank or null"); + } + } + } + record Location(Path value) implements Single { + public Location { + Objects.requireNonNull(value, "location cannot be null"); + } + } + record Resource(String value) implements Single { + public Resource { + if (StringUtils.isNullOrBlank(value)) { + throw new IllegalArgumentException("value cannot be blank or null"); + } + } + } + record Reference(String value) implements Single, Handler { + public Reference { + if (StringUtils.isNullOrBlank(value)) { + throw new IllegalArgumentException("value cannot be blank or null"); + } + } + } + record Element(FxmlElement value) implements Value.Single {} + record Attribute(FxmlAttribute.CommonAttribute value) implements Value.Single {} + record Script(String value) implements Handler { + public Script { + if (StringUtils.isNullOrBlank(value)) { + throw new IllegalArgumentException("value cannot be blank or null"); + } + } + } + record Method(String name) implements Handler { + public Method { + if (StringUtils.isNullOrBlank(name)) { + throw new IllegalArgumentException("name cannot be blank or null"); + } + } + } +} diff --git a/fx2j-parser/src/main/java/module-info.java b/fx2j-parser/src/main/java/module-info.java index e22afaa..1b2588d 100644 --- a/fx2j-parser/src/main/java/module-info.java +++ b/fx2j-parser/src/main/java/module-info.java @@ -2,4 +2,7 @@ requires java.xml; exports io.github.sheikah45.fx2j.parser; + exports io.github.sheikah45.fx2j.parser.attribute; + exports io.github.sheikah45.fx2j.parser.element; + exports io.github.sheikah45.fx2j.parser.property; } \ No newline at end of file diff --git a/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/FxmlParserElementTest.java b/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/FxmlParserElementTest.java index 212233e..3100871 100644 --- a/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/FxmlParserElementTest.java +++ b/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/FxmlParserElementTest.java @@ -1,5 +1,23 @@ package io.github.sheikah45.fx2j.parser; +import io.github.sheikah45.fx2j.parser.attribute.InstancePropertyAttribute; +import io.github.sheikah45.fx2j.parser.element.ClassInstanceElement; +import io.github.sheikah45.fx2j.parser.element.ConstantElement; +import io.github.sheikah45.fx2j.parser.element.CopyElement; +import io.github.sheikah45.fx2j.parser.element.DeclarationElement; +import io.github.sheikah45.fx2j.parser.element.DefineElement; +import io.github.sheikah45.fx2j.parser.element.FactoryElement; +import io.github.sheikah45.fx2j.parser.element.FxmlElement; +import io.github.sheikah45.fx2j.parser.element.IncludeElement; +import io.github.sheikah45.fx2j.parser.element.InstanceElement; +import io.github.sheikah45.fx2j.parser.element.InstancePropertyElement; +import io.github.sheikah45.fx2j.parser.element.ReferenceElement; +import io.github.sheikah45.fx2j.parser.element.RootElement; +import io.github.sheikah45.fx2j.parser.element.ScriptElement; +import io.github.sheikah45.fx2j.parser.element.ScriptSource; +import io.github.sheikah45.fx2j.parser.element.StaticPropertyElement; +import io.github.sheikah45.fx2j.parser.element.ValueElement; +import io.github.sheikah45.fx2j.parser.property.Value; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; @@ -14,201 +32,302 @@ @Execution(ExecutionMode.CONCURRENT) public class FxmlParserElementTest { + public static final ClassInstanceElement.Content EMPTY_CONTENT = new ClassInstanceElement.Content(List.of(), + List.of(), + new Value.Empty()); private static final Path FXML_ROOT = Path.of("src/test/resources/element/valid"); @Test - public void testProcessingInstructions() throws Exception { + public void testProcessingInstructions() { Path filePath = FXML_ROOT.resolve("processing-instructions.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - List processingInstructions = fxmlComponents.processingInstructions(); + List processingInstructions = fxmlComponents.rootProcessingInstructions(); assertEquals(List.of(new FxmlProcessingInstruction.Language("javascript"), new FxmlProcessingInstruction.Compile(false), new FxmlProcessingInstruction.Compile(true), new FxmlProcessingInstruction.Compile(true), new FxmlProcessingInstruction.Import("javafx.scene.control.Button"), new FxmlProcessingInstruction.Import("javafx.scene.layout.AnchorPane"), new FxmlProcessingInstruction.Custom("test", "test"), - new FxmlProcessingInstruction.Custom("test", "notest")), processingInstructions); + new FxmlProcessingInstruction.Custom("test", "")), processingInstructions); } @Test - public void testInclude() throws Exception { + public void testInclude() { Path filePath = FXML_ROOT.resolve("include.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); + DeclarationElement rootNode = fxmlComponents.rootNode(); - List children = rootNode.children(); + List children = rootNode.content().children(); assertEquals(2, children.size()); - FxmlNode first = children.getFirst(); - assertEquals(new FxmlElement.Include(Path.of("included1.fxml"), null, StandardCharsets.UTF_8), first.element()); + FxmlElement first = children.getFirst(); + assertEquals(new IncludeElement(Path.of("included1.fxml"), null, StandardCharsets.UTF_8, EMPTY_CONTENT), + first); - FxmlNode last = children.getLast(); - assertEquals( - new FxmlElement.Include(Path.of("included2.fxml"), Path.of("resource"), StandardCharsets.US_ASCII), - last.element()); + FxmlElement last = children.getLast(); + assertEquals(new IncludeElement(Path.of("included2.fxml"), Path.of("resource"), StandardCharsets.US_ASCII, + EMPTY_CONTENT), last); } @Test - public void testReference() throws Exception { + public void testReference() { Path filePath = FXML_ROOT.resolve("reference.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); + DeclarationElement rootNode = fxmlComponents.rootNode(); - List children = rootNode.children(); + List children = rootNode.content().children(); assertEquals(2, children.size()); - FxmlNode first = children.getFirst(); - assertEquals(new FxmlElement.Reference("reference"), first.element()); + FxmlElement first = children.getFirst(); + assertEquals(new ReferenceElement("reference", EMPTY_CONTENT), first); - FxmlNode last = children.getLast(); - assertEquals(new FxmlElement.Reference("reference1"), last.element()); + FxmlElement last = children.getLast(); + assertEquals(new ReferenceElement("reference1", EMPTY_CONTENT), last); } @Test - public void testCopy() throws Exception { + public void testCopy() { Path filePath = FXML_ROOT.resolve("copy.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); + DeclarationElement rootNode = fxmlComponents.rootNode(); - List children = rootNode.children(); + List children = rootNode.content().children(); assertEquals(2, children.size()); - FxmlNode first = children.getFirst(); - assertEquals(new FxmlElement.Copy("reference"), first.element()); + FxmlElement first = children.getFirst(); + assertEquals(new CopyElement("reference", EMPTY_CONTENT), first); - FxmlNode last = children.getLast(); - assertEquals(new FxmlElement.Copy("copy1"), last.element()); + FxmlElement last = children.getLast(); + assertEquals(new CopyElement("copy1", EMPTY_CONTENT), last); } @Test - public void testDefine() throws Exception { + public void testDefine() { Path filePath = FXML_ROOT.resolve("define.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); + DeclarationElement rootNode = fxmlComponents.rootNode(); - List children = rootNode.children(); + List children = rootNode.content().children(); assertEquals(1, children.size()); - FxmlNode defined = children.getFirst(); - assertEquals(new FxmlElement.Define(), defined.element()); + FxmlElement defined = children.getFirst(); + assertEquals(new DefineElement(List.of(new InstanceElement("VBox", EMPTY_CONTENT))), defined); + } + + @Test + public void testScriptInline() { + Path filePath = FXML_ROOT.resolve("script-inline.fxml"); + FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); + + assertEquals(new FxmlProcessingInstruction.Language("javascript"), + fxmlComponents.rootProcessingInstructions().getFirst()); + + DeclarationElement rootNode = fxmlComponents.rootNode(); - assertEquals(1, defined.children().size()); - FxmlNode child = defined.children().getFirst(); - assertEquals(new FxmlElement.Declaration.Class("VBox"), child.element()); + List children = rootNode.content().children(); + assertEquals(1, children.size()); + + assertEquals(new ScriptElement(new ScriptSource.Inline( + "function handleButtonAction(event) { java.lang.System.out.println('You clicked me!'); }")), + children.getFirst()); } @Test - public void testScript() throws Exception { - Path filePath = FXML_ROOT.resolve("script.fxml"); + public void testScriptReference() { + Path filePath = FXML_ROOT.resolve("script-reference.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); assertEquals(new FxmlProcessingInstruction.Language("javascript"), - fxmlComponents.processingInstructions().getFirst()); + fxmlComponents.rootProcessingInstructions().getFirst()); - FxmlNode rootNode = fxmlComponents.rootNode(); + DeclarationElement rootNode = fxmlComponents.rootNode(); - List children = rootNode.children(); + List children = rootNode.content().children(); assertEquals(1, children.size()); - FxmlNode script = children.getFirst(); - assertEquals(new FxmlElement.Script(), script.element()); + assertEquals(new ScriptElement(new ScriptSource.Reference(Path.of("test.js"), StandardCharsets.UTF_8)), + children.getFirst()); + } + + @Test + public void testInstanceProperty() { + Path filePath = FXML_ROOT.resolve("single-property.fxml"); + FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - assertEquals("function handleButtonAction(event) { java.lang.System.out.println('You clicked me!'); }", - script.innerText()); + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new InstanceElement("VBox", new ClassInstanceElement.Content(List.of(), + List.of(new InstancePropertyElement( + "alignment", + new Value.Literal( + "TOP_RIGHT"))), + new Value.Empty())), rootNode); } @Test - public void testInstanceProperty() throws Exception { - Path filePath = FXML_ROOT.resolve("instance.fxml"); + public void testPropertyText() { + Path filePath = FXML_ROOT.resolve("text.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - assertEquals(new FxmlElement.Declaration.Class("VBox"), rootNode.element()); - assertEquals("", rootNode.innerText()); - assertEquals(List.of(), rootNode.attributes()); + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new InstanceElement("Label", new ClassInstanceElement.Content(List.of(), List.of(), + new Value.Literal("test"))), + rootNode); + } - List children = rootNode.children(); - assertEquals(1, children.size()); + @Test + public void testMultiPropertyText() { + Path filePath = FXML_ROOT.resolve("multi-text.fxml"); + FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode property = children.getFirst(); - assertEquals(new FxmlElement.Property.Instance("alignment"), property.element()); - assertEquals("TOP_RIGHT", property.innerText()); + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new InstanceElement("Label", new ClassInstanceElement.Content(List.of(), + List.of(new InstancePropertyElement( + "alignment", + new Value.Literal( + "TOP_LEFT"))), + new Value.Literal("test2"))), + rootNode); } @Test - public void testStaticProperty() throws Exception { - Path filePath = FXML_ROOT.resolve("static.fxml"); + public void testPropertyAttribute() { + Path filePath = FXML_ROOT.resolve("property-attribute.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - assertEquals(new FxmlElement.Declaration.Class("VBox"), rootNode.element()); - assertEquals("", rootNode.innerText()); - assertEquals(List.of(), rootNode.attributes()); + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new InstanceElement("VBox", new ClassInstanceElement.Content(List.of(), + List.of(new InstancePropertyElement( + "properties", + new Value.Attribute( + new InstancePropertyAttribute( + "foo", + new Value.Literal( + "123"))))), + new Value.Empty())), + rootNode); + } - List children = rootNode.children(); - assertEquals(1, children.size()); + @Test + public void testPropertyElement() { + Path filePath = FXML_ROOT.resolve("property-element.fxml"); + FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode property = children.getFirst(); - assertEquals(new FxmlElement.Property.Static("GridPane", "alignment"), property.element()); - assertEquals("TOP_RIGHT", property.innerText()); + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new InstanceElement("VBox", new ClassInstanceElement.Content(List.of(), + List.of(new InstancePropertyElement( + "children", + new Value.Element( + new InstanceElement( + "VBox", + EMPTY_CONTENT)))), + new Value.Empty())), rootNode); } @Test - public void testQualifiedStaticProperty() throws Exception { - Path filePath = FXML_ROOT.resolve("qualified-static.fxml"); + public void testMultiProperty() { + Path filePath = FXML_ROOT.resolve("multi-property-value.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - assertEquals(new FxmlElement.Declaration.Class("VBox"), rootNode.element()); - assertEquals("", rootNode.innerText()); - assertEquals(List.of(), rootNode.attributes()); + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new InstanceElement("VBox", new ClassInstanceElement.Content(List.of(), + List.of(new InstancePropertyElement( + "alignment", + new Value.Multi( + List.of(new Value.Attribute( + new InstancePropertyAttribute( + "value", + new Value.Literal( + "TOP_RIGHT"))), + new Value.Element( + new InstanceElement( + "Label", + EMPTY_CONTENT)), + new Value.Literal( + "text"))))), + new Value.Empty())), rootNode); + } - List children = rootNode.children(); - assertEquals(1, children.size()); + @Test + public void testEmptyProperty() { + Path filePath = FXML_ROOT.resolve("empty-property.fxml"); + FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); + + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new InstanceElement("VBox", new ClassInstanceElement.Content(List.of(), + List.of(new InstancePropertyElement( + "alignment", + new Value.Empty())), + new Value.Empty())), + rootNode); + } + + @Test + public void testStaticProperty() { + Path filePath = FXML_ROOT.resolve("static.fxml"); + FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); + + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new InstanceElement("VBox", new ClassInstanceElement.Content(List.of(), + List.of(new StaticPropertyElement( + "GridPane", "alignment", + new Value.Literal( + "TOP_RIGHT"))), + new Value.Empty())), rootNode); + } + + @Test + public void testQualifiedStaticProperty() { + Path filePath = FXML_ROOT.resolve("qualified-static.fxml"); + FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode property = children.getFirst(); - assertEquals(new FxmlElement.Property.Static("javafx.scene.layout.GridPane", "alignment"), property.element()); - assertEquals("TOP_RIGHT", property.innerText()); + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new InstanceElement("VBox", new ClassInstanceElement.Content(List.of(), + List.of(new StaticPropertyElement( + "javafx.scene.layout.GridPane", + "alignment", + new Value.Literal( + "TOP_RIGHT"))), + new Value.Empty())), rootNode); } @Test - public void testRoot() throws Exception { + public void testRoot() { Path filePath = FXML_ROOT.resolve("root.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - assertEquals(new FxmlElement.Declaration.Root("javafx.scene.layout.VBox"), rootNode.element()); + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new RootElement("javafx.scene.layout.VBox", EMPTY_CONTENT), rootNode); } @Test - public void testValue() throws Exception { + public void testValue() { Path filePath = FXML_ROOT.resolve("value.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - assertEquals(new FxmlElement.Declaration.Value("Double", "1"), rootNode.element()); + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new ValueElement("Double", new Value.Literal("1"), EMPTY_CONTENT), rootNode); } @Test - public void testConstant() throws Exception { + public void testConstant() { Path filePath = FXML_ROOT.resolve("constant.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - assertEquals(new FxmlElement.Declaration.Constant("Double", "NEGATIVE_INFINITY"), rootNode.element()); + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new ConstantElement("Double", "NEGATIVE_INFINITY", EMPTY_CONTENT), rootNode); } @Test - public void testFactory() throws Exception { + public void testFactory() { Path filePath = FXML_ROOT.resolve("factory.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - assertEquals(new FxmlElement.Declaration.Factory("List", "of"), rootNode.element()); + DeclarationElement rootNode = fxmlComponents.rootNode(); + assertEquals(new FactoryElement("List", "of", EMPTY_CONTENT), rootNode); } } \ No newline at end of file diff --git a/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/FxmlParserInvalidElementTest.java b/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/FxmlParserInvalidElementTest.java index 8befd06..582299e 100644 --- a/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/FxmlParserInvalidElementTest.java +++ b/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/FxmlParserInvalidElementTest.java @@ -4,11 +4,8 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; -import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -18,29 +15,56 @@ public class FxmlParserInvalidElementTest { private static final Path FXML_ROOT = Path.of("src/test/resources/element/invalid"); @Test - public void testMultiDeclaration() throws Exception { + public void testEmptyImport() { + Path filePath = FXML_ROOT.resolve("empty-import.fxml"); + assertThrows(ParseException.class, () -> FxmlParser.readFxml(filePath)); + } + + @Test + public void testEmptyLanguage() { + Path filePath = FXML_ROOT.resolve("empty-language.fxml"); + assertThrows(ParseException.class, () -> FxmlParser.readFxml(filePath)); + } + + @Test + public void testNonDeclaration() { + Path filePath = FXML_ROOT.resolve("non-declaration-root.fxml"); + assertThrows(ParseException.class, () -> FxmlParser.readFxml(filePath)); + } + + @Test + public void testMultiDeclaration() { Path filePath = FXML_ROOT.resolve("multi-declaration.fxml"); assertThrows(ParseException.class, () -> FxmlParser.readFxml(filePath)); } @Test - public void testInclude() throws Exception { - Path filePath = FXML_ROOT.resolve("include.fxml"); + public void testNonCommonPropertyValue() { + Path filePath = FXML_ROOT.resolve("property-non-common-attribute.fxml"); assertThrows(ParseException.class, () -> FxmlParser.readFxml(filePath)); + } + @Test + public void testInclude() { + Path filePath = FXML_ROOT.resolve("include.fxml"); + assertThrows(ParseException.class, () -> FxmlParser.readFxml(filePath)); } @Test - public void testReference() throws Exception { + public void testReference() { Path filePath = FXML_ROOT.resolve("reference.fxml"); assertThrows(ParseException.class, () -> FxmlParser.readFxml(filePath)); - } @Test - public void testCopy() throws Exception { + public void testCopy() { Path filePath = FXML_ROOT.resolve("copy.fxml"); assertThrows(ParseException.class, () -> FxmlParser.readFxml(filePath)); + } + @Test + public void testDefine() { + Path filePath = FXML_ROOT.resolve("define.fxml"); + assertThrows(ParseException.class, () -> FxmlParser.readFxml(filePath)); } } \ No newline at end of file diff --git a/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/FxmlParserPropertyTest.java b/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/FxmlParserPropertyTest.java index 2dcdc16..16d8dc1 100644 --- a/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/FxmlParserPropertyTest.java +++ b/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/FxmlParserPropertyTest.java @@ -1,5 +1,13 @@ package io.github.sheikah45.fx2j.parser; +import io.github.sheikah45.fx2j.parser.attribute.ControllerAttribute; +import io.github.sheikah45.fx2j.parser.attribute.EventHandlerAttribute; +import io.github.sheikah45.fx2j.parser.attribute.FxmlAttribute; +import io.github.sheikah45.fx2j.parser.attribute.IdAttribute; +import io.github.sheikah45.fx2j.parser.attribute.InstancePropertyAttribute; +import io.github.sheikah45.fx2j.parser.attribute.StaticPropertyAttribute; +import io.github.sheikah45.fx2j.parser.element.DeclarationElement; +import io.github.sheikah45.fx2j.parser.property.Value; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; @@ -16,210 +24,163 @@ public class FxmlParserPropertyTest { private static final Path FXML_ROOT = Path.of("src/test/resources/property"); @Test - public void testIdProperty() throws Exception { + public void testIdProperty() { Path filePath = FXML_ROOT.resolve("id.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); + DeclarationElement rootNode = fxmlComponents.rootNode(); - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); - - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.Property.Id("box"), property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new IdAttribute("box")), attributes); } @Test - public void testControllerProperty() throws Exception { + public void testControllerProperty() { Path filePath = FXML_ROOT.resolve("controller.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); + DeclarationElement rootNode = fxmlComponents.rootNode(); - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.Property.Controller("io.github.sheikah45.fx2j.parser.FxmlParser"), property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new ControllerAttribute("io.github.sheikah45.fx2j.parser.FxmlParser")), attributes); } @Test - public void testInstanceProperty() throws Exception { + public void testInstanceProperty() { Path filePath = FXML_ROOT.resolve("instance.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); + DeclarationElement rootNode = fxmlComponents.rootNode(); - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); - - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.Property.Instance("alignment", new FxmlAttribute.Property.Literal("TOP_RIGHT")), - property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new InstancePropertyAttribute("alignment", new Value.Literal("TOP_RIGHT"))), attributes); } @Test - public void testStaticProperty() throws Exception { + public void testStaticProperty() { Path filePath = FXML_ROOT.resolve("static.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); + DeclarationElement rootNode = fxmlComponents.rootNode(); - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.Property.Static("GridPane", "alignment", - new FxmlAttribute.Property.Literal("TOP_RIGHT")), property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new StaticPropertyAttribute("GridPane", "alignment", new Value.Literal("TOP_RIGHT"))), + attributes); } @Test - public void testQualifiedStaticProperty() throws Exception { + public void testQualifiedStaticProperty() { Path filePath = FXML_ROOT.resolve("qualified-static.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); + DeclarationElement rootNode = fxmlComponents.rootNode(); - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); - - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.Property.Static("javafx.scene.layout.GridPane", "alignment", - new FxmlAttribute.Property.Literal("TOP_RIGHT")), property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new StaticPropertyAttribute("javafx.scene.layout.GridPane", "alignment", + new Value.Literal("TOP_RIGHT"))), attributes); } @Test - public void testEventHandlerProperty() throws Exception { + public void testEventHandlerProperty() { Path filePath = FXML_ROOT.resolve("event-handler.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); + DeclarationElement rootNode = fxmlComponents.rootNode(); - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.Property.EventHandler("onScroll", new FxmlAttribute.EventHandler.Script( - "java.lang.System.out.println('scrolled')")), property); + List attributes = rootNode.content().attributes(); + assertEquals( + List.of(new EventHandlerAttribute("onScroll", new Value.Handler.Script( + "java.lang.System.out.println('scrolled')"))), + attributes); } @Test - public void testEmptyProperty() throws Exception { + public void testEmptyProperty() { Path filePath = FXML_ROOT.resolve("empty.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); + DeclarationElement rootNode = fxmlComponents.rootNode(); - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.Property.Instance("text", new FxmlAttribute.Property.Empty()), property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new InstancePropertyAttribute("text", new Value.Empty())), attributes); } @Test - public void testLocationProperty() throws Exception { + public void testLocationProperty() { Path filePath = FXML_ROOT.resolve("location.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); + DeclarationElement rootNode = fxmlComponents.rootNode(); - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); - - FxmlAttribute property = attributes.getFirst(); - assertEquals( - new FxmlAttribute.Property.Instance("url", new FxmlAttribute.Property.Location(Path.of("test.png"))), - property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new InstancePropertyAttribute("url", new Value.Location(Path.of("test.png")))), + attributes); } @Test - public void testResourceProperty() throws Exception { + public void testResourceProperty() { Path filePath = FXML_ROOT.resolve("resource.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); + DeclarationElement rootNode = fxmlComponents.rootNode(); - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); - - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.Property.Instance("text", new FxmlAttribute.Property.Resource("test")), - property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new InstancePropertyAttribute("text", new Value.Resource("test"))), attributes); } @Test - public void testReferenceProperty() throws Exception { + public void testReferenceProperty() { Path filePath = FXML_ROOT.resolve("reference.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); + DeclarationElement rootNode = fxmlComponents.rootNode(); - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.Property.Instance("text", new FxmlAttribute.Property.Reference("test")), - property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new InstancePropertyAttribute("text", new Value.Reference("test"))), attributes); } @Test - public void testEscapeProperty() throws Exception { + public void testEscapeProperty() { Path filePath = FXML_ROOT.resolve("escape.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); + DeclarationElement rootNode = fxmlComponents.rootNode(); - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); - - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.Property.Instance("text", new FxmlAttribute.Property.Literal("$test")), - property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new InstancePropertyAttribute("text", new Value.Literal("$test"))), attributes); } @Test - public void testExpressionProperty() throws Exception { + public void testExpressionProperty() { Path filePath = FXML_ROOT.resolve("expression.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); + DeclarationElement rootNode = fxmlComponents.rootNode(); - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.Property.Instance("text", new FxmlAttribute.Property.Expression("test.text")), - property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new InstancePropertyAttribute("text", new Value.Expression("test.text"))), attributes); } @Test - public void testReferenceHandlerProperty() throws Exception { + public void testReferenceHandlerProperty() { Path filePath = FXML_ROOT.resolve("event-handler-reference.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); + DeclarationElement rootNode = fxmlComponents.rootNode(); - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); - - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.EventHandler("onScroll", new FxmlAttribute.EventHandler.Reference("scroller")), - property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new EventHandlerAttribute("onScroll", new Value.Handler.Reference("scroller"))), + attributes); } @Test - public void testMethodProperty() throws Exception { + public void testMethodProperty() { Path filePath = FXML_ROOT.resolve("event-handler-method.fxml"); FxmlComponents fxmlComponents = FxmlParser.readFxml(filePath); - FxmlNode rootNode = fxmlComponents.rootNode(); - - List attributes = rootNode.attributes(); - assertEquals(1, attributes.size()); + DeclarationElement rootNode = fxmlComponents.rootNode(); - FxmlAttribute property = attributes.getFirst(); - assertEquals(new FxmlAttribute.EventHandler("onScroll", new FxmlAttribute.EventHandler.Method("scroll")), - property); + List attributes = rootNode.content().attributes(); + assertEquals(List.of(new EventHandlerAttribute("onScroll", new Value.Handler.Method("scroll"))), attributes); } } \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/invalid/define.fxml b/fx2j-parser/src/test/resources/element/invalid/define.fxml new file mode 100644 index 0000000..e2383ec --- /dev/null +++ b/fx2j-parser/src/test/resources/element/invalid/define.fxml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/invalid/empty-import.fxml b/fx2j-parser/src/test/resources/element/invalid/empty-import.fxml new file mode 100644 index 0000000..12fdbfe --- /dev/null +++ b/fx2j-parser/src/test/resources/element/invalid/empty-import.fxml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/invalid/empty-language.fxml b/fx2j-parser/src/test/resources/element/invalid/empty-language.fxml new file mode 100644 index 0000000..1ae3833 --- /dev/null +++ b/fx2j-parser/src/test/resources/element/invalid/empty-language.fxml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/invalid/non-declaration-root.fxml b/fx2j-parser/src/test/resources/element/invalid/non-declaration-root.fxml new file mode 100644 index 0000000..d107879 --- /dev/null +++ b/fx2j-parser/src/test/resources/element/invalid/non-declaration-root.fxml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/invalid/property-elements.fxml b/fx2j-parser/src/test/resources/element/invalid/property-elements.fxml new file mode 100644 index 0000000..24e1814 --- /dev/null +++ b/fx2j-parser/src/test/resources/element/invalid/property-elements.fxml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/invalid/property-non-common-attribute.fxml b/fx2j-parser/src/test/resources/element/invalid/property-non-common-attribute.fxml new file mode 100644 index 0000000..e89fc96 --- /dev/null +++ b/fx2j-parser/src/test/resources/element/invalid/property-non-common-attribute.fxml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/valid/empty-property.fxml b/fx2j-parser/src/test/resources/element/valid/empty-property.fxml new file mode 100644 index 0000000..86d0007 --- /dev/null +++ b/fx2j-parser/src/test/resources/element/valid/empty-property.fxml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/valid/multi-property-value.fxml b/fx2j-parser/src/test/resources/element/valid/multi-property-value.fxml new file mode 100644 index 0000000..549d695 --- /dev/null +++ b/fx2j-parser/src/test/resources/element/valid/multi-property-value.fxml @@ -0,0 +1,8 @@ + + + + + text + + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/valid/multi-text.fxml b/fx2j-parser/src/test/resources/element/valid/multi-text.fxml new file mode 100644 index 0000000..9ede9f8 --- /dev/null +++ b/fx2j-parser/src/test/resources/element/valid/multi-text.fxml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/valid/processing-instructions.fxml b/fx2j-parser/src/test/resources/element/valid/processing-instructions.fxml index f278ba5..508b006 100644 --- a/fx2j-parser/src/test/resources/element/valid/processing-instructions.fxml +++ b/fx2j-parser/src/test/resources/element/valid/processing-instructions.fxml @@ -5,5 +5,5 @@ - + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/valid/property-attribute.fxml b/fx2j-parser/src/test/resources/element/valid/property-attribute.fxml new file mode 100644 index 0000000..5ae4c1a --- /dev/null +++ b/fx2j-parser/src/test/resources/element/valid/property-attribute.fxml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/valid/property-element.fxml b/fx2j-parser/src/test/resources/element/valid/property-element.fxml new file mode 100644 index 0000000..3586a20 --- /dev/null +++ b/fx2j-parser/src/test/resources/element/valid/property-element.fxml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/valid/script.fxml b/fx2j-parser/src/test/resources/element/valid/script-inline.fxml similarity index 100% rename from fx2j-parser/src/test/resources/element/valid/script.fxml rename to fx2j-parser/src/test/resources/element/valid/script-inline.fxml diff --git a/fx2j-parser/src/test/resources/element/valid/script-reference.fxml b/fx2j-parser/src/test/resources/element/valid/script-reference.fxml new file mode 100644 index 0000000..d37620d --- /dev/null +++ b/fx2j-parser/src/test/resources/element/valid/script-reference.fxml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/element/valid/instance.fxml b/fx2j-parser/src/test/resources/element/valid/single-property.fxml similarity index 100% rename from fx2j-parser/src/test/resources/element/valid/instance.fxml rename to fx2j-parser/src/test/resources/element/valid/single-property.fxml diff --git a/fx2j-parser/src/test/resources/element/valid/static.fxml b/fx2j-parser/src/test/resources/element/valid/static.fxml index ccbe67c..9524f41 100644 --- a/fx2j-parser/src/test/resources/element/valid/static.fxml +++ b/fx2j-parser/src/test/resources/element/valid/static.fxml @@ -1,5 +1,4 @@ - TOP_RIGHT diff --git a/fx2j-parser/src/test/resources/element/valid/text.fxml b/fx2j-parser/src/test/resources/element/valid/text.fxml new file mode 100644 index 0000000..c9bb1bd --- /dev/null +++ b/fx2j-parser/src/test/resources/element/valid/text.fxml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/property/event-handler-method.fxml b/fx2j-parser/src/test/resources/property/event-handler-method.fxml index def8167..46f2399 100644 --- a/fx2j-parser/src/test/resources/property/event-handler-method.fxml +++ b/fx2j-parser/src/test/resources/property/event-handler-method.fxml @@ -1,3 +1,2 @@ - \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/property/event-handler-reference.fxml b/fx2j-parser/src/test/resources/property/event-handler-reference.fxml index 0666895..e807ae4 100644 --- a/fx2j-parser/src/test/resources/property/event-handler-reference.fxml +++ b/fx2j-parser/src/test/resources/property/event-handler-reference.fxml @@ -1,3 +1,2 @@ - \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/property/event-handler.fxml b/fx2j-parser/src/test/resources/property/event-handler.fxml index 8dc5910..3c57a98 100644 --- a/fx2j-parser/src/test/resources/property/event-handler.fxml +++ b/fx2j-parser/src/test/resources/property/event-handler.fxml @@ -1,3 +1,2 @@ - \ No newline at end of file diff --git a/fx2j-parser/src/test/resources/property/static.fxml b/fx2j-parser/src/test/resources/property/static.fxml index 58ed996..98ff978 100644 --- a/fx2j-parser/src/test/resources/property/static.fxml +++ b/fx2j-parser/src/test/resources/property/static.fxml @@ -1,3 +1,2 @@ - \ No newline at end of file diff --git a/fx2j-processor/build.gradle.kts b/fx2j-processor/build.gradle.kts index 592b14b..e164b2c 100644 --- a/fx2j-processor/build.gradle.kts +++ b/fx2j-processor/build.gradle.kts @@ -5,6 +5,7 @@ plugins { dependencies { implementation(project(":fx2j-api")) + implementation(project(":fx2j-parser")) implementation("com.squareup:javapoet:1.13.0") } diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/Fx2jProcessor.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/Fx2jProcessor.java index d1f9fc6..eae1a6e 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/Fx2jProcessor.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/Fx2jProcessor.java @@ -71,7 +71,7 @@ public Fx2jProcessor(List fxmlProcessors, boolean modular) { throw new IllegalArgumentException("Multiple processor root packages detected: %s".formatted(rootPackages)); } - this.rootPackage = rootPackages.get(0); + this.rootPackage = rootPackages.getFirst(); builderFinderJavaFile = JavaFile.builder(this.rootPackage, buildBuilderFinderTypeSpec()).build(); builderFinderCanonicalClassName = JavaFileUtils.getCanonicalClassName(builderFinderJavaFile); } @@ -167,7 +167,7 @@ private String getModuleInfoContent() { module %s { requires io.github.sheikah45.fx2j.api; %s - + provides %s with %s; } """.formatted(rootPackage, moduleRequires.indent(4), Fx2jBuilderFinder.class.getCanonicalName(), diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/FxmlProcessor.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/FxmlProcessor.java index 3455eea..93e5a06 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/FxmlProcessor.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/FxmlProcessor.java @@ -9,11 +9,13 @@ import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.WildcardTypeName; import io.github.sheikah45.fx2j.api.Fx2jBuilder; +import io.github.sheikah45.fx2j.parser.FxmlComponents; +import io.github.sheikah45.fx2j.parser.FxmlParser; +import io.github.sheikah45.fx2j.parser.FxmlProcessingInstruction; +import io.github.sheikah45.fx2j.parser.attribute.ControllerAttribute; import io.github.sheikah45.fx2j.processor.internal.ObjectNodeProcessor; import io.github.sheikah45.fx2j.processor.internal.ReflectionResolver; -import io.github.sheikah45.fx2j.processor.internal.model.FxmlComponents; import io.github.sheikah45.fx2j.processor.internal.model.ObjectNodeCode; -import io.github.sheikah45.fx2j.processor.internal.utils.FXMLUtils; import io.github.sheikah45.fx2j.processor.internal.utils.JavaFileUtils; import io.github.sheikah45.fx2j.processor.internal.utils.StringUtils; @@ -26,6 +28,7 @@ import java.util.ResourceBundle; import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; /** * The FxmlProcessor class is responsible for processing an FXML file and generating Java code for it. @@ -56,24 +59,38 @@ public class FxmlProcessor { * @param rootPackage The root package for the generated Java code. * @param classLoader The class loader to use for resolving imported classes. */ + @SuppressWarnings("unchecked") public FxmlProcessor(Path filePath, Path resourceRootPath, String rootPackage, ClassLoader classLoader) { this.rootPackage = rootPackage; Path absoluteFilePath = filePath.toAbsolutePath(); - FxmlComponents fxmlComponents = FXMLUtils.readFxml(absoluteFilePath); - resolver = new ReflectionResolver(fxmlComponents.imports(), classLoader); - - String controllerClassName = fxmlComponents.rootNode() - .attributes() - .getOrDefault("fx:controller", fxmlComponents.controllerType()); - - if (controllerClassName != null) { - controllerClass = resolver.resolveRequired(controllerClassName); - if (controllerClass == null) { - throw new IllegalArgumentException("Unable to find controller class %s".formatted(controllerClassName)); - } - } else { - controllerClass = Object.class; - } + FxmlComponents fxmlComponents = FxmlParser.readFxml(absoluteFilePath); + Set imports = fxmlComponents.rootProcessingInstructions() + .stream() + .filter(FxmlProcessingInstruction.Import.class::isInstance) + .map(FxmlProcessingInstruction.Import.class::cast) + .map(FxmlProcessingInstruction.Import::value) + .collect(Collectors.toSet()); + + resolver = new ReflectionResolver(imports, classLoader); + + controllerClass = fxmlComponents.rootNode() + .content() + .attributes() + .stream() + .filter(ControllerAttribute.class::isInstance) + .map(ControllerAttribute.class::cast) + .map(ControllerAttribute::className) + .findFirst() + .or(() -> fxmlComponents.rootProcessingInstructions() + .stream() + .filter(FxmlProcessingInstruction.Custom.class::isInstance) + .map(FxmlProcessingInstruction.Custom.class::cast) + .filter(custom -> "fx2jControllerType".equals( + custom.name())) + .map(FxmlProcessingInstruction.Custom::value) + .findFirst()) + .map(typeName -> (Class) resolver.resolveRequired(typeName)) + .orElse(Object.class); Path absoluteResourceRootPath = resourceRootPath.toAbsolutePath(); objectNodeCode = new ObjectNodeProcessor(fxmlComponents.rootNode(), controllerClass, resolver, absoluteFilePath, diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/ObjectNodeProcessor.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/ObjectNodeProcessor.java index bcd5357..cb690bd 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/ObjectNodeProcessor.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/ObjectNodeProcessor.java @@ -2,9 +2,30 @@ import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import io.github.sheikah45.fx2j.parser.ParseException; +import io.github.sheikah45.fx2j.parser.attribute.EventHandlerAttribute; +import io.github.sheikah45.fx2j.parser.attribute.FxmlAttribute; +import io.github.sheikah45.fx2j.parser.attribute.IdAttribute; +import io.github.sheikah45.fx2j.parser.attribute.InstancePropertyAttribute; +import io.github.sheikah45.fx2j.parser.attribute.StaticPropertyAttribute; +import io.github.sheikah45.fx2j.parser.element.ClassInstanceElement; +import io.github.sheikah45.fx2j.parser.element.ConstantElement; +import io.github.sheikah45.fx2j.parser.element.CopyElement; +import io.github.sheikah45.fx2j.parser.element.DefineElement; +import io.github.sheikah45.fx2j.parser.element.FactoryElement; +import io.github.sheikah45.fx2j.parser.element.FxmlElement; +import io.github.sheikah45.fx2j.parser.element.IncludeElement; +import io.github.sheikah45.fx2j.parser.element.InstanceElement; +import io.github.sheikah45.fx2j.parser.element.InstancePropertyElement; +import io.github.sheikah45.fx2j.parser.element.ReferenceElement; +import io.github.sheikah45.fx2j.parser.element.RootElement; +import io.github.sheikah45.fx2j.parser.element.ScriptElement; +import io.github.sheikah45.fx2j.parser.element.StaticPropertyElement; +import io.github.sheikah45.fx2j.parser.element.ValueElement; +import io.github.sheikah45.fx2j.parser.property.FxmlProperty; +import io.github.sheikah45.fx2j.parser.property.Value; import io.github.sheikah45.fx2j.processor.FxmlProcessor; import io.github.sheikah45.fx2j.processor.ProcessorException; -import io.github.sheikah45.fx2j.processor.internal.model.FxmlNode; import io.github.sheikah45.fx2j.processor.internal.model.NamedArgValue; import io.github.sheikah45.fx2j.processor.internal.model.ObjectNodeCode; import io.github.sheikah45.fx2j.processor.internal.utils.StringUtils; @@ -15,6 +36,7 @@ import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.nio.charset.Charset; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; @@ -29,7 +51,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.Stream; public class ObjectNodeProcessor { private static final Map, Object> DEFAULTS_MAP = Map.of(byte.class, (byte) 0, short.class, (short) 0, @@ -41,7 +62,6 @@ public class ObjectNodeProcessor { Character.class, Float.class, Double.class, Boolean.class); - private static final Pattern BINDING_PATTERN = Pattern.compile("\\$\\{.*}"); private static final Pattern CHANGE_PROPERTY_PATTERN = Pattern.compile("on(?.+)Change"); private static final String EVENT_HANDLER_CLASS = "javafx.event.EventHandler"; private static final String ON_CHANGE = "onChange"; @@ -59,62 +79,85 @@ public class ObjectNodeProcessor { private final ReflectionResolver resolver; private final Path filePath; private final Class controllerClass; - private final FxmlNode objectNode; + private final ClassInstanceElement rootNode; private final NameTracker nameTracker; private final Path resourceRootPath; private final String rootPackage; private final ObjectNodeCode nodeCode; + private final String id; private final CodeBlock.Builder objectInitializationBuilder = CodeBlock.builder(); - private final Map fxAttributes = new HashMap<>(); - private final Map handlerAttributes = new HashMap<>(); - private final Map propertyAttributes = new HashMap<>(); - private final Map staticPropertyAttributes = new HashMap<>(); - private final Map propertyChildren = new HashMap<>(); - private final Map staticPropertyChildren = new HashMap<>(); - private final List definedChildren = new ArrayList<>(); - private final List defaultPropertyChildren = new ArrayList<>(); + private final List defaultPropertyChildren = new ArrayList<>(); + private final Map instanceProperties = new HashMap<>(); + private final List staticProperties = new ArrayList<>(); + private final List handlerProperties = new ArrayList<>(); + private final List definedChildren = new ArrayList<>(); + private final List scripts = new ArrayList<>(); private Class objectClass; private Class[] typeArguments; private String objectIdentifier; - public ObjectNodeProcessor(FxmlNode objectNode, Class controllerClass, ReflectionResolver resolver, - Path filePath, - Path resourceRootPath, String rootPackage) { - this(objectNode, controllerClass, resolver, filePath, resourceRootPath, rootPackage, new NameTracker()); + public ObjectNodeProcessor(ClassInstanceElement rootNode, Class controllerClass, ReflectionResolver resolver, + Path filePath, Path resourceRootPath, String rootPackage) { + this(rootNode, controllerClass, resolver, filePath, resourceRootPath, rootPackage, new NameTracker()); } - private ObjectNodeProcessor(FxmlNode objectNode, Class controllerClass, ReflectionResolver resolver, - Path filePath, - Path resourceRootPath, String rootPackage, NameTracker nameTracker) { + private ObjectNodeProcessor(ClassInstanceElement rootNode, Class controllerClass, ReflectionResolver resolver, + Path filePath, Path resourceRootPath, String rootPackage, NameTracker nameTracker) { this.resourceRootPath = resourceRootPath; this.rootPackage = rootPackage; - String nodeName = objectNode.name(); - if (!nodeName.equals("fx:root") && nodeName.matches("^[a-z]")) { - throw new IllegalArgumentException("Node represents a property"); - } - this.resolver = resolver; this.filePath = filePath; this.controllerClass = controllerClass; - this.objectNode = objectNode; + this.rootNode = rootNode; this.nameTracker = nameTracker; + List attributes = rootNode.content().attributes(); + this.id = attributes.stream() + .filter(IdAttribute.class::isInstance) + .map(IdAttribute.class::cast) + .map(IdAttribute::value) + .findFirst() + .orElse(null); + + attributes.forEach(attribute -> { + switch (attribute) { + case InstancePropertyAttribute instance -> instanceProperties.put(instance.property(), instance); + case StaticPropertyAttribute staticProperty -> staticProperties.add(staticProperty); + case EventHandlerAttribute handler -> handlerProperties.add(handler); + case FxmlAttribute.FxAttribute ignored -> {} + } + }); + + rootNode.content().children().forEach(child -> { + switch (child) { + case InstancePropertyElement instance -> instanceProperties.put(instance.property(), instance); + case StaticPropertyElement stat -> staticProperties.add(stat); + case DefineElement(List children) -> definedChildren.addAll(children); + case ClassInstanceElement classInstanceElement -> + defaultPropertyChildren.add(new Value.Element(classInstanceElement)); + case ScriptElement script -> scripts.add(script); + } + }); + + if (!(rootNode.content().body() instanceof Value.Empty)) { + defaultPropertyChildren.add(rootNode.content().body()); + } - validateAttributes(); - validateChildren(); nodeCode = processNode(); } private ObjectNodeCode processNodeInternal() { + if (!scripts.isEmpty()) { + throw new UnsupportedOperationException("Scripts not supported"); + } + processObjectInitialization(); processDefinedChildren(); processDefaultProperty(); - processPropertyAttributes(); - processPropertyChildren(); - processStaticPropertyAttributes(); - processStaticChildren(); - processHandlerAttributes(); + processInstanceProperties(); + processStaticProperties(); + processHandlerProperties(); return new ObjectNodeCode(objectIdentifier, objectClass, objectInitializationBuilder.add("\n").build()); } @@ -125,62 +168,22 @@ private ObjectNodeCode processNode() { } catch (ProcessorException processorException) { throw processorException; } catch (Exception exception) { - throw new ProcessorException(exception, objectNode.toString()); + throw new ProcessorException(exception, rootNode.toString()); } } - private void validateAttributes() { - this.objectNode.attributes().forEach((key, value) -> { - if (key.startsWith("xmlns")) { - return; - } - - if (key.startsWith("fx:")) { - fxAttributes.put(key.substring(3), value); - } else if (key.startsWith("on")) { - handlerAttributes.put(key, value); - } else if (key.matches("^[a-z]\\w*")) { - propertyAttributes.put(key, value); - } else if (key.matches("^[A-Z]\\w*\\.\\w*")) { - staticPropertyAttributes.put(key, value); - } else { - throw new IllegalArgumentException("Unknown property type %s".formatted(key)); - } - }); - } - - private void validateChildren() { - this.objectNode.children().forEach(child -> { - String childName = child.name(); - if (childName.equals("fx:define")) { - definedChildren.add(child); - } else if (childName.matches("[a-z]\\w*")) { - propertyChildren.put(childName, child); - } else if (childName.matches("(\\w*\\.)+\\w*")) { - if (resolver.isResolvable(childName)) { - defaultPropertyChildren.add(child); - } else { - staticPropertyChildren.put(childName, child); - } - } else { - defaultPropertyChildren.add(child); - } - }); - } - public ObjectNodeCode getNodeCode() { return nodeCode; } - private void processIncludeInitialization() { - Path relativeSourcePath = Path.of(propertyAttributes.remove("source")); - FxmlProcessor includedProcessor = new FxmlProcessor(filePath.resolveSibling(relativeSourcePath), - resourceRootPath, rootPackage, resolver.getClassLoader()); + private void processIncludeInitialization(Path source) { + FxmlProcessor includedProcessor = new FxmlProcessor(filePath.resolveSibling(source), resourceRootPath, + rootPackage, resolver.getClassLoader()); objectClass = resolver.checkResolved(includedProcessor.getRootClass()); if (objectClass == null) { throw new IllegalArgumentException( - "Unable to determine object class for %s".formatted(filePath.resolveSibling(relativeSourcePath))); + "Unable to determine object class for %s".formatted(filePath.resolveSibling(source))); } resolveIdentifier(); @@ -194,7 +197,7 @@ private void processIncludeInitialization() { builderIdentifier); Class includedControllerClass = resolver.checkResolved(includedProcessor.getControllerClass()); - if (includedControllerClass != Object.class && fxAttributes.containsKey("id")) { + if (includedControllerClass != Object.class && id != null) { String controllerIdentifier = objectIdentifier + "Controller"; objectInitializationBuilder.addStatement("$T $L = $L.getController()", includedControllerClass, controllerIdentifier, builderIdentifier); @@ -202,12 +205,12 @@ private void processIncludeInitialization() { } } - private void processHandlerAttributes() { - handlerAttributes.forEach(this::processHandlerAttribute); + private void processHandlerProperties() { + handlerProperties.forEach(this::processHandlerProperty); } - private void processConstructorInitialization() { - objectClass = resolver.resolveRequired(objectNode.name()); + private void processConstructorInitialization(String className) { + objectClass = resolver.resolveRequired(className); resolveIdentifier(); for (List constructorArgs : getMatchingConstructorArgs()) { @@ -220,91 +223,116 @@ private void processConstructorInitialization() { throw new IllegalArgumentException("Unknown constructor"); } - private void processPropertyAttributes() { + private void processInstanceProperties() { + Collection instanceProperties = this.instanceProperties.values(); if (Map.class.isAssignableFrom(objectClass)) { Class keyTypeBound = typeArguments == null ? Object.class : typeArguments[0]; Class valueTypeBound = typeArguments == null ? Object.class : typeArguments[1]; - addAttributesToMapWithTypeBounds(objectIdentifier, propertyAttributes, keyTypeBound, valueTypeBound); + addPropertiesToMapWithTypeBounds(objectIdentifier, instanceProperties, keyTypeBound, valueTypeBound); return; } - propertyAttributes.forEach(this::processPropertyAttribute); + instanceProperties.forEach(property -> processInstanceProperty(property.property(), property.value())); } - private void addAttributesToMapWithTypeBounds(String identifier, Map attributes, + private void addPropertiesToMapWithTypeBounds(String identifier, Collection attributes, Class keyTypeBound, Class valueTypeBound) { - attributes.forEach((key, valueString) -> { - CodeBlock keyValue = coerceValue(keyTypeBound, key); - CodeBlock valueValue = coerceValue(valueTypeBound, valueString); + attributes.forEach(attribute -> { + CodeBlock keyValue = coerceValue(keyTypeBound, attribute.property()); + CodeBlock valueValue = coerceValue(valueTypeBound, attribute.value()); objectInitializationBuilder.addStatement("$L.put($L, $L)", identifier, keyValue, valueValue); }); } - private void processStaticPropertyAttributes() { - staticPropertyAttributes.forEach(this::processStaticPropertyAttribute); + private void processStaticProperties() { + staticProperties.forEach( + property -> processStaticProperty(property.className(), property.property(), property.value())); } private void processDefinedChildren() { - definedChildren.stream().map(FxmlNode::children).flatMap(Collection::stream).forEach(this::buildChildNode); + definedChildren.forEach(this::buildChildNode); } - private void processStaticChildren() { - staticPropertyChildren.values().forEach(this::processStaticPropertyChild); - } - - private void processPropertyChildren() { - if (Map.class.isAssignableFrom(objectClass)) { - Class keyTypeBound = typeArguments == null ? Object.class : typeArguments[0]; - Class valueTypeBound = typeArguments == null ? Object.class : typeArguments[1]; - addNodesToMapWithTypeBounds(objectIdentifier, propertyChildren.values(), keyTypeBound, valueTypeBound); - return; - } + private void processInstanceProperty(String property, Value value) { + switch (value) { + case Value.Element(ClassInstanceElement classInstanceElement) -> + processPropertyClassInstanceElement(property, classInstanceElement); + case Value.Attribute(FxmlProperty.Instance attribute) -> + processPropertiesOnProperty(property, List.of(attribute)); + case Value.Single single -> processInstancePropertySingle(property, single); + case Value.Multi(List values) -> { + List elements = new ArrayList<>(); + List properties = new ArrayList<>(); + + values.forEach(val -> { + switch (val) { + case Value.Element(ClassInstanceElement element) -> elements.add(element); + case Value.Element(FxmlProperty.Instance element) -> properties.add(element); + case Value.Attribute(FxmlProperty.Instance attribute) -> properties.add(attribute); + case Value.Single single -> processInstancePropertySingle(property, single); + } + }); - if (Collection.class.isAssignableFrom(objectClass)) { - Class contentTypeBound = typeArguments == null ? Object.class : typeArguments[0]; - addNodesToCollectionWithTypeBound(objectIdentifier, propertyChildren.values(), contentTypeBound); - return; + if (!elements.isEmpty()) { + processPropertyElements(property, elements); + } + if (!properties.isEmpty()) { + processPropertiesOnProperty(property, properties); + } + } + default -> throw new UnsupportedOperationException( + "Cannot process value %s for property %s".formatted(value, property)); } - - propertyChildren.values().forEach(this::processPropertyChild); } - private void processPropertyChild(FxmlNode propertyNode) { - if (propertyNode.innerText() != null) { - processPropertyAttribute(propertyNode.name(), propertyNode.innerText()); - } - if (!propertyNode.children().isEmpty()) { - processPropertyNestedChildren(propertyNode.name(), propertyNode.children()); - } - if (!propertyNode.attributes().isEmpty()) { - processAttributesOnProperty(propertyNode.name(), propertyNode.attributes()); - } - } + private void processStaticProperty(String className, String property, Value value) { + switch (value) { + case Value.Single single -> processStaticPropertySingle(className, property, single); + case Value.Multi(List values) -> { + List elements = new ArrayList<>(); + + values.forEach(val -> { + switch (val) { + case Value.Element(ClassInstanceElement element) -> elements.add(element); + case Value.Attribute ignored -> + throw new UnsupportedOperationException("Attributes not allowed on static properties"); + case Value.Single single -> processStaticPropertySingle(className, property, single); + } + }); - private void processStaticPropertyChild(FxmlNode propertyNode) { - if (propertyNode.innerText() != null) { - processStaticPropertyAttribute(propertyNode.name(), propertyNode.innerText()); - } - if (!propertyNode.children().isEmpty()) { - processStaticPropertyNestedChildren(propertyNode.name(), propertyNode.children()); - } - if (!propertyNode.attributes().isEmpty()) { - throw new UnsupportedOperationException("Attributes on static property children not supported"); + processStaticPropertyElements(className, property, elements); + } + default -> throw new UnsupportedOperationException( + "Cannot process value %s for static property %s".formatted(value, property)); } } private void processObjectInitialization() { objectInitializationBuilder.add("\n"); - switch (objectNode.name()) { - case "fx:root" -> processRootInitialization(); - case "fx:reference" -> processReferenceInitialization(); - case "fx:copy" -> processCopyInitialization(); - case "fx:include" -> processIncludeInitialization(); - default -> processAttributeBasedInitialization(); + switch (rootNode) { + case RootElement(String type, ClassInstanceElement.Content ignored) -> processRootInitialization(type); + case ReferenceElement(String source, ClassInstanceElement.Content content) -> + processReferenceInitialization(source, content); + case CopyElement( + String source, ClassInstanceElement.Content ignored + ) -> processCopyInitialization(source); + case IncludeElement( + Path source, Path ignored1, Charset ignored2, ClassInstanceElement.Content ignored3 + ) -> processIncludeInitialization(source); + case FactoryElement( + String factoryClassName, String methodName, ClassInstanceElement.Content ignored + ) -> processFactoryBasedInitialization(factoryClassName, methodName); + case ConstantElement(String className, String member, ClassInstanceElement.Content ignored) -> + processConstantInitialization(className, member); + case ValueElement(String className, Value.Literal(String value), ClassInstanceElement.Content ignored) -> + processValueInitialization(className, value); + case InstanceElement(String className, ClassInstanceElement.Content ignored) -> + processConstructorInitialization(className); + default -> throw new UnsupportedOperationException("Unable to initialize object"); } - if (fxAttributes.containsKey("id")) { + if (id != null) { processControllerSetter(objectIdentifier, objectClass); resolver.resolveSetter(objectClass, "id", String.class) .ifPresent(method -> objectInitializationBuilder.addStatement("$L.$L($S)", objectIdentifier, @@ -337,7 +365,6 @@ private void processControllerSetter(String identifier, Class valueClass) { } private void resolveIdentifier() { - String id = fxAttributes.get("id"); if (id != null) { objectIdentifier = id; } else { @@ -346,25 +373,20 @@ private void resolveIdentifier() { nameTracker.storeIdClass(objectIdentifier, objectClass); } - private void processCopyInitialization() { - String sourceReference = propertyAttributes.remove("source"); - objectClass = nameTracker.getStoredClassById(sourceReference); + private void processCopyInitialization(String source) { + objectClass = nameTracker.getStoredClassById(source); if (!resolver.hasCopyConstructor(objectClass)) { throw new IllegalArgumentException("No copy constructor found for class %s".formatted(objectClass)); } - objectIdentifier = sourceReference + "Copy"; - objectInitializationBuilder.addStatement("$1T $2L = new $1T($3L)", objectClass, objectIdentifier, - sourceReference); + objectIdentifier = source + "Copy"; + objectInitializationBuilder.addStatement("$1T $2L = new $1T($3L)", objectClass, objectIdentifier, source); } - private void processReferenceInitialization() { - objectIdentifier = propertyAttributes.remove("source"); + private void processReferenceInitialization(String source, ClassInstanceElement.Content content) { + objectIdentifier = source; objectClass = nameTracker.getStoredClassById(objectIdentifier); - if (!propertyAttributes.isEmpty() || - !staticPropertyAttributes.isEmpty() || - !fxAttributes.isEmpty() || - !objectNode.children().isEmpty()) { + if (!content.attributes().isEmpty() || !content.children().isEmpty()) { throw new UnsupportedOperationException("References with children or attributes not supported"); } } @@ -381,15 +403,13 @@ private void processControllerSettersFromKnownClass(String identifier, Class .ifPresent(objectInitializationBuilder::addStatement); } - private void processRootInitialization() { + private void processRootInitialization(String type) { objectIdentifier = FxmlProcessor.BUILDER_PROVIDED_ROOT_NAME; - String rootType = propertyAttributes.remove("type"); - objectClass = resolver.resolveRequired(rootType); + objectClass = resolver.resolveRequired(type); } - private void processValueInitialization() { - String value = fxAttributes.get("value"); - objectClass = resolver.resolveRequired(objectNode.name()); + private void processValueInitialization(String className, String value) { + objectClass = resolver.resolveRequired(className); resolveIdentifier(); if (objectClass == String.class) { objectInitializationBuilder.addStatement("$T $L = $S", objectClass, objectIdentifier, value); @@ -412,22 +432,8 @@ private void processValueInitialization() { } - private void processAttributeBasedInitialization() { - if (fxAttributes.containsKey("factory")) { - processFactoryBasedInitialization(); - } else if (fxAttributes.containsKey("constant")) { - processConstantInitialization(); - } else if (fxAttributes.containsKey("value")) { - processValueInitialization(); - } else { - processConstructorInitialization(); - } - } - private Set> getMatchingConstructorArgs() { - Set definedProperties = Stream.of(propertyAttributes.keySet(), propertyChildren.keySet()) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); + Set definedProperties = instanceProperties.keySet(); Map> definedPropertiesByMutability = definedProperties.stream() .collect(Collectors.partitioningBy( @@ -509,26 +515,25 @@ private CodeBlock coerceUsingValueOfMethodResults(String valueString, Method val return CodeBlock.of("$T.$L($S)", valueType, valueOfMethod.getName(), valueString); } - private void processConstantInitialization() { - String constant = fxAttributes.get("constant"); - Class constantContainerClass = resolver.resolveRequired(objectNode.name()); - objectClass = resolver.resolveFieldTypeRequiredPublic(constantContainerClass, constant).orElseThrow(); + private void processConstantInitialization(String className, String member) { + Class constantContainerClass = resolver.resolveRequired(className); + objectClass = resolver.resolveFieldTypeRequiredPublic(constantContainerClass, member).orElseThrow(); resolveIdentifier(); objectInitializationBuilder.addStatement("$T $L = $T.$L", objectClass, objectIdentifier, constantContainerClass, - constant); + member); } - private void processFactoryBasedInitialization() { - String factory = fxAttributes.get("factory"); - Class factoryClazz = resolver.resolveRequired(objectNode.name()); - Method factoryMethod = resolver.findMethod(factoryClazz, factory) + private void processFactoryBasedInitialization(String factoryClassName, String factoryMethodName) { + Class factoryClass = resolver.resolveRequired(factoryClassName); + Method factoryMethod = resolver.findMethod(factoryClass, factoryMethodName) .orElseThrow(() -> new IllegalArgumentException( - "Factory method not found %s.%s".formatted(objectNode.name(), factory))); + "Factory method not found %s.%s".formatted(factoryClassName, + factoryMethodName))); objectClass = factoryMethod.getReturnType(); resolveIdentifier(); - objectInitializationBuilder.addStatement("$T $L = $T.$L()", objectClass, objectIdentifier, factoryClazz, + objectInitializationBuilder.addStatement("$T $L = $T.$L()", objectClass, objectIdentifier, factoryClass, factoryMethod.getName()); } @@ -537,10 +542,7 @@ private void buildWithConstructorArgs(List namedArgValues) { .map(this::resolveParameterValue) .collect(CodeBlock.joining(", ")); - namedArgValues.stream().map(NamedArgValue::name).forEach(paramName -> { - propertyAttributes.remove(paramName); - propertyChildren.remove(paramName); - }); + namedArgValues.stream().map(NamedArgValue::name).forEach(instanceProperties::remove); objectInitializationBuilder.addStatement("$T $L = new $T($L)", objectClass, objectIdentifier, objectClass, parameterValues); @@ -549,32 +551,24 @@ private void buildWithConstructorArgs(List namedArgValues) { private CodeBlock resolveParameterValue(NamedArgValue namedArgValue) { Class paramType = namedArgValue.parameterType(); String paramName = namedArgValue.name(); - FxmlNode paramNode = propertyChildren.get(paramName); - if (paramNode != null) { - List paramChildren = paramNode.children(); - if (paramChildren.isEmpty()) { - return coerceValue(paramType, paramNode.innerText()); + FxmlProperty.Instance property = instanceProperties.get(paramName); + Value value = property == null ? null : property.value(); + return switch (value) { + case Value.Element(ClassInstanceElement classInstanceElement) -> { + ObjectNodeCode nodeCode = buildChildNode(classInstanceElement); + yield CodeBlock.of("$L", nodeCode.nodeIdentifier()); } - - if (paramChildren.size() == 1) { - ObjectNodeCode nodeCode = buildChildNode(paramChildren.getFirst()); - return CodeBlock.of("$L", nodeCode.nodeIdentifier()); + case Value.Single single -> coerceValue(paramType, single); + case null -> { + String defaultValue = namedArgValue.defaultValue(); + if (defaultValue.isBlank()) { + yield CodeBlock.of("$L", DEFAULTS_MAP.get(namedArgValue.parameterType())); + } + yield coerceValue(paramType, defaultValue); } - - throw new UnsupportedOperationException("Multiple children parameter construction not supported"); - } - - String attributeValue = propertyAttributes.get(paramName); - if (attributeValue != null) { - return coerceValue(paramType, attributeValue); - } - - String defaultValue = namedArgValue.defaultValue(); - if (!defaultValue.isEmpty()) { - return coerceValue(paramType, defaultValue); - } - - return CodeBlock.of("$L", DEFAULTS_MAP.get(namedArgValue.parameterType())); + default -> + throw new UnsupportedOperationException("Cannot resolve parameter value from %s".formatted(value)); + }; } private List> getPossibleConstructorArgs() { @@ -584,20 +578,16 @@ private List> getPossibleConstructorArgs() { .toList(); } - private void addNodesToMapWithTypeBounds(String mapIdentifier, Collection nodes, Class keyTypeBound, - Class valueTypeBound) { - for (FxmlNode propertyNode : nodes) { - CodeBlock key = coerceValue(keyTypeBound, propertyNode.name()); - - if (propertyNode.innerText() != null) { - CodeBlock value = coerceValue(valueTypeBound, propertyNode.innerText()); - objectInitializationBuilder.addStatement("$L.put($L, $L)", mapIdentifier, key, value); - } + private void addNodesToMapWithTypeBounds(String mapIdentifier, Collection nodes, + Class keyTypeBound, Class valueTypeBound) { + for (FxmlProperty.Instance propertyNode : nodes) { + String property = propertyNode.property(); + Value value = propertyNode.value(); + CodeBlock key = coerceValue(keyTypeBound, property); - List children = propertyNode.children(); - if (!children.isEmpty()) { - if (children.size() == 1) { - ObjectNodeCode nodeCode = buildChildNode(children.getFirst()); + switch (value) { + case Value.Element(ClassInstanceElement classInstanceElement) -> { + ObjectNodeCode nodeCode = buildChildNode(classInstanceElement); if (!valueTypeBound.isAssignableFrom(nodeCode.nodeClass())) { throw new IllegalArgumentException( "Value type of %s can not be assigned to node type of %s".formatted(valueTypeBound, @@ -606,33 +596,40 @@ private void addNodesToMapWithTypeBounds(String mapIdentifier, Collection { + CodeBlock val = coerceValue(valueTypeBound, single); + objectInitializationBuilder.addStatement("$L.put($L, $L)", mapIdentifier, key, val); + } + default -> throw new UnsupportedOperationException( + "Cannot populate map with provided value: %s".formatted(value)); } } } - private void processHandlerAttribute(String key, String valueString) { - Method handlerSetter = resolver.resolveSetterRequiredPublicIfExists(objectClass, key, resolver.resolveRequired( - EVENT_HANDLER_CLASS)).orElse(null); + private void processHandlerProperty(FxmlProperty.EventHandler eventHandler) { + String eventName = eventHandler.eventName(); + Value.Handler handler = eventHandler.handler(); + Method handlerSetter = resolver.resolveSetterRequiredPublicIfExists(objectClass, eventName, + resolver.resolveRequired( + EVENT_HANDLER_CLASS)).orElse(null); if (handlerSetter != null) { - processHandlerSetter(valueString, handlerSetter); + processHandlerSetter(handler, handlerSetter); return; } - Matcher propertyMatcher = CHANGE_PROPERTY_PATTERN.matcher(key); + Matcher propertyMatcher = CHANGE_PROPERTY_PATTERN.matcher(eventName); if (propertyMatcher.matches()) { String property = propertyMatcher.group("property"); - processPropertyChangeListener(valueString, property); + processPropertyChangeListener(handler, property); return; } throw new UnsupportedOperationException( - "Unknown property %s for class %s".formatted(key, objectClass.getName())); + "Unknown property %s for class %s".formatted(eventName, objectClass.getName())); } - private void processPropertyChangeListener(String valueString, String property) { + private void processPropertyChangeListener(Value.Handler handler, String property) { Method propertyMethod = resolver.resolveProperty(objectClass, property) .orElseThrow(() -> new IllegalArgumentException( "Unable to find property method for %s".formatted(property))); @@ -642,12 +639,12 @@ private void processPropertyChangeListener(String valueString, String property) .orElseThrow(() -> new IllegalArgumentException( "Unable to determine the class of property %s".formatted(property))); - CodeBlock valueCode = resolveControllerPropertyChangeListener(propertyClass, valueString); + CodeBlock valueCode = resolveControllerPropertyChangeListener(propertyClass, handler); objectInitializationBuilder.addStatement("$L.$L().addListener($L)", objectIdentifier, propertyMethod.getName(), valueCode); } - private void processHandlerSetter(String valueString, Method handlerSetter) { + private void processHandlerSetter(Value.Handler handler, Method handlerSetter) { Type handlerType = handlerSetter.getGenericParameterTypes()[0]; Class eventClass; if (handlerType instanceof ParameterizedType parameterizedType) { @@ -661,11 +658,11 @@ private void processHandlerSetter(String valueString, Method handlerSetter) { } else { eventClass = Object.class; } - CodeBlock valueCode = resolveControllerEventHandler(eventClass, valueString); + CodeBlock valueCode = resolveControllerEventHandler(eventClass, handler); objectInitializationBuilder.addStatement("$L.$L($L)", objectIdentifier, handlerSetter.getName(), valueCode); } - private void processAttributesOnProperty(String property, Map attributes) { + private void processPropertiesOnProperty(String property, Collection attributes) { Method propertyGetter = resolver.resolveGetter(objectClass, property) .orElseThrow(() -> new IllegalArgumentException( "Unable to find getter for property %s on class %s".formatted(property, @@ -673,55 +670,73 @@ private void processAttributesOnProperty(String property, Map at Class propertyClass = propertyGetter.getReturnType(); - attributes.forEach((key, value) -> { - if (ON_CHANGE.equals(key)) { - Method propertyMethod = resolver.resolveProperty(objectClass, property).orElse(null); - if (propertyMethod != null) { - CodeBlock listener = resolveControllerPropertyChangeListener(propertyClass, value); - objectInitializationBuilder.addStatement("$L.$L().addListener($L)", objectIdentifier, - propertyMethod.getName(), listener); - return; + attributes.forEach(attribute -> { + switch (attribute) { + case EventHandlerAttribute eventHandler -> { + if (!ON_CHANGE.equals(eventHandler.eventName())) { + throw new UnsupportedOperationException("Only onChange handler supported on properties"); + } + Value.Handler handler = eventHandler.handler(); + resolver.resolveProperty(objectClass, property).ifPresentOrElse(propertyMethod -> { + CodeBlock listener = resolveControllerPropertyChangeListener(propertyClass, handler); + objectInitializationBuilder.addStatement("$L.$L().addListener($L)", objectIdentifier, + propertyMethod.getName(), listener); + }, () -> { + CodeBlock listener = resolveControllerContainerChangeListener( + propertyGetter.getGenericReturnType(), handler); + objectInitializationBuilder.addStatement("$L.$L().addListener($L)", objectIdentifier, + propertyGetter.getName(), listener); + }); } - - CodeBlock listener = resolveControllerContainerChangeListener(propertyGetter.getGenericReturnType(), - value); - objectInitializationBuilder.addStatement("$L.$L().addListener($L)", objectIdentifier, - propertyGetter.getName(), listener); - return; - } - - if (Map.class.isAssignableFrom(propertyClass)) { - Class keyClass = typeArguments == null ? Object.class : typeArguments[0]; - Class valueClass = typeArguments == null ? Object.class : typeArguments[1]; - CodeBlock keyValue = coerceValue(keyClass, key); - CodeBlock valueValue = coerceValue(valueClass, value); - objectInitializationBuilder.addStatement("$L.$L().put($L, $L)", objectIdentifier, - propertyGetter.getName(), keyValue, valueValue); + case InstancePropertyAttribute(String subProperty, Value.Single value) when Map.class.isAssignableFrom( + propertyClass) -> { + Class keyClass = typeArguments == null ? Object.class : typeArguments[0]; + Class valueClass = typeArguments == null ? Object.class : typeArguments[1]; + CodeBlock keyValue = coerceValue(keyClass, subProperty); + CodeBlock valueValue = coerceValue(valueClass, value); + objectInitializationBuilder.addStatement("$L.$L().put($L, $L)", objectIdentifier, + propertyGetter.getName(), keyValue, valueValue); + } + case InstancePropertyElement(String subProperty, Value value) when Map.class.isAssignableFrom( + propertyClass) -> { + Class keyClass = typeArguments == null ? Object.class : typeArguments[0]; + Class valueClass = typeArguments == null ? Object.class : typeArguments[1]; + CodeBlock keyValue = coerceValue(keyClass, subProperty); + CodeBlock valueValue = coerceValue(valueClass, value); + objectInitializationBuilder.addStatement("$L.$L().put($L, $L)", objectIdentifier, + propertyGetter.getName(), keyValue, valueValue); + } + default -> throw new UnsupportedOperationException( + "Cannot process property value %s for property class %s".formatted(attribute, propertyClass)); } }); } - private void processPropertyAttribute(String key, String valueString) { - Matcher bindingMatcher = BINDING_PATTERN.matcher(valueString); - if (bindingMatcher.matches()) { + private void processInstancePropertySingle(String property, Value.Single value) { + if (value instanceof Value.Expression) { throw new UnsupportedOperationException("Binding expressions not supported"); } - Method propertySetter = resolver.resolveSetter(objectClass, key).orElse(null); + if (value instanceof Value.Empty) { + return; + } + + Method propertySetter = resolver.resolveSetter(objectClass, property).orElse(null); if (propertySetter != null) { - processPropertySetter(valueString, propertySetter); + processPropertySetter(value, propertySetter); return; } - Method propertyGetter = resolver.resolveGetter(objectClass, key).orElse(null); + Method propertyGetter = resolver.resolveGetter(objectClass, property).orElse(null); if (propertyGetter != null) { Type propertyGenericType = propertyGetter.getGenericReturnType(); Class propertyClass = propertyGetter.getReturnType(); - String propertyName = objectIdentifier + StringUtils.capitalize(key); + String propertyName = objectIdentifier + StringUtils.capitalize(property); if (Collection.class.isAssignableFrom(propertyClass) && - propertyGenericType instanceof ParameterizedType parameterizedType) { - processCollectionInitialization(valueString, parameterizedType, propertyName, propertyGetter); + propertyGenericType instanceof ParameterizedType parameterizedType && + value instanceof Value.Literal(String val)) { + processCollectionInitialization(val, parameterizedType, propertyName, propertyGetter); return; } @@ -730,11 +745,11 @@ private void processPropertyAttribute(String key, String valueString) { } throw new UnsupportedOperationException( - "Unknown property %s for class %s".formatted(key, objectClass.getName())); + "Unknown property %s for class %s".formatted(property, objectClass.getName())); } - private void processCollectionInitialization(String valueString, ParameterizedType parameterizedType, - String propertyName, Method propertyGetter) { + private void processCollectionInitialization(String value, ParameterizedType parameterizedType, String propertyName, + Method propertyGetter) { objectInitializationBuilder.addStatement("$T $L = $L.$L()", resolver.resolveTypeNameWithoutVariables(parameterizedType), propertyName, objectIdentifier, propertyGetter.getName()); @@ -744,25 +759,27 @@ private void processCollectionInitialization(String valueString, ParameterizedTy "Unable to resolve contained type for type %s".formatted(parameterizedType)); } Class containedClassBound = resolver.resolveTypeUpperBound(actualTypeArguments[0]); - Arrays.stream(valueString.split(",\\s*")) - .map(value -> coerceValue(containedClassBound, value)) + Arrays.stream(value.split(",\\s*")) + .map(val -> coerceValue(containedClassBound, val)) .forEach(codeBlock -> objectInitializationBuilder.addStatement("$L.add($L)", propertyName, codeBlock)); } - private void processPropertySetter(String valueString, Method propertySetter) { + private void processPropertySetter(Value value, Method propertySetter) { + if (value instanceof Value.Empty) { + return; + } + Class valueType = propertySetter.getParameterTypes()[0]; - CodeBlock valueCode = coerceValue(valueType, valueString); + CodeBlock valueCode = coerceValue(valueType, value); objectInitializationBuilder.addStatement("$L.$L($L)", objectIdentifier, propertySetter.getName(), valueCode); } - private void processStaticPropertyAttribute(String key, String valueString) { - String[] propertyParts = key.split("\\."); - String staticProperty = propertyParts[1]; - Class staticPropertyClass = resolver.resolveRequired(propertyParts[0]); - Method propertySetter = resolver.resolveStaticSetter(staticPropertyClass, staticProperty).orElse(null); + private void processStaticPropertySingle(String className, String property, Value value) { + Class staticPropertyClass = resolver.resolveRequired(className); + Method propertySetter = resolver.resolveStaticSetter(staticPropertyClass, property).orElse(null); if (propertySetter == null) { throw new IllegalArgumentException( - "Unable to find static setter for %s on %s".formatted(staticProperty, staticPropertyClass)); + "Unable to find static setter for %s on %s".formatted(property, staticPropertyClass)); } Class objectType = propertySetter.getParameterTypes()[0]; @@ -771,48 +788,51 @@ private void processStaticPropertyAttribute(String key, String valueString) { } Class valueType = propertySetter.getParameterTypes()[1]; - CodeBlock valueCode = coerceValue(valueType, valueString); + CodeBlock valueCode = coerceValue(valueType, value); objectInitializationBuilder.addStatement("$T.$L($L, $L)", staticPropertyClass, propertySetter.getName(), objectIdentifier, valueCode); } - private CodeBlock coerceValue(Class valueType, String valueString) { + private CodeBlock coerceValue(Class valueType, Value value) { + return switch (value) { + case Value.Element(ClassInstanceElement element) -> { + ObjectNodeCode nodeCode = buildChildNode(element); + if (!valueType.isAssignableFrom(nodeCode.nodeClass())) { + throw new IllegalArgumentException( + "Cannot assign %s to %s".formatted(nodeCode.nodeClass(), valueType)); + } + yield CodeBlock.of("$L", nodeCode.nodeIdentifier()); + } + case Value.Location ignored -> + throw new UnsupportedOperationException("Location resolution not yet supported"); + case Value.Reference(String reference) -> { + Class referenceClass = nameTracker.getStoredClassById(reference); + if (!valueType.isAssignableFrom(referenceClass)) { + throw new IllegalArgumentException("Cannot assign %s to %s".formatted(referenceClass, valueType)); + } + + yield CodeBlock.of("$L", reference); + } + case Value.Resource(String resource) when valueType == String.class -> + CodeBlock.of("$1L.getString($2S)", FxmlProcessor.RESOURCES_NAME, resource); + case Value.Literal(String val) when valueType == String.class -> CodeBlock.of("$S", val); + case Value.Literal(String val) -> coerceValue(valueType, val); + default -> throw new UnsupportedOperationException( + "Cannot create type %s from %s".formatted(valueType, value)); + }; + } + + private CodeBlock coerceValue(Class valueType, String value) { if (valueType.isArray()) { Class componentType = valueType.getComponentType(); - CodeBlock arrayInitializer = Arrays.stream(valueString.split(",")) + CodeBlock arrayInitializer = Arrays.stream(value.split(",")) .map(componentString -> coerceValue(componentType, componentString)) .collect(CodeBlock.joining(", ")); return CodeBlock.of("new $T{$L}", valueType, arrayInitializer); } - if (valueString.startsWith("$")) { - String reference = valueString.substring(1); - Class referenceClass = nameTracker.getStoredClassById(reference); - if (!valueType.isAssignableFrom(referenceClass)) { - throw new IllegalArgumentException("Cannot assign %s to %s".formatted(referenceClass, valueType)); - } - - return CodeBlock.of("$L", reference); - } - - if (valueString.startsWith("@")) { - throw new UnsupportedOperationException("Location resolution not yet supported"); - } - if (valueType == String.class) { - if (valueString.startsWith("%")) { - return CodeBlock.of("$1L.getString($2S)", FxmlProcessor.RESOURCES_NAME, valueString.substring(1)); - } - - if (valueString.startsWith("\\")) { - valueString = valueString.substring(1); - } - - return CodeBlock.of("$S", valueString); - } - - if (valueString.startsWith("\\")) { - valueString = valueString.substring(1); + return CodeBlock.of("$S", value); } if (valueType.isPrimitive()) { @@ -821,7 +841,7 @@ private CodeBlock coerceValue(Class valueType, String valueString) { .orElse(null); if (method != null) { try { - return coerceUsingValueOfMethodResults(valueString, method, boxedType); + return coerceUsingValueOfMethodResults(value, method, boxedType); } catch (IllegalAccessException | InvocationTargetException ignored) {} } } @@ -830,43 +850,42 @@ private CodeBlock coerceValue(Class valueType, String valueString) { Method method = resolver.findMethod(boxedType, "valueOf", String.class).orElse(null); if (method != null) { try { - return coerceUsingValueOfMethodResults(valueString, method, boxedType); + return coerceUsingValueOfMethodResults(value, method, boxedType); } catch (IllegalAccessException | InvocationTargetException ignored) {} } if (valueType == Object.class) { - return CodeBlock.of("$S", valueString); + return CodeBlock.of("$S", value); } - throw new UnsupportedOperationException("Cannot create type %s from %s".formatted(valueType, valueString)); + throw new UnsupportedOperationException("Cannot create type %s from %s".formatted(valueType, value)); } - private ObjectNodeCode buildChildNode(FxmlNode childNode) { + private ObjectNodeCode buildChildNode(ClassInstanceElement childNode) { ObjectNodeCode nodeCode = new ObjectNodeProcessor(childNode, controllerClass, resolver, filePath, - resourceRootPath, rootPackage, nameTracker - ).getNodeCode(); + resourceRootPath, rootPackage, nameTracker).getNodeCode(); objectInitializationBuilder.add(nodeCode.objectInitializationCode()); return nodeCode; } - private CodeBlock resolveControllerEventHandler(Class eventType, String valueString) { - if (valueString.startsWith("#")) { - String methodName = valueString.substring(1); - return resolver.findMethod(controllerClass, methodName, eventType) - .map(method -> CodeBlock.of("$L::$L", FxmlProcessor.CONTROLLER_NAME, method.getName())) - .or(() -> resolver.findMethod(controllerClass, methodName) - .map(method -> CodeBlock.of("event -> $L.$L()", - FxmlProcessor.CONTROLLER_NAME, - method.getName()))) - .orElseThrow(() -> new IllegalArgumentException( - "No method %s on %s".formatted(methodName, controllerClass))); + private CodeBlock resolveControllerEventHandler(Class eventType, Value.Handler handler) { + if (!(handler instanceof Value.Handler.Method(String methodName))) { + throw new UnsupportedOperationException("None method handlers not supported"); } - throw new IllegalArgumentException("No method for %s".formatted(valueString)); + return resolver.findMethod(controllerClass, methodName, eventType) + .map(method -> CodeBlock.of("$L::$L", FxmlProcessor.CONTROLLER_NAME, method.getName())) + .or(() -> resolver.findMethod(controllerClass, methodName) + .map(method -> CodeBlock.of("event -> $L.$L()", FxmlProcessor.CONTROLLER_NAME, + method.getName()))) + .orElseThrow(() -> new IllegalArgumentException( + "No method %s on %s".formatted(methodName, controllerClass))); } - private CodeBlock resolveControllerContainerChangeListener(Type valueType, String valueString) { - String methodName = valueString.substring(1); + private CodeBlock resolveControllerContainerChangeListener(Type valueType, Value.Handler handler) { + if (!(handler instanceof Value.Handler.Method(String methodName))) { + throw new UnsupportedOperationException("Non method handlers not supported"); + } Class valueClass = resolver.resolveClassFromType(valueType); Class[] boundTypeArguments = resolver.resolveUpperBoundTypeArguments(valueType); @@ -891,12 +910,11 @@ private CodeBlock resolveControllerContainerChangeListener(Type valueType, Strin methodName, valueType))); } - private CodeBlock resolveControllerPropertyChangeListener(Class valueClass, String valueString) { - if (!valueString.startsWith("#")) { - throw new UnsupportedOperationException( - "Unknown value for change listener value %s".formatted(valueString)); + private CodeBlock resolveControllerPropertyChangeListener(Class valueClass, Value.Handler handler) { + if (!(handler instanceof Value.Handler.Method(String methodName))) { + throw new UnsupportedOperationException("Non method change listeners not supported"); } - String methodName = valueString.substring(1); + Method changeMethod = resolver.findMethod(controllerClass, methodName, resolver.resolveRequired(OBSERVABLE_VALUE_CLASS), valueClass, valueClass) @@ -916,27 +934,26 @@ private CodeBlock resolveControllerPropertyChangeListener(Class valueClass, S } private void processDefaultProperty() { - if (defaultPropertyChildren.isEmpty() && objectNode.innerText() == null) { + if (defaultPropertyChildren.isEmpty()) { return; } if (Collection.class.isAssignableFrom(objectClass)) { - Class contentTypeBound = typeArguments == null ? Object.class : typeArguments[0]; - addNodesToCollectionWithTypeBound(objectIdentifier, defaultPropertyChildren, contentTypeBound); - return; - } + List classInstanceElements = defaultPropertyChildren.stream().map(value -> { + if (!(value instanceof Value.Element(ClassInstanceElement classInstanceElement))) { + throw new ParseException("Collection property contains a non class instance element"); + } - if (Map.class.isAssignableFrom(objectClass)) { - Class keyTypeBound = typeArguments == null ? Object.class : typeArguments[0]; - Class valueTypeBound = typeArguments == null ? Object.class : typeArguments[1]; - addNodesToMapWithTypeBounds(objectIdentifier, defaultPropertyChildren, keyTypeBound, valueTypeBound); + return classInstanceElement; + }).toList(); + Class contentTypeBound = typeArguments == null ? Object.class : typeArguments[0]; + addNodesToCollectionWithTypeBound(objectIdentifier, classInstanceElements, contentTypeBound); return; } String defaultProperty = resolver.getDefaultProperty(objectClass); if (defaultProperty != null) { - processPropertyChild( - new FxmlNode(defaultProperty, objectNode.innerText(), Map.of(), defaultPropertyChildren)); + processInstanceProperty(defaultProperty, new Value.Multi(defaultPropertyChildren)); return; } @@ -944,74 +961,74 @@ private void processDefaultProperty() { throw new IllegalArgumentException("Unable to handle default children for class %s".formatted(objectClass)); } - private void processPropertyNestedChildren(String property, Collection childNodes) { - Map> splitNodes = childNodes.stream() - .collect(Collectors.partitioningBy( - node -> "fx:define".equals(node.name()))); - - splitNodes.get(true).stream().map(FxmlNode::children).flatMap(Collection::stream).forEach(this::buildChildNode); - - List propertyNodes = splitNodes.get(false); + private void processPropertyClassInstanceElement(String property, ClassInstanceElement element) { Method propertySetter = resolver.resolveSetter(objectClass, property).orElse(null); if (propertySetter != null) { - if (propertyNodes.size() != 1) { - throw new UnsupportedOperationException( - "Cannot set property %s from multiple child nodes".formatted(property)); - } - processPropertyChildSetter(propertyNodes.getFirst(), propertySetter); - return; - } - - Method propertyGetter = resolver.resolveGetter(objectClass, property).orElse(null); - if (propertyGetter != null) { - processReadOnlyProperty(property, propertyGetter, propertyNodes); - return; + processPropertyChildSetter(element, propertySetter); + } else { + processPropertyElements(property, List.of(element)); } + } - throw new UnsupportedOperationException("Unknown property element type for %s".formatted(property)); + private void processPropertyElements(String property, Collection childNodes) { + Method propertyGetter = resolver.resolveGetter(objectClass, property) + .orElseThrow(() -> new IllegalStateException( + "Cannot find getter for property %s".formatted(property))); + processReadOnlyProperty(property, propertyGetter, childNodes); } - private void processReadOnlyProperty(String property, Method propertyGetter, Collection propertyNodes) { + private void processReadOnlyProperty(String property, Method propertyGetter, + Collection propertyNodes) { Class propertyClass = propertyGetter.getReturnType(); String propertyName = objectIdentifier + StringUtils.capitalize(property); if (Collection.class.isAssignableFrom(propertyClass)) { - processPropertyNestedChildCollection(propertyGetter, propertyName, propertyNodes); + List classInstanceElements = propertyNodes.stream().map(attribute -> { + if (!(attribute instanceof ClassInstanceElement classInstanceElement)) { + throw new ParseException("property attribute contains a non common attribute"); + } + + return classInstanceElement; + }).toList(); + + processPropertyNestedChildCollection(propertyGetter, propertyName, classInstanceElements); return; } if (Map.class.isAssignableFrom(propertyClass)) { - processPropertyNestedChildMap(propertyGetter, propertyName, propertyNodes); - return; - } + Set properties = propertyNodes.stream().map(attribute -> { + if (!(attribute instanceof FxmlProperty.Instance instanceProperty)) { + throw new ParseException("property attribute contains a non common attribute"); + } - throw new UnsupportedOperationException("Unknown read only property type %s".formatted(propertyClass)); - } + return instanceProperty; + }).collect(Collectors.toSet()); + Type genericReturnType = propertyGetter.getGenericReturnType(); + objectInitializationBuilder.addStatement("$T $L = $L.$L()", + resolver.resolveTypeNameWithoutVariables(genericReturnType), + propertyName, objectIdentifier, propertyGetter.getName()); - private void processPropertyNestedChildMap(Method propertyGetter, String propertyName, - Collection propertyNodes) { - Type genericReturnType = propertyGetter.getGenericReturnType(); - objectInitializationBuilder.addStatement("$T $L = $L.$L()", - resolver.resolveTypeNameWithoutVariables(genericReturnType), - propertyName, objectIdentifier, propertyGetter.getName()); + if (!(genericReturnType instanceof ParameterizedType parameterizedType)) { + throw new IllegalArgumentException("Cannot determine bounds of map key and value"); + } - if (!(genericReturnType instanceof ParameterizedType parameterizedType)) { - throw new IllegalArgumentException("Cannot determine bounds of map key and value"); - } + Class[] mapTypeBounds = resolver.resolveUpperBoundTypeArguments(parameterizedType); + if (mapTypeBounds.length != 2) { + throw new IllegalArgumentException("Cannot determine bounds of map key and value"); + } - Class[] mapTypeBounds = resolver.resolveUpperBoundTypeArguments(parameterizedType); - if (mapTypeBounds.length != 2) { - throw new IllegalArgumentException("Cannot determine bounds of map key and value"); + Class keyTypeBound = mapTypeBounds[0]; + Class valueTypeBound = mapTypeBounds[1]; + addNodesToMapWithTypeBounds(propertyName, properties, keyTypeBound, valueTypeBound); + return; } - Class keyTypeBound = mapTypeBounds[0]; - Class valueTypeBound = mapTypeBounds[1]; - addNodesToMapWithTypeBounds(propertyName, propertyNodes, keyTypeBound, valueTypeBound); + throw new UnsupportedOperationException("Unknown read only property type %s".formatted(propertyClass)); } - private void addNodesToCollectionWithTypeBound(String collectionIdentifier, Collection nodes, + private void addNodesToCollectionWithTypeBound(String collectionIdentifier, Collection nodes, Class contentTypeBound) { - for (FxmlNode propertyNode : nodes) { + for (ClassInstanceElement propertyNode : nodes) { ObjectNodeCode nodeCode = buildChildNode(propertyNode); if (!contentTypeBound.isAssignableFrom(nodeCode.nodeClass())) { throw new IllegalArgumentException( @@ -1024,7 +1041,7 @@ private void addNodesToCollectionWithTypeBound(String collectionIdentifier, Coll } private void processPropertyNestedChildCollection(Method propertyGetter, String propertyName, - Collection propertyNodes) { + Collection propertyNodes) { Type genericReturnType = propertyGetter.getGenericReturnType(); objectInitializationBuilder.addStatement("$T $L = $L.$L()", resolver.resolveTypeNameWithoutVariables(genericReturnType), @@ -1043,7 +1060,7 @@ private void processPropertyNestedChildCollection(Method propertyGetter, String addNodesToCollectionWithTypeBound(propertyName, propertyNodes, contentTypeBound); } - private void processPropertyChildSetter(FxmlNode propertyNode, Method propertySetter) { + private void processPropertyChildSetter(ClassInstanceElement propertyNode, Method propertySetter) { ObjectNodeCode nodeCode = buildChildNode(propertyNode); Class parameterType = propertySetter.getParameterTypes()[0]; @@ -1051,34 +1068,39 @@ private void processPropertyChildSetter(FxmlNode propertyNode, Method propertySe throw new IllegalArgumentException( "Parameter type `%s` does not match node type `%s`".formatted(parameterType, nodeCode.nodeClass())); } + objectInitializationBuilder.addStatement("$L.$L($L)", objectIdentifier, propertySetter.getName(), nodeCode.nodeIdentifier()); } - private void processStaticPropertyNestedChildren(String property, List propertyNodes) { - String[] propertyParts = property.split("\\."); - String staticClass = propertyParts[0]; - String staticProperty = propertyParts[1]; - Class staticPropertyClass = resolver.resolveRequired(staticClass); - Method propertySetter = resolver.resolveStaticSetter(staticPropertyClass, staticProperty).orElse(null); + private void processStaticPropertyElements(String className, String property, + List propertyNodes) { + Class staticPropertyClass = resolver.resolveRequired(className); + Method propertySetter = resolver.resolveStaticSetter(staticPropertyClass, property).orElse(null); if (propertySetter != null && propertyNodes.size() == 1) { - processStaticPropertyChildSetter(propertyNodes.getFirst(), propertySetter, staticPropertyClass); + + FxmlElement element = propertyNodes.getFirst(); + if (!(element instanceof ClassInstanceElement classInstanceElement)) { + throw new UnsupportedOperationException("Cannot set property with non instance node"); + } + + processStaticPropertySetter(classInstanceElement, propertySetter, staticPropertyClass); return; } throw new UnsupportedOperationException("Read-only properties not supported for static properties"); } - private void processStaticPropertyChildSetter(FxmlNode propertyNode, Method propertySetter, - Class staticPropertyClass) { + private void processStaticPropertySetter(ClassInstanceElement staticProperty, Method propertySetter, + Class staticPropertyClass) { Class parameterType = propertySetter.getParameterTypes()[0]; if (!parameterType.isAssignableFrom(objectClass)) { throw new IllegalArgumentException("First parameter of static property setter does not match node type"); } - ObjectNodeCode nodeCode = buildChildNode(propertyNode); + ObjectNodeCode nodeCode = buildChildNode(staticProperty); objectInitializationBuilder.addStatement("$T.$L($L, $L)", staticPropertyClass, propertySetter.getName(), objectIdentifier, nodeCode.nodeIdentifier()); } diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/FxmlComponents.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/FxmlComponents.java deleted file mode 100644 index 61b77c3..0000000 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/FxmlComponents.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.sheikah45.fx2j.processor.internal.model; - -import java.util.Set; - -public record FxmlComponents(FxmlNode rootNode, Set imports, String controllerType) { - public FxmlComponents { - imports = Set.copyOf(imports); - } -} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/FxmlNode.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/FxmlNode.java deleted file mode 100644 index 6623bb0..0000000 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/FxmlNode.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.github.sheikah45.fx2j.processor.internal.model; - -import io.github.sheikah45.fx2j.processor.internal.utils.StringUtils; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - - -public record FxmlNode(String name, String innerText, Map attributes, List children) { - - @Override - public String toString() { - StringBuilder stringBuilder = new StringBuilder(); - - stringBuilder.append("<").append(name); - - if (!attributes.isEmpty()) { - String attributes = this.attributes.entrySet() - .stream() - .map(entry -> "%s=\"%s\"".formatted(entry.getKey(), entry.getValue())) - .collect(Collectors.joining(" ")); - stringBuilder.append(" ").append(attributes); - } - - if (StringUtils.isNullOrBlank(innerText) && children.isEmpty()) { - stringBuilder.append("/>"); - return stringBuilder.toString(); - } - - stringBuilder.append(">\n"); - - if (!StringUtils.isNullOrBlank(innerText)) { - stringBuilder.append("\t").append(innerText).append("\n"); - } - - if (!children.isEmpty()) { - String children = this.children.stream() - .map(FxmlNode::toString) - .map(childString -> childString.indent(4)) - .collect(Collectors.joining("\n")); - stringBuilder.append(children); - } - - stringBuilder.append(""); - - return stringBuilder.toString(); - } -} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/FXMLUtils.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/FXMLUtils.java deleted file mode 100644 index e86176c..0000000 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/FXMLUtils.java +++ /dev/null @@ -1,97 +0,0 @@ -package io.github.sheikah45.fx2j.processor.internal.utils; - -import io.github.sheikah45.fx2j.processor.internal.model.FxmlComponents; -import io.github.sheikah45.fx2j.processor.internal.model.FxmlNode; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.w3c.dom.Text; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -public class FXMLUtils { - - public static FxmlComponents readFxml(Path filePath) { - try { - Document document = readXmlPlain(filePath); - return new FxmlComponents(buildFXMLNode(document), extractImports(document), - extractControllerType(document)); - } catch (ParserConfigurationException | SAXException | IOException e) { - throw new RuntimeException(e); - } - } - - private static Document readXmlPlain(Path filePath) throws ParserConfigurationException, IOException, SAXException { - return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(filePath.toFile()); - } - - private static FxmlNode buildFXMLNode(Document document) { - return populateElement(document.getDocumentElement()); - } - - private static FxmlNode populateElement(Element element) { - NamedNodeMap attributesNodeMap = element.getAttributes(); - int attrLength = attributesNodeMap.getLength(); - - Map attributes = IntStream.range(0, attrLength) - .mapToObj(attributesNodeMap::item) - .collect(Collectors.toMap(Node::getNodeName, Node::getNodeValue)); - - if (!element.hasChildNodes()) { - return new FxmlNode(element.getTagName(), null, attributes, List.of()); - } - NodeList childNodes = element.getChildNodes(); - int childrenLength = childNodes.getLength(); - List children = new ArrayList<>(); - String innerText = null; - - for (int i = 0; i < childrenLength; i++) { - Node item = childNodes.item(i); - if (item instanceof Text text && !StringUtils.isNullOrBlank(text.getTextContent())) { - innerText = text.getTextContent().trim().replaceAll("\\s+", " "); - } else if (item instanceof Element childElement) { - children.add(populateElement(childElement)); - } - } - - return new FxmlNode(element.getTagName(), innerText, attributes, children); - } - - private static Set extractImports(Document document) { - Set imports = new HashSet<>(); - Node startNode = document.getFirstChild(); - while (startNode != null) { - if ("import".equals(startNode.getNodeName())) { - imports.add(startNode.getNodeValue()); - } - startNode = startNode.getNextSibling(); - } - - return Set.copyOf(imports); - } - - private static String extractControllerType(Document document) { - Node startNode = document.getFirstChild(); - while (startNode != null) { - if ("controllerType".equals(startNode.getNodeName())) { - return startNode.getNodeValue(); - } - startNode = startNode.getNextSibling(); - } - - return null; - } -} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/StringUtils.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/StringUtils.java index 25337a8..1d1ce37 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/StringUtils.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/StringUtils.java @@ -55,8 +55,4 @@ public static String capitalize(String text) { public static String fxmlFileToPackageName(Path filePath) { return filePath.getParent().toString().replace(File.separatorChar, '.').toLowerCase(Locale.ROOT); } - - public static boolean isNullOrBlank(String string) { - return string == null || string.isBlank(); - } } diff --git a/fx2j-processor/src/main/java/module-info.java b/fx2j-processor/src/main/java/module-info.java index 80f9280..625a65b 100644 --- a/fx2j-processor/src/main/java/module-info.java +++ b/fx2j-processor/src/main/java/module-info.java @@ -3,6 +3,7 @@ requires com.squareup.javapoet; requires java.compiler; requires io.github.sheikah45.fx2j.api; + requires io.github.sheikah45.fx2j.parser; exports io.github.sheikah45.fx2j.processor; } \ No newline at end of file diff --git a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/utils/FXMLUtilsTest.java b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/utils/FXMLUtilsTest.java deleted file mode 100644 index 2803a1c..0000000 --- a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/utils/FXMLUtilsTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.github.sheikah45.fx2j.processor.utils; - -import io.github.sheikah45.fx2j.processor.internal.model.FxmlComponents; -import io.github.sheikah45.fx2j.processor.internal.model.FxmlNode; -import io.github.sheikah45.fx2j.processor.internal.utils.FXMLUtils; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; - -import java.nio.file.Path; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Execution(ExecutionMode.CONCURRENT) -public class FXMLUtilsTest { - - @Test - public void testReadFxml() { - Path filePath = Path.of("src/test/resources/fxml/read/test.fxml"); - FxmlComponents fxmlComponents = FXMLUtils.readFxml(filePath); - - FxmlNode rootNode = fxmlComponents.rootNode(); - assertEquals(rootNode.name(), "AnchorPane"); - - assertEquals(rootNode.children().size(), 5); - - FxmlNode button1 = rootNode.children().get(0); - FxmlNode button2 = rootNode.children().get(1); - FxmlNode button3 = rootNode.children().get(2); - FxmlNode button4 = rootNode.children().get(3); - FxmlNode button5 = rootNode.children().get(4); - - assertEquals("Button", button1.name()); - assertTrue(button1.attributes().isEmpty()); - assertTrue(button1.children().isEmpty()); - assertNull(button1.innerText()); - - assertEquals("Button", button2.name()); - assertEquals("secondButton", button2.attributes().get("fx:id")); - assertTrue(button2.children().isEmpty()); - assertNull(button2.innerText()); - - assertEquals("Button", button3.name()); - assertTrue(button3.attributes().isEmpty()); - assertTrue(button3.children().isEmpty()); - assertEquals("Third Button", button3.innerText()); - - assertEquals("Button", button4.name()); - assertTrue(button4.attributes().isEmpty()); - assertEquals(1, button4.children().size()); - assertEquals("Test After", button4.innerText()); - - FxmlNode paddingNode = button4.children().get(0); - assertEquals("padding", paddingNode.name()); - assertEquals(paddingNode.children().size(), 1); - FxmlNode insetsNode = paddingNode.children().get(0); - assertEquals("1", insetsNode.attributes().get("top")); - assertEquals("2", insetsNode.attributes().get("bottom")); - assertEquals("3", insetsNode.attributes().get("right")); - assertEquals("4", insetsNode.attributes().get("left")); - - assertEquals("Button", button5.name()); - assertTrue(button5.attributes().isEmpty()); - assertEquals(1, button5.children().size()); - assertEquals("Empty After", button5.innerText()); - - Set imports = fxmlComponents.imports(); - assertEquals(imports.size(), 3); - assertTrue(imports.contains("javafx.scene.control.Button")); - assertTrue(imports.contains("javafx.scene.layout.AnchorPane")); - assertTrue(imports.contains("javafx.geometry.Insets")); - } -} \ No newline at end of file diff --git a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/utils/StringUtilsTest.java b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/utils/StringUtilsTest.java index f9f1418..8586932 100644 --- a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/utils/StringUtilsTest.java +++ b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/utils/StringUtilsTest.java @@ -80,24 +80,4 @@ public void testFxmlFileToPackageName() { Path filePath = Paths.get("myapp/test/fxml/test.fxml"); assertEquals("myapp.test.fxml", StringUtils.fxmlFileToPackageName(filePath)); } - - @Test - public void testIsNullOrBlankNull() { - assertTrue(StringUtils.isNullOrBlank(null)); - } - - @Test - public void testIsNullOrBlankEmpty() { - assertTrue(StringUtils.isNullOrBlank("")); - } - - @Test - public void testIsNullOrBlankSpaces() { - assertTrue(StringUtils.isNullOrBlank(" ")); - } - - @Test - public void testIsNullOrBlankNotEmpty() { - assertFalse(StringUtils.isNullOrBlank("not blank")); - } } diff --git a/fx2j-processor/src/test/resources/fxml/controller/controller-type.fxml b/fx2j-processor/src/test/resources/fxml/controller/controller-type.fxml index db35cec..eb97f79 100644 --- a/fx2j-processor/src/test/resources/fxml/controller/controller-type.fxml +++ b/fx2j-processor/src/test/resources/fxml/controller/controller-type.fxml @@ -1,4 +1,4 @@ - + diff --git a/fx2j-processor/src/test/resources/fxml/read/test.fxml b/fx2j-processor/src/test/resources/fxml/read/test.fxml index f9f2166..01250c7 100644 --- a/fx2j-processor/src/test/resources/fxml/read/test.fxml +++ b/fx2j-processor/src/test/resources/fxml/read/test.fxml @@ -1,4 +1,4 @@ - +