From ce37c48768c7a5566ad9275162d46608c97860a3 Mon Sep 17 00:00:00 2001 From: Sheikah45 Date: Mon, 10 Jun 2024 20:53:39 -0400 Subject: [PATCH] Add more granular tests for ObjectNodeProcessor --- .../sheikah45/fx2j/parser/FxmlParser.java | 77 +- .../antlr/BindExpressionVisitorImpl.java | 106 +- .../fx2j/parser/element/IncludeElement.java | 1 + .../{Expression.java => BindExpression.java} | 63 +- .../sheikah45/fx2j/parser/property/Value.java | 2 +- .../fx2j/parser/FxmlParserPropertyTest.java | 24 +- .../fx2j/parser/property/ExpressionTest.java | 105 +- fx2j-processor/build.gradle.kts | 2 +- .../fx2j/processor/FxmlProcessor.java | 6 +- .../internal/ObjectNodeProcessor.java | 1030 +++++++-------- .../fx2j/processor/internal/code/Block.java | 48 + .../processor/internal/code/CodeTypes.java | 76 -- .../processor/internal/code/CodeValue.java | 218 ---- .../processor/internal/code/CodeValues.java | 254 ++-- .../processor/internal/code/Declarator.java | 3 + .../processor/internal/code/Expression.java | 77 ++ .../fx2j/processor/internal/code/Literal.java | 20 + .../processor/internal/code/Parameter.java | 3 + .../processor/internal/code/Resource.java | 6 + .../processor/internal/code/Statement.java | 39 + .../internal/code/StatementExpression.java | 49 + .../code/{CodeType.java => TypeValue.java} | 18 +- .../processor/internal/code/TypeValues.java | 76 ++ .../internal/code/builder/BlockBuilder.java | 11 +- .../internal/code/builder/LambdaBuilder.java | 24 +- .../internal/code/builder/TryBuilder.java | 40 +- .../internal/model/ExpressionResult.java | 5 +- .../internal/model/ObjectNodeCode.java | 7 +- .../internal/resolve/ExpressionResolver.java | 90 +- .../internal/resolve/MethodResolver.java | 2 +- .../internal/resolve/ValueResolver.java | 21 +- ...lockUtils.java => CodeBlockConverter.java} | 233 ++-- .../internal/ObjectNodeProcessorTest.java | 1106 +++++++++++++++++ .../resolve/ExpressionResolverTest.java | 177 +-- .../internal/resolve/ValueResolverTest.java | 12 +- .../FxmlProcessorControllerTest.java | 4 +- .../ChangeHandlerController.java | 8 +- .../EventHandlerMethodController.java | 4 +- .../controller/change-handler-controller.fxml | 2 +- .../fxml/controller/event-handler-method.fxml | 2 +- 40 files changed, 2646 insertions(+), 1405 deletions(-) rename fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/{Expression.java => BindExpression.java} (61%) create mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Block.java delete mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeTypes.java delete mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeValue.java create mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Declarator.java create mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Expression.java create mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Literal.java create mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Parameter.java create mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Resource.java create mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Statement.java create mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/StatementExpression.java rename fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/{CodeType.java => TypeValue.java} (69%) create mode 100644 fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/TypeValues.java rename fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/{CodeBlockUtils.java => CodeBlockConverter.java} (56%) create mode 100644 fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/internal/ObjectNodeProcessorTest.java 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 15040e4..c6b8c4a 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 @@ -27,7 +27,7 @@ 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.Expression; +import io.github.sheikah45.fx2j.parser.property.BindExpression; import io.github.sheikah45.fx2j.parser.property.Handler; import io.github.sheikah45.fx2j.parser.property.Value; import io.github.sheikah45.fx2j.parser.utils.StringUtils; @@ -55,6 +55,9 @@ public class FxmlParser { + private static final Value.Empty EMPTY_VALUE = new Value.Empty(); + private static final ElementContent EMPTY_CONTENT = new ElementContent<>(List.of(), List.of(), EMPTY_VALUE); + public static FxmlComponents readFxml(Path filePath) { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance(); try { @@ -74,12 +77,17 @@ public static FxmlComponents readFxml(Path filePath) { private static ElementContent createContent(Element element) { List fxmlAttributes = createFxmlAttributes(element); List fxmlElements = createFxmlElements(element); - return new ElementContent<>(fxmlAttributes, fxmlElements, retrieveInnerValue(element)); + Value innerValue = retrieveInnerValue(element); + if (fxmlAttributes.isEmpty() && fxmlElements.isEmpty() && innerValue instanceof Value.Empty) { + return EMPTY_CONTENT; + } + + return new ElementContent<>(fxmlAttributes, fxmlElements, innerValue); } private static Value retrieveInnerValue(Element element) { String innerText = retrieveInnerText(element); - return innerText.isBlank() ? new Value.Empty() : createPropertyValue(innerText); + return innerText.isBlank() ? EMPTY_VALUE : createPropertyValue(innerText); } private static String retrieveInnerText(Element element) { @@ -133,40 +141,18 @@ private static FxmlElement createFxmlElement(Element element) { case String tag -> { int separatorIndex = tag.lastIndexOf("."); if (Character.isLowerCase(tag.charAt(separatorIndex + 1))) { - ElementContent content = createContent(element); - List assignableElements = content.elements().stream().map(fxmlElement -> { - if (!(fxmlElement instanceof AssignableElement assignable)) { - throw new ParseException( - "A property element cannot contain unassignable values"); - } - - return assignable; - }).toList(); - List assignableAttributes = content.attributes() - .stream() - .map(fxmlAttribute -> { - if (!(fxmlAttribute instanceof AssignableAttribute assignable)) { - throw new ParseException( - "A property attribute cannot contain unassignable values"); - } - - return assignable; - }) - .toList(); + ElementContent newContent = createAssignableContent( + element); if (separatorIndex == -1) { - yield new InstancePropertyElement(tag, - new ElementContent<>(assignableAttributes, assignableElements, - content.value())); + yield new InstancePropertyElement(tag, newContent); } else { - if (!content.attributes().isEmpty()) { + if (!newContent.attributes().isEmpty()) { throw new ParseException("static property elements cannot have attributes"); } yield new StaticPropertyElement(tag.substring(0, separatorIndex), - tag.substring(separatorIndex + 1), - new ElementContent<>(assignableAttributes, assignableElements, - content.value())); + tag.substring(separatorIndex + 1), newContent); } } else { yield createInstanceElement(element); @@ -175,6 +161,32 @@ yield new StaticPropertyElement(tag.substring(0, separatorIndex), }; } + @SuppressWarnings("unchecked") + private static ElementContent createAssignableContent(Element element) { + ElementContent content = createContent(element); + if (EMPTY_CONTENT.equals(content)) { + return (ElementContent) content; + } + + List assignableElements = content.elements().stream().map(fxmlElement -> { + if (!(fxmlElement instanceof AssignableElement assignable)) { + throw new ParseException("A property element cannot contain unassignable values"); + } + + return assignable; + }).toList(); + + List assignableAttributes = content.attributes().stream().map(fxmlAttribute -> { + if (!(fxmlAttribute instanceof AssignableAttribute assignable)) { + throw new ParseException("A property attribute cannot contain unassignable values"); + } + + return assignable; + }).toList(); + + return new ElementContent<>(assignableAttributes, assignableElements, content.value()); + } + private static ClassInstanceElement createInstanceElement(Element element) { String className = element.getTagName(); String factory = removeAndGetValueIfPresent(element, "fx:factory").orElse(null); @@ -243,7 +255,8 @@ private static ScriptElement createScriptElement(Element element) { scriptSource = new ScriptSource.Inline(retrieveInnerText(element), charset); } else { if (!content.attributes().isEmpty() || - !content.elements().isEmpty() || !(content.value() instanceof Value.Empty)) { + !content.elements().isEmpty() || + !(content.value() instanceof Value.Empty)) { throw new ParseException( "fx:script with reference source cannot have any elements, attributes other than charset and source, or an inner value"); } @@ -306,7 +319,7 @@ private static Value createPropertyValue(String value) { 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.startsWith("${") && val.endsWith("}") -> - Expression.parse(val.substring(2, val.length() - 1)); + BindExpression.parse(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 -> new Value.Literal(val); diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/antlr/BindExpressionVisitorImpl.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/antlr/BindExpressionVisitorImpl.java index 466f038..e046463 100644 --- a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/antlr/BindExpressionVisitorImpl.java +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/antlr/BindExpressionVisitorImpl.java @@ -1,122 +1,122 @@ package io.github.sheikah45.fx2j.parser.antlr; -import io.github.sheikah45.fx2j.parser.property.Expression; +import io.github.sheikah45.fx2j.parser.property.BindExpression; import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; -public class BindExpressionVisitorImpl extends AbstractParseTreeVisitor - implements BindExpressionVisitor { +public class BindExpressionVisitorImpl extends AbstractParseTreeVisitor + implements BindExpressionVisitor { @Override - public Expression visitFractionLiteral(BindExpressionParser.FractionLiteralContext ctx) { - return new Expression.Fraction(Double.parseDouble(ctx.getText())); + public BindExpression visitFractionLiteral(BindExpressionParser.FractionLiteralContext ctx) { + return new BindExpression.Fraction(Double.parseDouble(ctx.getText())); } @Override - public Expression visitCollectionAccess(BindExpressionParser.CollectionAccessContext ctx) { - return new Expression.CollectionAccess(visit(ctx.collection), visit(ctx.accessor)); + public BindExpression visitCollectionAccess(BindExpressionParser.CollectionAccessContext ctx) { + return new BindExpression.CollectionAccess(visit(ctx.collection), visit(ctx.accessor)); } @Override - public Expression visitNullLiteral(BindExpressionParser.NullLiteralContext ctx) { - return new Expression.Null(); + public BindExpression visitNullLiteral(BindExpressionParser.NullLiteralContext ctx) { + return new BindExpression.Null(); } @Override - public Expression visitInvert(BindExpressionParser.InvertContext ctx) { - return new Expression.Invert(visit(ctx.base)); + public BindExpression visitInvert(BindExpressionParser.InvertContext ctx) { + return new BindExpression.Invert(visit(ctx.base)); } @Override - public Expression visitComparative(BindExpressionParser.ComparativeContext ctx) { - Expression left = visit(ctx.left); - Expression right = visit(ctx.right); + public BindExpression visitComparative(BindExpressionParser.ComparativeContext ctx) { + BindExpression left = visit(ctx.left); + BindExpression right = visit(ctx.right); return switch (ctx.operator.getText()) { - case "==" -> new Expression.Equal(left, right); - case "!=" -> new Expression.NotEqual(left, right); - case ">=" -> new Expression.GreaterThanEqual(left, right); - case ">" -> new Expression.GreaterThan(left, right); - case "<=" -> new Expression.LessThanEqual(left, right); - case "<" -> new Expression.LessThan(left, right); + case "==" -> new BindExpression.Equal(left, right); + case "!=" -> new BindExpression.NotEqual(left, right); + case ">=" -> new BindExpression.GreaterThanEqual(left, right); + case ">" -> new BindExpression.GreaterThan(left, right); + case "<=" -> new BindExpression.LessThanEqual(left, right); + case "<" -> new BindExpression.LessThan(left, right); case String operator -> throw new IllegalArgumentException("Unknown operator: %s".formatted(operator)); }; } @Override - public Expression visitMultiplicative(BindExpressionParser.MultiplicativeContext ctx) { - Expression left = visit(ctx.left); - Expression right = visit(ctx.right); + public BindExpression visitMultiplicative(BindExpressionParser.MultiplicativeContext ctx) { + BindExpression left = visit(ctx.left); + BindExpression right = visit(ctx.right); return switch (ctx.operator.getText()) { - case "*" -> new Expression.Multiply(left, right); - case "/" -> new Expression.Divide(left, right); - case "%" -> new Expression.Modulo(left, right); + case "*" -> new BindExpression.Multiply(left, right); + case "/" -> new BindExpression.Divide(left, right); + case "%" -> new BindExpression.Modulo(left, right); case String operator -> throw new IllegalArgumentException("Unknown operator: %s".formatted(operator)); }; } @Override - public Expression visitLogical(BindExpressionParser.LogicalContext ctx) { - Expression left = visit(ctx.left); - Expression right = visit(ctx.right); + public BindExpression visitLogical(BindExpressionParser.LogicalContext ctx) { + BindExpression left = visit(ctx.left); + BindExpression right = visit(ctx.right); return switch (ctx.operator.getText()) { - case "&&" -> new Expression.And(left, right); - case "||" -> new Expression.Or(left, right); + case "&&" -> new BindExpression.And(left, right); + case "||" -> new BindExpression.Or(left, right); case String operator -> throw new IllegalArgumentException("Unknown operator: %s".formatted(operator)); }; } @Override - public Expression visitEnclosed(BindExpressionParser.EnclosedContext ctx) { + public BindExpression visitEnclosed(BindExpressionParser.EnclosedContext ctx) { return visit(ctx.inside); } @Override - public Expression visitAdditive(BindExpressionParser.AdditiveContext ctx) { - Expression left = visit(ctx.left); - Expression right = visit(ctx.right); + public BindExpression visitAdditive(BindExpressionParser.AdditiveContext ctx) { + BindExpression left = visit(ctx.left); + BindExpression right = visit(ctx.right); return switch (ctx.operator.getText()) { - case "+" -> new Expression.Add(left, right); - case "-" -> new Expression.Subtract(left, right); + case "+" -> new BindExpression.Add(left, right); + case "-" -> new BindExpression.Subtract(left, right); case String operator -> throw new IllegalArgumentException("Unknown operator: %s".formatted(operator)); }; } @Override - public Expression visitStringLiteral(BindExpressionParser.StringLiteralContext ctx) { + public BindExpression visitStringLiteral(BindExpressionParser.StringLiteralContext ctx) { String text = ctx.getText(); - return new Expression.String(text.substring(1, text.length() - 1)); + return new BindExpression.String(text.substring(1, text.length() - 1)); } @Override - public Expression visitNegate(BindExpressionParser.NegateContext ctx) { - return new Expression.Negate(visit(ctx.base)); + public BindExpression visitNegate(BindExpressionParser.NegateContext ctx) { + return new BindExpression.Negate(visit(ctx.base)); } @Override - public Expression visitVariable(BindExpressionParser.VariableContext ctx) { - return new Expression.Variable(ctx.getText()); + public BindExpression visitVariable(BindExpressionParser.VariableContext ctx) { + return new BindExpression.Variable(ctx.getText()); } @Override - public Expression visitWholeLiteral(BindExpressionParser.WholeLiteralContext ctx) { - return new Expression.Whole(Long.parseLong(ctx.getText())); + public BindExpression visitWholeLiteral(BindExpressionParser.WholeLiteralContext ctx) { + return new BindExpression.Whole(Long.parseLong(ctx.getText())); } @Override - public Expression visitPropertyRead(BindExpressionParser.PropertyReadContext ctx) { - return new Expression.PropertyRead(visit(ctx.base), ctx.property.getText()); + public BindExpression visitPropertyRead(BindExpressionParser.PropertyReadContext ctx) { + return new BindExpression.PropertyRead(visit(ctx.base), ctx.property.getText()); } @Override - public Expression visitBooleanLiteral(BindExpressionParser.BooleanLiteralContext ctx) { + public BindExpression visitBooleanLiteral(BindExpressionParser.BooleanLiteralContext ctx) { return switch (ctx.getText()) { - case "true" -> new Expression.Boolean(true); - case "false" -> new Expression.Boolean(false); + case "true" -> new BindExpression.Boolean(true); + case "false" -> new BindExpression.Boolean(false); case String val -> throw new IllegalArgumentException("Unknown boolean: %s".formatted(val)); }; } @Override - public Expression visitMethodCall(BindExpressionParser.MethodCallContext ctx) { - return new Expression.MethodCall(visit(ctx.base), ctx.method.getText(), - ctx.args.stream().map(this::visit).toList()); + public BindExpression visitMethodCall(BindExpressionParser.MethodCallContext ctx) { + return new BindExpression.MethodCall(visit(ctx.base), ctx.method.getText(), + ctx.args.stream().map(this::visit).toList()); } } 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 index e1378ea..b75ad15 100644 --- 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 @@ -9,5 +9,6 @@ public record IncludeElement(Path source, Path resources, Charset charset, Eleme public IncludeElement { Objects.requireNonNull(source, "source cannot be null"); Objects.requireNonNull(content, "content cannot be null"); + Objects.requireNonNull(charset, "charset cannot be null"); } } diff --git a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/Expression.java b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/BindExpression.java similarity index 61% rename from fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/Expression.java rename to fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/BindExpression.java index 920be8b..13ee093 100644 --- a/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/Expression.java +++ b/fx2j-parser/src/main/java/io/github/sheikah45/fx2j/parser/property/BindExpression.java @@ -11,8 +11,8 @@ import java.util.List; import java.util.Objects; -sealed public interface Expression extends Value { - static Expression parse(java.lang.String expression) { +sealed public interface BindExpression extends Value { + static BindExpression parse(java.lang.String expression) { CodePointCharStream charStream = CharStreams.fromString(expression); BindExpressionLexer expressionLexer = new BindExpressionLexer(charStream); CommonTokenStream commonTokenStream = new CommonTokenStream(expressionLexer); @@ -20,131 +20,132 @@ static Expression parse(java.lang.String expression) { return expressionParser.expression().accept(new BindExpressionVisitorImpl()); } - record PropertyRead(Expression expression, java.lang.String property) implements Expression { + record PropertyRead(BindExpression bindExpression, java.lang.String property) implements BindExpression { public PropertyRead { - Objects.requireNonNull(expression, "expression cannot be null"); + Objects.requireNonNull(bindExpression, "expression cannot be null"); if (StringUtils.isNullOrBlank(property)) { throw new IllegalArgumentException("propertyName cannot be blank or null"); } } } - record MethodCall(Expression expression, java.lang.String methodName, List args) implements Expression { + record MethodCall(BindExpression bindExpression, java.lang.String methodName, List args) implements + BindExpression { public MethodCall { - Objects.requireNonNull(expression, "expression cannot be null"); + Objects.requireNonNull(bindExpression, "expression cannot be null"); Objects.requireNonNull(args, "args cannot be null"); if (StringUtils.isNullOrBlank(methodName)) { throw new IllegalArgumentException("methodName cannot be blank or null"); } } } - record CollectionAccess(Expression expression, Expression key) implements Expression { + record CollectionAccess(BindExpression bindExpression, BindExpression key) implements BindExpression { public CollectionAccess { - Objects.requireNonNull(expression, "expression cannot be null"); + Objects.requireNonNull(bindExpression, "expression cannot be null"); Objects.requireNonNull(key, "key cannot be null"); } } - record Negate(Expression expression) implements Expression { + record Negate(BindExpression bindExpression) implements BindExpression { public Negate { - Objects.requireNonNull(expression, "expression cannot be null"); + Objects.requireNonNull(bindExpression, "expression cannot be null"); } } - record Invert(Expression expression) implements Expression { + record Invert(BindExpression bindExpression) implements BindExpression { public Invert { - Objects.requireNonNull(expression, "expression cannot be null"); + Objects.requireNonNull(bindExpression, "expression cannot be null"); } } - record Multiply(Expression left, Expression right) implements Expression { + record Multiply(BindExpression left, BindExpression right) implements BindExpression { public Multiply { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record Divide(Expression left, Expression right) implements Expression { + record Divide(BindExpression left, BindExpression right) implements BindExpression { public Divide { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record Modulo(Expression left, Expression right) implements Expression { + record Modulo(BindExpression left, BindExpression right) implements BindExpression { public Modulo { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record Add(Expression left, Expression right) implements Expression { + record Add(BindExpression left, BindExpression right) implements BindExpression { public Add { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record Subtract(Expression left, Expression right) implements Expression { + record Subtract(BindExpression left, BindExpression right) implements BindExpression { public Subtract { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record GreaterThan(Expression left, Expression right) implements Expression { + record GreaterThan(BindExpression left, BindExpression right) implements BindExpression { public GreaterThan { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record GreaterThanEqual(Expression left, Expression right) implements Expression { + record GreaterThanEqual(BindExpression left, BindExpression right) implements BindExpression { public GreaterThanEqual { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record LessThan(Expression left, Expression right) implements Expression { + record LessThan(BindExpression left, BindExpression right) implements BindExpression { public LessThan { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record LessThanEqual(Expression left, Expression right) implements Expression { + record LessThanEqual(BindExpression left, BindExpression right) implements BindExpression { public LessThanEqual { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record Equal(Expression left, Expression right) implements Expression { + record Equal(BindExpression left, BindExpression right) implements BindExpression { public Equal { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record NotEqual(Expression left, Expression right) implements Expression { + record NotEqual(BindExpression left, BindExpression right) implements BindExpression { public NotEqual { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record And(Expression left, Expression right) implements Expression { + record And(BindExpression left, BindExpression right) implements BindExpression { public And { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record Or(Expression left, Expression right) implements Expression { + record Or(BindExpression left, BindExpression right) implements BindExpression { public Or { Objects.requireNonNull(left, "left cannot be null"); Objects.requireNonNull(right, "right cannot be null"); } } - record Variable(java.lang.String value) implements Expression { + record Variable(java.lang.String value) implements BindExpression { public Variable { if (StringUtils.isNullOrBlank(value)) { throw new IllegalArgumentException("value cannot be null or blank"); } } } - record String(java.lang.String value) implements Expression { + record String(java.lang.String value) implements BindExpression { public String { Objects.requireNonNull(value, "left cannot be null"); } } - record Whole(long value) implements Expression {} - record Fraction(double value) implements Expression {} - record Boolean(boolean value) implements Expression {} - record Null() implements Expression {} + record Whole(long value) implements BindExpression {} + record Fraction(double value) implements BindExpression {} + record Boolean(boolean value) implements BindExpression {} + record Null() implements BindExpression {} } 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 index 7750423..5fa6d10 100644 --- 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 @@ -6,7 +6,7 @@ import java.util.Objects; sealed public interface Value - permits Expression, Value.Empty, Value.Literal, Value.Location, Value.Reference, Value.Resource { + permits BindExpression, Value.Empty, Value.Literal, Value.Location, Value.Reference, Value.Resource { record Empty() implements Value {} record Literal(String value) implements Value {} record Location(Path value) implements Value { 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 2f8cc8b..4c3c454 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 @@ -9,11 +9,7 @@ import io.github.sheikah45.fx2j.parser.attribute.NameSpaceAttribute; import io.github.sheikah45.fx2j.parser.attribute.StaticPropertyAttribute; import io.github.sheikah45.fx2j.parser.element.DeclarationElement; -import io.github.sheikah45.fx2j.parser.element.ElementContent; -import io.github.sheikah45.fx2j.parser.element.FxmlElement; -import io.github.sheikah45.fx2j.parser.element.InstanceElement; -import io.github.sheikah45.fx2j.parser.element.StaticPropertyElement; -import io.github.sheikah45.fx2j.parser.property.Expression; +import io.github.sheikah45.fx2j.parser.property.BindExpression; import io.github.sheikah45.fx2j.parser.property.Handler; import io.github.sheikah45.fx2j.parser.property.Value; import org.junit.jupiter.api.Test; @@ -195,8 +191,8 @@ void testExpressionProperty() { DeclarationElement rootNode = fxmlComponents.rootNode(); List attributes = rootNode.content().attributes(); - assertEquals(List.of(new InstancePropertyAttribute("text", new Expression.PropertyRead( - new Expression.Variable("test"), "text")), NAME_SPACE_ATTRIBUTE), attributes); + assertEquals(List.of(new InstancePropertyAttribute("text", new BindExpression.PropertyRead( + new BindExpression.Variable("test"), "text")), NAME_SPACE_ATTRIBUTE), attributes); } @Test @@ -207,13 +203,13 @@ void testComplexExpressionProperty() { DeclarationElement rootNode = fxmlComponents.rootNode(); List attributes = rootNode.content().attributes(); - assertEquals(List.of(new InstancePropertyAttribute("width", new Expression.Add(new Expression.Add( - new Expression.PropertyRead( - new Expression.PropertyRead(new Expression.Variable("test"), "text"), "length"), - new Expression.Multiply( - new Expression.PropertyRead(new Expression.Variable("test"), "width"), - new Expression.PropertyRead(new Expression.Variable("root"), "margin"))), - new Expression.Whole( + assertEquals(List.of(new InstancePropertyAttribute("width", new BindExpression.Add(new BindExpression.Add( + new BindExpression.PropertyRead( + new BindExpression.PropertyRead(new BindExpression.Variable("test"), "text"), "length"), + new BindExpression.Multiply( + new BindExpression.PropertyRead(new BindExpression.Variable("test"), "width"), + new BindExpression.PropertyRead(new BindExpression.Variable("root"), "margin"))), + new BindExpression.Whole( 10))), NAME_SPACE_ATTRIBUTE), attributes); } diff --git a/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/property/ExpressionTest.java b/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/property/ExpressionTest.java index 782e46a..22eb1f4 100644 --- a/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/property/ExpressionTest.java +++ b/fx2j-parser/src/test/java/io/github/sheikah45/fx2j/parser/property/ExpressionTest.java @@ -13,120 +13,125 @@ class ExpressionTest { @Test void testNullLiteral() { - assertEquals(new Expression.Null(), Expression.parse("null")); + assertEquals(new BindExpression.Null(), BindExpression.parse("null")); } @Test void testBooleanLiteral() { - assertEquals(new Expression.Boolean(true), Expression.parse("true")); - assertEquals(new Expression.Boolean(false), Expression.parse("false")); + assertEquals(new BindExpression.Boolean(true), BindExpression.parse("true")); + assertEquals(new BindExpression.Boolean(false), BindExpression.parse("false")); } @Test void testStringLiteral() { - assertEquals(new Expression.String("\"true\""), Expression.parse("'\"true\"'")); - assertEquals(new Expression.String("'false'"), Expression.parse("\"'false'\"")); + assertEquals(new BindExpression.String("\"true\""), BindExpression.parse("'\"true\"'")); + assertEquals(new BindExpression.String("'false'"), BindExpression.parse("\"'false'\"")); } @Test void testWholeLiteral() { - assertEquals(new Expression.Whole(10), Expression.parse("10")); + assertEquals(new BindExpression.Whole(10), BindExpression.parse("10")); } @Test void testFractionalLiteral() { - assertEquals(new Expression.Fraction(10.0), Expression.parse("10.0")); - assertEquals(new Expression.Fraction(10.0), Expression.parse("1.0e1")); - assertEquals(new Expression.Fraction(10.0), Expression.parse("1e1")); - assertEquals(new Expression.Fraction(10.0), Expression.parse(".1e2")); + assertEquals(new BindExpression.Fraction(10.0), BindExpression.parse("10.0")); + assertEquals(new BindExpression.Fraction(10.0), BindExpression.parse("1.0e1")); + assertEquals(new BindExpression.Fraction(10.0), BindExpression.parse("1e1")); + assertEquals(new BindExpression.Fraction(10.0), BindExpression.parse(".1e2")); } @Test void testVariable() { - assertEquals(new Expression.Variable("test"), Expression.parse("test")); + assertEquals(new BindExpression.Variable("test"), BindExpression.parse("test")); } @Test void testEnclosed() { - assertEquals(new Expression.Whole(10), Expression.parse("(10)")); + assertEquals(new BindExpression.Whole(10), BindExpression.parse("(10)")); } @Test void testPropertyRead() { - assertEquals(new Expression.PropertyRead(new Expression.Variable("test"), "text"), - Expression.parse("test.text")); + assertEquals(new BindExpression.PropertyRead(new BindExpression.Variable("test"), "text"), + BindExpression.parse("test.text")); } @Test void testMethodCall() { - assertEquals(new Expression.MethodCall(new Expression.Variable("test"), "run", - List.of(new Expression.Variable("a"))), Expression.parse("test.run(a)")); + assertEquals(new BindExpression.MethodCall(new BindExpression.Variable("test"), "run", + List.of(new BindExpression.Variable("a"))), + BindExpression.parse("test.run(a)")); } @Test void testCollectionAccess() { - assertEquals(new Expression.CollectionAccess(new Expression.Variable("test"), new Expression.Whole(0)), - Expression.parse("test[0]")); + assertEquals( + new BindExpression.CollectionAccess(new BindExpression.Variable("test"), new BindExpression.Whole(0)), + BindExpression.parse("test[0]")); } @Test void testNegate() { - assertEquals(new Expression.Negate(new Expression.Whole(10)), Expression.parse("-10")); + assertEquals(new BindExpression.Negate(new BindExpression.Whole(10)), BindExpression.parse("-10")); } @Test void testMultiplicative() { - assertEquals(new Expression.Multiply(new Expression.Whole(10), new Expression.Whole(10)), - Expression.parse("10*10")); - assertEquals(new Expression.Divide(new Expression.Whole(10), new Expression.Whole(10)), - Expression.parse("10/10")); - assertEquals(new Expression.Modulo(new Expression.Whole(10), new Expression.Whole(10)), - Expression.parse("10%10")); + assertEquals(new BindExpression.Multiply(new BindExpression.Whole(10), new BindExpression.Whole(10)), + BindExpression.parse("10*10")); + assertEquals(new BindExpression.Divide(new BindExpression.Whole(10), new BindExpression.Whole(10)), + BindExpression.parse("10/10")); + assertEquals(new BindExpression.Modulo(new BindExpression.Whole(10), new BindExpression.Whole(10)), + BindExpression.parse("10%10")); } @Test void testAdditive() { - assertEquals(new Expression.Add(new Expression.Whole(10), new Expression.Whole(10)), Expression.parse("10+10")); - assertEquals(new Expression.Subtract(new Expression.Whole(10), new Expression.Whole(10)), - Expression.parse("10-10")); + assertEquals(new BindExpression.Add(new BindExpression.Whole(10), new BindExpression.Whole(10)), + BindExpression.parse("10+10")); + assertEquals(new BindExpression.Subtract(new BindExpression.Whole(10), new BindExpression.Whole(10)), + BindExpression.parse("10-10")); } @Test void testComparative() { - assertEquals(new Expression.GreaterThan(new Expression.Whole(10), new Expression.Whole(10)), - Expression.parse("10>10")); - assertEquals(new Expression.GreaterThanEqual(new Expression.Whole(10), new Expression.Whole(10)), - Expression.parse("10>=10")); - assertEquals(new Expression.LessThan(new Expression.Whole(10), new Expression.Whole(10)), - Expression.parse("10<10")); - assertEquals(new Expression.LessThanEqual(new Expression.Whole(10), new Expression.Whole(10)), - Expression.parse("10<=10")); - assertEquals(new Expression.Equal(new Expression.Whole(10), new Expression.Whole(10)), - Expression.parse("10==10")); - assertEquals(new Expression.NotEqual(new Expression.Whole(10), new Expression.Whole(10)), - Expression.parse("10!=10")); + assertEquals(new BindExpression.GreaterThan(new BindExpression.Whole(10), new BindExpression.Whole(10)), + BindExpression.parse("10>10")); + assertEquals(new BindExpression.GreaterThanEqual(new BindExpression.Whole(10), new BindExpression.Whole(10)), + BindExpression.parse("10>=10")); + assertEquals(new BindExpression.LessThan(new BindExpression.Whole(10), new BindExpression.Whole(10)), + BindExpression.parse("10<10")); + assertEquals(new BindExpression.LessThanEqual(new BindExpression.Whole(10), new BindExpression.Whole(10)), + BindExpression.parse("10<=10")); + assertEquals(new BindExpression.Equal(new BindExpression.Whole(10), new BindExpression.Whole(10)), + BindExpression.parse("10==10")); + assertEquals(new BindExpression.NotEqual(new BindExpression.Whole(10), new BindExpression.Whole(10)), + BindExpression.parse("10!=10")); } @Test void testInvert() { - assertEquals(new Expression.Invert(new Expression.Boolean(false)), Expression.parse("!false")); + assertEquals(new BindExpression.Invert(new BindExpression.Boolean(false)), BindExpression.parse("!false")); } @Test void testLogical() { - assertEquals(new Expression.And(new Expression.Boolean(true), new Expression.Boolean(true)), - Expression.parse("true&&true")); - assertEquals(new Expression.Or(new Expression.Boolean(true), new Expression.Boolean(true)), - Expression.parse("true||true")); + assertEquals(new BindExpression.And(new BindExpression.Boolean(true), new BindExpression.Boolean(true)), + BindExpression.parse("true&&true")); + assertEquals(new BindExpression.Or(new BindExpression.Boolean(true), new BindExpression.Boolean(true)), + BindExpression.parse("true||true")); } @Test void testOrderOfOperations() { - Expression.Whole whole10 = new Expression.Whole(10); - assertEquals(new Expression.And(new Expression.LessThan( - new Expression.Add(new Expression.Subtract(whole10, new Expression.Multiply(whole10, whole10)), - whole10), - new Expression.Variable("a")), new Expression.Boolean(true)), Expression.parse("10-10*10+10 initializers = new ArrayList<>(); + private final List initializers = new ArrayList<>(); private final List defaultPropertyElements = new ArrayList<>(); private final SequencedMap instanceProperties = new LinkedHashMap<>(); private final List staticProperties = new ArrayList<>(); @@ -145,6 +149,20 @@ public ObjectNodeProcessor(ClassInstanceElement rootNode, Class controllerCla nodeCode = processNode(); } + public ObjectNodeCode getNodeCode() { + return nodeCode; + } + + private ObjectNodeCode processNode() { + try { + return processNodeInternal(); + } catch (ProcessorException processorException) { + throw processorException; + } catch (Exception exception) { + throw new ProcessorException(exception, rootNode.toString()); + } + } + private ObjectNodeCode processNodeInternal() { if (!scripts.isEmpty()) { throw new UnsupportedOperationException("Scripts not supported"); @@ -161,18 +179,111 @@ private ObjectNodeCode processNodeInternal() { return new ObjectNodeCode(CodeValues.variable(objectIdentifier), objectType, initializers); } - private ObjectNodeCode processNode() { - try { - return processNodeInternal(); - } catch (ProcessorException processorException) { - throw processorException; - } catch (Exception exception) { - throw new ProcessorException(exception, rootNode.toString()); + private void processObjectInitialization() { + switch (rootNode) { + case RootElement(String type, ElementContent ignored) -> processRootInitialization(type); + case ReferenceElement(String source, ElementContent content) -> + processReferenceInitialization(source, content); + case CopyElement( + String source, ElementContent ignored + ) -> processCopyInitialization(source); + case IncludeElement( + Path source, Path ignored1, Charset ignored2, ElementContent ignored3 + ) -> processIncludeInitialization(source); + case FactoryElement( + String factoryClassName, String methodName, ElementContent ignored + ) -> processFactoryBasedInitialization(factoryClassName, methodName); + case ConstantElement(String className, String member, ElementContent ignored) -> + processConstantInitialization(className, member); + case ValueElement(String className, String value, ElementContent ignored) -> + processValueInitialization(className, value); + case InstanceElement(String className, ElementContent ignored) -> + processConstructorInitialization(className); + default -> throw new UnsupportedOperationException("Unable to initialize object"); + } + + if (providedId != null) { + processControllerSetter(objectIdentifier, objectType); + methodResolver.resolveSetter(objectType, "id", String.class) + .ifPresent(method -> initializers.add( + CodeValues.methodCall(objectIdentifier, method, objectIdentifier))); + typeArguments = extractTypeArguments(); } } - public ObjectNodeCode getNodeCode() { - return nodeCode; + private void processRootInitialization(String type) { + objectIdentifier = FxmlProcessor.BUILDER_PROVIDED_ROOT_NAME; + objectType = typeResolver.resolve(type); + } + + private void processReferenceInitialization(String source, ElementContent content) { + objectIdentifier = source; + objectType = nameResolver.resolveTypeById(source); + if (!content.attributes().isEmpty() || !content.elements().isEmpty()) { + throw new UnsupportedOperationException("References with elements or attributes not supported"); + } + } + + private void processCopyInitialization(String source) { + objectType = nameResolver.resolveTypeById(source); + + if (!methodResolver.hasCopyConstructor(objectType)) { + throw new IllegalArgumentException("No copy constructor found for class %s".formatted(objectType)); + } + + objectIdentifier = source + "Copy"; + initializers.add(CodeValues.declaration(objectType, objectIdentifier, + CodeValues.newInstance(objectType, CodeValues.variable(source)))); + } + + private void processFactoryBasedInitialization(String factoryClassName, String factoryMethodName) { + Class factoryClass = typeResolver.resolve(factoryClassName); + Method factoryMethod = methodResolver.findMethod(factoryClass, factoryMethodName, 0) + .orElseThrow(() -> new IllegalArgumentException( + "Factory method not found %s.%s".formatted(factoryClassName, + factoryMethodName))); + + objectType = factoryMethod.getReturnType(); + resolveIdentifier(); + + initializers.add(CodeValues.declaration(objectType, objectIdentifier, CodeValues.methodCall(factoryMethod))); + } + + private void processConstantInitialization(String className, String member) { + Class constantContainerClass = typeResolver.resolve(className); + Field constantField = methodResolver.resolveFieldRequiredPublicIfExists(constantContainerClass, member) + .orElseThrow(() -> new IllegalArgumentException( + "Field %s of %s is not found or public".formatted(member, + constantContainerClass))); + objectType = constantField.getType(); + resolveIdentifier(); + initializers.add(CodeValues.declaration(objectType, objectIdentifier, CodeValues.fieldAccess(constantField))); + } + + private void processValueInitialization(String className, String value) { + objectType = typeResolver.unwrapType(typeResolver.resolve(className)); + resolveIdentifier(); + if (objectType == String.class) { + initializers.add(CodeValues.declaration(objectType, objectIdentifier, + valueResolver.resolveCodeValue(objectType, value))); + return; + } + + + Class wrappedType = typeResolver.wrapType(objectType); + Method method = methodResolver.findMethodRequiredPublicIfExists(wrappedType, "valueOf", String.class) + .orElseThrow(() -> new IllegalArgumentException( + "Class %s does not have a valueOf method".formatted(objectType))); + + + try { + Expression codeValue = valueResolver.resolveCodeValue(method, value); + initializers.add(CodeValues.declaration(objectType, objectIdentifier, codeValue)); + } catch (InvocationTargetException | IllegalAccessException | RuntimeException e) { + throw new IllegalArgumentException( + "Value %s not parseable by %s.%s".formatted(value, objectType, method.getName())); + } + } private void processIncludeInitialization(Path source) { @@ -221,26 +332,159 @@ private void processConstructorInitialization(String className) { throw new IllegalArgumentException("Unknown constructor"); } - private void processDefaultPropertyElements() { - if (defaultPropertyElements.isEmpty()) { - return; + private void processControllerSetter(String identifier, Type valueClass) { + if (controllerClass != Object.class) { + processControllerSettersFromKnownClass(identifier, valueClass); } + } - String defaultProperty = methodResolver.resolveDefaultProperty(objectType); - if (defaultProperty != null) { - defaultPropertyElements.forEach(element -> processInstancePropertyElement(defaultProperty, element)); - return; + private Type[] extractTypeArguments() { + return methodResolver.resolveSetter(controllerClass, objectIdentifier, objectType) + .map(Method::getGenericParameterTypes) + .map(types -> types[0]) + .or(() -> methodResolver.resolveField(controllerClass, objectIdentifier) + .map(Field::getGenericType)) + .map(typeResolver::resolveUpperBoundTypeArguments) + .orElse(null); + } + + private void resolveIdentifier() { + if (providedId != null) { + objectIdentifier = providedId; + nameResolver.storeIdType(objectIdentifier, objectType); + } else { + objectIdentifier = nameResolver.resolveUniqueName(objectType); } + } - if (typeResolver.isAssignableFrom(Collection.class, objectType)) { - Type contentTypeBound = typeArguments == null ? Object.class : typeArguments[0]; - CodeValue.Variable variableValue = CodeValues.variable(objectIdentifier); - defaultPropertyElements.forEach( - element -> addToCollectionWithTypeBound(variableValue, element, contentTypeBound)); - return; + private void processControllerSettersFromKnownClass(String identifier, Type valueClass) { + methodResolver.resolveSetterRequiredPublicIfExists(controllerClass, identifier, valueClass) + .map(method -> CodeValues.methodCall(FxmlProcessor.CONTROLLER_NAME, method, + CodeValues.variable(identifier))) + .or(() -> methodResolver.resolveFieldRequiredPublicIfExists(controllerClass, identifier) + .filter(field -> typeResolver.isAssignableFrom(field.getType(), + valueClass)) + .map(field -> CodeValues.assignment( + CodeValues.fieldAccess(FxmlProcessor.CONTROLLER_NAME, field), + CodeValues.variable(field.getName())))) + .ifPresent(initializers::add); + } + + private Set> getMatchingConstructorArgs() { + Set definedProperties = instanceProperties.keySet(); + + Map> definedPropertiesByMutability = definedProperties.stream() + .collect(Collectors.partitioningBy( + this::propertyIsMutable, + Collectors.toSet())); + + + Set constructorProperties = definedPropertiesByMutability.getOrDefault(false, Set.of()); + + Set setterProperties = definedPropertiesByMutability.getOrDefault(true, Set.of()); + + int undefinedConstructorPropertiesBest = Integer.MAX_VALUE; + int setterPropertiesBest = Integer.MAX_VALUE; + + Set> bestConstructorArgs = new LinkedHashSet<>(); + for (List namedArgValues : getPossibleConstructorArgs()) { + Set argumentNames = namedArgValues.stream().map(NamedArgValue::name).collect(Collectors.toSet()); + + if (!argumentNames.containsAll(constructorProperties)) { + continue; + } + + Set undefinedConstructorProperties = new HashSet<>(argumentNames); + undefinedConstructorProperties.removeAll(definedProperties); + + Set remainingSetterProperties = new HashSet<>(setterProperties); + remainingSetterProperties.removeAll(argumentNames); + + int undefinedConstructorPropertiesCount = undefinedConstructorProperties.size(); + if (undefinedConstructorPropertiesBest == undefinedConstructorPropertiesCount && + setterPropertiesBest == remainingSetterProperties.size()) { + bestConstructorArgs.add(namedArgValues); + } else if (undefinedConstructorPropertiesBest > undefinedConstructorPropertiesCount || + (undefinedConstructorPropertiesBest == undefinedConstructorPropertiesCount && + setterPropertiesBest > remainingSetterProperties.size())) { + undefinedConstructorPropertiesBest = undefinedConstructorPropertiesCount; + setterPropertiesBest = remainingSetterProperties.size(); + bestConstructorArgs.clear(); + bestConstructorArgs.add(namedArgValues); + } } - throw new IllegalArgumentException("Unable to handle default elements for elements %s".formatted(objectType)); + return bestConstructorArgs; + } + + private List> getPossibleConstructorArgs() { + return methodResolver.getConstructors(objectType) + .stream() + .filter(methodResolver::hasAllNamedArgs) + .map(methodResolver::getNamedArgs) + .toList(); + } + + private void buildWithConstructorArgs(List namedArgValues) { + Object[] parameterValues = namedArgValues.stream() + .map(this::resolveParameterValue) + .toArray(Expression[]::new); + + namedArgValues.stream().map(NamedArgValue::name).forEach(instanceProperties::remove); + + initializers.add(CodeValues.declaration(objectType, objectIdentifier, + CodeValues.newInstance(objectType, parameterValues))); + } + + private Expression resolveParameterValue(NamedArgValue namedArgValue) { + Class paramType = namedArgValue.parameterType(); + String paramName = namedArgValue.name(); + FxmlProperty.Instance property = instanceProperties.get(paramName); + return switch (property) { + case InstancePropertyElement(String ignored, ElementContent content) -> { + if (!content.attributes().isEmpty()) { + throw new UnsupportedOperationException( + "Cannot resolve property value from content with attributes"); + } + + if ((!content.elements().isEmpty() && !(content.value() instanceof Value.Empty)) || + content.elements().size() > 1) { + throw new UnsupportedOperationException("Cannot handle multiple values for parameter element"); + } + + if (content.elements().size() == 1) { + if (!(content.elements().getFirst() instanceof ClassInstanceElement classInstanceElement)) { + throw new UnsupportedOperationException( + "Cannot handle static property element value that is not a class instance"); + } + + ObjectNodeCode parameterNodeCode = buildChildNode(classInstanceElement); + if (!typeResolver.isAssignableFrom(paramType, parameterNodeCode.nodeClass())) { + throw new IllegalArgumentException( + "Unable to assign type %s from %s".formatted(paramType, parameterNodeCode.nodeClass())); + } + + yield parameterNodeCode.nodeValue(); + } + + Expression value = valueResolver.resolveCodeValue(paramType, content.value()); + if (!(value instanceof Literal.Null)) { + yield value; + } + + yield valueResolver.coerceDefaultValue(namedArgValue); + + } + case InstancePropertyAttribute(String ignored, Value value) -> { + Expression val = valueResolver.resolveCodeValue(paramType, value); + if (!(val instanceof Literal.Null)) { + yield val; + } + + yield valueResolver.coerceDefaultValue(namedArgValue); + } + case null -> valueResolver.coerceDefaultValue(namedArgValue); + }; } private void processDefaultPropertyValue() { @@ -281,21 +525,26 @@ private void processInstantPropertyContent(String propertyName, processInstancePropertyValue(propertyName, content.value()); } - private void processElementOnProperty(String propertyName, AssignableElement element) { - switch (element) { - case ClassInstanceElement classInstanceElement -> - processInstancePropertyElement(propertyName, classInstanceElement); - case InstancePropertyElement( - String key, ElementContent innerContent - ) -> { - if (!innerContent.attributes().isEmpty() || !innerContent.elements().isEmpty()) { - throw new UnsupportedOperationException( - "Cannot resolve property value from content with attributes"); - } + private void processDefaultPropertyElements() { + if (defaultPropertyElements.isEmpty()) { + return; + } - addToPropertyMap(propertyName, key, innerContent.value()); - } + String defaultProperty = methodResolver.resolveDefaultProperty(objectType); + if (defaultProperty != null) { + defaultPropertyElements.forEach(element -> processInstancePropertyElement(defaultProperty, element)); + return; + } + + if (typeResolver.isAssignableFrom(Collection.class, objectType)) { + Type contentTypeBound = typeArguments == null ? Object.class : typeArguments[0]; + Expression.Variable variableValue = CodeValues.variable(objectIdentifier); + defaultPropertyElements.forEach( + element -> addToCollectionWithTypeBound(variableValue, element, contentTypeBound)); + return; } + + throw new IllegalArgumentException("Unable to handle default elements for elements %s".formatted(objectType)); } private void processAttributeOnProperty(String propertyName, AssignableAttribute attribute) { @@ -308,34 +557,6 @@ case EventHandlerAttribute(String eventName, Handler handler) when "onChange".eq } } - private void addToPropertyMap(String propertyName, String key, Value value) { - Method propertyGetter = methodResolver.resolveGetter(objectType, propertyName) - .orElseThrow(() -> new IllegalArgumentException( - "Unable to find getter for property %s on class %s".formatted( - propertyName, objectType))); - - - Type propertyType = propertyGetter.getGenericReturnType(); - Type[] typeArguments = typeResolver.resolveUpperBoundTypeArguments(propertyType); - if (!typeResolver.isAssignableFrom(Map.class, propertyType)) { - throw new IllegalArgumentException("Property %s does not extend map".formatted(propertyName)); - } - - if (typeArguments == null || typeArguments.length != 2) { - throw new IllegalArgumentException( - "Property %s does not represent a map with two type arguments".formatted(propertyName)); - } - addToMapWithTypeBounds(CodeValues.methodCall(objectIdentifier, propertyGetter), key, value, typeArguments[0], - typeArguments[1]); - } - - private void addToMapWithTypeBounds(CodeValue.Expression mapValue, String key, Value value, Type keyTypeBound, - Type valueTypeBound) { - CodeValue.Expression keyValue = valueResolver.resolveCodeValue(keyTypeBound, key); - CodeValue.Expression valueValue = valueResolver.resolveCodeValue(valueTypeBound, value); - initializers.add(CodeValues.methodCall(mapValue, "put", keyValue, valueValue)); - } - private void processInstancePropertyElement(String propertyName, ClassInstanceElement element) { Method propertySetter = methodResolver.resolveSetter(objectType, propertyName).orElse(null); @@ -401,108 +622,45 @@ case StaticPropertyAttribute(String className, String propertyName, Value value) } } - private void processStaticPropertyContent(String className, String propertyName, - ElementContent content) { - if (!content.attributes().isEmpty()) { - throw new UnsupportedOperationException("Cannot handle attributes on static property element"); - } - - if ((!content.elements().isEmpty() && !(content.value() instanceof Value.Empty)) || - content.elements().size() > 1) { - throw new UnsupportedOperationException("Cannot handle multiple values for static property element"); - } - - if (content.elements().size() == 1) { - if (!(content.elements().getFirst() instanceof ClassInstanceElement classInstanceElement)) { - throw new UnsupportedOperationException( - "Cannot handle static property element value that is not a class instance"); - } - processStaticPropertyElement(className, propertyName, classInstanceElement); - } - - processStaticPropertyValue(className, propertyName, content.value()); - } - - private void processInstancePropertyValue(String propertyName, Value value) { - switch (value) { - case Value.Empty ignored -> {} - case Expression expression -> { - Method propertyMethod = methodResolver.resolveProperty(objectType, propertyName) - .orElseThrow(() -> new IllegalArgumentException( - "No property found for expression binding %s".formatted( - propertyName))); - Type valueType = propertyMethod.getGenericReturnType(); - ExpressionResult result = expressionResolver.resolveExpression(expression); - Method bindMethod = methodResolver.findMethod(valueType, "bind", result.type()) - .orElseThrow(() -> new IllegalArgumentException( - "Property %s does not have a bind method".formatted( - propertyName))); - initializers.addAll(result.initializers()); - initializers.add( - CodeValues.methodCall(CodeValues.methodCall(objectIdentifier, propertyMethod), bindMethod, - result.value())); - } - case Value val -> { - Method propertySetter = methodResolver.resolveSetter(objectType, propertyName).orElse(null); - if (propertySetter != null) { - Type valueType = propertySetter.getGenericParameterTypes()[0]; - CodeValue codeValue = valueResolver.resolveCodeValue(valueType, val); - initializers.add(CodeValues.methodCall(objectIdentifier, propertySetter, codeValue)); - return; - } - - Method propertyGetter = methodResolver.resolveGetter(objectType, propertyName).orElse(null); - if (propertyGetter != null) { - Type propertyGenericType = propertyGetter.getGenericReturnType(); - CodeValue.Expression propertyCodeValue = CodeValues.methodCall(objectIdentifier, propertyGetter); - if (typeResolver.isAssignableFrom(Collection.class, propertyGenericType) && - propertyGenericType instanceof ParameterizedType parameterizedType) { - - Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - if (actualTypeArguments.length != 1) { - throw new IllegalArgumentException( - "Unable to resolve contained type for type %s".formatted(parameterizedType)); - } - - addToCollectionWithTypeBound(propertyCodeValue, val, actualTypeArguments[0]); - return; - } - - if (typeResolver.isAssignableFrom(Map.class, propertyGenericType) && - propertyGenericType instanceof ParameterizedType parameterizedType) { + private void addToCollectionWithTypeBound(Expression collectionCodeValue, ClassInstanceElement element, + Type contentTypeBound) { + ObjectNodeCode nodeCode = buildChildNode(element); + if (!typeResolver.isAssignableFrom(contentTypeBound, nodeCode.nodeClass())) { + throw new IllegalArgumentException( + "Content type bound %s does not match node type %s".formatted(contentTypeBound, + nodeCode.nodeClass())); + } - Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - if (actualTypeArguments.length != 2) { - throw new IllegalArgumentException( - "Unable to resolve key and value type for type %s".formatted(parameterizedType)); - } + initializers.add(CodeValues.methodCall(collectionCodeValue, "add", nodeCode.nodeValue())); + } - addToMapWithTypeBounds(propertyCodeValue, propertyName, val, actualTypeArguments[0], - actualTypeArguments[1]); - return; - } + private void processElementOnProperty(String propertyName, AssignableElement element) { + switch (element) { + case ClassInstanceElement classInstanceElement -> + processInstancePropertyElement(propertyName, classInstanceElement); + case InstancePropertyElement( + String key, ElementContent innerContent + ) -> { + if (!innerContent.attributes().isEmpty()) { + throw new UnsupportedOperationException( + "Cannot resolve property value from content with attributes"); + } - throw new IllegalArgumentException( - "Unable to process read only property %s type %s".formatted(propertyName, - propertyGenericType)); + if ((!innerContent.elements().isEmpty() && !(innerContent.value() instanceof Value.Empty)) || + innerContent.elements().size() > 1) { + throw new UnsupportedOperationException( + "Cannot handle multiple values for static property element"); } - if (typeResolver.isAssignableFrom(Map.class, objectType)) { - Type[] actualTypeArguments = typeArguments == null ? - new Type[]{Object.class, Object.class} : - typeArguments; - if (typeArguments != null && typeArguments.length != 2) { - throw new IllegalArgumentException( - "Unable to resolve key and value type for type %s".formatted(objectType)); + if (innerContent.elements().size() == 1) { + if (!(innerContent.elements().getFirst() instanceof ClassInstanceElement classInstanceElement)) { + throw new UnsupportedOperationException( + "Cannot handle static property element value that is not a class instance"); } - - addToMapWithTypeBounds(CodeValues.variable(objectIdentifier), propertyName, value, - actualTypeArguments[0], actualTypeArguments[1]); - return; + addToPropertyMap(propertyName, key, classInstanceElement); + } else { + addToPropertyMap(propertyName, key, innerContent.value()); } - - throw new UnsupportedOperationException( - "Unknown property %s for class %s".formatted(propertyName, objectType)); } } } @@ -553,275 +711,210 @@ private boolean propertyIsMutable(String property) { .orElse(false); } - private void addToCollectionWithTypeBound(CodeValue.Expression collectionCodeValue, Value value, - Type contentTypeBound) { - switch (value) { - case Value.Empty() -> {} - case Value.Literal(String val) when val.contains(",") -> Arrays.stream(val.split(",\\s*")) - .map(item -> valueResolver.resolveCodeValue( - contentTypeBound, item)) - .forEach(valueCode -> initializers.add( - CodeValues.methodCall( - collectionCodeValue, "add", - valueCode))); - case Value val -> initializers.add(CodeValues.methodCall(collectionCodeValue, "add", - valueResolver.resolveCodeValue(contentTypeBound, - val))); - } - } - - private List> getPossibleConstructorArgs() { - return methodResolver.getConstructors(objectType) - .stream() - .filter(methodResolver::hasAllNamedArgs) - .map(methodResolver::getNamedArgs) - .toList(); - } + private void addToPropertyMap(String propertyName, String key, ClassInstanceElement element) { + Method propertyGetter = methodResolver.resolveGetter(objectType, propertyName) + .orElseThrow(() -> new IllegalArgumentException( + "Unable to find getter for property %s on class %s".formatted( + propertyName, objectType))); - private void processRootInitialization(String type) { - objectIdentifier = FxmlProcessor.BUILDER_PROVIDED_ROOT_NAME; - objectType = typeResolver.resolve(type); - } - private void processStaticPropertyValue(String className, String property, Value value) { - if (value instanceof Value.Empty) { - return; + Type propertyType = propertyGetter.getGenericReturnType(); + Type[] typeArguments = typeResolver.resolveUpperBoundTypeArguments(propertyType); + if (!typeResolver.isAssignableFrom(Map.class, propertyType)) { + throw new IllegalArgumentException("Property %s does not extend map".formatted(propertyName)); } - Class staticPropertyClass = typeResolver.resolve(className); - Method propertySetter = methodResolver.resolveStaticSetter(staticPropertyClass, property).orElse(null); - if (propertySetter == null) { + if (typeArguments == null || typeArguments.length != 2) { throw new IllegalArgumentException( - "Unable to find static setter for %s on %s".formatted(property, staticPropertyClass)); - } - - Class[] parameterTypes = propertySetter.getParameterTypes(); - if (!typeResolver.isAssignableFrom(parameterTypes[0], objectType)) { - throw new IllegalArgumentException("First parameter of static property setter does not match node type"); + "Property %s does not represent a map with two type arguments".formatted(propertyName)); } - CodeValue codeValue = valueResolver.resolveCodeValue(parameterTypes[1], value); - initializers.add(CodeValues.methodCall(propertySetter, CodeValues.variable(objectIdentifier), codeValue)); + addToMapWithTypeBounds(CodeValues.methodCall(objectIdentifier, propertyGetter), key, element, typeArguments[0], + typeArguments[1]); } - private Set> getMatchingConstructorArgs() { - Set definedProperties = instanceProperties.keySet(); - - Map> definedPropertiesByMutability = definedProperties.stream() - .collect(Collectors.partitioningBy( - this::propertyIsMutable, - Collectors.toSet())); - - - Set constructorProperties = definedPropertiesByMutability.getOrDefault(false, Set.of()); + private void addToPropertyMap(String propertyName, String key, Value value) { + Method propertyGetter = methodResolver.resolveGetter(objectType, propertyName) + .orElseThrow(() -> new IllegalArgumentException( + "Unable to find getter for property %s on class %s".formatted( + propertyName, objectType))); - Set setterProperties = definedPropertiesByMutability.getOrDefault(true, Set.of()); - int undefinedConstructorPropertiesBest = Integer.MAX_VALUE; - int setterPropertiesBest = Integer.MAX_VALUE; + Type propertyType = propertyGetter.getGenericReturnType(); + Type[] typeArguments = typeResolver.resolveUpperBoundTypeArguments(propertyType); + if (!typeResolver.isAssignableFrom(Map.class, propertyType)) { + throw new IllegalArgumentException("Property %s does not extend map".formatted(propertyName)); + } - Set> bestConstructorArgs = new LinkedHashSet<>(); - for (List namedArgValues : getPossibleConstructorArgs()) { - Set argumentNames = namedArgValues.stream().map(NamedArgValue::name).collect(Collectors.toSet()); + if (typeArguments == null || typeArguments.length != 2) { + throw new IllegalArgumentException( + "Property %s does not represent a map with two type arguments".formatted(propertyName)); + } + addToMapWithTypeBounds(CodeValues.methodCall(objectIdentifier, propertyGetter), key, value, typeArguments[0], + typeArguments[1]); + } - if (!argumentNames.containsAll(constructorProperties)) { - continue; - } + private void addToMapWithTypeBounds(Expression mapExpression, String key, ClassInstanceElement element, + Type keyTypeBound, Type valueTypeBound) { + ObjectNodeCode nodeCode = buildChildNode(element); + if (!typeResolver.isAssignableFrom(valueTypeBound, nodeCode.nodeClass())) { + throw new IllegalArgumentException( + "Content type bound %s does not match node type %s".formatted(valueTypeBound, + nodeCode.nodeClass())); + } - Set undefinedConstructorProperties = new HashSet<>(argumentNames); - undefinedConstructorProperties.removeAll(definedProperties); + Expression keyValue = valueResolver.resolveCodeValue(keyTypeBound, key); + initializers.add(CodeValues.methodCall(mapExpression, "put", keyValue, nodeCode.nodeValue())); + } - Set remainingSetterProperties = new HashSet<>(setterProperties); - remainingSetterProperties.removeAll(argumentNames); + private void addToMapWithTypeBounds(Expression mapValue, String key, Value value, Type keyTypeBound, + Type valueTypeBound) { + Expression keyValue = valueResolver.resolveCodeValue(keyTypeBound, key); + Expression valueValue = valueResolver.resolveCodeValue(valueTypeBound, value); + initializers.add(CodeValues.methodCall(mapValue, "put", keyValue, valueValue)); + } - int undefinedConstructorPropertiesCount = undefinedConstructorProperties.size(); - if (undefinedConstructorPropertiesBest == undefinedConstructorPropertiesCount && - setterPropertiesBest == remainingSetterProperties.size()) { - bestConstructorArgs.add(namedArgValues); - } else if (undefinedConstructorPropertiesBest > undefinedConstructorPropertiesCount || - (undefinedConstructorPropertiesBest == undefinedConstructorPropertiesCount && - setterPropertiesBest > remainingSetterProperties.size())) { - undefinedConstructorPropertiesBest = undefinedConstructorPropertiesCount; - setterPropertiesBest = remainingSetterProperties.size(); - bestConstructorArgs.clear(); - bestConstructorArgs.add(namedArgValues); - } + private void processStaticPropertyContent(String className, String propertyName, + ElementContent content) { + if (!content.attributes().isEmpty()) { + throw new UnsupportedOperationException("Cannot handle attributes on static property element"); } - return bestConstructorArgs; - } - - private void processObjectInitialization() { - switch (rootNode) { - case RootElement(String type, ElementContent ignored) -> processRootInitialization(type); - case ReferenceElement(String source, ElementContent content) -> - processReferenceInitialization(source, content); - case CopyElement( - String source, ElementContent ignored - ) -> processCopyInitialization(source); - case IncludeElement( - Path source, Path ignored1, Charset ignored2, ElementContent ignored3 - ) -> processIncludeInitialization(source); - case FactoryElement( - String factoryClassName, String methodName, ElementContent ignored - ) -> processFactoryBasedInitialization(factoryClassName, methodName); - case ConstantElement(String className, String member, ElementContent ignored) -> - processConstantInitialization(className, member); - case ValueElement(String className, String value, ElementContent ignored) -> - processValueInitialization(className, value); - case InstanceElement(String className, ElementContent ignored) -> - processConstructorInitialization(className); - default -> throw new UnsupportedOperationException("Unable to initialize object"); + if ((!content.elements().isEmpty() && !(content.value() instanceof Value.Empty)) || + content.elements().size() > 1) { + throw new UnsupportedOperationException("Cannot handle multiple values for static property element"); } - if (providedId != null) { - processControllerSetter(objectIdentifier, objectType); - methodResolver.resolveSetter(objectType, "id", String.class) - .ifPresent(method -> initializers.add( - CodeValues.methodCall(objectIdentifier, method, objectIdentifier))); - typeArguments = extractTypeArguments(); + if (content.elements().size() == 1) { + if (!(content.elements().getFirst() instanceof ClassInstanceElement classInstanceElement)) { + throw new UnsupportedOperationException( + "Cannot handle static property element value that is not a class instance"); + } + processStaticPropertyElement(className, propertyName, classInstanceElement); + } else { + processStaticPropertyValue(className, propertyName, content.value()); } } - private void processValueInitialization(String className, String value) { - objectType = typeResolver.unwrapType(typeResolver.resolve(className)); - resolveIdentifier(); - if (objectType == String.class) { - initializers.add(CodeValues.declaration(objectType, objectIdentifier, - valueResolver.resolveCodeValue(objectType, value))); + private void processStaticPropertyValue(String className, String property, Value value) { + if (value instanceof Value.Empty) { return; } - - Class wrappedType = typeResolver.wrapType(objectType); - Method method = methodResolver.findMethodRequiredPublicIfExists(wrappedType, "valueOf", String.class) - .orElseThrow(() -> new IllegalArgumentException( - "Class %s does not have a valueOf method".formatted(objectType))); - - - try { - CodeValue codeValue = valueResolver.resolveCodeValue(method, value); - initializers.add(CodeValues.declaration(objectType, objectIdentifier, codeValue)); - } catch (InvocationTargetException | IllegalAccessException | RuntimeException e) { + Class staticPropertyClass = typeResolver.resolve(className); + Method propertySetter = methodResolver.resolveStaticSetter(staticPropertyClass, property).orElse(null); + if (propertySetter == null) { throw new IllegalArgumentException( - "Value %s not parseable by %s.%s".formatted(value, objectType, method.getName())); + "Unable to find static setter for %s on %s".formatted(property, staticPropertyClass)); } - } - - private void processConstantInitialization(String className, String member) { - Class constantContainerClass = typeResolver.resolve(className); - Field constantField = methodResolver.resolveFieldRequiredPublicIfExists(constantContainerClass, member) - .orElseThrow(() -> new IllegalArgumentException( - "Field %s of %s is not found or public".formatted(member, - constantContainerClass))); - objectType = constantField.getType(); - resolveIdentifier(); - initializers.add(CodeValues.declaration(objectType, objectIdentifier, CodeValues.fieldAccess(constantField))); - } - - private void resolveIdentifier() { - if (providedId != null) { - objectIdentifier = providedId; - nameResolver.storeIdType(objectIdentifier, objectType); - } else { - objectIdentifier = nameResolver.resolveUniqueName(objectType); + Class[] parameterTypes = propertySetter.getParameterTypes(); + if (!typeResolver.isAssignableFrom(parameterTypes[0], objectType)) { + throw new IllegalArgumentException("First parameter of static property setter does not match node type"); } + + Expression codeValue = valueResolver.resolveCodeValue(parameterTypes[1], value); + initializers.add(CodeValues.methodCall(propertySetter, CodeValues.variable(objectIdentifier), codeValue)); } - private void buildWithConstructorArgs(List namedArgValues) { - Object[] parameterValues = namedArgValues.stream() - .map(this::resolveParameterValue) - .toArray(CodeValue.Expression[]::new); + private void processInstancePropertyValue(String propertyName, Value value) { + switch (value) { + case Value.Empty ignored -> {} + case BindExpression bindExpression -> { + Method propertyMethod = methodResolver.resolveProperty(objectType, propertyName) + .orElseThrow(() -> new IllegalArgumentException( + "No property found for expression binding %s".formatted( + propertyName))); + Type valueType = propertyMethod.getGenericReturnType(); + ExpressionResult result = expressionResolver.resolveExpression(bindExpression); + Method bindMethod = methodResolver.findMethod(valueType, "bind", result.type()) + .orElseThrow(() -> new IllegalArgumentException( + "Property %s does not have a bind method".formatted( + propertyName))); + initializers.addAll(result.initializers()); + initializers.add( + CodeValues.methodCall(CodeValues.methodCall(objectIdentifier, propertyMethod), bindMethod, + result.value())); + } + case Value val -> { + Method propertySetter = methodResolver.resolveSetter(objectType, propertyName).orElse(null); + if (propertySetter != null) { + Type valueType = propertySetter.getGenericParameterTypes()[0]; + Expression codeValue = valueResolver.resolveCodeValue(valueType, val); + initializers.add(CodeValues.methodCall(objectIdentifier, propertySetter, codeValue)); + return; + } - namedArgValues.stream().map(NamedArgValue::name).forEach(instanceProperties::remove); + Method propertyGetter = methodResolver.resolveGetter(objectType, propertyName).orElse(null); + if (propertyGetter != null) { + Type propertyGenericType = propertyGetter.getGenericReturnType(); + Expression propertyCodeValue = CodeValues.methodCall(objectIdentifier, propertyGetter); + if (typeResolver.isAssignableFrom(Collection.class, propertyGenericType) && + propertyGenericType instanceof ParameterizedType parameterizedType) { - initializers.add(CodeValues.declaration(objectType, objectIdentifier, - CodeValues.newInstance(objectType, parameterValues))); - } + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + if (actualTypeArguments.length != 1) { + throw new IllegalArgumentException( + "Unable to resolve contained type for type %s".formatted(parameterizedType)); + } - private CodeValue.Expression resolveParameterValue(NamedArgValue namedArgValue) { - Class paramType = namedArgValue.parameterType(); - String paramName = namedArgValue.name(); - FxmlProperty.Instance property = instanceProperties.get(paramName); - return switch (property) { - case InstancePropertyElement(String ignored, ElementContent content) -> { - if (!content.attributes().isEmpty()) { - throw new UnsupportedOperationException( - "Cannot resolve property value from content with attributes"); - } + addToCollectionWithTypeBound(propertyCodeValue, val, actualTypeArguments[0]); + return; + } - if ((!content.elements().isEmpty() && !(content.value() instanceof Value.Empty)) || - content.elements().size() > 1) { - throw new UnsupportedOperationException("Cannot handle multiple values for parameter element"); + throw new IllegalArgumentException( + "Unable to process read only property %s type %s".formatted(propertyName, + propertyGenericType)); } - if (content.elements().size() == 1) { - if (!(content.elements().getFirst() instanceof ClassInstanceElement classInstanceElement)) { - throw new UnsupportedOperationException( - "Cannot handle static property element value that is not a class instance"); - } - - ObjectNodeCode parameterNodeCode = buildChildNode(classInstanceElement); - if (!typeResolver.isAssignableFrom(paramType, parameterNodeCode.nodeClass())) { + if (typeResolver.isAssignableFrom(Map.class, objectType)) { + Type[] actualTypeArguments = typeArguments == null ? + new Type[]{Object.class, Object.class} : + typeArguments; + if (typeArguments != null && typeArguments.length != 2) { throw new IllegalArgumentException( - "Unable to assign type %s from %s".formatted(paramType, parameterNodeCode.nodeClass())); + "Unable to resolve key and value type for type %s".formatted(objectType)); } - yield parameterNodeCode.nodeValue(); - } - - CodeValue.Expression value = valueResolver.resolveCodeValue(paramType, content.value()); - if (!(value instanceof CodeValue.Literal.Null)) { - yield value; + addToMapWithTypeBounds(CodeValues.variable(objectIdentifier), propertyName, value, + actualTypeArguments[0], actualTypeArguments[1]); + return; } - yield valueResolver.coerceDefaultValue(namedArgValue); - + throw new UnsupportedOperationException( + "Unknown property %s for class %s".formatted(propertyName, objectType)); } - case InstancePropertyAttribute(String ignored, Value value) -> - valueResolver.resolveCodeValue(paramType, value); - case null -> valueResolver.coerceDefaultValue(namedArgValue); - }; - } - - private void processControllerSetter(String identifier, Type valueClass) { - if (controllerClass != Object.class) { - processControllerSettersFromKnownClass(identifier, valueClass); } } - private Type[] extractTypeArguments() { - return methodResolver.resolveSetter(controllerClass, objectIdentifier, objectType) - .map(Method::getGenericParameterTypes) - .map(types -> types[0]) - .or(() -> methodResolver.resolveField(controllerClass, objectIdentifier) - .map(Field::getGenericType)) - .map(typeResolver::resolveUpperBoundTypeArguments) - .orElse(null); + private void processHandlerOnProperty(String propertyName, Handler handler) { + methodResolver.resolveProperty(objectType, propertyName) + .ifPresentOrElse(propertyMethod -> processPropertyChangeListener(handler, propertyName), () -> { + Method propertyGetter = methodResolver.resolveGetter(objectType, propertyName) + .orElseThrow(() -> new IllegalArgumentException( + "Unable to find getter for %s on class %s".formatted( + propertyName, objectType))); + processPropertyContainerListener(propertyGetter, handler); + }); } - private void processReferenceInitialization(String source, ElementContent content) { - objectIdentifier = source; - objectType = nameResolver.resolveTypeById(source); - if (!content.attributes().isEmpty() || !content.elements().isEmpty()) { - throw new UnsupportedOperationException("References with elements or attributes not supported"); + private void addToCollectionWithTypeBound(Expression collectionCodeValue, Value value, + Type contentTypeBound) { + switch (value) { + case Value.Empty() -> {} + case Value.Literal(String val) when val.contains(",") -> Arrays.stream(val.split(",\\s*")) + .map(item -> valueResolver.resolveCodeValue( + contentTypeBound, item)) + .forEach(valueCode -> initializers.add( + CodeValues.methodCall( + collectionCodeValue, "add", + valueCode))); + case Value val -> initializers.add(CodeValues.methodCall(collectionCodeValue, "add", + valueResolver.resolveCodeValue(contentTypeBound, + val))); } } - private void processFactoryBasedInitialization(String factoryClassName, String factoryMethodName) { - Class factoryClass = typeResolver.resolve(factoryClassName); - Method factoryMethod = methodResolver.findMethod(factoryClass, factoryMethodName, 0) - .orElseThrow(() -> new IllegalArgumentException( - "Factory method not found %s.%s".formatted(factoryClassName, - factoryMethodName))); - - objectType = factoryMethod.getReturnType(); - resolveIdentifier(); - - initializers.add(CodeValues.declaration(objectType, objectIdentifier, CodeValues.methodCall(factoryMethod))); - } - private void processHandlerProperty(FxmlProperty.EventHandler eventHandler) { String eventName = eventHandler.eventName(); Handler handler = eventHandler.handler(); @@ -835,27 +928,21 @@ private void processHandlerProperty(FxmlProperty.EventHandler eventHandler) { } if (eventName.startsWith("on") && eventName.endsWith("Change")) { - String property = eventName.substring(2, eventName.length() - 6); - processPropertyChangeListener(handler, property); + String property = StringUtils.camelCase(eventName.substring(2, eventName.length() - 6)); + methodResolver.resolveProperty(objectType, property) + .ifPresentOrElse(propertyMethod -> processPropertyChangeListener(handler, property), () -> { + Method propertyGetter = methodResolver.resolveGetter(objectType, property) + .orElseThrow(() -> new IllegalArgumentException( + "Unable to find getter for %s on class %s".formatted( + property, objectType))); + processPropertyContainerListener(propertyGetter, handler); + }); return; } throw new UnsupportedOperationException("Unknown event %s for class %s".formatted(eventName, objectType)); } - private void processControllerSettersFromKnownClass(String identifier, Type valueClass) { - methodResolver.resolveSetterRequiredPublicIfExists(controllerClass, identifier, valueClass) - .map(method -> CodeValues.methodCall(FxmlProcessor.CONTROLLER_NAME, method, - CodeValues.variable(identifier))) - .or(() -> methodResolver.resolveFieldRequiredPublicIfExists(controllerClass, identifier) - .filter(field -> typeResolver.isAssignableFrom(field.getType(), - valueClass)) - .map(field -> CodeValues.assignment( - CodeValues.fieldAccess(FxmlProcessor.CONTROLLER_NAME, field), - CodeValues.variable(field.getName())))) - .ifPresent(initializers::add); - } - private void processHandlerSetter(Handler handler, Method handlerSetter) { Type handlerType = handlerSetter.getGenericParameterTypes()[0]; Class eventClass; @@ -870,51 +957,10 @@ private void processHandlerSetter(Handler handler, Method handlerSetter) { } else { eventClass = Object.class; } - CodeValue.Expression codeValue = resolveControllerEventHandler(eventClass, handler); + Expression codeValue = resolveControllerEventHandler(eventClass, handler); initializers.add(CodeValues.methodCall(objectIdentifier, handlerSetter, codeValue)); } - private CodeValue.Expression resolveControllerEventHandler(Class eventType, Handler handler) { - if (!(handler instanceof Handler.Method(String methodName))) { - throw new UnsupportedOperationException("None method handlers not supported"); - } - - return methodResolver.findMethod(controllerClass, methodName, eventType) - .map(method -> { - if (method.getExceptionTypes().length == 0) { - return CodeValues.methodReference(FxmlProcessor.CONTROLLER_NAME, method); - } else { - CodeValue.MethodCall eventHandlerCall = CodeValues.methodCall( - FxmlProcessor.CONTROLLER_NAME, method, CodeValues.variable("event")); - return CodeValues.lambdaBuilder() - .untyped(parameterBuilder -> parameterBuilder.parameter("event")) - .body(lambdaBodyBuilder -> lambdaBodyBuilder.statement( - CodeValues.rethrow(eventHandlerCall))) - .build(); - } - }) - .or(() -> methodResolver.findMethod(controllerClass, methodName).map(method -> { - if (method.getExceptionTypes().length == 0) { - return CodeValues.lambdaBuilder() - .untyped(parameters -> parameters.parameter("event")) - .body(body -> body.statement( - CodeValues.methodCall(FxmlProcessor.CONTROLLER_NAME, - method))) - .build(); - } else { - CodeValue.MethodCall eventHandlerCall = CodeValues.methodCall( - FxmlProcessor.CONTROLLER_NAME, method); - return CodeValues.lambdaBuilder() - .untyped(parameterBuilder -> parameterBuilder.parameter("event")) - .body(lambdaBodyBuilder -> lambdaBodyBuilder.statement( - CodeValues.rethrow(eventHandlerCall))) - .build(); - } - })) - .orElseThrow(() -> new IllegalArgumentException( - "No method %s on %s".formatted(methodName, controllerClass))); - } - private void processPropertyChangeListener(Handler handler, String property) { Method propertyMethod = methodResolver.resolveProperty(objectType, property) .orElseThrow(() -> new IllegalArgumentException( @@ -926,24 +972,31 @@ private void processPropertyChangeListener(Handler handler, String property) { "Unable to determine the class of property %s".formatted( property))); - CodeValue.Lambda.MethodReference listener = resolveControllerPropertyChangeListener(propertyClass, handler); - CodeValue.MethodCall propertyMethodCall = CodeValues.methodCall(objectIdentifier, propertyMethod); + Expression.Lambda.MethodReference listener = resolveControllerPropertyChangeListener(propertyClass, handler); + StatementExpression.MethodCall propertyMethodCall = CodeValues.methodCall(objectIdentifier, propertyMethod); initializers.add(CodeValues.methodCall(propertyMethodCall, "addListener", listener)); } - private void processHandlerOnProperty(String propertyName, Handler handler) { - methodResolver.resolveProperty(objectType, propertyName) - .ifPresentOrElse(propertyMethod -> processPropertyChangeListener(handler, propertyName), () -> { - Method propertyGetter = methodResolver.resolveGetter(objectType, propertyName) - .orElseThrow(() -> new IllegalArgumentException( - "Unable to find getter for %s on class %s".formatted( - propertyName, objectType))); - processPropertyContainerListener(propertyGetter, handler); - }); + private void processPropertyContainerListener(Method propertyGetter, Handler handler) { + Expression listener = resolveControllerContainerChangeListener(propertyGetter.getGenericReturnType(), handler); + StatementExpression.MethodCall propertyMethodCall = CodeValues.methodCall(objectIdentifier, propertyGetter); + initializers.add(CodeValues.methodCall(propertyMethodCall, "addListener", listener)); + } + + private Expression resolveControllerEventHandler(Class eventType, Handler handler) { + return switch (handler) { + case Handler.Method(String methodName) -> resolveControllerEventHandlerMethod(eventType, methodName); + case Handler.Reference(String ignored) -> + throw new UnsupportedOperationException("reference event handlers not supported"); + case Handler.Script ignored -> + throw new UnsupportedOperationException("script event handlers not supported"); + case Handler.Empty() -> + CodeValues.lambdaBuilder().untyped(parameters -> parameters.parameter("event")).build(); + }; } - private CodeValue.Lambda.MethodReference resolveControllerPropertyChangeListener(Class valueClass, - Handler handler) { + private Expression.Lambda.MethodReference resolveControllerPropertyChangeListener(Class valueClass, + Handler handler) { if (!(handler instanceof Handler.Method(String methodName))) { throw new UnsupportedOperationException("Non method change listeners not supported"); } @@ -966,13 +1019,8 @@ private CodeValue.Lambda.MethodReference resolveControllerPropertyChangeListener return CodeValues.methodReference(FxmlProcessor.CONTROLLER_NAME, changeMethod); } - private void processPropertyContainerListener(Method propertyGetter, Handler handler) { - CodeValue listener = resolveControllerContainerChangeListener(propertyGetter.getGenericReturnType(), handler); - CodeValue.MethodCall propertyMethodCall = CodeValues.methodCall(objectIdentifier, propertyGetter); - initializers.add(CodeValues.methodCall(propertyMethodCall, "addListener", listener)); - } - - private CodeValue.Lambda.MethodReference resolveControllerContainerChangeListener(Type valueType, Handler handler) { + private Expression.Lambda.MethodReference resolveControllerContainerChangeListener(Type valueType, + Handler handler) { if (!(handler instanceof Handler.Method(String methodName))) { throw new UnsupportedOperationException("Non method handlers not supported"); } @@ -999,40 +1047,40 @@ private CodeValue.Lambda.MethodReference resolveControllerContainerChangeListene methodName, valueType))); } - private void addToCollectionWithTypeBound(CodeValue.Expression collectionCodeValue, ClassInstanceElement element, - Type contentTypeBound) { - ObjectNodeCode nodeCode = buildChildNode(element); - if (!typeResolver.isAssignableFrom(contentTypeBound, nodeCode.nodeClass())) { - throw new IllegalArgumentException( - "Content type bound %s does not match node type %s".formatted(contentTypeBound, - nodeCode.nodeClass())); - } - - initializers.add(CodeValues.methodCall(collectionCodeValue, "add", nodeCode.nodeValue())); - } - - private void processCopyInitialization(String source) { - objectType = nameResolver.resolveTypeById(source); - - if (!methodResolver.hasCopyConstructor(objectType)) { - throw new IllegalArgumentException("No copy constructor found for class %s".formatted(objectType)); - } - - objectIdentifier = source + "Copy"; - initializers.add(CodeValues.declaration(objectType, objectIdentifier, - CodeValues.newInstance(objectType, CodeValues.variable(source)))); - } - - private void addToMapWithTypeBounds(CodeValue.Expression mapExpression, String key, ClassInstanceElement element, - Type keyTypeBound, Type valueTypeBound) { - ObjectNodeCode nodeCode = buildChildNode(element); - if (!typeResolver.isAssignableFrom(valueTypeBound, nodeCode.nodeClass())) { - throw new IllegalArgumentException( - "Content type bound %s does not match node type %s".formatted(valueTypeBound, - nodeCode.nodeClass())); - } - - CodeValue.Expression keyValue = valueResolver.resolveCodeValue(keyTypeBound, key); - initializers.add(CodeValues.methodCall(mapExpression, "put", keyValue, nodeCode.nodeValue())); + private Expression.Lambda resolveControllerEventHandlerMethod(Class eventType, String methodName) { + return methodResolver.findMethod(controllerClass, methodName, eventType) + .map(method -> { + if (method.getExceptionTypes().length == 0) { + return CodeValues.methodReference(FxmlProcessor.CONTROLLER_NAME, method); + } else { + StatementExpression.MethodCall eventHandlerCall = CodeValues.methodCall( + FxmlProcessor.CONTROLLER_NAME, method, CodeValues.variable("event")); + return CodeValues.lambdaBuilder() + .untyped(parameters -> parameters.parameter("event")) + .body(lambdaBodyBuilder -> lambdaBodyBuilder.statement( + CodeValues.rethrow(eventHandlerCall))) + .build(); + } + }) + .or(() -> methodResolver.findMethod(controllerClass, methodName).map(method -> { + if (method.getExceptionTypes().length == 0) { + return CodeValues.lambdaBuilder() + .untyped(parameters -> parameters.parameter("event")) + .body(body -> body.statement( + CodeValues.methodCall(FxmlProcessor.CONTROLLER_NAME, + method))) + .build(); + } else { + StatementExpression.MethodCall eventHandlerCall = CodeValues.methodCall( + FxmlProcessor.CONTROLLER_NAME, method); + return CodeValues.lambdaBuilder() + .untyped(parameters -> parameters.parameter("event")) + .body(lambdaBodyBuilder -> lambdaBodyBuilder.statement( + CodeValues.rethrow(eventHandlerCall))) + .build(); + } + })) + .orElseThrow(() -> new IllegalArgumentException( + "No method %s on %s".formatted(methodName, controllerClass))); } } diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Block.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Block.java new file mode 100644 index 0000000..8dbaef2 --- /dev/null +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Block.java @@ -0,0 +1,48 @@ +package io.github.sheikah45.fx2j.processor.internal.code; + +import java.util.List; +import java.util.Objects; + +sealed public interface Block extends Statement { + sealed interface For extends Block { + record Loop(Expression initializer, + Expression termination, + List incrementors, + Simple body) + implements For { + public Loop { + Objects.requireNonNull(initializer, "initializer cannot be null"); + Objects.requireNonNull(termination, "termination cannot be null"); + Objects.requireNonNull(incrementors, "incrementors cannot be null"); + incrementors = List.copyOf(incrementors); + } + } + + record Each(Parameter loopParameter, Expression parameters, Simple body) implements For { + public Each { + Objects.requireNonNull(loopParameter, "loopParameter cannot be null"); + Objects.requireNonNull(parameters, "parameters cannot be null"); + Objects.requireNonNull(body, "body cannot be null"); + } + } + } + record Try(List resources, Simple body, List catchBlocks, Simple finallyBlock) + implements Block { + public Try { + Objects.requireNonNull(resources, "resources cannot be null"); + Objects.requireNonNull(body, "body cannot be null"); + Objects.requireNonNull(catchBlocks, "catchBlocks cannot be null"); + Objects.requireNonNull(finallyBlock, "finallyBlock cannot be null"); + resources = List.copyOf(resources); + catchBlocks = List.copyOf(catchBlocks); + } + + public record Catch(String identifier, List exceptionTypes, Simple body) {} + } + record Simple(List statements) { + public Simple { + Objects.requireNonNull(statements, "statements cannot be null"); + statements = List.copyOf(statements); + } + } +} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeTypes.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeTypes.java deleted file mode 100644 index a003f09..0000000 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeTypes.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.github.sheikah45.fx2j.processor.internal.code; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; -import java.util.Arrays; -import java.util.List; - -public final class CodeTypes { - - private CodeTypes() {} - - public static CodeType.Raw.Array arrayOf(Class clazz) { - return arrayOf(of(clazz)); - } - - public static CodeType.Raw.Array arrayOf(ParameterizedType type) { - return arrayOf(of(type)); - } - - public static CodeType.Raw.Array arrayOf(CodeType.Declarable type) { - return new CodeType.Raw.Array(type); - } - - public static CodeType.Raw.TopLevel of(String className) { - int index = className.lastIndexOf("."); - return new CodeType.Raw.TopLevel(className.substring(0, index), className.substring(index + 1)); - } - - public static CodeType.Raw of(Class clazz) { - if (clazz.isArray()) { - return arrayOf(clazz.componentType()); - } else if (clazz.isPrimitive()) { - return new CodeType.Raw.Primitive(clazz.getSimpleName()); - } else if (clazz.getNestHost() != clazz) { - return new CodeType.Raw.Nested(of(clazz.getNestHost()), clazz.getSimpleName()); - } else { - return new CodeType.Raw.TopLevel(clazz.getPackageName(), clazz.getSimpleName()); - } - } - - public static CodeType.Parameterized of(ParameterizedType parameterizedType) { - if (!(of(parameterizedType.getRawType()) instanceof CodeType.Raw raw)) { - throw new IllegalArgumentException("Parameterized type %s raw type not raw".formatted(parameterizedType)); - } - - List typeArguments = Arrays.stream(parameterizedType.getActualTypeArguments()) - .map(CodeTypes::of) - .toList(); - - return new CodeType.Parameterized(raw, typeArguments); - } - - public static CodeType.Variable of(TypeVariable typeVariable) { - return new CodeType.Variable(typeVariable.getName(), - Arrays.stream(typeVariable.getBounds()).map(CodeTypes::of).toList()); - } - - public static CodeType.Wildcard of(WildcardType wildcardType) { - return new CodeType.Wildcard(Arrays.stream(wildcardType.getLowerBounds()).map(CodeTypes::of).toList(), - Arrays.stream(wildcardType.getUpperBounds()).map(CodeTypes::of).toList()); - } - - public static CodeType of(Type type) { - return switch (type) { - case Class clazz when clazz.isArray() -> CodeTypes.of(clazz); - case Class clazz -> CodeTypes.of(clazz); - case ParameterizedType parameterizedType -> of(parameterizedType); - case WildcardType wildcardType -> of(wildcardType); - case TypeVariable typeVariable -> of(typeVariable); - default -> throw new UnsupportedOperationException("Cannot create CodeType from %s".formatted(type)); - }; - } - -} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeValue.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeValue.java deleted file mode 100644 index 86d265b..0000000 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeValue.java +++ /dev/null @@ -1,218 +0,0 @@ -package io.github.sheikah45.fx2j.processor.internal.code; - -import java.util.List; -import java.util.Objects; - -public sealed interface CodeValue { - - sealed interface Expression extends CodeValue {} - sealed interface Assignable extends Expression {} - sealed interface Statement extends CodeValue {} - sealed interface BlockStatement extends Statement {} - sealed interface StatementExpression extends Expression, Statement {} - sealed interface Resource {} - sealed interface Declarator {} - - sealed interface Literal extends Expression { - record Null() implements Literal {} - record Bool(boolean value) implements Literal {} - record Char(char value) implements Literal {} - record Byte(byte value) implements Literal {} - record Short(short value) implements Literal {} - record Int(int value) implements Literal {} - record Long(long value) implements Literal {} - record Float(float value) implements Literal {} - record Double(double value) implements Literal {} - record Str(String value) implements Literal { - public Str { - Objects.requireNonNull(value, "value cannot be null"); - } - } - } - - sealed interface Array extends Expression { - record Declared(CodeType.Declarable componentType, List values) implements - Array { - public Declared { - Objects.requireNonNull(componentType, "componentType cannot be null"); - Objects.requireNonNull(values, "values cannot be null"); - values = List.copyOf(values); - } - } - record Sized(CodeType.Declarable componentType, int size) implements Array { - public Sized { - Objects.requireNonNull(componentType, "componentType cannot be null"); - } - } - } - - sealed interface Lambda extends Expression { - sealed interface Arrow extends Lambda { - record Typed(List parameters, Block body) implements Arrow { - public Typed { - Objects.requireNonNull(parameters, "parameters cannot be null"); - Objects.requireNonNull(body, "body cannot be null"); - parameters = List.copyOf(parameters); - } - } - record Untyped(List parameters, Block body) implements Arrow { - public Untyped { - Objects.requireNonNull(parameters, "receiver cannot be null"); - Objects.requireNonNull(body, "body cannot be null"); - parameters = List.copyOf(parameters); - } - } - } - record MethodReference(CodeValue.Expression receiver, String methodName) implements Lambda { - public MethodReference { - Objects.requireNonNull(receiver, "receiver cannot be null"); - Objects.requireNonNull(methodName, "methodName cannot be null"); - } - } - - } - - sealed interface Return extends Statement { - record Value(CodeValue.Expression value) implements Return { - public Value { - Objects.requireNonNull(value, "value cannot be null"); - } - } - - record Void() implements Return {} - } - - sealed interface Break extends Statement { - record Labeled(String label) implements Break { - public Labeled { - Objects.requireNonNull(label, "label cannot be null"); - } - } - - record Unlabeled() implements Break {} - } - - sealed interface Continue extends Statement { - record Labeled(String label) implements Continue { - public Labeled { - Objects.requireNonNull(label, "label cannot be null"); - } - } - - record Unlabeled() implements Continue {} - } - - sealed interface For extends BlockStatement { - record Loop(Expression initializer, Expression termination, List incrementors, Block body) - implements For { - public Loop { - Objects.requireNonNull(initializer, "initializer cannot be null"); - Objects.requireNonNull(termination, "termination cannot be null"); - Objects.requireNonNull(incrementors, "incrementors cannot be null"); - incrementors = List.copyOf(incrementors); - } - } - - record Each(Parameter loopParameter, Expression parameters, Block body) implements For { - public Each { - Objects.requireNonNull(loopParameter, "loopParameter cannot be null"); - Objects.requireNonNull(parameters, "parameters cannot be null"); - Objects.requireNonNull(body, "body cannot be null"); - } - } - } - record Variable(String identifier) implements Assignable, Resource, Declarator { - public Variable { - Objects.requireNonNull(identifier, "identifier cannot be null"); - } - } - record Type(CodeType type) implements Expression { - public Type { - Objects.requireNonNull(type, "type cannot be null"); - } - } - record Enum(java.lang.Enum value) implements Expression { - public Enum { - Objects.requireNonNull(value, "value cannot be null"); - } - } - record FieldAccess(Expression receiver, java.lang.String field) implements Assignable { - public FieldAccess { - Objects.requireNonNull(receiver, "receiver cannot be null"); - Objects.requireNonNull(field, "field cannot be null"); - } - } - record ArrayAccess(Expression receiver, Expression accessor) implements Assignable { - public ArrayAccess { - Objects.requireNonNull(receiver, "receiver cannot be null"); - Objects.requireNonNull(accessor, "accessor cannot be null"); - } - } - record NewInstance(CodeType.Declarable type, List args) implements StatementExpression { - public NewInstance { - Objects.requireNonNull(type, "type cannot be null"); - Objects.requireNonNull(args, "args cannot be null"); - args = List.copyOf(args); - } - } - record MethodCall(Expression receiver, java.lang.String methodName, List args) - implements StatementExpression { - public MethodCall { - Objects.requireNonNull(receiver, "receiver cannot be null"); - Objects.requireNonNull(methodName, "methodName cannot be null"); - Objects.requireNonNull(args, "args cannot be null"); - args = List.copyOf(args); - } - } - record Assignment(T receiver, Expression value) implements StatementExpression, Declarator { - public Assignment { - Objects.requireNonNull(receiver, "receiver cannot be null"); - Objects.requireNonNull(value, "value cannot be null"); - } - } - record PreIncrement(Assignable receiver) implements StatementExpression { - public PreIncrement { - Objects.requireNonNull(receiver, "receiver cannot be null"); - } - } - record PostIncrement(Assignable receiver) implements StatementExpression { - public PostIncrement { - Objects.requireNonNull(receiver, "receiver cannot be null"); - } - } - record PreDecrement(Assignable receiver) implements StatementExpression { - public PreDecrement { - Objects.requireNonNull(receiver, "receiver cannot be null"); - } - } - record PostDecrement(Assignable receiver) implements StatementExpression { - public PostDecrement { - Objects.requireNonNull(receiver, "receiver cannot be null"); - } - } - record Declaration(CodeType.Declarable type, List declarators) implements Statement {} - record LineBreak() implements Statement {} - record Block(List statements) { - public Block { - Objects.requireNonNull(statements, "statements cannot be null"); - statements = List.copyOf(statements); - } - } - record ResourceDeclaration(CodeType.Declarable type, String identifier, Expression initializer) - implements Resource {} - record Try(List resources, Block body, List catchBlocks, Block finallyBlock) - implements BlockStatement { - public Try { - Objects.requireNonNull(resources, "resources cannot be null"); - Objects.requireNonNull(body, "body cannot be null"); - Objects.requireNonNull(catchBlocks, "catchBlocks cannot be null"); - Objects.requireNonNull(finallyBlock, "finallyBlock cannot be null"); - resources = List.copyOf(resources); - catchBlocks = List.copyOf(catchBlocks); - } - } - record Catch(String identifier, List exceptionTypes, Block body) {} - record Throw(CodeValue.Expression exception) implements Statement {} - - record Parameter(CodeType.Declarable type, String identifier) {} -} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeValues.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeValues.java index 63a094c..85d778c 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeValues.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeValues.java @@ -13,104 +13,100 @@ public final class CodeValues { - private static final CodeValue.LineBreak LINE_BREAK = new CodeValue.LineBreak(); - private static final CodeValue.Literal.Null NULL = new CodeValue.Literal.Null(); - private static final CodeValue.Block EMPTY_BLOCK = new CodeValue.Block(List.of()); - private static final CodeValue.Return.Void VOID_RETURN = new CodeValue.Return.Void(); + private static final Statement.LineBreak LINE_BREAK = new Statement.LineBreak(); + private static final Literal.Null NULL = new Literal.Null(); + private static final Block.Simple EMPTY_BLOCK = new Block.Simple(List.of()); + private static final Statement.Return.Void VOID_RETURN = new Statement.Return.Void(); private CodeValues() {} - public static CodeValue.LineBreak lineBreak() { + public static Statement.LineBreak lineBreak() { return LINE_BREAK; } - public static CodeValue.Block block(CodeValue.Statement... statements) { + public static Block.Simple block(Statement... statements) { return block(List.of(statements)); } - public static CodeValue.Block block(List statements) { + public static Block.Simple block(List statements) { if (statements.isEmpty()) { return EMPTY_BLOCK; } - return new CodeValue.Block(statements); + return new Block.Simple(statements); } - public static CodeValue.Parameter parameter(Type type, String identifier) { - if (!(CodeTypes.of(type) instanceof CodeType.Declarable declarable)) { + public static Parameter parameter(Type type, String identifier) { + if (!(TypeValues.of(type) instanceof TypeValue.Declarable declarable)) { throw new IllegalArgumentException("Type %s is not declarable".formatted(type)); } return parameter(declarable, identifier); } - public static CodeValue.Parameter parameter(CodeType.Declarable type, String identifier) { - return new CodeValue.Parameter(type, identifier); + public static Parameter parameter(TypeValue.Declarable type, String identifier) { + return new Parameter(type, identifier); } - public static CodeValue.Enum enumValue(java.lang.Enum value) { - return new CodeValue.Enum(value); + public static Expression.FieldAccess fieldAccess(Expression receiver, String field) { + return new Expression.FieldAccess(receiver, field); } - public static CodeValue.FieldAccess fieldAccess(CodeValue.Expression receiver, String field) { - return new CodeValue.FieldAccess(receiver, field); + public static Expression.FieldAccess fieldAccess(String receiver, String field) { + return new Expression.FieldAccess(literal(receiver), field); } - public static CodeValue.FieldAccess fieldAccess(String receiver, String field) { - return new CodeValue.FieldAccess(literal(receiver), field); + public static Literal.Str literal(String value) { + return new Literal.Str(value); } - public static CodeValue.Literal.Str literal(String value) { - return new CodeValue.Literal.Str(value); + public static Expression.FieldAccess fieldAccess(Type receiver, String field) { + return new Expression.FieldAccess(type(receiver), field); } - public static CodeValue.FieldAccess fieldAccess(Type receiver, String field) { - return new CodeValue.FieldAccess(type(receiver), field); + public static Expression.Type type(Type codeType) { + return type(TypeValues.of(codeType)); } - public static CodeValue.Type type(Type codeType) { - return type(CodeTypes.of(codeType)); + public static Expression.Type type(TypeValue codeType) { + return new Expression.Type(codeType); } - public static CodeValue.Type type(CodeType codeType) { - return new CodeValue.Type(codeType); + public static Expression.FieldAccess fieldAccess(TypeValue.Declarable receiver, String field) { + return new Expression.FieldAccess(type(receiver), field); } - public static CodeValue.FieldAccess fieldAccess(CodeType.Declarable receiver, String field) { - return new CodeValue.FieldAccess(type(receiver), field); - } - - public static CodeValue.FieldAccess fieldAccess(String receiver, Field field) { + public static Expression.FieldAccess fieldAccess(String receiver, Field field) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalArgumentException("Field %s is static".formatted(field)); } - return new CodeValue.FieldAccess(variable(receiver), field.getName()); + return new Expression.FieldAccess(variable(receiver), field.getName()); } - public static CodeValue.Variable variable(String identifier) { - return new CodeValue.Variable(identifier); + public static Expression.Variable variable(String identifier) { + return new Expression.Variable(identifier); } - public static CodeValue.FieldAccess fieldAccess(Field field) { + public static Expression.FieldAccess fieldAccess(Field field) { if (!Modifier.isStatic(field.getModifiers())) { throw new IllegalArgumentException("Field %s is not static".formatted(field)); } - return new CodeValue.FieldAccess(type(field.getDeclaringClass()), field.getName()); + return new Expression.FieldAccess(type(field.getDeclaringClass()), field.getName()); } - public static CodeValue.Array.Declared array(Type componentType, Object... values) { - if (!(CodeTypes.of(componentType) instanceof CodeType.Declarable declarable)) { + public static Expression.Array.Declared array(Type componentType, Object... values) { + if (!(TypeValues.of(componentType) instanceof TypeValue.Declarable declarable)) { throw new IllegalArgumentException("Type %s is not declarable".formatted(componentType)); } return array(declarable, values); } - public static CodeValue.Array.Declared array(CodeType.Declarable componentType, Object... values) { - return new CodeValue.Array.Declared(componentType, Arrays.stream(values).map(CodeValues::of).toList()); + public static Expression.Array.Declared array(TypeValue.Declarable componentType, Object... values) { + return new Expression.Array.Declared(componentType, Arrays.stream(values).map(CodeValues::of).toList()); } - public static CodeValue.Expression of(Object value) { + public static Expression of(Object value) { return switch (value) { - case CodeValue.Expression expression -> expression; + case Expression expression -> expression; case Boolean val -> literal(val); case Byte val -> literal(val); case Short val -> literal(val); @@ -120,96 +116,101 @@ public static CodeValue.Expression of(Object value) { case Double val -> literal(val); case Character val -> literal(val); case String val -> literal(val); + case Enum val -> enumValue(val); case null -> nullValue(); default -> throw new IllegalArgumentException("Cannot create literal from %s".formatted(value)); }; } - public static CodeValue.Literal.Bool literal(boolean value) { - return new CodeValue.Literal.Bool(value); + public static Literal.Bool literal(boolean value) { + return new Literal.Bool(value); } - public static CodeValue.Literal.Byte literal(byte value) { - return new CodeValue.Literal.Byte(value); + public static Literal.Byte literal(byte value) { + return new Literal.Byte(value); } - public static CodeValue.Literal.Short literal(short value) { - return new CodeValue.Literal.Short(value); + public static Literal.Short literal(short value) { + return new Literal.Short(value); } - public static CodeValue.Literal.Int literal(int value) { - return new CodeValue.Literal.Int(value); + public static Literal.Int literal(int value) { + return new Literal.Int(value); } - public static CodeValue.Literal.Long literal(long value) { - return new CodeValue.Literal.Long(value); + public static Literal.Long literal(long value) { + return new Literal.Long(value); } - public static CodeValue.Literal.Float literal(float value) { - return new CodeValue.Literal.Float(value); + public static Literal.Float literal(float value) { + return new Literal.Float(value); } - public static CodeValue.Literal.Double literal(double value) { - return new CodeValue.Literal.Double(value); + public static Literal.Double literal(double value) { + return new Literal.Double(value); } - public static CodeValue.Literal.Char literal(char value) { - return new CodeValue.Literal.Char(value); + public static Literal.Char literal(char value) { + return new Literal.Char(value); } - public static CodeValue.Literal.Null nullValue() { + public static Expression.Enum enumValue(java.lang.Enum value) { + return new Expression.Enum(value); + } + + public static Literal.Null nullValue() { return NULL; } - public static CodeValue.Array.Declared array(String qualifiedComponentTypeName, Object... values) { + public static Expression.Array.Declared array(String qualifiedComponentTypeName, Object... values) { int index = qualifiedComponentTypeName.lastIndexOf("."); - return array(new CodeType.Raw.TopLevel(qualifiedComponentTypeName.substring(0, index), - qualifiedComponentTypeName.substring(index + 1)), values); + return array(new TypeValue.Raw.Top(qualifiedComponentTypeName.substring(0, index), + qualifiedComponentTypeName.substring(index + 1)), values); } @SafeVarargs - public static CodeValue.Array.Declared array(T... values) { - return array(CodeTypes.of(values.getClass().getComponentType()), (Object[]) values); + public static Expression.Array.Declared array(T... values) { + return array(TypeValues.of(values.getClass().getComponentType()), (Object[]) values); } - public static CodeValue.Array.Sized array(Type componentType, int size) { - if (!(CodeTypes.of(componentType) instanceof CodeType.Declarable declarable)) { + public static Expression.Array.Sized array(Type componentType, int size) { + if (!(TypeValues.of(componentType) instanceof TypeValue.Declarable declarable)) { throw new IllegalArgumentException("Type %s is not declarable".formatted(componentType)); } return array(declarable, size); } - public static CodeValue.Array.Sized array(CodeType.Declarable componentType, int size) { - return new CodeValue.Array.Sized(componentType, size); + public static Expression.Array.Sized array(TypeValue.Declarable componentType, int size) { + return new Expression.Array.Sized(componentType, size); } - public static CodeValue.Array.Sized array(String qualifiedComponentTypeName, int size) { + public static Expression.Array.Sized array(String qualifiedComponentTypeName, int size) { int index = qualifiedComponentTypeName.lastIndexOf("."); - return array(new CodeType.Raw.TopLevel(qualifiedComponentTypeName.substring(0, index), - qualifiedComponentTypeName.substring(index + 1)), size); + return array(new TypeValue.Raw.Top(qualifiedComponentTypeName.substring(0, index), + qualifiedComponentTypeName.substring(index + 1)), size); } - public static CodeValue.Lambda.MethodReference methodReference(CodeType.Declarable type, String methodName) { + public static Expression.Lambda.MethodReference methodReference(TypeValue.Declarable type, String methodName) { return methodReference(type(type), methodName); } - public static CodeValue.Lambda.MethodReference methodReference(CodeValue.Expression receiver, String methodName) { - return new CodeValue.Lambda.MethodReference(receiver, methodName); + public static Expression.Lambda.MethodReference methodReference(Expression receiver, String methodName) { + return new Expression.Lambda.MethodReference(receiver, methodName); } - public static CodeValue.Lambda.MethodReference methodReference(String identifier, Method method) { + public static Expression.Lambda.MethodReference methodReference(String identifier, Method method) { return methodReference(variable(identifier), method.getName()); } - public static CodeValue.Lambda.MethodReference methodReference(String identifier, String methodName) { + public static Expression.Lambda.MethodReference methodReference(String identifier, String methodName) { return methodReference(variable(identifier), methodName); } - public static CodeValue.Lambda.MethodReference methodReference(Method method) { + public static Expression.Lambda.MethodReference methodReference(Method method) { return methodReference(method.getDeclaringClass(), method.getName()); } - public static CodeValue.Lambda.MethodReference methodReference(Type type, String methodName) { + public static Expression.Lambda.MethodReference methodReference(Type type, String methodName) { return methodReference(type(type), methodName); } @@ -217,22 +218,22 @@ public static LambdaBuilder lambdaBuilder() { return new LambdaBuilder(); } - public static CodeValue.NewInstance newInstance(String className, Object... args) { - return newInstance(CodeTypes.of(className), args); + public static StatementExpression.NewInstance newInstance(String className, Object... args) { + return newInstance(TypeValues.of(className), args); } - public static CodeValue.NewInstance newInstance(CodeType.Declarable type, Object... args) { - return new CodeValue.NewInstance(type, Arrays.stream(args).map(CodeValues::of).toList()); + public static StatementExpression.NewInstance newInstance(TypeValue.Declarable type, Object... args) { + return new StatementExpression.NewInstance(type, Arrays.stream(args).map(CodeValues::of).toList()); } - public static CodeValue.NewInstance newInstance(Constructor constructor, Object... args) { + public static StatementExpression.NewInstance newInstance(Constructor constructor, Object... args) { if (constructor.getParameters().length != args.length) { throw new IllegalArgumentException("Provided number of args does not match constructor number of args"); } - return newInstance(CodeTypes.of(constructor.getDeclaringClass()), args); + return newInstance(TypeValues.of(constructor.getDeclaringClass()), args); } - public static CodeValue.MethodCall methodCall(String receiver, Method method, Object... args) { + public static StatementExpression.MethodCall methodCall(String receiver, Method method, Object... args) { if (Modifier.isStatic(method.getModifiers())) { throw new IllegalArgumentException("Method %s is static".formatted(method)); } @@ -242,15 +243,16 @@ public static CodeValue.MethodCall methodCall(String receiver, Method method, Ob return methodCall(receiver, method.getName(), args); } - public static CodeValue.MethodCall methodCall(String receiver, String methodName, Object... args) { + public static StatementExpression.MethodCall methodCall(String receiver, String methodName, Object... args) { return methodCall(variable(receiver), methodName, args); } - public static CodeValue.MethodCall methodCall(CodeValue.Expression receiver, String methodName, Object... args) { - return new CodeValue.MethodCall(receiver, methodName, Arrays.stream(args).map(CodeValues::of).toList()); + public static StatementExpression.MethodCall methodCall(Expression receiver, String methodName, Object... args) { + return new StatementExpression.MethodCall(receiver, methodName, + Arrays.stream(args).map(CodeValues::of).toList()); } - public static CodeValue.MethodCall methodCall(Type type, Method method, Object... args) { + public static StatementExpression.MethodCall methodCall(Type type, Method method, Object... args) { if (Modifier.isStatic(method.getModifiers())) { throw new IllegalArgumentException("Method %s is static".formatted(method)); } @@ -260,11 +262,11 @@ public static CodeValue.MethodCall methodCall(Type type, Method method, Object.. return methodCall(type, method.getName(), args); } - public static CodeValue.MethodCall methodCall(Type type, String methodName, Object... args) { + public static StatementExpression.MethodCall methodCall(Type type, String methodName, Object... args) { return methodCall(type(type), methodName, args); } - public static CodeValue.MethodCall methodCall(CodeValue.Expression receiver, Method method, Object... args) { + public static StatementExpression.MethodCall methodCall(Expression receiver, Method method, Object... args) { if (Modifier.isStatic(method.getModifiers())) { throw new IllegalArgumentException("Method %s is static".formatted(method)); } @@ -274,7 +276,7 @@ public static CodeValue.MethodCall methodCall(CodeValue.Expression receiver, Met return methodCall(receiver, method.getName(), args); } - public static CodeValue.MethodCall methodCall(Method method, Object... args) { + public static StatementExpression.MethodCall methodCall(Method method, Object... args) { if (!Modifier.isStatic(method.getModifiers())) { throw new IllegalArgumentException("Method %s is not static".formatted(method)); } @@ -284,70 +286,72 @@ public static CodeValue.MethodCall methodCall(Method method, Object... args) { return methodCall(type(method.getDeclaringClass()), method.getName(), args); } - public static CodeValue.Declaration declaration(Type type, java.lang.String identifier) { - if (!(CodeTypes.of(type) instanceof CodeType.Declarable declarable)) { + public static Statement.Declaration declaration(Type type, java.lang.String identifier) { + if (!(TypeValues.of(type) instanceof TypeValue.Declarable declarable)) { throw new IllegalArgumentException("Type %s is not declarable".formatted(type)); } return declaration(declarable, identifier); } - public static CodeValue.Declaration declaration(CodeType.Declarable type, java.lang.String identifier) { + public static Statement.Declaration declaration(TypeValue.Declarable type, java.lang.String identifier) { return declaration(type, variable(identifier)); } - public static CodeValue.Declaration declaration(CodeType.Declarable type, CodeValue.Declarator... declarators) { - return new CodeValue.Declaration(type, List.of(declarators)); + public static Statement.Declaration declaration(TypeValue.Declarable type, Declarator... declarators) { + return new Statement.Declaration(type, List.of(declarators)); } - public static CodeValue.Declaration declaration(Type type, java.lang.String identifier, Object initializer) { - if (!(CodeTypes.of(type) instanceof CodeType.Declarable declarable)) { + public static Statement.Declaration declaration(Type type, java.lang.String identifier, Object initializer) { + if (!(TypeValues.of(type) instanceof TypeValue.Declarable declarable)) { throw new IllegalArgumentException("Type %s is not declarable".formatted(type)); } return declaration(declarable, identifier, of(initializer)); } - public static CodeValue.Declaration declaration(String className, java.lang.String identifier, Object initializer) { - return declaration(CodeTypes.of(className), assignment(identifier, initializer)); + public static Statement.Declaration declaration(String className, java.lang.String identifier, Object initializer) { + return declaration(TypeValues.of(className), assignment(identifier, initializer)); } - public static CodeValue.Assignment assignment(String identifier, Object value) { + public static StatementExpression.Assignment assignment(String identifier, Object value) { return assignment(variable(identifier), value); } - public static CodeValue.Assignment assignment(T receiver, Object value) { - return new CodeValue.Assignment<>(receiver, of(value)); + public static StatementExpression.Assignment assignment(T receiver, + Object value) { + return new StatementExpression.Assignment<>(receiver, of(value)); } - public static CodeValue.Declaration declaration(CodeType.Declarable type, java.lang.String identifier, + public static Statement.Declaration declaration(TypeValue.Declarable type, java.lang.String identifier, Object initializer) { return declaration(type, assignment(identifier, initializer)); } - public static CodeValue.Declaration declaration(Type type, CodeValue.Variable variable) { - if (!(CodeTypes.of(type) instanceof CodeType.Declarable declarable)) { + public static Statement.Declaration declaration(Type type, Expression.Variable variable) { + if (!(TypeValues.of(type) instanceof TypeValue.Declarable declarable)) { throw new IllegalArgumentException("Type %s is not declarable".formatted(type)); } return declaration(declarable, variable); } - public static CodeValue.Declaration declaration(Type type, CodeValue.Variable variable, Object initializer) { - if (!(CodeTypes.of(type) instanceof CodeType.Declarable declarable)) { + public static Statement.Declaration declaration(Type type, Expression.Variable variable, Object initializer) { + if (!(TypeValues.of(type) instanceof TypeValue.Declarable declarable)) { throw new IllegalArgumentException("Type %s is not declarable".formatted(type)); } return declaration(declarable, variable, of(initializer)); } - public static CodeValue.Declaration declaration(String className, CodeValue.Variable variable, Object initializer) { - return declaration(CodeTypes.of(className), assignment(variable, initializer)); + public static Statement.Declaration declaration(String className, Expression.Variable variable, + Object initializer) { + return declaration(TypeValues.of(className), assignment(variable, initializer)); } - public static CodeValue.Declaration declaration(CodeType.Declarable type, CodeValue.Variable variable, + public static Statement.Declaration declaration(TypeValue.Declarable type, Expression.Variable variable, Object initializer) { return declaration(type, assignment(variable, initializer)); } - public static CodeValue.Try rethrow(CodeValue.Statement statement) { - CodeValue.Throw throwsException = CodeValues.throwsException(RuntimeException.class, + public static Block.Try rethrow(Statement statement) { + Statement.Throw throwsException = CodeValues.throwsException(RuntimeException.class, CodeValues.variable("exception")); return CodeValues.tryBuilder() .body(body -> body.statement(statement)) @@ -356,8 +360,8 @@ public static CodeValue.Try rethrow(CodeValue.Statement statement) { .build(); } - public static CodeValue.Throw throwsException(Type type, Object... args) { - if (!(CodeTypes.of(type) instanceof CodeType.Declarable declarable)) { + public static Statement.Throw throwsException(Type type, Object... args) { + if (!(TypeValues.of(type) instanceof TypeValue.Declarable declarable)) { throw new IllegalArgumentException("Type %s is not declarable".formatted(type)); } @@ -368,20 +372,20 @@ public static TryBuilder tryBuilder() { return new TryBuilder(); } - public static CodeValue.Throw throwsException(CodeValue.Expression expression) { - return new CodeValue.Throw(expression); + public static Statement.Throw throwsException(Expression expression) { + return new Statement.Throw(expression); } - public static CodeValue.NewInstance newInstance(Type type, Object... args) { - if (!(CodeTypes.of(type) instanceof CodeType.Declarable declarable)) { + public static StatementExpression.NewInstance newInstance(Type type, Object... args) { + if (!(TypeValues.of(type) instanceof TypeValue.Declarable declarable)) { throw new IllegalArgumentException("Type %s is not declarable".formatted(type)); } return newInstance(declarable, args); } - public static CodeValue.Try rethrow(CodeValue.Block block) { + public static Block.Try rethrow(Block.Simple block) { String exceptionIdentifier = "exception"; - CodeValue.Throw throwsException = CodeValues.throwsException(RuntimeException.class, + Statement.Throw throwsException = CodeValues.throwsException(RuntimeException.class, CodeValues.variable(exceptionIdentifier)); return CodeValues.tryBuilder() .body(body -> body.block(block)) @@ -391,11 +395,11 @@ public static CodeValue.Try rethrow(CodeValue.Block block) { .build(); } - public static CodeValue.Return.Value returns(CodeValue.Expression value) { - return new CodeValue.Return.Value(value); + public static Statement.Return.Value returns(Expression value) { + return new Statement.Return.Value(value); } - public static CodeValue.Return.Void returns() { + public static Statement.Return.Void returns() { return VOID_RETURN; } } diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Declarator.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Declarator.java new file mode 100644 index 0000000..378596e --- /dev/null +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Declarator.java @@ -0,0 +1,3 @@ +package io.github.sheikah45.fx2j.processor.internal.code; + +sealed public interface Declarator permits Expression.Variable, StatementExpression.Assignment {} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Expression.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Expression.java new file mode 100644 index 0000000..d47158f --- /dev/null +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Expression.java @@ -0,0 +1,77 @@ +package io.github.sheikah45.fx2j.processor.internal.code; + +import java.util.List; +import java.util.Objects; + +sealed public interface Expression + permits Expression.Array, Expression.Assignable, Expression.Enum, Expression.Lambda, Literal, + StatementExpression, Expression.Type { + sealed interface Assignable extends Expression {} + sealed interface Array extends Expression { + record Declared(TypeValue.Declarable componentType, List values) implements + Array { + public Declared { + Objects.requireNonNull(componentType, "componentType cannot be null"); + Objects.requireNonNull(values, "values cannot be null"); + values = List.copyOf(values); + } + } + record Sized(TypeValue.Declarable componentType, int size) implements Array { + public Sized { + Objects.requireNonNull(componentType, "componentType cannot be null"); + } + } + } + sealed interface Lambda extends Expression { + sealed interface Arrow extends Lambda { + record Typed(List parameters, Block.Simple body) implements Arrow { + public Typed { + Objects.requireNonNull(parameters, "parameters cannot be null"); + Objects.requireNonNull(body, "body cannot be null"); + parameters = List.copyOf(parameters); + } + } + record Untyped(List parameters, Block.Simple body) implements Arrow { + public Untyped { + Objects.requireNonNull(parameters, "receiver cannot be null"); + Objects.requireNonNull(body, "body cannot be null"); + parameters = List.copyOf(parameters); + } + } + } + record MethodReference(Expression receiver, String methodName) implements Lambda { + public MethodReference { + Objects.requireNonNull(receiver, "receiver cannot be null"); + Objects.requireNonNull(methodName, "methodName cannot be null"); + } + } + + } + record Variable(String identifier) implements Assignable, Resource, Declarator { + public Variable { + Objects.requireNonNull(identifier, "identifier cannot be null"); + } + } + record Type(TypeValue type) implements Expression { + public Type { + Objects.requireNonNull(type, "type cannot be null"); + } + } + record Enum(java.lang.Enum value) implements Expression { + public Enum { + Objects.requireNonNull(value, "value cannot be null"); + } + } + record FieldAccess(Expression receiver, String field) implements Assignable { + public FieldAccess { + Objects.requireNonNull(receiver, "receiver cannot be null"); + Objects.requireNonNull(field, "field cannot be null"); + } + } + record ArrayAccess(Expression receiver, Expression accessor) implements Assignable { + public ArrayAccess { + Objects.requireNonNull(receiver, "receiver cannot be null"); + Objects.requireNonNull(accessor, "accessor cannot be null"); + } + } +} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Literal.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Literal.java new file mode 100644 index 0000000..f6a5839 --- /dev/null +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Literal.java @@ -0,0 +1,20 @@ +package io.github.sheikah45.fx2j.processor.internal.code; + +import java.util.Objects; + +sealed public interface Literal extends Expression { + record Null() implements Literal {} + record Bool(boolean value) implements Literal {} + record Char(char value) implements Literal {} + record Byte(byte value) implements Literal {} + record Short(short value) implements Literal {} + record Int(int value) implements Literal {} + record Long(long value) implements Literal {} + record Float(float value) implements Literal {} + record Double(double value) implements Literal {} + record Str(String value) implements Literal { + public Str { + Objects.requireNonNull(value, "value cannot be null"); + } + } +} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Parameter.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Parameter.java new file mode 100644 index 0000000..263afa6 --- /dev/null +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Parameter.java @@ -0,0 +1,3 @@ +package io.github.sheikah45.fx2j.processor.internal.code; + +public record Parameter(TypeValue.Declarable type, String identifier) {} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Resource.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Resource.java new file mode 100644 index 0000000..5e0bc27 --- /dev/null +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Resource.java @@ -0,0 +1,6 @@ +package io.github.sheikah45.fx2j.processor.internal.code; + +sealed public interface Resource permits Expression.Variable, Resource.ResourceDeclaration { + record ResourceDeclaration(TypeValue.Declarable type, String identifier, Expression initializer) + implements Resource {} +} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Statement.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Statement.java new file mode 100644 index 0000000..e18d62c --- /dev/null +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/Statement.java @@ -0,0 +1,39 @@ +package io.github.sheikah45.fx2j.processor.internal.code; + +import java.util.List; +import java.util.Objects; + +sealed public interface Statement + permits Block, Statement.Declaration, Statement.LineBreak, Statement.Throw, Statement.Break, + Statement.Continue, Statement.Return, StatementExpression { + sealed interface Return extends Statement { + record Value(Expression value) implements Return { + public Value { + Objects.requireNonNull(value, "value cannot be null"); + } + } + + record Void() implements Return {} + } + sealed interface Break extends Statement { + record Labeled(String label) implements Break { + public Labeled { + Objects.requireNonNull(label, "label cannot be null"); + } + } + + record Unlabeled() implements Break {} + } + sealed interface Continue extends Statement { + record Labeled(String label) implements Continue { + public Labeled { + Objects.requireNonNull(label, "label cannot be null"); + } + } + + record Unlabeled() implements Continue {} + } + record Declaration(TypeValue.Declarable type, List declarators) implements Statement {} + record LineBreak() implements Statement {} + record Throw(Expression exception) implements Statement {} +} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/StatementExpression.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/StatementExpression.java new file mode 100644 index 0000000..790666e --- /dev/null +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/StatementExpression.java @@ -0,0 +1,49 @@ +package io.github.sheikah45.fx2j.processor.internal.code; + +import java.util.List; +import java.util.Objects; + +sealed public interface StatementExpression extends Expression, Statement { + record NewInstance(TypeValue.Declarable type, List args) implements StatementExpression { + public NewInstance { + Objects.requireNonNull(type, "type cannot be null"); + Objects.requireNonNull(args, "args cannot be null"); + args = List.copyOf(args); + } + } + record MethodCall(Expression receiver, String methodName, List args) + implements StatementExpression { + public MethodCall { + Objects.requireNonNull(receiver, "receiver cannot be null"); + Objects.requireNonNull(methodName, "methodName cannot be null"); + Objects.requireNonNull(args, "args cannot be null"); + args = List.copyOf(args); + } + } + record Assignment(T receiver, Expression value) implements StatementExpression, Declarator { + public Assignment { + Objects.requireNonNull(receiver, "receiver cannot be null"); + Objects.requireNonNull(value, "value cannot be null"); + } + } + record PreIncrement(Assignable receiver) implements StatementExpression { + public PreIncrement { + Objects.requireNonNull(receiver, "receiver cannot be null"); + } + } + record PostIncrement(Assignable receiver) implements StatementExpression { + public PostIncrement { + Objects.requireNonNull(receiver, "receiver cannot be null"); + } + } + record PreDecrement(Assignable receiver) implements StatementExpression { + public PreDecrement { + Objects.requireNonNull(receiver, "receiver cannot be null"); + } + } + record PostDecrement(Assignable receiver) implements StatementExpression { + public PostDecrement { + Objects.requireNonNull(receiver, "receiver cannot be null"); + } + } +} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeType.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/TypeValue.java similarity index 69% rename from fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeType.java rename to fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/TypeValue.java index a0d7eb5..ea656d0 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/CodeType.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/TypeValue.java @@ -3,34 +3,34 @@ import java.util.List; import java.util.Objects; -public sealed interface CodeType { +public sealed interface TypeValue { - sealed interface Declarable extends CodeType {} + sealed interface Declarable extends TypeValue {} sealed interface Raw extends Declarable { - record Nested(CodeType.Raw ownerType, String simpleName) implements Raw { + record Nested(TypeValue.Raw ownerType, String simpleName) implements Raw { public Nested { Objects.requireNonNull(ownerType, "ownerType cannot be null"); Objects.requireNonNull(simpleName, "simpleName cannot be null"); } } - record TopLevel(String packageName, String simpleName) implements Raw { - public TopLevel { + record Top(String packageName, String simpleName) implements Raw { + public Top { Objects.requireNonNull(packageName, "packageName cannot be null"); Objects.requireNonNull(simpleName, "simpleName cannot be null"); } } record Primitive(String name) implements Raw {} - record Array(CodeType.Declarable componentType) implements Raw {} + record Array(TypeValue.Declarable componentType) implements Raw {} } - record Parameterized(CodeType.Raw rawType, List typeArguments) implements Declarable { + record Parameterized(TypeValue.Raw rawType, List typeArguments) implements Declarable { public Parameterized { Objects.requireNonNull(rawType, "rawType cannot be null"); Objects.requireNonNull(typeArguments, "typeArguments cannot be null"); typeArguments = List.copyOf(typeArguments); } } - record Wildcard(List lowerBounds, List upperBounds) implements CodeType { + record Wildcard(List lowerBounds, List upperBounds) implements TypeValue { public Wildcard { Objects.requireNonNull(upperBounds, "upperBounds cannot be null"); Objects.requireNonNull(lowerBounds, "lowerBounds cannot be null"); @@ -38,7 +38,7 @@ record Wildcard(List lowerBounds, List upperBounds) implemen lowerBounds = List.copyOf(lowerBounds); } } - record Variable(String name, List upperBounds) implements CodeType { + record Variable(String name, List upperBounds) implements TypeValue { public Variable { Objects.requireNonNull(name, "name cannot be null"); Objects.requireNonNull(upperBounds, "upperBounds cannot be null"); diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/TypeValues.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/TypeValues.java new file mode 100644 index 0000000..320ed54 --- /dev/null +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/TypeValues.java @@ -0,0 +1,76 @@ +package io.github.sheikah45.fx2j.processor.internal.code; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Arrays; +import java.util.List; + +public final class TypeValues { + + private TypeValues() {} + + public static TypeValue.Raw.Array arrayOf(Class clazz) { + return arrayOf(of(clazz)); + } + + public static TypeValue.Raw.Array arrayOf(ParameterizedType type) { + return arrayOf(of(type)); + } + + public static TypeValue.Raw.Array arrayOf(TypeValue.Declarable type) { + return new TypeValue.Raw.Array(type); + } + + public static TypeValue.Raw.Top of(String className) { + int index = className.lastIndexOf("."); + return new TypeValue.Raw.Top(className.substring(0, index), className.substring(index + 1)); + } + + public static TypeValue.Raw of(Class clazz) { + if (clazz.isArray()) { + return arrayOf(clazz.componentType()); + } else if (clazz.isPrimitive()) { + return new TypeValue.Raw.Primitive(clazz.getSimpleName()); + } else if (clazz.getNestHost() != clazz) { + return new TypeValue.Raw.Nested(of(clazz.getNestHost()), clazz.getSimpleName()); + } else { + return new TypeValue.Raw.Top(clazz.getPackageName(), clazz.getSimpleName()); + } + } + + public static TypeValue.Parameterized of(ParameterizedType parameterizedType) { + if (!(of(parameterizedType.getRawType()) instanceof TypeValue.Raw raw)) { + throw new IllegalArgumentException("Parameterized type %s raw type not raw".formatted(parameterizedType)); + } + + List typeArguments = Arrays.stream(parameterizedType.getActualTypeArguments()) + .map(TypeValues::of) + .toList(); + + return new TypeValue.Parameterized(raw, typeArguments); + } + + public static TypeValue.Variable of(TypeVariable typeVariable) { + return new TypeValue.Variable(typeVariable.getName(), + Arrays.stream(typeVariable.getBounds()).map(TypeValues::of).toList()); + } + + public static TypeValue.Wildcard of(WildcardType wildcardType) { + return new TypeValue.Wildcard(Arrays.stream(wildcardType.getLowerBounds()).map(TypeValues::of).toList(), + Arrays.stream(wildcardType.getUpperBounds()).map(TypeValues::of).toList()); + } + + public static TypeValue of(Type type) { + return switch (type) { + case Class clazz when clazz.isArray() -> TypeValues.of(clazz); + case Class clazz -> TypeValues.of(clazz); + case ParameterizedType parameterizedType -> of(parameterizedType); + case WildcardType wildcardType -> of(wildcardType); + case TypeVariable typeVariable -> of(typeVariable); + default -> throw new UnsupportedOperationException("Cannot create CodeType from %s".formatted(type)); + }; + } + +} diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/builder/BlockBuilder.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/builder/BlockBuilder.java index be7c36b..a6aab32 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/builder/BlockBuilder.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/builder/BlockBuilder.java @@ -1,28 +1,29 @@ package io.github.sheikah45.fx2j.processor.internal.code.builder; -import io.github.sheikah45.fx2j.processor.internal.code.CodeValue; +import io.github.sheikah45.fx2j.processor.internal.code.Block; import io.github.sheikah45.fx2j.processor.internal.code.CodeValues; +import io.github.sheikah45.fx2j.processor.internal.code.Statement; import java.util.ArrayList; import java.util.List; public final class BlockBuilder { - private final List statements = new ArrayList<>(); + private final List statements = new ArrayList<>(); public BlockBuilder() {} - public BlockBuilder statement(CodeValue.Statement... statements) { + public BlockBuilder statement(Statement... statements) { this.statements.addAll(List.of(statements)); return this; } - public BlockBuilder block(CodeValue.Block block) { + public BlockBuilder block(Block.Simple block) { statements.addAll(block.statements()); return this; } - public CodeValue.Block build() { + public Block.Simple build() { return CodeValues.block(statements); } } diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/builder/LambdaBuilder.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/builder/LambdaBuilder.java index fb949c0..a593ebf 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/builder/LambdaBuilder.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/builder/LambdaBuilder.java @@ -1,8 +1,10 @@ package io.github.sheikah45.fx2j.processor.internal.code.builder; -import io.github.sheikah45.fx2j.processor.internal.code.CodeType; -import io.github.sheikah45.fx2j.processor.internal.code.CodeValue; +import io.github.sheikah45.fx2j.processor.internal.code.Block; +import io.github.sheikah45.fx2j.processor.internal.code.TypeValue; import io.github.sheikah45.fx2j.processor.internal.code.CodeValues; +import io.github.sheikah45.fx2j.processor.internal.code.Expression; +import io.github.sheikah45.fx2j.processor.internal.code.Parameter; import java.lang.reflect.Type; import java.util.ArrayList; @@ -12,7 +14,7 @@ public final class LambdaBuilder { private LambdaParameterBuilder lambdaParameterBuilder; - private CodeValue.Block body = CodeValues.block(); + private Block.Simple body = CodeValues.block(); public LambdaBuilder typed(Consumer consumer) { LambdaParameterBuilder.Typed parameterBuilder = new LambdaParameterBuilder.Typed(); @@ -35,35 +37,35 @@ public LambdaBuilder body(Consumer consumer) { return this; } - public CodeValue.Lambda.Arrow build() { + public Expression.Lambda.Arrow build() { return switch (lambdaParameterBuilder) { - case LambdaParameterBuilder.Typed builder -> new CodeValue.Lambda.Arrow.Typed(builder.build(), body); - case LambdaParameterBuilder.Untyped builder -> new CodeValue.Lambda.Arrow.Untyped(builder.build(), body); - case null -> new CodeValue.Lambda.Arrow.Untyped(List.of(), body); + case LambdaParameterBuilder.Typed builder -> new Expression.Lambda.Arrow.Typed(builder.build(), body); + case LambdaParameterBuilder.Untyped builder -> new Expression.Lambda.Arrow.Untyped(builder.build(), body); + case null -> new Expression.Lambda.Arrow.Untyped(List.of(), body); }; } public sealed interface LambdaParameterBuilder { final class Typed implements LambdaParameterBuilder { - private final List parameters = new ArrayList<>(); + private final List parameters = new ArrayList<>(); public Typed parameter(Type type, String identifier) { parameters.add(CodeValues.parameter(type, identifier)); return this; } - public Typed parameter(CodeType.Declarable type, String identifier) { + public Typed parameter(TypeValue.Declarable type, String identifier) { parameters.add(CodeValues.parameter(type, identifier)); return this; } - public Typed parameter(CodeValue.Parameter parameter) { + public Typed parameter(Parameter parameter) { parameters.add(parameter); return this; } - private List build() { + private List build() { return parameters; } } diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/builder/TryBuilder.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/builder/TryBuilder.java index 7e194c2..41bf0c4 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/builder/TryBuilder.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/code/builder/TryBuilder.java @@ -1,9 +1,11 @@ package io.github.sheikah45.fx2j.processor.internal.code.builder; -import io.github.sheikah45.fx2j.processor.internal.code.CodeType; -import io.github.sheikah45.fx2j.processor.internal.code.CodeTypes; -import io.github.sheikah45.fx2j.processor.internal.code.CodeValue; +import io.github.sheikah45.fx2j.processor.internal.code.Block; +import io.github.sheikah45.fx2j.processor.internal.code.TypeValue; +import io.github.sheikah45.fx2j.processor.internal.code.TypeValues; import io.github.sheikah45.fx2j.processor.internal.code.CodeValues; +import io.github.sheikah45.fx2j.processor.internal.code.Expression; +import io.github.sheikah45.fx2j.processor.internal.code.Resource; import java.util.ArrayList; import java.util.List; @@ -11,11 +13,11 @@ public final class TryBuilder { - private final List catches = new ArrayList<>(); + private final List catches = new ArrayList<>(); - private List resources = List.of(); - private CodeValue.Block body = CodeValues.block(); - private CodeValue.Block finallyBlock = CodeValues.block(); + private List resources = List.of(); + private Block.Simple body = CodeValues.block(); + private Block.Simple finallyBlock = CodeValues.block(); public TryBuilder() {} @@ -47,12 +49,12 @@ public TryBuilder finallyRuns(Consumer consumer) { return this; } - public CodeValue.Try build() { - return new CodeValue.Try(resources, body, catches, finallyBlock); + public Block.Try build() { + return new Block.Try(resources, body, catches, finallyBlock); } public static final class ResourcesBuilder { - private final List resources = new ArrayList<>(); + private final List resources = new ArrayList<>(); private ResourcesBuilder() {} @@ -62,26 +64,26 @@ public ResourcesBuilder resource(String identifier) { } public ResourcesBuilder resource(Class type, String identifier, - CodeValue.Expression initializer) { - resources.add(new CodeValue.ResourceDeclaration(CodeTypes.of(type), identifier, initializer)); + Expression initializer) { + resources.add(new Resource.ResourceDeclaration(TypeValues.of(type), identifier, initializer)); return this; } - public ResourcesBuilder resource(CodeValue.Resource resource) { + public ResourcesBuilder resource(Resource resource) { resources.add(resource); return this; } - private List build() { + private List build() { return List.copyOf(resources); } } public static final class CatchBuilder { - private final List exceptionTypes = new ArrayList<>(); + private final List exceptionTypes = new ArrayList<>(); private String identifier = "exception"; - private CodeValue.Block body = CodeValues.block(); + private Block.Simple body = CodeValues.block(); private CatchBuilder() {} @@ -91,7 +93,7 @@ public CatchBuilder identifier(String identifier) { } public CatchBuilder exception(Class exceptionType) { - exceptionTypes.add(CodeTypes.of(exceptionType)); + exceptionTypes.add(TypeValues.of(exceptionType)); return this; } @@ -102,8 +104,8 @@ public CatchBuilder body(Consumer consumer) { return this; } - private CodeValue.Catch build() { - return new CodeValue.Catch(identifier, exceptionTypes, body); + private Block.Try.Catch build() { + return new Block.Try.Catch(identifier, exceptionTypes, body); } } diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/ExpressionResult.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/ExpressionResult.java index 1d43330..6861407 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/ExpressionResult.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/ExpressionResult.java @@ -1,12 +1,13 @@ package io.github.sheikah45.fx2j.processor.internal.model; -import io.github.sheikah45.fx2j.processor.internal.code.CodeValue; +import io.github.sheikah45.fx2j.processor.internal.code.Expression; +import io.github.sheikah45.fx2j.processor.internal.code.Statement; import java.lang.reflect.Type; import java.util.List; import java.util.Objects; -public record ExpressionResult(Type type, CodeValue.Expression value, List initializers) { +public record ExpressionResult(Type type, Expression value, List initializers) { public ExpressionResult { Objects.requireNonNull(type, "type cannot be null"); Objects.requireNonNull(value, "value cannot be null"); diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/ObjectNodeCode.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/ObjectNodeCode.java index 2315e6d..218ab8d 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/ObjectNodeCode.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/model/ObjectNodeCode.java @@ -1,13 +1,14 @@ package io.github.sheikah45.fx2j.processor.internal.model; -import io.github.sheikah45.fx2j.processor.internal.code.CodeValue; +import io.github.sheikah45.fx2j.processor.internal.code.Expression; +import io.github.sheikah45.fx2j.processor.internal.code.Statement; import java.lang.reflect.Type; import java.util.List; -public record ObjectNodeCode(CodeValue.Variable nodeValue, +public record ObjectNodeCode(Expression.Variable nodeValue, Type nodeClass, - List initializers) { + List initializers) { public ObjectNodeCode { initializers = List.copyOf(initializers); } diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/resolve/ExpressionResolver.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/resolve/ExpressionResolver.java index 291b9df..2c298e6 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/resolve/ExpressionResolver.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/resolve/ExpressionResolver.java @@ -1,8 +1,9 @@ package io.github.sheikah45.fx2j.processor.internal.resolve; -import io.github.sheikah45.fx2j.parser.property.Expression; -import io.github.sheikah45.fx2j.processor.internal.code.CodeValue; +import io.github.sheikah45.fx2j.parser.property.BindExpression; import io.github.sheikah45.fx2j.processor.internal.code.CodeValues; +import io.github.sheikah45.fx2j.processor.internal.code.Expression; +import io.github.sheikah45.fx2j.processor.internal.code.Statement; import io.github.sheikah45.fx2j.processor.internal.model.ExpressionResult; import java.lang.reflect.Method; @@ -26,26 +27,26 @@ public class ExpressionResolver { this.nameResolver = nameResolver; } - public ExpressionResult resolveExpression(Expression value) { + public ExpressionResult resolveExpression(BindExpression value) { return switch (value) { - case Expression.Null() -> new ExpressionResult(Object.class, CodeValues.nullValue(), List.of()); - case Expression.Whole(long val) when val > Integer.MAX_VALUE || val < Integer.MIN_VALUE -> + case BindExpression.Null() -> new ExpressionResult(Object.class, CodeValues.nullValue(), List.of()); + case BindExpression.Whole(long val) when val > Integer.MAX_VALUE || val < Integer.MIN_VALUE -> new ExpressionResult(long.class, CodeValues.literal(val), List.of()); - case Expression.Whole(long val) -> + case BindExpression.Whole(long val) -> new ExpressionResult(int.class, CodeValues.literal((int) val), List.of()); - case Expression.Fraction(double val) when val > Float.MAX_VALUE || val < Float.MIN_VALUE -> + case BindExpression.Fraction(double val) when val > Float.MAX_VALUE || val < Float.MIN_VALUE -> new ExpressionResult(double.class, CodeValues.literal(val), List.of()); - case Expression.Fraction(double val) -> + case BindExpression.Fraction(double val) -> new ExpressionResult(float.class, CodeValues.literal((float) val), List.of()); - case Expression.Boolean(boolean val) -> + case BindExpression.Boolean(boolean val) -> new ExpressionResult(boolean.class, CodeValues.literal(val), List.of()); - case Expression.String(String val) -> + case BindExpression.String(String val) -> new ExpressionResult(String.class, CodeValues.literal(val), List.of()); - case Expression.Variable(String name) -> + case BindExpression.Variable(String name) -> new ExpressionResult(nameResolver.resolveTypeById(name), CodeValues.variable(name), List.of()); - case Expression.PropertyRead(Expression expression, String property) -> { - ExpressionResult expressionResult = resolveExpression(expression); - List initializers = new ArrayList<>(expressionResult.initializers()); + case BindExpression.PropertyRead(BindExpression bindExpression, String property) -> { + ExpressionResult expressionResult = resolveExpression(bindExpression); + List initializers = new ArrayList<>(expressionResult.initializers()); Method readProperty = methodResolver.resolveProperty(expressionResult.type(), property) .orElseThrow(() -> new IllegalArgumentException( @@ -58,14 +59,14 @@ public ExpressionResult resolveExpression(Expression value) { CodeValues.methodCall(expressionResult.value(), readProperty))); yield new ExpressionResult(valueType, CodeValues.variable(identifier), initializers); } - case Expression.MethodCall( - Expression expression, String methodName, List args + case BindExpression.MethodCall( + BindExpression bindExpression, String methodName, List args ) -> { - ExpressionResult expressionResult = resolveExpression(expression); - List initializers = new ArrayList<>(expressionResult.initializers()); + ExpressionResult expressionResult = resolveExpression(bindExpression); + List initializers = new ArrayList<>(expressionResult.initializers()); List parameterTypes = new ArrayList<>(); - List methodArgs = new ArrayList<>(); - for (Expression arg : args) { + List methodArgs = new ArrayList<>(); + for (BindExpression arg : args) { ExpressionResult argResult = resolveExpression(arg); parameterTypes.add(argResult.type()); methodArgs.add(argResult.value()); @@ -85,10 +86,10 @@ public ExpressionResult resolveExpression(Expression value) { methodArgs.toArray()))); yield new ExpressionResult(valueType, CodeValues.variable(identifier), initializers); } - case Expression.CollectionAccess(Expression expression, Expression key) -> { - ExpressionResult expressionResult = resolveExpression(expression); + case BindExpression.CollectionAccess(BindExpression bindExpression, BindExpression key) -> { + ExpressionResult expressionResult = resolveExpression(bindExpression); ExpressionResult keyResult = resolveExpression(key); - List initializers = new ArrayList<>(); + List initializers = new ArrayList<>(); initializers.addAll(expressionResult.initializers()); initializers.addAll(keyResult.initializers()); Class bindingsClass = typeResolver.resolve(BINDINGS_CLASS_NAME); @@ -103,39 +104,44 @@ public ExpressionResult resolveExpression(Expression value) { keyResult.value()))); yield new ExpressionResult(valueType, CodeValues.variable(identifier), initializers); } - case Expression.Add(Expression left, Expression right) -> + case BindExpression.Add(BindExpression left, BindExpression right) -> computeExpressionWithMethod(left, right, "add", "concat"); - case Expression.Subtract(Expression left, Expression right) -> + case BindExpression.Subtract(BindExpression left, BindExpression right) -> computeExpressionWithMethod(left, right, "subtract"); - case Expression.Multiply(Expression left, Expression right) -> + case BindExpression.Multiply(BindExpression left, BindExpression right) -> computeExpressionWithMethod(left, right, "multiply"); - case Expression.Divide(Expression left, Expression right) -> + case BindExpression.Divide(BindExpression left, BindExpression right) -> computeExpressionWithMethod(left, right, "divide"); - case Expression.GreaterThan(Expression left, Expression right) -> + case BindExpression.GreaterThan(BindExpression left, BindExpression right) -> computeExpressionWithMethod(left, right, "greaterThan"); - case Expression.GreaterThanEqual(Expression left, Expression right) -> + case BindExpression.GreaterThanEqual(BindExpression left, BindExpression right) -> computeExpressionWithMethod(left, right, "greaterThanOrEqualTo", "greaterThanOrEqual"); - case Expression.LessThan(Expression left, Expression right) -> + case BindExpression.LessThan(BindExpression left, BindExpression right) -> computeExpressionWithMethod(left, right, "lessThan"); - case Expression.LessThanEqual(Expression left, Expression right) -> + case BindExpression.LessThanEqual(BindExpression left, BindExpression right) -> computeExpressionWithMethod(left, right, "lessThanOrEqualTo", "lessThanOrEqual"); - case Expression.Equal(Expression left, Expression right) -> + case BindExpression.Equal(BindExpression left, BindExpression right) -> computeExpressionWithMethod(left, right, "isEqualTo", "equal"); - case Expression.NotEqual(Expression left, Expression right) -> + case BindExpression.NotEqual(BindExpression left, BindExpression right) -> computeExpressionWithMethod(left, right, "isNotEqualTo", "notEqual"); - case Expression.And(Expression left, Expression right) -> computeExpressionWithMethod(left, right, "and"); - case Expression.Or(Expression left, Expression right) -> computeExpressionWithMethod(left, right, "or"); - case Expression.Invert(Expression expression) -> computeExpressionWithMethod(expression, "not"); - case Expression.Negate(Expression expression) -> computeExpressionWithMethod(expression, "negate"); - case Expression.Modulo ignored -> + case BindExpression.And(BindExpression left, BindExpression right) -> + computeExpressionWithMethod(left, right, "and"); + case BindExpression.Or(BindExpression left, BindExpression right) -> + computeExpressionWithMethod(left, right, "or"); + case BindExpression.Invert(BindExpression bindExpression) -> + computeExpressionWithMethod(bindExpression, "not"); + case BindExpression.Negate(BindExpression bindExpression) -> + computeExpressionWithMethod(bindExpression, "negate"); + case BindExpression.Modulo ignored -> throw new UnsupportedOperationException("Modulo operation in expression not supported"); }; } - private ExpressionResult computeExpressionWithMethod(Expression left, Expression right, String... methodNames) { + private ExpressionResult computeExpressionWithMethod(BindExpression left, BindExpression right, + String... methodNames) { ExpressionResult leftResult = resolveExpression(left); ExpressionResult rightResult = resolveExpression(right); - List initializers = new ArrayList<>(); + List initializers = new ArrayList<>(); initializers.addAll(leftResult.initializers()); initializers.addAll(rightResult.initializers()); @@ -175,9 +181,9 @@ private ExpressionResult computeExpressionWithMethod(Expression left, Expression "Cannot %s %s and %s".formatted(String.join(" or ", methodNames), left, right)); } - private ExpressionResult computeExpressionWithMethod(Expression value, String methodName) { + private ExpressionResult computeExpressionWithMethod(BindExpression value, String methodName) { ExpressionResult result = resolveExpression(value); - List initializers = new ArrayList<>(result.initializers()); + List initializers = new ArrayList<>(result.initializers()); Method directMethod = methodResolver.findMethod(result.type(), methodName).orElse(null); if (directMethod != null) { diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/resolve/MethodResolver.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/resolve/MethodResolver.java index ba36ba5..4a224f3 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/resolve/MethodResolver.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/resolve/MethodResolver.java @@ -128,9 +128,9 @@ private Optional findMethodWithCacheKey(MethodCacheKey methodCacheKey) { private static Optional findMatchingMethod(String name, int paramCount, Method[] methods) { List matchingMethods = Arrays.stream(methods) + .filter(method -> !method.isBridge()) .filter(method -> method.getName().equals(name)) .filter(method -> method.getParameterCount() == paramCount) - .filter(method -> !method.isBridge()) .toList(); if (matchingMethods.size() > 1) { diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/resolve/ValueResolver.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/resolve/ValueResolver.java index b181ef7..d9308ed 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/resolve/ValueResolver.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/resolve/ValueResolver.java @@ -1,10 +1,11 @@ package io.github.sheikah45.fx2j.processor.internal.resolve; -import io.github.sheikah45.fx2j.parser.property.Expression; +import io.github.sheikah45.fx2j.parser.property.BindExpression; import io.github.sheikah45.fx2j.parser.property.Value; import io.github.sheikah45.fx2j.processor.FxmlProcessor; -import io.github.sheikah45.fx2j.processor.internal.code.CodeValue; import io.github.sheikah45.fx2j.processor.internal.code.CodeValues; +import io.github.sheikah45.fx2j.processor.internal.code.Expression; +import io.github.sheikah45.fx2j.processor.internal.code.StatementExpression; import io.github.sheikah45.fx2j.processor.internal.model.NamedArgValue; import java.lang.reflect.InvocationTargetException; @@ -31,7 +32,7 @@ public class ValueResolver { this.nameResolver = nameResolver; } - public CodeValue.Expression coerceDefaultValue(NamedArgValue namedArgValue) { + public Expression coerceDefaultValue(NamedArgValue namedArgValue) { String defaultValue = namedArgValue.defaultValue(); if (defaultValue.isBlank()) { Object typeDefault = DEFAULTS_MAP.get(namedArgValue.parameterType()); @@ -44,7 +45,7 @@ public CodeValue.Expression coerceDefaultValue(NamedArgValue namedArgValue) { return resolveCodeValue(namedArgValue.parameterType(), defaultValue); } - public CodeValue.Expression resolveCodeValue(Type valueType, String value) { + public Expression resolveCodeValue(Type valueType, String value) { if (typeResolver.isAssignableFrom(char.class, valueType) || typeResolver.isAssignableFrom(Character.class, valueType)) { if (value.length() != 1) { @@ -63,7 +64,7 @@ public CodeValue.Expression resolveCodeValue(Type valueType, String value) { Object[] arrayValues = Arrays.stream(value.split(",")) .map(componentString -> resolveCodeValue(componentType, componentString)) - .toArray(CodeValue.Expression[]::new); + .toArray(Expression[]::new); return CodeValues.array(componentType, arrayValues); } @@ -93,7 +94,7 @@ public CodeValue.Expression resolveCodeValue(Type valueType, String value) { throw new UnsupportedOperationException("Cannot create type %s from %s".formatted(valueType, value)); } - public CodeValue.Expression resolveCodeValue(Method staticMethod, String valueString) + public Expression resolveCodeValue(Method staticMethod, String valueString) throws IllegalAccessException, InvocationTargetException { if (!Modifier.isStatic(staticMethod.getModifiers())) { throw new IllegalArgumentException("Provided method is not static"); @@ -123,7 +124,7 @@ public CodeValue.Expression resolveCodeValue(Method staticMethod, String valueSt }; } - public CodeValue.Expression resolveCodeValue(Type valueType, Value value) { + public Expression resolveCodeValue(Type valueType, Value value) { return switch (value) { case Value.Empty() -> CodeValues.nullValue(); case Value.Reference(String reference) -> { @@ -135,14 +136,14 @@ public CodeValue.Expression resolveCodeValue(Type valueType, Value value) { yield CodeValues.variable(reference); } case Value.Resource(String resource) when valueType == String.class -> - new CodeValue.MethodCall(CodeValues.variable(FxmlProcessor.RESOURCES_NAME), "getString", - List.of(CodeValues.literal(resource))); + new StatementExpression.MethodCall(CodeValues.variable(FxmlProcessor.RESOURCES_NAME), "getString", + List.of(CodeValues.literal(resource))); case Value.Literal(String val) -> resolveCodeValue(valueType, val); case Value.Location ignored -> throw new UnsupportedOperationException("Location resolution not yet supported"); case Value.Resource ignored -> throw new UnsupportedOperationException( "Non string resource types not supported"); - case Expression ignored -> + case BindExpression ignored -> throw new UnsupportedOperationException("Cannot resolve an expression to a code value"); }; } diff --git a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/CodeBlockUtils.java b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/CodeBlockConverter.java similarity index 56% rename from fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/CodeBlockUtils.java rename to fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/CodeBlockConverter.java index 68417ab..5539f8f 100644 --- a/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/CodeBlockUtils.java +++ b/fx2j-processor/src/main/java/io/github/sheikah45/fx2j/processor/internal/utils/CodeBlockConverter.java @@ -7,48 +7,55 @@ import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeVariableName; import com.squareup.javapoet.WildcardTypeName; -import io.github.sheikah45.fx2j.processor.internal.code.CodeType; -import io.github.sheikah45.fx2j.processor.internal.code.CodeValue; +import io.github.sheikah45.fx2j.processor.internal.code.Block; +import io.github.sheikah45.fx2j.processor.internal.code.TypeValue; +import io.github.sheikah45.fx2j.processor.internal.code.Declarator; +import io.github.sheikah45.fx2j.processor.internal.code.Expression; +import io.github.sheikah45.fx2j.processor.internal.code.Literal; +import io.github.sheikah45.fx2j.processor.internal.code.Parameter; +import io.github.sheikah45.fx2j.processor.internal.code.Resource; +import io.github.sheikah45.fx2j.processor.internal.code.Statement; +import io.github.sheikah45.fx2j.processor.internal.code.StatementExpression; import java.util.List; -public class CodeBlockUtils { +public class CodeBlockConverter { - private static TypeName convertToTypeName(CodeType type) { + private static TypeName convertToTypeName(TypeValue type) { return switch (type) { - case CodeType.Raw.Primitive(String primitive) -> convertPrimitiveToTypeName(primitive); - case CodeType.Raw.Array array -> convertToArrayName(array); - case CodeType.Raw raw -> convertToClassName(raw); - case CodeType.Parameterized(CodeType.Raw rawType, List arguments) -> + case TypeValue.Raw.Primitive(String primitive) -> convertPrimitiveToTypeName(primitive); + case TypeValue.Raw.Array array -> convertToArrayName(array); + case TypeValue.Raw raw -> convertToClassName(raw); + case TypeValue.Parameterized(TypeValue.Raw rawType, List arguments) -> ParameterizedTypeName.get(convertToClassName(rawType), arguments.stream() - .map(CodeBlockUtils::convertToTypeName) + .map(CodeBlockConverter::convertToTypeName) .toArray(TypeName[]::new)); - case CodeType.Variable(String name, List upperBounds) -> TypeVariableName.get(name, - upperBounds.stream() - .map(CodeBlockUtils::convertToTypeName) + case TypeValue.Variable(String name, List upperBounds) -> TypeVariableName.get(name, + upperBounds.stream() + .map(CodeBlockConverter::convertToTypeName) .toArray( TypeName[]::new)); - case CodeType.Wildcard(List lowerBounds, List upperBounds) when lowerBounds.size() == - 1 && - upperBounds.isEmpty() -> + case TypeValue.Wildcard(List lowerBounds, List upperBounds) when lowerBounds.size() == + 1 && + upperBounds.isEmpty() -> WildcardTypeName.supertypeOf(convertToTypeName(lowerBounds.getFirst())); - case CodeType.Wildcard(List lowerBounds, List upperBounds) when upperBounds.size() == - 1 && - lowerBounds.isEmpty() -> + case TypeValue.Wildcard(List lowerBounds, List upperBounds) when upperBounds.size() == + 1 && + lowerBounds.isEmpty() -> WildcardTypeName.subtypeOf(convertToTypeName(upperBounds.getFirst())); - case CodeType.Wildcard ignored -> throw new UnsupportedOperationException( + case TypeValue.Wildcard ignored -> throw new UnsupportedOperationException( "Cannot generate wildcard typeName with multiple bound parameters"); }; } - private static ClassName convertToClassName(CodeType.Raw rawType) { + private static ClassName convertToClassName(TypeValue.Raw rawType) { return switch (rawType) { - case CodeType.Raw.Array ignored -> + case TypeValue.Raw.Array ignored -> throw new UnsupportedOperationException("Cannot convert array type to ClassName"); - case CodeType.Raw.Primitive ignored -> + case TypeValue.Raw.Primitive ignored -> throw new UnsupportedOperationException("Cannot convert primitive type to ClassName"); - case CodeType.Raw.TopLevel(String packageName, String simpleName) -> ClassName.get(packageName, simpleName); - case CodeType.Raw.Nested(CodeType.Raw ownerType, String simpleName) -> { + case TypeValue.Raw.Top(String packageName, String simpleName) -> ClassName.get(packageName, simpleName); + case TypeValue.Raw.Nested(TypeValue.Raw ownerType, String simpleName) -> { ClassName className = convertToClassName(ownerType); if (className.enclosingClassName() != null) { yield ClassName.get(className.enclosingClassName().packageName(), @@ -61,7 +68,7 @@ private static ClassName convertToClassName(CodeType.Raw rawType) { }; } - private static ArrayTypeName convertToArrayName(CodeType.Raw.Array arrayType) { + private static ArrayTypeName convertToArrayName(TypeValue.Raw.Array arrayType) { return ArrayTypeName.of(convertToTypeName(arrayType.componentType())); } @@ -79,42 +86,42 @@ private static TypeName convertPrimitiveToTypeName(String primitive) { }; } - public static CodeBlock convertExpressionToCodeBlock(CodeValue.Expression codeValue) { + public static CodeBlock convertExpressionToCodeBlock(Expression codeValue) { return switch (codeValue) { - case CodeValue.Literal.Null() -> CodeBlock.of("null"); - case CodeValue.Literal.Bool(boolean value) -> CodeBlock.of("$L", value); - case CodeValue.Literal.Char(char value) -> CodeBlock.of("$L", value); - case CodeValue.Literal.Byte(byte value) -> CodeBlock.of("$L", value); - case CodeValue.Literal.Short(short value) -> CodeBlock.of("$L", value); - case CodeValue.Literal.Int(int value) -> CodeBlock.of("$L", value); - case CodeValue.Literal.Long(long value) -> CodeBlock.of("$L", value); - case CodeValue.Literal.Float(float value) -> CodeBlock.of("$L", value); - case CodeValue.Literal.Double(double value) -> CodeBlock.of("$L", value); - case CodeValue.Literal.Str(String value) -> CodeBlock.of("$S", value); - case CodeValue.Variable(String value) -> CodeBlock.of("$L", value); - case CodeValue.Type(CodeType type) -> CodeBlock.of("$T", convertToTypeName(type)); - case CodeValue.Enum(Enum value) -> CodeBlock.of("$T.$L", value.getDeclaringClass(), value.name()); - case CodeValue.FieldAccess(CodeValue.Expression receiver, String field) -> + case Literal.Null() -> CodeBlock.of("null"); + case Literal.Bool(boolean value) -> CodeBlock.of("$L", value); + case Literal.Char(char value) -> CodeBlock.of("$L", value); + case Literal.Byte(byte value) -> CodeBlock.of("$L", value); + case Literal.Short(short value) -> CodeBlock.of("$L", value); + case Literal.Int(int value) -> CodeBlock.of("$L", value); + case Literal.Long(long value) -> CodeBlock.of("$L", value); + case Literal.Float(float value) -> CodeBlock.of("$L", value); + case Literal.Double(double value) -> CodeBlock.of("$L", value); + case Literal.Str(String value) -> CodeBlock.of("$S", value); + case Expression.Variable(String value) -> CodeBlock.of("$L", value); + case Expression.Type(TypeValue type) -> CodeBlock.of("$T", convertToTypeName(type)); + case Expression.Enum(Enum value) -> CodeBlock.of("$T.$L", value.getDeclaringClass(), value.name()); + case Expression.FieldAccess(Expression receiver, String field) -> CodeBlock.of("$L.$L", convertExpressionToCodeBlock(receiver), field); - case CodeValue.Array.Declared(CodeType componentType, List values) -> { + case Expression.Array.Declared(TypeValue componentType, List values) -> { CodeBlock valuesBlock = values.stream() - .map(CodeBlockUtils::convertExpressionToCodeBlock) + .map(CodeBlockConverter::convertExpressionToCodeBlock) .collect(CodeBlock.joining(", ")); yield CodeBlock.of("new $T[]{$L}", convertToTypeName(componentType), valuesBlock); } - case CodeValue.Array.Sized(CodeType componentType, int size) -> + case Expression.Array.Sized(TypeValue componentType, int size) -> CodeBlock.of("new $T[$L]", componentType, size); - case CodeValue.NewInstance(CodeType.Declarable type, List args) -> { + case StatementExpression.NewInstance(TypeValue.Declarable type, List args) -> { CodeBlock argsBlock = args.stream() - .map(CodeBlockUtils::convertExpressionToCodeBlock) + .map(CodeBlockConverter::convertExpressionToCodeBlock) .collect(CodeBlock.joining(", ")); yield CodeBlock.of("new $T($L)", convertToTypeName(type), argsBlock); } - case CodeValue.Lambda.MethodReference(CodeValue.Expression receiver, String methodName) -> + case Expression.Lambda.MethodReference(Expression receiver, String methodName) -> CodeBlock.of("$L::$L", convertExpressionToCodeBlock(receiver), methodName); - case CodeValue.Lambda.Arrow.Typed( - List parameters, - CodeValue.Block(List statements) + case Expression.Lambda.Arrow.Typed( + List parameters, + Block.Simple(List statements) ) -> { CodeBlock paramBlock = parameters.stream() .map(parameter -> CodeBlock.of("$T $L", @@ -123,89 +130,89 @@ public static CodeBlock convertExpressionToCodeBlock(CodeValue.Expression codeVa .collect(CodeBlock.joining(", ")); yield convertToLambda(paramBlock, statements); } - case CodeValue.Lambda.Arrow.Untyped( - List parameters, CodeValue.Block(List statements) + case Expression.Lambda.Arrow.Untyped( + List parameters, Block.Simple(List statements) ) -> { CodeBlock paramBlock = parameters.stream() .map(parameter -> CodeBlock.of("$L", parameter)) .collect(CodeBlock.joining(", ")); yield convertToLambda(paramBlock, statements); } - case CodeValue.ArrayAccess(CodeValue.Expression receiver, CodeValue.Expression accessor) -> + case Expression.ArrayAccess(Expression receiver, Expression accessor) -> CodeBlock.of("$L[$L]", convertExpressionToCodeBlock(receiver), convertExpressionToCodeBlock(accessor)); - case CodeValue.StatementExpression statementExpression -> + case StatementExpression statementExpression -> convertStatementExpressionToCodeBlock(statementExpression); }; } - private static CodeBlock convertToLambda(CodeBlock paramBlock, List body) { + private static CodeBlock convertToLambda(CodeBlock paramBlock, List body) { if (body.isEmpty()) { return CodeBlock.of("($L) -> {}", paramBlock); } else if (body.size() == 1) { CodeBlock bodyBlock = switch (body.getFirst()) { - case CodeValue.Return.Void() -> CodeBlock.of("{}"); - case CodeValue.Return.Value(CodeValue.Expression value) -> convertExpressionToCodeBlock(value); - case CodeValue.BlockStatement value -> CodeBlock.builder() - .beginControlFlow("") - .add(convertStatementToUnterminatedCodeBlock(value)) - .endControlFlow() - .build(); - case CodeValue.Statement value -> convertStatementToUnterminatedCodeBlock(value); + case Statement.Return.Void() -> CodeBlock.of("{}"); + case Statement.Return.Value(Expression value) -> convertExpressionToCodeBlock(value); + case Block value -> CodeBlock.builder() + .beginControlFlow("") + .add(convertStatementToUnterminatedCodeBlock(value)) + .endControlFlow() + .build(); + case Statement value -> convertStatementToUnterminatedCodeBlock(value); }; return CodeBlock.of("($L) -> $L", paramBlock, bodyBlock); } else { CodeBlock bodyBlock = body.stream() - .map(CodeBlockUtils::convertStatementToCodeBlock) + .map(CodeBlockConverter::convertStatementToCodeBlock) .map(codeBlock -> CodeBlock.builder().add("\t").add(codeBlock).build()) .collect(CodeBlock.joining("\n")); return CodeBlock.builder().beginControlFlow("($L) -> ", paramBlock).add(bodyBlock).endControlFlow().build(); } } - public static CodeBlock convertStatementToCodeBlock(CodeValue.Statement codeValue) { + public static CodeBlock convertStatementToCodeBlock(Statement codeValue) { CodeBlock codeBlock = convertStatementToUnterminatedCodeBlock(codeValue); return switch (codeValue) { - case CodeValue.BlockStatement ignored -> CodeBlock.builder().add(codeBlock).build(); - case CodeValue.LineBreak ignored -> CodeBlock.of("\n"); + case Block ignored -> CodeBlock.builder().add(codeBlock).build(); + case Statement.LineBreak ignored -> CodeBlock.of("\n"); default -> CodeBlock.builder().add(codeBlock).add(";\n").build(); }; } - private static CodeBlock convertStatementToUnterminatedCodeBlock(CodeValue.Statement codeValue) { + private static CodeBlock convertStatementToUnterminatedCodeBlock(Statement codeValue) { return switch (codeValue) { - case CodeValue.Declaration(CodeType.Declarable type, List declarators) -> { + case Statement.Declaration(TypeValue.Declarable type, List declarators) -> { CodeBlock declaratorsBlock = declarators.stream().map(declarator -> switch (declarator) { - case CodeValue.Assignment(CodeValue.Assignable receiver, CodeValue.Expression initializer) -> + case StatementExpression.Assignment(Expression.Assignable receiver, Expression initializer) -> CodeBlock.of("$L = $L", convertExpressionToCodeBlock(receiver), convertExpressionToCodeBlock(initializer)); - case CodeValue.Variable(String identifier) -> CodeBlock.of("$L", identifier); + case Expression.Variable(String identifier) -> CodeBlock.of("$L", identifier); }).collect(CodeBlock.joining(", ")); yield CodeBlock.of("$T $L", convertToTypeName(type), declaratorsBlock); } - case CodeValue.Assignment(CodeValue.Assignable identifier, CodeValue.Expression value) -> + case StatementExpression.Assignment(Expression.Assignable identifier, Expression value) -> CodeBlock.of("$L = $L", convertExpressionToCodeBlock(identifier), convertExpressionToCodeBlock(value)); - case CodeValue.Return.Value(CodeValue.Expression value) -> + case Statement.Return.Value(Expression value) -> CodeBlock.of("return $L", convertExpressionToCodeBlock(value)); - case CodeValue.Return.Void() -> CodeBlock.of("return"); - case CodeValue.Continue.Unlabeled() -> CodeBlock.of("continue"); - case CodeValue.Continue.Labeled(String label) -> CodeBlock.of("continue $L", label); - case CodeValue.Break.Unlabeled() -> CodeBlock.of("break"); - case CodeValue.Break.Labeled(String label) -> CodeBlock.of("break $L", label); - case CodeValue.Throw(CodeValue.Expression exception) -> + case Statement.Return.Void() -> CodeBlock.of("return"); + case Statement.Continue.Unlabeled() -> CodeBlock.of("continue"); + case Statement.Continue.Labeled(String label) -> CodeBlock.of("continue $L", label); + case Statement.Break.Unlabeled() -> CodeBlock.of("break"); + case Statement.Break.Labeled(String label) -> CodeBlock.of("break $L", label); + case Statement.Throw(Expression exception) -> CodeBlock.of("throw $L", convertExpressionToCodeBlock(exception)); - case CodeValue.LineBreak() -> CodeBlock.of("\n"); - case CodeValue.For.Loop( - CodeValue.Expression initializer, CodeValue.Expression termination, - List incrementors, - CodeValue.Block(List statements) + case Statement.LineBreak() -> CodeBlock.of("\n"); + case Block.For.Loop( + Expression initializer, Expression termination, + List incrementors, + Block.Simple(List statements) ) -> { CodeBlock incrementorBlock = incrementors.stream() - .map(CodeBlockUtils::convertExpressionToCodeBlock) + .map(CodeBlockConverter::convertExpressionToCodeBlock) .collect(CodeBlock.joining(", ")); CodeBlock bodyBlock = statements.stream() - .map(CodeBlockUtils::convertStatementToCodeBlock) + .map(CodeBlockConverter::convertStatementToCodeBlock) .collect(CodeBlock.joining("")); yield CodeBlock.builder() @@ -215,12 +222,12 @@ private static CodeBlock convertStatementToUnterminatedCodeBlock(CodeValue.State .endControlFlow() .build(); } - case CodeValue.For.Each( - CodeValue.Parameter(CodeType.Declarable type, String identifier), CodeValue.Expression parameters, - CodeValue.Block(List statements) + case Block.For.Each( + Parameter(TypeValue.Declarable type, String identifier), Expression parameters, + Block.Simple(List statements) ) -> { CodeBlock bodyBlock = statements.stream() - .map(CodeBlockUtils::convertStatementToCodeBlock) + .map(CodeBlockConverter::convertStatementToCodeBlock) .collect(CodeBlock.joining("")); yield CodeBlock.builder() @@ -230,16 +237,16 @@ private static CodeBlock convertStatementToUnterminatedCodeBlock(CodeValue.State .endControlFlow() .build(); } - case CodeValue.Try( - List resources, CodeValue.Block(List statements), - List catchBlocks, - CodeValue.Block(List finallyStatements) + case Block.Try( + List resources, Block.Simple(List statements), + List catchBlocks, + Block.Simple(List finallyStatements) ) -> { CodeBlock resourcesBlock = resources.stream().map(resource -> switch (resource) { - case CodeValue.Variable(String identifier) -> CodeBlock.of("$L", identifier); - case CodeValue.ResourceDeclaration( - CodeType.Declarable type, String identifier, CodeValue.Expression initializer + case Expression.Variable(String identifier) -> CodeBlock.of("$L", identifier); + case Resource.ResourceDeclaration( + TypeValue.Declarable type, String identifier, Expression initializer ) -> CodeBlock.of("$T $L = $L", convertToTypeName(type), identifier, convertExpressionToCodeBlock(initializer)); }).collect(CodeBlock.joining("; ")); @@ -252,22 +259,22 @@ private static CodeBlock convertStatementToUnterminatedCodeBlock(CodeValue.State } CodeBlock bodyBlock = statements.stream() - .map(CodeBlockUtils::convertStatementToCodeBlock) + .map(CodeBlockConverter::convertStatementToCodeBlock) .collect(CodeBlock.joining("")); builder.add(bodyBlock); - for (CodeValue.Catch catchBlock : catchBlocks) { + for (Block.Try.Catch catchBlock : catchBlocks) { CodeBlock exceptionTypesBlock = catchBlock.exceptionTypes() .stream() - .map(CodeBlockUtils::convertToTypeName) + .map(CodeBlockConverter::convertToTypeName) .map(typeName -> CodeBlock.of("$T", typeName)) .collect(CodeBlock.joining(" | ")); CodeBlock catchCodeBlock = catchBlock.body() .statements() .stream() - .map(CodeBlockUtils::convertStatementToCodeBlock) + .map(CodeBlockConverter::convertStatementToCodeBlock) .collect(CodeBlock.joining("")); builder.nextControlFlow("catch ($L $L)", exceptionTypesBlock, catchBlock.identifier()) @@ -277,7 +284,7 @@ private static CodeBlock convertStatementToUnterminatedCodeBlock(CodeValue.State if (!finallyStatements.isEmpty()) { builder.nextControlFlow("finally"); CodeBlock finallyBlock = finallyStatements.stream() - .map(CodeBlockUtils::convertStatementToCodeBlock) + .map(CodeBlockConverter::convertStatementToCodeBlock) .collect(CodeBlock.joining("")); builder.add(finallyBlock); } else { @@ -286,36 +293,36 @@ private static CodeBlock convertStatementToUnterminatedCodeBlock(CodeValue.State yield builder.build(); } - case CodeValue.StatementExpression statementExpression -> convertExpressionToCodeBlock(statementExpression); + case StatementExpression statementExpression -> convertExpressionToCodeBlock(statementExpression); }; } - private static CodeBlock convertStatementExpressionToCodeBlock(CodeValue.StatementExpression statementExpression) { + private static CodeBlock convertStatementExpressionToCodeBlock(StatementExpression statementExpression) { return switch (statementExpression) { - case CodeValue.Assignment(CodeValue.Assignable receiver, CodeValue.Expression value) -> + case StatementExpression.Assignment(Expression.Assignable receiver, Expression value) -> CodeBlock.of("$L = $L", convertExpressionToCodeBlock(receiver), convertExpressionToCodeBlock(value)); - case CodeValue.NewInstance(CodeType.Declarable type, List args) -> { + case StatementExpression.NewInstance(TypeValue.Declarable type, List args) -> { CodeBlock argsBlock = args.stream() - .map(CodeBlockUtils::convertExpressionToCodeBlock) + .map(CodeBlockConverter::convertExpressionToCodeBlock) .collect(CodeBlock.joining(", ")); yield CodeBlock.of("new $T($L)", convertToTypeName(type), argsBlock); } - case CodeValue.MethodCall( - CodeValue.Expression receiver, String methodName, List args + case StatementExpression.MethodCall( + Expression receiver, String methodName, List args ) -> { CodeBlock argsBlock = args.stream() - .map(CodeBlockUtils::convertExpressionToCodeBlock) + .map(CodeBlockConverter::convertExpressionToCodeBlock) .collect(CodeBlock.joining(", ")); yield CodeBlock.of("$L.$L($L)", convertExpressionToCodeBlock(receiver), methodName, argsBlock); } - case CodeValue.PostDecrement(CodeValue.Assignable receiver) -> + case StatementExpression.PostDecrement(Expression.Assignable receiver) -> CodeBlock.of("$L--", convertExpressionToCodeBlock(receiver)); - case CodeValue.PostIncrement(CodeValue.Assignable receiver) -> + case StatementExpression.PostIncrement(Expression.Assignable receiver) -> CodeBlock.of("$L++", convertExpressionToCodeBlock(receiver)); - case CodeValue.PreDecrement(CodeValue.Assignable receiver) -> + case StatementExpression.PreDecrement(Expression.Assignable receiver) -> CodeBlock.of("--$L", convertExpressionToCodeBlock(receiver)); - case CodeValue.PreIncrement(CodeValue.Assignable receiver) -> + case StatementExpression.PreIncrement(Expression.Assignable receiver) -> CodeBlock.of("++$L", convertExpressionToCodeBlock(receiver)); }; } diff --git a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/internal/ObjectNodeProcessorTest.java b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/internal/ObjectNodeProcessorTest.java new file mode 100644 index 0000000..d623e81 --- /dev/null +++ b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/internal/ObjectNodeProcessorTest.java @@ -0,0 +1,1106 @@ +package io.github.sheikah45.fx2j.processor.internal; + +import io.github.sheikah45.fx2j.parser.attribute.EventHandlerAttribute; +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.ConstantElement; +import io.github.sheikah45.fx2j.parser.element.CopyElement; +import io.github.sheikah45.fx2j.parser.element.ElementContent; +import io.github.sheikah45.fx2j.parser.element.FactoryElement; +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.StaticPropertyElement; +import io.github.sheikah45.fx2j.parser.element.ValueElement; +import io.github.sheikah45.fx2j.parser.property.BindExpression; +import io.github.sheikah45.fx2j.parser.property.Handler; +import io.github.sheikah45.fx2j.parser.property.Value; +import io.github.sheikah45.fx2j.processor.FxmlProcessor; +import io.github.sheikah45.fx2j.processor.internal.code.CodeValues; +import io.github.sheikah45.fx2j.processor.internal.code.Expression; +import io.github.sheikah45.fx2j.processor.internal.code.Statement; +import io.github.sheikah45.fx2j.processor.internal.code.StatementExpression; +import io.github.sheikah45.fx2j.processor.internal.model.ObjectNodeCode; +import io.github.sheikah45.fx2j.processor.internal.resolve.ResolverContainer; +import io.github.sheikah45.fx2j.processor.testcontroller.ChangeHandlerController; +import io.github.sheikah45.fx2j.processor.testcontroller.EventHandlerMethodController; +import io.github.sheikah45.fx2j.processor.testcontroller.PublicController; +import io.github.sheikah45.fx2j.processor.testutils.CopyObject; +import javafx.beans.property.DoubleProperty; +import javafx.geometry.Insets; +import javafx.geometry.VPos; +import javafx.scene.control.Button; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import org.junit.jupiter.api.Test; +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.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Execution(ExecutionMode.CONCURRENT) +class ObjectNodeProcessorTest { + + private static final ElementContent EMPTY_CONTENT = new ElementContent<>(List.of(), List.of(), + new Value.Empty()); + private static final Path EMPTY_PATH = Path.of(""); + + private final ResolverContainer resolverContainer = ResolverContainer.from(Set.of(), getClass().getClassLoader()); + + @Test + void testRootInitialization() { + Class objectClass = Integer.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor( + new RootElement(objectClass.getCanonicalName(), EMPTY_CONTENT), Object.class, resolverContainer, + EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + Expression.Variable variable = CodeValues.variable(FxmlProcessor.BUILDER_PROVIDED_ROOT_NAME); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertTrue(initializers.isEmpty()); + } + + @Test + void testReferenceInitialization() { + Class objectClass = Integer.class; + resolverContainer.getNameResolver().storeIdType("obj", objectClass); + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new ReferenceElement("obj", EMPTY_CONTENT), Object.class, + resolverContainer, EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + Expression.Variable variable = CodeValues.variable("obj"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertTrue(initializers.isEmpty()); + } + + @Test + void testIncludeInitialization() { + ObjectNodeCode nodeCode = new ObjectNodeProcessor( + new IncludeElement(Path.of("constant.fxml"), null, StandardCharsets.UTF_8, EMPTY_CONTENT), Object.class, + resolverContainer, Path.of("src/test/resources/fxml/process/include.fxml"), + Path.of("src/test/resources"), "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("integer0"); + Class objectClass = Integer.class; + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(3, initializers.size()); + Statement builderDeclaration = initializers.getFirst(); + String builderClassName = "fxml.process.ConstantBuilder"; + String builderIdentifier = "integer0Builder"; + assertEquals( + CodeValues.declaration(builderClassName, builderIdentifier, CodeValues.newInstance(builderClassName)), + builderDeclaration); + Statement build = initializers.get(1); + assertEquals(CodeValues.methodCall(builderIdentifier, "build", null, null, + CodeValues.variable(FxmlProcessor.RESOURCES_NAME), + CodeValues.variable(FxmlProcessor.CONTROLLER_FACTORY_NAME)), build); + Statement declaration = initializers.getLast(); + assertEquals(CodeValues.declaration(objectClass, variable, CodeValues.methodCall(builderIdentifier, "getRoot")), + declaration); + } + + @Test + void testIncludeInitializationWithController() { + ObjectNodeCode nodeCode = new ObjectNodeProcessor( + new IncludeElement(Path.of("public-controller.fxml"), null, StandardCharsets.UTF_8, + new ElementContent<>(List.of(new IdAttribute("pane")), List.of(), + new Value.Empty())), Object.class, resolverContainer, + Path.of("src/test/resources/fxml/controller/include.fxml"), Path.of("src/test/resources"), + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("pane"); + Class objectClass = AnchorPane.class; + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(5, initializers.size()); + Statement builderDeclaration = initializers.getFirst(); + String builderClassName = "fxml.controller.PublicControllerBuilder"; + String builderIdentifier = "paneBuilder"; + assertEquals( + CodeValues.declaration(builderClassName, builderIdentifier, CodeValues.newInstance(builderClassName)), + builderDeclaration); + Statement build = initializers.get(1); + assertEquals(CodeValues.methodCall(builderIdentifier, "build", null, null, + CodeValues.variable(FxmlProcessor.RESOURCES_NAME), + CodeValues.variable(FxmlProcessor.CONTROLLER_FACTORY_NAME)), build); + Statement declaration = initializers.get(2); + assertEquals(CodeValues.declaration(objectClass, variable, CodeValues.methodCall(builderIdentifier, "getRoot")), + declaration); + Statement controllerDeclaration = initializers.get(3); + assertEquals(CodeValues.declaration(PublicController.class, "paneController", + CodeValues.methodCall(builderIdentifier, "getController")), + controllerDeclaration); + } + + @Test + void testCopyInitialization() { + Class objectClass = CopyObject.class; + resolverContainer.getNameResolver().storeIdType("obj", objectClass); + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new CopyElement("obj", EMPTY_CONTENT), Object.class, + resolverContainer, EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("objCopy"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(1, initializers.size()); + Statement statement = initializers.getFirst(); + assertEquals(CodeValues.declaration(objectClass, variable, + CodeValues.newInstance(objectClass, CodeValues.variable("obj"))), + statement); + } + + @Test + void testFactoryInitialization() { + Class objectClass = List.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor( + new FactoryElement(objectClass.getCanonicalName(), "of", EMPTY_CONTENT), Object.class, + resolverContainer, EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("list0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(1, initializers.size()); + Statement statement = initializers.getFirst(); + assertEquals(CodeValues.declaration(objectClass, variable, CodeValues.methodCall(objectClass, "of")), + statement); + } + + @Test + void testConstantInitialization() { + ObjectNodeCode nodeCode = new ObjectNodeProcessor( + new ConstantElement("java.lang.Double", "MAX_EXPONENT", EMPTY_CONTENT), Object.class, resolverContainer, + EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("int0"); + Class objectClass = int.class; + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(1, initializers.size()); + Statement statement = initializers.getFirst(); + assertEquals( + CodeValues.declaration(objectClass, variable, CodeValues.fieldAccess(Double.class, "MAX_EXPONENT")), + statement); + } + + @Test + void testValueInitialization() { + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new ValueElement("java.lang.Double", "1", EMPTY_CONTENT), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("double0"); + Class objectClass = double.class; + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(1, initializers.size()); + Statement statement = initializers.getFirst(); + assertEquals(CodeValues.declaration(objectClass, variable, CodeValues.literal(1d)), statement); + } + + @Test + void testValueInitializationString() { + Class objectClass = String.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor( + new ValueElement(objectClass.getCanonicalName(), "1", EMPTY_CONTENT), Object.class, resolverContainer, + EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("string0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(1, initializers.size()); + Statement statement = initializers.getFirst(); + assertEquals(CodeValues.declaration(objectClass, variable, CodeValues.literal("1")), statement); + } + + @Test + void testObjectParameterInitialization() { + Class objectClass = Insets.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new InstancePropertyAttribute( + "top", + new Value.Literal("10")), + new InstancePropertyAttribute( + "bottom", + new Value.Empty())), + List.of(new InstancePropertyElement( + "left", + new ElementContent<>( + List.of(), + List.of(), + new Value.Literal( + "20"))), + new InstancePropertyElement( + "right", + new ElementContent<>( + List.of(), + List.of(new ValueElement( + Double.class.getCanonicalName(), + "30", + EMPTY_CONTENT)), + new Value.Empty()))), + new Value.Empty())), Object.class, + resolverContainer, EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("insets0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(3, initializers.size()); + Statement lineBreak = initializers.getFirst(); + assertInstanceOf(Statement.LineBreak.class, lineBreak); + Statement rightDeclaration = initializers.get(1); + Expression.Variable rightVariable = CodeValues.variable("double0"); + assertEquals(CodeValues.declaration(double.class, rightVariable, 30d), rightDeclaration); + Statement statement = initializers.getLast(); + assertEquals(CodeValues.declaration(objectClass, variable, + CodeValues.newInstance(objectClass, 10d, rightVariable, 0d, 20d)), + statement); + } + + @Test + void testObjectParameterInitializationEmptyElement() { + Class objectClass = Insets.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new InstancePropertyElement( + "top", + new ElementContent<>( + List.of(), + List.of(), + new Value.Empty()))), + new Value.Empty())), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("insets0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(1, initializers.size()); + Statement statement = initializers.getLast(); + assertEquals(CodeValues.declaration(objectClass, variable, CodeValues.newInstance(objectClass, 0d, 0d, 0d, 0d)), + statement); + } + + @Test + void testObjectDefaultInitialization() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor( + new InstanceElement(objectClass.getCanonicalName(), EMPTY_CONTENT), Object.class, resolverContainer, + EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(1, initializers.size()); + Statement statement = initializers.getFirst(); + assertEquals(CodeValues.declaration(objectClass, variable, CodeValues.newInstance(objectClass)), statement); + } + + @Test + void testDefaultValue() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), List.of(), + new Value.Literal( + "test"))), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + assertEquals(CodeValues.methodCall(variable, "setText", "test"), statement); + } + + @Test + void testDefaultElement() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new ValueElement( + String.class.getCanonicalName(), + "test", + EMPTY_CONTENT)), + new Value.Empty())), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(4, initializers.size()); + Statement lineBreak = initializers.get(1); + assertInstanceOf(Statement.LineBreak.class, lineBreak); + Statement textNode = initializers.get(2); + Expression.Variable textVariable = CodeValues.variable("string0"); + assertEquals(CodeValues.declaration(String.class, textVariable, "test"), textNode); + Statement statement = initializers.getLast(); + assertEquals(CodeValues.methodCall(variable, "setText", textVariable), statement); + } + + @Test + void testCollectionDefaults() { + Class objectClass = ArrayList.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new ValueElement( + String.class.getCanonicalName(), + "test", + EMPTY_CONTENT)), + new Value.Literal( + "temp"))), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("arrayList0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(5, initializers.size()); + Statement lineBreak = initializers.get(1); + assertInstanceOf(Statement.LineBreak.class, lineBreak); + Statement stringNode = initializers.get(2); + Expression.Variable stringVariable = CodeValues.variable("string0"); + assertEquals(CodeValues.declaration(String.class, stringVariable, "test"), stringNode); + Statement testAdd = initializers.get(3); + assertEquals(CodeValues.methodCall(variable, "add", stringVariable), testAdd); + Statement tempAdd = initializers.getLast(); + assertEquals(CodeValues.methodCall(variable, "add", "temp"), tempAdd); + } + + @Test + void testPropertyCollectionPropertyElement() { + Class objectClass = VBox.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new InstancePropertyElement( + "children", + new ElementContent<>( + List.of(), + List.of(new InstanceElement( + Button.class.getCanonicalName(), + EMPTY_CONTENT)), + new Value.Empty()))), + new Value.Empty())), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("vBox0"); + StatementExpression.MethodCall getChildren = CodeValues.methodCall(variable, "getChildren"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(4, initializers.size()); + Statement lineBreak = initializers.get(1); + assertInstanceOf(Statement.LineBreak.class, lineBreak); + Expression.Variable buttonVariable = CodeValues.variable("button0"); + Statement buttonDeclaration = initializers.get(2); + assertEquals(CodeValues.declaration(Button.class, buttonVariable, CodeValues.newInstance(Button.class)), + buttonDeclaration); + Statement put1 = initializers.getLast(); + assertEquals(CodeValues.methodCall(getChildren, "add", buttonVariable), put1); + } + + @Test + void testPropertyCollectionPropertyElementValue() { + Class objectClass = VBox.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new InstancePropertyElement( + "styleClass", + new ElementContent<>( + List.of(), + List.of(), + new Value.Literal( + "child,dog")))), + new Value.Empty())), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("vBox0"); + StatementExpression.MethodCall getStyleClass = CodeValues.methodCall(variable, "getStyleClass"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(3, initializers.size()); + Statement add1 = initializers.get(1); + assertEquals(CodeValues.methodCall(getStyleClass, "add", "child"), add1); + Statement add2 = initializers.getLast(); + assertEquals(CodeValues.methodCall(getStyleClass, "add", "dog"), add2); + } + + @Test + void testPropertyCollectionPropertyAttribute() { + Class objectClass = VBox.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new InstancePropertyAttribute( + "styleClass", + new Value.Literal( + "child,dog"))), + List.of(), new Value.Empty())), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("vBox0"); + StatementExpression.MethodCall getStyleClass = CodeValues.methodCall(variable, "getStyleClass"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(3, initializers.size()); + Statement add1 = initializers.get(1); + assertEquals(CodeValues.methodCall(getStyleClass, "add", "child"), add1); + Statement add2 = initializers.getLast(); + assertEquals(CodeValues.methodCall(getStyleClass, "add", "dog"), add2); + } + + @Test + void testMapInitialization() { + Class objectClass = HashMap.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new InstancePropertyAttribute( + "val0", + new Value.Literal("0"))), + List.of(new InstancePropertyElement( + "val1", + new ElementContent<>( + List.of(), + List.of(), + new Value.Literal( + "1"))), + new InstancePropertyElement( + "val2", + new ElementContent<>( + List.of(), + List.of(new ValueElement( + String.class.getCanonicalName(), + "2", + EMPTY_CONTENT)), + new Value.Empty()))), + new Value.Empty())), Object.class, + resolverContainer, EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("hashMap0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(6, initializers.size()); + Statement put0 = initializers.get(1); + assertEquals(CodeValues.methodCall(variable, "put", "val0", "0"), put0); + Statement put1 = initializers.get(2); + assertEquals(CodeValues.methodCall(variable, "put", "val1", "1"), put1); + Statement lineBreak = initializers.get(3); + assertInstanceOf(Statement.LineBreak.class, lineBreak); + Statement stringNode = initializers.get(4); + Expression.Variable stringVariable = CodeValues.variable("string0"); + assertEquals(CodeValues.declaration(String.class, stringVariable, "2"), stringNode); + Statement put2 = initializers.getLast(); + assertEquals(CodeValues.methodCall(variable, "put", "val2", stringVariable), put2); + } + + @Test + void testPropertyMapProperties() { + Class objectClass = Pane.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new InstancePropertyElement( + "properties", + new ElementContent<>( + List.of(new InstancePropertyAttribute( + "val0", + new Value.Literal( + "0"))), + List.of(new InstancePropertyElement( + "val1", + new ElementContent<>( + List.of(), + List.of(), + new Value.Literal( + "1"))), + new InstancePropertyElement( + "val2", + new ElementContent<>( + List.of(), + List.of(new ValueElement( + String.class.getCanonicalName(), + "2", + EMPTY_CONTENT)), + new Value.Empty()))), + new Value.Empty()))), + new Value.Empty())), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("pane0"); + StatementExpression.MethodCall getProperties = CodeValues.methodCall(variable, "getProperties"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(6, initializers.size()); + Statement put0 = initializers.get(1); + assertEquals(CodeValues.methodCall(getProperties, "put", "val0", "0"), put0); + Statement put1 = initializers.get(2); + assertEquals(CodeValues.methodCall(getProperties, "put", "val1", "1"), put1); + Statement lineBreak = initializers.get(3); + assertInstanceOf(Statement.LineBreak.class, lineBreak); + Statement stringNode = initializers.get(4); + Expression.Variable stringVariable = CodeValues.variable("string0"); + assertEquals(CodeValues.declaration(String.class, stringVariable, "2"), stringNode); + Statement put2 = initializers.getLast(); + assertEquals(CodeValues.methodCall(getProperties, "put", "val2", stringVariable), put2); + } + + @Test + void testObjectInstancePropertyAttribute() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new InstancePropertyAttribute( + "text", new Value.Literal( + "test"))), List.of(), + new Value.Empty())), Object.class, + resolverContainer, EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + assertEquals(CodeValues.methodCall(variable, "setText", "test"), statement); + } + + @Test + void testObjectInstancePropertyElementValue() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new InstancePropertyElement( + "text", + new ElementContent<>( + List.of(), + List.of(), + new Value.Literal( + "test")))), + new Value.Empty())), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + assertEquals(CodeValues.methodCall(variable, "setText", "test"), statement); + } + + @Test + void testObjectInstancePropertyElementElement() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new InstancePropertyElement( + "text", + new ElementContent<>( + List.of(), + List.of(new ValueElement( + String.class.getCanonicalName(), + "test", + EMPTY_CONTENT)), + new Value.Empty()))), + new Value.Empty())), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(4, initializers.size()); + Statement lineBreak = initializers.get(1); + assertInstanceOf(Statement.LineBreak.class, lineBreak); + Statement stringNode = initializers.get(2); + Expression.Variable stringVariable = CodeValues.variable("string0"); + assertEquals(CodeValues.declaration(String.class, stringVariable, "test"), stringNode); + Statement statement = initializers.getLast(); + assertEquals(CodeValues.methodCall(variable, "setText", stringVariable), statement); + } + + @Test + void testObjectInstancePropertyBindExpression() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new InstancePropertyAttribute( + "prefWidth", + new BindExpression.PropertyRead( + new BindExpression.Variable( + "button"), + "minWidth")), + new IdAttribute( + "button")), + List.of(), new Value.Empty())), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(4, initializers.size()); + Statement expressionDeclaration = initializers.get(2); + Expression.Variable propertyVariable = CodeValues.variable("doubleProperty0"); + assertEquals(CodeValues.declaration(DoubleProperty.class, propertyVariable, + CodeValues.methodCall(variable, "minWidthProperty")), + expressionDeclaration); + Statement statement = initializers.get(3); + assertEquals( + CodeValues.methodCall(CodeValues.methodCall(variable, "prefWidthProperty"), "bind", propertyVariable), + statement); + } + + @Test + void testObjectStaticPropertyAttribute() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new StaticPropertyAttribute( + "javafx.scene.layout.GridPane", + "valignment", + new Value.Literal( + "CENTER"))), + List.of(), new Value.Empty())), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + assertEquals(CodeValues.methodCall(GridPane.class, "setValignment", variable, VPos.CENTER), statement); + } + + @Test + void testObjectStaticPropertyElementValue() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new StaticPropertyElement( + "javafx.scene.layout.GridPane", + "valignment", + new ElementContent<>( + List.of(), + List.of(), + new Value.Literal( + "CENTER")))), + new Value.Empty())), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + assertEquals(CodeValues.methodCall(GridPane.class, "setValignment", variable, VPos.CENTER), statement); + } + + @Test + void testObjectStaticPropertyElementElement() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new StaticPropertyElement( + "javafx.scene.layout.GridPane", + "margin", + new ElementContent<>( + List.of(), + List.of(new InstanceElement( + Insets.class.getCanonicalName(), + new ElementContent<>( + List.of(new InstancePropertyAttribute( + "topRightBottomLeft", + new Value.Literal( + "10"))), + List.of(), + new Value.Empty()))), + new Value.Empty()))), + new Value.Empty())), + Object.class, resolverContainer, EMPTY_PATH, EMPTY_PATH, + "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(4, initializers.size()); + Statement lineBreak = initializers.get(1); + assertInstanceOf(Statement.LineBreak.class, lineBreak); + Statement insetsNode = initializers.get(2); + Expression.Variable insetsVariable = CodeValues.variable("insets0"); + assertEquals(CodeValues.declaration(Insets.class, insetsVariable, CodeValues.newInstance(Insets.class, 10d)), + insetsNode); + Statement statement = initializers.getLast(); + assertEquals(CodeValues.methodCall(GridPane.class, "setMargin", variable, insetsVariable), statement); + } + + @Test + void testObjectEventHandlerAttribute() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new EventHandlerAttribute( + "onAction", + new Handler.Method( + "onActionWithEvent"))), + List.of(), new Value.Empty())), + EventHandlerMethodController.class, resolverContainer, + EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + assertEquals(CodeValues.methodCall(variable, "setOnAction", + CodeValues.methodReference(CodeValues.variable("controller"), + "onActionWithEvent")), statement); + } + + @Test + void testObjectEventHandlerAttributeThrows() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new EventHandlerAttribute( + "onAction", + new Handler.Method( + "onActionWithEventThrow"))), + List.of(), new Value.Empty())), + EventHandlerMethodController.class, resolverContainer, + EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + Expression.Lambda.Arrow lambda = CodeValues.lambdaBuilder() + .untyped(config -> config.parameter("event")) + .body(body -> body.statement(CodeValues.rethrow( + CodeValues.methodCall(CodeValues.variable("controller"), + "onActionWithEventThrow", + CodeValues.variable("event"))))) + .build(); + assertEquals(CodeValues.methodCall(variable, "setOnAction", lambda), statement); + } + + @Test + void testObjectEventHandlerAttributeWithoutEvent() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new EventHandlerAttribute( + "onAction", + new Handler.Method( + "onActionWithoutEvent"))), + List.of(), new Value.Empty())), + EventHandlerMethodController.class, resolverContainer, + EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + Expression.Lambda.Arrow lambda = CodeValues.lambdaBuilder() + .untyped(config -> config.parameter("event")) + .body(body -> body.statement( + CodeValues.methodCall(CodeValues.variable("controller"), + "onActionWithoutEvent"))) + .build(); + assertEquals(CodeValues.methodCall(variable, "setOnAction", lambda), statement); + } + + @Test + void testObjectEventHandlerAttributeWithoutEventThrows() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new EventHandlerAttribute( + "onAction", + new Handler.Method( + "onActionWithoutEventThrow"))), + List.of(), new Value.Empty())), + EventHandlerMethodController.class, resolverContainer, + EMPTY_PATH, EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + Expression.Lambda.Arrow lambda = CodeValues.lambdaBuilder() + .untyped(config -> config.parameter("event")) + .body(body -> body.statement(CodeValues.rethrow( + CodeValues.methodCall(CodeValues.variable("controller"), + "onActionWithoutEventThrow")))) + .build(); + assertEquals(CodeValues.methodCall(variable, "setOnAction", lambda), statement); + } + + @Test + void testObjectPropertyHandlerChangeMethodAttribute() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new EventHandlerAttribute( + "onTextChange", + new Handler.Method( + "onTextChange"))), + List.of(), new Value.Empty())), + ChangeHandlerController.class, resolverContainer, EMPTY_PATH, + EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + StatementExpression.MethodCall textProperty = CodeValues.methodCall(variable, "textProperty"); + assertEquals(CodeValues.methodCall(textProperty, "addListener", + CodeValues.methodReference(CodeValues.variable("controller"), + "onTextChange")), statement); + } + + @Test + void testObjectPropertyHandlerChangeMethodPropertyAttribute() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new InstancePropertyElement( + "text", + new ElementContent<>( + List.of(new EventHandlerAttribute( + "onChange", + new Handler.Method( + "onTextChange"))), + List.of(), + new Value.Empty()))), + new Value.Empty())), + ChangeHandlerController.class, resolverContainer, EMPTY_PATH, + EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + StatementExpression.MethodCall textProperty = CodeValues.methodCall(variable, "textProperty"); + assertEquals(CodeValues.methodCall(textProperty, "addListener", + CodeValues.methodReference(CodeValues.variable("controller"), + "onTextChange")), statement); + } + + @Test + void testObjectPropertyHandlerListChangeMethodAttribute() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new EventHandlerAttribute( + "onStyleClassChange", + new Handler.Method( + "onStyleClassChange"))), + List.of(), new Value.Empty())), + ChangeHandlerController.class, resolverContainer, EMPTY_PATH, + EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + StatementExpression.MethodCall textProperty = CodeValues.methodCall(variable, "getStyleClass"); + assertEquals(CodeValues.methodCall(textProperty, "addListener", + CodeValues.methodReference(CodeValues.variable("controller"), + "onStyleClassChange")), statement); + } + + @Test + void testObjectPropertyHandlerSetChangeMethodAttribute() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new EventHandlerAttribute( + "onPseudoClassStatesChange", + new Handler.Method( + "onPseudoClassStatesChange"))), + List.of(), new Value.Empty())), + ChangeHandlerController.class, resolverContainer, EMPTY_PATH, + EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + StatementExpression.MethodCall textProperty = CodeValues.methodCall(variable, "getPseudoClassStates"); + assertEquals(CodeValues.methodCall(textProperty, "addListener", + CodeValues.methodReference(CodeValues.variable("controller"), + "onPseudoClassStatesChange")), statement); + } + + @Test + void testObjectPropertyHandlerMapChangeMethodAttribute() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>( + List.of(new EventHandlerAttribute( + "onPropertiesChange", + new Handler.Method( + "onPropertiesChange"))), + List.of(), new Value.Empty())), + ChangeHandlerController.class, resolverContainer, EMPTY_PATH, + EMPTY_PATH, "").getNodeCode(); + + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + StatementExpression.MethodCall textProperty = CodeValues.methodCall(variable, "getProperties"); + assertEquals(CodeValues.methodCall(textProperty, "addListener", + CodeValues.methodReference(CodeValues.variable("controller"), + "onPropertiesChange")), statement); + } + + @Test + void testObjectPropertyHandlerListChangeMethodPropertyAttribute() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new InstancePropertyElement( + "styleClass", + new ElementContent<>( + List.of(new EventHandlerAttribute( + "onChange", + new Handler.Method( + "onStyleClassChange"))), + List.of(), + new Value.Empty()))), + new Value.Empty())), + ChangeHandlerController.class, resolverContainer, EMPTY_PATH, + EMPTY_PATH, "").getNodeCode(); + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + StatementExpression.MethodCall textProperty = CodeValues.methodCall(variable, "getStyleClass"); + assertEquals(CodeValues.methodCall(textProperty, "addListener", + CodeValues.methodReference(CodeValues.variable("controller"), + "onStyleClassChange")), statement); + } + + @Test + void testObjectPropertyHandlerSetChangeMethodPropertyAttribute() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new InstancePropertyElement( + "pseudoClassStates", + new ElementContent<>( + List.of(new EventHandlerAttribute( + "onChange", + new Handler.Method( + "onPseudoClassStatesChange"))), + List.of(), + new Value.Empty()))), + new Value.Empty())), + ChangeHandlerController.class, resolverContainer, EMPTY_PATH, + EMPTY_PATH, "").getNodeCode(); + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + StatementExpression.MethodCall textProperty = CodeValues.methodCall(variable, "getPseudoClassStates"); + assertEquals(CodeValues.methodCall(textProperty, "addListener", + CodeValues.methodReference(CodeValues.variable("controller"), + "onPseudoClassStatesChange")), statement); + } + + @Test + void testObjectPropertyHandlerMapChangeMethodPropertyAttribute() { + Class objectClass = Button.class; + ObjectNodeCode nodeCode = new ObjectNodeProcessor(new InstanceElement(objectClass.getCanonicalName(), + new ElementContent<>(List.of(), + List.of(new InstancePropertyElement( + "properties", + new ElementContent<>( + List.of(new EventHandlerAttribute( + "onChange", + new Handler.Method( + "onPropertiesChange"))), + List.of(), + new Value.Empty()))), + new Value.Empty())), + ChangeHandlerController.class, resolverContainer, EMPTY_PATH, + EMPTY_PATH, "").getNodeCode(); + Expression.Variable variable = CodeValues.variable("button0"); + assertEquals(objectClass, nodeCode.nodeClass()); + assertEquals(variable, nodeCode.nodeValue()); + List initializers = nodeCode.initializers(); + assertEquals(2, initializers.size()); + Statement statement = initializers.getLast(); + StatementExpression.MethodCall textProperty = CodeValues.methodCall(variable, "getProperties"); + assertEquals(CodeValues.methodCall(textProperty, "addListener", + CodeValues.methodReference(CodeValues.variable("controller"), + "onPropertiesChange")), statement); + } +} diff --git a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/internal/resolve/ExpressionResolverTest.java b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/internal/resolve/ExpressionResolverTest.java index 1a85114..1e23135 100644 --- a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/internal/resolve/ExpressionResolverTest.java +++ b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/internal/resolve/ExpressionResolverTest.java @@ -1,8 +1,8 @@ package io.github.sheikah45.fx2j.processor.internal.resolve; -import io.github.sheikah45.fx2j.parser.property.Expression; -import io.github.sheikah45.fx2j.processor.internal.code.CodeValue; +import io.github.sheikah45.fx2j.parser.property.BindExpression; import io.github.sheikah45.fx2j.processor.internal.code.CodeValues; +import io.github.sheikah45.fx2j.processor.internal.code.Expression; import io.github.sheikah45.fx2j.processor.internal.model.ExpressionResult; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; @@ -32,105 +32,105 @@ class ExpressionResolverTest extends AbstractResolverTest { @Test void testResolveNull() { assertEquals(new ExpressionResult(Object.class, CodeValues.nullValue(), List.of()), - expressionResolver.resolveExpression(new Expression.Null())); + expressionResolver.resolveExpression(new BindExpression.Null())); } @Test void testResolveWhole() { int value = 1; assertEquals(new ExpressionResult(int.class, CodeValues.literal(value), List.of()), - expressionResolver.resolveExpression(new Expression.Whole(value))); + expressionResolver.resolveExpression(new BindExpression.Whole(value))); } @Test void testResolveWholeLarge() { long value = Integer.MAX_VALUE + 1L; assertEquals(new ExpressionResult(long.class, CodeValues.literal(value), List.of()), - expressionResolver.resolveExpression(new Expression.Whole(value))); + expressionResolver.resolveExpression(new BindExpression.Whole(value))); } @Test void testResolveWholeLargeNegative() { long value = Integer.MIN_VALUE - 1L; assertEquals(new ExpressionResult(long.class, CodeValues.literal(value), List.of()), - expressionResolver.resolveExpression(new Expression.Whole(value))); + expressionResolver.resolveExpression(new BindExpression.Whole(value))); } @Test void testResolveFraction() { assertEquals(new ExpressionResult(float.class, CodeValues.literal(1f), List.of()), - expressionResolver.resolveExpression(new Expression.Fraction(1))); + expressionResolver.resolveExpression(new BindExpression.Fraction(1))); } @Test void testResolveFractionLarge() { double value = Float.MAX_VALUE * 2d; assertEquals(new ExpressionResult(double.class, CodeValues.literal(value), List.of()), - expressionResolver.resolveExpression(new Expression.Fraction(value))); + expressionResolver.resolveExpression(new BindExpression.Fraction(value))); } @Test void testResolveFractionLargeNegative() { double value = -(Float.MAX_VALUE * 2d); assertEquals(new ExpressionResult(double.class, CodeValues.literal(value), List.of()), - expressionResolver.resolveExpression(new Expression.Fraction(value))); + expressionResolver.resolveExpression(new BindExpression.Fraction(value))); } @Test void testResolveBoolean() { assertEquals(new ExpressionResult(boolean.class, CodeValues.literal(true), List.of()), - expressionResolver.resolveExpression(new Expression.Boolean(true))); + expressionResolver.resolveExpression(new BindExpression.Boolean(true))); } @Test void testResolveString() { assertEquals(new ExpressionResult(String.class, CodeValues.literal("hello"), List.of()), - expressionResolver.resolveExpression(new Expression.String("hello"))); + expressionResolver.resolveExpression(new BindExpression.String("hello"))); } @Test void testResolveVariable() { resolverContainer.getNameResolver().storeIdType("a", Integer.class); assertEquals(new ExpressionResult(Integer.class, CodeValues.variable("a"), List.of()), - expressionResolver.resolveExpression(new Expression.Variable("a"))); + expressionResolver.resolveExpression(new BindExpression.Variable("a"))); } @Test void testResolvePropertyRead() { resolverContainer.getNameResolver().storeIdType("a", Label.class); - CodeValue.Variable stringProperty0 = CodeValues.variable("stringProperty0"); + Expression.Variable stringProperty0 = CodeValues.variable("stringProperty0"); assertEquals(new ExpressionResult(StringProperty.class, stringProperty0, List.of(CodeValues.declaration(StringProperty.class, stringProperty0, CodeValues.methodCall("a", "textProperty")))), expressionResolver.resolveExpression( - new Expression.PropertyRead(new Expression.Variable("a"), "text"))); + new BindExpression.PropertyRead(new BindExpression.Variable("a"), "text"))); } @Test void testResolvePropertyReadUnknownProperty() { resolverContainer.getNameResolver().storeIdType("a", Label.class); assertThrows(IllegalArgumentException.class, () -> expressionResolver.resolveExpression( - new Expression.PropertyRead(new Expression.Variable("a"), "blank"))); + new BindExpression.PropertyRead(new BindExpression.Variable("a"), "blank"))); } @Test void testResolveMethodCall() { resolverContainer.getNameResolver().storeIdType("a", Label.class); - CodeValue.Variable variable = CodeValues.variable("node0"); + Expression.Variable variable = CodeValues.variable("node0"); assertEquals(new ExpressionResult(Node.class, variable, List.of(CodeValues.declaration(Node.class, variable, CodeValues.methodCall( "a", "lookup", "parent")))), expressionResolver.resolveExpression( - new Expression.MethodCall(new Expression.Variable("a"), "lookup", - List.of(new Expression.String("parent"))))); + new BindExpression.MethodCall(new BindExpression.Variable("a"), "lookup", + List.of(new BindExpression.String("parent"))))); } @Test void testResolveMethodCallUnknownProperty() { resolverContainer.getNameResolver().storeIdType("a", Label.class); assertThrows(IllegalArgumentException.class, () -> expressionResolver.resolveExpression( - new Expression.MethodCall(new Expression.Variable("a"), "getBlank", List.of()))); + new BindExpression.MethodCall(new BindExpression.Variable("a"), "getBlank", List.of()))); } @Test @@ -140,7 +140,7 @@ void testResolveCollectionAccess() { .orElseThrow(); resolverContainer.getNameResolver().storeIdType("a", ObservableList.class); Type bindingType = valueAt.getGenericReturnType(); - CodeValue.Variable variable = CodeValues.variable("objectBinding0"); + Expression.Variable variable = CodeValues.variable("objectBinding0"); assertEquals(new ExpressionResult(bindingType, variable, List.of(CodeValues.declaration(bindingType, variable, CodeValues.methodCall( valueAt, @@ -148,79 +148,81 @@ void testResolveCollectionAccess() { "a"), 0)))), expressionResolver.resolveExpression( - new Expression.CollectionAccess(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.CollectionAccess(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveCollectionAccessUnknownProperty() { resolverContainer.getNameResolver().storeIdType("a", Label.class); assertThrows(IllegalArgumentException.class, () -> expressionResolver.resolveExpression( - new Expression.MethodCall(new Expression.Variable("a"), "getBlank", List.of()))); + new BindExpression.MethodCall(new BindExpression.Variable("a"), "getBlank", List.of()))); } @Test void testResolveNegate() { resolverContainer.getNameResolver().storeIdType("a", IntegerProperty.class); - CodeValue.Variable variable = CodeValues.variable("integerBinding0"); + Expression.Variable variable = CodeValues.variable("integerBinding0"); assertEquals(new ExpressionResult(IntegerBinding.class, variable, List.of(CodeValues.declaration(IntegerBinding.class, variable, CodeValues.methodCall("a", "negate")))), - expressionResolver.resolveExpression(new Expression.Negate(new Expression.Variable("a")))); + expressionResolver.resolveExpression(new BindExpression.Negate(new BindExpression.Variable("a")))); } @Test void testResolveNegateObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableNumberValue.class); - CodeValue.Variable variable = CodeValues.variable("numberBinding0"); + Expression.Variable variable = CodeValues.variable("numberBinding0"); assertEquals(new ExpressionResult(NumberBinding.class, variable, List.of(CodeValues.declaration(NumberBinding.class, variable, CodeValues.methodCall(Bindings.class, "negate", CodeValues.variable( "a"))))), - expressionResolver.resolveExpression(new Expression.Negate(new Expression.Variable("a")))); + expressionResolver.resolveExpression(new BindExpression.Negate(new BindExpression.Variable("a")))); } @Test void testResolveAdd() { resolverContainer.getNameResolver().storeIdType("a", IntegerProperty.class); - CodeValue.Variable variable = CodeValues.variable("integerBinding0"); + Expression.Variable variable = CodeValues.variable("integerBinding0"); assertEquals(new ExpressionResult(IntegerBinding.class, variable, List.of(CodeValues.declaration(IntegerBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), "add", 0)))), expressionResolver.resolveExpression( - new Expression.Add(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.Add(new BindExpression.Variable("a"), new BindExpression.Whole(0)))); } @Test void testResolveAddObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableNumberValue.class); - CodeValue.Variable variable = CodeValues.variable("numberBinding0"); + Expression.Variable variable = CodeValues.variable("numberBinding0"); assertEquals(new ExpressionResult(NumberBinding.class, variable, List.of(CodeValues.declaration(NumberBinding.class, variable, CodeValues.methodCall(Bindings.class, "add", CodeValues.variable("a"), 0)))), expressionResolver.resolveExpression( - new Expression.Add(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.Add(new BindExpression.Variable("a"), new BindExpression.Whole(0)))); } @Test void testResolveSubtract() { resolverContainer.getNameResolver().storeIdType("a", IntegerProperty.class); - CodeValue.Variable variable = CodeValues.variable("integerBinding0"); + Expression.Variable variable = CodeValues.variable("integerBinding0"); assertEquals(new ExpressionResult(IntegerBinding.class, variable, List.of(CodeValues.declaration(IntegerBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), "subtract", 0)))), expressionResolver.resolveExpression( - new Expression.Subtract(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.Subtract(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveSubtractObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableNumberValue.class); - CodeValue.Variable variable = CodeValues.variable("numberBinding0"); + Expression.Variable variable = CodeValues.variable("numberBinding0"); assertEquals(new ExpressionResult(NumberBinding.class, variable, List.of(CodeValues.declaration(NumberBinding.class, variable, CodeValues.methodCall(Bindings.class, @@ -228,25 +230,27 @@ void testResolveSubtractObservable() { CodeValues.variable("a"), 0)))), expressionResolver.resolveExpression( - new Expression.Subtract(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.Subtract(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveMultiply() { resolverContainer.getNameResolver().storeIdType("a", IntegerProperty.class); - CodeValue.Variable variable = CodeValues.variable("integerBinding0"); + Expression.Variable variable = CodeValues.variable("integerBinding0"); assertEquals(new ExpressionResult(IntegerBinding.class, variable, List.of(CodeValues.declaration(IntegerBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), "multiply", 0)))), expressionResolver.resolveExpression( - new Expression.Multiply(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.Multiply(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveMultiplyObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableNumberValue.class); - CodeValue.Variable variable = CodeValues.variable("numberBinding0"); + Expression.Variable variable = CodeValues.variable("numberBinding0"); assertEquals(new ExpressionResult(NumberBinding.class, variable, List.of(CodeValues.declaration(NumberBinding.class, variable, CodeValues.methodCall(Bindings.class, @@ -254,56 +258,58 @@ void testResolveMultiplyObservable() { CodeValues.variable("a"), 0)))), expressionResolver.resolveExpression( - new Expression.Multiply(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.Multiply(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveDivide() { resolverContainer.getNameResolver().storeIdType("a", IntegerProperty.class); - CodeValue.Variable variable = CodeValues.variable("integerBinding0"); + Expression.Variable variable = CodeValues.variable("integerBinding0"); assertEquals(new ExpressionResult(IntegerBinding.class, variable, List.of(CodeValues.declaration(IntegerBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), "divide", 0)))), expressionResolver.resolveExpression( - new Expression.Divide(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.Divide(new BindExpression.Variable("a"), new BindExpression.Whole(0)))); } @Test void testResolveDivideObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableNumberValue.class); - CodeValue.Variable variable = CodeValues.variable("numberBinding0"); + Expression.Variable variable = CodeValues.variable("numberBinding0"); assertEquals(new ExpressionResult(NumberBinding.class, variable, List.of(CodeValues.declaration(NumberBinding.class, variable, CodeValues.methodCall(Bindings.class, "divide", CodeValues.variable("a"), 0)))), expressionResolver.resolveExpression( - new Expression.Divide(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.Divide(new BindExpression.Variable("a"), new BindExpression.Whole(0)))); } @Test void testResolveModulo() { assertThrows(UnsupportedOperationException.class, () -> expressionResolver.resolveExpression( - new Expression.Modulo(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.Modulo(new BindExpression.Variable("a"), new BindExpression.Whole(0)))); } @Test void testResolveGreaterThan() { resolverContainer.getNameResolver().storeIdType("a", IntegerProperty.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), "greaterThan", 0)))), expressionResolver.resolveExpression( - new Expression.GreaterThan(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.GreaterThan(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveGreaterThanObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableNumberValue.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(Bindings.class, @@ -311,26 +317,28 @@ void testResolveGreaterThanObservable() { CodeValues.variable("a"), 0)))), expressionResolver.resolveExpression( - new Expression.GreaterThan(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.GreaterThan(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveGreaterThanEqual() { resolverContainer.getNameResolver().storeIdType("a", IntegerProperty.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), "greaterThanOrEqualTo", 0)))), expressionResolver.resolveExpression( - new Expression.GreaterThanEqual(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.GreaterThanEqual(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveGreaterThanOrEqualObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableNumberValue.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(Bindings.class, @@ -338,25 +346,27 @@ void testResolveGreaterThanOrEqualObservable() { CodeValues.variable("a"), 0)))), expressionResolver.resolveExpression( - new Expression.GreaterThanEqual(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.GreaterThanEqual(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveLessThan() { resolverContainer.getNameResolver().storeIdType("a", IntegerProperty.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), "lessThan", 0)))), expressionResolver.resolveExpression( - new Expression.LessThan(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.LessThan(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveLessThanObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableNumberValue.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(Bindings.class, @@ -364,26 +374,28 @@ void testResolveLessThanObservable() { CodeValues.variable("a"), 0)))), expressionResolver.resolveExpression( - new Expression.LessThan(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.LessThan(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveLessThanEqual() { resolverContainer.getNameResolver().storeIdType("a", IntegerProperty.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), "lessThanOrEqualTo", 0)))), expressionResolver.resolveExpression( - new Expression.LessThanEqual(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.LessThanEqual(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveLessThanOrEqualObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableNumberValue.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(Bindings.class, @@ -391,50 +403,52 @@ void testResolveLessThanOrEqualObservable() { CodeValues.variable("a"), 0)))), expressionResolver.resolveExpression( - new Expression.LessThanEqual(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.LessThanEqual(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveEqual() { resolverContainer.getNameResolver().storeIdType("a", IntegerProperty.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), "isEqualTo", 0)))), expressionResolver.resolveExpression( - new Expression.Equal(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.Equal(new BindExpression.Variable("a"), new BindExpression.Whole(0)))); } @Test void testResolveEqualObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableNumberValue.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(Bindings.class, "equal", CodeValues.variable("a"), 0)))), expressionResolver.resolveExpression( - new Expression.Equal(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.Equal(new BindExpression.Variable("a"), new BindExpression.Whole(0)))); } @Test void testResolveNotEqual() { resolverContainer.getNameResolver().storeIdType("a", IntegerProperty.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), "isNotEqualTo", 0)))), expressionResolver.resolveExpression( - new Expression.NotEqual(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.NotEqual(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveNotEqualObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableNumberValue.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(Bindings.class, @@ -442,14 +456,15 @@ void testResolveNotEqualObservable() { CodeValues.variable("a"), 0)))), expressionResolver.resolveExpression( - new Expression.NotEqual(new Expression.Variable("a"), new Expression.Whole(0)))); + new BindExpression.NotEqual(new BindExpression.Variable("a"), + new BindExpression.Whole(0)))); } @Test void testResolveAnd() { resolverContainer.getNameResolver().storeIdType("a", BooleanProperty.class); resolverContainer.getNameResolver().storeIdType("b", BooleanProperty.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), @@ -457,14 +472,15 @@ void testResolveAnd() { CodeValues.variable( "b"))))), expressionResolver.resolveExpression( - new Expression.And(new Expression.Variable("a"), new Expression.Variable("b")))); + new BindExpression.And(new BindExpression.Variable("a"), + new BindExpression.Variable("b")))); } @Test void testResolveAndObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableBooleanValue.class); resolverContainer.getNameResolver().storeIdType("b", ObservableBooleanValue.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(Bindings.class, "and", @@ -472,14 +488,15 @@ void testResolveAndObservable() { CodeValues.variable( "b"))))), expressionResolver.resolveExpression( - new Expression.And(new Expression.Variable("a"), new Expression.Variable("b")))); + new BindExpression.And(new BindExpression.Variable("a"), + new BindExpression.Variable("b")))); } @Test void testResolveOr() { resolverContainer.getNameResolver().storeIdType("a", BooleanProperty.class); resolverContainer.getNameResolver().storeIdType("b", BooleanProperty.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), @@ -487,14 +504,15 @@ void testResolveOr() { CodeValues.variable( "b"))))), expressionResolver.resolveExpression( - new Expression.Or(new Expression.Variable("a"), new Expression.Variable("b")))); + new BindExpression.Or(new BindExpression.Variable("a"), + new BindExpression.Variable("b")))); } @Test void testResolveOrObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableBooleanValue.class); resolverContainer.getNameResolver().storeIdType("b", ObservableBooleanValue.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(Bindings.class, "or", @@ -502,30 +520,31 @@ void testResolveOrObservable() { CodeValues.variable( "b"))))), expressionResolver.resolveExpression( - new Expression.Or(new Expression.Variable("a"), new Expression.Variable("b")))); + new BindExpression.Or(new BindExpression.Variable("a"), + new BindExpression.Variable("b")))); } @Test void testResolveNot() { resolverContainer.getNameResolver().storeIdType("a", BooleanProperty.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(CodeValues.variable("a"), "not")))), - expressionResolver.resolveExpression(new Expression.Invert(new Expression.Variable("a")))); + expressionResolver.resolveExpression(new BindExpression.Invert(new BindExpression.Variable("a")))); } @Test void testResolveNotObservable() { resolverContainer.getNameResolver().storeIdType("a", ObservableBooleanValue.class); - CodeValue.Variable variable = CodeValues.variable("booleanBinding0"); + Expression.Variable variable = CodeValues.variable("booleanBinding0"); assertEquals(new ExpressionResult(BooleanBinding.class, variable, List.of(CodeValues.declaration(BooleanBinding.class, variable, CodeValues.methodCall(Bindings.class, "not", CodeValues.variable( "a"))))), - expressionResolver.resolveExpression(new Expression.Invert(new Expression.Variable("a")))); + expressionResolver.resolveExpression(new BindExpression.Invert(new BindExpression.Variable("a")))); } } diff --git a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/internal/resolve/ValueResolverTest.java b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/internal/resolve/ValueResolverTest.java index 8051053..e51ffbb 100644 --- a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/internal/resolve/ValueResolverTest.java +++ b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/internal/resolve/ValueResolverTest.java @@ -1,11 +1,11 @@ package io.github.sheikah45.fx2j.processor.internal.resolve; -import io.github.sheikah45.fx2j.parser.property.Expression; +import io.github.sheikah45.fx2j.parser.property.BindExpression; import io.github.sheikah45.fx2j.parser.property.Value; import io.github.sheikah45.fx2j.processor.FxmlProcessor; -import io.github.sheikah45.fx2j.processor.internal.code.CodeTypes; -import io.github.sheikah45.fx2j.processor.internal.code.CodeValue; +import io.github.sheikah45.fx2j.processor.internal.code.TypeValues; import io.github.sheikah45.fx2j.processor.internal.code.CodeValues; +import io.github.sheikah45.fx2j.processor.internal.code.Expression; import io.github.sheikah45.fx2j.processor.internal.model.NamedArgValue; import javafx.geometry.VPos; import javafx.util.Duration; @@ -75,8 +75,8 @@ void testCoerceString() { @Test void testCoerceArray() { - assertEquals(new CodeValue.Array.Declared(CodeTypes.of(String.class), List.of(CodeValues.literal("hello"), - CodeValues.literal("world"))), + assertEquals(new Expression.Array.Declared(TypeValues.of(String.class), List.of(CodeValues.literal("hello"), + CodeValues.literal("world"))), valueResolver.resolveCodeValue(String[].class, "hello,world")); } @@ -171,7 +171,7 @@ void testResolveLiteral() { @Test void testResolveExpression() { assertThrows(UnsupportedOperationException.class, - () -> valueResolver.resolveCodeValue(Float.class, new Expression.Null())); + () -> valueResolver.resolveCodeValue(Float.class, new BindExpression.Null())); } @Test diff --git a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/processor/FxmlProcessorControllerTest.java b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/processor/FxmlProcessorControllerTest.java index 44bf609..bddf5fb 100644 --- a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/processor/FxmlProcessorControllerTest.java +++ b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/processor/FxmlProcessorControllerTest.java @@ -107,10 +107,10 @@ void testOnChangeController() throws Exception { root.getStyleClass().add("test"); assertEquals(controller.styleClasses, List.of("test")); - assertEquals(controller.psudeoStyleClasses, Set.of()); + assertEquals(controller.pseudoClassStates, Set.of()); PseudoClass pseudoClass = PseudoClass.getPseudoClass("test"); root.pseudoClassStateChanged(pseudoClass, true); - assertEquals(controller.psudeoStyleClasses, Set.of(pseudoClass)); + assertEquals(controller.pseudoClassStates, Set.of(pseudoClass)); assertEquals(controller.properties, Map.of()); root.getProperties().put("key", "value"); diff --git a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/testcontroller/ChangeHandlerController.java b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/testcontroller/ChangeHandlerController.java index 19e899b..210226c 100644 --- a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/testcontroller/ChangeHandlerController.java +++ b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/testcontroller/ChangeHandlerController.java @@ -21,7 +21,7 @@ public class ChangeHandlerController { public AnchorPane root; public String textValue; public final List styleClasses = new ArrayList<>(); - public final Set psudeoStyleClasses = new HashSet<>(); + public final Set pseudoClassStates = new HashSet<>(); public final Map properties = new HashMap<>(); public void onTextChange(ObservableValue observable, String oldValue, String newValue) { @@ -35,9 +35,9 @@ public void onStyleClassChange(ListChangeListener.Change chang } } - public void onPsuedoStyleClassChange(SetChangeListener.Change change) { - psudeoStyleClasses.add(change.getElementAdded()); - psudeoStyleClasses.remove(change.getElementRemoved()); + public void onPseudoClassStatesChange(SetChangeListener.Change change) { + pseudoClassStates.add(change.getElementAdded()); + pseudoClassStates.remove(change.getElementRemoved()); } public void onPropertiesChange(MapChangeListener.Change change) { diff --git a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/testcontroller/EventHandlerMethodController.java b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/testcontroller/EventHandlerMethodController.java index b2d4cfc..9beb10a 100644 --- a/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/testcontroller/EventHandlerMethodController.java +++ b/fx2j-processor/src/test/java/io/github/sheikah45/fx2j/processor/testcontroller/EventHandlerMethodController.java @@ -21,11 +21,11 @@ public class EventHandlerMethodController { public int throwsNoEventCount = 0; public int contextRequestCount = 0; - public void onActionWithEventThrow(ActionEvent event) throws IOException, FileNotFoundException { + public void onActionWithEventThrow(ActionEvent event) throws Exception { throwsEventCount++; } - public void onActionWithNoEventThrow() throws Exception { + public void onActionWithoutEventThrow() throws Exception { throwsNoEventCount++; } diff --git a/fx2j-processor/src/test/resources/fxml/controller/change-handler-controller.fxml b/fx2j-processor/src/test/resources/fxml/controller/change-handler-controller.fxml index 4c8036d..addf5a1 100644 --- a/fx2j-processor/src/test/resources/fxml/controller/change-handler-controller.fxml +++ b/fx2j-processor/src/test/resources/fxml/controller/change-handler-controller.fxml @@ -7,6 +7,6 @@