From f583c9942b31fb4fc44e10288b36efc6e4447b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20T=C3=A2che?= Date: Fri, 15 Nov 2024 19:17:27 +0100 Subject: [PATCH] Initial commit --- .gitignore | 35 + README.md | 4 + api/pom.xml | 14 + .../fxml/compiler/ControllerFactory.java | 19 + .../gtache/fxml/compiler/ControllerInfo.java | 40 + .../fxml/compiler/ControllerInjection.java | 22 + .../fxml/compiler/GenerationParameters.java | 29 + .../fxml/compiler/GenerationRequest.java | 29 + .../gtache/fxml/compiler/Generator.java | 15 + .../gtache/fxml/compiler/InjectionType.java | 13 + .../compiler/ResourceBundleInjection.java | 17 + .../fxml/compiler/parsing/ParsedInclude.java | 58 + .../fxml/compiler/parsing/ParsedObject.java | 25 + .../fxml/compiler/parsing/ParsedProperty.java | 22 + api/src/main/java/module-info.java | 7 + .../fxml/compiler/TestControllerInfo.java | 63 ++ .../compiler/parsing/TestParsedInclude.java | 81 ++ core/pom.xml | 26 + .../impl/ControllerFieldInjectionTypes.java | 26 + .../compiler/impl/ControllerInfoImpl.java | 21 + .../impl/ControllerInjectionImpl.java | 22 + .../impl/ControllerMethodsInjectionType.java | 18 + .../impl/GenerationParametersImpl.java | 30 + .../compiler/impl/GenerationRequestImpl.java | 26 + .../fxml/compiler/impl/GeneratorImpl.java | 998 ++++++++++++++++++ .../impl/ResourceBundleInjectionImpl.java | 21 + .../impl/ResourceBundleInjectionTypes.java | 22 + .../parsing/impl/ParsedIncludeImpl.java | 19 + .../parsing/impl/ParsedObjectImpl.java | 90 ++ .../parsing/impl/ParsedPropertyImpl.java | 19 + .../compiler/impl/TestControllerInfoImpl.java | 65 ++ .../impl/TestControllerInjectionImpl.java | 43 + .../impl/TestGenerationParametersImpl.java | 49 + .../impl/TestGenerationRequestImpl.java | 49 + .../fxml/compiler/impl/TestGeneratorImpl.java | 4 + .../impl/TestResourceBundleInjectionImpl.java | 39 + .../parsing/impl/TestParsedIncludeImpl.java | 4 + .../parsing/impl/TestParsedObjectImpl.java | 66 ++ .../impl/TestParsedObjectImplBuilder.java | 108 ++ .../parsing/impl/TestParsedPropertyImpl.java | 35 + loader/pom.xml | 84 ++ .../parsing/listener/ParsingLoadListener.java | 192 ++++ .../compiler/loader/AssignIncludeView.java | 439 ++++++++ .../fxml/compiler/loader/AssignTestView.java | 198 ++++ .../compiler/loader/CreationBenchmark.java | 128 +++ .../compiler/loader/FactoryIncludeView.java | 444 ++++++++ .../fxml/compiler/loader/FactoryTestView.java | 202 ++++ .../compiler/loader/IncludeController.java | 489 +++++++++ .../gtache/fxml/compiler/loader/Main.java | 10 + .../compiler/loader/PrintLoadListener.java | 96 ++ .../loader/ReflectionIncludeView.java | 494 +++++++++ .../compiler/loader/ReflectionTestView.java | 253 +++++ .../compiler/loader/SettersIncludeView.java | 439 ++++++++ .../fxml/compiler/loader/SettersTestView.java | 198 ++++ .../gtache/fxml/compiler/loader/TestApp.java | 56 + .../fxml/compiler/loader/TestController.java | 179 ++++ .../compiler/loader/IncludeBundle.properties | 1 + .../compiler/loader/TestBundle.properties | 1 + .../fxml/compiler/loader/includeView.fxml | 189 ++++ .../gtache/fxml/compiler/loader/style.css | 3 + .../gtache/fxml/compiler/loader/testView.fxml | 141 +++ maven-plugin/pom.xml | 84 ++ .../fxml/compiler/maven/FXMLCompilerMojo.java | 32 + pom.xml | 404 +++++++ xml/pom.xml | 20 + 65 files changed, 7069 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 api/pom.xml create mode 100644 api/src/main/java/com/github/gtache/fxml/compiler/ControllerFactory.java create mode 100644 api/src/main/java/com/github/gtache/fxml/compiler/ControllerInfo.java create mode 100644 api/src/main/java/com/github/gtache/fxml/compiler/ControllerInjection.java create mode 100644 api/src/main/java/com/github/gtache/fxml/compiler/GenerationParameters.java create mode 100644 api/src/main/java/com/github/gtache/fxml/compiler/GenerationRequest.java create mode 100644 api/src/main/java/com/github/gtache/fxml/compiler/Generator.java create mode 100644 api/src/main/java/com/github/gtache/fxml/compiler/InjectionType.java create mode 100644 api/src/main/java/com/github/gtache/fxml/compiler/ResourceBundleInjection.java create mode 100644 api/src/main/java/com/github/gtache/fxml/compiler/parsing/ParsedInclude.java create mode 100644 api/src/main/java/com/github/gtache/fxml/compiler/parsing/ParsedObject.java create mode 100644 api/src/main/java/com/github/gtache/fxml/compiler/parsing/ParsedProperty.java create mode 100644 api/src/main/java/module-info.java create mode 100644 api/src/test/java/com/github/gtache/fxml/compiler/TestControllerInfo.java create mode 100644 api/src/test/java/com/github/gtache/fxml/compiler/parsing/TestParsedInclude.java create mode 100644 core/pom.xml create mode 100644 core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerFieldInjectionTypes.java create mode 100644 core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerInfoImpl.java create mode 100644 core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerInjectionImpl.java create mode 100644 core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerMethodsInjectionType.java create mode 100644 core/src/main/java/com/github/gtache/fxml/compiler/impl/GenerationParametersImpl.java create mode 100644 core/src/main/java/com/github/gtache/fxml/compiler/impl/GenerationRequestImpl.java create mode 100644 core/src/main/java/com/github/gtache/fxml/compiler/impl/GeneratorImpl.java create mode 100644 core/src/main/java/com/github/gtache/fxml/compiler/impl/ResourceBundleInjectionImpl.java create mode 100644 core/src/main/java/com/github/gtache/fxml/compiler/impl/ResourceBundleInjectionTypes.java create mode 100644 core/src/main/java/com/github/gtache/fxml/compiler/parsing/impl/ParsedIncludeImpl.java create mode 100644 core/src/main/java/com/github/gtache/fxml/compiler/parsing/impl/ParsedObjectImpl.java create mode 100644 core/src/main/java/com/github/gtache/fxml/compiler/parsing/impl/ParsedPropertyImpl.java create mode 100644 core/src/test/java/com/github/gtache/fxml/compiler/impl/TestControllerInfoImpl.java create mode 100644 core/src/test/java/com/github/gtache/fxml/compiler/impl/TestControllerInjectionImpl.java create mode 100644 core/src/test/java/com/github/gtache/fxml/compiler/impl/TestGenerationParametersImpl.java create mode 100644 core/src/test/java/com/github/gtache/fxml/compiler/impl/TestGenerationRequestImpl.java create mode 100644 core/src/test/java/com/github/gtache/fxml/compiler/impl/TestGeneratorImpl.java create mode 100644 core/src/test/java/com/github/gtache/fxml/compiler/impl/TestResourceBundleInjectionImpl.java create mode 100644 core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedIncludeImpl.java create mode 100644 core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedObjectImpl.java create mode 100644 core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedObjectImplBuilder.java create mode 100644 core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedPropertyImpl.java create mode 100644 loader/pom.xml create mode 100644 loader/src/main/java/com/github/gtache/fxml/compiler/parsing/listener/ParsingLoadListener.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/AssignIncludeView.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/AssignTestView.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/CreationBenchmark.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/FactoryIncludeView.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/FactoryTestView.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/IncludeController.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/Main.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/PrintLoadListener.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/ReflectionIncludeView.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/ReflectionTestView.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/SettersIncludeView.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/SettersTestView.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/TestApp.java create mode 100644 loader/src/test/java/com/github/gtache/fxml/compiler/loader/TestController.java create mode 100644 loader/src/test/resources/com/github/gtache/fxml/compiler/loader/IncludeBundle.properties create mode 100644 loader/src/test/resources/com/github/gtache/fxml/compiler/loader/TestBundle.properties create mode 100644 loader/src/test/resources/com/github/gtache/fxml/compiler/loader/includeView.fxml create mode 100644 loader/src/test/resources/com/github/gtache/fxml/compiler/loader/style.css create mode 100644 loader/src/test/resources/com/github/gtache/fxml/compiler/loader/testView.fxml create mode 100644 maven-plugin/pom.xml create mode 100644 maven-plugin/src/main/java/com/github/gtache/fxml/compiler/maven/FXMLCompilerMojo.java create mode 100644 pom.xml create mode 100644 xml/pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d769462 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..447bee7 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +## TODO + +- How to support resources in fx:include? +- Get included controllers without needing to specify them? \ No newline at end of file diff --git a/api/pom.xml b/api/pom.xml new file mode 100644 index 0000000..4d843c3 --- /dev/null +++ b/api/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + + com.github.gtache + fxml-compiler + 1.0-SNAPSHOT + + + fxml-compiler-api + + \ No newline at end of file diff --git a/api/src/main/java/com/github/gtache/fxml/compiler/ControllerFactory.java b/api/src/main/java/com/github/gtache/fxml/compiler/ControllerFactory.java new file mode 100644 index 0000000..9cf1312 --- /dev/null +++ b/api/src/main/java/com/github/gtache/fxml/compiler/ControllerFactory.java @@ -0,0 +1,19 @@ +package com.github.gtache.fxml.compiler; + +import java.util.Map; + +/** + * Factory for creating controllers + * + * @param The type of the controller + */ +public interface ControllerFactory { + + /** + * Creates a controller + * + * @param fieldMap The assignment of field name to value + * @return The created controller + */ + T create(final Map fieldMap); +} diff --git a/api/src/main/java/com/github/gtache/fxml/compiler/ControllerInfo.java b/api/src/main/java/com/github/gtache/fxml/compiler/ControllerInfo.java new file mode 100644 index 0000000..5d4a1b7 --- /dev/null +++ b/api/src/main/java/com/github/gtache/fxml/compiler/ControllerInfo.java @@ -0,0 +1,40 @@ +package com.github.gtache.fxml.compiler; + +import java.util.List; +import java.util.Map; + +/** + * Info about a controller for code generation + */ +public interface ControllerInfo { + + /** + * @return A mapping of method name to true if the method has an argument + */ + Map handlerHasArgument(); + + /** + * Returns whether the given event handler method has an argument + * + * @param methodName The method name + * @return A mapping of method name to true if the method has an event + */ + default boolean handlerHasArgument(final String methodName) { + return handlerHasArgument().getOrDefault(methodName, true); + } + + /** + * @return A mapping of property name to generic types + */ + Map> propertyGenericTypes(); + + /** + * Returns the generic types for the given property (null if not generic) + * + * @param property The property + * @return The generic types + */ + default List propertyGenericTypes(final String property) { + return propertyGenericTypes().get(property); + } +} diff --git a/api/src/main/java/com/github/gtache/fxml/compiler/ControllerInjection.java b/api/src/main/java/com/github/gtache/fxml/compiler/ControllerInjection.java new file mode 100644 index 0000000..c015bfa --- /dev/null +++ b/api/src/main/java/com/github/gtache/fxml/compiler/ControllerInjection.java @@ -0,0 +1,22 @@ +package com.github.gtache.fxml.compiler; + +/** + * Represents a controller injection to use for generated code + */ +public interface ControllerInjection { + + /** + * @return The injection type for fields + */ + InjectionType fieldInjectionType(); + + /** + * @return The injection type for event handlers + */ + InjectionType methodInjectionType(); + + /** + * @return The injection class + */ + String injectionClass(); +} diff --git a/api/src/main/java/com/github/gtache/fxml/compiler/GenerationParameters.java b/api/src/main/java/com/github/gtache/fxml/compiler/GenerationParameters.java new file mode 100644 index 0000000..5dc018a --- /dev/null +++ b/api/src/main/java/com/github/gtache/fxml/compiler/GenerationParameters.java @@ -0,0 +1,29 @@ +package com.github.gtache.fxml.compiler; + +import java.util.Map; + +/** + * Parameters for FXML generation + */ +public interface GenerationParameters { + + /** + * @return The mapping of controller class name to controller injection + */ + Map controllerInjections(); + + /** + * @return The mapping of fx:include source to generated class name + */ + Map sourceToGeneratedClassName(); + + /** + * @return The mapping of fx:include source to controller class name + */ + Map sourceToControllerName(); + + /** + * @return The resource bundle injection to use + */ + ResourceBundleInjection resourceBundleInjection(); +} diff --git a/api/src/main/java/com/github/gtache/fxml/compiler/GenerationRequest.java b/api/src/main/java/com/github/gtache/fxml/compiler/GenerationRequest.java new file mode 100644 index 0000000..7e6b6d9 --- /dev/null +++ b/api/src/main/java/com/github/gtache/fxml/compiler/GenerationRequest.java @@ -0,0 +1,29 @@ +package com.github.gtache.fxml.compiler; + +import com.github.gtache.fxml.compiler.parsing.ParsedObject; + +/** + * Represents a request for a code generation + */ +public interface GenerationRequest { + + /** + * @return The main controller info + */ + ControllerInfo controllerInfo(); + + /** + * @return The request parameters + */ + GenerationParameters parameters(); + + /** + * @return The object to generate code for + */ + ParsedObject rootObject(); + + /** + * @return The output class name + */ + String outputClassName(); +} diff --git a/api/src/main/java/com/github/gtache/fxml/compiler/Generator.java b/api/src/main/java/com/github/gtache/fxml/compiler/Generator.java new file mode 100644 index 0000000..95188d0 --- /dev/null +++ b/api/src/main/java/com/github/gtache/fxml/compiler/Generator.java @@ -0,0 +1,15 @@ +package com.github.gtache.fxml.compiler; + +/** + * Generates compiled FXML code + */ +public interface Generator { + + /** + * Generates the java code + * + * @param request The request + * @return The java code + */ + String generate(GenerationRequest request); +} diff --git a/api/src/main/java/com/github/gtache/fxml/compiler/InjectionType.java b/api/src/main/java/com/github/gtache/fxml/compiler/InjectionType.java new file mode 100644 index 0000000..b4029af --- /dev/null +++ b/api/src/main/java/com/github/gtache/fxml/compiler/InjectionType.java @@ -0,0 +1,13 @@ +package com.github.gtache.fxml.compiler; + +/** + * A type of injection for controllers + */ +@FunctionalInterface +public interface InjectionType { + + /** + * @return The name of the type + */ + String name(); +} diff --git a/api/src/main/java/com/github/gtache/fxml/compiler/ResourceBundleInjection.java b/api/src/main/java/com/github/gtache/fxml/compiler/ResourceBundleInjection.java new file mode 100644 index 0000000..0fe0653 --- /dev/null +++ b/api/src/main/java/com/github/gtache/fxml/compiler/ResourceBundleInjection.java @@ -0,0 +1,17 @@ +package com.github.gtache.fxml.compiler; + +/** + * Represents a controller injection to use for generated code + */ +public interface ResourceBundleInjection { + + /** + * @return The injection type + */ + InjectionType injectionType(); + + /** + * @return The resource bundle path + */ + String bundleName(); +} diff --git a/api/src/main/java/com/github/gtache/fxml/compiler/parsing/ParsedInclude.java b/api/src/main/java/com/github/gtache/fxml/compiler/parsing/ParsedInclude.java new file mode 100644 index 0000000..f92b013 --- /dev/null +++ b/api/src/main/java/com/github/gtache/fxml/compiler/parsing/ParsedInclude.java @@ -0,0 +1,58 @@ +package com.github.gtache.fxml.compiler.parsing; + +import java.util.LinkedHashMap; +import java.util.SequencedCollection; +import java.util.SequencedMap; + +/** + * Special {@link ParsedObject} for fx:include + */ +@FunctionalInterface +public interface ParsedInclude extends ParsedObject { + + /** + * @return The controller id if present + */ + default String controllerId() { + final var property = properties().get("fx:id"); + if (property == null) { + return null; + } else { + return property.value() + "Controller"; + } + } + + /** + * @return The resources if present + */ + default String resources() { + final var property = properties().get("resources"); + if (property == null) { + return null; + } else { + return property.value().replace("/", "."); + } + } + + /** + * @return The source + */ + default String source() { + final var property = properties().get("source"); + if (property == null) { + throw new IllegalStateException("Missing source"); + } else { + return property.value(); + } + } + + @Override + default Class clazz() { + return ParsedInclude.class; + } + + @Override + default SequencedMap> children() { + return new LinkedHashMap<>(); + } +} diff --git a/api/src/main/java/com/github/gtache/fxml/compiler/parsing/ParsedObject.java b/api/src/main/java/com/github/gtache/fxml/compiler/parsing/ParsedObject.java new file mode 100644 index 0000000..49f4af3 --- /dev/null +++ b/api/src/main/java/com/github/gtache/fxml/compiler/parsing/ParsedObject.java @@ -0,0 +1,25 @@ +package com.github.gtache.fxml.compiler.parsing; + +import java.util.SequencedCollection; +import java.util.SequencedMap; + +/** + * Parsed object from FXML + */ +public interface ParsedObject { + + /** + * @return The object class + */ + Class clazz(); + + /** + * @return The object properties + */ + SequencedMap properties(); + + /** + * @return The object children (complex properties) + */ + SequencedMap> children(); +} diff --git a/api/src/main/java/com/github/gtache/fxml/compiler/parsing/ParsedProperty.java b/api/src/main/java/com/github/gtache/fxml/compiler/parsing/ParsedProperty.java new file mode 100644 index 0000000..7c93e2c --- /dev/null +++ b/api/src/main/java/com/github/gtache/fxml/compiler/parsing/ParsedProperty.java @@ -0,0 +1,22 @@ +package com.github.gtache.fxml.compiler.parsing; + +/** + * Parsed property/attribute from FXML + */ +public interface ParsedProperty { + + /** + * @return The property name + */ + String name(); + + /** + * @return The property source type (in case of static property) + */ + Class sourceType(); + + /** + * @return The property value + */ + String value(); +} diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java new file mode 100644 index 0000000..7560e1f --- /dev/null +++ b/api/src/main/java/module-info.java @@ -0,0 +1,7 @@ +/** + * API module for FXML compiler + */ +module com.github.gtache.fxml.compiler.api { + exports com.github.gtache.fxml.compiler; + exports com.github.gtache.fxml.compiler.parsing; +} \ No newline at end of file diff --git a/api/src/test/java/com/github/gtache/fxml/compiler/TestControllerInfo.java b/api/src/test/java/com/github/gtache/fxml/compiler/TestControllerInfo.java new file mode 100644 index 0000000..3fd8333 --- /dev/null +++ b/api/src/test/java/com/github/gtache/fxml/compiler/TestControllerInfo.java @@ -0,0 +1,63 @@ +package com.github.gtache.fxml.compiler; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +class TestControllerInfo { + + private final String string; + private final Map handlerHasArgument; + private final Map> propertyGenericTypes; + private final List genericTypes; + private final ControllerInfo controllerInfo; + + TestControllerInfo() { + this.string = "string"; + this.handlerHasArgument = new HashMap<>(); + this.propertyGenericTypes = new HashMap<>(); + this.genericTypes = List.of("a", "b"); + this.controllerInfo = spy(ControllerInfo.class); + } + + @BeforeEach + void beforeEach() { + when(controllerInfo.handlerHasArgument()).thenReturn(handlerHasArgument); + when(controllerInfo.propertyGenericTypes()).thenReturn(propertyGenericTypes); + } + + @Test + void testHandlerHasArgumentNull() { + assertTrue(controllerInfo.handlerHasArgument(string)); + } + + @Test + void testHandlerHasArgumentFalse() { + handlerHasArgument.put(string, false); + assertFalse(controllerInfo.handlerHasArgument(string)); + } + + @Test + void testHandlerHasArgumentTrue() { + handlerHasArgument.put(string, true); + assertTrue(controllerInfo.handlerHasArgument(string)); + } + + @Test + void testPropertyGenericTypesNull() { + assertNull(controllerInfo.propertyGenericTypes(string)); + } + + @Test + void testPropertyGenericTypes() { + propertyGenericTypes.put(string, genericTypes); + assertEquals(genericTypes, controllerInfo.propertyGenericTypes(string)); + } +} diff --git a/api/src/test/java/com/github/gtache/fxml/compiler/parsing/TestParsedInclude.java b/api/src/test/java/com/github/gtache/fxml/compiler/parsing/TestParsedInclude.java new file mode 100644 index 0000000..7a5164a --- /dev/null +++ b/api/src/test/java/com/github/gtache/fxml/compiler/parsing/TestParsedInclude.java @@ -0,0 +1,81 @@ +package com.github.gtache.fxml.compiler.parsing; + + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.LinkedHashMap; +import java.util.Objects; +import java.util.SequencedMap; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TestParsedInclude { + + private final SequencedMap properties; + private final ParsedProperty property; + private final String string; + private final ParsedInclude include; + + TestParsedInclude(@Mock final ParsedProperty property) { + this.properties = new LinkedHashMap<>(); + this.property = Objects.requireNonNull(property); + this.string = "str/ing"; + this.include = spy(ParsedInclude.class); + } + + @BeforeEach + void beforeEach() { + when(include.properties()).thenReturn(properties); + when(property.value()).thenReturn(string); + } + + @Test + void testControllerIdNull() { + assertNull(include.controllerId()); + } + + @Test + void testControllerId() { + properties.put("fx:id", property); + assertEquals(string + "Controller", include.controllerId()); + } + + @Test + void testResourcesNull() { + assertNull(include.resources()); + } + + @Test + void testResources() { + properties.put("resources", property); + assertEquals(string.replace("/", "."), include.resources()); + } + + @Test + void testSourceNull() { + assertThrows(IllegalStateException.class, include::source); + } + + @Test + void testSource() { + properties.put("source", property); + assertEquals(string, include.source()); + } + + @Test + void testClazz() { + assertEquals(ParsedInclude.class, include.clazz()); + } + + @Test + void testChildren() { + assertEquals(new LinkedHashMap<>(), include.children()); + } +} diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..6f5758d --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + com.github.gtache + fxml-compiler + 1.0-SNAPSHOT + + + fxml-compiler-core + + + + com.github.gtache + fxml-compiler-api + + + org.openjfx + javafx-graphics + ${javafx.version} + provided + + + \ No newline at end of file diff --git a/core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerFieldInjectionTypes.java b/core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerFieldInjectionTypes.java new file mode 100644 index 0000000..4183e62 --- /dev/null +++ b/core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerFieldInjectionTypes.java @@ -0,0 +1,26 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.ControllerInjection; +import com.github.gtache.fxml.compiler.InjectionType; + +/** + * Base field {@link InjectionType}s for {@link ControllerInjection} + */ +public enum ControllerFieldInjectionTypes implements InjectionType { + /** + * Inject using variable assignment + */ + ASSIGN, + /** + * Inject using a factory + */ + FACTORY, + /** + * Inject using reflection + */ + REFLECTION, + /** + * Inject using setters + */ + SETTERS +} diff --git a/core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerInfoImpl.java b/core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerInfoImpl.java new file mode 100644 index 0000000..53c2307 --- /dev/null +++ b/core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerInfoImpl.java @@ -0,0 +1,21 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.ControllerInfo; + +import java.util.List; +import java.util.Map; + +/** + * Implementation of {@link ControllerInfo} + * + * @param handlerHasArgument The mapping of method name to true if the method has an argument + * @param propertyGenericTypes The mapping of property name to generic types + */ +public record ControllerInfoImpl(Map handlerHasArgument, + Map> propertyGenericTypes) implements ControllerInfo { + + public ControllerInfoImpl { + handlerHasArgument = Map.copyOf(handlerHasArgument); + propertyGenericTypes = Map.copyOf(propertyGenericTypes); + } +} diff --git a/core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerInjectionImpl.java b/core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerInjectionImpl.java new file mode 100644 index 0000000..5b94367 --- /dev/null +++ b/core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerInjectionImpl.java @@ -0,0 +1,22 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.ControllerInjection; +import com.github.gtache.fxml.compiler.InjectionType; + +import java.util.Objects; + +/** + * Implementation of {@link ControllerInjection} + * + * @param fieldInjectionType The field injection type + * @param methodInjectionType The method injection type + * @param injectionClass The injection class name + */ +public record ControllerInjectionImpl(InjectionType fieldInjectionType, InjectionType methodInjectionType, + String injectionClass) implements ControllerInjection { + public ControllerInjectionImpl { + Objects.requireNonNull(fieldInjectionType); + Objects.requireNonNull(methodInjectionType); + Objects.requireNonNull(injectionClass); + } +} diff --git a/core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerMethodsInjectionType.java b/core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerMethodsInjectionType.java new file mode 100644 index 0000000..1870c1d --- /dev/null +++ b/core/src/main/java/com/github/gtache/fxml/compiler/impl/ControllerMethodsInjectionType.java @@ -0,0 +1,18 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.ControllerInjection; +import com.github.gtache.fxml.compiler.InjectionType; + +/** + * Base methods {@link InjectionType}s for {@link ControllerInjection} + */ +public enum ControllerMethodsInjectionType implements InjectionType { + /** + * Inject using visible methods + */ + REFERENCE, + /** + * Inject using reflection + */ + REFLECTION, +} diff --git a/core/src/main/java/com/github/gtache/fxml/compiler/impl/GenerationParametersImpl.java b/core/src/main/java/com/github/gtache/fxml/compiler/impl/GenerationParametersImpl.java new file mode 100644 index 0000000..fb5739d --- /dev/null +++ b/core/src/main/java/com/github/gtache/fxml/compiler/impl/GenerationParametersImpl.java @@ -0,0 +1,30 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.ControllerInjection; +import com.github.gtache.fxml.compiler.GenerationParameters; +import com.github.gtache.fxml.compiler.ResourceBundleInjection; + +import java.util.Map; +import java.util.Objects; + + +/** + * Implementation of {@link GenerationParameters} + * + * @param controllerInjections The mapping of controller class name to controller injection + * @param sourceToGeneratedClassName The mapping of fx:include source to generated class name + * @param sourceToControllerName The mapping of fx:include source to controller class name + * @param resourceBundleInjection The resource bundle injection + */ +public record GenerationParametersImpl(Map controllerInjections, + Map sourceToGeneratedClassName, + Map sourceToControllerName, + ResourceBundleInjection resourceBundleInjection) implements GenerationParameters { + + public GenerationParametersImpl { + controllerInjections = Map.copyOf(controllerInjections); + sourceToGeneratedClassName = Map.copyOf(sourceToGeneratedClassName); + sourceToControllerName = Map.copyOf(sourceToControllerName); + Objects.requireNonNull(resourceBundleInjection); + } +} diff --git a/core/src/main/java/com/github/gtache/fxml/compiler/impl/GenerationRequestImpl.java b/core/src/main/java/com/github/gtache/fxml/compiler/impl/GenerationRequestImpl.java new file mode 100644 index 0000000..7599a7c --- /dev/null +++ b/core/src/main/java/com/github/gtache/fxml/compiler/impl/GenerationRequestImpl.java @@ -0,0 +1,26 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.ControllerInfo; +import com.github.gtache.fxml.compiler.GenerationParameters; +import com.github.gtache.fxml.compiler.GenerationRequest; +import com.github.gtache.fxml.compiler.parsing.ParsedObject; + +import java.util.Objects; + +/** + * Implementation of {@link GenerationRequest} + * + * @param parameters The generation parameters + * @param rootObject The root object + * @param outputClassName The output class name + */ +public record GenerationRequestImpl(GenerationParameters parameters, ControllerInfo controllerInfo, + ParsedObject rootObject, + String outputClassName) implements GenerationRequest { + public GenerationRequestImpl { + Objects.requireNonNull(parameters); + Objects.requireNonNull(controllerInfo); + Objects.requireNonNull(rootObject); + Objects.requireNonNull(outputClassName); + } +} diff --git a/core/src/main/java/com/github/gtache/fxml/compiler/impl/GeneratorImpl.java b/core/src/main/java/com/github/gtache/fxml/compiler/impl/GeneratorImpl.java new file mode 100644 index 0000000..4f15fea --- /dev/null +++ b/core/src/main/java/com/github/gtache/fxml/compiler/impl/GeneratorImpl.java @@ -0,0 +1,998 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.ControllerInjection; +import com.github.gtache.fxml.compiler.GenerationRequest; +import com.github.gtache.fxml.compiler.Generator; +import com.github.gtache.fxml.compiler.parsing.ParsedInclude; +import com.github.gtache.fxml.compiler.parsing.ParsedObject; +import com.github.gtache.fxml.compiler.parsing.ParsedProperty; +import javafx.beans.NamedArg; +import javafx.event.EventHandler; +import javafx.scene.Node; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.SequencedMap; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; + +/** + * Implementation of {@link Generator} + */ +public class GeneratorImpl implements Generator { + + private static final Map, Boolean> HAS_VALUE_OF = new ConcurrentHashMap<>(); + private static final Map, Boolean> IS_GENERIC = new ConcurrentHashMap<>(); + private static final Map, Map> METHODS = new ConcurrentHashMap<>(); + private static final Map, Map> STATIC_METHODS = new ConcurrentHashMap<>(); + + private final Collection controllerFactoryPostAction; + private final Map variableNameCounters; + + /** + * Instantiates a new generator + */ + public GeneratorImpl() { + this.controllerFactoryPostAction = new ArrayList<>(); + this.variableNameCounters = new ConcurrentHashMap<>(); + } + + @Override + public String generate(final GenerationRequest request) { + controllerFactoryPostAction.clear(); + variableNameCounters.clear(); + final var className = request.outputClassName(); + final var pkgName = className.substring(0, className.lastIndexOf('.')); + final var simpleClassName = className.substring(className.lastIndexOf('.') + 1); + final var loadMethod = getLoadMethod(request); + final var controllerInjection = getControllerInjection(request); + final var controllerInjectionType = controllerInjection.fieldInjectionType(); + final String constructorArgument; + final String constructorControllerJavadoc; + final String controllerArgumentType; + final String controllerMapType; + final var controllerInjectionClass = controllerInjection.injectionClass(); + final var imports = getImports(request); + if (controllerInjectionType == ControllerFieldInjectionTypes.FACTORY) { + constructorArgument = "controllerFactory"; + constructorControllerJavadoc = "controller factory"; + controllerArgumentType = "ControllerFactory<" + controllerInjectionClass + ">"; + controllerMapType = "ControllerFactory"; + } else { + constructorArgument = "controller"; + constructorControllerJavadoc = "controller"; + controllerArgumentType = controllerInjectionClass; + controllerMapType = "Object"; + } + final var helperMethods = getHelperMethods(request); + return """ + package %1$s; + + %9$s + + /** + * Generated code, not thread-safe + */ + public final class %2$s { + + private final Map, %7$s> controllersMap; + private final Map, ResourceBundle> resourceBundlesMap; + private boolean loaded; + private %3$s controller; + + /** + * Instantiates a new %2$s with no nested controllers and no resource bundle + * @param %4$s The %5$s + */ + public %2$s(final %8$s %4$s) { + this(Map.of(%3$s.class, %4$s), Map.of()); + } + + /** + * Instantiates a new %2$s with no nested controllers + * @param %4$s The %5$s + * @param resourceBundle The resource bundle + */ + public %2$s(final %8$s %4$s, final ResourceBundle resourceBundle) { + this(Map.of(%3$s.class, %4$s), Map.of(%3$s.class, resourceBundle)); + } + + /** + * Instantiates a new %2$s with nested controllers + * @param controllersMap The map of controller class to %5$s + * @param resourceBundlesMap The map of controller class to resource bundle + */ + public %2$s(final Map, %7$s> controllersMap, final Map, ResourceBundle> resourceBundlesMap) { + this.controllersMap = Map.copyOf(controllersMap); + this.resourceBundlesMap = Map.copyOf(resourceBundlesMap); + } + + %6$s + + %10$s + + /** + * @return The controller + */ + public %3$s controller() { + if (loaded) { + return controller; + } else { + throw new IllegalStateException("Not loaded"); + } + } + } + """.formatted(pkgName, simpleClassName, controllerInjectionClass, constructorArgument, constructorControllerJavadoc, + loadMethod, controllerMapType, controllerArgumentType, imports, helperMethods); + } + + /** + * Gets helper methods string for the given generation request + * + * @param request The generation request + * @return The helper methods + */ + private static String getHelperMethods(final GenerationRequest request) { + final var injection = getControllerInjection(request); + final var methodInjectionType = injection.methodInjectionType(); + final var sb = new StringBuilder(); + if (methodInjectionType == ControllerMethodsInjectionType.REFLECTION) { + sb.append(""" + private void callMethod(final String methodName, final T event) { + try { + final Method method; + final var methods = Arrays.stream(controller.getClass().getDeclaredMethods()) + .filter(m -> m.getName().equals(methodName)).toList(); + if (methods.size() > 1) { + final var eventMethods = methods.stream().filter(m -> + m.getParameterCount() == 1 && Event.class.isAssignableFrom(m.getParameterTypes()[0])).toList(); + if (eventMethods.size() == 1) { + method = eventMethods.getFirst(); + } else { + final var emptyMethods = methods.stream().filter(m -> m.getParameterCount() == 0).toList(); + if (emptyMethods.size() == 1) { + method = emptyMethods.getFirst(); + } else { + throw new IllegalArgumentException("Multiple matching methods for " + methodName); + } + } + } else if (methods.size() == 1) { + method = methods.getFirst(); + } else { + throw new IllegalArgumentException("No matching method for " + methodName); + } + method.setAccessible(true); + if (method.getParameterCount() == 0) { + method.invoke(controller); + } else { + method.invoke(controller, event); + } + } catch (final IllegalAccessException | InvocationTargetException ex) { + throw new RuntimeException("Error using reflection on " + methodName, ex); + } + } + """); + } + if (injection.fieldInjectionType() == ControllerFieldInjectionTypes.REFLECTION) { + sb.append(""" + private void injectField(final String fieldName, final T object) { + try { + final var field = controller.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(controller, object); + } catch (final NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Error using reflection on " + fieldName, e); + } + }"""); + } + return sb.toString(); + } + + /** + * Gets imports for the given generation request + * + * @param request The generation request + * @return The imports + */ + private static String getImports(final GenerationRequest request) { + final var injection = getControllerInjection(request); + final var fieldInjectionType = injection.fieldInjectionType(); + final var sb = new StringBuilder("import java.util.Map;\nimport java.util.ResourceBundle;\nimport java.util.HashMap;\n"); + if (fieldInjectionType == ControllerFieldInjectionTypes.FACTORY) { + sb.append("import com.github.gtache.fxml.compiler.ControllerFactory;\n"); + } + final var methodInjectionType = injection.methodInjectionType(); + if (methodInjectionType == ControllerMethodsInjectionType.REFLECTION) { + sb.append("import java.lang.reflect.InvocationTargetException;\n"); + sb.append("import java.util.Arrays;\n"); + sb.append("import javafx.event.Event;\n"); + sb.append("import java.lang.reflect.Method;\n"); + } + return sb.toString(); + } + + /** + * Computes the load method + * + * @param request The generation request + * @return The load method + */ + private String getLoadMethod(final GenerationRequest request) { + final var rootObject = request.rootObject(); + final var controllerInjection = getControllerInjection(request); + final var controllerInjectionType = controllerInjection.fieldInjectionType(); + final var controllerClass = controllerInjection.injectionClass(); + final var sb = new StringBuilder("public javafx.scene.Parent load() {\n"); + sb.append(" if (loaded) {\n"); + sb.append(" throw new IllegalStateException(\"Already loaded\");\n"); + sb.append(" }\n"); + final var resourceBundleInjection = request.parameters().resourceBundleInjection(); + if (resourceBundleInjection.injectionType() == ResourceBundleInjectionTypes.GET_BUNDLE) { + sb.append(" final var bundle = ResourceBundle.getBundle(\"").append(resourceBundleInjection.bundleName()).append("\");\n"); + } else if (resourceBundleInjection.injectionType() == ResourceBundleInjectionTypes.CONSTRUCTOR) { + sb.append(" final var bundle = resourceBundlesMap.get(").append(controllerClass).append(".class);\n"); + } + if (controllerInjectionType == ControllerFieldInjectionTypes.FACTORY) { + sb.append(" final var fieldMap = new HashMap();\n"); + } else { + sb.append(" controller = (").append(controllerClass).append(") controllersMap.get(").append(controllerClass).append(".class);\n"); + } + final var variableName = getNextVariableName("object"); + format(request, rootObject, variableName, sb); + if (controllerInjectionType == ControllerFieldInjectionTypes.FACTORY) { + sb.append(" final var controllerFactory = controllersMap.get(").append(controllerClass).append(".class);\n"); + sb.append(" controller = (").append(controllerClass).append(") controllerFactory.create(fieldMap);\n"); + controllerFactoryPostAction.forEach(sb::append); + } + if (controllerInjection.methodInjectionType() == ControllerMethodsInjectionType.REFLECTION) { + sb.append(" try {\n"); + sb.append(" final var initialize = controller.getClass().getDeclaredMethod(\"initialize\");\n"); + sb.append(" initialize.setAccessible(true);\n"); + sb.append(" initialize.invoke(controller);\n"); + sb.append(" } catch (final InvocationTargetException | IllegalAccessException e) {\n"); + sb.append(" throw new RuntimeException(\"Error using reflection\", e);\n"); + sb.append(" } catch (final NoSuchMethodException ignored) {\n"); + sb.append(" }\n"); + } else { + sb.append(" controller.initialize();\n"); + } + sb.append(" loaded = true;\n"); + sb.append(" return ").append(variableName).append(";\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Formats an object + * + * @param request The generation request + * @param parsedObject The parsed object to format + * @param variableName The variable name for the object + * @param sb The string builder + */ + private void format(final GenerationRequest request, final ParsedObject parsedObject, final String variableName, final StringBuilder sb) { + if (parsedObject instanceof final ParsedInclude include) { + formatInclude(request, include, variableName, sb); + } else { + final var clazz = parsedObject.clazz(); + final var constructors = clazz.getConstructors(); + final var allPropertyNames = new HashSet<>(parsedObject.properties().keySet()); + allPropertyNames.addAll(parsedObject.children().keySet().stream().map(ParsedProperty::name).collect(Collectors.toSet())); + final var constructorArgs = getMatchingConstructorArgs(constructors, allPropertyNames); + if (constructorArgs == null) { + if (allPropertyNames.size() == 1 && allPropertyNames.iterator().next().equals("fx:constant")) { + final var property = parsedObject.properties().get("fx:constant"); + sb.append(" final var ").append(variableName).append(" = ").append(clazz.getCanonicalName()).append(".").append(property.value()).append(";\n"); + } else { + throw new IllegalStateException("Cannot find constructor for " + clazz.getCanonicalName()); + } + } else { + final var args = getListConstructorArgs(constructorArgs, parsedObject); + final var genericTypes = getGenericTypes(request, parsedObject); + sb.append(" final var ").append(variableName).append(" = new ").append(clazz.getCanonicalName()) + .append(genericTypes).append("(").append(String.join(", ", args)).append(");\n"); + parsedObject.properties().entrySet().stream().filter(e -> !constructorArgs.namedArgs().containsKey(e.getKey())).forEach(e -> { + final var p = e.getValue(); + formatProperty(request, p, parsedObject, variableName, sb); + }); + parsedObject.children().entrySet().stream().filter(e -> !constructorArgs.namedArgs().containsKey(e.getKey().name())).forEach(e -> { + final var p = e.getKey(); + final var o = e.getValue(); + formatChild(request, parsedObject, p, o, variableName, sb); + }); + } + } + } + + private static String getGenericTypes(final GenerationRequest request, final ParsedObject parsedObject) { + final var clazz = parsedObject.clazz(); + if (isGeneric(clazz)) { + final var idProperty = parsedObject.properties().get("fx:id"); + if (idProperty == null) { + return "<>"; + } else { + final var id = idProperty.value(); + final var genericTypes = request.controllerInfo().propertyGenericTypes(id); + if (genericTypes == null) { //Raw + return ""; + } else { + return "<" + String.join(", ", genericTypes) + ">"; + } + } + } + return ""; + } + + /** + * Checks if the given class is generic + * The result is cached + * + * @param clazz The class + * @return True if the class is generic + */ + private static boolean isGeneric(final Class clazz) { + return IS_GENERIC.computeIfAbsent(clazz, c -> c.getTypeParameters().length > 0); + } + + /** + * Formats an include object + * + * @param request The generation request + * @param include The include object + * @param subNodeName The sub node name + * @param sb The string builder + */ + private void formatInclude(final GenerationRequest request, final ParsedInclude include, final String subNodeName, final StringBuilder sb) { + final var subViewVariable = getNextVariableName("view"); + final var source = include.source(); + final var resources = include.resources(); + final var subControllerClass = request.parameters().sourceToControllerName().get(source); + final var subClassName = request.parameters().sourceToGeneratedClassName().get(source); + if (subClassName == null) { + throw new IllegalArgumentException("Unknown include source : " + source); + } + if (resources == null) { + sb.append(" final var ").append(subViewVariable).append(" = new ").append(subClassName).append("(controllersMap, resourceBundlesMap);\n"); + } else { + final var subResourceBundlesMapVariable = getNextVariableName("map"); + final var subBundleVariable = getNextVariableName("bundle"); + sb.append(" final var ").append(subResourceBundlesMapVariable).append(" = new HashMap<>(resourceBundlesMap);\n"); + sb.append(" final var ").append(subBundleVariable).append(" = ResourceBundle.getBundle(\"").append(resources).append("\");\n"); + sb.append(" ").append(subResourceBundlesMapVariable).append(".put(").append(subControllerClass).append(", ").append(subBundleVariable).append(");\n"); + sb.append(" final var ").append(subViewVariable).append(" = new ").append(subClassName).append("(controllersMap, ").append(subResourceBundlesMapVariable).append(");\n"); + } + sb.append(" final var ").append(subNodeName).append(" = ").append(subViewVariable).append(".load();\n"); + final var id = include.controllerId(); + if (id != null) { + final var subControllerVariable = getNextVariableName("controller"); + sb.append(" final var ").append(subControllerVariable).append(" = ").append(subViewVariable).append(".controller();\n"); + injectControllerField(request, id, subControllerVariable, sb); + } + } + + /** + * Formats a property + * + * @param request The generation request + * @param property The property to format + * @param parent The property's parent object + * @param parentVariable The parent variable + * @param sb The string builder + */ + private void formatProperty(final GenerationRequest request, final ParsedProperty property, final ParsedObject parent, final String parentVariable, final StringBuilder sb) { + final var propertyName = property.name(); + final var setMethod = getSetMethod(propertyName); + if (propertyName.equals("fx:id")) { + final var id = property.value(); + injectControllerField(request, id, parentVariable, sb); + } else if (propertyName.equals("fx:controller")) { + if (parent != request.rootObject()) { + throw new IllegalStateException("Invalid nested controller"); + } + } else if (property.sourceType() == EventHandler.class) { + injectControllerMethod(request, property, parentVariable, sb); + } else if (property.sourceType() != null) { + if (hasStaticMethod(property.sourceType(), setMethod)) { + final var method = getStaticMethod(property.sourceType(), setMethod); + final var parameterType = method.getParameterTypes()[1]; + final var arg = getArg(request, property.value(), parameterType); + sb.append(" ").append(property.sourceType().getName()).append(".").append(setMethod).append("(").append(parentVariable).append(", ").append(arg).append(");\n"); + } else { + throw new IllegalStateException("Cannot set " + propertyName + " on " + property.sourceType().getCanonicalName()); + } + } else { + final var getMethod = getGetMethod(propertyName); + if (hasMethod(parent.clazz(), setMethod)) { + final var method = getMethod(parent.clazz(), setMethod); + final var parameterType = method.getParameterTypes()[0]; + final var arg = getArg(request, property.value(), parameterType); + sb.append(" ").append(parentVariable).append(".").append(setMethod).append("(").append(arg).append(");\n"); + } else if (hasMethod(parent.clazz(), getMethod)) { + final var method = getMethod(parent.clazz(), getMethod); + final var returnType = method.getReturnType(); + if (hasMethod(returnType, "addAll")) { + final var arg = getArg(request, property.value(), String.class); + sb.append(" ").append(parentVariable).append(".").append(getMethod).append("().addAll(").append(arg).append(");\n"); + } + } else { + throw new IllegalStateException("Cannot set " + propertyName + " on " + parent.clazz().getCanonicalName()); + } + } + } + + /** + * Injects a controller method + * + * @param request The generation request + * @param property The property to inject + * @param parentVariable The parent variable + * @param sb The string builder + */ + private void injectControllerMethod(final GenerationRequest request, final ParsedProperty property, final String parentVariable, final StringBuilder sb) { + final var injection = getControllerInjection(request); + final var methodInjection = getMethodInjection(request, property, parentVariable, sb); + if (injection.fieldInjectionType() instanceof final ControllerFieldInjectionTypes fieldTypes) { + switch (fieldTypes) { + case FACTORY -> controllerFactoryPostAction.add(methodInjection); + case ASSIGN, SETTERS, REFLECTION -> sb.append(methodInjection); + } + } else { + throw new IllegalArgumentException("Unknown injection type : " + injection.fieldInjectionType()); + } + } + + /** + * Computes the method injection + * + * @param request The generation request + * @param property The property + * @param parentVariable The parent variable + * @param sb The string builder + * @return The method injection + */ + private static String getMethodInjection(final GenerationRequest request, final ParsedProperty property, final String parentVariable, final StringBuilder sb) { + final var setMethod = getSetMethod(property.name()); + final var injection = getControllerInjection(request); + final var controllerMethod = property.value().replace("#", ""); + if (injection.methodInjectionType() instanceof final ControllerMethodsInjectionType methodTypes) { + return switch (methodTypes) { + case REFERENCE -> { + final var hasArgument = request.controllerInfo().handlerHasArgument(controllerMethod); + if (hasArgument) { + yield " " + parentVariable + "." + setMethod + "(controller::" + controllerMethod + ");\n"; + } else { + yield " " + parentVariable + "." + setMethod + "(e -> controller." + controllerMethod + "());\n"; + } + } + case REFLECTION -> + " " + parentVariable + "." + setMethod + "(e -> callMethod(\"" + controllerMethod + "\", e));\n"; + }; + } else { + throw new IllegalArgumentException("Unknown injection type : " + injection.methodInjectionType()); + } + } + + /** + * Formats an argument to a method + * + * @param request The generation request + * @param value The value + * @param parameterType The parameter type + * @return The formatted value + */ + private static String getArg(final GenerationRequest request, final String value, final Class parameterType) { + if (parameterType == String.class && value.startsWith("%")) { + return getBundleValue(request, value.substring(1)); + } else { + return toString(value, parameterType); + } + } + + /** + * Injects the given variable into the controller + * + * @param request The generation request + * @param id The object id + * @param variable The object variable + * @param sb The string builder + */ + private static void injectControllerField(final GenerationRequest request, final String id, final String variable, final StringBuilder sb) { + final var controllerInjection = getControllerInjection(request); + final var controllerInjectionType = controllerInjection.fieldInjectionType(); + if (controllerInjectionType instanceof final ControllerFieldInjectionTypes types) { + switch (types) { + case FACTORY -> + sb.append(" fieldMap.put(\"").append(id).append("\", ").append(variable).append(");\n"); + case ASSIGN -> sb.append(" controller.").append(id).append(" = ").append(variable).append(";\n"); + case SETTERS -> { + final var setMethod = getSetMethod(id); + sb.append(" controller.").append(setMethod).append("(").append(variable).append(");\n"); + } + case REFLECTION -> { + sb.append(" injectField(\"").append(id).append("\", ").append(variable).append(");\n"); + } + } + } else { + throw new IllegalArgumentException("Unknown controller injection type : " + controllerInjectionType); + } + } + + /** + * Gets the controller injection object from the generation request + * + * @param request The generation request + * @return The controller injection + */ + private static ControllerInjection getControllerInjection(final GenerationRequest request) { + final var property = request.rootObject().properties().get("fx:controller"); + if (property == null) { + throw new IllegalArgumentException("Root object must have a controller property"); + } else { + final var id = property.value(); + return request.parameters().controllerInjections().get(id); + } + } + + /** + * Formats the children objects of a property + * + * @param request The generation request + * @param parent The parent object + * @param property The parent property + * @param objects The child objects + * @param parentVariable The parent object variable + * @param sb The string builder + */ + private void formatChild(final GenerationRequest request, final ParsedObject parent, final ParsedProperty property, + final Collection objects, final String parentVariable, final StringBuilder sb) { + final var propertyName = property.name(); + final var variables = objects.stream().map(go -> { + final var vn = getNextVariableName("object"); + format(request, go, vn, sb); + return vn; + }).toList(); + if (variables.size() > 1) { + formatMultipleChildren(variables, propertyName, parent, parentVariable, sb); + } else if (variables.size() == 1) { + final var vn = variables.getFirst(); + formatSingleChild(vn, property, parent, parentVariable, sb); + } + } + + /** + * Formats children objects given that they are more than one + * + * @param variables The children variables + * @param propertyName The property name + * @param parent The parent object + * @param parentVariable The parent object variable + * @param sb The string builder + */ + private static void formatMultipleChildren(final Iterable variables, final String propertyName, final ParsedObject parent, + final String parentVariable, final StringBuilder sb) { + final var getMethod = getGetMethod(propertyName); + if (hasMethod(parent.clazz(), getMethod)) { + sb.append(" ").append(parentVariable).append(".").append(getMethod).append("().addAll(").append(String.join(", ", variables)).append(");\n"); + } + } + + /** + * Formats a single child object + * + * @param variableName The child's variable name + * @param property The parent property + * @param parent The parent object + * @param parentVariable The parent object variable + * @param sb The string builder + */ + private static void formatSingleChild(final String variableName, final ParsedProperty property, final ParsedObject parent, + final String parentVariable, final StringBuilder sb) { + if (property.sourceType() == null) { + formatSingleChildInstance(variableName, property, parent, parentVariable, sb); + } else { + formatSingleChildStatic(variableName, property, parentVariable, sb); + } + } + + /** + * Formats a single child object using an instance method on the parent object + * + * @param variableName The child's variable name + * @param property The parent property + * @param parent The parent object + * @param parentVariable The parent object variable + * @param sb The string builder + */ + private static void formatSingleChildInstance(final String variableName, final ParsedProperty property, final ParsedObject parent, + final String parentVariable, final StringBuilder sb) { + final var setMethod = getSetMethod(property); + final var getMethod = getGetMethod(property); + if (hasMethod(parent.clazz(), setMethod)) { + sb.append(" ").append(parentVariable).append(".").append(setMethod).append("(").append(variableName).append(");\n"); + } else if (hasMethod(parent.clazz(), getMethod)) { + //Probably a list method that has only one element + sb.append(" ").append(parentVariable).append(".").append(getMethod).append("().addAll(").append(variableName).append(");\n"); + } else { + throw new IllegalStateException("Cannot set " + property.name() + " on " + parent.clazz().getCanonicalName()); + } + } + + /** + * Formats a child object using a static method + * + * @param variableName The child's variable name + * @param property The parent property + * @param parentVariable The parent variable + * @param sb The string builder + */ + private static void formatSingleChildStatic(final String variableName, final ParsedProperty property, final String parentVariable, final StringBuilder sb) { + final var setMethod = getSetMethod(property); + if (hasStaticMethod(property.sourceType(), setMethod)) { + sb.append(" ").append(property.sourceType().getName()).append(".").append(setMethod).append("(").append(parentVariable).append(", ").append(variableName).append(");\n"); + } else { + throw new IllegalStateException("Cannot set " + property.name() + " on " + property.sourceType().getCanonicalName()); + } + } + + /** + * Returns the getter method name for the given property + * + * @param property The property + * @return The getter method name + */ + private static String getGetMethod(final ParsedProperty property) { + return getGetMethod(property.name()); + } + + /** + * Returns the getter method name for the given property name + * + * @param propertyName The property name + * @return The getter method name + */ + private static String getGetMethod(final String propertyName) { + return "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); + } + + /** + * Returns the setter method name for the given property + * + * @param property The property + * @return The setter method name + */ + private static String getSetMethod(final ParsedProperty property) { + return getSetMethod(property.name()); + } + + /** + * Returns the setter method name for the given property name + * + * @param propertyName The property name + * @return The setter method name + */ + private static String getSetMethod(final String propertyName) { + return "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); + } + + /** + * Checks if the given class has a method with the given name + * The result is cached + * + * @param clazz The class + * @param methodName The method name + * @return True if the class has a method with the given name + */ + private static boolean hasMethod(final Class clazz, final String methodName) { + final var methodMap = METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>()); + final var method = methodMap.computeIfAbsent(methodName, m -> computeMethod(clazz, m)); + return method != null; + } + + /** + * Gets the method corresponding to the given class and name + * The result is cached + * + * @param clazz The class + * @param methodName The method name + * @return The method + */ + private static Method getMethod(final Class clazz, final String methodName) { + final var methodMap = METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>()); + return methodMap.computeIfAbsent(methodName, m -> computeMethod(clazz, m)); + } + + /** + * Checks if the given class has a method with the given name + * + * @param clazz The class + * @param methodName The method name + * @return True if the class has a method with the given name + */ + private static Method computeMethod(final Class clazz, final String methodName) { + final var matching = Arrays.stream(clazz.getMethods()).filter(m -> { + if (m.getName().equals(methodName) && !Modifier.isStatic(m.getModifiers())) { + final var parameterTypes = m.getParameterTypes(); + return methodName.startsWith("get") ? parameterTypes.length == 0 : parameterTypes.length >= 1; //TODO not very clean + } else { + return false; + } + }).toList(); + if (matching.size() > 1) { + final var varargsFilter = matching.stream().filter(Method::isVarArgs).toList(); + if (varargsFilter.size() == 1) { + return varargsFilter.getFirst(); + } else { + throw new UnsupportedOperationException("Multiple matching methods not supported yet : " + clazz + " - " + methodName); + } + } else if (matching.size() == 1) { + return matching.getFirst(); + } else { + return null; + } + } + + /** + * Checks if the given class has a static method with the given name + * The result is cached + * + * @param clazz The class + * @param methodName The method name + * @return True if the class has a static method with the given name + */ + private static boolean hasStaticMethod(final Class clazz, final String methodName) { + final var methodMap = STATIC_METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>()); + final var method = methodMap.computeIfAbsent(methodName, m -> computeStaticMethod(clazz, m)); + return method != null; + } + + /** + * Gets the static method corresponding to the given class and name + * The result is cached + * + * @param clazz The class + * @param methodName The method name + * @return The method + */ + private static Method getStaticMethod(final Class clazz, final String methodName) { + final var methodMap = STATIC_METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>()); + return methodMap.computeIfAbsent(methodName, m -> computeStaticMethod(clazz, m)); + } + + /** + * Gets the static method corresponding to the given class and name + * + * @param clazz The class name + * @param methodName The method name + * @return The method, or null if not found + */ + private static Method computeStaticMethod(final Class clazz, final String methodName) { + final var matching = Arrays.stream(clazz.getMethods()).filter(m -> { + if (m.getName().equals(methodName) && Modifier.isStatic(m.getModifiers())) { + final var parameterTypes = m.getParameterTypes(); + return parameterTypes.length > 1 && parameterTypes[0] == Node.class; + } else { + return false; + } + }).toList(); + if (matching.size() > 1) { + throw new UnsupportedOperationException("Multiple matching methods not supported yet : " + clazz + " - " + methodName); + } else if (matching.size() == 1) { + return matching.getFirst(); + } else { + return null; + } + } + + private static String getBundleValue(final GenerationRequest request, final String value) { + final var resourceBundleInjectionType = request.parameters().resourceBundleInjection().injectionType(); + if (resourceBundleInjectionType instanceof final ResourceBundleInjectionTypes types) { + return switch (types) { + case CONSTRUCTOR, GET_BUNDLE -> "bundle.getString(\"" + value + "\")"; + case GETTER -> { + if (getControllerInjection(request).fieldInjectionType() == ControllerFieldInjectionTypes.FACTORY) { + throw new UnsupportedOperationException("Factory injection with bundle getter not supported yet"); + } else { + yield "controller.resources().getString(\"" + value + "\")"; + } + } + }; + } else { + throw new IllegalArgumentException("Unknown resource bundle injection type : " + resourceBundleInjectionType); + } + } + + /** + * Gets the constructor arguments as a list of strings + * + * @param constructorArgs The constructor arguments + * @param parsedObject The parsed object + * @return The list of constructor arguments + */ + private static List getListConstructorArgs(final ConstructorArgs constructorArgs, final ParsedObject parsedObject) { + final var args = new ArrayList(constructorArgs.namedArgs().size()); + for (final var entry : constructorArgs.namedArgs().entrySet()) { + final var type = entry.getValue().type(); + final var p = parsedObject.properties().get(entry.getKey()); + if (p == null) { + final var c = parsedObject.children().entrySet().stream().filter(e -> + e.getKey().name().equals(entry.getKey())).findFirst().orElse(null); + if (c == null) { + args.add(toString(entry.getValue().defaultValue(), type)); + } else { + throw new UnsupportedOperationException("Constructor using complex property not supported yet"); + } + } else { + args.add(toString(p.value(), type)); + } + } + return args; + } + + /** + * Gets the constructor arguments that best match the given property names + * + * @param constructors The constructors + * @param allPropertyNames The property names + * @return The matching constructor arguments, or null if no constructor matches and no default constructor exists + */ + private static ConstructorArgs getMatchingConstructorArgs(final Constructor[] constructors, final Set allPropertyNames) { + ConstructorArgs matchingConstructorArgs = null; + for (final var constructor : constructors) { + final var constructorArgs = getConstructorArgs(constructor); + final var matchingArgsCount = getMatchingArgsCount(constructorArgs, allPropertyNames); + if (matchingConstructorArgs == null ? matchingArgsCount > 0 : matchingArgsCount > getMatchingArgsCount(matchingConstructorArgs, allPropertyNames)) { + matchingConstructorArgs = constructorArgs; + } + } + if (matchingConstructorArgs == null) { + return Arrays.stream(constructors).filter(c -> c.getParameterCount() == 0).findFirst().map(c -> new ConstructorArgs(c, new LinkedHashMap<>())).orElse(null); + } else { + return matchingConstructorArgs; + } + } + + /** + * Checks how many arguments of the given constructor match the given property names + * + * @param constructorArgs The constructor arguments + * @param allPropertyNames The property names + * @return The number of matching arguments + */ + private static long getMatchingArgsCount(final ConstructorArgs constructorArgs, final Set allPropertyNames) { + return constructorArgs.namedArgs().keySet().stream().filter(allPropertyNames::contains).count(); + } + + /** + * Computes the constructor arguments for the given constructor + * + * @param constructor The constructor + * @return The constructor arguments + */ + private static ConstructorArgs getConstructorArgs(final Constructor constructor) { + final var namedArgs = new LinkedHashMap(); + final var annotationsArray = constructor.getParameterAnnotations(); + for (var i = 0; i < annotationsArray.length; i++) { + final var annotations = annotationsArray[i]; + final var getNamedArg = Arrays.stream(annotations).filter(NamedArg.class::isInstance).findFirst().orElse(null); + if (getNamedArg != null) { + final var namedArg = (NamedArg) getNamedArg; + final var name = namedArg.value(); + final var clazz = constructor.getParameterTypes()[i]; + namedArgs.put(name, new Parameter(name, constructor.getParameterTypes()[i], namedArg.defaultValue().isEmpty() ? + getDefaultValue(clazz) : namedArg.defaultValue())); + } + } + return new ConstructorArgs(constructor, namedArgs); + } + + /** + * Computes the default value for the given class + * + * @param clazz The class + * @return The value + */ + private static String getDefaultValue(final Class clazz) { + final var primitiveWrappers = Set.of(Integer.class, Byte.class, Short.class, Long.class, Float.class, Double.class); + if (clazz == char.class || clazz == Character.class) { + return "\u0000"; + } else if (clazz == boolean.class || clazz == Boolean.class) { + return "false"; + } else if (clazz.isPrimitive() || primitiveWrappers.contains(clazz)) { + return "0"; + } else { + return "null"; + } + } + + /** + * Computes the string value to use in the generated code + * + * @param value The value + * @param clazz The value class + * @return The computed string value + */ + private static String toString(final String value, final Class clazz) { + final var primitiveWrappers = Set.of(Integer.class, Byte.class, Short.class, Long.class, Float.class, Double.class, Boolean.class); + if (clazz == String.class) { + return "\"" + value.replace("\"", "\\\"") + "\""; + } else if (clazz == char.class || clazz == Character.class) { + return "'" + value + "'"; + } else if (clazz.isPrimitive() || primitiveWrappers.contains(clazz)) { + return value; + } else if (hasValueOf(clazz)) { + if (clazz.isEnum()) { + return clazz.getCanonicalName() + "." + value; + } else { + return clazz.getCanonicalName() + ".valueOf(\"" + value + "\")"; + } + } else { + return value; + } + } + + /** + * Checks if the given class has a valueOf(String) method + * The result is cached + * + * @param clazz The class + * @return True if the class has a valueOf(String) + */ + private static boolean hasValueOf(final Class clazz) { + return HAS_VALUE_OF.computeIfAbsent(clazz, GeneratorImpl::computeHasValueOf); + } + + /** + * Computes if the given class has a valueOf(String) method + * + * @param clazz The class + * @return True if the class has a valueOf(String) + */ + private static boolean computeHasValueOf(final Class clazz) { + try { + clazz.getMethod("valueOf", String.class); + return true; + } catch (final NoSuchMethodException ignored) { + return false; + } + } + + /** + * Computes the next available variable name for the given prefix + * + * @param prefix The variable name prefix + * @return The next available variable name + */ + private String getNextVariableName(final String prefix) { + final var counter = variableNameCounters.computeIfAbsent(prefix, k -> new AtomicInteger(0)); + return prefix + counter.getAndIncrement(); + } + + private record ConstructorArgs(Constructor constructor, + SequencedMap namedArgs) { + private ConstructorArgs { + requireNonNull(constructor); + namedArgs = new LinkedHashMap<>(namedArgs); + } + } + + private record Parameter(String name, Class type, String defaultValue) { + + private Parameter { + requireNonNull(name); + requireNonNull(type); + requireNonNull(defaultValue); + } + } +} diff --git a/core/src/main/java/com/github/gtache/fxml/compiler/impl/ResourceBundleInjectionImpl.java b/core/src/main/java/com/github/gtache/fxml/compiler/impl/ResourceBundleInjectionImpl.java new file mode 100644 index 0000000..af66fe5 --- /dev/null +++ b/core/src/main/java/com/github/gtache/fxml/compiler/impl/ResourceBundleInjectionImpl.java @@ -0,0 +1,21 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.InjectionType; +import com.github.gtache.fxml.compiler.ResourceBundleInjection; + +import java.util.Objects; + +/** + * Implementation of {@link ResourceBundleInjection} + * + * @param injectionType The injection type + * @param bundleName The bundle name + */ +public record ResourceBundleInjectionImpl(InjectionType injectionType, + String bundleName) implements ResourceBundleInjection { + + public ResourceBundleInjectionImpl { + Objects.requireNonNull(injectionType); + Objects.requireNonNull(bundleName); + } +} diff --git a/core/src/main/java/com/github/gtache/fxml/compiler/impl/ResourceBundleInjectionTypes.java b/core/src/main/java/com/github/gtache/fxml/compiler/impl/ResourceBundleInjectionTypes.java new file mode 100644 index 0000000..b129ed1 --- /dev/null +++ b/core/src/main/java/com/github/gtache/fxml/compiler/impl/ResourceBundleInjectionTypes.java @@ -0,0 +1,22 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.InjectionType; +import com.github.gtache.fxml.compiler.ResourceBundleInjection; + +/** + * Base {@link InjectionType}s for {@link ResourceBundleInjection} + */ +public enum ResourceBundleInjectionTypes implements InjectionType { + /** + * Resource bundle is injected in the constructor + */ + CONSTRUCTOR, + /** + * Resource bundle is loaded using getBundle + */ + GET_BUNDLE, + /** + * Resource bundle is retrieved from controller using getter + */ + GETTER +} diff --git a/core/src/main/java/com/github/gtache/fxml/compiler/parsing/impl/ParsedIncludeImpl.java b/core/src/main/java/com/github/gtache/fxml/compiler/parsing/impl/ParsedIncludeImpl.java new file mode 100644 index 0000000..05a1a07 --- /dev/null +++ b/core/src/main/java/com/github/gtache/fxml/compiler/parsing/impl/ParsedIncludeImpl.java @@ -0,0 +1,19 @@ +package com.github.gtache.fxml.compiler.parsing.impl; + +import com.github.gtache.fxml.compiler.parsing.ParsedInclude; +import com.github.gtache.fxml.compiler.parsing.ParsedProperty; + +import java.util.LinkedHashMap; +import java.util.SequencedMap; + +/** + * Implementation of {@link ParsedInclude} + * + * @param properties The object properties + */ +public record ParsedIncludeImpl(SequencedMap properties) implements ParsedInclude { + + public ParsedIncludeImpl { + properties = new LinkedHashMap<>(properties); + } +} diff --git a/core/src/main/java/com/github/gtache/fxml/compiler/parsing/impl/ParsedObjectImpl.java b/core/src/main/java/com/github/gtache/fxml/compiler/parsing/impl/ParsedObjectImpl.java new file mode 100644 index 0000000..46c8301 --- /dev/null +++ b/core/src/main/java/com/github/gtache/fxml/compiler/parsing/impl/ParsedObjectImpl.java @@ -0,0 +1,90 @@ +package com.github.gtache.fxml.compiler.parsing.impl; + +import com.github.gtache.fxml.compiler.parsing.ParsedObject; +import com.github.gtache.fxml.compiler.parsing.ParsedProperty; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Objects; +import java.util.SequencedCollection; +import java.util.SequencedMap; + +/** + * Implementation of {@link ParsedObject} + * + * @param clazz The object class + * @param properties The object properties + * @param children The object children (complex properties) + */ +public record ParsedObjectImpl(Class clazz, SequencedMap properties, + SequencedMap> children) implements ParsedObject { + + public ParsedObjectImpl { + Objects.requireNonNull(clazz); + properties = new LinkedHashMap<>(properties); + children = new LinkedHashMap<>(children); + } + + /** + * Builder for {@link ParsedObjectImpl} + */ + public static class Builder { + + private Class clazz; + private final SequencedMap properties; + private final SequencedMap> children; + + /** + * Creates a new builder + */ + public Builder() { + this.properties = new LinkedHashMap<>(); + this.children = new LinkedHashMap<>(); + } + + /** + * Sets the object class + * + * @param clazz The object class + * @return The builder + */ + public Builder clazz(final Class clazz) { + this.clazz = clazz; + return this; + } + + /** + * Adds a property + * + * @param property The property + * @return The builder + */ + public Builder addProperty(final ParsedProperty property) { + properties.put(property.name(), property); + return this; + } + + /** + * Adds a child + * + * @param property The property + * @param child The child + * @return The builder + */ + public Builder addChild(final ParsedProperty property, final ParsedObject child) { + final var sequence = children.computeIfAbsent(property, k -> new ArrayList<>()); + sequence.add(child); + children.put(property, sequence); + return this; + } + + /** + * Builds the object + * + * @return The object + */ + public ParsedObjectImpl build() { + return new ParsedObjectImpl(clazz, properties, children); + } + } +} diff --git a/core/src/main/java/com/github/gtache/fxml/compiler/parsing/impl/ParsedPropertyImpl.java b/core/src/main/java/com/github/gtache/fxml/compiler/parsing/impl/ParsedPropertyImpl.java new file mode 100644 index 0000000..81f073f --- /dev/null +++ b/core/src/main/java/com/github/gtache/fxml/compiler/parsing/impl/ParsedPropertyImpl.java @@ -0,0 +1,19 @@ +package com.github.gtache.fxml.compiler.parsing.impl; + +import com.github.gtache.fxml.compiler.parsing.ParsedProperty; + +import java.util.Objects; + +/** + * Implementation of {@link ParsedProperty} + * + * @param name The property name + * @param sourceType The property source type + * @param value The property value + */ +public record ParsedPropertyImpl(String name, Class sourceType, String value) implements ParsedProperty { + + public ParsedPropertyImpl { + Objects.requireNonNull(name); + } +} diff --git a/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestControllerInfoImpl.java b/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestControllerInfoImpl.java new file mode 100644 index 0000000..0dcdd65 --- /dev/null +++ b/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestControllerInfoImpl.java @@ -0,0 +1,65 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.ControllerInfo; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class TestControllerInfoImpl { + + private final Map handlerHasArgument; + private final Map> propertyGenericTypes; + private final ControllerInfo info; + + TestControllerInfoImpl() { + this.handlerHasArgument = new HashMap<>(Map.of("one", true, "two", false)); + this.propertyGenericTypes = new HashMap<>(Map.of("one", List.of("a", "b"), "two", List.of())); + this.info = new ControllerInfoImpl(handlerHasArgument, propertyGenericTypes); + } + + @Test + void testHandlerHasArgument() { + assertEquals(handlerHasArgument, info.handlerHasArgument()); + assertTrue(info.handlerHasArgument("one")); + assertFalse(info.handlerHasArgument("two")); + assertTrue(info.handlerHasArgument("three")); + } + + @Test + void testPropertyGenericTypes() { + assertEquals(propertyGenericTypes, info.propertyGenericTypes()); + assertEquals(List.of("a", "b"), info.propertyGenericTypes("one")); + assertEquals(List.of(), info.propertyGenericTypes("two")); + } + + @Test + void testMapsCopied() { + final var originalHandler = Map.copyOf(handlerHasArgument); + final var originalPropertyTypes = Map.copyOf(propertyGenericTypes); + assertEquals(originalHandler, info.handlerHasArgument()); + assertEquals(originalPropertyTypes, info.propertyGenericTypes()); + + handlerHasArgument.clear(); + propertyGenericTypes.clear(); + assertEquals(originalHandler, info.handlerHasArgument()); + assertEquals(originalPropertyTypes, info.propertyGenericTypes()); + } + + @Test + void testUnmodifiable() { + final var infoHandler = info.handlerHasArgument(); + final var infoProperty = info.propertyGenericTypes(); + assertThrows(UnsupportedOperationException.class, infoHandler::clear); + assertThrows(UnsupportedOperationException.class, infoProperty::clear); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new ControllerInfoImpl(null, propertyGenericTypes)); + assertThrows(NullPointerException.class, () -> new ControllerInfoImpl(handlerHasArgument, null)); + } +} diff --git a/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestControllerInjectionImpl.java b/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestControllerInjectionImpl.java new file mode 100644 index 0000000..3545032 --- /dev/null +++ b/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestControllerInjectionImpl.java @@ -0,0 +1,43 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.ControllerInjection; +import com.github.gtache.fxml.compiler.InjectionType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class TestControllerInjectionImpl { + + private final InjectionType fieldInjectionType; + private final InjectionType methodInjectionType; + private final String injectionClass; + private final ControllerInjection controllerInjection; + + TestControllerInjectionImpl(@Mock final InjectionType fieldInjectionType, @Mock final InjectionType methodInjectionType) { + this.fieldInjectionType = Objects.requireNonNull(fieldInjectionType); + this.methodInjectionType = Objects.requireNonNull(methodInjectionType); + this.injectionClass = "class"; + this.controllerInjection = new ControllerInjectionImpl(fieldInjectionType, methodInjectionType, injectionClass); + } + + @Test + void testGetters() { + assertEquals(fieldInjectionType, controllerInjection.fieldInjectionType()); + assertEquals(methodInjectionType, controllerInjection.methodInjectionType()); + assertEquals(injectionClass, controllerInjection.injectionClass()); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new ControllerInjectionImpl(null, methodInjectionType, injectionClass)); + assertThrows(NullPointerException.class, () -> new ControllerInjectionImpl(fieldInjectionType, null, injectionClass)); + assertThrows(NullPointerException.class, () -> new ControllerInjectionImpl(fieldInjectionType, methodInjectionType, null)); + } +} diff --git a/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestGenerationParametersImpl.java b/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestGenerationParametersImpl.java new file mode 100644 index 0000000..cc6ef8a --- /dev/null +++ b/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestGenerationParametersImpl.java @@ -0,0 +1,49 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.ControllerInjection; +import com.github.gtache.fxml.compiler.GenerationParameters; +import com.github.gtache.fxml.compiler.ResourceBundleInjection; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Map; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class TestGenerationParametersImpl { + + private final Map controllerInjections; + private final Map sourceToGeneratedClassName; + private final Map sourceToControllerName; + private final ResourceBundleInjection resourceBundleInjection; + private final GenerationParameters parameters; + + TestGenerationParametersImpl(@Mock final ControllerInjection controllerInjection, @Mock final ResourceBundleInjection resourceBundleInjection) { + this.controllerInjections = Map.of("class", controllerInjection); + this.sourceToGeneratedClassName = Map.of("source", "generated"); + this.sourceToControllerName = Map.of("source", "class"); + this.resourceBundleInjection = Objects.requireNonNull(resourceBundleInjection); + this.parameters = new GenerationParametersImpl(controllerInjections, sourceToGeneratedClassName, sourceToControllerName, resourceBundleInjection); + } + + @Test + void testGetters() { + assertEquals(controllerInjections, parameters.controllerInjections()); + assertEquals(sourceToGeneratedClassName, parameters.sourceToGeneratedClassName()); + assertEquals(sourceToControllerName, parameters.sourceToControllerName()); + assertEquals(resourceBundleInjection, parameters.resourceBundleInjection()); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(null, sourceToGeneratedClassName, sourceToControllerName, resourceBundleInjection)); + assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(controllerInjections, null, sourceToControllerName, resourceBundleInjection)); + assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(controllerInjections, sourceToGeneratedClassName, null, resourceBundleInjection)); + assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(controllerInjections, sourceToGeneratedClassName, sourceToControllerName, null)); + } +} diff --git a/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestGenerationRequestImpl.java b/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestGenerationRequestImpl.java new file mode 100644 index 0000000..f57cf79 --- /dev/null +++ b/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestGenerationRequestImpl.java @@ -0,0 +1,49 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.ControllerInfo; +import com.github.gtache.fxml.compiler.GenerationParameters; +import com.github.gtache.fxml.compiler.GenerationRequest; +import com.github.gtache.fxml.compiler.parsing.ParsedObject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class TestGenerationRequestImpl { + + private final GenerationParameters parameters; + private final ControllerInfo controllerInfo; + private final ParsedObject rootObject; + private final String outputClassName; + private final GenerationRequest request; + + TestGenerationRequestImpl(@Mock final GenerationParameters parameters, @Mock final ControllerInfo controllerInfo, @Mock final ParsedObject rootObject) { + this.parameters = Objects.requireNonNull(parameters); + this.controllerInfo = Objects.requireNonNull(controllerInfo); + this.rootObject = Objects.requireNonNull(rootObject); + this.outputClassName = "class"; + this.request = new GenerationRequestImpl(parameters, controllerInfo, rootObject, outputClassName); + } + + @Test + void testGetters() { + assertEquals(parameters, request.parameters()); + assertEquals(controllerInfo, request.controllerInfo()); + assertEquals(rootObject, request.rootObject()); + assertEquals(outputClassName, request.outputClassName()); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(null, controllerInfo, rootObject, outputClassName)); + assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(parameters, null, rootObject, outputClassName)); + assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(parameters, controllerInfo, null, outputClassName)); + assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(parameters, controllerInfo, rootObject, null)); + } +} diff --git a/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestGeneratorImpl.java b/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestGeneratorImpl.java new file mode 100644 index 0000000..8ee1282 --- /dev/null +++ b/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestGeneratorImpl.java @@ -0,0 +1,4 @@ +package com.github.gtache.fxml.compiler.impl; + +class TestGeneratorImpl { +} diff --git a/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestResourceBundleInjectionImpl.java b/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestResourceBundleInjectionImpl.java new file mode 100644 index 0000000..9c500de --- /dev/null +++ b/core/src/test/java/com/github/gtache/fxml/compiler/impl/TestResourceBundleInjectionImpl.java @@ -0,0 +1,39 @@ +package com.github.gtache.fxml.compiler.impl; + +import com.github.gtache.fxml.compiler.InjectionType; +import com.github.gtache.fxml.compiler.ResourceBundleInjection; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class TestResourceBundleInjectionImpl { + + private final InjectionType injectionType; + private final String bundleName; + private final ResourceBundleInjection resourceBundleInjection; + + TestResourceBundleInjectionImpl(@Mock final InjectionType injectionType) { + this.injectionType = Objects.requireNonNull(injectionType); + this.bundleName = "bundle"; + this.resourceBundleInjection = new ResourceBundleInjectionImpl(injectionType, bundleName); + } + + @Test + void testGetters() { + assertEquals(injectionType, resourceBundleInjection.injectionType()); + assertEquals(bundleName, resourceBundleInjection.bundleName()); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new ResourceBundleInjectionImpl(null, bundleName)); + assertThrows(NullPointerException.class, () -> new ResourceBundleInjectionImpl(injectionType, null)); + } +} diff --git a/core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedIncludeImpl.java b/core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedIncludeImpl.java new file mode 100644 index 0000000..f657c61 --- /dev/null +++ b/core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedIncludeImpl.java @@ -0,0 +1,4 @@ +package com.github.gtache.fxml.compiler.parsing.impl; + +public class TestParsedIncludeImpl { +} diff --git a/core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedObjectImpl.java b/core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedObjectImpl.java new file mode 100644 index 0000000..97c4472 --- /dev/null +++ b/core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedObjectImpl.java @@ -0,0 +1,66 @@ +package com.github.gtache.fxml.compiler.parsing.impl; + +import com.github.gtache.fxml.compiler.parsing.ParsedObject; +import com.github.gtache.fxml.compiler.parsing.ParsedProperty; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.SequencedCollection; +import java.util.SequencedMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class TestParsedObjectImpl { + + private final Class clazz; + private final SequencedMap properties; + private final SequencedMap> children; + private final ParsedObject parsedObject; + + TestParsedObjectImpl(@Mock final ParsedProperty property, @Mock final ParsedObject object) { + this.clazz = Object.class; + this.properties = new LinkedHashMap<>(); + this.properties.put("name", property); + this.children = new LinkedHashMap<>(); + this.children.put(property, List.of(object)); + this.parsedObject = new ParsedObjectImpl(clazz, properties, children); + } + + @Test + void testGetters() { + assertEquals(clazz, parsedObject.clazz()); + assertEquals(properties, parsedObject.properties()); + assertEquals(children, parsedObject.children()); + } + + @Test + void testCopyMap() { + final var originalProperties = parsedObject.properties(); + final var originalChildren = parsedObject.children(); + properties.clear(); + children.clear(); + assertEquals(originalProperties, parsedObject.properties()); + assertEquals(originalChildren, parsedObject.children()); + } + + @Test + void testUnmodifiable() { + final var objectProperties = parsedObject.properties(); + final var objectChildren = parsedObject.children(); + assertThrows(UnsupportedOperationException.class, objectProperties::clear); + assertThrows(UnsupportedOperationException.class, objectChildren::clear); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new ParsedObjectImpl(null, properties, children)); + assertThrows(NullPointerException.class, () -> new ParsedObjectImpl(clazz, null, children)); + assertThrows(NullPointerException.class, () -> new ParsedObjectImpl(clazz, properties, null)); + } +} diff --git a/core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedObjectImplBuilder.java b/core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedObjectImplBuilder.java new file mode 100644 index 0000000..6c1e4fe --- /dev/null +++ b/core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedObjectImplBuilder.java @@ -0,0 +1,108 @@ +package com.github.gtache.fxml.compiler.parsing.impl; + +import com.github.gtache.fxml.compiler.parsing.ParsedObject; +import com.github.gtache.fxml.compiler.parsing.ParsedProperty; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TestParsedObjectImplBuilder { + + private final Class clazz1; + private final Class clazz2; + private final ParsedProperty property1; + private final ParsedProperty property2; + private final ParsedObject object1; + private final ParsedObject object2; + private final ParsedObjectImpl.Builder builder; + + TestParsedObjectImplBuilder(@Mock final ParsedProperty property1, @Mock final ParsedProperty property2, + @Mock final ParsedObject object1, @Mock final ParsedObject object2) { + this.clazz1 = Object.class; + this.clazz2 = String.class; + this.property1 = Objects.requireNonNull(property1); + this.property2 = Objects.requireNonNull(property2); + this.object1 = Objects.requireNonNull(object1); + this.object2 = Objects.requireNonNull(object2); + this.builder = new ParsedObjectImpl.Builder(); + } + + @BeforeEach + void beforeEach() { + when(property1.name()).thenReturn("property1"); + when(property2.name()).thenReturn("property2"); + } + + @Test + void testBuildNullClass() { + assertThrows(NullPointerException.class, builder::build); + } + + @Test + void testClazz() { + builder.clazz(clazz1); + final var built = builder.build(); + assertEquals(clazz1, built.clazz()); + assertEquals(Map.of(), built.properties()); + assertEquals(Map.of(), built.children()); + } + + @Test + void testOverwriteClazz() { + builder.clazz(clazz1); + builder.clazz(clazz2); + final var built = builder.build(); + assertEquals(clazz2, built.clazz()); + assertEquals(Map.of(), built.properties()); + assertEquals(Map.of(), built.children()); + } + + @Test + void testAddProperty() { + builder.clazz(clazz1); + builder.addProperty(property1); + final var built = builder.build(); + assertEquals(Map.of(property1.name(), property1), built.properties()); + assertEquals(Map.of(), built.children()); + } + + @Test + void testAddMultipleProperties() { + builder.clazz(clazz1); + builder.addProperty(property1); + builder.addProperty(property2); + final var built = builder.build(); + assertEquals(Map.of(property1.name(), property1, property2.name(), property2), built.properties()); + assertEquals(Map.of(), built.children()); + } + + @Test + void testAddChild() { + builder.clazz(clazz1); + builder.addChild(property1, object1); + final var built = builder.build(); + assertEquals(Map.of(), built.properties()); + assertEquals(Map.of(property1, List.of(object1)), built.children()); + } + + @Test + void testAddMultipleChildren() { + builder.clazz(clazz1); + builder.addChild(property1, object1); + builder.addChild(property2, object2); + final var built = builder.build(); + assertEquals(Map.of(), built.properties()); + assertEquals(Map.of(property1, List.of(object1), property2, List.of(object2)), built.children()); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedPropertyImpl.java b/core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedPropertyImpl.java new file mode 100644 index 0000000..d2fef5a --- /dev/null +++ b/core/src/test/java/com/github/gtache/fxml/compiler/parsing/impl/TestParsedPropertyImpl.java @@ -0,0 +1,35 @@ +package com.github.gtache.fxml.compiler.parsing.impl; + +import com.github.gtache.fxml.compiler.parsing.ParsedProperty; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TestParsedPropertyImpl { + + private final String name; + private final Class sourceType; + private final String value; + private final ParsedProperty property; + + TestParsedPropertyImpl() { + this.name = "name"; + this.sourceType = Object.class; + this.value = "value"; + this.property = new ParsedPropertyImpl(name, sourceType, value); + } + + @Test + void testGetters() { + assertEquals(name, property.name()); + assertEquals(sourceType, property.sourceType()); + assertEquals(value, property.value()); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new ParsedPropertyImpl(null, sourceType, value)); + assertDoesNotThrow(() -> new ParsedPropertyImpl(name, null, value)); + assertDoesNotThrow(() -> new ParsedPropertyImpl(name, sourceType, null)); + } +} diff --git a/loader/pom.xml b/loader/pom.xml new file mode 100644 index 0000000..7e36094 --- /dev/null +++ b/loader/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + com.github.gtache + fxml-compiler + 1.0-SNAPSHOT + + + fxml-compiler-loader + + + 1.37 + + + + com.github.gtache + fxml-compiler-core + + + org.openjfx + javafx-graphics + ${javafx.version} + provided + + + org.openjfx + javafx-fxml + ${javafx.version} + provided + + + + org.openjfx + javafx-controls + ${javafx.version} + test + + + org.openjfx + javafx-media + ${javafx.version} + test + + + org.openjfx + javafx-web + ${javafx.version} + test + + + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + + + + + + + \ No newline at end of file diff --git a/loader/src/main/java/com/github/gtache/fxml/compiler/parsing/listener/ParsingLoadListener.java b/loader/src/main/java/com/github/gtache/fxml/compiler/parsing/listener/ParsingLoadListener.java new file mode 100644 index 0000000..7b5ddd5 --- /dev/null +++ b/loader/src/main/java/com/github/gtache/fxml/compiler/parsing/listener/ParsingLoadListener.java @@ -0,0 +1,192 @@ +package com.github.gtache.fxml.compiler.parsing.listener; + +import com.github.gtache.fxml.compiler.parsing.ParsedObject; +import com.github.gtache.fxml.compiler.parsing.ParsedProperty; +import com.github.gtache.fxml.compiler.parsing.impl.ParsedIncludeImpl; +import com.github.gtache.fxml.compiler.parsing.impl.ParsedObjectImpl; +import com.github.gtache.fxml.compiler.parsing.impl.ParsedPropertyImpl; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.fxml.LoadListener; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.SequencedMap; + +/** + * {@link LoadListener} implementation parsing the FXML file to {@link ParsedObject} + */ +public class ParsingLoadListener implements LoadListener { + + private final Deque stack; + private final Deque propertyStack; + private final Deque> currentObjectStack; + private ParsedObjectImpl.Builder current; + private ParsedProperty currentProperty; + private List currentObjects; + private Object previousEnd; + private boolean isInclude; + private SequencedMap currentIncludeProperties; + + /** + * Instantiates the listener + */ + public ParsingLoadListener() { + this.stack = new ArrayDeque<>(); + this.propertyStack = new ArrayDeque<>(); + this.currentObjectStack = new ArrayDeque<>(); + this.currentObjects = new ArrayList<>(); + this.currentIncludeProperties = new LinkedHashMap<>(); + } + + /** + * @return The parsed root + */ + public ParsedObject root() { + if (currentObjects != null && currentObjects.size() == 1) { + return currentObjects.getFirst(); + } else { + throw new IllegalStateException("Expected 1 root object, found " + currentObjects); + } + } + + + @Override + public void readImportProcessingInstruction(final String target) { + previousEnd = null; + //Do nothing + } + + @Override + public void readLanguageProcessingInstruction(final String language) { + previousEnd = null; + //Do nothing + } + + @Override + public void readComment(final String comment) { + //Do nothing + } + + @Override + public void beginInstanceDeclarationElement(final Class type) { + previousEnd = null; + if (current != null) { + stack.push(current); + } + current = new ParsedObjectImpl.Builder(); + current.clazz(type); + } + + @Override + public void beginUnknownTypeElement(final String name) { + throw new IllegalArgumentException("Unknown type : " + name); + } + + @Override + public void beginIncludeElement() { + previousEnd = null; + if (isInclude) { + throw new IllegalStateException("Nested include"); + } else if (!currentIncludeProperties.isEmpty()) { + throw new IllegalStateException("Include properties not empty"); + } + isInclude = true; + } + + @Override + public void beginReferenceElement() { + throw new UnsupportedOperationException("Reference not supported yet"); + } + + @Override + public void beginCopyElement() { + throw new UnsupportedOperationException("Copy not supported yet"); + } + + @Override + public void beginRootElement() { + throw new UnsupportedOperationException("Root element not supported yet"); + } + + @Override + public void beginPropertyElement(final String name, final Class sourceType) { + previousEnd = null; + if (currentProperty != null) { + propertyStack.push(currentProperty); + } + currentProperty = new ParsedPropertyImpl(name, sourceType, null); + currentObjectStack.push(currentObjects); + currentObjects = new ArrayList<>(); + } + + @Override + public void beginUnknownStaticPropertyElement(final String name) { + throw new IllegalArgumentException("Unknown static property : " + name); + } + + @Override + public void beginScriptElement() { + throw new UnsupportedOperationException("Script not supported yet"); + } + + @Override + public void beginDefineElement() { + throw new UnsupportedOperationException("Define not supported yet"); + } + + @Override + public void readInternalAttribute(final String name, final String value) { + previousEnd = null; + final var property = new ParsedPropertyImpl(name, null, value); + if (isInclude) { + currentIncludeProperties.put(name, property); + } else { + current.addProperty(property); + } + } + + @Override + public void readPropertyAttribute(final String name, final Class sourceType, final String value) { + previousEnd = null; + current.addProperty(new ParsedPropertyImpl(name, sourceType, value)); + } + + @Override + public void readUnknownStaticPropertyAttribute(final String name, final String value) { + throw new IllegalArgumentException("Unknown static property : " + name); + } + + @Override + public void readEventHandlerAttribute(final String name, final String value) { + current.addProperty(new ParsedPropertyImpl(name, EventHandler.class, value)); + } + + @Override + public void endElement(final Object value) { + if (isInclude) { + currentObjects.add(new ParsedIncludeImpl(currentIncludeProperties)); + currentIncludeProperties.clear(); + isInclude = false; + } else if (previousEnd == value || value instanceof ObservableList) { + //End of property + if (currentProperty == null) { + throw new IllegalStateException("Unexpected end element (property is null) : " + value); + } else { + currentObjects.forEach(go -> current.addChild(currentProperty, go)); + currentObjects = currentObjectStack.isEmpty() ? new ArrayList<>() : currentObjectStack.pop(); + currentProperty = propertyStack.isEmpty() ? null : propertyStack.pop(); + } + } else if (current != null) { + final var built = current.build(); + currentObjects.add(built); + current = stack.isEmpty() ? null : stack.pop(); + previousEnd = value; + } else { + throw new IllegalStateException("Unexpected end element (current is null) : " + value); + } + } +} diff --git a/loader/src/test/java/com/github/gtache/fxml/compiler/loader/AssignIncludeView.java b/loader/src/test/java/com/github/gtache/fxml/compiler/loader/AssignIncludeView.java new file mode 100644 index 0000000..f65a5e8 --- /dev/null +++ b/loader/src/test/java/com/github/gtache/fxml/compiler/loader/AssignIncludeView.java @@ -0,0 +1,439 @@ +package com.github.gtache.fxml.compiler.loader; + +import java.util.Map; +import java.util.ResourceBundle; + + +/** + * Generated code, not thread-safe + */ +public final class AssignIncludeView { + + private final Map, Object> controllersMap; + private final Map, ResourceBundle> resourceBundlesMap; + private final Map> includeControllersMap; + private boolean loaded; + private com.github.gtache.fxml.compiler.loader.IncludeController controller; + + /** + * Instantiates a new AssignIncludeView with no nested controllers and no resource bundle + * + * @param controller The controller + */ + public AssignIncludeView(final com.github.gtache.fxml.compiler.loader.IncludeController controller) { + this(Map.of(com.github.gtache.fxml.compiler.loader.IncludeController.class, controller), Map.of(), Map.of()); + } + + /** + * Instantiates a new AssignIncludeView with no nested controllers + * + * @param controller The controller + * @param resourceBundle The resource bundle + */ + public AssignIncludeView(final com.github.gtache.fxml.compiler.loader.IncludeController controller, final ResourceBundle resourceBundle) { + this(Map.of(com.github.gtache.fxml.compiler.loader.IncludeController.class, controller), Map.of(), Map.of(com.github.gtache.fxml.compiler.loader.IncludeController.class, resourceBundle)); + } + + /** + * Instantiates a new AssignIncludeView with nested controllers and no resource bundle + * + * @param controllersMap The map of controller class to controller + * @param includeControllersMap The map of source to controller class + */ + public AssignIncludeView(final Map, Object> controllersMap, final Map> includeControllersMap) { + this(controllersMap, includeControllersMap, Map.of()); + } + + /** + * Instantiates a new AssignIncludeView with nested controllers + * + * @param controllersMap The map of controller class to controller + * @param includeControllersMap The map of source to controller class + * @param resourceBundlesMap The map of controller class to resource bundle + */ + public AssignIncludeView(final Map, Object> controllersMap, final Map> includeControllersMap, final Map, ResourceBundle> resourceBundlesMap) { + this.controllersMap = Map.copyOf(controllersMap); + this.includeControllersMap = Map.copyOf(includeControllersMap); + this.resourceBundlesMap = Map.copyOf(resourceBundlesMap); + } + + public javafx.scene.Parent load() { + if (loaded) { + throw new IllegalStateException("Already loaded"); + } + final var bundle = resourceBundlesMap.get(com.github.gtache.fxml.compiler.loader.IncludeController.class); + controller = (com.github.gtache.fxml.compiler.loader.IncludeController) controllersMap.get(com.github.gtache.fxml.compiler.loader.IncludeController.class); + final var object0 = new javafx.scene.layout.GridPane(); + controller.gridPane = object0; + object0.setOnInputMethodTextChanged(controller::inputMethodTextChanged); + object0.setOnKeyPressed(e -> controller.keyPressed()); + object0.setOnKeyReleased(controller::keyReleased); + object0.setOnKeyTyped(controller::keyTyped); + final var object1 = new javafx.scene.layout.RowConstraints(); + final var object2 = new javafx.scene.layout.RowConstraints(); + final var object3 = new javafx.scene.layout.RowConstraints(); + final var object4 = new javafx.scene.layout.RowConstraints(); + final var object5 = new javafx.scene.layout.RowConstraints(); + final var object6 = new javafx.scene.layout.RowConstraints(); + final var object7 = new javafx.scene.layout.RowConstraints(); + final var object8 = new javafx.scene.layout.RowConstraints(); + final var object9 = new javafx.scene.layout.RowConstraints(); + final var object10 = new javafx.scene.layout.RowConstraints(); + final var object11 = new javafx.scene.layout.RowConstraints(); + final var object12 = new javafx.scene.layout.RowConstraints(); + final var object13 = new javafx.scene.layout.RowConstraints(); + final var object14 = new javafx.scene.layout.RowConstraints(); + final var object15 = new javafx.scene.layout.RowConstraints(); + final var object16 = new javafx.scene.layout.RowConstraints(); + final var object17 = new javafx.scene.layout.RowConstraints(); + final var object18 = new javafx.scene.layout.RowConstraints(); + object0.getRowConstraints().addAll(object1, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11, object12, object13, object14, object15, object16, object17, object18); + final var object19 = new javafx.scene.layout.ColumnConstraints(); + final var object20 = new javafx.scene.layout.ColumnConstraints(); + final var object21 = new javafx.scene.layout.ColumnConstraints(); + object21.setMinWidth(10.0); + object21.setPrefWidth(100.0); + object0.getColumnConstraints().addAll(object19, object20, object21); + final var object22 = new javafx.scene.control.Button(); + controller.button = object22; + object22.setMnemonicParsing(false); + object22.setText("Button"); + final var object23 = new javafx.scene.control.CheckBox(); + controller.checkBox = object23; + object23.setIndeterminate(true); + object23.setMnemonicParsing(false); + object23.setText("CheckBox"); + javafx.scene.layout.GridPane.setColumnIndex(object23, 1); + final var object24 = new javafx.scene.control.ChoiceBox(); + controller.choiceBox = object24; + object24.setCacheShape(false); + object24.setCenterShape(false); + object24.setDisable(true); + object24.setFocusTraversable(false); + object24.setPrefWidth(150.0); + object24.setScaleShape(false); + object24.setVisible(false); + javafx.scene.layout.GridPane.setRowIndex(object24, 1); + final var object25 = new javafx.scene.control.ColorPicker(); + controller.colorPicker = object25; + object25.setNodeOrientation(javafx.geometry.NodeOrientation.LEFT_TO_RIGHT); + object25.setOpacity(0.5); + javafx.scene.layout.GridPane.setColumnIndex(object25, 1); + javafx.scene.layout.GridPane.setRowIndex(object25, 1); + final var object26 = new javafx.scene.paint.Color(0.7894737124443054, 0.08771929889917374, 0.08771929889917374, 1); + controller.color = object26; + object25.setValue(object26); + final var object27 = new javafx.geometry.Insets(5.0, 4.0, 3.0, 2.0); + object25.setOpaqueInsets(object27); + final var object28 = new javafx.scene.control.ComboBox(); + controller.comboBox = object28; + object28.setEditable(true); + object28.setPrefWidth(150.0); + object28.setPromptText("Text"); + object28.setVisibleRowCount(5); + javafx.scene.layout.GridPane.setRowIndex(object28, 2); + final var object29 = javafx.scene.Cursor.CLOSED_HAND; + object28.setCursor(object29); + final var object30 = new javafx.scene.effect.Bloom(); + object28.setEffect(object30); + final var object31 = new javafx.scene.control.DatePicker(); + controller.datePicker = object31; + object31.setShowWeekNumbers(true); + object31.setStyle("-fx-background-color: #ffffff;"); + javafx.scene.layout.GridPane.setColumnIndex(object31, 1); + javafx.scene.layout.GridPane.setRowIndex(object31, 2); + final var object32 = new javafx.scene.web.HTMLEditor(); + controller.htmlEditor = object32; + object32.setHtmlText(""); + object32.setPrefHeight(200.0); + object32.setPrefWidth(506.0); + object32.getStyleClass().addAll("clazz"); + object32.getStylesheets().addAll("@style.css"); + javafx.scene.layout.GridPane.setRowIndex(object32, 3); + final var object33 = new javafx.scene.control.Hyperlink(); + controller.hyperlink = object33; + object33.setText("Hyperlink"); + javafx.scene.layout.GridPane.setColumnIndex(object33, 1); + javafx.scene.layout.GridPane.setRowIndex(object33, 3); + final var object34 = new javafx.scene.image.ImageView(); + controller.imageView = object34; + object34.setFitHeight(150.0); + object34.setFitWidth(200.0); + object34.setPickOnBounds(true); + object34.setPreserveRatio(true); + javafx.scene.layout.GridPane.setRowIndex(object34, 4); + final var object35 = new javafx.scene.control.Label(); + controller.label = object35; + object35.setAccessibleHelp("TTTTT"); + object35.setAccessibleText("TTT"); + object35.setBlendMode(javafx.scene.effect.BlendMode.ADD); + object35.setCache(true); + object35.setCacheHint(javafx.scene.CacheHint.QUALITY); + object35.setDepthTest(javafx.scene.DepthTest.ENABLE); + object35.setMnemonicParsing(true); + object35.setMouseTransparent(true); + object35.setText(bundle.getString("include.label")); + javafx.scene.layout.GridPane.setColumnIndex(object35, 1); + javafx.scene.layout.GridPane.setRowIndex(object35, 4); + final var object36 = new javafx.scene.control.ListView(); + controller.listView = object36; + object36.setFixedCellSize(20.0); + object36.setNodeOrientation(javafx.geometry.NodeOrientation.RIGHT_TO_LEFT); + object36.setOrientation(javafx.geometry.Orientation.HORIZONTAL); + object36.setPrefHeight(200.0); + object36.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setRowIndex(object36, 5); + final var object37 = new javafx.scene.media.MediaView(); + controller.mediaView = object37; + object37.setFitHeight(200.0); + object37.setFitWidth(200.0); + javafx.scene.layout.GridPane.setColumnIndex(object37, 1); + javafx.scene.layout.GridPane.setColumnSpan(object37, 2); + javafx.scene.layout.GridPane.setRowIndex(object37, 5); + javafx.scene.layout.GridPane.setRowSpan(object37, 2); + final var object38 = new javafx.scene.control.MenuBar(); + controller.menuBar = object38; + javafx.scene.layout.GridPane.setHalignment(object38, javafx.geometry.HPos.RIGHT); + javafx.scene.layout.GridPane.setHgrow(object38, javafx.scene.layout.Priority.ALWAYS); + javafx.scene.layout.GridPane.setRowIndex(object38, 7); + javafx.scene.layout.GridPane.setValignment(object38, javafx.geometry.VPos.BASELINE); + javafx.scene.layout.GridPane.setVgrow(object38, javafx.scene.layout.Priority.SOMETIMES); + final var object39 = new javafx.scene.control.Menu(); + controller.menu1 = object39; + object39.setMnemonicParsing(false); + object39.setText("File"); + final var object40 = new javafx.scene.control.MenuItem(); + controller.menuItem1 = object40; + object40.setMnemonicParsing(false); + object40.setText("Close"); + object39.getItems().addAll(object40); + final var object41 = new javafx.scene.control.Menu(); + object41.setMnemonicParsing(false); + object41.setText("Edit"); + final var object42 = new javafx.scene.control.MenuItem(); + object42.setMnemonicParsing(false); + object42.setText("Delete"); + object41.getItems().addAll(object42); + final var object43 = new javafx.scene.control.Menu(); + object43.setMnemonicParsing(false); + object43.setText("Help"); + final var object44 = new javafx.scene.control.MenuItem(); + object44.setMnemonicParsing(false); + object44.setText("About"); + object43.getItems().addAll(object44); + object38.getMenus().addAll(object39, object41, object43); + final var object45 = new javafx.scene.control.MenuButton(); + controller.menuButton = object45; + object45.setMnemonicParsing(false); + object45.setText("MenuButton"); + javafx.scene.layout.GridPane.setColumnIndex(object45, 1); + javafx.scene.layout.GridPane.setRowIndex(object45, 7); + final var object46 = new javafx.scene.control.MenuItem(); + object46.setMnemonicParsing(false); + object46.setText("Action 1"); + final var object47 = new javafx.scene.control.MenuItem(); + object47.setMnemonicParsing(false); + object47.setText("Action 2"); + object45.getItems().addAll(object46, object47); + final var object48 = new javafx.geometry.Insets(5.0, 4.0, 3.0, 2.0); + javafx.scene.layout.GridPane.setMargin(object45, object48); + final var object49 = new javafx.scene.control.Pagination(); + controller.pagination = object49; + object49.setPrefHeight(200.0); + object49.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setRowIndex(object49, 8); + final var object50 = new javafx.geometry.Insets(5.0, 4.0, 3.0, 2.0); + object49.setPadding(object50); + final var object51 = new javafx.scene.control.PasswordField(); + controller.passwordField = object51; + object51.setMaxHeight(6.0); + object51.setMaxWidth(5.0); + object51.setMinHeight(2.0); + object51.setMinWidth(1.0); + object51.setPrefColumnCount(7); + object51.setPrefHeight(4.0); + object51.setPrefWidth(3.0); + javafx.scene.layout.GridPane.setColumnIndex(object51, 1); + javafx.scene.layout.GridPane.setRowIndex(object51, 8); + final var object52 = new javafx.scene.control.ProgressBar(); + controller.progressBar = object52; + object52.setLayoutX(10.0); + object52.setLayoutY(20.0); + object52.setPrefWidth(200.0); + object52.setProgress(0.0); + javafx.scene.layout.GridPane.setRowIndex(object52, 9); + final var object53 = new javafx.scene.control.ProgressIndicator(); + controller.progressIndicator = object53; + object53.setProgress(0.0); + object53.setRotate(2.0); + javafx.scene.layout.GridPane.setColumnIndex(object53, 1); + javafx.scene.layout.GridPane.setRowIndex(object53, 9); + final var object54 = new javafx.geometry.Point3D(4.0, 5.0, 6.0); + object53.setRotationAxis(object54); + final var object55 = new javafx.scene.control.RadioButton(); + controller.radioButton = object55; + object55.setMnemonicParsing(false); + object55.setScaleX(7.0); + object55.setScaleY(2.0); + object55.setScaleZ(3.0); + object55.setText("RadioButton"); + object55.setTranslateX(4.0); + object55.setTranslateY(5.0); + object55.setTranslateZ(6.0); + javafx.scene.layout.GridPane.setRowIndex(object55, 10); + final var object56 = new javafx.scene.control.ScrollBar(); + controller.scrollBarH = object56; + javafx.scene.layout.GridPane.setColumnIndex(object56, 1); + javafx.scene.layout.GridPane.setRowIndex(object56, 10); + final var object57 = new javafx.scene.control.ScrollBar(); + controller.scrollBarV = object57; + object57.setOrientation(javafx.geometry.Orientation.VERTICAL); + javafx.scene.layout.GridPane.setRowIndex(object57, 11); + final var object58 = new javafx.scene.control.Separator(); + controller.separatorH = object58; + object58.setOnDragDetected(controller::dragDetected); + object58.setOnDragDone(controller::dragDone); + object58.setOnDragDropped(controller::dragDropped); + object58.setOnDragEntered(controller::dragEntered); + object58.setOnDragExited(controller::dragExited); + object58.setOnDragOver(controller::dragOver); + object58.setOnMouseDragEntered(controller::mouseDragEntered); + object58.setOnMouseDragExited(controller::mouseDragExited); + object58.setOnMouseDragOver(controller::mouseDragOver); + object58.setOnMouseDragReleased(controller::mouseDragReleased); + object58.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setColumnIndex(object58, 1); + javafx.scene.layout.GridPane.setRowIndex(object58, 11); + final var object59 = new javafx.scene.control.Separator(); + controller.separatorV = object59; + object59.setOrientation(javafx.geometry.Orientation.VERTICAL); + object59.setPrefHeight(200.0); + javafx.scene.layout.GridPane.setRowIndex(object59, 12); + final var object60 = new javafx.scene.control.Slider(); + controller.sliderH = object60; + object60.setOnContextMenuRequested(controller::contextMenuRequested); + object60.setOnMouseClicked(e -> controller.mouseClicked()); + object60.setOnMouseDragged(controller::mouseDragged); + object60.setOnMouseEntered(controller::mouseEntered); + object60.setOnMouseExited(controller::mouseExited); + object60.setOnMouseMoved(controller::mouseMoved); + object60.setOnMousePressed(controller::mousePressed); + object60.setOnMouseReleased(controller::mouseReleased); + object60.setOnScroll(controller::onScroll); + object60.setOnScrollFinished(controller::onScrollFinished); + object60.setOnScrollStarted(controller::onScrollStarted); + javafx.scene.layout.GridPane.setColumnIndex(object60, 1); + javafx.scene.layout.GridPane.setRowIndex(object60, 12); + final var object61 = new javafx.scene.control.Slider(); + controller.sliderV = object61; + object61.setOnZoom(controller::onZoom); + object61.setOnZoomFinished(controller::onZoomFinished); + object61.setOnZoomStarted(controller::onZoomStarted); + object61.setOrientation(javafx.geometry.Orientation.VERTICAL); + javafx.scene.layout.GridPane.setRowIndex(object61, 13); + final var object62 = new javafx.scene.control.Spinner(); + controller.spinner = object62; + javafx.scene.layout.GridPane.setColumnIndex(object62, 1); + javafx.scene.layout.GridPane.setRowIndex(object62, 13); + final var object63 = new javafx.scene.control.SplitMenuButton(); + controller.splitMenuButton = object63; + object63.setMnemonicParsing(false); + object63.setText("SplitMenuButton"); + javafx.scene.layout.GridPane.setRowIndex(object63, 14); + final var object64 = new javafx.scene.control.MenuItem(); + controller.item1 = object64; + object64.setMnemonicParsing(false); + object64.setText("Action 1"); + final var object65 = new javafx.scene.control.MenuItem(); + controller.item2 = object65; + object65.setMnemonicParsing(false); + object65.setText("Action 2"); + object63.getItems().addAll(object64, object65); + final var object66 = new javafx.scene.control.TableView(); + controller.tableView = object66; + object66.setPrefHeight(200.0); + object66.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setColumnIndex(object66, 1); + javafx.scene.layout.GridPane.setRowIndex(object66, 14); + final var object67 = new javafx.scene.control.TableColumn(); + controller.tableColumn1 = object67; + object67.setPrefWidth(75.0); + object67.setText("C1"); + final var object68 = new javafx.scene.control.TableColumn(); + controller.tableColumn2 = object68; + object68.setPrefWidth(75.0); + object68.setText("C2"); + object66.getColumns().addAll(object67, object68); + final var object69 = new javafx.scene.control.TextArea(); + controller.textArea = object69; + object69.setPrefHeight(200.0); + object69.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setRowIndex(object69, 15); + final var object70 = new javafx.scene.control.TextField(); + controller.textField = object70; + javafx.scene.layout.GridPane.setColumnIndex(object70, 1); + javafx.scene.layout.GridPane.setRowIndex(object70, 15); + final var object71 = new javafx.scene.control.ToggleButton(); + object71.setMnemonicParsing(false); + object71.setOnAction(controller::onAction); + object71.setOnRotate(controller::onRotate); + object71.setOnRotationFinished(controller::onRotationFinished); + object71.setOnRotationStarted(controller::onRotationStarted); + object71.setText("ToggleButton"); + javafx.scene.layout.GridPane.setRowIndex(object71, 16); + final var object72 = new javafx.scene.control.TreeTableView>(); + controller.treeTableView = object72; + object72.setPrefHeight(200.0); + object72.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setColumnIndex(object72, 1); + javafx.scene.layout.GridPane.setRowIndex(object72, 16); + final var object73 = new javafx.scene.control.TreeTableColumn, String>(); + controller.treeTableColumn1 = object73; + object73.setOnEditCancel(controller::onEditCancel); + object73.setOnEditCommit(controller::onEditCommit); + object73.setOnEditStart(controller::onEditStart); + object73.setPrefWidth(75.0); + object73.setText("C1"); + final var object74 = new javafx.scene.control.TreeTableColumn, Integer>(); + controller.treeTableColumn2 = object74; + object74.setPrefWidth(75.0); + object74.setSortType(javafx.scene.control.TreeTableColumn.SortType.DESCENDING); + object74.setText("C2"); + object72.getColumns().addAll(object73, object74); + final var object75 = new javafx.scene.control.TreeView(); + controller.treeView = object75; + object75.setOnSwipeDown(controller::onSwipeDown); + object75.setOnSwipeLeft(controller::onSwipeLeft); + object75.setOnSwipeRight(controller::onSwipeRight); + object75.setOnSwipeUp(controller::onSwipeUp); + object75.setPrefHeight(200.0); + object75.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setRowIndex(object75, 17); + final var object76 = new javafx.scene.web.WebView(); + controller.webView = object76; + object76.setOnTouchMoved(controller::onTouchMoved); + object76.setOnTouchPressed(controller::onTouchPressed); + object76.setOnTouchReleased(controller::onTouchReleased); + object76.setOnTouchStationary(controller::onTouchStationary); + object76.setPrefHeight(200.0); + object76.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setColumnIndex(object76, 1); + javafx.scene.layout.GridPane.setRowIndex(object76, 17); + object0.getChildren().addAll(object22, object23, object24, object25, object28, object31, object32, object33, object34, object35, object36, object37, object38, object45, object49, object51, object52, object53, object55, object56, object57, object58, object59, object60, object61, object62, object63, object66, object69, object70, object71, object72, object75, object76); + controller.initialize(); + loaded = true; + return object0; + } + + + /** + * @return The controller + */ + public com.github.gtache.fxml.compiler.loader.IncludeController controller() { + if (loaded) { + return controller; + } else { + throw new IllegalStateException("Not loaded"); + } + } +} diff --git a/loader/src/test/java/com/github/gtache/fxml/compiler/loader/AssignTestView.java b/loader/src/test/java/com/github/gtache/fxml/compiler/loader/AssignTestView.java new file mode 100644 index 0000000..af5acb0 --- /dev/null +++ b/loader/src/test/java/com/github/gtache/fxml/compiler/loader/AssignTestView.java @@ -0,0 +1,198 @@ +package com.github.gtache.fxml.compiler.loader; + +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + + +/** + * Generated code, not thread-safe + */ +public final class AssignTestView { + + private final Map, Object> controllersMap; + private final Map, ResourceBundle> resourceBundlesMap; + private final Map> includeControllersMap; + private boolean loaded; + private com.github.gtache.fxml.compiler.loader.TestController controller; + + /** + * Instantiates a new AssignTestView with no nested controllers and no resource bundle + * + * @param controller The controller + */ + public AssignTestView(final com.github.gtache.fxml.compiler.loader.TestController controller) { + this(Map.of(com.github.gtache.fxml.compiler.loader.TestController.class, controller), Map.of(), Map.of()); + } + + /** + * Instantiates a new AssignTestView with no nested controllers + * + * @param controller The controller + * @param resourceBundle The resource bundle + */ + public AssignTestView(final com.github.gtache.fxml.compiler.loader.TestController controller, final ResourceBundle resourceBundle) { + this(Map.of(com.github.gtache.fxml.compiler.loader.TestController.class, controller), Map.of(), Map.of(com.github.gtache.fxml.compiler.loader.TestController.class, resourceBundle)); + } + + /** + * Instantiates a new AssignTestView with nested controllers and no resource bundle + * + * @param controllersMap The map of controller class to controller + * @param includeControllersMap The map of source to controller class + */ + public AssignTestView(final Map, Object> controllersMap, final Map> includeControllersMap) { + this(controllersMap, includeControllersMap, Map.of()); + } + + /** + * Instantiates a new AssignTestView with nested controllers + * + * @param controllersMap The map of controller class to controller + * @param includeControllersMap The map of source to controller class + * @param resourceBundlesMap The map of controller class to resource bundle + */ + public AssignTestView(final Map, Object> controllersMap, final Map> includeControllersMap, final Map, ResourceBundle> resourceBundlesMap) { + this.controllersMap = Map.copyOf(controllersMap); + this.includeControllersMap = Map.copyOf(includeControllersMap); + this.resourceBundlesMap = Map.copyOf(resourceBundlesMap); + } + + public javafx.scene.Parent load() { + if (loaded) { + throw new IllegalStateException("Already loaded"); + } + final var bundle = resourceBundlesMap.get(com.github.gtache.fxml.compiler.loader.TestController.class); + controller = (com.github.gtache.fxml.compiler.loader.TestController) controllersMap.get(com.github.gtache.fxml.compiler.loader.TestController.class); + final var object0 = new javafx.scene.layout.BorderPane(); + final var object1 = new javafx.scene.layout.VBox(); + javafx.scene.layout.BorderPane.setAlignment(object1, javafx.geometry.Pos.CENTER); + final var object2 = new javafx.scene.layout.HBox(); + object2.setAlignment(javafx.geometry.Pos.CENTER); + object2.setSpacing(10.0); + final var object3 = new javafx.scene.control.Slider(); + controller.playSlider = object3; + javafx.scene.layout.HBox.setHgrow(object3, javafx.scene.layout.Priority.ALWAYS); + final var object4 = new javafx.geometry.Insets(0, 0, 0, 10.0); + object3.setPadding(object4); + final var object5 = new javafx.scene.control.Label(); + controller.playLabel = object5; + object5.setText("Label"); + final var object6 = new javafx.geometry.Insets(0, 10.0, 0, 0); + object5.setPadding(object6); + object2.getChildren().addAll(object3, object5); + final var object7 = new javafx.geometry.Insets(10.0, 0, 0, 0); + object2.setPadding(object7); + final var object8 = new javafx.scene.layout.HBox(); + object8.setAlignment(javafx.geometry.Pos.CENTER); + object8.setSpacing(10.0); + final var object9 = new javafx.scene.control.Button(); + controller.playButton = object9; + object9.setMnemonicParsing(false); + object9.setOnAction(controller::playPressed); + final var object10 = new javafx.geometry.Insets(0, 20.0, 0, 0); + javafx.scene.layout.HBox.setMargin(object9, object10); + final var object11 = new javafx.scene.control.Label(); + object11.setText(bundle.getString("media.volume.label")); + final var object12 = new javafx.scene.control.Slider(); + controller.volumeSlider = object12; + object12.setValue(100); + final var object13 = new javafx.scene.control.Label(); + controller.volumeValueLabel = object13; + object13.setText("Label"); + final var class0 = includeControllersMap.get("includeView.fxml"); + final var map0 = new HashMap<>(resourceBundlesMap); + final var bundle0 = ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.IncludeBundle"); + map0.put(class0, bundle0); + final var view0 = new com.github.gtache.fxml.compiler.loader.AssignIncludeView(controllersMap, includeControllersMap, map0); + final var object14 = view0.load(); + final var controller0 = view0.controller(); + controller.includeController = controller0; + object8.getChildren().addAll(object9, object11, object12, object13, object14); + final var object15 = new javafx.geometry.Insets(10.0, 10.0, 10.0, 10.0); + object8.setPadding(object15); + object1.getChildren().addAll(object2, object8); + object0.setBottom(object1); + final var object16 = new javafx.scene.layout.VBox(); + controller.vbox = object16; + final var object17 = new javafx.scene.control.ToolBar(); + controller.toolBar = object17; + final var object18 = new javafx.scene.control.TitledPane(); + controller.titledPane = object18; + final var object19 = new javafx.scene.layout.TilePane(); + controller.tilePane = object19; + final var object20 = new javafx.scene.text.TextFlow(); + controller.textFlow = object20; + final var object21 = new javafx.scene.control.TabPane(); + controller.tabPane = object21; + final var object22 = new javafx.scene.control.Tab(); + controller.tab = object22; + final var object23 = new javafx.scene.layout.StackPane(); + controller.stackPane = object23; + final var object24 = new javafx.scene.control.SplitPane(); + controller.splitPane = object24; + final var object25 = new javafx.scene.control.ScrollPane(); + controller.scrollPane = object25; + final var object26 = new javafx.scene.layout.Pane(); + controller.pane = object26; + final var object27 = new javafx.scene.layout.HBox(); + controller.hbox = object27; + final var object28 = new javafx.scene.Group(); + controller.group = object28; + final var object29 = new javafx.scene.layout.GridPane(); + controller.gridPane = object29; + final var object30 = new javafx.scene.layout.ColumnConstraints(); + controller.columnConstraints = object30; + object30.setHgrow(javafx.scene.layout.Priority.SOMETIMES); + object30.setMinWidth(10.0); + object29.getColumnConstraints().addAll(object30); + final var object31 = new javafx.scene.layout.RowConstraints(); + object31.setMinHeight(10.0); + object31.setVgrow(javafx.scene.layout.Priority.SOMETIMES); + object29.getRowConstraints().addAll(object31); + final var object32 = new javafx.scene.layout.FlowPane(); + controller.flowPane = object32; + final var object33 = new javafx.scene.control.DialogPane(); + controller.dialogPane = object33; + final var object34 = new javafx.scene.control.ButtonBar(); + controller.buttonBar = object34; + final var object35 = new javafx.scene.layout.AnchorPane(); + controller.anchorPane = object35; + final var object36 = new javafx.scene.control.Label(); + object36.setManaged(false); + object35.getChildren().addAll(object36); + object34.getButtons().addAll(object35); + object33.setContent(object34); + object32.getChildren().addAll(object33); + object29.getChildren().addAll(object32); + object28.getChildren().addAll(object29); + object27.getChildren().addAll(object28); + object26.getChildren().addAll(object27); + object25.setContent(object26); + object24.getItems().addAll(object25); + object23.getChildren().addAll(object24); + object22.setContent(object23); + object21.getTabs().addAll(object22); + object20.getChildren().addAll(object21); + object19.getChildren().addAll(object20); + object18.setContent(object19); + object17.getItems().addAll(object18); + object16.getChildren().addAll(object17); + object0.setCenter(object16); + controller.initialize(); + loaded = true; + return object0; + } + + + /** + * @return The controller + */ + public com.github.gtache.fxml.compiler.loader.TestController controller() { + if (loaded) { + return controller; + } else { + throw new IllegalStateException("Not loaded"); + } + } +} \ No newline at end of file diff --git a/loader/src/test/java/com/github/gtache/fxml/compiler/loader/CreationBenchmark.java b/loader/src/test/java/com/github/gtache/fxml/compiler/loader/CreationBenchmark.java new file mode 100644 index 0000000..14d93af --- /dev/null +++ b/loader/src/test/java/com/github/gtache/fxml/compiler/loader/CreationBenchmark.java @@ -0,0 +1,128 @@ +package com.github.gtache.fxml.compiler.loader; + +import javafx.application.Platform; +import javafx.fxml.FXMLLoader; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.io.IOException; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; + +@Fork(1) +@State(Scope.Benchmark) +public class CreationBenchmark { + + public static void main(final String[] args) throws java.io.IOException { + org.openjdk.jmh.Main.main(args); + } + + @Setup(Level.Trial) + public void setup() { + Platform.startup(() -> { + }); + } + + @Benchmark + public Object useFXMLLoader() { + return CompletableFuture.supplyAsync(() -> { + final var loader = new FXMLLoader(getClass().getResource("/com/github/gtache/fxml/compiler/loader/includeView.fxml")); + loader.setResources(ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.IncludeBundle")); + try { + return loader.load(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + }, Platform::runLater).join(); + } + + @Benchmark + public Object useFXMLLoaderInclude() { + return CompletableFuture.supplyAsync(() -> { + final var loader = new FXMLLoader(getClass().getResource("/com/github/gtache/fxml/compiler/loader/testView.fxml")); + loader.setResources(ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.TestBundle")); + try { + return loader.load(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + }, Platform::runLater).join(); + } + + @Benchmark + public Object useReflection() { + return CompletableFuture.supplyAsync(() -> { + final var view = new ReflectionIncludeView(new IncludeController(), ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.IncludeBundle")); + return view.load(); + }, Platform::runLater).join(); + } + + @Benchmark + public Object useReflectionInclude() { + return CompletableFuture.supplyAsync(() -> { + final var view = new ReflectionTestView(Map.of(TestController.class, new TestController(), IncludeController.class, new IncludeController()), Map.of("includeView.fxml", IncludeController.class), + Map.of(TestController.class, ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.TestBundle"), + IncludeController.class, ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.IncludeBundle"))); + return view.load(); + }, Platform::runLater).join(); + } + + @Benchmark + public Object useAssignment() { + return CompletableFuture.supplyAsync(() -> { + final var view = new AssignIncludeView(new IncludeController(), ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.IncludeBundle")); + return view.load(); + }, Platform::runLater).join(); + } + + @Benchmark + public Object useAssignmentInclude() { + return CompletableFuture.supplyAsync(() -> { + final var view = new AssignTestView(Map.of(TestController.class, new TestController(), IncludeController.class, new IncludeController()), Map.of("includeView.fxml", IncludeController.class), + Map.of(TestController.class, ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.TestBundle"), + IncludeController.class, ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.IncludeBundle"))); + return view.load(); + }, Platform::runLater).join(); + } + + @Benchmark + public Object useFactory() { + return CompletableFuture.supplyAsync(() -> { + final var view = new FactoryIncludeView(c -> new IncludeController(), ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.IncludeBundle")); + return view.load(); + }, Platform::runLater).join(); + } + + @Benchmark + public Object useFactoryInclude() { + return CompletableFuture.supplyAsync(() -> { + final var view = new FactoryIncludeView(Map.of(TestController.class, c -> new TestController(), IncludeController.class, c -> new IncludeController()), Map.of("includeView.fxml", IncludeController.class), + Map.of(TestController.class, ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.TestBundle"), + IncludeController.class, ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.IncludeBundle"))); + return view.load(); + }, Platform::runLater).join(); + } + + @Benchmark + public Object useSetters() { + return CompletableFuture.supplyAsync(() -> { + final var view = new SettersIncludeView(new IncludeController(), ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.IncludeBundle")); + return view.load(); + }, Platform::runLater).join(); + } + + @Benchmark + public Object useSettersInclude() { + return CompletableFuture.supplyAsync(() -> { + final var view = new SettersIncludeView(Map.of(TestController.class, new TestController(), IncludeController.class, new IncludeController()), Map.of("includeView.fxml", IncludeController.class), + Map.of(TestController.class, ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.TestBundle"), + IncludeController.class, ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.IncludeBundle"))); + return view.load(); + }, Platform::runLater).join(); + } +} diff --git a/loader/src/test/java/com/github/gtache/fxml/compiler/loader/FactoryIncludeView.java b/loader/src/test/java/com/github/gtache/fxml/compiler/loader/FactoryIncludeView.java new file mode 100644 index 0000000..5a2e9f7 --- /dev/null +++ b/loader/src/test/java/com/github/gtache/fxml/compiler/loader/FactoryIncludeView.java @@ -0,0 +1,444 @@ +package com.github.gtache.fxml.compiler.loader; + +import com.github.gtache.fxml.compiler.ControllerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + + +/** + * Generated code, not thread-safe + */ +public final class FactoryIncludeView { + + private final Map, ControllerFactory> controllersMap; + private final Map, ResourceBundle> resourceBundlesMap; + private final Map> includeControllersMap; + private boolean loaded; + private com.github.gtache.fxml.compiler.loader.IncludeController controller; + + /** + * Instantiates a new FactoryIncludeView with no nested controllers and no resource bundle + * + * @param controllerFactory The controller factory + */ + public FactoryIncludeView(final ControllerFactory controllerFactory) { + this(Map.of(com.github.gtache.fxml.compiler.loader.IncludeController.class, controllerFactory), Map.of(), Map.of()); + } + + /** + * Instantiates a new FactoryIncludeView with no nested controllers + * + * @param controllerFactory The controller factory + * @param resourceBundle The resource bundle + */ + public FactoryIncludeView(final ControllerFactory controllerFactory, final ResourceBundle resourceBundle) { + this(Map.of(com.github.gtache.fxml.compiler.loader.IncludeController.class, controllerFactory), Map.of(), Map.of(com.github.gtache.fxml.compiler.loader.IncludeController.class, resourceBundle)); + } + + /** + * Instantiates a new FactoryIncludeView with nested controllers and no resource bundle + * + * @param controllersMap The map of controller class to controller factory + * @param includeControllersMap The map of source to controller class + */ + public FactoryIncludeView(final Map, ControllerFactory> controllersMap, final Map> includeControllersMap) { + this(controllersMap, includeControllersMap, Map.of()); + } + + /** + * Instantiates a new FactoryIncludeView with nested controllers + * + * @param controllersMap The map of controller class to controller factory + * @param includeControllersMap The map of source to controller class + * @param resourceBundlesMap The map of controller class to resource bundle + */ + public FactoryIncludeView(final Map, ControllerFactory> controllersMap, final Map> includeControllersMap, final Map, ResourceBundle> resourceBundlesMap) { + this.controllersMap = Map.copyOf(controllersMap); + this.includeControllersMap = Map.copyOf(includeControllersMap); + this.resourceBundlesMap = Map.copyOf(resourceBundlesMap); + } + + public javafx.scene.Parent load() { + if (loaded) { + throw new IllegalStateException("Already loaded"); + } + final var bundle = resourceBundlesMap.get(com.github.gtache.fxml.compiler.loader.IncludeController.class); + final var fieldMap = new HashMap(); + final var object0 = new javafx.scene.layout.GridPane(); + fieldMap.put("gridPane", object0); + final var object1 = new javafx.scene.layout.RowConstraints(); + final var object2 = new javafx.scene.layout.RowConstraints(); + final var object3 = new javafx.scene.layout.RowConstraints(); + final var object4 = new javafx.scene.layout.RowConstraints(); + final var object5 = new javafx.scene.layout.RowConstraints(); + final var object6 = new javafx.scene.layout.RowConstraints(); + final var object7 = new javafx.scene.layout.RowConstraints(); + final var object8 = new javafx.scene.layout.RowConstraints(); + final var object9 = new javafx.scene.layout.RowConstraints(); + final var object10 = new javafx.scene.layout.RowConstraints(); + final var object11 = new javafx.scene.layout.RowConstraints(); + final var object12 = new javafx.scene.layout.RowConstraints(); + final var object13 = new javafx.scene.layout.RowConstraints(); + final var object14 = new javafx.scene.layout.RowConstraints(); + final var object15 = new javafx.scene.layout.RowConstraints(); + final var object16 = new javafx.scene.layout.RowConstraints(); + final var object17 = new javafx.scene.layout.RowConstraints(); + final var object18 = new javafx.scene.layout.RowConstraints(); + object0.getRowConstraints().addAll(object1, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11, object12, object13, object14, object15, object16, object17, object18); + final var object19 = new javafx.scene.layout.ColumnConstraints(); + final var object20 = new javafx.scene.layout.ColumnConstraints(); + final var object21 = new javafx.scene.layout.ColumnConstraints(); + object21.setMinWidth(10.0); + object21.setPrefWidth(100.0); + object0.getColumnConstraints().addAll(object19, object20, object21); + final var object22 = new javafx.scene.control.Button(); + fieldMap.put("button", object22); + object22.setMnemonicParsing(false); + object22.setText("Button"); + final var object23 = new javafx.scene.control.CheckBox(); + fieldMap.put("checkBox", object23); + object23.setIndeterminate(true); + object23.setMnemonicParsing(false); + object23.setText("CheckBox"); + javafx.scene.layout.GridPane.setColumnIndex(object23, 1); + final var object24 = new javafx.scene.control.ChoiceBox(); + fieldMap.put("choiceBox", object24); + object24.setCacheShape(false); + object24.setCenterShape(false); + object24.setDisable(true); + object24.setFocusTraversable(false); + object24.setPrefWidth(150.0); + object24.setScaleShape(false); + object24.setVisible(false); + javafx.scene.layout.GridPane.setRowIndex(object24, 1); + final var object25 = new javafx.scene.control.ColorPicker(); + fieldMap.put("colorPicker", object25); + object25.setNodeOrientation(javafx.geometry.NodeOrientation.LEFT_TO_RIGHT); + object25.setOpacity(0.5); + javafx.scene.layout.GridPane.setColumnIndex(object25, 1); + javafx.scene.layout.GridPane.setRowIndex(object25, 1); + final var object26 = new javafx.scene.paint.Color(0.7894737124443054, 0.08771929889917374, 0.08771929889917374, 1); + fieldMap.put("color", object26); + object25.setValue(object26); + final var object27 = new javafx.geometry.Insets(5.0, 4.0, 3.0, 2.0); + object25.setOpaqueInsets(object27); + final var object28 = new javafx.scene.control.ComboBox(); + fieldMap.put("comboBox", object28); + object28.setEditable(true); + object28.setPrefWidth(150.0); + object28.setPromptText("Text"); + object28.setVisibleRowCount(5); + javafx.scene.layout.GridPane.setRowIndex(object28, 2); + final var object29 = javafx.scene.Cursor.CLOSED_HAND; + object28.setCursor(object29); + final var object30 = new javafx.scene.effect.Bloom(); + object28.setEffect(object30); + final var object31 = new javafx.scene.control.DatePicker(); + fieldMap.put("datePicker", object31); + object31.setShowWeekNumbers(true); + object31.setStyle("-fx-background-color: #ffffff;"); + javafx.scene.layout.GridPane.setColumnIndex(object31, 1); + javafx.scene.layout.GridPane.setRowIndex(object31, 2); + final var object32 = new javafx.scene.web.HTMLEditor(); + fieldMap.put("htmlEditor", object32); + object32.setHtmlText(""); + object32.setPrefHeight(200.0); + object32.setPrefWidth(506.0); + object32.getStyleClass().addAll("clazz"); + object32.getStylesheets().addAll("@style.css"); + javafx.scene.layout.GridPane.setRowIndex(object32, 3); + final var object33 = new javafx.scene.control.Hyperlink(); + fieldMap.put("hyperlink", object33); + object33.setText("Hyperlink"); + javafx.scene.layout.GridPane.setColumnIndex(object33, 1); + javafx.scene.layout.GridPane.setRowIndex(object33, 3); + final var object34 = new javafx.scene.image.ImageView(); + fieldMap.put("imageView", object34); + object34.setFitHeight(150.0); + object34.setFitWidth(200.0); + object34.setPickOnBounds(true); + object34.setPreserveRatio(true); + javafx.scene.layout.GridPane.setRowIndex(object34, 4); + final var object35 = new javafx.scene.control.Label(); + fieldMap.put("label", object35); + object35.setAccessibleHelp("TTTTT"); + object35.setAccessibleText("TTT"); + object35.setBlendMode(javafx.scene.effect.BlendMode.ADD); + object35.setCache(true); + object35.setCacheHint(javafx.scene.CacheHint.QUALITY); + object35.setDepthTest(javafx.scene.DepthTest.ENABLE); + object35.setMnemonicParsing(true); + object35.setMouseTransparent(true); + object35.setText(bundle.getString("include.label")); + javafx.scene.layout.GridPane.setColumnIndex(object35, 1); + javafx.scene.layout.GridPane.setRowIndex(object35, 4); + final var object36 = new javafx.scene.control.ListView(); + fieldMap.put("listView", object36); + object36.setFixedCellSize(20.0); + object36.setNodeOrientation(javafx.geometry.NodeOrientation.RIGHT_TO_LEFT); + object36.setOrientation(javafx.geometry.Orientation.HORIZONTAL); + object36.setPrefHeight(200.0); + object36.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setRowIndex(object36, 5); + final var object37 = new javafx.scene.media.MediaView(); + fieldMap.put("mediaView", object37); + object37.setFitHeight(200.0); + object37.setFitWidth(200.0); + javafx.scene.layout.GridPane.setColumnIndex(object37, 1); + javafx.scene.layout.GridPane.setColumnSpan(object37, 2); + javafx.scene.layout.GridPane.setRowIndex(object37, 5); + javafx.scene.layout.GridPane.setRowSpan(object37, 2); + final var object38 = new javafx.scene.control.MenuBar(); + fieldMap.put("menuBar", object38); + javafx.scene.layout.GridPane.setHalignment(object38, javafx.geometry.HPos.RIGHT); + javafx.scene.layout.GridPane.setHgrow(object38, javafx.scene.layout.Priority.ALWAYS); + javafx.scene.layout.GridPane.setRowIndex(object38, 7); + javafx.scene.layout.GridPane.setValignment(object38, javafx.geometry.VPos.BASELINE); + javafx.scene.layout.GridPane.setVgrow(object38, javafx.scene.layout.Priority.SOMETIMES); + final var object39 = new javafx.scene.control.Menu(); + fieldMap.put("menu1", object39); + object39.setMnemonicParsing(false); + object39.setText("File"); + final var object40 = new javafx.scene.control.MenuItem(); + fieldMap.put("menuItem1", object40); + object40.setMnemonicParsing(false); + object40.setText("Close"); + object39.getItems().addAll(object40); + final var object41 = new javafx.scene.control.Menu(); + object41.setMnemonicParsing(false); + object41.setText("Edit"); + final var object42 = new javafx.scene.control.MenuItem(); + object42.setMnemonicParsing(false); + object42.setText("Delete"); + object41.getItems().addAll(object42); + final var object43 = new javafx.scene.control.Menu(); + object43.setMnemonicParsing(false); + object43.setText("Help"); + final var object44 = new javafx.scene.control.MenuItem(); + object44.setMnemonicParsing(false); + object44.setText("About"); + object43.getItems().addAll(object44); + object38.getMenus().addAll(object39, object41, object43); + final var object45 = new javafx.scene.control.MenuButton(); + fieldMap.put("menuButton", object45); + object45.setMnemonicParsing(false); + object45.setText("MenuButton"); + javafx.scene.layout.GridPane.setColumnIndex(object45, 1); + javafx.scene.layout.GridPane.setRowIndex(object45, 7); + final var object46 = new javafx.scene.control.MenuItem(); + object46.setMnemonicParsing(false); + object46.setText("Action 1"); + final var object47 = new javafx.scene.control.MenuItem(); + object47.setMnemonicParsing(false); + object47.setText("Action 2"); + object45.getItems().addAll(object46, object47); + final var object48 = new javafx.geometry.Insets(5.0, 4.0, 3.0, 2.0); + javafx.scene.layout.GridPane.setMargin(object45, object48); + final var object49 = new javafx.scene.control.Pagination(); + fieldMap.put("pagination", object49); + object49.setPrefHeight(200.0); + object49.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setRowIndex(object49, 8); + final var object50 = new javafx.geometry.Insets(5.0, 4.0, 3.0, 2.0); + object49.setPadding(object50); + final var object51 = new javafx.scene.control.PasswordField(); + fieldMap.put("passwordField", object51); + object51.setMaxHeight(6.0); + object51.setMaxWidth(5.0); + object51.setMinHeight(2.0); + object51.setMinWidth(1.0); + object51.setPrefColumnCount(7); + object51.setPrefHeight(4.0); + object51.setPrefWidth(3.0); + javafx.scene.layout.GridPane.setColumnIndex(object51, 1); + javafx.scene.layout.GridPane.setRowIndex(object51, 8); + final var object52 = new javafx.scene.control.ProgressBar(); + fieldMap.put("progressBar", object52); + object52.setLayoutX(10.0); + object52.setLayoutY(20.0); + object52.setPrefWidth(200.0); + object52.setProgress(0.0); + javafx.scene.layout.GridPane.setRowIndex(object52, 9); + final var object53 = new javafx.scene.control.ProgressIndicator(); + fieldMap.put("progressIndicator", object53); + object53.setProgress(0.0); + object53.setRotate(2.0); + javafx.scene.layout.GridPane.setColumnIndex(object53, 1); + javafx.scene.layout.GridPane.setRowIndex(object53, 9); + final var object54 = new javafx.geometry.Point3D(4.0, 5.0, 6.0); + object53.setRotationAxis(object54); + final var object55 = new javafx.scene.control.RadioButton(); + fieldMap.put("radioButton", object55); + object55.setMnemonicParsing(false); + object55.setScaleX(7.0); + object55.setScaleY(2.0); + object55.setScaleZ(3.0); + object55.setText("RadioButton"); + object55.setTranslateX(4.0); + object55.setTranslateY(5.0); + object55.setTranslateZ(6.0); + javafx.scene.layout.GridPane.setRowIndex(object55, 10); + final var object56 = new javafx.scene.control.ScrollBar(); + fieldMap.put("scrollBarH", object56); + javafx.scene.layout.GridPane.setColumnIndex(object56, 1); + javafx.scene.layout.GridPane.setRowIndex(object56, 10); + final var object57 = new javafx.scene.control.ScrollBar(); + fieldMap.put("scrollBarV", object57); + object57.setOrientation(javafx.geometry.Orientation.VERTICAL); + javafx.scene.layout.GridPane.setRowIndex(object57, 11); + final var object58 = new javafx.scene.control.Separator(); + fieldMap.put("separatorH", object58); + object58.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setColumnIndex(object58, 1); + javafx.scene.layout.GridPane.setRowIndex(object58, 11); + final var object59 = new javafx.scene.control.Separator(); + fieldMap.put("separatorV", object59); + object59.setOrientation(javafx.geometry.Orientation.VERTICAL); + object59.setPrefHeight(200.0); + javafx.scene.layout.GridPane.setRowIndex(object59, 12); + final var object60 = new javafx.scene.control.Slider(); + fieldMap.put("sliderH", object60); + javafx.scene.layout.GridPane.setColumnIndex(object60, 1); + javafx.scene.layout.GridPane.setRowIndex(object60, 12); + final var object61 = new javafx.scene.control.Slider(); + fieldMap.put("sliderV", object61); + object61.setOrientation(javafx.geometry.Orientation.VERTICAL); + javafx.scene.layout.GridPane.setRowIndex(object61, 13); + final var object62 = new javafx.scene.control.Spinner(); + fieldMap.put("spinner", object62); + javafx.scene.layout.GridPane.setColumnIndex(object62, 1); + javafx.scene.layout.GridPane.setRowIndex(object62, 13); + final var object63 = new javafx.scene.control.SplitMenuButton(); + fieldMap.put("splitMenuButton", object63); + object63.setMnemonicParsing(false); + object63.setText("SplitMenuButton"); + javafx.scene.layout.GridPane.setRowIndex(object63, 14); + final var object64 = new javafx.scene.control.MenuItem(); + fieldMap.put("item1", object64); + object64.setMnemonicParsing(false); + object64.setText("Action 1"); + final var object65 = new javafx.scene.control.MenuItem(); + fieldMap.put("item2", object65); + object65.setMnemonicParsing(false); + object65.setText("Action 2"); + object63.getItems().addAll(object64, object65); + final var object66 = new javafx.scene.control.TableView(); + fieldMap.put("tableView", object66); + object66.setPrefHeight(200.0); + object66.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setColumnIndex(object66, 1); + javafx.scene.layout.GridPane.setRowIndex(object66, 14); + final var object67 = new javafx.scene.control.TableColumn(); + fieldMap.put("tableColumn1", object67); + object67.setPrefWidth(75.0); + object67.setText("C1"); + final var object68 = new javafx.scene.control.TableColumn(); + fieldMap.put("tableColumn2", object68); + object68.setPrefWidth(75.0); + object68.setText("C2"); + object66.getColumns().addAll(object67, object68); + final var object69 = new javafx.scene.control.TextArea(); + fieldMap.put("textArea", object69); + object69.setPrefHeight(200.0); + object69.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setRowIndex(object69, 15); + final var object70 = new javafx.scene.control.TextField(); + fieldMap.put("textField", object70); + javafx.scene.layout.GridPane.setColumnIndex(object70, 1); + javafx.scene.layout.GridPane.setRowIndex(object70, 15); + final var object71 = new javafx.scene.control.ToggleButton(); + object71.setMnemonicParsing(false); + object71.setText("ToggleButton"); + javafx.scene.layout.GridPane.setRowIndex(object71, 16); + final var object72 = new javafx.scene.control.TreeTableView>(); + fieldMap.put("treeTableView", object72); + object72.setPrefHeight(200.0); + object72.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setColumnIndex(object72, 1); + javafx.scene.layout.GridPane.setRowIndex(object72, 16); + final var object73 = new javafx.scene.control.TreeTableColumn, String>(); + fieldMap.put("treeTableColumn1", object73); + object73.setPrefWidth(75.0); + object73.setText("C1"); + final var object74 = new javafx.scene.control.TreeTableColumn, Integer>(); + fieldMap.put("treeTableColumn2", object74); + object74.setPrefWidth(75.0); + object74.setSortType(javafx.scene.control.TreeTableColumn.SortType.DESCENDING); + object74.setText("C2"); + object72.getColumns().addAll(object73, object74); + final var object75 = new javafx.scene.control.TreeView(); + fieldMap.put("treeView", object75); + object75.setPrefHeight(200.0); + object75.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setRowIndex(object75, 17); + final var object76 = new javafx.scene.web.WebView(); + fieldMap.put("webView", object76); + object76.setPrefHeight(200.0); + object76.setPrefWidth(200.0); + javafx.scene.layout.GridPane.setColumnIndex(object76, 1); + javafx.scene.layout.GridPane.setRowIndex(object76, 17); + object0.getChildren().addAll(object22, object23, object24, object25, object28, object31, object32, object33, object34, object35, object36, object37, object38, object45, object49, object51, object52, object53, object55, object56, object57, object58, object59, object60, object61, object62, object63, object66, object69, object70, object71, object72, object75, object76); + final var controllerFactory = controllersMap.get(com.github.gtache.fxml.compiler.loader.IncludeController.class); + controller = (com.github.gtache.fxml.compiler.loader.IncludeController) controllerFactory.create(fieldMap); + object0.setOnInputMethodTextChanged(controller::inputMethodTextChanged); + object0.setOnKeyPressed(e -> controller.keyPressed()); + object0.setOnKeyReleased(controller::keyReleased); + object0.setOnKeyTyped(controller::keyTyped); + object58.setOnDragDetected(controller::dragDetected); + object58.setOnDragDone(controller::dragDone); + object58.setOnDragDropped(controller::dragDropped); + object58.setOnDragEntered(controller::dragEntered); + object58.setOnDragExited(controller::dragExited); + object58.setOnDragOver(controller::dragOver); + object58.setOnMouseDragEntered(controller::mouseDragEntered); + object58.setOnMouseDragExited(controller::mouseDragExited); + object58.setOnMouseDragOver(controller::mouseDragOver); + object58.setOnMouseDragReleased(controller::mouseDragReleased); + object60.setOnContextMenuRequested(controller::contextMenuRequested); + object60.setOnMouseClicked(e -> controller.mouseClicked()); + object60.setOnMouseDragged(controller::mouseDragged); + object60.setOnMouseEntered(controller::mouseEntered); + object60.setOnMouseExited(controller::mouseExited); + object60.setOnMouseMoved(controller::mouseMoved); + object60.setOnMousePressed(controller::mousePressed); + object60.setOnMouseReleased(controller::mouseReleased); + object60.setOnScroll(controller::onScroll); + object60.setOnScrollFinished(controller::onScrollFinished); + object60.setOnScrollStarted(controller::onScrollStarted); + object61.setOnZoom(controller::onZoom); + object61.setOnZoomFinished(controller::onZoomFinished); + object61.setOnZoomStarted(controller::onZoomStarted); + object71.setOnAction(controller::onAction); + object71.setOnRotate(controller::onRotate); + object71.setOnRotationFinished(controller::onRotationFinished); + object71.setOnRotationStarted(controller::onRotationStarted); + object73.setOnEditCancel(controller::onEditCancel); + object73.setOnEditCommit(controller::onEditCommit); + object73.setOnEditStart(controller::onEditStart); + object75.setOnSwipeDown(controller::onSwipeDown); + object75.setOnSwipeLeft(controller::onSwipeLeft); + object75.setOnSwipeRight(controller::onSwipeRight); + object75.setOnSwipeUp(controller::onSwipeUp); + object76.setOnTouchMoved(controller::onTouchMoved); + object76.setOnTouchPressed(controller::onTouchPressed); + object76.setOnTouchReleased(controller::onTouchReleased); + object76.setOnTouchStationary(controller::onTouchStationary); + controller.initialize(); + loaded = true; + return object0; + } + + + /** + * @return The controller + */ + public com.github.gtache.fxml.compiler.loader.IncludeController controller() { + if (loaded) { + return controller; + } else { + throw new IllegalStateException("Not loaded"); + } + } +} diff --git a/loader/src/test/java/com/github/gtache/fxml/compiler/loader/FactoryTestView.java b/loader/src/test/java/com/github/gtache/fxml/compiler/loader/FactoryTestView.java new file mode 100644 index 0000000..e425e0a --- /dev/null +++ b/loader/src/test/java/com/github/gtache/fxml/compiler/loader/FactoryTestView.java @@ -0,0 +1,202 @@ +package com.github.gtache.fxml.compiler.loader; + +import com.github.gtache.fxml.compiler.ControllerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + + +/** + * Generated code, not thread-safe + */ +public final class FactoryTestView { + + private final Map, ControllerFactory> controllersMap; + private final Map, ResourceBundle> resourceBundlesMap; + private final Map> includeControllersMap; + private boolean loaded; + private com.github.gtache.fxml.compiler.loader.TestController controller; + + /** + * Instantiates a new FactoryTestView with no nested controllers and no resource bundle + * + * @param controllerFactory The controller factory + */ + public FactoryTestView(final ControllerFactory controllerFactory) { + this(Map.of(com.github.gtache.fxml.compiler.loader.TestController.class, controllerFactory), Map.of(), Map.of()); + } + + /** + * Instantiates a new FactoryTestView with no nested controllers + * + * @param controllerFactory The controller factory + * @param resourceBundle The resource bundle + */ + public FactoryTestView(final ControllerFactory controllerFactory, final ResourceBundle resourceBundle) { + this(Map.of(com.github.gtache.fxml.compiler.loader.TestController.class, controllerFactory), Map.of(), Map.of(com.github.gtache.fxml.compiler.loader.TestController.class, resourceBundle)); + } + + /** + * Instantiates a new FactoryTestView with nested controllers and no resource bundle + * + * @param controllersMap The map of controller class to controller factory + * @param includeControllersMap The map of source to controller class + */ + public FactoryTestView(final Map, ControllerFactory> controllersMap, final Map> includeControllersMap) { + this(controllersMap, includeControllersMap, Map.of()); + } + + /** + * Instantiates a new FactoryTestView with nested controllers + * + * @param controllersMap The map of controller class to controller factory + * @param includeControllersMap The map of source to controller class + * @param resourceBundlesMap The map of controller class to resource bundle + */ + public FactoryTestView(final Map, ControllerFactory> controllersMap, final Map> includeControllersMap, final Map, ResourceBundle> resourceBundlesMap) { + this.controllersMap = Map.copyOf(controllersMap); + this.includeControllersMap = Map.copyOf(includeControllersMap); + this.resourceBundlesMap = Map.copyOf(resourceBundlesMap); + } + + public javafx.scene.Parent load() { + if (loaded) { + throw new IllegalStateException("Already loaded"); + } + final var bundle = resourceBundlesMap.get(com.github.gtache.fxml.compiler.loader.TestController.class); + final var fieldMap = new HashMap(); + final var object0 = new javafx.scene.layout.BorderPane(); + final var object1 = new javafx.scene.layout.VBox(); + javafx.scene.layout.BorderPane.setAlignment(object1, javafx.geometry.Pos.CENTER); + final var object2 = new javafx.scene.layout.HBox(); + object2.setAlignment(javafx.geometry.Pos.CENTER); + object2.setSpacing(10.0); + final var object3 = new javafx.scene.control.Slider(); + fieldMap.put("playSlider", object3); + javafx.scene.layout.HBox.setHgrow(object3, javafx.scene.layout.Priority.ALWAYS); + final var object4 = new javafx.geometry.Insets(0, 0, 0, 10.0); + object3.setPadding(object4); + final var object5 = new javafx.scene.control.Label(); + fieldMap.put("playLabel", object5); + object5.setText("Label"); + final var object6 = new javafx.geometry.Insets(0, 10.0, 0, 0); + object5.setPadding(object6); + object2.getChildren().addAll(object3, object5); + final var object7 = new javafx.geometry.Insets(10.0, 0, 0, 0); + object2.setPadding(object7); + final var object8 = new javafx.scene.layout.HBox(); + object8.setAlignment(javafx.geometry.Pos.CENTER); + object8.setSpacing(10.0); + final var object9 = new javafx.scene.control.Button(); + fieldMap.put("playButton", object9); + object9.setMnemonicParsing(false); + final var object10 = new javafx.geometry.Insets(0, 20.0, 0, 0); + javafx.scene.layout.HBox.setMargin(object9, object10); + final var object11 = new javafx.scene.control.Label(); + object11.setText(bundle.getString("media.volume.label")); + final var object12 = new javafx.scene.control.Slider(); + fieldMap.put("volumeSlider", object12); + object12.setValue(100); + final var object13 = new javafx.scene.control.Label(); + fieldMap.put("volumeValueLabel", object13); + object13.setText("Label"); + final var class0 = includeControllersMap.get("includeView.fxml"); + final var map0 = new HashMap<>(resourceBundlesMap); + final var bundle0 = ResourceBundle.getBundle("com.github.gtache.fxml.compiler.loader.IncludeBundle"); + map0.put(class0, bundle0); + final var view0 = new com.github.gtache.fxml.compiler.loader.FactoryIncludeView(controllersMap, includeControllersMap, map0); + final var object14 = view0.load(); + final var controller0 = view0.controller(); + fieldMap.put("includeController", controller0); + object8.getChildren().addAll(object9, object11, object12, object13, object14); + final var object15 = new javafx.geometry.Insets(10.0, 10.0, 10.0, 10.0); + object8.setPadding(object15); + object1.getChildren().addAll(object2, object8); + object0.setBottom(object1); + final var object16 = new javafx.scene.layout.VBox(); + fieldMap.put("vbox", object16); + final var object17 = new javafx.scene.control.ToolBar(); + fieldMap.put("toolBar", object17); + final var object18 = new javafx.scene.control.TitledPane(); + fieldMap.put("titledPane", object18); + final var object19 = new javafx.scene.layout.TilePane(); + fieldMap.put("tilePane", object19); + final var object20 = new javafx.scene.text.TextFlow(); + fieldMap.put("textFlow", object20); + final var object21 = new javafx.scene.control.TabPane(); + fieldMap.put("tabPane", object21); + final var object22 = new javafx.scene.control.Tab(); + fieldMap.put("tab", object22); + final var object23 = new javafx.scene.layout.StackPane(); + fieldMap.put("stackPane", object23); + final var object24 = new javafx.scene.control.SplitPane(); + fieldMap.put("splitPane", object24); + final var object25 = new javafx.scene.control.ScrollPane(); + fieldMap.put("scrollPane", object25); + final var object26 = new javafx.scene.layout.Pane(); + fieldMap.put("pane", object26); + final var object27 = new javafx.scene.layout.HBox(); + fieldMap.put("hbox", object27); + final var object28 = new javafx.scene.Group(); + fieldMap.put("group", object28); + final var object29 = new javafx.scene.layout.GridPane(); + fieldMap.put("gridPane", object29); + final var object30 = new javafx.scene.layout.ColumnConstraints(); + fieldMap.put("columnConstraints", object30); + object30.setHgrow(javafx.scene.layout.Priority.SOMETIMES); + object30.setMinWidth(10.0); + object29.getColumnConstraints().addAll(object30); + final var object31 = new javafx.scene.layout.RowConstraints(); + object31.setMinHeight(10.0); + object31.setVgrow(javafx.scene.layout.Priority.SOMETIMES); + object29.getRowConstraints().addAll(object31); + final var object32 = new javafx.scene.layout.FlowPane(); + fieldMap.put("flowPane", object32); + final var object33 = new javafx.scene.control.DialogPane(); + fieldMap.put("dialogPane", object33); + final var object34 = new javafx.scene.control.ButtonBar(); + fieldMap.put("buttonBar", object34); + final var object35 = new javafx.scene.layout.AnchorPane(); + fieldMap.put("anchorPane", object35); + final var object36 = new javafx.scene.control.Label(); + object36.setManaged(false); + object35.getChildren().addAll(object36); + object34.getButtons().addAll(object35); + object33.setContent(object34); + object32.getChildren().addAll(object33); + object29.getChildren().addAll(object32); + object28.getChildren().addAll(object29); + object27.getChildren().addAll(object28); + object26.getChildren().addAll(object27); + object25.setContent(object26); + object24.getItems().addAll(object25); + object23.getChildren().addAll(object24); + object22.setContent(object23); + object21.getTabs().addAll(object22); + object20.getChildren().addAll(object21); + object19.getChildren().addAll(object20); + object18.setContent(object19); + object17.getItems().addAll(object18); + object16.getChildren().addAll(object17); + object0.setCenter(object16); + final var controllerFactory = controllersMap.get(com.github.gtache.fxml.compiler.loader.TestController.class); + controller = (com.github.gtache.fxml.compiler.loader.TestController) controllerFactory.create(fieldMap); + object9.setOnAction(controller::playPressed); + controller.initialize(); + loaded = true; + return object0; + } + + + /** + * @return The controller + */ + public com.github.gtache.fxml.compiler.loader.TestController controller() { + if (loaded) { + return controller; + } else { + throw new IllegalStateException("Not loaded"); + } + } +} diff --git a/loader/src/test/java/com/github/gtache/fxml/compiler/loader/IncludeController.java b/loader/src/test/java/com/github/gtache/fxml/compiler/loader/IncludeController.java new file mode 100644 index 0000000..0c21256 --- /dev/null +++ b/loader/src/test/java/com/github/gtache/fxml/compiler/loader/IncludeController.java @@ -0,0 +1,489 @@ +package com.github.gtache.fxml.compiler.loader; + +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; +import javafx.scene.media.MediaView; +import javafx.scene.paint.Color; +import javafx.scene.web.HTMLEditor; +import javafx.scene.web.WebView; + +public class IncludeController { + @FXML + GridPane gridPane; + @FXML + Button button; + @FXML + CheckBox checkBox; + @FXML + ChoiceBox choiceBox; + @FXML + ColorPicker colorPicker; + @FXML + ComboBox comboBox; + @FXML + DatePicker datePicker; + @FXML + HTMLEditor htmlEditor; + @FXML + Hyperlink hyperlink; + @FXML + ImageView imageView; + @FXML + Label label; + @FXML + ListView