Fixes generics, only creates one constructor, adds some tests, adds java compatibility options, rework some classes, fixes some problems

This commit is contained in:
Guillaume Tâche
2024-12-13 21:20:49 +01:00
parent 7ec52cd175
commit d63188e8ee
172 changed files with 3955 additions and 17340 deletions

276
README.md
View File

@@ -1 +1,275 @@
# FXML Compiler # FXML Compiler
## Introduction
This projects aims at generating Java code from FXML files.
## Requirements
- Java 21 (at least for the plugin, the generated code can be compatible with older Java versions)
- Maven 3.8.0
## Installation
Add the plugin to your project:
```xml
<build>
<plugins>
<plugin>
<groupId>com.github.gtache</groupId>
<artifactId>fxml-compiler-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
```
Optionally add dependencies to the plugin (e.g. when using MediaView and controlsfx):
```xml
<build>
<plugins>
<plugin>
<groupId>com.github.gtache</groupId>
<artifactId>fxml-compiler-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
<version>${controlsfx.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
```
## Advantages
- Compile-time validation
- Faster startup speed for the application
- Possibility to use controller factories to instantiate controllers with final fields
- Easier time with JPMS
- No need to open the controllers packages to javafx.fxml
- No need to open the resources packages when using use-image-inputstream-constructor (if images or resource bundles
are in the project resources)
## Disadvantages
- Possible bugs (file an issue if you see one)
- Expression binding is limited
- Probably not fully compatible with all FXML features (file an issue if you need one in specific)
## Parameters
### Field injection
There are four ways to inject fields into a controller:
- `REFLECTION`: Inject fields using reflection (like FXMLLoader)
-
- ```java
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);
}
```
-
- Slowest method
-
- Fully compatible with FXMLLoader, so this allows easy switching between the two.
-
- This is the default injection method (for compatibility reasons).
- `ASSIGN`: variable assignment
-
- `controller.field = value`
-
- This means that the field must be accessible from the view (e.g. package-private).
- `SETTERS`: controller setters methods
-
- `controller.setField(value)`
- `FACTORY`: controller factory
-
- `controller = factory.create(fieldMap)`
-
- `factory` is a `ControllerFactory` instance that is created at runtime and passed to the view.
-
- `fieldMap` is a map of field name (String) to value (Object) that is computed during the view `load` method.
-
- This allows the controller to have final fields.
### Method injections
There are two ways to inject methods (meaning use them as event handlers) into a controller:
- `REFLECTION`: Inject methods using reflection (like FXMLLoader)
-
- ```java
try {
final java.lang.reflect.Method method;
final var methods = java.util.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 && javafx.event.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 | java.lang.reflect.InvocationTargetException ex) {
throw new RuntimeException("Error using reflection on " + methodName, ex);
}
```
-
- Slowest method
-
- Fully compatible with FXMLLoader, so this allows easy switching between the two.
-
- This is the default injection method (for compatibility reasons).
- `REFERENCE`: Directly reference the method
-
- `controller.method(event)`
-
- This means that the method must be accessible from the view (e.g. package-private).
### Resource bundle injection
There are three ways to inject resource bundles into a controller:
- `CONSTRUCTOR`: Inject resource bundle in the view constructor
-
- ```java
view = new View(controller, resourceBundle);
```
-
- This is the default injection method because it is the most similar to FXMLLoader (
`FXMLLoader.setResources(resourceBundle)`).
- `CONSTRUCTOR_FUNCTION`: Injects a function in the view constructor
-
- `bundleFunction.apply(key)`
-
- The function takes a string (the key) and returns a string (the value)
- This allows using another object than a resource bundle for example
- `GETTER`: Retrieves the resource bundle using a controller getter method
-
- `controller.resources()`
-
- The method name (resources) was chosen because it matches the name of the field injected by FXMLLoader.
-
- The method must be accessible from the view (e.g. package-private).
- `GET-BUNDLE`: Injects the bundle name in the view constructor and retrieves it using
`ResourceBundle.getBundle(bundleName)`
-
- `ResourceBundle.getBundle(bundleName)`
- Also used when fx:include specifies a resource attribute to pass it to the included view.
## View creation
The views are generated in the same packages as the FXML files.
The name of the class is generated from the name of the FXML file.
The constructor of the view is generated depending on the parameters of the plugin.
The constructor will have as many arguments as the number of controllers in the FXML tree (recursive fx:include) +
potentially the resource bundle if necessary. If no resource reference (`%key.to.resource`) is found in the FXML tree or
if all the includes using references specify a resources attribute, the argument is not created.
The type of the constructor arguments will either be the controller instance or the controller factory (a function of
fields map -> controller).
The resource bundle argument will either be the resource bundle instance, the resource bundle name or a function of
string ->
string.
The smallest constructor will have only one argument: The controller (or controller factory).
## Maven Plugin
### Parameters
- output-directory
-
- The output directory of the generated classes
-
- default: `${project.build.directory}/generated-sources/java`)
- target-version
-
- The target Java version for the generated code
- default: `21`
- minimum: `8`
- File an issue if the generated code is not compatible with the target version
- use-image-inputstream-constructor
-
- Use the InputStream constructor for Image instead of the String (URL) one.
-
- default: `true`
- Disables background loading
- field-injection
-
- The type of field injections to use (see [Field injection](#field-injection))
- default: `REFLECTION`
- method-injection
-
- The type of method injections to use (see [Method injection](#method-injection))
- default: `REFLECTION`
- bundle-injection
-
- The type of resource bundle injection to use (see [Resource bundle injection](#resource-bundle-injection))
- default: `CONSTRUCTOR`
- bundle-map
-
- A map of resource bundle name to resource bundle path
- Used with `GET-BUNDLE` injection
- default: `{}`
### Limitations
- Given that the plugin operates during the `generate-sources` phase, it doesn't have access to the classes of the
application.
-
- The controller info (fields, methods) is obtained from the source file and may therefore be inaccurate.
-
- Custom classes instantiated in the FXML files are not available during generation and may therefore cause it to
fail.
- If the application uses e.g. WebView, the javafx-web dependency must be added to the plugin dependencies.

View File

@@ -1,20 +0,0 @@
package com.github.gtache.fxml.compiler;
import java.util.Map;
/**
* Factory for creating controllers
*
* @param <T> The type of the controller
*/
@FunctionalInterface
public interface ControllerFactory<T> {
/**
* Creates a controller
*
* @param fieldMap The assignment of field name to value
* @return The created controller
*/
T create(final Map<String, Object> fieldMap);
}

View File

@@ -28,5 +28,5 @@ public interface ControllerFieldInfo {
* *
* @return The generic types as a list, empty if not generic or raw * @return The generic types as a list, empty if not generic or raw
*/ */
List<String> genericTypes(); List<GenericTypes> genericTypes();
} }

View File

@@ -7,10 +7,17 @@ import java.util.Map;
*/ */
public interface ControllerInfo { public interface ControllerInfo {
/**
* Returns the controller class name
*
* @return The name
*/
String className();
/** /**
* Returns a mapping of event handler method name -> boolean * Returns a mapping of event handler method name -> boolean
* *
* @return A mapping of method name to true if the method has an argument * @return A mapping of method name to true if the method has an argument (event handler may not have an argument)
*/ */
Map<String, Boolean> handlerHasArgument(); Map<String, Boolean> handlerHasArgument();
@@ -18,7 +25,7 @@ public interface ControllerInfo {
* Returns whether the given event handler method has an argument * Returns whether the given event handler method has an argument
* *
* @param methodName The method name * @param methodName The method name
* @return A mapping of method name to true if the method has an event * @return True if the method has an argument
*/ */
default boolean handlerHasArgument(final String methodName) { default boolean handlerHasArgument(final String methodName) {
return handlerHasArgument().getOrDefault(methodName, true); return handlerHasArgument().getOrDefault(methodName, true);
@@ -40,4 +47,11 @@ public interface ControllerInfo {
default ControllerFieldInfo fieldInfo(final String property) { default ControllerFieldInfo fieldInfo(final String property) {
return fieldInfo().get(property); return fieldInfo().get(property);
} }
/**
* Returns whether the controller has an initialize method
*
* @return True if the controller has an initialize method
*/
boolean hasInitialize();
} }

View File

@@ -1,28 +0,0 @@
package com.github.gtache.fxml.compiler;
/**
* Represents a controller injection to use for generated code
*/
public interface ControllerInjection {
/**
* Returns the injection type of class fields
*
* @return The injection type for fields
*/
InjectionType fieldInjectionType();
/**
* Returns the injection type for event handlers methods
*
* @return The injection type for event handlers
*/
InjectionType methodInjectionType();
/**
* The name of the controller class
*
* @return The class
*/
String injectionClass();
}

View File

@@ -6,7 +6,7 @@ package com.github.gtache.fxml.compiler;
public class GenerationException extends Exception { public class GenerationException extends Exception {
/** /**
* Instantiates a new GenerationException * Instantiates a new exception
* *
* @param message The message * @param message The message
*/ */
@@ -15,7 +15,7 @@ public class GenerationException extends Exception {
} }
/** /**
* Instantiates a new GenerationException * Instantiates a new exception
* *
* @param message The message * @param message The message
* @param cause The cause * @param cause The cause
@@ -25,7 +25,7 @@ public class GenerationException extends Exception {
} }
/** /**
* Instantiates a new GenerationException * Instantiates a new exception
* *
* @param cause The cause * @param cause The cause
*/ */

View File

@@ -1,5 +1,7 @@
package com.github.gtache.fxml.compiler; package com.github.gtache.fxml.compiler;
import com.github.gtache.fxml.compiler.compatibility.GenerationCompatibility;
import java.util.Map; import java.util.Map;
/** /**
@@ -8,31 +10,52 @@ import java.util.Map;
public interface GenerationParameters { public interface GenerationParameters {
/** /**
* Returns the mapping of controller class name to controller injection * Returns the compatibility information
* *
* @return The mapping * @return The compatibility
*/ */
Map<String, ControllerInjection> controllerInjections(); GenerationCompatibility compatibility();
/** /**
* Returns the mapping of fx:include source to generated class name * Returns whether to use Image InputStream constructor instead of the String (url) one.
* This allows avoiding opening some packages with JPMS
* *
* @return The mapping * @return True if the constructor should be used
*/ */
Map<String, String> sourceToGeneratedClassName(); boolean useImageInputStreamConstructor();
/** /**
* Returns the mapping of fx:include source to controller class name * Returns the mapping of controller class to resource bundle path (in case of GET-BUNDLE injection)
* *
* @return The mapping * @return The map
*/ */
Map<String, String> sourceToControllerName(); Map<String, String> bundleMap();
/** /**
* Returns the resource bundle injection to use * Returns the controller injection to use
* *
* @return The injection * @return The injection
*/ */
ResourceBundleInjection resourceBundleInjection(); InjectionType controllerInjectionType();
/**
* Returns the field injection to use
*
* @return The injection
*/
InjectionType fieldInjectionType();
/**
* Returns the method injection to use
*
* @return The injection
*/
InjectionType methodInjectionType();
/**
* Returns the resource injection to use
*
* @return The injection
*/
InjectionType resourceInjectionType();
} }

View File

@@ -21,6 +21,13 @@ public interface GenerationRequest {
*/ */
GenerationParameters parameters(); GenerationParameters parameters();
/**
* Returns the info about the main source file
*
* @return The info
*/
SourceInfo sourceInfo();
/** /**
* Returns the object to generate code for * Returns the object to generate code for
* *

View File

@@ -0,0 +1,23 @@
package com.github.gtache.fxml.compiler;
import java.util.List;
/**
* Represents generic types for a field
*/
public interface GenericTypes {
/**
* Returns the name of the type
*
* @return The name
*/
String name();
/**
* Returns the possible subtypes of the type
*
* @return The list of subtypes, empty if no subtypes
*/
List<GenericTypes> subTypes();
}

View File

@@ -1,21 +0,0 @@
package com.github.gtache.fxml.compiler;
/**
* Represents a controller injection to use for generated code
*/
public interface ResourceBundleInjection {
/**
* Returns the injection type for the resource bundle
*
* @return The injection type
*/
InjectionType injectionType();
/**
* Returns the resource bundle name
*
* @return The path
*/
String bundleName();
}

View File

@@ -0,0 +1,54 @@
package com.github.gtache.fxml.compiler;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
/**
* Info about a source file
*/
public interface SourceInfo {
/**
* Returns the generated view class name
*
* @return The class name
*/
String generatedClassName();
/**
* Returns the controller class name
*
* @return The class name
*/
String controllerClassName();
/**
* Returns the source file
*
* @return The file
*/
Path sourceFile();
/**
* Returns the included sources.
* Note that there can be multiple times the same source.
*
* @return The sources
*/
List<SourceInfo> includedSources();
/**
* Returns the mapping of source value to source info
*
* @return The mapping
*/
Map<String, SourceInfo> sourceToSourceInfo();
/**
* Returns whether the source or its children requires a resource bundle
*
* @return True if the subtree requires a resource bundle
*/
boolean requiresResourceBundle();
}

View File

@@ -0,0 +1,57 @@
package com.github.gtache.fxml.compiler.compatibility;
/**
* Compatibility information for generated code
*/
@FunctionalInterface
public interface GenerationCompatibility {
/**
* Returns the minimum supported Java version
*
* @return The version
*/
int javaVersion();
/**
* Returns whether to use var for object declaration
*
* @return True if var should be used
*/
default boolean useVar() {
return javaVersion() >= 10;
}
/**
* Returns the type of list collector to use
*
* @return The collector
*/
default ListCollector listCollector() {
if (javaVersion() >= 16) {
return ListCollector.TO_LIST;
} else if (javaVersion() >= 10) {
return ListCollector.COLLECT_TO_UNMODIFIABLE_LIST;
} else {
return ListCollector.COLLECT_TO_LIST;
}
}
/**
* Returns whether to use List.of() (or Set.of() etc.) instead of Arrays.asList()
*
* @return True if List.of() should be used
*/
default boolean useCollectionsOf() {
return javaVersion() >= 9;
}
/**
* Returns whether to use getFirst() or get(0)
*
* @return True if getFirst() should be used
*/
default boolean useGetFirst() {
return javaVersion() >= 21;
}
}

View File

@@ -0,0 +1,22 @@
package com.github.gtache.fxml.compiler.compatibility;
/**
* Type of list collector to use for generated code
*/
public enum ListCollector {
/**
* Use .toList()
*/
TO_LIST,
/**
* Use .collect(Collectors.toUnmodifiableList())
*/
COLLECT_TO_UNMODIFIABLE_LIST,
/**
* Use .collect(Collectors.toList())
*/
COLLECT_TO_LIST
}

View File

@@ -6,7 +6,7 @@ package com.github.gtache.fxml.compiler.parsing;
public class ParseException extends Exception { public class ParseException extends Exception {
/** /**
* Instantiates a new ParseException * Instantiates a new exception
* *
* @param message The message * @param message The message
*/ */
@@ -15,7 +15,7 @@ public class ParseException extends Exception {
} }
/** /**
* Instantiates a new ParseException * Instantiates a new exception
* *
* @param message The message * @param message The message
* @param cause The cause * @param cause The cause
@@ -25,7 +25,7 @@ public class ParseException extends Exception {
} }
/** /**
* Instantiates a new ParseException * Instantiates a new exception
* *
* @param cause The cause * @param cause The cause
*/ */

View File

@@ -1,5 +1,6 @@
package com.github.gtache.fxml.compiler.parsing; package com.github.gtache.fxml.compiler.parsing;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.SequencedCollection; import java.util.SequencedCollection;
import java.util.SequencedMap; import java.util.SequencedMap;
@@ -10,30 +11,18 @@ import java.util.SequencedMap;
@FunctionalInterface @FunctionalInterface
public interface ParsedDefine extends ParsedObject { public interface ParsedDefine extends ParsedObject {
/**
* Returns the object defined by this fx:define
*
* @return The object
*/
ParsedObject object();
@Override @Override
default String className() { default String className() {
return object().className(); return ParsedDefine.class.getName();
} }
@Override @Override
default Map<String, ParsedProperty> attributes() { default Map<String, ParsedProperty> attributes() {
return object().attributes(); return Map.of();
} }
@Override @Override
default SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> properties() { default SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> properties() {
return object().properties(); return new LinkedHashMap<>();
}
@Override
default SequencedCollection<ParsedObject> children() {
return object().children();
} }
} }

View File

@@ -29,7 +29,8 @@ public interface ParsedFactory extends ParsedObject {
} }
/** /**
* Returns the arguments for the factory * Returns the arguments for the factory.
* Different from {@link ParsedObject#children()} (in practice, children should only contain fx:define)
* *
* @return The arguments * @return The arguments
*/ */

View File

@@ -10,7 +10,7 @@ import java.util.SequencedMap;
public interface ParsedObject { public interface ParsedObject {
/** /**
* The type of the object * Returns the type of the object
* *
* @return The class name * @return The class name
*/ */

View File

@@ -21,7 +21,7 @@ public interface ParsedText extends ParsedObject {
@Override @Override
default String className() { default String className() {
return "java.lang.String"; return String.class.getName();
} }
@Override @Override

View File

@@ -3,5 +3,6 @@
*/ */
module com.github.gtache.fxml.compiler.api { module com.github.gtache.fxml.compiler.api {
exports com.github.gtache.fxml.compiler; exports com.github.gtache.fxml.compiler;
exports com.github.gtache.fxml.compiler.compatibility;
exports com.github.gtache.fxml.compiler.parsing; exports com.github.gtache.fxml.compiler.parsing;
} }

View File

@@ -6,8 +6,7 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.when;
class TestControllerFieldInfo { class TestControllerFieldInfo {
@@ -25,7 +24,7 @@ class TestControllerFieldInfo {
@Test @Test
void testIsGenericTrue() { void testIsGenericTrue() {
when(info.genericTypes()).thenReturn(List.of("A", "B", "C")); when(info.genericTypes()).thenReturn(List.of(mock(GenericTypes.class)));
assertTrue(info.isGeneric()); assertTrue(info.isGeneric());
} }
} }

View File

@@ -0,0 +1,72 @@
package com.github.gtache.fxml.compiler.compatibility;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
class TestGenerationCompatibility {
private final GenerationCompatibility compatibility;
TestGenerationCompatibility() {
this.compatibility = spy(GenerationCompatibility.class);
}
@Test
void testUseVar() {
when(compatibility.javaVersion()).thenReturn(10);
assertTrue(compatibility.useVar());
}
@Test
void testDontUseVar() {
when(compatibility.javaVersion()).thenReturn(9);
assertFalse(compatibility.useVar());
}
@Test
void testToListCollector() {
when(compatibility.javaVersion()).thenReturn(16);
assertEquals(ListCollector.TO_LIST, compatibility.listCollector());
}
@Test
void testCollectToUnmodifiableListCollector() {
when(compatibility.javaVersion()).thenReturn(15);
assertEquals(ListCollector.COLLECT_TO_UNMODIFIABLE_LIST, compatibility.listCollector());
when(compatibility.javaVersion()).thenReturn(10);
assertEquals(ListCollector.COLLECT_TO_UNMODIFIABLE_LIST, compatibility.listCollector());
}
@Test
void testCollectToListCollector() {
when(compatibility.javaVersion()).thenReturn(9);
assertEquals(ListCollector.COLLECT_TO_LIST, compatibility.listCollector());
}
@Test
void testUseCollectionsOf() {
when(compatibility.javaVersion()).thenReturn(9);
assertTrue(compatibility.useCollectionsOf());
}
@Test
void testDontUseCollectionsOf() {
when(compatibility.javaVersion()).thenReturn(8);
assertFalse(compatibility.useCollectionsOf());
}
@Test
void testUseGetFirst() {
when(compatibility.javaVersion()).thenReturn(21);
assertTrue(compatibility.useGetFirst());
}
@Test
void testDontUseGetFirst() {
when(compatibility.javaVersion()).thenReturn(20);
assertFalse(compatibility.useGetFirst());
}
}

View File

@@ -1,80 +1,34 @@
package com.github.gtache.fxml.compiler.parsing; package com.github.gtache.fxml.compiler.parsing;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; 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.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.SequencedCollection;
import static java.util.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.spy;
@ExtendWith(MockitoExtension.class)
class TestParsedDefine { class TestParsedDefine {
private final ParsedProperty property;
private final ParsedObject object;
private final String string;
private final ParsedDefine define; private final ParsedDefine define;
TestParsedDefine(@Mock final ParsedProperty property, @Mock final ParsedObject object) { TestParsedDefine() {
this.property = requireNonNull(property);
this.object = requireNonNull(object);
this.string = "str/ing";
this.define = spy(ParsedDefine.class); this.define = spy(ParsedDefine.class);
} }
@BeforeEach
void beforeEach() {
when(property.value()).thenReturn(string);
when(define.object()).thenReturn(object);
when(object.className()).thenReturn(string);
when(object.children()).thenReturn(List.of(define));
final var map = new LinkedHashMap<ParsedProperty, SequencedCollection<ParsedObject>>();
map.put(property, List.of(object));
when(object.properties()).thenReturn(map);
when(object.attributes()).thenReturn(Map.of(string, property));
}
@Test
void testObject() {
assertEquals(object, define.object());
}
@Test @Test
void testClassName() { void testClassName() {
assertEquals(string, define.className()); assertEquals(ParsedDefine.class.getName(), define.className());
verify(define).object();
verify(object).className();
} }
@Test @Test
void testAttributes() { void testAttributes() {
assertEquals(Map.of(string, property), define.attributes()); assertEquals(Map.of(), define.attributes());
verify(define).object();
verify(object).attributes();
} }
@Test @Test
void testProperties() { void testProperties() {
final var map = new LinkedHashMap<ParsedProperty, SequencedCollection<ParsedObject>>(); assertEquals(new LinkedHashMap<>(), define.properties());
map.put(property, List.of(object));
assertEquals(map, define.properties());
verify(define).object();
verify(object).properties();
}
@Test
void testChildren() {
assertEquals(List.of(define), define.children());
verify(define).object();
verify(object).children();
} }
} }

View File

@@ -0,0 +1,22 @@
package com.github.gtache.fxml.compiler.compatibility.impl;
import com.github.gtache.fxml.compiler.compatibility.GenerationCompatibility;
/**
* Implementation of {@link GenerationCompatibility}
*
* @param javaVersion The minimum supported Java version
*/
public record GenerationCompatibilityImpl(int javaVersion) implements GenerationCompatibility {
/**
* Instantiates a new compatibility
*
* @param javaVersion The minimum supported Java version
*/
public GenerationCompatibilityImpl {
if (javaVersion < 8) {
throw new IllegalArgumentException("Java version must be at least 8");
}
}
}

View File

@@ -1,6 +1,7 @@
package com.github.gtache.fxml.compiler.impl; package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.ControllerFieldInfo; import com.github.gtache.fxml.compiler.ControllerFieldInfo;
import com.github.gtache.fxml.compiler.GenericTypes;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@@ -11,7 +12,7 @@ import java.util.Objects;
* @param name The field name * @param name The field name
* @param genericTypes The generic types * @param genericTypes The generic types
*/ */
public record ControllerFieldInfoImpl(String name, List<String> genericTypes) implements ControllerFieldInfo { public record ControllerFieldInfoImpl(String name, List<GenericTypes> genericTypes) implements ControllerFieldInfo {
/** /**
* Instantiates a new info * Instantiates a new info

View File

@@ -1,10 +1,9 @@
package com.github.gtache.fxml.compiler.impl; package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.ControllerInjection;
import com.github.gtache.fxml.compiler.InjectionType; import com.github.gtache.fxml.compiler.InjectionType;
/** /**
* Base field {@link InjectionType}s for {@link ControllerInjection} * Base field {@link InjectionType}s
*/ */
public enum ControllerFieldInjectionTypes implements InjectionType { public enum ControllerFieldInjectionTypes implements InjectionType {
/** /**

View File

@@ -4,17 +4,22 @@ import com.github.gtache.fxml.compiler.ControllerFieldInfo;
import com.github.gtache.fxml.compiler.ControllerInfo; import com.github.gtache.fxml.compiler.ControllerInfo;
import java.util.Map; import java.util.Map;
import java.util.Objects;
/** /**
* Implementation of {@link ControllerInfo} * Implementation of {@link ControllerInfo}
* *
* @param className The controller class name
* @param handlerHasArgument The mapping of method name to true if the method has an argument * @param handlerHasArgument The mapping of method name to true if the method has an argument
* @param fieldInfo The mapping of property name to controller field info * @param fieldInfo The mapping of property name to controller field info
* @param hasInitialize True if the controller has an initialize method
*/ */
public record ControllerInfoImpl(Map<String, Boolean> handlerHasArgument, public record ControllerInfoImpl(String className, Map<String, Boolean> handlerHasArgument,
Map<String, ControllerFieldInfo> fieldInfo) implements ControllerInfo { Map<String, ControllerFieldInfo> fieldInfo,
boolean hasInitialize) implements ControllerInfo {
public ControllerInfoImpl { public ControllerInfoImpl {
Objects.requireNonNull(className);
handlerHasArgument = Map.copyOf(handlerHasArgument); handlerHasArgument = Map.copyOf(handlerHasArgument);
fieldInfo = Map.copyOf(fieldInfo); fieldInfo = Map.copyOf(fieldInfo);
} }

View File

@@ -1,22 +0,0 @@
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);
}
}

View File

@@ -0,0 +1,17 @@
package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.InjectionType;
/**
* Base controller {@link InjectionType}s
*/
public enum ControllerInjectionTypes implements InjectionType {
/**
* Inject the controller instance
*/
INSTANCE,
/**
* Inject a controller factory
*/
FACTORY
}

View File

@@ -1,10 +1,9 @@
package com.github.gtache.fxml.compiler.impl; package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.ControllerInjection;
import com.github.gtache.fxml.compiler.InjectionType; import com.github.gtache.fxml.compiler.InjectionType;
/** /**
* Base methods {@link InjectionType}s for {@link ControllerInjection} * Base methods {@link InjectionType}s
*/ */
public enum ControllerMethodsInjectionType implements InjectionType { public enum ControllerMethodsInjectionType implements InjectionType {
/** /**

View File

@@ -1,30 +1,38 @@
package com.github.gtache.fxml.compiler.impl; 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.GenerationParameters;
import com.github.gtache.fxml.compiler.ResourceBundleInjection; import com.github.gtache.fxml.compiler.InjectionType;
import com.github.gtache.fxml.compiler.compatibility.GenerationCompatibility;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import static java.util.Objects.requireNonNull;
/** /**
* Implementation of {@link GenerationParameters} * Implementation of {@link GenerationParameters}
* *
* @param controllerInjections The mapping of controller class name to controller injection * @param compatibility The compatibility info
* @param sourceToGeneratedClassName The mapping of fx:include source to generated class name * @param useImageInputStreamConstructor True if the InputStream constructor should be used
* @param sourceToControllerName The mapping of fx:include source to controller class name * @param bundleMap The mapping of controller class to resource bundle path
* @param resourceBundleInjection The resource bundle injection * @param controllerInjectionType The controller injection type
* @param fieldInjectionType The field injection type
* @param methodInjectionType The method injection type
* @param resourceInjectionType The resource injection type
*/ */
public record GenerationParametersImpl(Map<String, ControllerInjection> controllerInjections, public record GenerationParametersImpl(GenerationCompatibility compatibility, boolean useImageInputStreamConstructor,
Map<String, String> sourceToGeneratedClassName, Map<String, String> bundleMap,
Map<String, String> sourceToControllerName, InjectionType controllerInjectionType,
ResourceBundleInjection resourceBundleInjection) implements GenerationParameters { InjectionType fieldInjectionType,
InjectionType methodInjectionType,
InjectionType resourceInjectionType) implements GenerationParameters {
public GenerationParametersImpl { public GenerationParametersImpl {
controllerInjections = Map.copyOf(controllerInjections); requireNonNull(compatibility);
sourceToGeneratedClassName = Map.copyOf(sourceToGeneratedClassName); bundleMap = Map.copyOf(bundleMap);
sourceToControllerName = Map.copyOf(sourceToControllerName); requireNonNull(controllerInjectionType);
Objects.requireNonNull(resourceBundleInjection); requireNonNull(fieldInjectionType);
requireNonNull(methodInjectionType);
requireNonNull(resourceInjectionType);
} }
} }

View File

@@ -3,6 +3,7 @@ package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.ControllerInfo; import com.github.gtache.fxml.compiler.ControllerInfo;
import com.github.gtache.fxml.compiler.GenerationParameters; import com.github.gtache.fxml.compiler.GenerationParameters;
import com.github.gtache.fxml.compiler.GenerationRequest; import com.github.gtache.fxml.compiler.GenerationRequest;
import com.github.gtache.fxml.compiler.SourceInfo;
import com.github.gtache.fxml.compiler.parsing.ParsedObject; import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import java.util.Objects; import java.util.Objects;
@@ -12,15 +13,17 @@ import java.util.Objects;
* *
* @param parameters The generation parameters * @param parameters The generation parameters
* @param controllerInfo The controller info * @param controllerInfo The controller info
* @param sourceInfo The source info
* @param rootObject The root object * @param rootObject The root object
* @param outputClassName The output class name * @param outputClassName The output class name
*/ */
public record GenerationRequestImpl(GenerationParameters parameters, ControllerInfo controllerInfo, public record GenerationRequestImpl(GenerationParameters parameters, ControllerInfo controllerInfo,
ParsedObject rootObject, SourceInfo sourceInfo, ParsedObject rootObject,
String outputClassName) implements GenerationRequest { String outputClassName) implements GenerationRequest {
public GenerationRequestImpl { public GenerationRequestImpl {
Objects.requireNonNull(parameters); Objects.requireNonNull(parameters);
Objects.requireNonNull(controllerInfo); Objects.requireNonNull(controllerInfo);
Objects.requireNonNull(sourceInfo);
Objects.requireNonNull(rootObject); Objects.requireNonNull(rootObject);
Objects.requireNonNull(outputClassName); Objects.requireNonNull(outputClassName);
} }

View File

@@ -3,12 +3,10 @@ package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.GenerationException; import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.GenerationRequest; import com.github.gtache.fxml.compiler.GenerationRequest;
import com.github.gtache.fxml.compiler.Generator; import com.github.gtache.fxml.compiler.Generator;
import com.github.gtache.fxml.compiler.impl.internal.ConstructorFormatter;
import com.github.gtache.fxml.compiler.impl.internal.GenerationProgress; import com.github.gtache.fxml.compiler.impl.internal.GenerationProgress;
import com.github.gtache.fxml.compiler.impl.internal.HelperMethodsProvider; import com.github.gtache.fxml.compiler.impl.internal.HelperMethodsFormatter;
import com.github.gtache.fxml.compiler.impl.internal.LoadMethodFormatter;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getControllerInjection;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getVariablePrefix;
import static com.github.gtache.fxml.compiler.impl.internal.ObjectFormatter.format;
//TODO handle binding (${}) //TODO handle binding (${})
@@ -16,7 +14,7 @@ import static com.github.gtache.fxml.compiler.impl.internal.ObjectFormatter.form
* Implementation of {@link Generator} * Implementation of {@link Generator}
*/ */
public class GeneratorImpl implements Generator { public class GeneratorImpl implements Generator {
@Override @Override
public String generate(final GenerationRequest request) throws GenerationException { public String generate(final GenerationRequest request) throws GenerationException {
@@ -24,143 +22,37 @@ public class GeneratorImpl implements Generator {
final var className = request.outputClassName(); final var className = request.outputClassName();
final var pkgName = className.substring(0, className.lastIndexOf('.')); final var pkgName = className.substring(0, className.lastIndexOf('.'));
final var simpleClassName = className.substring(className.lastIndexOf('.') + 1); final var simpleClassName = className.substring(className.lastIndexOf('.') + 1);
final var loadMethod = getLoadMethod(progress); final var controllerInjectionClass = request.controllerInfo().className();
final var controllerInjection = getControllerInjection(progress);
final var controllerInjectionType = controllerInjection.fieldInjectionType();
final var controllerInjectionClass = controllerInjection.injectionClass();
final String constructorArgument;
final String constructorControllerJavadoc;
final String controllerArgumentType;
final String controllerMapType;
if (controllerInjectionType == ControllerFieldInjectionTypes.FACTORY) {
constructorArgument = "controllerFactory";
constructorControllerJavadoc = "controller factory";
controllerArgumentType = "com.github.gtache.fxml.compiler.ControllerFactory<" + controllerInjectionClass + ">";
controllerMapType = "com.github.gtache.fxml.compiler.ControllerFactory<?>";
} else {
constructorArgument = "controller";
constructorControllerJavadoc = "controller";
controllerArgumentType = controllerInjectionClass;
controllerMapType = "Object";
}
final var helperMethods = HelperMethodsProvider.getHelperMethods(progress);
return """
package %1$s;
/**
* Generated code, not thread-safe
*/
public final class %2$s {
private final java.util.Map<Class<?>, %7$s> controllersMap;
private final java.util.Map<Class<?>, java.util.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(java.util.Map.of(%3$s.class, %4$s), java.util.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 java.util.ResourceBundle resourceBundle) {
this(java.util.Map.of(%3$s.class, %4$s), java.util.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 java.util.Map<Class<?>, %7$s> controllersMap, final java.util.Map<Class<?>, java.util.ResourceBundle> resourceBundlesMap) {
this.controllersMap = java.util.Map.copyOf(controllersMap);
this.resourceBundlesMap = java.util.Map.copyOf(resourceBundlesMap);
}
/**
* Loads the view. Can only be called once.
*
* @return The view parent
*/
%6$s
%9$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, helperMethods);
}
/**
* Computes the load method
*
* @param progress The generation progress
* @return The load method
*/
private static String getLoadMethod(final GenerationProgress progress) throws GenerationException {
final var request = progress.request();
final var rootObject = request.rootObject();
final var controllerInjection = getControllerInjection(progress);
final var controllerInjectionType = controllerInjection.fieldInjectionType();
final var controllerClass = controllerInjection.injectionClass();
final var sb = progress.stringBuilder(); final var sb = progress.stringBuilder();
sb.append("public <T> T load() {\n"); sb.append("package ").append(pkgName).append(";\n");
sb.append(" if (loaded) {\n"); sb.append("\n");
sb.append(" throw new IllegalStateException(\"Already loaded\");\n"); sb.append("/**\n");
sb.append(" }\n"); sb.append(" * Generated code, not thread-safe\n");
final var resourceBundleInjection = request.parameters().resourceBundleInjection(); sb.append(" */\n");
if (resourceBundleInjection.injectionType() == ResourceBundleInjectionTypes.GET_BUNDLE) { sb.append("public final class ").append(simpleClassName).append(" {\n");
sb.append(" final var bundle = java.util.ResourceBundle.getBundle(\"").append(resourceBundleInjection.bundleName()).append("\");\n"); sb.append("\n");
} else if (resourceBundleInjection.injectionType() == ResourceBundleInjectionTypes.CONSTRUCTOR) { ConstructorFormatter.formatFieldsAndConstructor(progress);
sb.append(" final var bundle = resourceBundlesMap.get(").append(controllerClass).append(".class);\n"); sb.append("\n");
} LoadMethodFormatter.formatLoadMethod(progress);
if (controllerInjectionType == ControllerFieldInjectionTypes.FACTORY) { sb.append("\n");
sb.append(" final var fieldMap = new HashMap<String, Object>();\n"); HelperMethodsFormatter.formatHelperMethods(progress);
} else { sb.append("\n");
sb.append(" controller = (").append(controllerClass).append(") controllersMap.get(").append(controllerClass).append(".class);\n"); formatControllerMethod(progress, controllerInjectionClass);
} sb.append("}\n");
final var variableName = progress.getNextVariableName(getVariablePrefix(rootObject));
format(progress, rootObject, variableName);
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");
progress.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 java.lang.reflect.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 (T) ").append(variableName).append(";\n");
sb.append("}");
return sb.toString(); return sb.toString();
} }
private static void formatControllerMethod(final GenerationProgress progress, final String controllerInjectionClass) {
final var sb = progress.stringBuilder();
sb.append(" /**\n");
sb.append(" * @return The controller\n");
sb.append(" */\n");
sb.append(" public ").append(controllerInjectionClass).append(" controller() {\n");
sb.append(" if (loaded) {\n");
sb.append(" return controller;\n");
sb.append(" } else {\n");
sb.append(" throw new IllegalStateException(\"Not loaded\");\n");
sb.append(" }\n");
sb.append(" }\n");
}
} }

View File

@@ -0,0 +1,27 @@
package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.GenericTypes;
import java.util.List;
import java.util.Objects;
/**
* Implementation of {@link GenericTypes}
*
* @param name The name
* @param subTypes The subtypes
*/
public record GenericTypesImpl(String name, List<GenericTypes> subTypes) implements GenericTypes {
/**
* Instantiates a new generic types
*
* @param name The name
* @param subTypes The subtypes
* @throws NullPointerException if any parameter is null
*/
public GenericTypesImpl {
Objects.requireNonNull(name);
subTypes = List.copyOf(subTypes);
}
}

View File

@@ -1,21 +0,0 @@
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);
}
}

View File

@@ -1,16 +1,23 @@
package com.github.gtache.fxml.compiler.impl; package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.InjectionType; import com.github.gtache.fxml.compiler.InjectionType;
import com.github.gtache.fxml.compiler.ResourceBundleInjection;
/** /**
* Base {@link InjectionType}s for {@link ResourceBundleInjection} * Base {@link InjectionType}s for resource bundles
*/ */
public enum ResourceBundleInjectionTypes implements InjectionType { public enum ResourceBundleInjectionTypes implements InjectionType {
/** /**
* Resource bundle is injected in the constructor * Resource bundle is injected in the constructor
*/ */
CONSTRUCTOR, CONSTRUCTOR,
/**
* Resource bundle is injected as a function in the constructor
*/
CONSTRUCTOR_FUNCTION,
/**
* Resource bundle name is injected in the constructor
*/
CONSTRUCTOR_NAME,
/** /**
* Resource bundle is loaded using getBundle * Resource bundle is loaded using getBundle
*/ */

View File

@@ -0,0 +1,32 @@
package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.SourceInfo;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Implementation of {@link SourceInfo}
*
* @param generatedClassName The generated class name
* @param controllerClassName The controller class name
* @param sourceFile The source file
* @param includedSources The included sources
* @param sourceToSourceInfo The mapping of source value to source info
* @param requiresResourceBundle True if the subtree requires a resource bundle
*/
public record SourceInfoImpl(String generatedClassName, String controllerClassName, Path sourceFile,
List<SourceInfo> includedSources,
Map<String, SourceInfo> sourceToSourceInfo,
boolean requiresResourceBundle) implements SourceInfo {
public SourceInfoImpl {
Objects.requireNonNull(generatedClassName);
Objects.requireNonNull(controllerClassName);
Objects.requireNonNull(sourceFile);
includedSources = List.copyOf(includedSources);
sourceToSourceInfo = Map.copyOf(sourceToSourceInfo);
}
}

View File

@@ -0,0 +1,193 @@
package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.SourceInfo;
import com.github.gtache.fxml.compiler.impl.ControllerInjectionTypes;
import com.github.gtache.fxml.compiler.impl.ResourceBundleInjectionTypes;
import com.github.gtache.fxml.compiler.parsing.ParsedInclude;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Utility class to provide the view's constructor and fields
*/
public final class ConstructorFormatter {
private ConstructorFormatter() {
}
public static void formatFieldsAndConstructor(final GenerationProgress progress) throws GenerationException {
final var className = progress.request().outputClassName();
final var simpleClassName = className.substring(className.lastIndexOf('.') + 1);
final var mainControllerClass = progress.request().controllerInfo().className();
final var isFactory = progress.request().parameters().controllerInjectionType() == ControllerInjectionTypes.FACTORY;
if (hasDuplicateControllerClass(progress) && !isFactory) {
throw new GenerationException("Some controllers in the view tree have the same class ; Factory field injection is required");
}
fillControllers(progress);
final var sb = progress.stringBuilder();
final var controllerMap = progress.controllerClassToVariable();
controllerMap.forEach((c, v) -> sb.append(" private final ").append(c).append(" ").append(v).append(isFactory ? "Factory" : "").append(";\n"));
final var controllerArg = getVariableName("controller", isFactory);
final var controllerArgClass = getType(mainControllerClass, isFactory);
final var resourceBundleInfo = getResourceBundleInfo(progress);
final var resourceBundleType = resourceBundleInfo.type();
final var resourceBundleArg = resourceBundleInfo.variableName();
if (isFactory) {
sb.append(" private final ").append(controllerArgClass).append(" ").append(controllerArg).append(";\n");
sb.append(" private ").append(mainControllerClass).append(" controller;\n");
} else {
sb.append(" private final ").append(mainControllerClass).append(" controller;\n");
}
if (resourceBundleType != null) {
sb.append(" private final ").append(resourceBundleType).append(" ").append(resourceBundleArg).append(";\n");
}
sb.append(" private boolean loaded;\n");
sb.append("\n");
sb.append(" /**\n");
sb.append(" * Instantiates a new ").append(simpleClassName).append("\n");
sb.append(" * @param ").append(controllerArg).append(" The controller ").append(isFactory ? "factory" : "instance").append("\n");
controllerMap.forEach((c, s) -> sb.append(" * @param ").append(getVariableName(s, isFactory))
.append(" The subcontroller ").append(isFactory ? "factory" : "instance").append(" for ").append(c).append("\n"));
if (resourceBundleType != null) {
sb.append(" * @param ").append(resourceBundleArg).append(" The resource bundle\n");
}
sb.append(" */\n");
final var arguments = "final " + controllerArgClass + " " + controllerArg +
((controllerMap.isEmpty()) ? "" : ", ") +
controllerMap.entrySet().stream().map(e -> "final " + getType(e.getKey(), isFactory) + " " + getVariableName(e.getValue(), isFactory))
.sorted().collect(Collectors.joining(", "))
+ (resourceBundleType == null ? "" : ", final " + resourceBundleType + " " + resourceBundleArg);
sb.append(" public ").append(simpleClassName).append("(").append(arguments).append(") {\n");
sb.append(" this.").append(controllerArg).append(" = java.util.Objects.requireNonNull(").append(controllerArg).append(");\n");
controllerMap.values().forEach(s -> sb.append(" this.").append(getVariableName(s, isFactory)).append(" = java.util.Objects.requireNonNull(").append(getVariableName(s, isFactory)).append(");\n"));
if (resourceBundleType != null) {
sb.append(" this.").append(resourceBundleArg).append(" = java.util.Objects.requireNonNull(").append(resourceBundleArg).append(");\n");
}
sb.append(" }\n");
}
private static ResourceBundleInfo getResourceBundleInfo(final GenerationProgress progress) throws GenerationException {
final var injectionType = progress.request().parameters().resourceInjectionType();
if (injectionType instanceof final ResourceBundleInjectionTypes types) {
return switch (types) {
case CONSTRUCTOR -> new ResourceBundleInfo("java.util.ResourceBundle", "resourceBundle");
case CONSTRUCTOR_FUNCTION ->
new ResourceBundleInfo("java.util.function.Function<String, String>", "resourceBundleFunction");
case CONSTRUCTOR_NAME -> new ResourceBundleInfo("String", "resourceBundleName");
case GETTER -> new ResourceBundleInfo(null, null);
case GET_BUNDLE -> new ResourceBundleInfo(null, null);
};
} else {
throw new GenerationException("Unknown resource injection type : " + injectionType);
}
}
private record ResourceBundleInfo(String type, String variableName) {
}
private static String getType(final String controllerClass, final boolean isFactory) {
if (isFactory) {
return "java.util.function.Function<java.util.Map<String, Object>, " + controllerClass + ">";
} else {
return controllerClass;
}
}
private static String getVariableName(final String variableName, final boolean isFactory) {
if (isFactory) {
return getVariableName(variableName, "Factory");
} else {
return variableName;
}
}
private static String getVariableName(final String variableName, final String suffix) {
return variableName + suffix;
}
private static boolean hasDuplicateControllerClass(final GenerationProgress progress) {
final var set = new HashSet<String>();
return hasDuplicateControllerClass(progress.request().sourceInfo(), set);
}
private static boolean hasDuplicateControllerClass(final SourceInfo info, final Set<String> controllers) {
final var controllerClass = info.controllerClassName();
if (controllers.contains(controllerClass)) {
return true;
}
return info.includedSources().stream().anyMatch(s -> hasDuplicateControllerClass(s, controllers));
}
private static void fillControllers(final GenerationProgress progress) {
progress.request().sourceInfo().includedSources().forEach(s -> fillControllers(progress, s));
}
private static void fillControllers(final GenerationProgress progress, final SourceInfo info) {
progress.controllerClassToVariable().put(info.controllerClassName(), progress.getNextVariableName(GenerationHelper.getVariablePrefix(info.controllerClassName())));
info.includedSources().forEach(s -> fillControllers(progress, s));
}
private static void fillControllers(final SourceInfo info, final Set<? super String> controllers) {
controllers.add(info.controllerClassName());
info.includedSources().forEach(s -> fillControllers(s, controllers));
}
static String formatSubViewConstructorCall(final GenerationProgress progress, final ParsedInclude include) throws GenerationException {
final var request = progress.request();
final var info = request.sourceInfo();
final var subInfo = info.sourceToSourceInfo().get(include.source());
if (subInfo == null) {
throw new GenerationException("Unknown include source : " + include.source());
} else {
final var isFactory = request.parameters().controllerInjectionType() == ControllerInjectionTypes.FACTORY;
final var subClassName = subInfo.controllerClassName();
final var subControllerVariable = getVariableName(progress.controllerClassToVariable().get(subClassName), isFactory);
final var subControllers = new HashSet<String>();
subInfo.includedSources().forEach(s -> fillControllers(s, subControllers));
final var arguments = subControllers.stream().sorted().map(c -> getVariableName(progress.controllerClassToVariable().get(c), isFactory)).collect(Collectors.joining(", "));
final var bundleVariable = subInfo.requiresResourceBundle() ? getBundleVariable(progress, include) : null;
final var argumentList = subControllerVariable + (arguments.isEmpty() ? "" : ", " + arguments) + (bundleVariable == null ? "" : ", " + bundleVariable);
final var subViewName = subInfo.generatedClassName();
final var variable = progress.getNextVariableName(GenerationHelper.getVariablePrefix(subViewName));
progress.stringBuilder().append(GenerationCompatibilityHelper.getStartVar(progress, subViewName)).append(variable).append(" = new ").append(subViewName).append("(").append(argumentList).append(");\n");
return variable;
}
}
private static String getBundleVariable(final GenerationProgress progress, final ParsedInclude include) throws GenerationException {
final var info = getResourceBundleInfo(progress);
if (info.type() == null) {
return null;
} else if (include.resources() == null) {
return info.variableName();
} else {
final var sb = progress.stringBuilder();
if (progress.request().parameters().resourceInjectionType() instanceof final ResourceBundleInjectionTypes types) {
return switch (types) {
case GETTER, GET_BUNDLE -> null;
case CONSTRUCTOR_NAME -> {
final var bundleVariable = progress.getNextVariableName("resourceBundleName");
sb.append(GenerationCompatibilityHelper.getStartVar(progress, "String")).append(bundleVariable).append(" = \"").append(include.resources()).append("\";\n");
yield bundleVariable;
}
case CONSTRUCTOR_FUNCTION -> {
final var bundleVariable = progress.getNextVariableName("resourceBundleFunction");
sb.append(GenerationCompatibilityHelper.getStartVar(progress, "java.util.function.Function<String, String>")).append(bundleVariable).append(" = (java.util.function.Function<String, String>) s -> \"").append(include.resources()).append("\";\n");
yield bundleVariable;
}
case CONSTRUCTOR -> {
final var bundleVariable = progress.getNextVariableName("resourceBundle");
sb.append(GenerationCompatibilityHelper.getStartVar(progress, "java.util.ResourceBundle")).append(bundleVariable).append(" = java.util.ResourceBundle.getBundle(\"").append(include.resources()).append("\");\n");
yield bundleVariable;
}
};
} else {
throw new GenerationException("Unknown resource injection type : " + progress.request().parameters().resourceInjectionType());
}
}
}
}

View File

@@ -11,12 +11,11 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static com.github.gtache.fxml.compiler.impl.internal.ReflectionHelper.getConstructorArgs;
/** /**
* Helper methods for {@link GeneratorImpl} to handle constructors * Helper methods for {@link GeneratorImpl} to handle objects constructors
*/ */
final class ConstructorHelper { final class ConstructorHelper {
private ConstructorHelper() { private ConstructorHelper() {
} }
@@ -58,7 +57,7 @@ final class ConstructorHelper {
static ConstructorArgs getMatchingConstructorArgs(final Constructor<?>[] constructors, final Set<String> allPropertyNames) { static ConstructorArgs getMatchingConstructorArgs(final Constructor<?>[] constructors, final Set<String> allPropertyNames) {
ConstructorArgs matchingConstructorArgs = null; ConstructorArgs matchingConstructorArgs = null;
for (final var constructor : constructors) { for (final var constructor : constructors) {
final var constructorArgs = getConstructorArgs(constructor); final var constructorArgs = ReflectionHelper.getConstructorArgs(constructor);
final var matchingArgsCount = getMatchingArgsCount(constructorArgs, allPropertyNames); final var matchingArgsCount = getMatchingArgsCount(constructorArgs, allPropertyNames);
if (matchingConstructorArgs == null ? matchingArgsCount > 0 : matchingArgsCount > getMatchingArgsCount(matchingConstructorArgs, allPropertyNames)) { if (matchingConstructorArgs == null ? matchingArgsCount > 0 : matchingArgsCount > getMatchingArgsCount(matchingConstructorArgs, allPropertyNames)) {
matchingConstructorArgs = constructorArgs; matchingConstructorArgs = constructorArgs;

View File

@@ -7,15 +7,12 @@ import com.github.gtache.fxml.compiler.impl.ControllerMethodsInjectionType;
import com.github.gtache.fxml.compiler.impl.GeneratorImpl; import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
import com.github.gtache.fxml.compiler.parsing.ParsedProperty; import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getControllerInjection;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getSetMethod;
/** /**
* Various methods to help {@link GeneratorImpl} for injecting controllers * Various methods to help {@link GeneratorImpl} for injecting controllers
*/ */
final class ControllerInjector { final class ControllerInjector {
private ControllerInjector() {
private ControllerInjector() {
} }
/** /**
@@ -27,23 +24,22 @@ final class ControllerInjector {
* @throws GenerationException if an error occurs * @throws GenerationException if an error occurs
*/ */
static void injectControllerField(final GenerationProgress progress, final String id, final String variable) throws GenerationException { static void injectControllerField(final GenerationProgress progress, final String id, final String variable) throws GenerationException {
final var controllerInjection = getControllerInjection(progress); final var fieldInjectionType = progress.request().parameters().fieldInjectionType();
final var controllerInjectionType = controllerInjection.fieldInjectionType(); if (fieldInjectionType instanceof final ControllerFieldInjectionTypes types) {
if (controllerInjectionType instanceof final ControllerFieldInjectionTypes types) {
final var sb = progress.stringBuilder(); final var sb = progress.stringBuilder();
switch (types) { switch (types) {
case FACTORY -> case FACTORY ->
sb.append(" fieldMap.put(\"").append(id).append("\", ").append(variable).append(");\n"); sb.append(" fieldMap.put(\"").append(id).append("\", ").append(variable).append(");\n");
case ASSIGN -> sb.append(" controller.").append(id).append(" = ").append(variable).append(";\n"); case ASSIGN -> sb.append(" controller.").append(id).append(" = ").append(variable).append(";\n");
case SETTERS -> { case SETTERS -> {
final var setMethod = getSetMethod(id); final var setMethod = GenerationHelper.getSetMethod(id);
sb.append(" controller.").append(setMethod).append("(").append(variable).append(");\n"); sb.append(" controller.").append(setMethod).append("(").append(variable).append(");\n");
} }
case REFLECTION -> case REFLECTION ->
sb.append(" injectField(\"").append(id).append("\", ").append(variable).append(");\n"); sb.append(" injectField(\"").append(id).append("\", ").append(variable).append(");\n");
} }
} else { } else {
throw new GenerationException("Unknown controller injection type : " + controllerInjectionType); throw new GenerationException("Unknown controller injection type : " + fieldInjectionType);
} }
} }
@@ -80,14 +76,14 @@ final class ControllerInjector {
* @throws GenerationException if an error occurs * @throws GenerationException if an error occurs
*/ */
private static void injectControllerMethod(final GenerationProgress progress, final String methodInjection) throws GenerationException { private static void injectControllerMethod(final GenerationProgress progress, final String methodInjection) throws GenerationException {
final var injection = getControllerInjection(progress); final var fieldInjectionType = progress.request().parameters().fieldInjectionType();
if (injection.fieldInjectionType() instanceof final ControllerFieldInjectionTypes fieldTypes) { if (fieldInjectionType instanceof final ControllerFieldInjectionTypes fieldTypes) {
switch (fieldTypes) { switch (fieldTypes) {
case FACTORY -> progress.controllerFactoryPostAction().add(methodInjection); case FACTORY -> progress.controllerFactoryPostAction().add(methodInjection);
case ASSIGN, SETTERS, REFLECTION -> progress.stringBuilder().append(methodInjection); case ASSIGN, SETTERS, REFLECTION -> progress.stringBuilder().append(methodInjection);
} }
} else { } else {
throw getUnknownInjectionException(injection.fieldInjectionType()); throw getUnknownInjectionException(fieldInjectionType);
} }
} }
@@ -101,24 +97,24 @@ final class ControllerInjector {
* @throws GenerationException if an error occurs * @throws GenerationException if an error occurs
*/ */
private static String getEventHandlerMethodInjection(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) throws GenerationException { private static String getEventHandlerMethodInjection(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) throws GenerationException {
final var setMethod = getSetMethod(property.name()); final var setMethod = GenerationHelper.getSetMethod(property.name());
final var injection = getControllerInjection(progress); final var methodInjectionType = progress.request().parameters().methodInjectionType();
final var controllerMethod = property.value().replace("#", ""); final var controllerMethod = property.value().replace("#", "");
if (injection.methodInjectionType() instanceof final ControllerMethodsInjectionType methodTypes) { if (methodInjectionType instanceof final ControllerMethodsInjectionType methodTypes) {
return switch (methodTypes) { return switch (methodTypes) {
case REFERENCE -> { case REFERENCE -> {
final var hasArgument = progress.request().controllerInfo().handlerHasArgument(controllerMethod); final var hasArgument = progress.request().controllerInfo().handlerHasArgument(controllerMethod);
if (hasArgument) { if (hasArgument) {
yield " " + parentVariable + "." + setMethod + "(controller::" + controllerMethod + ");\n"; yield " " + parentVariable + "." + setMethod + "(controller::" + controllerMethod + ");\n";
} else { } else {
yield " " + parentVariable + "." + setMethod + "(e -> controller." + controllerMethod + "());\n"; yield " " + parentVariable + "." + setMethod + "(e -> controller." + controllerMethod + "());\n";
} }
} }
case REFLECTION -> case REFLECTION ->
" " + parentVariable + "." + setMethod + "(e -> callEventHandlerMethod(\"" + controllerMethod + "\", e));\n"; " " + parentVariable + "." + setMethod + "(e -> callEventHandlerMethod(\"" + controllerMethod + "\", e));\n";
}; };
} else { } else {
throw getUnknownInjectionException(injection.methodInjectionType()); throw getUnknownInjectionException(methodInjectionType);
} }
} }
@@ -133,18 +129,18 @@ final class ControllerInjector {
* @throws GenerationException if an error occurs * @throws GenerationException if an error occurs
*/ */
private static String getCallbackMethodInjection(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String argumentClazz) throws GenerationException { private static String getCallbackMethodInjection(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String argumentClazz) throws GenerationException {
final var setMethod = getSetMethod(property.name()); final var setMethod = GenerationHelper.getSetMethod(property.name());
final var injection = getControllerInjection(progress); final var methodInjectionType = progress.request().parameters().methodInjectionType();
final var controllerMethod = property.value().replace("#", ""); final var controllerMethod = property.value().replace("#", "");
if (injection.methodInjectionType() instanceof final ControllerMethodsInjectionType methodTypes) { if (methodInjectionType instanceof final ControllerMethodsInjectionType methodTypes) {
return switch (methodTypes) { return switch (methodTypes) {
case REFERENCE -> case REFERENCE ->
" " + parentVariable + "." + setMethod + "(controller::" + controllerMethod + ");\n"; " " + parentVariable + "." + setMethod + "(controller::" + controllerMethod + ");\n";
case REFLECTION -> case REFLECTION ->
" " + parentVariable + "." + setMethod + "(e -> callCallbackMethod(\"" + controllerMethod + "\", e, " + argumentClazz + "));\n"; " " + parentVariable + "." + setMethod + "(e -> callCallbackMethod(\"" + controllerMethod + "\", e, " + argumentClazz + "));\n";
}; };
} else { } else {
throw getUnknownInjectionException(injection.methodInjectionType()); throw getUnknownInjectionException(methodInjectionType);
} }
} }

View File

@@ -5,7 +5,7 @@ import com.github.gtache.fxml.compiler.impl.ControllerFieldInjectionTypes;
import com.github.gtache.fxml.compiler.impl.GeneratorImpl; import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
import com.github.gtache.fxml.compiler.parsing.ParsedProperty; import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*; import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.EXPRESSION_PREFIX;
/** /**
* Helper methods for {@link GeneratorImpl} to set fields * Helper methods for {@link GeneratorImpl} to set fields
@@ -13,7 +13,6 @@ import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
final class FieldSetter { final class FieldSetter {
private FieldSetter() { private FieldSetter() {
} }
/** /**
@@ -39,8 +38,8 @@ final class FieldSetter {
* @throws GenerationException if an error occurs * @throws GenerationException if an error occurs
*/ */
static void setField(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String fieldType) throws GenerationException { static void setField(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String fieldType) throws GenerationException {
final var injection = getControllerInjection(progress); final var fieldInjectionType = progress.request().parameters().fieldInjectionType();
if (injection.fieldInjectionType() instanceof final ControllerFieldInjectionTypes fieldTypes) { if (fieldInjectionType instanceof final ControllerFieldInjectionTypes fieldTypes) {
switch (fieldTypes) { switch (fieldTypes) {
case ASSIGN -> setAssign(progress, property, parentVariable); case ASSIGN -> setAssign(progress, property, parentVariable);
case FACTORY -> setFactory(progress, property, parentVariable); case FACTORY -> setFactory(progress, property, parentVariable);
@@ -48,14 +47,14 @@ final class FieldSetter {
case REFLECTION -> setReflection(progress, property, parentVariable, fieldType); case REFLECTION -> setReflection(progress, property, parentVariable, fieldType);
} }
} else { } else {
throw new GenerationException("Unknown injection type : " + injection.fieldInjectionType()); throw new GenerationException("Unknown injection type : " + fieldInjectionType);
} }
} }
private static void setAssign(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) { private static void setAssign(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) {
final var methodName = getSetMethod(property); final var methodName = GenerationHelper.getSetMethod(property);
final var value = property.value().replace("$", ""); final var value = property.value().replace(EXPRESSION_PREFIX, "");
progress.stringBuilder().append(" ").append(parentVariable).append(".").append(methodName).append("(").append(value).append(");\n"); progress.stringBuilder().append(" ").append(parentVariable).append(".").append(methodName).append("(").append(value).append(");\n");
} }
private static void setFactory(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) { private static void setFactory(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) {
@@ -67,28 +66,27 @@ final class FieldSetter {
} }
private static String getSetString(final ParsedProperty property, final String parentVariable) { private static String getSetString(final ParsedProperty property, final String parentVariable) {
final var methodName = getSetMethod(property); final var methodName = GenerationHelper.getSetMethod(property);
final var value = property.value().replace("$", ""); final var value = property.value().replace(EXPRESSION_PREFIX, "");
final var split = value.split("\\."); final var split = value.split("\\.");
final var getterName = getGetMethod(split[1]); final var getterName = GenerationHelper.getGetMethod(split[1]);
return " " + parentVariable + "." + methodName + "(" + split[0] + "." + getterName + ");\n"; return " " + parentVariable + "." + methodName + "(" + split[0] + "." + getterName + ");\n";
} }
private static void setReflection(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String fieldType) { private static void setReflection(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String fieldType) {
final var methodName = getSetMethod(property); final var methodName = GenerationHelper.getSetMethod(property);
final var value = property.value().replace("$", ""); final var value = property.value().replace(EXPRESSION_PREFIX, "");
final var split = value.split("\\."); final var split = value.split("\\.");
final var fieldName = split[1]; final var fieldName = split[1];
progress.stringBuilder().append(""" final var sb = progress.stringBuilder();
try { sb.append(" try {\n");
final var field = controller.getClass().getDeclaredField("%s"); sb.append(" ").append(GenerationCompatibilityHelper.getStartVar(progress, "java.lang.reflect.Field", 0)).append("field = controller.getClass().getDeclaredField(\"").append(fieldName).append("\");\n");
field.setAccessible(true); sb.append(" field.setAccessible(true);\n");
final var value = (%s) field.get(controller); sb.append(" final var value = (").append(fieldType).append(") field.get(controller);\n");
%s.%s(value); sb.append(" ").append(parentVariable).append(".").append(methodName).append("(value);\n");
} catch (NoSuchFieldException | IllegalAccessException e) { sb.append(" } catch (final NoSuchFieldException | IllegalAccessException e) {\n");
throw new RuntimeException(e); sb.append(" throw new RuntimeException(e);\n");
} sb.append(" }\n");
""".formatted(fieldName, fieldType, parentVariable, methodName));
} }
} }

View File

@@ -12,7 +12,8 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*; import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.FX_ID;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getSortedAttributes;
/** /**
* Helper methods for {@link GeneratorImpl} to format fonts * Helper methods for {@link GeneratorImpl} to format fonts
@@ -23,6 +24,10 @@ final class FontFormatter {
} }
private static String getStartFont(final GenerationProgress progress) {
return GenerationCompatibilityHelper.getStartVar(progress, "javafx.scene.text.Font");
}
static void formatFont(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException { static void formatFont(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
if (parsedObject.children().isEmpty() && parsedObject.properties().isEmpty()) { if (parsedObject.children().isEmpty() && parsedObject.properties().isEmpty()) {
final var value = parseFontValue(parsedObject); final var value = parseFontValue(parsedObject);
@@ -38,32 +43,31 @@ final class FontFormatter {
} else { } else {
formatStyle(progress, fw, fp, size, name, variableName); formatStyle(progress, fw, fp, size, name, variableName);
} }
handleId(progress, parsedObject, variableName); GenerationHelper.handleId(progress, parsedObject, variableName);
} else { } else {
throw new GenerationException("Font cannot have children or properties : " + parsedObject); throw new GenerationException("Font cannot have children or properties : " + parsedObject);
} }
} }
private static void formatURL(final GenerationProgress progress, final URL url, final double size, final String variableName) { private static void formatURL(final GenerationProgress progress, final URL url, final double size, final String variableName) {
final var urlVariableName = URLBuilder.formatURL(progress, url.toString()); final var urlVariableName = URLFormatter.formatURL(progress, url.toString());
progress.stringBuilder().append(""" final var sb = progress.stringBuilder();
Font %1$s; sb.append(" final javafx.scene.text.Font ").append(variableName).append(";\n");
try (final var in = %2$s.openStream()) { sb.append(" try (").append(GenerationCompatibilityHelper.getStartVar(progress, "java.io.InputStream", 0)).append(" in = ").append(urlVariableName).append(".openStream()) {\n");
%1$s = Font.loadFont(in, %3$s); sb.append(" ").append(variableName).append(" = javafx.scene.text.Font.loadFont(in, ").append(size).append(");\n");
} catch (final IOException e) { sb.append(" } catch (final java.io.IOException e) {\n");
throw new RuntimeException(e); sb.append(" throw new RuntimeException(e);\n");
} sb.append(" }\n");
""".formatted(variableName, urlVariableName, size));
} }
private static void formatNoStyle(final GenerationProgress progress, final String name, final double size, final String variableName) { private static void formatNoStyle(final GenerationProgress progress, final String name, final double size, final String variableName) {
progress.stringBuilder().append(START_VAR).append(variableName).append(" = new javafx.scene.text.Font(\"").append(name).append("\", ").append(size).append(");\n"); progress.stringBuilder().append(getStartFont(progress)).append(variableName).append(" = new javafx.scene.text.Font(\"").append(name).append("\", ").append(size).append(");\n");
} }
private static void formatStyle(final GenerationProgress progress, final FontWeight fw, final FontPosture fp, final double size, final String name, final String variableName) { private static void formatStyle(final GenerationProgress progress, final FontWeight fw, final FontPosture fp, final double size, final String name, final String variableName) {
final var finalFW = fw == null ? FontWeight.NORMAL : fw; final var finalFW = fw == null ? FontWeight.NORMAL : fw;
final var finalFP = fp == null ? FontPosture.REGULAR : fp; final var finalFP = fp == null ? FontPosture.REGULAR : fp;
progress.stringBuilder().append(START_VAR).append(variableName).append(" = new javafx.scene.text.Font(\"").append(name) progress.stringBuilder().append(getStartFont(progress)).append(variableName).append(" = new javafx.scene.text.Font(\"").append(name)
.append("\", javafx.scene.text.FontWeight.").append(finalFW.name()).append(", javafx.scene.text.FontPosture.") .append("\", javafx.scene.text.FontWeight.").append(finalFW.name()).append(", javafx.scene.text.FontPosture.")
.append(finalFP.name()).append(", ").append(size).append(");\n"); .append(finalFP.name()).append(", ").append(size).append(");\n");
} }

View File

@@ -0,0 +1,55 @@
package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
/**
* Various helper methods for {@link GeneratorImpl} to handle compatibility with older java versions
*/
final class GenerationCompatibilityHelper {
private GenerationCompatibilityHelper() {
}
static String getStartVar(final GenerationProgress progress, final ParsedObject parsedObject) throws GenerationException {
return getStartVar(progress, parsedObject.className() + ReflectionHelper.getGenericTypes(progress, parsedObject));
}
static String getStartVar(final GenerationProgress progress, final String className) {
return getStartVar(progress, className, 8);
}
static String getStartVar(final GenerationProgress progress, final String className, final int indent) {
if (progress.request().parameters().compatibility().useVar()) {
return " ".repeat(indent) + "final var ";
} else {
return " ".repeat(indent) + "final " + className + " ";
}
}
static String getToList(final GenerationProgress progress) {
return switch (progress.request().parameters().compatibility().listCollector()) {
case TO_LIST -> ".toList()";
case COLLECT_TO_UNMODIFIABLE_LIST -> ".collect(java.util.stream.Collectors.toUnmodifiableList())";
case COLLECT_TO_LIST -> ".collect(java.util.stream.Collectors.toList())";
};
}
static String getGetFirst(final GenerationProgress progress) {
if (progress.request().parameters().compatibility().useGetFirst()) {
return ".getFirst()";
} else {
return ".get(0)";
}
}
static String getListOf(final GenerationProgress progress) {
if (progress.request().parameters().compatibility().useCollectionsOf()) {
return "java.util.List.of(";
} else {
return "java.util.Arrays.asList(";
}
}
}

View File

@@ -1,6 +1,5 @@
package com.github.gtache.fxml.compiler.impl.internal; package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.ControllerInjection;
import com.github.gtache.fxml.compiler.GenerationException; import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.impl.GeneratorImpl; import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
import com.github.gtache.fxml.compiler.parsing.ParsedObject; import com.github.gtache.fxml.compiler.parsing.ParsedObject;
@@ -11,21 +10,54 @@ import org.apache.logging.log4j.Logger;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static com.github.gtache.fxml.compiler.impl.internal.ControllerInjector.injectControllerField;
/** /**
* Various helper methods for {@link GeneratorImpl} * Various helper methods for {@link GeneratorImpl}
*/ */
public final class GenerationHelper { final class GenerationHelper {
private static final Logger logger = LogManager.getLogger(GenerationHelper.class); private static final Logger logger = LogManager.getLogger(GenerationHelper.class);
static final String FX_ID = "fx:id"; static final String FX_ID = "fx:id";
static final String FX_VALUE = "fx:value"; static final String FX_VALUE = "fx:value";
static final String VALUE = "value"; static final String VALUE = "value";
static final String START_VAR = " final var ";
//Taken from FXMLLoader
static final String ESCAPE_PREFIX = "\\";
static final String RELATIVE_PATH_PREFIX = "@";
static final String RESOURCE_KEY_PREFIX = "%";
static final String EXPRESSION_PREFIX = "$";
static final String BINDING_EXPRESSION_PREFIX = "${";
static final String BI_DIRECTIONAL_BINDING_PREFIX = "#{";
private GenerationHelper() { private GenerationHelper() {
}
/**
* Handles the fx:id attribute of an object
*
* @param progress The generation progress
* @param parsedObject The parsed object
* @param variableName The variable name
* @throws GenerationException if an error occurs
*/
static void handleId(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
final var id = parsedObject.attributes().get(FX_ID);
if (id != null) {
final var idValue = id.value();
final String className;
if (progress.request().controllerInfo().fieldInfo(idValue) == null) {
className = parsedObject.className();
logger.debug("Not injecting {} because it is not found in controller", idValue);
} else {
if (ReflectionHelper.isGeneric(ReflectionHelper.getClass(parsedObject.className()))) {
className = parsedObject.className() + ReflectionHelper.getGenericTypes(progress, parsedObject);
} else {
className = parsedObject.className();
}
ControllerInjector.injectControllerField(progress, idValue, variableName);
}
progress.idToVariableInfo().put(idValue, new VariableInfo(idValue, parsedObject, variableName, className));
}
} }
/** /**
@@ -34,27 +66,18 @@ public final class GenerationHelper {
* @param object The object * @param object The object
* @return The variable prefix * @return The variable prefix
*/ */
public static String getVariablePrefix(final ParsedObject object) { static String getVariablePrefix(final ParsedObject object) {
final var className = object.className(); return getVariablePrefix(object.className());
return className.substring(className.lastIndexOf('.') + 1).toLowerCase();
} }
/** /**
* Gets the controller injection object from the generation request * Returns the variable prefix for the given class name
* *
* @param progress The generation progress * @param className The class name
* @return The controller injection * @return The variable prefix
* @throws GenerationException If the controller is not found
*/ */
public static ControllerInjection getControllerInjection(final GenerationProgress progress) throws GenerationException { static String getVariablePrefix(final String className) {
final var request = progress.request(); return className.substring(className.lastIndexOf('.') + 1).toLowerCase();
final var property = request.rootObject().attributes().get("fx:controller");
if (property == null) {
throw new GenerationException("Root object must have a controller property");
} else {
final var id = property.value();
return request.parameters().controllerInjections().get(id);
}
} }
/** /**
@@ -97,27 +120,6 @@ public final class GenerationHelper {
return "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); return "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
} }
/**
* Handles the fx:id attribute of an object
*
* @param progress The generation progress
* @param parsedObject The parsed object
* @param variableName The variable name
* @throws GenerationException if an error occurs
*/
static void handleId(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
final var id = parsedObject.attributes().get(FX_ID);
if (id != null) {
progress.idToVariableName().put(id.value(), variableName);
progress.idToObject().put(id.value(), parsedObject);
if (progress.request().controllerInfo().fieldInfo(id.value()) == null) {
logger.debug("Not injecting {} because it is not found in controller", id.value());
} else {
injectControllerField(progress, id.value(), variableName);
}
}
}
/** /**
* Returns the sorted attributes of the given object * Returns the sorted attributes of the given object
* *

View File

@@ -2,28 +2,29 @@ package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.GenerationRequest; import com.github.gtache.fxml.compiler.GenerationRequest;
import com.github.gtache.fxml.compiler.impl.GeneratorImpl; import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.SequencedCollection; import java.util.SequencedCollection;
import java.util.SequencedMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Used by {@link GeneratorImpl} to track the generation progress * Used by {@link GeneratorImpl} to track the generation progress
* *
* @param request The generation request * @param request The generation request
* @param idToVariableName The id to variable name mapping * @param idToVariableInfo The id to variable info
* @param idToObject The id to parsed object mapping
* @param variableNameCounters The variable name counters for variable name generation * @param variableNameCounters The variable name counters for variable name generation
* @param controllerClassToVariable The controller class to variable mapping
* @param controllerFactoryPostAction The controller factory post action for factory injection * @param controllerFactoryPostAction The controller factory post action for factory injection
* @param stringBuilder The string builder * @param stringBuilder The string builder
*/ */
public record GenerationProgress(GenerationRequest request, Map<String, String> idToVariableName, public record GenerationProgress(GenerationRequest request, Map<String, VariableInfo> idToVariableInfo,
Map<String, ParsedObject> idToObject,
Map<String, AtomicInteger> variableNameCounters, Map<String, AtomicInteger> variableNameCounters,
SequencedMap<String, String> controllerClassToVariable,
SequencedCollection<String> controllerFactoryPostAction, SequencedCollection<String> controllerFactoryPostAction,
StringBuilder stringBuilder) { StringBuilder stringBuilder) {
@@ -31,18 +32,18 @@ public record GenerationProgress(GenerationRequest request, Map<String, String>
* Instantiates a new GenerationProgress * Instantiates a new GenerationProgress
* *
* @param request The generation request * @param request The generation request
* @param idToVariableName The id to variable name mapping * @param idToVariableInfo The id to variable info mapping
* @param idToObject The id to parsed object mapping
* @param variableNameCounters The variable name counters * @param variableNameCounters The variable name counters
* @param controllerClassToVariable The controller class to variable mapping
* @param controllerFactoryPostAction The controller factory post action * @param controllerFactoryPostAction The controller factory post action
* @param stringBuilder The string builder * @param stringBuilder The string builder
* @throws NullPointerException if any parameter is null * @throws NullPointerException if any parameter is null
*/ */
public GenerationProgress { public GenerationProgress {
Objects.requireNonNull(request); Objects.requireNonNull(request);
Objects.requireNonNull(idToVariableName); Objects.requireNonNull(idToVariableInfo);
Objects.requireNonNull(idToObject);
Objects.requireNonNull(variableNameCounters); Objects.requireNonNull(variableNameCounters);
Objects.requireNonNull(controllerClassToVariable);
Objects.requireNonNull(controllerFactoryPostAction); Objects.requireNonNull(controllerFactoryPostAction);
Objects.requireNonNull(stringBuilder); Objects.requireNonNull(stringBuilder);
} }
@@ -54,7 +55,7 @@ public record GenerationProgress(GenerationRequest request, Map<String, String>
* @throws NullPointerException if request is null * @throws NullPointerException if request is null
*/ */
public GenerationProgress(final GenerationRequest request) { public GenerationProgress(final GenerationRequest request) {
this(request, new HashMap<>(), new HashMap<>(), new HashMap<>(), new ArrayList<>(), new StringBuilder()); this(request, new HashMap<>(), new HashMap<>(), new LinkedHashMap<>(), new ArrayList<>(), new StringBuilder());
} }
/** /**

View File

@@ -0,0 +1,99 @@
package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.impl.ControllerFieldInjectionTypes;
import com.github.gtache.fxml.compiler.impl.ControllerMethodsInjectionType;
/**
* Formats the helper methods for the generated code
*/
public final class HelperMethodsFormatter {
private HelperMethodsFormatter() {
}
/**
* Formats the helper methods for the given generation progress
*
* @param progress The generation progress
*/
public static void formatHelperMethods(final GenerationProgress progress) {
final var parameters = progress.request().parameters();
final var methodInjectionType = parameters.methodInjectionType();
final var sb = progress.stringBuilder();
if (methodInjectionType == ControllerMethodsInjectionType.REFLECTION) {
final var toList = GenerationCompatibilityHelper.getToList(progress);
final var getFirst = GenerationCompatibilityHelper.getGetFirst(progress);
final var startVariableMethodList = GenerationCompatibilityHelper.getStartVar(progress, "java.util.List<java.lang.reflect.Method>", 0);
sb.append(" private <T extends javafx.event.Event> void callEventHandlerMethod(final String methodName, final T event) {\n");
sb.append(" try {\n");
sb.append(" final java.lang.reflect.Method method;\n");
sb.append(" ").append(startVariableMethodList).append("methods = java.util.Arrays.stream(controller.getClass().getDeclaredMethods())\n");
sb.append(" .filter(m -> m.getName().equals(methodName))").append(toList).append(";\n");
sb.append(" if (methods.size() > 1) {\n");
sb.append(" ").append(startVariableMethodList).append("eventMethods = methods.stream().filter(m ->\n");
sb.append(" m.getParameterCount() == 1 && javafx.event.Event.class.isAssignableFrom(m.getParameterTypes()[0]))").append(toList).append(";\n");
sb.append(" if (eventMethods.size() == 1) {\n");
sb.append(" method = eventMethods").append(getFirst).append(";\n");
sb.append(" } else {\n");
sb.append(" ").append(startVariableMethodList).append("emptyMethods = methods.stream().filter(m -> m.getParameterCount() == 0)").append(toList).append(";\n");
sb.append(" if (emptyMethods.size() == 1) {\n");
sb.append(" method = emptyMethods").append(getFirst).append(";\n");
sb.append(" } else {\n");
sb.append(" throw new IllegalArgumentException(\"Multiple matching methods for \" + methodName);\n");
sb.append(" }\n");
sb.append(" }\n");
sb.append(" } else if (methods.size() == 1) {\n");
sb.append(" method = methods").append(getFirst).append(";\n");
sb.append(" } else {\n");
sb.append(" throw new IllegalArgumentException(\"No matching method for \" + methodName);\n");
sb.append(" }\n");
sb.append(" method.setAccessible(true);\n");
sb.append(" if (method.getParameterCount() == 0) {\n");
sb.append(" method.invoke(controller);\n");
sb.append(" } else {\n");
sb.append(" method.invoke(controller, event);\n");
sb.append(" }\n");
sb.append(" } catch (final IllegalAccessException | java.lang.reflect.InvocationTargetException ex) {\n");
sb.append(" throw new RuntimeException(\"Error using reflection on \" + methodName, ex);\n");
sb.append(" }\n");
sb.append(" }\n");
sb.append("\n");
sb.append(" private <T, U> U callCallbackMethod(final String methodName, final T value, final Class<T> clazz) {\n");
sb.append(" try {\n");
sb.append(" final java.lang.reflect.Method method;\n");
sb.append(" ").append(startVariableMethodList).append("methods = java.util.Arrays.stream(controller.getClass().getDeclaredMethods())\n");
sb.append(" .filter(m -> m.getName().equals(methodName))").append(toList).append(";\n");
sb.append(" if (methods.size() > 1) {\n");
sb.append(" ").append(startVariableMethodList).append("eventMethods = methods.stream().filter(m ->\n");
sb.append(" m.getParameterCount() == 1 && clazz.isAssignableFrom(m.getParameterTypes()[0]))").append(toList).append(";\n");
sb.append(" if (eventMethods.size() == 1) {\n");
sb.append(" method = eventMethods").append(getFirst).append(";\n");
sb.append(" } else {\n");
sb.append(" throw new IllegalArgumentException(\"Multiple matching methods for \" + methodName);\n");
sb.append(" }\n");
sb.append(" } else if (methods.size() == 1) {\n");
sb.append(" method = methods").append(getFirst).append(";\n");
sb.append(" } else {\n");
sb.append(" throw new IllegalArgumentException(\"No matching method for \" + methodName);\n");
sb.append(" }\n");
sb.append(" method.setAccessible(true);\n");
sb.append(" return (U) method.invoke(controller, value);\n");
sb.append(" } catch (final IllegalAccessException | java.lang.reflect.InvocationTargetException ex) {\n");
sb.append(" throw new RuntimeException(\"Error using reflection on \" + methodName, ex);\n");
sb.append(" }\n");
sb.append(" }\n");
}
if (parameters.fieldInjectionType() == ControllerFieldInjectionTypes.REFLECTION) {
sb.append(" private <T> void injectField(final String fieldName, final T object) {\n");
sb.append(" try {\n");
sb.append(" ").append(GenerationCompatibilityHelper.getStartVar(progress, "java.lang.reflect.Field", 0)).append("field = controller.getClass().getDeclaredField(fieldName);\n");
sb.append(" field.setAccessible(true);\n");
sb.append(" field.set(controller, object);\n");
sb.append(" } catch (final NoSuchFieldException | IllegalAccessException e) {\n");
sb.append(" throw new RuntimeException(\"Error using reflection on \" + fieldName, e);\n");
sb.append(" }\n");
sb.append(" }\n");
}
}
}

View File

@@ -1,106 +0,0 @@
package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.impl.ControllerFieldInjectionTypes;
import com.github.gtache.fxml.compiler.impl.ControllerMethodsInjectionType;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getControllerInjection;
/**
* Provides the helper methods for the generated code
*/
public final class HelperMethodsProvider {
private HelperMethodsProvider() {
}
/**
* Gets helper methods string for the given generation progress
*
* @param progress The generation progress
* @return The helper methods
* @throws GenerationException if an error occurs
*/
public static String getHelperMethods(final GenerationProgress progress) throws GenerationException {
final var injection = getControllerInjection(progress);
final var methodInjectionType = injection.methodInjectionType();
final var sb = new StringBuilder();
if (methodInjectionType == ControllerMethodsInjectionType.REFLECTION) {
sb.append("""
private <T extends javafx.event.Event> void callEventHandlerMethod(final String methodName, final T event) {
try {
final java.lang.reflect.Method method;
final var methods = java.util.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 && javafx.event.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 | java.lang.reflect.InvocationTargetException ex) {
throw new RuntimeException("Error using reflection on " + methodName, ex);
}
}
private <T, U> U callCallbackMethod(final String methodName, final T value, final Class<T> clazz) {
try {
final java.lang.reflect.Method method;
final var methods = java.util.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 && clazz.isAssignableFrom(m.getParameterTypes()[0])).toList();
if (eventMethods.size() == 1) {
method = eventMethods.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);
return (U) method.invoke(controller, value);
} catch (final IllegalAccessException | java.lang.reflect.InvocationTargetException ex) {
throw new RuntimeException("Error using reflection on " + methodName, ex);
}
}
""");
}
if (injection.fieldInjectionType() == ControllerFieldInjectionTypes.REFLECTION) {
sb.append("""
private <T> 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();
}
}

View File

@@ -1,52 +0,0 @@
package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
import static com.github.gtache.fxml.compiler.impl.internal.URLBuilder.formatURL;
/**
* Helper methods for {@link GeneratorImpl} to format Images
*/
final class ImageBuilder {
private ImageBuilder() {
}
static void formatImage(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
if (parsedObject.children().isEmpty() && parsedObject.properties().isEmpty()) {
final var sortedAttributes = getSortedAttributes(parsedObject);
String url = null;
var requestedWidth = 0.0;
var requestedHeight = 0.0;
var preserveRatio = false;
var smooth = false;
var backgroundLoading = false;
for (final var property : sortedAttributes) {
switch (property.name()) {
case FX_ID -> {
//Do nothing
}
case "url" -> url = formatURL(progress, property.value());
case "requestedWidth" -> requestedWidth = Double.parseDouble(property.value());
case "requestedHeight" -> requestedHeight = Double.parseDouble(property.value());
case "preserveRatio" -> preserveRatio = Boolean.parseBoolean(property.value());
case "smooth" -> smooth = Boolean.parseBoolean(property.value());
case "backgroundLoading" -> backgroundLoading = Boolean.parseBoolean(property.value());
default -> throw new GenerationException("Unknown image attribute : " + property.name());
}
}
final var urlString = progress.getNextVariableName("urlStr");
progress.stringBuilder().append(START_VAR).append(urlString).append(" = ").append(url).append(".toString();\n");
progress.stringBuilder().append(START_VAR).append(variableName).append(" = new javafx.scene.image.Image(").append(urlString)
.append(", ").append(requestedWidth).append(", ").append(requestedHeight).append(", ")
.append(preserveRatio).append(", ").append(smooth).append(", ").append(backgroundLoading).append(");\n");
handleId(progress, parsedObject, variableName);
} else {
throw new GenerationException("Image cannot have children or properties : " + parsedObject);
}
}
}

View File

@@ -0,0 +1,81 @@
package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.FX_ID;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getSortedAttributes;
/**
* Helper methods for {@link GeneratorImpl} to format Images
*/
final class ImageFormatter {
private ImageFormatter() {
}
static void formatImage(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
if (parsedObject.children().isEmpty() && parsedObject.properties().isEmpty()) {
doFormatImage(progress, parsedObject, variableName);
} else {
throw new GenerationException("Image cannot have children or properties : " + parsedObject);
}
}
private static void formatInputStream(final GenerationProgress progress, final String url, final double requestedWidth,
final double requestedHeight, final boolean preserveRatio, final boolean smooth, final String variableName) {
final var inputStream = progress.getNextVariableName("inputStream");
final var sb = progress.stringBuilder();
sb.append(" final javafx.scene.image.Image ").append(variableName).append(";\n");
sb.append(" try (").append(GenerationCompatibilityHelper.getStartVar(progress, "java.io.InputStream", 0)).append(inputStream).append(" = ").append(url).append(".openStream()) {\n");
sb.append(" ").append(variableName).append(" = new javafx.scene.image.Image(").append(inputStream);
sb.append(", ").append(requestedWidth).append(", ").append(requestedHeight).append(", ").append(preserveRatio).append(", ").append(smooth).append(");\n");
sb.append(" } catch (final java.io.IOException e) {\n");
sb.append(" throw new RuntimeException(e);\n");
sb.append(" }\n");
}
private static void doFormatImage(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
final var sortedAttributes = getSortedAttributes(parsedObject);
String url = null;
var requestedWidth = 0.0;
var requestedHeight = 0.0;
var preserveRatio = false;
var smooth = false;
var backgroundLoading = false;
for (final var property : sortedAttributes) {
switch (property.name()) {
case FX_ID -> {
//Do nothing
}
case "url" -> url = URLFormatter.formatURL(progress, property.value());
case "requestedWidth" -> requestedWidth = Double.parseDouble(property.value());
case "requestedHeight" -> requestedHeight = Double.parseDouble(property.value());
case "preserveRatio" -> preserveRatio = Boolean.parseBoolean(property.value());
case "smooth" -> smooth = Boolean.parseBoolean(property.value());
case "backgroundLoading" -> backgroundLoading = Boolean.parseBoolean(property.value());
default -> throw new GenerationException("Unknown image attribute : " + property.name());
}
}
if (progress.request().parameters().useImageInputStreamConstructor()) {
formatInputStream(progress, url, requestedWidth, requestedHeight, preserveRatio, smooth, variableName);
} else {
formatURL(progress, url, requestedWidth, requestedHeight, preserveRatio, smooth, backgroundLoading, variableName);
}
GenerationHelper.handleId(progress, parsedObject, variableName);
}
private static void formatURL(final GenerationProgress progress, final String url, final double requestedWidth,
final double requestedHeight, final boolean preserveRatio, final boolean smooth,
final boolean backgroundLoading, final String variableName) {
final var urlString = progress.getNextVariableName("urlStr");
final var sb = progress.stringBuilder();
sb.append(GenerationCompatibilityHelper.getStartVar(progress, "String")).append(urlString).append(" = ").append(url).append(".toString();\n");
sb.append(GenerationCompatibilityHelper.getStartVar(progress, "javafx.scene.image.Image")).append(variableName).append(" = new javafx.scene.image.Image(").append(urlString)
.append(", ").append(requestedWidth).append(", ").append(requestedHeight).append(", ")
.append(preserveRatio).append(", ").append(smooth).append(", ").append(backgroundLoading).append(");\n");
}
}

View File

@@ -0,0 +1,70 @@
package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.impl.ControllerFieldInjectionTypes;
import com.github.gtache.fxml.compiler.impl.ControllerMethodsInjectionType;
import com.github.gtache.fxml.compiler.impl.ResourceBundleInjectionTypes;
/**
* Formats the load method for the generated code
*/
public final class LoadMethodFormatter {
private LoadMethodFormatter() {
}
/**
* Formats the load method
*
* @param progress The generation progress
* @throws GenerationException if an error occurs
*/
public static void formatLoadMethod(final GenerationProgress progress) throws GenerationException {
final var request = progress.request();
final var rootObject = request.rootObject();
final var parameters = progress.request().parameters();
final var controllerInjectionType = parameters.fieldInjectionType();
final var controllerClass = progress.request().controllerInfo().className();
final var sb = progress.stringBuilder();
sb.append(" /**\n");
sb.append(" * Loads the view. Can only be called once.\n");
sb.append(" *\n");
sb.append(" * @return The view parent\n");
sb.append(" */\n");
sb.append(" public <T> T load() {\n");
sb.append(" if (loaded) {\n");
sb.append(" throw new IllegalStateException(\"Already loaded\");\n");
sb.append(" }\n");
final var resourceBundleInjection = parameters.resourceInjectionType();
if (resourceBundleInjection == ResourceBundleInjectionTypes.CONSTRUCTOR_NAME) {
sb.append(GenerationCompatibilityHelper.getStartVar(progress, "java.util.ResourceBundle")).append("resourceBundle = java.util.ResourceBundle.getBundle(\"resourceBundleName\");\n");
} else if (resourceBundleInjection == ResourceBundleInjectionTypes.GET_BUNDLE && parameters.bundleMap().containsKey(controllerClass)) {
sb.append(GenerationCompatibilityHelper.getStartVar(progress, "java.util.ResourceBundle")).append("resourceBundle = java.util.ResourceBundle.getBundle(\"").append(parameters.bundleMap().get(controllerClass)).append("\");\n");
}
if (controllerInjectionType == ControllerFieldInjectionTypes.FACTORY) {
sb.append(GenerationCompatibilityHelper.getStartVar(progress, "java.util.Map<String, Object>")).append("fieldMap = new java.util.HashMap<String, Object>();\n");
}
final var variableName = progress.getNextVariableName(GenerationHelper.getVariablePrefix(rootObject));
ObjectFormatter.format(progress, rootObject, variableName);
if (controllerInjectionType == ControllerFieldInjectionTypes.FACTORY) {
sb.append(" controller = (").append(controllerClass).append(") controllerFactory.create(fieldMap);\n");
progress.controllerFactoryPostAction().forEach(sb::append);
}
if (parameters.methodInjectionType() == ControllerMethodsInjectionType.REFLECTION) {
sb.append(" try {\n");
sb.append(" ").append(GenerationCompatibilityHelper.getStartVar(progress, "java.lang.reflect.Method", 0)).append("initialize = controller.getClass().getDeclaredMethod(\"initialize\");\n");
sb.append(" initialize.setAccessible(true);\n");
sb.append(" initialize.invoke(controller);\n");
sb.append(" } catch (final java.lang.reflect.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 (T) ").append(variableName).append(";\n");
sb.append(" }\n");
}
}

View File

@@ -15,18 +15,7 @@ import java.util.SequencedCollection;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.github.gtache.fxml.compiler.impl.internal.ConstructorHelper.getListConstructorArgs;
import static com.github.gtache.fxml.compiler.impl.internal.ConstructorHelper.getMatchingConstructorArgs;
import static com.github.gtache.fxml.compiler.impl.internal.ControllerInjector.injectControllerField;
import static com.github.gtache.fxml.compiler.impl.internal.FontFormatter.formatFont;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*; import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
import static com.github.gtache.fxml.compiler.impl.internal.ImageBuilder.formatImage;
import static com.github.gtache.fxml.compiler.impl.internal.PropertyFormatter.formatProperty;
import static com.github.gtache.fxml.compiler.impl.internal.ReflectionHelper.*;
import static com.github.gtache.fxml.compiler.impl.internal.SceneBuilder.formatScene;
import static com.github.gtache.fxml.compiler.impl.internal.TriangleMeshBuilder.formatTriangleMesh;
import static com.github.gtache.fxml.compiler.impl.internal.URLBuilder.formatURL;
import static com.github.gtache.fxml.compiler.impl.internal.WebViewBuilder.formatWebView;
/** /**
* Helper methods for {@link GeneratorImpl} to format properties * Helper methods for {@link GeneratorImpl} to format properties
@@ -34,7 +23,7 @@ import static com.github.gtache.fxml.compiler.impl.internal.WebViewBuilder.forma
public final class ObjectFormatter { public final class ObjectFormatter {
private static final Logger logger = LogManager.getLogger(ObjectFormatter.class); private static final Logger logger = LogManager.getLogger(ObjectFormatter.class);
private static final String NEW_ASSIGN = " = new "; private static final String NEW_ASSIGN = " = new ";
private static final Set<String> BUILDER_CLASSES = Set.of( private static final Set<String> BUILDER_CLASSES = Set.of(
@@ -57,7 +46,6 @@ public final class ObjectFormatter {
); );
private ObjectFormatter() { private ObjectFormatter() {
} }
/** /**
@@ -72,7 +60,7 @@ public final class ObjectFormatter {
switch (parsedObject) { switch (parsedObject) {
case final ParsedConstant constant -> formatConstant(progress, constant, variableName); case final ParsedConstant constant -> formatConstant(progress, constant, variableName);
case final ParsedCopy copy -> formatCopy(progress, copy, variableName); case final ParsedCopy copy -> formatCopy(progress, copy, variableName);
case final ParsedDefine define -> formatDefine(progress, define, variableName); case final ParsedDefine define -> formatDefine(progress, define);
case final ParsedFactory factory -> formatFactory(progress, factory, variableName); case final ParsedFactory factory -> formatFactory(progress, factory, variableName);
case final ParsedInclude include -> formatInclude(progress, include, variableName); case final ParsedInclude include -> formatInclude(progress, include, variableName);
case final ParsedReference reference -> formatReference(progress, reference, variableName); case final ParsedReference reference -> formatReference(progress, reference, variableName);
@@ -90,7 +78,7 @@ public final class ObjectFormatter {
* @param variableName The variable name * @param variableName The variable name
*/ */
private static void formatText(final GenerationProgress progress, final ParsedText text, final String variableName) { private static void formatText(final GenerationProgress progress, final ParsedText text, final String variableName) {
progress.stringBuilder().append(START_VAR).append(variableName).append(" = \"").append(text.text()).append("\";\n"); progress.stringBuilder().append(GenerationCompatibilityHelper.getStartVar(progress, "String")).append(variableName).append(" = \"").append(text.text()).append("\";\n");
} }
/** /**
@@ -118,12 +106,13 @@ public final class ObjectFormatter {
private static void formatBuilderObject(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException { private static void formatBuilderObject(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
final var className = parsedObject.className(); final var className = parsedObject.className();
switch (className) { switch (className) {
case "javafx.scene.Scene" -> formatScene(progress, parsedObject, variableName); case "javafx.scene.Scene" -> SceneFormatter.formatScene(progress, parsedObject, variableName);
case "javafx.scene.text.Font" -> formatFont(progress, parsedObject, variableName); case "javafx.scene.text.Font" -> FontFormatter.formatFont(progress, parsedObject, variableName);
case "javafx.scene.image.Image" -> formatImage(progress, parsedObject, variableName); case "javafx.scene.image.Image" -> ImageFormatter.formatImage(progress, parsedObject, variableName);
case "java.net.URL" -> formatURL(progress, parsedObject, variableName); case "java.net.URL" -> URLFormatter.formatURL(progress, parsedObject, variableName);
case "javafx.scene.shape.TriangleMesh" -> formatTriangleMesh(progress, parsedObject, variableName); case "javafx.scene.shape.TriangleMesh" ->
case "javafx.scene.web.WebView" -> formatWebView(progress, parsedObject, variableName); TriangleMeshFormatter.formatTriangleMesh(progress, parsedObject, variableName);
case "javafx.scene.web.WebView" -> WebViewFormatter.formatWebView(progress, parsedObject, variableName);
default -> throw new IllegalArgumentException("Unknown builder class : " + className); default -> throw new IllegalArgumentException("Unknown builder class : " + className);
} }
} }
@@ -145,8 +134,8 @@ public final class ObjectFormatter {
} }
final var value = getSimpleValue(progress, parsedObject); final var value = getSimpleValue(progress, parsedObject);
final var valueStr = ValueFormatter.toString(value, ReflectionHelper.getClass(parsedObject.className())); final var valueStr = ValueFormatter.toString(value, ReflectionHelper.getClass(parsedObject.className()));
progress.stringBuilder().append(START_VAR).append(variableName).append(" = ").append(valueStr).append(";\n"); progress.stringBuilder().append(GenerationCompatibilityHelper.getStartVar(progress, parsedObject)).append(variableName).append(" = ").append(valueStr).append(";\n");
handleId(progress, parsedObject, variableName); GenerationHelper.handleId(progress, parsedObject, variableName);
} }
private static String getSimpleValue(final GenerationProgress progress, final ParsedObject parsedObject) throws GenerationException { private static String getSimpleValue(final GenerationProgress progress, final ParsedObject parsedObject) throws GenerationException {
@@ -217,12 +206,12 @@ public final class ObjectFormatter {
} }
} }
if (!notDefinedChildren.isEmpty()) { if (!notDefinedChildren.isEmpty()) {
final var defaultProperty = getDefaultProperty(parsedObject.className()); final var defaultProperty = ReflectionHelper.getDefaultProperty(parsedObject.className());
if (defaultProperty != null) { if (defaultProperty != null) {
allPropertyNames.add(defaultProperty); allPropertyNames.add(defaultProperty);
} }
} }
final var constructorArgs = getMatchingConstructorArgs(constructors, allPropertyNames); final var constructorArgs = ConstructorHelper.getMatchingConstructorArgs(constructors, allPropertyNames);
if (constructorArgs == null) { if (constructorArgs == null) {
formatNoConstructor(progress, parsedObject, variableName, allPropertyNames); formatNoConstructor(progress, parsedObject, variableName, allPropertyNames);
} else { } else {
@@ -234,21 +223,21 @@ public final class ObjectFormatter {
final var clazz = ReflectionHelper.getClass(parsedObject.className()); final var clazz = ReflectionHelper.getClass(parsedObject.className());
if (allPropertyNames.size() == 1 && allPropertyNames.iterator().next().equals("fx:constant")) { if (allPropertyNames.size() == 1 && allPropertyNames.iterator().next().equals("fx:constant")) {
final var property = parsedObject.attributes().get("fx:constant"); final var property = parsedObject.attributes().get("fx:constant");
progress.stringBuilder().append(START_VAR).append(variableName).append(" = ").append(clazz.getCanonicalName()).append(".").append(property.value()).append(";\n"); progress.stringBuilder().append(GenerationCompatibilityHelper.getStartVar(progress, parsedObject)).append(variableName).append(" = ").append(clazz.getCanonicalName()).append(".").append(property.value()).append(";\n");
} else { } else {
throw new GenerationException("Cannot find constructor for " + clazz.getCanonicalName()); throw new GenerationException("Cannot find constructor for " + clazz.getCanonicalName());
} }
} }
private static void formatConstructor(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName, final ConstructorArgs constructorArgs) throws GenerationException { private static void formatConstructor(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName, final ConstructorArgs constructorArgs) throws GenerationException {
final var args = getListConstructorArgs(constructorArgs, parsedObject); final var args = ConstructorHelper.getListConstructorArgs(constructorArgs, parsedObject);
final var genericTypes = getGenericTypes(progress, parsedObject); final var genericTypes = ReflectionHelper.getGenericTypes(progress, parsedObject);
progress.stringBuilder().append(START_VAR).append(variableName).append(NEW_ASSIGN).append(parsedObject.className()) progress.stringBuilder().append(GenerationCompatibilityHelper.getStartVar(progress, parsedObject)).append(variableName).append(NEW_ASSIGN).append(parsedObject.className())
.append(genericTypes).append("(").append(String.join(", ", args)).append(");\n"); .append(genericTypes).append("(").append(String.join(", ", args)).append(");\n");
final var sortedAttributes = getSortedAttributes(parsedObject); final var sortedAttributes = getSortedAttributes(parsedObject);
for (final var value : sortedAttributes) { for (final var value : sortedAttributes) {
if (!constructorArgs.namedArgs().containsKey(value.name())) { if (!constructorArgs.namedArgs().containsKey(value.name())) {
formatProperty(progress, value, parsedObject, variableName); PropertyFormatter.formatProperty(progress, value, parsedObject, variableName);
} }
} }
final var sortedProperties = parsedObject.properties().entrySet().stream().sorted(Comparator.comparing(e -> e.getKey().name())).toList(); final var sortedProperties = parsedObject.properties().entrySet().stream().sorted(Comparator.comparing(e -> e.getKey().name())).toList();
@@ -261,7 +250,7 @@ public final class ObjectFormatter {
} }
final var notDefinedChildren = parsedObject.children().stream().filter(c -> !(c instanceof ParsedDefine)).toList(); final var notDefinedChildren = parsedObject.children().stream().filter(c -> !(c instanceof ParsedDefine)).toList();
if (!notDefinedChildren.isEmpty()) { if (!notDefinedChildren.isEmpty()) {
final var defaultProperty = getDefaultProperty(parsedObject.className()); final var defaultProperty = ReflectionHelper.getDefaultProperty(parsedObject.className());
if (!constructorArgs.namedArgs().containsKey(defaultProperty)) { if (!constructorArgs.namedArgs().containsKey(defaultProperty)) {
final var property = new ParsedPropertyImpl(defaultProperty, null, null); final var property = new ParsedPropertyImpl(defaultProperty, null, null);
formatChild(progress, parsedObject, property, notDefinedChildren, variableName); formatChild(progress, parsedObject, property, notDefinedChildren, variableName);
@@ -278,26 +267,8 @@ public final class ObjectFormatter {
*/ */
private static void formatInclude(final GenerationProgress progress, final ParsedInclude include, final String subNodeName) throws GenerationException { private static void formatInclude(final GenerationProgress progress, final ParsedInclude include, final String subNodeName) throws GenerationException {
final var subViewVariable = progress.getNextVariableName("view"); final var subViewVariable = progress.getNextVariableName("view");
final var source = include.source(); final var viewVariable = ConstructorFormatter.formatSubViewConstructorCall(progress, include);
final var resources = include.resources(); progress.stringBuilder().append(" final javafx.scene.Parent ").append(subNodeName).append(" = ").append(viewVariable).append(".load();\n");
final var request = progress.request();
final var subControllerClass = request.parameters().sourceToControllerName().get(source);
final var subClassName = request.parameters().sourceToGeneratedClassName().get(source);
if (subClassName == null) {
throw new GenerationException("Unknown include source : " + source);
}
final var sb = progress.stringBuilder();
if (resources == null) {
sb.append(START_VAR).append(subViewVariable).append(NEW_ASSIGN).append(subClassName).append("(controllersMap, resourceBundlesMap);\n");
} else {
final var subResourceBundlesMapVariable = progress.getNextVariableName("map");
final var subBundleVariable = progress.getNextVariableName("bundle");
sb.append(START_VAR).append(subResourceBundlesMapVariable).append(" = new HashMap<>(resourceBundlesMap);\n");
sb.append(START_VAR).append(subBundleVariable).append(" = java.util.ResourceBundle.getBundle(\"").append(resources).append("\");\n");
sb.append(" ").append(subResourceBundlesMapVariable).append(".put(").append(subControllerClass).append(", ").append(subBundleVariable).append(");\n");
sb.append(START_VAR).append(subViewVariable).append(NEW_ASSIGN).append(subClassName).append("(controllersMap, ").append(subResourceBundlesMapVariable).append(");\n");
}
sb.append(" final javafx.scene.Parent ").append(subNodeName).append(" = ").append(subViewVariable).append(".load();\n");
injectSubController(progress, include, subViewVariable); injectSubController(progress, include, subViewVariable);
} }
@@ -305,13 +276,13 @@ public final class ObjectFormatter {
final var id = include.controllerId(); final var id = include.controllerId();
if (id != null) { if (id != null) {
final var subControllerVariable = progress.getNextVariableName("controller"); final var subControllerVariable = progress.getNextVariableName("controller");
progress.stringBuilder().append(START_VAR).append(subControllerVariable).append(" = ").append(subViewVariable).append(".controller();\n"); final var controllerClass = progress.request().sourceInfo().sourceToSourceInfo().get(include.source()).controllerClassName();
progress.idToVariableName().put(id, subControllerVariable); progress.stringBuilder().append(GenerationCompatibilityHelper.getStartVar(progress, controllerClass)).append(subControllerVariable).append(" = ").append(subViewVariable).append(".controller();\n");
progress.idToObject().put(id, include); progress.idToVariableInfo().put(id, new VariableInfo(id, include, subControllerVariable, controllerClass));
if (progress.request().controllerInfo().fieldInfo(id) == null) { if (progress.request().controllerInfo().fieldInfo(id) == null) {
logger.debug("Not injecting {} because it is not found in controller", id); logger.debug("Not injecting {} because it is not found in controller", id);
} else { } else {
injectControllerField(progress, id, subControllerVariable); ControllerInjector.injectControllerField(progress, id, subControllerVariable);
} }
} }
} }
@@ -319,13 +290,14 @@ public final class ObjectFormatter {
/** /**
* Formats a fx:define * Formats a fx:define
* *
* @param progress The generation progress * @param progress The generation progress
* @param define The parsed define * @param define The parsed define
* @param variableName The variable name
* @throws GenerationException if an error occurs * @throws GenerationException if an error occurs
*/ */
private static void formatDefine(final GenerationProgress progress, final ParsedDefine define, final String variableName) throws GenerationException { private static void formatDefine(final GenerationProgress progress, final ParsedObject define) throws GenerationException {
formatObject(progress, define.object(), variableName); for (final var child : define.children()) {
format(progress, child, progress.getNextVariableName("definedObject"));
}
} }
/** /**
@@ -337,11 +309,12 @@ public final class ObjectFormatter {
*/ */
private static void formatReference(final GenerationProgress progress, final ParsedReference reference, final String variableName) throws GenerationException { private static void formatReference(final GenerationProgress progress, final ParsedReference reference, final String variableName) throws GenerationException {
final var id = reference.source(); final var id = reference.source();
final var variable = progress.idToVariableName().get(id); final var variableInfo = progress.idToVariableInfo().get(id);
if (variable == null) { if (variableInfo == null) {
throw new GenerationException("Unknown id : " + id); throw new GenerationException("Unknown id : " + id);
} }
progress.stringBuilder().append(START_VAR).append(variableName).append(" = ").append(variable).append(";\n"); final var referenceName = variableInfo.variableName();
progress.stringBuilder().append(GenerationCompatibilityHelper.getStartVar(progress, variableInfo.className())).append(variableName).append(" = ").append(referenceName).append(";\n");
} }
/** /**
@@ -354,12 +327,12 @@ public final class ObjectFormatter {
*/ */
private static void formatCopy(final GenerationProgress progress, final ParsedCopy copy, final String variableName) throws GenerationException { private static void formatCopy(final GenerationProgress progress, final ParsedCopy copy, final String variableName) throws GenerationException {
final var id = copy.source(); final var id = copy.source();
final var variable = progress.idToVariableName().get(id); final var variableInfo = progress.idToVariableInfo().get(id);
final var object = progress.idToObject().get(id); if (variableInfo == null) {
if (variable == null || object == null) {
throw new GenerationException("Unknown id : " + id); throw new GenerationException("Unknown id : " + id);
} }
progress.stringBuilder().append(START_VAR).append(variableName).append(NEW_ASSIGN).append(object.className()).append("(").append(variable).append(");\n"); final var copyVariable = variableInfo.variableName();
progress.stringBuilder().append(GenerationCompatibilityHelper.getStartVar(progress, variableInfo.className())).append(variableName).append(NEW_ASSIGN).append(variableInfo.className()).append("(").append(copyVariable).append(");\n");
} }
/** /**
@@ -370,7 +343,7 @@ public final class ObjectFormatter {
* @param variableName The variable name * @param variableName The variable name
*/ */
private static void formatConstant(final GenerationProgress progress, final ParsedConstant constant, final String variableName) { private static void formatConstant(final GenerationProgress progress, final ParsedConstant constant, final String variableName) {
progress.stringBuilder().append(START_VAR).append(variableName).append(" = ").append(constant.className()).append(".").append(constant.constant()).append(";\n"); progress.stringBuilder().append(GenerationCompatibilityHelper.getStartVar(progress, constant.className())).append(variableName).append(" = ").append(constant.className()).append(".").append(constant.constant()).append(";\n");
} }
/** /**
@@ -381,7 +354,7 @@ public final class ObjectFormatter {
* @param variableName The variable name * @param variableName The variable name
*/ */
private static void formatValue(final GenerationProgress progress, final ParsedValue value, final String variableName) { private static void formatValue(final GenerationProgress progress, final ParsedValue value, final String variableName) {
progress.stringBuilder().append(START_VAR).append(variableName).append(" = ").append(value.className()).append(".valueOf(\"").append(value.value()).append("\");\n"); progress.stringBuilder().append(GenerationCompatibilityHelper.getStartVar(progress, value.className())).append(variableName).append(" = ").append(value.className()).append(".valueOf(\"").append(value.value()).append("\");\n");
} }
/** /**
@@ -398,8 +371,14 @@ public final class ObjectFormatter {
variables.add(argumentVariable); variables.add(argumentVariable);
format(progress, argument, argumentVariable); format(progress, argument, argumentVariable);
} }
progress.stringBuilder().append(START_VAR).append(variableName).append(" = ").append(factory.className()) if (progress.request().parameters().compatibility().useVar()) {
.append(".").append(factory.factory()).append("(").append(String.join(", ", variables)).append(");\n"); progress.stringBuilder().append(GenerationCompatibilityHelper.getStartVar(progress, factory.className())).append(variableName).append(" = ").append(factory.className())
.append(".").append(factory.factory()).append("(").append(String.join(", ", variables)).append(");\n");
} else {
final var returnType = ReflectionHelper.getReturnType(factory.className(), factory.factory());
progress.stringBuilder().append(GenerationCompatibilityHelper.getStartVar(progress, returnType)).append(variableName).append(" = ").append(factory.className())
.append(".").append(factory.factory()).append("(").append(String.join(", ", variables)).append(");\n");
}
} }
/** /**
@@ -416,7 +395,7 @@ public final class ObjectFormatter {
final var propertyName = property.name(); final var propertyName = property.name();
final var variables = new ArrayList<String>(); final var variables = new ArrayList<String>();
for (final var object : objects) { for (final var object : objects) {
final var vn = progress.getNextVariableName(getVariablePrefix(object)); final var vn = progress.getNextVariableName(GenerationHelper.getVariablePrefix(object));
format(progress, object, vn); format(progress, object, vn);
if (!(object instanceof ParsedDefine)) { if (!(object instanceof ParsedDefine)) {
variables.add(vn); variables.add(vn);
@@ -441,9 +420,9 @@ public final class ObjectFormatter {
*/ */
private static void formatMultipleChildren(final GenerationProgress progress, final Iterable<String> variables, final String propertyName, final ParsedObject parent, private static void formatMultipleChildren(final GenerationProgress progress, final Iterable<String> variables, final String propertyName, final ParsedObject parent,
final String parentVariable) throws GenerationException { final String parentVariable) throws GenerationException {
final var getMethod = getGetMethod(propertyName); final var getMethod = GenerationHelper.getGetMethod(propertyName);
if (hasMethod(ReflectionHelper.getClass(parent.className()), getMethod)) { if (ReflectionHelper.hasMethod(ReflectionHelper.getClass(parent.className()), getMethod)) {
progress.stringBuilder().append(" ").append(parentVariable).append(".").append(getMethod).append("().addAll(java.util.List.of(").append(String.join(", ", variables)).append("));\n"); progress.stringBuilder().append(" ").append(parentVariable).append(".").append(getMethod).append("().addAll(").append(GenerationCompatibilityHelper.getListOf(progress)).append(String.join(", ", variables)).append("));\n");
} else { } else {
throw getCannotSetException(propertyName, parent.className()); throw getCannotSetException(propertyName, parent.className());
} }
@@ -479,15 +458,15 @@ public final class ObjectFormatter {
private static void formatSingleChildInstance(final GenerationProgress progress, final String variableName, private static void formatSingleChildInstance(final GenerationProgress progress, final String variableName,
final ParsedProperty property, final ParsedObject parent, final ParsedProperty property, final ParsedObject parent,
final String parentVariable) throws GenerationException { final String parentVariable) throws GenerationException {
final var setMethod = getSetMethod(property); final var setMethod = GenerationHelper.getSetMethod(property);
final var getMethod = getGetMethod(property); final var getMethod = GenerationHelper.getGetMethod(property);
final var parentClass = ReflectionHelper.getClass(parent.className()); final var parentClass = ReflectionHelper.getClass(parent.className());
final var sb = progress.stringBuilder(); final var sb = progress.stringBuilder();
if (hasMethod(parentClass, setMethod)) { if (ReflectionHelper.hasMethod(parentClass, setMethod)) {
sb.append(" ").append(parentVariable).append(".").append(setMethod).append("(").append(variableName).append(");\n"); sb.append(" ").append(parentVariable).append(".").append(setMethod).append("(").append(variableName).append(");\n");
} else if (hasMethod(parentClass, getMethod)) { } else if (ReflectionHelper.hasMethod(parentClass, getMethod)) {
//Probably a list method that has only one element //Probably a list method that has only one element
sb.append(" ").append(parentVariable).append(".").append(getMethod).append("().addAll(java.util.List.of(").append(variableName).append("));\n"); sb.append(" ").append(parentVariable).append(".").append(getMethod).append("().addAll(").append(GenerationCompatibilityHelper.getListOf(progress)).append(variableName).append("));\n");
} else { } else {
throw getCannotSetException(property.name(), parent.className()); throw getCannotSetException(property.name(), parent.className());
} }
@@ -503,9 +482,9 @@ public final class ObjectFormatter {
*/ */
private static void formatSingleChildStatic(final GenerationProgress progress, final String variableName, private static void formatSingleChildStatic(final GenerationProgress progress, final String variableName,
final ParsedProperty property, final String parentVariable) throws GenerationException { final ParsedProperty property, final String parentVariable) throws GenerationException {
final var setMethod = getSetMethod(property); final var setMethod = GenerationHelper.getSetMethod(property);
if (hasStaticMethod(ReflectionHelper.getClass(property.sourceType()), setMethod)) { if (ReflectionHelper.hasStaticMethod(ReflectionHelper.getClass(property.sourceType()), setMethod)) {
progress.stringBuilder().append(" ").append(property.sourceType()).append(".").append(setMethod) progress.stringBuilder().append(" ").append(property.sourceType()).append(".").append(setMethod)
.append("(").append(parentVariable).append(", ").append(variableName).append(");\n"); .append("(").append(parentVariable).append(", ").append(variableName).append(");\n");
} else { } else {
throw getCannotSetException(property.name(), property.sourceType()); throw getCannotSetException(property.name(), property.sourceType());

View File

@@ -19,7 +19,7 @@ record Parameter(String name, Class<?> type, String defaultValue) {
* @param defaultValue The parameter default value * @param defaultValue The parameter default value
* @throws NullPointerException if any parameter is null * @throws NullPointerException if any parameter is null
*/ */
public Parameter { Parameter {
requireNonNull(name); requireNonNull(name);
requireNonNull(type); requireNonNull(type);
requireNonNull(defaultValue); requireNonNull(defaultValue);

View File

@@ -10,18 +10,15 @@ import javafx.event.EventHandler;
import java.util.Objects; import java.util.Objects;
import static com.github.gtache.fxml.compiler.impl.internal.ControllerInjector.injectEventHandlerControllerMethod; import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.FX_ID;
import static com.github.gtache.fxml.compiler.impl.internal.FieldSetter.setEventHandler; import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.RESOURCE_KEY_PREFIX;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
import static com.github.gtache.fxml.compiler.impl.internal.ReflectionHelper.*;
import static com.github.gtache.fxml.compiler.impl.internal.ValueFormatter.getArg;
/** /**
* Helper methods for {@link GeneratorImpl} to format properties * Helper methods for {@link GeneratorImpl} to format properties
*/ */
final class PropertyFormatter { final class PropertyFormatter {
private PropertyFormatter() {
private PropertyFormatter() {
} }
/** /**
@@ -36,7 +33,7 @@ final class PropertyFormatter {
static void formatProperty(final GenerationProgress progress, final ParsedProperty property, final ParsedObject parent, final String parentVariable) throws GenerationException { static void formatProperty(final GenerationProgress progress, final ParsedProperty property, final ParsedObject parent, final String parentVariable) throws GenerationException {
final var propertyName = property.name(); final var propertyName = property.name();
if (propertyName.equals(FX_ID)) { if (propertyName.equals(FX_ID)) {
handleId(progress, parent, parentVariable); GenerationHelper.handleId(progress, parent, parentVariable);
} else if (propertyName.equals("fx:controller")) { } else if (propertyName.equals("fx:controller")) {
checkDuplicateController(progress, parent); checkDuplicateController(progress, parent);
} else if (Objects.equals(property.sourceType(), EventHandler.class.getName())) { } else if (Objects.equals(property.sourceType(), EventHandler.class.getName())) {
@@ -56,20 +53,20 @@ final class PropertyFormatter {
private static void handleEventHandler(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) throws GenerationException { private static void handleEventHandler(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) throws GenerationException {
if (property.value().startsWith("#")) { if (property.value().startsWith("#")) {
injectEventHandlerControllerMethod(progress, property, parentVariable); ControllerInjector.injectEventHandlerControllerMethod(progress, property, parentVariable);
} else { } else {
setEventHandler(progress, property, parentVariable); FieldSetter.setEventHandler(progress, property, parentVariable);
} }
} }
private static void handleStaticProperty(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String propertyName) throws GenerationException { private static void handleStaticProperty(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String propertyName) throws GenerationException {
final var setMethod = getSetMethod(propertyName); final var setMethod = GenerationHelper.getSetMethod(propertyName);
final var propertySourceTypeClass = ReflectionHelper.getClass(property.sourceType()); final var propertySourceTypeClass = ReflectionHelper.getClass(property.sourceType());
if (hasStaticMethod(propertySourceTypeClass, setMethod)) { if (ReflectionHelper.hasStaticMethod(propertySourceTypeClass, setMethod)) {
final var method = getStaticMethod(propertySourceTypeClass, setMethod); final var method = ReflectionHelper.getStaticMethod(propertySourceTypeClass, setMethod);
final var parameterType = method.getParameterTypes()[1]; final var parameterType = method.getParameterTypes()[1];
final var arg = getArg(progress, property.value(), parameterType); final var arg = ValueFormatter.getArg(progress, property.value(), parameterType);
setLaterIfNeeded(progress, property, parameterType, " " + property.sourceType() + "." + setMethod + "(" + parentVariable + ", " + arg + ");\n"); setLaterIfNeeded(progress, property, parameterType, " " + property.sourceType() + "." + setMethod + "(" + parentVariable + ", " + arg + ");\n");
} else { } else {
throw new GenerationException("Cannot set " + propertyName + " on " + property.sourceType()); throw new GenerationException("Cannot set " + propertyName + " on " + property.sourceType());
} }
@@ -77,12 +74,12 @@ final class PropertyFormatter {
private static void handleProperty(final GenerationProgress progress, final ParsedProperty property, final ParsedObject parent, final String parentVariable) throws GenerationException { private static void handleProperty(final GenerationProgress progress, final ParsedProperty property, final ParsedObject parent, final String parentVariable) throws GenerationException {
final var propertyName = property.name(); final var propertyName = property.name();
final var setMethod = getSetMethod(propertyName); final var setMethod = GenerationHelper.getSetMethod(propertyName);
final var getMethod = getGetMethod(propertyName); final var getMethod = GenerationHelper.getGetMethod(propertyName);
final var parentClass = ReflectionHelper.getClass(parent.className()); final var parentClass = ReflectionHelper.getClass(parent.className());
if (hasMethod(parentClass, setMethod)) { if (ReflectionHelper.hasMethod(parentClass, setMethod)) {
handleSetProperty(progress, property, parentClass, parentVariable); handleSetProperty(progress, property, parentClass, parentVariable);
} else if (hasMethod(parentClass, getMethod)) { } else if (ReflectionHelper.hasMethod(parentClass, getMethod)) {
handleGetProperty(progress, property, parentClass, parentVariable); handleGetProperty(progress, property, parentClass, parentVariable);
} else { } else {
throw new GenerationException("Cannot set " + propertyName + " on " + parent.className()); throw new GenerationException("Cannot set " + propertyName + " on " + parent.className());
@@ -90,20 +87,20 @@ final class PropertyFormatter {
} }
private static void handleSetProperty(final GenerationProgress progress, final ParsedProperty property, final Class<?> parentClass, final String parentVariable) throws GenerationException { private static void handleSetProperty(final GenerationProgress progress, final ParsedProperty property, final Class<?> parentClass, final String parentVariable) throws GenerationException {
final var setMethod = getSetMethod(property.name()); final var setMethod = GenerationHelper.getSetMethod(property.name());
final var method = getMethod(parentClass, setMethod); final var method = ReflectionHelper.getMethod(parentClass, setMethod);
final var parameterType = method.getParameterTypes()[0]; final var parameterType = method.getParameterTypes()[0];
final var arg = getArg(progress, property.value(), parameterType); final var arg = ValueFormatter.getArg(progress, property.value(), parameterType);
setLaterIfNeeded(progress, property, parameterType, " " + parentVariable + "." + setMethod + "(" + arg + ");\n"); setLaterIfNeeded(progress, property, parameterType, " " + parentVariable + "." + setMethod + "(" + arg + ");\n");
} }
private static void handleGetProperty(final GenerationProgress progress, final ParsedProperty property, final Class<?> parentClass, final String parentVariable) throws GenerationException { private static void handleGetProperty(final GenerationProgress progress, final ParsedProperty property, final Class<?> parentClass, final String parentVariable) throws GenerationException {
final var getMethod = getGetMethod(property.name()); final var getMethod = GenerationHelper.getGetMethod(property.name());
final var method = getMethod(parentClass, getMethod); final var method = ReflectionHelper.getMethod(parentClass, getMethod);
final var returnType = method.getReturnType(); final var returnType = method.getReturnType();
if (hasMethod(returnType, "addAll")) { if (ReflectionHelper.hasMethod(returnType, "addAll")) {
final var arg = getArg(progress, property.value(), String.class); final var arg = ValueFormatter.getArg(progress, property.value(), String.class);
setLaterIfNeeded(progress, property, String.class, " " + parentVariable + "." + getMethod + "().addAll(java.util.List.of(" + arg + "));\n"); setLaterIfNeeded(progress, property, String.class, " " + parentVariable + "." + getMethod + "().addAll(" + GenerationCompatibilityHelper.getListOf(progress) + arg + "));\n");
} }
} }
@@ -114,11 +111,11 @@ final class PropertyFormatter {
* @param property The property * @param property The property
* @param type The type * @param type The type
* @param arg The argument * @param arg The argument
* @throws GenerationException if an error occurs
*/ */
private static void setLaterIfNeeded(final GenerationProgress progress, final ParsedProperty property, final Class<?> type, final String arg) throws GenerationException { private static void setLaterIfNeeded(final GenerationProgress progress, final ParsedProperty property, final Class<?> type, final String arg) {
if (type == String.class && property.value().startsWith("%") && progress.request().parameters().resourceBundleInjection().injectionType() == ResourceBundleInjectionTypes.GETTER final var parameters = progress.request().parameters();
&& getControllerInjection(progress).fieldInjectionType() == ControllerFieldInjectionTypes.FACTORY) { if (type == String.class && property.value().startsWith(RESOURCE_KEY_PREFIX) && parameters.resourceInjectionType() == ResourceBundleInjectionTypes.GETTER
&& parameters.fieldInjectionType() == ControllerFieldInjectionTypes.FACTORY) {
progress.controllerFactoryPostAction().add(arg); progress.controllerFactoryPostAction().add(arg);
} else { } else {
progress.stringBuilder().append(arg); progress.stringBuilder().append(arg);

View File

@@ -1,6 +1,7 @@
package com.github.gtache.fxml.compiler.impl.internal; package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.GenerationException; import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.GenericTypes;
import com.github.gtache.fxml.compiler.parsing.ParsedObject; import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import javafx.beans.DefaultProperty; import javafx.beans.DefaultProperty;
import javafx.beans.NamedArg; import javafx.beans.NamedArg;
@@ -8,11 +9,14 @@ import javafx.scene.Node;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -24,11 +28,23 @@ import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.FX_
*/ */
final class ReflectionHelper { final class ReflectionHelper {
private static final Logger logger = LogManager.getLogger(ReflectionHelper.class); private static final Logger logger = LogManager.getLogger(ReflectionHelper.class);
private static final Map<Class<?>, Boolean> HAS_VALUE_OF = new ConcurrentHashMap<>(); private static final Map<String, Class<?>> classMap = new HashMap<>();
private static final Map<Class<?>, Boolean> IS_GENERIC = new ConcurrentHashMap<>(); private static final Map<Class<?>, Boolean> hasValueOf = new HashMap<>();
private static final Map<String, String> DEFAULT_PROPERTY = new ConcurrentHashMap<>(); private static final Map<Class<?>, Boolean> isGeneric = new HashMap<>();
private static final Map<Class<?>, Map<String, Method>> METHODS = new ConcurrentHashMap<>(); private static final Map<String, String> defaultProperty = new HashMap<>();
private static final Map<Class<?>, Map<String, Method>> STATIC_METHODS = new ConcurrentHashMap<>(); private static final Map<Class<?>, Map<String, Method>> methods = new HashMap<>();
private static final Map<Class<?>, Map<String, Method>> staticMethods = new HashMap<>();
private static final Map<String, Class<?>> PRIMITIVE_TYPES = Map.of(
"boolean", boolean.class,
"byte", byte.class,
"char", char.class,
"short", short.class,
"int", int.class,
"long", long.class,
"float", float.class,
"double", double.class
);
private ReflectionHelper() { private ReflectionHelper() {
} }
@@ -41,7 +57,7 @@ final class ReflectionHelper {
* @return True if the class is generic * @return True if the class is generic
*/ */
static boolean isGeneric(final Class<?> clazz) { static boolean isGeneric(final Class<?> clazz) {
return IS_GENERIC.computeIfAbsent(clazz, c -> c.getTypeParameters().length > 0); return isGeneric.computeIfAbsent(clazz, c -> c.getTypeParameters().length > 0);
} }
/** /**
@@ -53,7 +69,7 @@ final class ReflectionHelper {
* @return True if the class has a method with the given name * @return True if the class has a method with the given name
*/ */
static boolean hasMethod(final Class<?> clazz, final String methodName) { static boolean hasMethod(final Class<?> clazz, final String methodName) {
final var methodMap = METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>()); final var methodMap = methods.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
final var method = methodMap.computeIfAbsent(methodName, m -> computeMethod(clazz, m)); final var method = methodMap.computeIfAbsent(methodName, m -> computeMethod(clazz, m));
return method != null; return method != null;
} }
@@ -67,7 +83,7 @@ final class ReflectionHelper {
* @return The method * @return The method
*/ */
static Method getMethod(final Class<?> clazz, final String methodName) { static Method getMethod(final Class<?> clazz, final String methodName) {
final var methodMap = METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>()); final var methodMap = methods.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
return methodMap.computeIfAbsent(methodName, m -> computeMethod(clazz, m)); return methodMap.computeIfAbsent(methodName, m -> computeMethod(clazz, m));
} }
@@ -110,7 +126,7 @@ final class ReflectionHelper {
* @return True if the class has a static method with the given name * @return True if the class has a static method with the given name
*/ */
static boolean hasStaticMethod(final Class<?> clazz, final String methodName) { static boolean hasStaticMethod(final Class<?> clazz, final String methodName) {
final var methodMap = STATIC_METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>()); final var methodMap = staticMethods.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
final var method = methodMap.computeIfAbsent(methodName, m -> computeStaticMethod(clazz, m)); final var method = methodMap.computeIfAbsent(methodName, m -> computeStaticMethod(clazz, m));
return method != null; return method != null;
} }
@@ -124,7 +140,7 @@ final class ReflectionHelper {
* @return The method * @return The method
*/ */
static Method getStaticMethod(final Class<?> clazz, final String methodName) { static Method getStaticMethod(final Class<?> clazz, final String methodName) {
final var methodMap = STATIC_METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>()); final var methodMap = staticMethods.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
return methodMap.computeIfAbsent(methodName, m -> computeStaticMethod(clazz, m)); return methodMap.computeIfAbsent(methodName, m -> computeStaticMethod(clazz, m));
} }
@@ -161,7 +177,7 @@ final class ReflectionHelper {
* @return True if the class has a valueOf(String) * @return True if the class has a valueOf(String)
*/ */
static boolean hasValueOf(final Class<?> clazz) { static boolean hasValueOf(final Class<?> clazz) {
return HAS_VALUE_OF.computeIfAbsent(clazz, ReflectionHelper::computeHasValueOf); return hasValueOf.computeIfAbsent(clazz, ReflectionHelper::computeHasValueOf);
} }
/** /**
@@ -216,14 +232,14 @@ final class ReflectionHelper {
* @throws GenerationException If the class is not found or no default property is found * @throws GenerationException If the class is not found or no default property is found
*/ */
static String getDefaultProperty(final String className) throws GenerationException { static String getDefaultProperty(final String className) throws GenerationException {
if (DEFAULT_PROPERTY.containsKey(className)) { if (defaultProperty.containsKey(className)) {
return DEFAULT_PROPERTY.get(className); return defaultProperty.get(className);
} else { } else {
final var defaultProperty = computeDefaultProperty(className); final var property = computeDefaultProperty(className);
if (defaultProperty != null) { if (property != null) {
DEFAULT_PROPERTY.put(className, defaultProperty); defaultProperty.put(className, property);
} }
return defaultProperty; return property;
} }
} }
@@ -238,7 +254,7 @@ final class ReflectionHelper {
if (name.contains(".") || Character.isUpperCase(name.charAt(0))) { if (name.contains(".") || Character.isUpperCase(name.charAt(0))) {
return name; return name;
} else { } else {
return name.substring(0, 1).toUpperCase() + name.substring(1); return MethodType.methodType(clazz).wrap().returnType().getName();
} }
} }
@@ -249,11 +265,25 @@ final class ReflectionHelper {
* @return The class * @return The class
* @throws GenerationException If the class is not found * @throws GenerationException If the class is not found
*/ */
public static Class<?> getClass(final String className) throws GenerationException { static Class<?> getClass(final String className) throws GenerationException {
try { if (classMap.containsKey(className)) {
return Class.forName(className, false, Thread.currentThread().getContextClassLoader()); return classMap.get(className);
} catch (final ClassNotFoundException e) { } else {
throw new GenerationException("Cannot find class " + className + " ; Is a dependency missing for the plugin?", e); final var clazz = computeClass(className);
classMap.put(className, clazz);
return clazz;
}
}
private static Class<?> computeClass(final String className) throws GenerationException {
if (PRIMITIVE_TYPES.containsKey(className)) {
return PRIMITIVE_TYPES.get(className);
} else {
try {
return Class.forName(className, false, Thread.currentThread().getContextClassLoader());
} catch (final ClassNotFoundException e) {
throw new GenerationException("Cannot find class " + className + " ; Is a dependency missing for the plugin?", e);
}
} }
} }
@@ -312,10 +342,55 @@ final class ReflectionHelper {
logger.warn("No field found for generic class {} (id={}) ; Using raw", clazz.getName(), id); logger.warn("No field found for generic class {} (id={}) ; Using raw", clazz.getName(), id);
return ""; return "";
} else if (fieldInfo.isGeneric()) { } else if (fieldInfo.isGeneric()) {
return "<" + String.join(", ", fieldInfo.genericTypes()) + ">"; return formatGenerics(fieldInfo.genericTypes());
} }
} }
} }
return ""; return "";
} }
private static String formatGenerics(final List<? extends GenericTypes> genericTypes) {
final var sb = new StringBuilder();
sb.append("<");
for (var i = 0; i < genericTypes.size(); i++) {
final var genericType = genericTypes.get(i);
sb.append(genericType.name());
formatGenerics(genericType.subTypes(), sb);
if (i < genericTypes.size() - 1) {
sb.append(", ");
}
}
sb.append(">");
return sb.toString();
}
private static void formatGenerics(final List<? extends GenericTypes> genericTypes, final StringBuilder sb) {
if (!genericTypes.isEmpty()) {
sb.append("<");
for (var i = 0; i < genericTypes.size(); i++) {
final var genericType = genericTypes.get(i);
sb.append(genericType.name());
formatGenerics(genericType.subTypes(), sb);
if (i < genericTypes.size() - 1) {
sb.append(", ");
}
}
sb.append(">");
}
}
/**
* Gets the return type of the given method for the given class
*
* @param className The class
* @param methodName The method
* @return The return type
* @throws GenerationException if an error occurs
*/
static String getReturnType(final String className, final String methodName) throws GenerationException {
final var clazz = getClass(className);
final var method = Arrays.stream(clazz.getMethods()).filter(m -> m.getName().equals(methodName))
.findFirst().orElseThrow(() -> new GenerationException("Method " + methodName + " not found in class " + className));
return method.getReturnType().getName();
}
} }

View File

@@ -8,23 +8,21 @@ import javafx.scene.paint.Color;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*; import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.FX_ID;
import static com.github.gtache.fxml.compiler.impl.internal.ObjectFormatter.format; import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getSortedAttributes;
import static com.github.gtache.fxml.compiler.impl.internal.URLBuilder.formatURL;
/** /**
* Helper methods for {@link GeneratorImpl} to format Scenes * Helper methods for {@link GeneratorImpl} to format Scenes
*/ */
final class SceneBuilder { final class SceneFormatter {
private SceneBuilder() {
private SceneFormatter() {
} }
static void formatScene(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException { static void formatScene(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
final var root = findRoot(parsedObject); final var root = findRoot(parsedObject);
final var rootVariableName = progress.getNextVariableName("root"); final var rootVariableName = progress.getNextVariableName("root");
format(progress, root, rootVariableName); ObjectFormatter.format(progress, root, rootVariableName);
final var sortedAttributes = getSortedAttributes(parsedObject); final var sortedAttributes = getSortedAttributes(parsedObject);
double width = -1; double width = -1;
double height = -1; double height = -1;
@@ -43,10 +41,10 @@ final class SceneBuilder {
} }
} }
final var sb = progress.stringBuilder(); final var sb = progress.stringBuilder();
sb.append(START_VAR).append(variableName).append(" = new javafx.scene.Scene(").append(rootVariableName).append(", ") sb.append(GenerationCompatibilityHelper.getStartVar(progress, "javafx.scene.Scene")).append(variableName).append(" = new javafx.scene.Scene(").append(rootVariableName).append(", ")
.append(width).append(", ").append(height).append(", javafx.scene.paint.Color.valueOf(\"").append(paint).append("\"));\n"); .append(width).append(", ").append(height).append(", javafx.scene.paint.Color.valueOf(\"").append(paint).append("\"));\n");
addStylesheets(progress, variableName, stylesheets); addStylesheets(progress, variableName, stylesheets);
handleId(progress, parsedObject, variableName); GenerationHelper.handleId(progress, parsedObject, variableName);
} }
private static ParsedObject findRoot(final ParsedObject parsedObject) throws GenerationException { private static ParsedObject findRoot(final ParsedObject parsedObject) throws GenerationException {
@@ -64,14 +62,11 @@ final class SceneBuilder {
private static void addStylesheets(final GenerationProgress progress, final String variableName, final Collection<String> stylesheets) { private static void addStylesheets(final GenerationProgress progress, final String variableName, final Collection<String> stylesheets) {
if (!stylesheets.isEmpty()) { if (!stylesheets.isEmpty()) {
final var urlVariables = formatURL(progress, stylesheets); final var urlVariables = URLFormatter.formatURL(progress, stylesheets);
final var tmpVariable = progress.getNextVariableName("stylesheets"); final var tmpVariable = progress.getNextVariableName("stylesheets");
final var sb = progress.stringBuilder(); final var sb = progress.stringBuilder();
sb.append(""" sb.append(GenerationCompatibilityHelper.getStartVar(progress, "java.util.List<String>")).append(tmpVariable).append(" = ").append(variableName).append(".getStyleSheets();\n");
final var %1$s = %2$s.getStylesheets(); sb.append(" ").append(tmpVariable).append(".addAll(").append(GenerationCompatibilityHelper.getListOf(progress)).append(String.join(", ", urlVariables)).append("));\n");
%1$s.addAll(java.util.List.of(%3$s));
""".formatted(tmpVariable, variableName, String.join(", ", urlVariables)));
stylesheets.forEach(s -> sb.append(" ").append(variableName).append(".getStyleSheets().add(\"").append(s).append("\");\n"));
} }
} }
} }

View File

@@ -14,15 +14,15 @@ import java.util.function.Function;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*; import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.FX_ID;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getSortedAttributes;
/** /**
* Helper methods for {@link GeneratorImpl} to format TriangleMeshes * Helper methods for {@link GeneratorImpl} to format TriangleMeshes
*/ */
final class TriangleMeshBuilder { final class TriangleMeshFormatter {
private TriangleMeshBuilder() {
private TriangleMeshFormatter() {
} }
static void formatTriangleMesh(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException { static void formatTriangleMesh(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
@@ -64,14 +64,14 @@ final class TriangleMeshBuilder {
} }
} }
final var sb = progress.stringBuilder(); final var sb = progress.stringBuilder();
sb.append(START_VAR).append(variableName).append(" = new javafx.scene.shape.TriangleMesh();\n"); sb.append(GenerationCompatibilityHelper.getStartVar(progress, "javafx.scene.shape.TriangleMesh")).append(variableName).append(" = new javafx.scene.shape.TriangleMesh();\n");
setPoints(progress, variableName, points); setPoints(progress, variableName, points);
setTexCoords(progress, variableName, texCoords); setTexCoords(progress, variableName, texCoords);
setNormals(progress, variableName, normals); setNormals(progress, variableName, normals);
setFaces(progress, variableName, faces); setFaces(progress, variableName, faces);
setFaceSmoothingGroups(progress, variableName, faceSmoothingGroups); setFaceSmoothingGroups(progress, variableName, faceSmoothingGroups);
setVertexFormat(progress, variableName, vertexFormat); setVertexFormat(progress, variableName, vertexFormat);
handleId(progress, parsedObject, variableName); GenerationHelper.handleId(progress, parsedObject, variableName);
} else { } else {
throw new GenerationException("Image cannot have children or properties : " + parsedObject); throw new GenerationException("Image cannot have children or properties : " + parsedObject);
} }
@@ -89,37 +89,37 @@ final class TriangleMeshBuilder {
private static void setPoints(final GenerationProgress progress, final String variableName, final Collection<Float> points) { private static void setPoints(final GenerationProgress progress, final String variableName, final Collection<Float> points) {
if (!points.isEmpty()) { if (!points.isEmpty()) {
progress.stringBuilder().append(" ").append(variableName).append(".getPoints().setAll(new float[]{").append(formatList(points)).append("});\n"); progress.stringBuilder().append(" ").append(variableName).append(".getPoints().setAll(new float[]{").append(formatList(points)).append("});\n");
} }
} }
private static void setTexCoords(final GenerationProgress progress, final String variableName, final Collection<Float> texCoords) { private static void setTexCoords(final GenerationProgress progress, final String variableName, final Collection<Float> texCoords) {
if (!texCoords.isEmpty()) { if (!texCoords.isEmpty()) {
progress.stringBuilder().append(" ").append(variableName).append(".getTexCoords().setAll(new float[]{").append(formatList(texCoords)).append("});\n"); progress.stringBuilder().append(" ").append(variableName).append(".getTexCoords().setAll(new float[]{").append(formatList(texCoords)).append("});\n");
} }
} }
private static void setNormals(final GenerationProgress progress, final String variableName, final Collection<Float> normals) { private static void setNormals(final GenerationProgress progress, final String variableName, final Collection<Float> normals) {
if (!normals.isEmpty()) { if (!normals.isEmpty()) {
progress.stringBuilder().append(" ").append(variableName).append(".getNormals().setAll(new float[]{").append(formatList(normals)).append("});\n"); progress.stringBuilder().append(" ").append(variableName).append(".getNormals().setAll(new float[]{").append(formatList(normals)).append("});\n");
} }
} }
private static void setFaces(final GenerationProgress progress, final String variableName, final Collection<Integer> faces) { private static void setFaces(final GenerationProgress progress, final String variableName, final Collection<Integer> faces) {
if (!faces.isEmpty()) { if (!faces.isEmpty()) {
progress.stringBuilder().append(" ").append(variableName).append(".getFaces().setAll(new int[]{").append(formatList(faces)).append("});\n"); progress.stringBuilder().append(" ").append(variableName).append(".getFaces().setAll(new int[]{").append(formatList(faces)).append("});\n");
} }
} }
private static void setFaceSmoothingGroups(final GenerationProgress progress, final String variableName, final Collection<Integer> faceSmoothingGroups) { private static void setFaceSmoothingGroups(final GenerationProgress progress, final String variableName, final Collection<Integer> faceSmoothingGroups) {
if (!faceSmoothingGroups.isEmpty()) { if (!faceSmoothingGroups.isEmpty()) {
progress.stringBuilder().append(" ").append(variableName).append(".getFaceSmoothingGroups().setAll(new int[]{").append(formatList(faceSmoothingGroups)).append("});\n"); progress.stringBuilder().append(" ").append(variableName).append(".getFaceSmoothingGroups().setAll(new int[]{").append(formatList(faceSmoothingGroups)).append("});\n");
} }
} }
private static void setVertexFormat(final GenerationProgress progress, final String variableName, final VertexFormat vertexFormat) { private static void setVertexFormat(final GenerationProgress progress, final String variableName, final VertexFormat vertexFormat) {
if (vertexFormat != null) { if (vertexFormat != null) {
progress.stringBuilder().append(" ").append(variableName).append(".setVertexFormat(javafx.scene.shape.VertexFormat.").append(vertexFormat).append(");\n"); progress.stringBuilder().append(" ").append(variableName).append(".setVertexFormat(javafx.scene.shape.VertexFormat.").append(vertexFormat).append(");\n");
} }
} }

View File

@@ -9,13 +9,13 @@ import java.util.List;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*; import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
/** /**
* Helper methods for {@link GeneratorImpl} to format URLs * Helper methods for {@link GeneratorImpl} to format URLs
*/ */
final class URLBuilder { final class URLFormatter {
private URLBuilder() {
private URLFormatter() {
} }
static List<String> formatURL(final GenerationProgress progress, final Collection<String> stylesheets) { static List<String> formatURL(final GenerationProgress progress, final Collection<String> stylesheets) {
@@ -25,17 +25,15 @@ final class URLBuilder {
static String formatURL(final GenerationProgress progress, final String url) { static String formatURL(final GenerationProgress progress, final String url) {
final var variableName = progress.getNextVariableName("url"); final var variableName = progress.getNextVariableName("url");
final var sb = progress.stringBuilder(); final var sb = progress.stringBuilder();
if (url.startsWith("@")) { if (url.startsWith(RELATIVE_PATH_PREFIX)) {
sb.append(START_VAR).append(variableName).append(" = getClass().getResource(\"").append(url.substring(1)).append("\");\n"); sb.append(getStartURL(progress)).append(variableName).append(" = getClass().getResource(\"").append(url.substring(1)).append("\");\n");
} else { } else {
sb.append(""" sb.append(" final java.net.URL ").append(variableName).append(";\n");
final java.net.URL %1$s; sb.append(" try {\n");
try { sb.append(" ").append(variableName).append(" = new java.net.URI(\"").append(url).append("\").toURL();\n");
%1$s = new java.net.URI("%2$s").toURL(); sb.append(" } catch (final java.net.MalformedURLException | java.net.URISyntaxException e) {\n");
} catch (final java.net.MalformedURLException | java.net.URISyntaxException e) { sb.append(" throw new RuntimeException(\"Couldn't parse url : ").append(url).append("\", e);\n");
throw new RuntimeException("Couldn't parse url : %2$s", e); sb.append(" }\n");
}
""".formatted(variableName, url));
} }
return variableName; return variableName;
} }
@@ -53,10 +51,14 @@ final class URLBuilder {
default -> throw new GenerationException("Unknown URL attribute : " + property.name()); default -> throw new GenerationException("Unknown URL attribute : " + property.name());
} }
} }
progress.stringBuilder().append(START_VAR).append(variableName).append(" = getClass().getResource(\"").append(value).append("\");\n"); progress.stringBuilder().append(getStartURL(progress)).append(variableName).append(" = getClass().getResource(\"").append(value).append("\");\n");
handleId(progress, parsedObject, variableName); handleId(progress, parsedObject, variableName);
} else { } else {
throw new GenerationException("URL cannot have children or properties : " + parsedObject); throw new GenerationException("URL cannot have children or properties : " + parsedObject);
} }
} }
private static String getStartURL(final GenerationProgress progress) {
return GenerationCompatibilityHelper.getStartVar(progress, "java.net.URL");
}
} }

View File

@@ -6,8 +6,7 @@ import com.github.gtache.fxml.compiler.impl.ResourceBundleInjectionTypes;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static com.github.gtache.fxml.compiler.impl.internal.ReflectionHelper.getWrapperClass; import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
import static com.github.gtache.fxml.compiler.impl.internal.ReflectionHelper.hasValueOf;
/** /**
* Helper methods for {@link GeneratorImpl} to format values * Helper methods for {@link GeneratorImpl} to format values
@@ -31,19 +30,19 @@ final class ValueFormatter {
* @throws GenerationException if an error occurs * @throws GenerationException if an error occurs
*/ */
static String getArg(final GenerationProgress progress, final String value, final Class<?> parameterType) throws GenerationException { static String getArg(final GenerationProgress progress, final String value, final Class<?> parameterType) throws GenerationException {
if (parameterType == String.class && value.startsWith("%")) { if (parameterType == String.class && value.startsWith(RESOURCE_KEY_PREFIX)) {
return getBundleValue(progress, value.substring(1)); return getBundleValue(progress, value.substring(1));
} else if (value.startsWith("@")) { } else if (value.startsWith(RELATIVE_PATH_PREFIX)) {
final var subpath = value.substring(1); final var subpath = value.substring(1);
return getResourceValue(subpath); return getResourceValue(subpath);
} else if (value.startsWith("${")) { } else if (value.startsWith(BINDING_EXPRESSION_PREFIX)) {
throw new UnsupportedOperationException("Not implemented yet"); throw new UnsupportedOperationException("Not implemented yet");
} else if (value.startsWith("$")) { } else if (value.startsWith(EXPRESSION_PREFIX)) {
final var variable = progress.idToVariableName().get(value.substring(1)); final var variable = progress.idToVariableInfo().get(value.substring(1));
if (variable == null) { if (variable == null) {
throw new GenerationException("Unknown variable : " + value.substring(1)); throw new GenerationException("Unknown variable : " + value.substring(1));
} }
return variable; return variable.variableName();
} else { } else {
return toString(value, parameterType); return toString(value, parameterType);
} }
@@ -62,11 +61,12 @@ final class ValueFormatter {
* @throws GenerationException if an error occurs * @throws GenerationException if an error occurs
*/ */
private static String getBundleValue(final GenerationProgress progress, final String value) throws GenerationException { private static String getBundleValue(final GenerationProgress progress, final String value) throws GenerationException {
final var resourceBundleInjectionType = progress.request().parameters().resourceBundleInjection().injectionType(); final var resourceBundleInjectionType = progress.request().parameters().resourceInjectionType();
if (resourceBundleInjectionType instanceof final ResourceBundleInjectionTypes types) { if (resourceBundleInjectionType instanceof final ResourceBundleInjectionTypes types) {
return switch (types) { return switch (types) {
case CONSTRUCTOR, GET_BUNDLE -> "bundle.getString(\"" + value + "\")"; case CONSTRUCTOR, GET_BUNDLE, CONSTRUCTOR_NAME -> "resourceBundle.getString(\"" + value + "\")";
case GETTER -> "controller.resources().getString(\"" + value + "\")"; case GETTER -> "controller.resources().getString(\"" + value + "\")";
case CONSTRUCTOR_FUNCTION -> "resourceBundleFunction.apply(\"" + value + "\")";
}; };
} else { } else {
throw new GenerationException("Unknown resource bundle injection type : " + resourceBundleInjectionType); throw new GenerationException("Unknown resource bundle injection type : " + resourceBundleInjectionType);
@@ -90,28 +90,40 @@ final class ValueFormatter {
return value; return value;
} else if (clazz == byte.class || clazz == Byte.class || clazz == short.class || clazz == Short.class || } else if (clazz == byte.class || clazz == Byte.class || clazz == short.class || clazz == Short.class ||
clazz == int.class || clazz == Integer.class || clazz == long.class || clazz == Long.class) { clazz == int.class || clazz == Integer.class || clazz == long.class || clazz == Long.class) {
if (INT_PATTERN.matcher(value).matches()) { return intToString(value, clazz);
return value;
} else {
return getValueOf(getWrapperClass(clazz), value);
}
} else if (clazz == float.class || clazz == Float.class || clazz == double.class || clazz == Double.class) { } else if (clazz == float.class || clazz == Float.class || clazz == double.class || clazz == Double.class) {
if (DECIMAL_PATTERN.matcher(value).matches()) { return decimalToString(value, clazz);
return value; } else if (ReflectionHelper.hasValueOf(clazz)) {
} else { return valueOfToString(value, clazz);
return getValueOf(getWrapperClass(clazz), value);
}
} else if (hasValueOf(clazz)) {
if (clazz.isEnum()) {
return clazz.getCanonicalName() + "." + value;
} else {
return getValueOf(clazz.getCanonicalName(), value);
}
} else { } else {
return value; return value;
} }
} }
private static String intToString(final String value, final Class<?> clazz) {
if (INT_PATTERN.matcher(value).matches()) {
return value;
} else {
return getValueOf(ReflectionHelper.getWrapperClass(clazz), value);
}
}
private static String decimalToString(final String value, final Class<?> clazz) {
if (DECIMAL_PATTERN.matcher(value).matches()) {
return value;
} else {
return getValueOf(ReflectionHelper.getWrapperClass(clazz), value);
}
}
private static String valueOfToString(final String value, final Class<?> clazz) {
if (clazz.isEnum()) {
return clazz.getCanonicalName() + "." + value;
} else {
return getValueOf(clazz.getCanonicalName(), value);
}
}
private static String getValueOf(final String clazz, final String value) { private static String getValueOf(final String clazz, final String value) {
return clazz + ".valueOf(\"" + value + "\")"; return clazz + ".valueOf(\"" + value + "\")";
} }

View File

@@ -0,0 +1,23 @@
package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import java.util.Objects;
/**
* Info about a variable
*
* @param id The fx:id of the variable
* @param parsedObject The parsed object of the variable
* @param variableName The variable name
* @param className The class name of the variable
*/
record VariableInfo(String id, ParsedObject parsedObject, String variableName, String className) {
VariableInfo {
Objects.requireNonNull(id);
Objects.requireNonNull(parsedObject);
Objects.requireNonNull(variableName);
Objects.requireNonNull(className);
}
}

View File

@@ -5,20 +5,15 @@ import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
import com.github.gtache.fxml.compiler.parsing.ParsedObject; import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import com.github.gtache.fxml.compiler.parsing.ParsedProperty; import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
import static com.github.gtache.fxml.compiler.impl.internal.ControllerInjector.injectCallbackControllerMethod; import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.FX_ID;
import static com.github.gtache.fxml.compiler.impl.internal.ControllerInjector.injectEventHandlerControllerMethod; import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getSortedAttributes;
import static com.github.gtache.fxml.compiler.impl.internal.FieldSetter.setEventHandler;
import static com.github.gtache.fxml.compiler.impl.internal.FieldSetter.setField;
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
import static com.github.gtache.fxml.compiler.impl.internal.PropertyFormatter.formatProperty;
/** /**
* Helper methods for {@link GeneratorImpl} to format WebViews * Helper methods for {@link GeneratorImpl} to format WebViews
*/ */
final class WebViewBuilder { final class WebViewFormatter {
private WebViewBuilder() {
private WebViewFormatter() {
} }
/** /**
@@ -33,13 +28,13 @@ final class WebViewBuilder {
if (parsedObject.children().isEmpty() && parsedObject.properties().isEmpty()) { if (parsedObject.children().isEmpty() && parsedObject.properties().isEmpty()) {
final var sortedAttributes = getSortedAttributes(parsedObject); final var sortedAttributes = getSortedAttributes(parsedObject);
final var sb = progress.stringBuilder(); final var sb = progress.stringBuilder();
sb.append(START_VAR).append(variableName).append(" = new javafx.scene.web.WebView();\n"); sb.append(GenerationCompatibilityHelper.getStartVar(progress, "javafx.scene.web.WebView")).append(variableName).append(" = new javafx.scene.web.WebView();\n");
final var engineVariable = progress.getNextVariableName("engine"); final var engineVariable = progress.getNextVariableName("engine");
sb.append(START_VAR).append(engineVariable).append(" = ").append(variableName).append(".getEngine();\n"); sb.append(GenerationCompatibilityHelper.getStartVar(progress, "javafx.scene.web.WebEngine")).append(engineVariable).append(" = ").append(variableName).append(".getEngine();\n");
for (final var value : sortedAttributes) { for (final var value : sortedAttributes) {
formatAttribute(progress, value, parsedObject, variableName, engineVariable); formatAttribute(progress, value, parsedObject, variableName, engineVariable);
} }
handleId(progress, parsedObject, variableName); GenerationHelper.handleId(progress, parsedObject, variableName);
} else { } else {
throw new GenerationException("WebView cannot have children or properties : " + parsedObject); throw new GenerationException("WebView cannot have children or properties : " + parsedObject);
} }
@@ -57,13 +52,13 @@ final class WebViewBuilder {
injectEventHandler(progress, value, engineVariable); injectEventHandler(progress, value, engineVariable);
case "promptHandler" -> injectPromptHandler(progress, value, engineVariable); case "promptHandler" -> injectPromptHandler(progress, value, engineVariable);
case "location" -> injectLocation(progress, value, engineVariable); case "location" -> injectLocation(progress, value, engineVariable);
default -> formatProperty(progress, value, parsedObject, variableName); default -> PropertyFormatter.formatProperty(progress, value, parsedObject, variableName);
} }
} }
private static void injectConfirmHandler(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) throws GenerationException { private static void injectConfirmHandler(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) throws GenerationException {
if (value.value().startsWith("#")) { if (value.value().startsWith("#")) {
injectCallbackControllerMethod(progress, value, engineVariable, "String.class"); ControllerInjector.injectCallbackControllerMethod(progress, value, engineVariable, "String.class");
} else { } else {
setCallback(progress, value, engineVariable); setCallback(progress, value, engineVariable);
} }
@@ -71,7 +66,7 @@ final class WebViewBuilder {
private static void injectCreatePopupHandler(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) throws GenerationException { private static void injectCreatePopupHandler(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) throws GenerationException {
if (value.value().startsWith("#")) { if (value.value().startsWith("#")) {
injectCallbackControllerMethod(progress, value, engineVariable, "javafx.scene.web.PopupFeatures.class"); ControllerInjector.injectCallbackControllerMethod(progress, value, engineVariable, "javafx.scene.web.PopupFeatures.class");
} else { } else {
setCallback(progress, value, engineVariable); setCallback(progress, value, engineVariable);
} }
@@ -79,22 +74,22 @@ final class WebViewBuilder {
private static void injectEventHandler(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) throws GenerationException { private static void injectEventHandler(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) throws GenerationException {
if (value.value().startsWith("#")) { if (value.value().startsWith("#")) {
injectEventHandlerControllerMethod(progress, value, engineVariable); ControllerInjector.injectEventHandlerControllerMethod(progress, value, engineVariable);
} else { } else {
setEventHandler(progress, value, engineVariable); FieldSetter.setEventHandler(progress, value, engineVariable);
} }
} }
private static void injectPromptHandler(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) throws GenerationException { private static void injectPromptHandler(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) throws GenerationException {
if (value.value().startsWith("#")) { if (value.value().startsWith("#")) {
injectCallbackControllerMethod(progress, value, engineVariable, "javafx.scene.web.PromptData.class"); ControllerInjector.injectCallbackControllerMethod(progress, value, engineVariable, "javafx.scene.web.PromptData.class");
} else { } else {
setCallback(progress, value, engineVariable); setCallback(progress, value, engineVariable);
} }
} }
private static void injectLocation(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) { private static void injectLocation(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) {
progress.stringBuilder().append(" ").append(engineVariable).append(".load(\"").append(value.value()).append("\");\n"); progress.stringBuilder().append(" ").append(engineVariable).append(".load(\"").append(value.value()).append("\");\n");
} }
@@ -106,6 +101,6 @@ final class WebViewBuilder {
* @param parentVariable The parent variable * @param parentVariable The parent variable
*/ */
private static void setCallback(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) throws GenerationException { private static void setCallback(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) throws GenerationException {
setField(progress, property, parentVariable, "javafx.util.Callback"); FieldSetter.setField(progress, property, parentVariable, "javafx.util.Callback");
} }
} }

View File

@@ -3,7 +3,7 @@ package com.github.gtache.fxml.compiler.parsing.impl;
import com.github.gtache.fxml.compiler.parsing.ParsedDefine; import com.github.gtache.fxml.compiler.parsing.ParsedDefine;
import com.github.gtache.fxml.compiler.parsing.ParsedObject; import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import java.util.Objects; import java.util.List;
/** /**
* Implementation of {@link ParsedObject} * Implementation of {@link ParsedObject}
@@ -12,9 +12,9 @@ import java.util.Objects;
* @param attributes The object properties * @param attributes The object properties
* @param properties The object children (complex properties) * @param properties The object children (complex properties)
*/ */
public record ParsedDefineImpl(ParsedObject object) implements ParsedDefine { public record ParsedDefineImpl(List<ParsedObject> children) implements ParsedDefine {
public ParsedDefineImpl { public ParsedDefineImpl {
Objects.requireNonNull(object); children = List.copyOf(children);
} }
} }

View File

@@ -5,7 +5,9 @@ module com.github.gtache.fxml.compiler.core {
requires transitive com.github.gtache.fxml.compiler.api; requires transitive com.github.gtache.fxml.compiler.api;
requires transitive javafx.graphics; requires transitive javafx.graphics;
requires org.apache.logging.log4j; requires org.apache.logging.log4j;
requires java.sql;
exports com.github.gtache.fxml.compiler.impl; exports com.github.gtache.fxml.compiler.impl;
exports com.github.gtache.fxml.compiler.parsing.impl; exports com.github.gtache.fxml.compiler.parsing.impl;
exports com.github.gtache.fxml.compiler.compatibility.impl;
} }

View File

@@ -0,0 +1,28 @@
package com.github.gtache.fxml.compiler.compatibility.impl;
import com.github.gtache.fxml.compiler.compatibility.GenerationCompatibility;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class TestGenerationCompatibilityImpl {
private final int javaVersion;
private final GenerationCompatibility compatibility;
TestGenerationCompatibilityImpl() {
this.javaVersion = 8;
this.compatibility = new GenerationCompatibilityImpl(javaVersion);
}
@Test
void testGetters() {
assertEquals(javaVersion, compatibility.javaVersion());
}
@Test
void testIllegal() {
assertThrows(IllegalArgumentException.class, () -> new GenerationCompatibilityImpl(7));
}
}

View File

@@ -14,6 +14,7 @@ class TestClassesFinder {
final var expected = Set.of( final var expected = Set.of(
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedConstantImpl", "com.github.gtache.fxml.compiler.parsing.impl.TestParsedConstantImpl",
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedCopyImpl", "com.github.gtache.fxml.compiler.parsing.impl.TestParsedCopyImpl",
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedDefineImpl",
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedFactoryImpl", "com.github.gtache.fxml.compiler.parsing.impl.TestParsedFactoryImpl",
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedIncludeImpl", "com.github.gtache.fxml.compiler.parsing.impl.TestParsedIncludeImpl",
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedObjectImpl", "com.github.gtache.fxml.compiler.parsing.impl.TestParsedObjectImpl",

View File

@@ -1,6 +1,7 @@
package com.github.gtache.fxml.compiler.impl; package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.ControllerFieldInfo; import com.github.gtache.fxml.compiler.ControllerFieldInfo;
import com.github.gtache.fxml.compiler.GenericTypes;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.ArrayList; import java.util.ArrayList;
@@ -8,16 +9,17 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
class TestControllerFieldInfoImpl { class TestControllerFieldInfoImpl {
private final String name; private final String name;
private final List<String> genericTypes; private final List<GenericTypes> genericTypes;
private final ControllerFieldInfo info; private final ControllerFieldInfo info;
TestControllerFieldInfoImpl() { TestControllerFieldInfoImpl() {
this.name = "name"; this.name = "name";
this.genericTypes = new ArrayList<>(List.of("A", "B", "C")); this.genericTypes = new ArrayList<>(List.of(mock(GenericTypes.class)));
this.info = new ControllerFieldInfoImpl(name, genericTypes); this.info = new ControllerFieldInfoImpl(name, genericTypes);
} }

View File

@@ -16,29 +16,39 @@ import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class TestControllerInfoImpl { class TestControllerInfoImpl {
private final String className;
private final Map<String, Boolean> handlerHasArgument; private final Map<String, Boolean> handlerHasArgument;
private final ControllerFieldInfo fieldInfo; private final ControllerFieldInfo fieldInfo;
private final Map<String, ControllerFieldInfo> fieldInfoMap; private final Map<String, ControllerFieldInfo> fieldInfoMap;
private final boolean hasInitialize;
private final ControllerInfo info; private final ControllerInfo info;
TestControllerInfoImpl(@Mock final ControllerFieldInfo fieldInfo) { TestControllerInfoImpl(@Mock final ControllerFieldInfo fieldInfo) {
this.className = "controllerName";
this.handlerHasArgument = new HashMap<>(Map.of("one", true, "two", false)); this.handlerHasArgument = new HashMap<>(Map.of("one", true, "two", false));
this.fieldInfo = Objects.requireNonNull(fieldInfo); this.fieldInfo = Objects.requireNonNull(fieldInfo);
this.fieldInfoMap = new HashMap<>(Map.of("one", fieldInfo)); this.fieldInfoMap = new HashMap<>(Map.of("one", fieldInfo));
this.info = new ControllerInfoImpl(handlerHasArgument, fieldInfoMap); this.hasInitialize = true;
this.info = new ControllerInfoImpl(className, handlerHasArgument, fieldInfoMap, hasInitialize);
}
@Test
void testGetters() {
assertEquals(className, info.className());
assertEquals(handlerHasArgument, info.handlerHasArgument());
assertEquals(fieldInfoMap, info.fieldInfo());
assertEquals(hasInitialize, info.hasInitialize());
} }
@Test @Test
void testHandlerHasArgument() { void testHandlerHasArgument() {
assertEquals(handlerHasArgument, info.handlerHasArgument());
assertTrue(info.handlerHasArgument("one")); assertTrue(info.handlerHasArgument("one"));
assertFalse(info.handlerHasArgument("two")); assertFalse(info.handlerHasArgument("two"));
assertTrue(info.handlerHasArgument("three")); assertTrue(info.handlerHasArgument("three"));
} }
@Test @Test
void testPropertyGenericTypes() { void testFieldInfo() {
assertEquals(fieldInfoMap, info.fieldInfo());
assertEquals(fieldInfo, info.fieldInfo("one")); assertEquals(fieldInfo, info.fieldInfo("one"));
} }
@@ -65,7 +75,8 @@ class TestControllerInfoImpl {
@Test @Test
void testIllegal() { void testIllegal() {
assertThrows(NullPointerException.class, () -> new ControllerInfoImpl(null, fieldInfoMap)); assertThrows(NullPointerException.class, () -> new ControllerInfoImpl(null, handlerHasArgument, fieldInfoMap, hasInitialize));
assertThrows(NullPointerException.class, () -> new ControllerInfoImpl(handlerHasArgument, null)); assertThrows(NullPointerException.class, () -> new ControllerInfoImpl(className, null, fieldInfoMap, hasInitialize));
assertThrows(NullPointerException.class, () -> new ControllerInfoImpl(className, handlerHasArgument, null, hasInitialize));
} }
} }

View File

@@ -1,43 +0,0 @@
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));
}
}

View File

@@ -1,49 +1,62 @@
package com.github.gtache.fxml.compiler.impl; 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.GenerationParameters;
import com.github.gtache.fxml.compiler.ResourceBundleInjection; import com.github.gtache.fxml.compiler.InjectionType;
import com.github.gtache.fxml.compiler.compatibility.GenerationCompatibility;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import static java.util.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class TestGenerationParametersImpl { class TestGenerationParametersImpl {
private final Map<String, ControllerInjection> controllerInjections; private final GenerationCompatibility compatibility;
private final Map<String, String> sourceToGeneratedClassName; private final boolean useImageInputStreamConstructor;
private final Map<String, String> sourceToControllerName; private final Map<String, String> bundleMap;
private final ResourceBundleInjection resourceBundleInjection; private final InjectionType controllerInjectionType;
private final InjectionType fieldInjectionType;
private final InjectionType methodInjectionType;
private final InjectionType resourceInjectionType;
private final GenerationParameters parameters; private final GenerationParameters parameters;
TestGenerationParametersImpl(@Mock final ControllerInjection controllerInjection, @Mock final ResourceBundleInjection resourceBundleInjection) { TestGenerationParametersImpl(@Mock final GenerationCompatibility compatibility, @Mock final InjectionType controllerInjectionType,
this.controllerInjections = Map.of("class", controllerInjection); @Mock final InjectionType fieldInjectionType, @Mock final InjectionType methodInjectionType,
this.sourceToGeneratedClassName = Map.of("source", "generated"); @Mock final InjectionType resourceInjectionType) {
this.sourceToControllerName = Map.of("source", "class"); this.compatibility = requireNonNull(compatibility);
this.resourceBundleInjection = Objects.requireNonNull(resourceBundleInjection); this.useImageInputStreamConstructor = true;
this.parameters = new GenerationParametersImpl(controllerInjections, sourceToGeneratedClassName, sourceToControllerName, resourceBundleInjection); this.controllerInjectionType = requireNonNull(controllerInjectionType);
this.fieldInjectionType = requireNonNull(fieldInjectionType);
this.methodInjectionType = requireNonNull(methodInjectionType);
this.resourceInjectionType = requireNonNull(resourceInjectionType);
this.bundleMap = Map.of("source", "generated");
this.parameters = new GenerationParametersImpl(compatibility, useImageInputStreamConstructor, bundleMap, controllerInjectionType, fieldInjectionType, methodInjectionType, resourceInjectionType);
} }
@Test @Test
void testGetters() { void testGetters() {
assertEquals(controllerInjections, parameters.controllerInjections()); assertEquals(compatibility, parameters.compatibility());
assertEquals(sourceToGeneratedClassName, parameters.sourceToGeneratedClassName()); assertEquals(useImageInputStreamConstructor, parameters.useImageInputStreamConstructor());
assertEquals(sourceToControllerName, parameters.sourceToControllerName()); assertEquals(bundleMap, parameters.bundleMap());
assertEquals(resourceBundleInjection, parameters.resourceBundleInjection()); assertEquals(controllerInjectionType, parameters.controllerInjectionType());
assertEquals(fieldInjectionType, parameters.fieldInjectionType());
assertEquals(methodInjectionType, parameters.methodInjectionType());
assertEquals(resourceInjectionType, parameters.resourceInjectionType());
} }
@Test @Test
void testIllegal() { void testIllegal() {
assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(null, sourceToGeneratedClassName, sourceToControllerName, resourceBundleInjection)); assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(null, useImageInputStreamConstructor, bundleMap, controllerInjectionType, fieldInjectionType, methodInjectionType, resourceInjectionType));
assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(controllerInjections, null, sourceToControllerName, resourceBundleInjection)); assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(compatibility, useImageInputStreamConstructor, null, controllerInjectionType, fieldInjectionType, methodInjectionType, resourceInjectionType));
assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(controllerInjections, sourceToGeneratedClassName, null, resourceBundleInjection)); assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(compatibility, useImageInputStreamConstructor, bundleMap, null, fieldInjectionType, methodInjectionType, resourceInjectionType));
assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(controllerInjections, sourceToGeneratedClassName, sourceToControllerName, null)); assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(compatibility, useImageInputStreamConstructor, bundleMap, controllerInjectionType, null, methodInjectionType, resourceInjectionType));
assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(compatibility, useImageInputStreamConstructor, bundleMap, controllerInjectionType, fieldInjectionType, null, resourceInjectionType));
assertThrows(NullPointerException.class, () -> new GenerationParametersImpl(compatibility, useImageInputStreamConstructor, bundleMap, controllerInjectionType, fieldInjectionType, methodInjectionType, null));
} }
} }

View File

@@ -3,6 +3,7 @@ package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.ControllerInfo; import com.github.gtache.fxml.compiler.ControllerInfo;
import com.github.gtache.fxml.compiler.GenerationParameters; import com.github.gtache.fxml.compiler.GenerationParameters;
import com.github.gtache.fxml.compiler.GenerationRequest; import com.github.gtache.fxml.compiler.GenerationRequest;
import com.github.gtache.fxml.compiler.SourceInfo;
import com.github.gtache.fxml.compiler.parsing.ParsedObject; import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -19,31 +20,36 @@ class TestGenerationRequestImpl {
private final GenerationParameters parameters; private final GenerationParameters parameters;
private final ControllerInfo controllerInfo; private final ControllerInfo controllerInfo;
private final SourceInfo sourceInfo;
private final ParsedObject rootObject; private final ParsedObject rootObject;
private final String outputClassName; private final String outputClassName;
private final GenerationRequest request; private final GenerationRequest request;
TestGenerationRequestImpl(@Mock final GenerationParameters parameters, @Mock final ControllerInfo controllerInfo, @Mock final ParsedObject rootObject) { TestGenerationRequestImpl(@Mock final GenerationParameters parameters, @Mock final ControllerInfo controllerInfo,
@Mock final SourceInfo sourceInfo, @Mock final ParsedObject rootObject) {
this.parameters = Objects.requireNonNull(parameters); this.parameters = Objects.requireNonNull(parameters);
this.controllerInfo = Objects.requireNonNull(controllerInfo); this.controllerInfo = Objects.requireNonNull(controllerInfo);
this.sourceInfo = Objects.requireNonNull(sourceInfo);
this.rootObject = Objects.requireNonNull(rootObject); this.rootObject = Objects.requireNonNull(rootObject);
this.outputClassName = "class"; this.outputClassName = "class";
this.request = new GenerationRequestImpl(parameters, controllerInfo, rootObject, outputClassName); this.request = new GenerationRequestImpl(parameters, controllerInfo, sourceInfo, rootObject, outputClassName);
} }
@Test @Test
void testGetters() { void testGetters() {
assertEquals(parameters, request.parameters()); assertEquals(parameters, request.parameters());
assertEquals(controllerInfo, request.controllerInfo()); assertEquals(controllerInfo, request.controllerInfo());
assertEquals(sourceInfo, request.sourceInfo());
assertEquals(rootObject, request.rootObject()); assertEquals(rootObject, request.rootObject());
assertEquals(outputClassName, request.outputClassName()); assertEquals(outputClassName, request.outputClassName());
} }
@Test @Test
void testIllegal() { void testIllegal() {
assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(null, controllerInfo, rootObject, outputClassName)); assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(null, controllerInfo, sourceInfo, rootObject, outputClassName));
assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(parameters, null, rootObject, outputClassName)); assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(parameters, null, sourceInfo, rootObject, outputClassName));
assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(parameters, controllerInfo, null, outputClassName)); assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(parameters, controllerInfo, null, rootObject, outputClassName));
assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(parameters, controllerInfo, rootObject, null)); assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(parameters, controllerInfo, sourceInfo, null, outputClassName));
assertThrows(NullPointerException.class, () -> new GenerationRequestImpl(parameters, controllerInfo, sourceInfo, rootObject, null));
} }
} }

View File

@@ -0,0 +1,49 @@
package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.GenericTypes;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
class TestGenericTypesImpl {
private final String name;
private final List<GenericTypes> subTypes;
private final GenericTypes types;
TestGenericTypesImpl() {
this.name = "name";
this.subTypes = new ArrayList<>(List.of(mock(GenericTypes.class)));
this.types = new GenericTypesImpl(name, subTypes);
}
@Test
void testGetters() {
assertEquals(name, types.name());
assertEquals(subTypes, types.subTypes());
}
@Test
void testCopyList() {
final var originalGenericTypes = types.subTypes();
subTypes.clear();
assertEquals(originalGenericTypes, types.subTypes());
}
@Test
void testUnmodifiable() {
final var infoList = types.subTypes();
assertThrows(UnsupportedOperationException.class, infoList::clear);
}
@Test
void testIllegal() {
assertThrows(NullPointerException.class, () -> new ControllerFieldInfoImpl(null, subTypes));
assertThrows(NullPointerException.class, () -> new ControllerFieldInfoImpl(name, null));
}
}

View File

@@ -1,39 +0,0 @@
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));
}
}

View File

@@ -0,0 +1,80 @@
package com.github.gtache.fxml.compiler.impl;
import com.github.gtache.fxml.compiler.SourceInfo;
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.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ExtendWith(MockitoExtension.class)
class TestSourceInfoImpl {
private final String generatedClassName;
private final String controllerClassName;
private final Path sourceFile;
private final List<SourceInfo> includedSources;
private final Map<String, SourceInfo> sourceToSourceInfo;
private final boolean requiresResourceBundle;
private final SourceInfo info;
TestSourceInfoImpl(@Mock final SourceInfo subInfo) {
this.generatedClassName = "class";
this.controllerClassName = "controller";
this.sourceFile = Paths.get("path");
this.includedSources = new ArrayList<>(List.of(subInfo));
this.sourceToSourceInfo = new HashMap<>(Map.of("source", subInfo));
this.requiresResourceBundle = false;
this.info = new SourceInfoImpl(generatedClassName, controllerClassName, sourceFile, includedSources, sourceToSourceInfo, requiresResourceBundle);
}
@Test
void testGetters() {
assertEquals(generatedClassName, info.generatedClassName());
assertEquals(controllerClassName, info.controllerClassName());
assertEquals(sourceFile, info.sourceFile());
assertEquals(includedSources, info.includedSources());
assertEquals(sourceToSourceInfo, info.sourceToSourceInfo());
assertEquals(requiresResourceBundle, info.requiresResourceBundle());
}
@Test
void testCopyList() {
final var originalIncludedSources = info.includedSources();
includedSources.clear();
assertEquals(originalIncludedSources, info.includedSources());
}
@Test
void testCopyMap() {
final var originalSourceToSourceInfo = info.sourceToSourceInfo();
sourceToSourceInfo.clear();
assertEquals(originalSourceToSourceInfo, info.sourceToSourceInfo());
}
@Test
void testUnmodifiable() {
final var infoIncludedSources = info.includedSources();
final var infoSourceToSourceInfo = info.sourceToSourceInfo();
assertThrows(UnsupportedOperationException.class, infoIncludedSources::clear);
assertThrows(UnsupportedOperationException.class, infoSourceToSourceInfo::clear);
}
@Test
void testIllegal() {
assertThrows(NullPointerException.class, () -> new SourceInfoImpl(null, controllerClassName, sourceFile, includedSources, sourceToSourceInfo, requiresResourceBundle));
assertThrows(NullPointerException.class, () -> new SourceInfoImpl(generatedClassName, null, sourceFile, includedSources, sourceToSourceInfo, requiresResourceBundle));
assertThrows(NullPointerException.class, () -> new SourceInfoImpl(generatedClassName, controllerClassName, null, includedSources, sourceToSourceInfo, requiresResourceBundle));
assertThrows(NullPointerException.class, () -> new SourceInfoImpl(generatedClassName, controllerClassName, sourceFile, null, sourceToSourceInfo, requiresResourceBundle));
assertThrows(NullPointerException.class, () -> new SourceInfoImpl(generatedClassName, controllerClassName, sourceFile, includedSources, null, requiresResourceBundle));
}
}

View File

@@ -0,0 +1,58 @@
package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.GenerationParameters;
import com.github.gtache.fxml.compiler.GenerationRequest;
import com.github.gtache.fxml.compiler.SourceInfo;
import com.github.gtache.fxml.compiler.parsing.ParsedInclude;
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.HashMap;
import java.util.Map;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class TestConstructorFormatter {
private final GenerationProgress progress;
private final GenerationRequest request;
private final GenerationParameters parameters;
private final StringBuilder stringBuilder;
private final SourceInfo sourceInfo;
private final ParsedInclude include;
private final Map<String, SourceInfo> sourceToSourceInfo;
TestConstructorFormatter(@Mock final GenerationProgress progress, @Mock final GenerationRequest request,
@Mock final GenerationParameters parameters, @Mock final StringBuilder stringBuilder,
@Mock final SourceInfo sourceInfo, @Mock final ParsedInclude include) {
this.progress = Objects.requireNonNull(progress);
this.request = Objects.requireNonNull(request);
this.parameters = Objects.requireNonNull(parameters);
this.stringBuilder = Objects.requireNonNull(stringBuilder);
this.sourceInfo = Objects.requireNonNull(sourceInfo);
this.include = Objects.requireNonNull(include);
this.sourceToSourceInfo = new HashMap<>();
}
@BeforeEach
void beforeEach() {
when(request.parameters()).thenReturn(parameters);
when(progress.request()).thenReturn(request);
when(progress.stringBuilder()).thenReturn(stringBuilder);
when(request.sourceInfo()).thenReturn(sourceInfo);
when(sourceInfo.sourceToSourceInfo()).thenReturn(sourceToSourceInfo);
}
@Test
void testFormatSubViewConstructorCallNullSubInfo() {
assertThrows(GenerationException.class, () -> ConstructorFormatter.formatSubViewConstructorCall(progress, include));
}
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestConstructorHelper {
}

View File

@@ -0,0 +1,172 @@
package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.ControllerInfo;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.GenerationParameters;
import com.github.gtache.fxml.compiler.GenerationRequest;
import com.github.gtache.fxml.compiler.impl.ControllerFieldInjectionTypes;
import com.github.gtache.fxml.compiler.impl.ControllerMethodsInjectionType;
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.ArrayList;
import java.util.List;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class TestControllerInjector {
private final GenerationProgress progress;
private final GenerationRequest request;
private final GenerationParameters parameters;
private final ControllerInfo controllerInfo;
private final List<String> controllerFactoryPostAction;
private final String id;
private final String variable;
private final ParsedProperty property;
private final String propertyValue;
private final StringBuilder sb;
TestControllerInjector(@Mock final GenerationProgress progress, @Mock final GenerationRequest request,
@Mock final GenerationParameters parameters, @Mock final ControllerInfo controllerInfo,
@Mock final ParsedProperty property) {
this.progress = Objects.requireNonNull(progress);
this.request = Objects.requireNonNull(request);
this.parameters = Objects.requireNonNull(parameters);
this.controllerInfo = Objects.requireNonNull(controllerInfo);
this.controllerFactoryPostAction = new ArrayList<>();
this.id = "id";
this.variable = "variable";
this.propertyValue = "#property";
this.property = Objects.requireNonNull(property);
this.sb = new StringBuilder();
}
@BeforeEach
void beforeEach() {
when(progress.request()).thenReturn(request);
when(request.parameters()).thenReturn(parameters);
when(request.controllerInfo()).thenReturn(controllerInfo);
when(progress.controllerFactoryPostAction()).thenReturn(controllerFactoryPostAction);
when(progress.stringBuilder()).thenReturn(sb);
when(property.name()).thenReturn("name");
when(property.value()).thenReturn(propertyValue);
}
@Test
void testInjectControllerFieldFactory() throws GenerationException {
when(parameters.fieldInjectionType()).thenReturn(ControllerFieldInjectionTypes.FACTORY);
ControllerInjector.injectControllerField(progress, id, variable);
final var expected = " fieldMap.put(\"" + id + "\", " + variable + ");\n";
assertEquals(expected, sb.toString());
assertTrue(controllerFactoryPostAction.isEmpty());
}
@Test
void testInjectControllerFieldAssign() throws GenerationException {
when(parameters.fieldInjectionType()).thenReturn(ControllerFieldInjectionTypes.ASSIGN);
ControllerInjector.injectControllerField(progress, id, variable);
final var expected = " controller." + id + " = " + variable + ";\n";
assertEquals(expected, sb.toString());
assertTrue(controllerFactoryPostAction.isEmpty());
}
@Test
void testInjectControllerFieldSetters() throws GenerationException {
when(parameters.fieldInjectionType()).thenReturn(ControllerFieldInjectionTypes.SETTERS);
ControllerInjector.injectControllerField(progress, id, variable);
final var expected = " controller." + GenerationHelper.getSetMethod(id) + "(" + variable + ");\n";
assertEquals(expected, sb.toString());
assertTrue(controllerFactoryPostAction.isEmpty());
}
@Test
void testInjectControllerFieldReflection() throws GenerationException {
when(parameters.fieldInjectionType()).thenReturn(ControllerFieldInjectionTypes.REFLECTION);
ControllerInjector.injectControllerField(progress, id, variable);
final var expected = " injectField(\"" + id + "\", " + variable + ");\n";
assertEquals(expected, sb.toString());
assertTrue(controllerFactoryPostAction.isEmpty());
}
@Test
void testInjectControllerFieldUnknown() {
when(parameters.fieldInjectionType()).thenReturn(null);
assertThrows(GenerationException.class, () -> ControllerInjector.injectControllerField(progress, id, variable));
}
@Test
void testInjectEventHandlerReferenceFactoryNoArgument() throws GenerationException {
when(parameters.fieldInjectionType()).thenReturn(ControllerFieldInjectionTypes.FACTORY);
when(parameters.methodInjectionType()).thenReturn(ControllerMethodsInjectionType.REFERENCE);
ControllerInjector.injectEventHandlerControllerMethod(progress, property, variable);
final var expected = " " + variable + "." + GenerationHelper.getSetMethod(property.name()) + "(e -> controller." + property.value().replace("#", "") + "());\n";
assertEquals(1, controllerFactoryPostAction.size());
assertEquals(expected, controllerFactoryPostAction.getFirst());
assertEquals("", sb.toString());
}
@Test
void testInjectEventHandlerReferenceFactoryWithArgument() throws GenerationException {
when(parameters.fieldInjectionType()).thenReturn(ControllerFieldInjectionTypes.FACTORY);
when(parameters.methodInjectionType()).thenReturn(ControllerMethodsInjectionType.REFERENCE);
when(controllerInfo.handlerHasArgument(propertyValue.replace("#", ""))).thenReturn(true);
ControllerInjector.injectEventHandlerControllerMethod(progress, property, variable);
final var expected = " " + variable + "." + GenerationHelper.getSetMethod(property.name()) + "(controller::" + propertyValue.replace("#", "") + ");\n";
assertEquals(1, controllerFactoryPostAction.size());
assertEquals(expected, controllerFactoryPostAction.getFirst());
assertEquals("", sb.toString());
}
@Test
void testInjectEventHandlerReflectionAssign() throws GenerationException {
when(parameters.fieldInjectionType()).thenReturn(ControllerFieldInjectionTypes.ASSIGN);
when(parameters.methodInjectionType()).thenReturn(ControllerMethodsInjectionType.REFLECTION);
ControllerInjector.injectEventHandlerControllerMethod(progress, property, variable);
final var expected = " " + variable + "." + GenerationHelper.getSetMethod(property.name()) + "(e -> callEventHandlerMethod(\"" + propertyValue.replace("#", "") + "\", e));\n";
assertEquals(expected, sb.toString());
assertTrue(controllerFactoryPostAction.isEmpty());
}
@Test
void testInjectEventHandlerUnknownMethod() {
when(parameters.fieldInjectionType()).thenReturn(ControllerFieldInjectionTypes.ASSIGN);
when(parameters.methodInjectionType()).thenReturn(null);
assertThrows(GenerationException.class, () -> ControllerInjector.injectEventHandlerControllerMethod(progress, property, variable));
}
@Test
void testInjectEventHandlerUnknownField() {
when(parameters.fieldInjectionType()).thenReturn(null);
when(parameters.methodInjectionType()).thenReturn(ControllerMethodsInjectionType.REFLECTION);
assertThrows(GenerationException.class, () -> ControllerInjector.injectEventHandlerControllerMethod(progress, property, variable));
}
@Test
void testInjectCallbackReflectionSetters() throws GenerationException {
when(parameters.fieldInjectionType()).thenReturn(ControllerFieldInjectionTypes.ASSIGN);
when(parameters.methodInjectionType()).thenReturn(ControllerMethodsInjectionType.REFLECTION);
ControllerInjector.injectCallbackControllerMethod(progress, property, variable, "clazz");
final var expected = " " + variable + "." + GenerationHelper.getSetMethod(property.name()) + "(e -> callCallbackMethod(\"" + propertyValue.replace("#", "") + "\", e, clazz));\n";
assertEquals(expected, sb.toString());
assertTrue(controllerFactoryPostAction.isEmpty());
}
@Test
void testInjectCallbackReferenceFactory() throws GenerationException {
when(parameters.fieldInjectionType()).thenReturn(ControllerFieldInjectionTypes.FACTORY);
when(parameters.methodInjectionType()).thenReturn(ControllerMethodsInjectionType.REFERENCE);
ControllerInjector.injectCallbackControllerMethod(progress, property, variable, "clazz");
final var expected = " " + variable + "." + GenerationHelper.getSetMethod(property.name()) + "(controller::" + propertyValue.replace("#", "") + ");\n";
assertEquals(1, controllerFactoryPostAction.size());
assertEquals(expected, controllerFactoryPostAction.getFirst());
assertEquals("", sb.toString());
}
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestFieldSetter {
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestFontFormatter {
}

View File

@@ -0,0 +1,141 @@
package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.ControllerFieldInfo;
import com.github.gtache.fxml.compiler.ControllerInfo;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.GenerationParameters;
import com.github.gtache.fxml.compiler.GenerationRequest;
import com.github.gtache.fxml.compiler.compatibility.GenerationCompatibility;
import com.github.gtache.fxml.compiler.compatibility.ListCollector;
import com.github.gtache.fxml.compiler.impl.GenericTypesImpl;
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import com.github.gtache.fxml.compiler.parsing.impl.ParsedPropertyImpl;
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.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class TestGenerationCompatibilityHelper {
private final GenerationProgress progress;
private final GenerationRequest request;
private final GenerationParameters parameters;
private final GenerationCompatibility compatibility;
private final ParsedObject parsedObject;
TestGenerationCompatibilityHelper(@Mock final GenerationProgress progress, @Mock final GenerationRequest request,
@Mock final GenerationParameters parameters, @Mock final GenerationCompatibility compatibility,
@Mock final ParsedObject parsedObject) {
this.progress = Objects.requireNonNull(progress);
this.request = Objects.requireNonNull(request);
this.parameters = Objects.requireNonNull(parameters);
this.compatibility = Objects.requireNonNull(compatibility);
this.parsedObject = Objects.requireNonNull(parsedObject);
}
@BeforeEach
void beforeEach() {
when(progress.request()).thenReturn(request);
when(request.parameters()).thenReturn(parameters);
when(parameters.compatibility()).thenReturn(compatibility);
when(parsedObject.className()).thenReturn("java.lang.String");
}
@Test
void testGetStartVarUseVar() {
when(compatibility.useVar()).thenReturn(true);
assertEquals(" final var ", GenerationCompatibilityHelper.getStartVar(progress, "class", 2));
}
@Test
void testGetStartVarUseVarDefaultIndent() {
when(compatibility.useVar()).thenReturn(true);
assertEquals(" final var ", GenerationCompatibilityHelper.getStartVar(progress, "class"));
}
@Test
void testGetStartVarUseVarObject() throws GenerationException {
when(compatibility.useVar()).thenReturn(true);
assertEquals(" final var ", GenerationCompatibilityHelper.getStartVar(progress, parsedObject));
}
@Test
void testGetStartVarDontUseVar() {
when(compatibility.useVar()).thenReturn(false);
assertEquals(" final javafx.scene.control.Label ", GenerationCompatibilityHelper.getStartVar(progress, "javafx.scene.control.Label", 2));
}
@Test
void testGetStartVarDontUseVarObject() throws GenerationException {
when(compatibility.useVar()).thenReturn(false);
when(parsedObject.className()).thenReturn("javafx.scene.control.Label");
assertEquals(" final javafx.scene.control.Label ", GenerationCompatibilityHelper.getStartVar(progress, parsedObject));
}
@Test
void testGetStartVarDontUseVarGenericObject() throws GenerationException {
when(compatibility.useVar()).thenReturn(false);
when(parsedObject.className()).thenReturn("javafx.scene.control.TableView");
final var id = "tableView";
when(parsedObject.attributes()).thenReturn(Map.of("fx:id", new ParsedPropertyImpl("fx:id", null, id)));
final var info = mock(ControllerInfo.class);
final var fieldInfo = mock(ControllerFieldInfo.class);
when(info.fieldInfo(id)).thenReturn(fieldInfo);
when(request.controllerInfo()).thenReturn(info);
when(fieldInfo.isGeneric()).thenReturn(true);
when(fieldInfo.genericTypes()).thenReturn(List.of(new GenericTypesImpl("java.lang.String", List.of()), new GenericTypesImpl("java.lang.Integer", List.of())));
assertEquals(" final javafx.scene.control.TableView<java.lang.String, java.lang.Integer> ", GenerationCompatibilityHelper.getStartVar(progress, parsedObject));
}
@Test
void testGetToListToList() {
when(compatibility.listCollector()).thenReturn(ListCollector.TO_LIST);
assertEquals(".toList()", GenerationCompatibilityHelper.getToList(progress));
}
@Test
void testGetToListCollectToUnmodifiableList() {
when(compatibility.listCollector()).thenReturn(ListCollector.COLLECT_TO_UNMODIFIABLE_LIST);
assertEquals(".collect(java.util.stream.Collectors.toUnmodifiableList())", GenerationCompatibilityHelper.getToList(progress));
}
@Test
void testGetToListCollectToList() {
when(compatibility.listCollector()).thenReturn(ListCollector.COLLECT_TO_LIST);
assertEquals(".collect(java.util.stream.Collectors.toList())", GenerationCompatibilityHelper.getToList(progress));
}
@Test
void testGetFirstUse() {
when(compatibility.useGetFirst()).thenReturn(true);
assertEquals(".getFirst()", GenerationCompatibilityHelper.getGetFirst(progress));
}
@Test
void testGetFirstDontUse() {
when(compatibility.useGetFirst()).thenReturn(false);
assertEquals(".get(0)", GenerationCompatibilityHelper.getGetFirst(progress));
}
@Test
void testGetListOfUse() {
when(compatibility.useCollectionsOf()).thenReturn(true);
assertEquals("java.util.List.of(", GenerationCompatibilityHelper.getListOf(progress));
}
@Test
void testGetListOfDontUse() {
when(compatibility.useCollectionsOf()).thenReturn(false);
assertEquals("java.util.Arrays.asList(", GenerationCompatibilityHelper.getListOf(progress));
}
}

View File

@@ -0,0 +1,102 @@
package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.ControllerFieldInfo;
import com.github.gtache.fxml.compiler.ControllerInfo;
import com.github.gtache.fxml.compiler.GenerationRequest;
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
import com.github.gtache.fxml.compiler.parsing.impl.ParsedPropertyImpl;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class TestGenerationHelper {
private final GenerationProgress progress;
private final GenerationRequest request;
private final ControllerInfo controllerInfo;
private final ControllerFieldInfo fieldInfo;
private final Map<String, VariableInfo> idToVariableInfo;
private final String variableName;
private final ParsedObject object;
private final Map<String, ParsedProperty> attributes;
private final String className;
private final ParsedProperty property;
private final String propertyName;
TestGenerationHelper(@Mock final GenerationProgress progress, @Mock final GenerationRequest request,
@Mock final ControllerInfo controllerInfo, @Mock final ControllerFieldInfo fieldInfo,
@Mock final ParsedObject object, @Mock final ParsedProperty property) {
this.progress = Objects.requireNonNull(progress);
this.request = Objects.requireNonNull(request);
this.controllerInfo = Objects.requireNonNull(controllerInfo);
this.fieldInfo = Objects.requireNonNull(fieldInfo);
this.object = Objects.requireNonNull(object);
this.property = Objects.requireNonNull(property);
this.idToVariableInfo = new HashMap<>();
this.variableName = "variable";
this.attributes = new HashMap<>();
this.className = "java.lang.String";
this.propertyName = "property";
}
@BeforeEach
void beforeEach() {
when(progress.request()).thenReturn(request);
when(request.controllerInfo()).thenReturn(controllerInfo);
when(object.attributes()).thenReturn(attributes);
when(object.className()).thenReturn(className);
when(property.name()).thenReturn(propertyName);
when(progress.idToVariableInfo()).thenReturn(idToVariableInfo);
}
@Test
void testGetVariablePrefixObject() {
assertEquals("string", GenerationHelper.getVariablePrefix(object));
}
@Test
void testGetVariablePrefix() {
assertEquals("int", GenerationHelper.getVariablePrefix("int"));
}
@Test
void testGetGetMethodProperty() {
assertEquals("getProperty", GenerationHelper.getGetMethod(property));
}
@Test
void testGetGetMethod() {
assertEquals("getSomething", GenerationHelper.getGetMethod("Something"));
}
@Test
void testGetSetMethodProperty() {
assertEquals("setProperty", GenerationHelper.getSetMethod(property));
}
@Test
void testGetSetMethod() {
assertEquals("setSomething", GenerationHelper.getSetMethod("Something"));
}
@Test
void testGetSortedAttributes() {
attributes.put("a", new ParsedPropertyImpl("a", null, "valueA"));
attributes.put("b", new ParsedPropertyImpl("b", null, "valueB"));
attributes.put("c", new ParsedPropertyImpl("c", null, "valueC"));
final var expected = List.of(attributes.get("a"), attributes.get("b"), attributes.get("c"));
assertEquals(expected, GenerationHelper.getSortedAttributes(object));
}
}

View File

@@ -1,8 +1,6 @@
package com.github.gtache.fxml.compiler.impl; package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.GenerationRequest; import com.github.gtache.fxml.compiler.GenerationRequest;
import com.github.gtache.fxml.compiler.impl.internal.GenerationProgress;
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
@@ -10,61 +8,66 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.SequencedCollection; import java.util.SequencedCollection;
import java.util.SequencedMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class TestGenerationProgress { class TestGenerationProgress {
private final GenerationRequest request; private final GenerationRequest request;
private final Map<String, String> idToVariableName; private final VariableInfo variableInfo;
private final Map<String, ParsedObject> idToObject; private final Map<String, VariableInfo> idToVariableInfo;
private final Map<String, AtomicInteger> variableNameCounters; private final Map<String, AtomicInteger> variableNameCounters;
private final SequencedMap<String, String> controllerClassToVariable;
private final SequencedCollection<String> controllerFactoryPostAction; private final SequencedCollection<String> controllerFactoryPostAction;
private final StringBuilder sb; private final StringBuilder sb;
private final GenerationProgress progress; private final GenerationProgress progress;
TestGenerationProgress(@Mock final GenerationRequest request, @Mock final ParsedObject object) { TestGenerationProgress(@Mock final GenerationRequest request, @Mock final VariableInfo variableInfo) {
this.request = Objects.requireNonNull(request); this.request = requireNonNull(request);
this.idToVariableName = new HashMap<>(); this.variableInfo = requireNonNull(variableInfo);
idToVariableName.put("var1", "var2"); this.idToVariableInfo = new HashMap<>();
this.idToObject = new HashMap<>(); idToVariableInfo.put("var1", variableInfo);
idToObject.put("var1", object); this.controllerClassToVariable = new LinkedHashMap<String, String>();
controllerClassToVariable.put("bla", "var1");
controllerClassToVariable.put("bla2", "var2");
this.variableNameCounters = new HashMap<>(); this.variableNameCounters = new HashMap<>();
variableNameCounters.put("var", new AtomicInteger(0)); variableNameCounters.put("var", new AtomicInteger(0));
this.controllerFactoryPostAction = new ArrayList<>(); this.controllerFactoryPostAction = new ArrayList<>();
controllerFactoryPostAction.add("bla"); controllerFactoryPostAction.add("bla");
this.sb = new StringBuilder("test"); this.sb = new StringBuilder("test");
this.progress = new GenerationProgress(request, idToVariableName, idToObject, variableNameCounters, controllerFactoryPostAction, sb); this.progress = new GenerationProgress(request, idToVariableInfo, variableNameCounters, controllerClassToVariable, controllerFactoryPostAction, sb);
} }
@Test @Test
void testGetters() { void testGetters() {
assertEquals(request, progress.request()); assertEquals(request, progress.request());
assertEquals(idToVariableName, progress.idToVariableName()); assertEquals(idToVariableInfo, progress.idToVariableInfo());
assertEquals(variableNameCounters, progress.variableNameCounters()); assertEquals(variableNameCounters, progress.variableNameCounters());
assertEquals(controllerClassToVariable, progress.controllerClassToVariable());
assertEquals(controllerFactoryPostAction, progress.controllerFactoryPostAction()); assertEquals(controllerFactoryPostAction, progress.controllerFactoryPostAction());
assertEquals(sb, progress.stringBuilder()); assertEquals(sb, progress.stringBuilder());
} }
@Test @Test
void testConstructorDoesntCopy() { void testConstructorDoesntCopy() {
idToVariableName.clear(); idToVariableInfo.clear();
assertEquals(idToVariableName, progress.idToVariableName()); assertEquals(idToVariableInfo, progress.idToVariableInfo());
idToObject.clear();
assertEquals(idToObject, progress.idToObject());
variableNameCounters.clear(); variableNameCounters.clear();
assertEquals(variableNameCounters, progress.variableNameCounters()); assertEquals(variableNameCounters, progress.variableNameCounters());
controllerClassToVariable.clear();
assertEquals(controllerClassToVariable, progress.controllerClassToVariable());
controllerFactoryPostAction.clear(); controllerFactoryPostAction.clear();
assertEquals(controllerFactoryPostAction, progress.controllerFactoryPostAction()); assertEquals(controllerFactoryPostAction, progress.controllerFactoryPostAction());
@@ -74,15 +77,15 @@ class TestGenerationProgress {
@Test @Test
void testCanModify() { void testCanModify() {
progress.idToVariableName().put("var3", "var4"); progress.idToVariableInfo().put("var3", variableInfo);
assertEquals(idToVariableName, progress.idToVariableName()); assertEquals(idToVariableInfo, progress.idToVariableInfo());
progress.idToObject().put("var3", mock(ParsedObject.class));
assertEquals(idToObject, progress.idToObject());
progress.variableNameCounters().put("var5", new AtomicInteger(0)); progress.variableNameCounters().put("var5", new AtomicInteger(0));
assertEquals(variableNameCounters, progress.variableNameCounters()); assertEquals(variableNameCounters, progress.variableNameCounters());
progress.controllerClassToVariable().put("bla3", "var3");
assertEquals(controllerClassToVariable, progress.controllerClassToVariable());
progress.controllerFactoryPostAction().add("bla2"); progress.controllerFactoryPostAction().add("bla2");
assertEquals(controllerFactoryPostAction, progress.controllerFactoryPostAction()); assertEquals(controllerFactoryPostAction, progress.controllerFactoryPostAction());
@@ -94,8 +97,9 @@ class TestGenerationProgress {
void testOtherConstructor() { void testOtherConstructor() {
final var progress2 = new GenerationProgress(request); final var progress2 = new GenerationProgress(request);
assertEquals(request, progress2.request()); assertEquals(request, progress2.request());
assertEquals(Map.of(), progress2.idToVariableName()); assertEquals(Map.of(), progress2.idToVariableInfo());
assertEquals(Map.of(), progress2.variableNameCounters()); assertEquals(Map.of(), progress2.variableNameCounters());
assertEquals(Map.of(), progress2.controllerClassToVariable());
assertEquals(List.of(), progress2.controllerFactoryPostAction()); assertEquals(List.of(), progress2.controllerFactoryPostAction());
assertEquals("", progress2.stringBuilder().toString()); assertEquals("", progress2.stringBuilder().toString());
} }
@@ -110,12 +114,12 @@ class TestGenerationProgress {
@Test @Test
void testIllegal() { void testIllegal() {
assertThrows(NullPointerException.class, () -> new GenerationProgress(null, idToVariableName, idToObject, variableNameCounters, controllerFactoryPostAction, sb)); assertThrows(NullPointerException.class, () -> new GenerationProgress(null, idToVariableInfo, variableNameCounters, controllerClassToVariable, controllerFactoryPostAction, sb));
assertThrows(NullPointerException.class, () -> new GenerationProgress(request, null, idToObject, variableNameCounters, controllerFactoryPostAction, sb)); assertThrows(NullPointerException.class, () -> new GenerationProgress(request, null, variableNameCounters, controllerClassToVariable, controllerFactoryPostAction, sb));
assertThrows(NullPointerException.class, () -> new GenerationProgress(request, idToVariableName, null, variableNameCounters, controllerFactoryPostAction, sb)); assertThrows(NullPointerException.class, () -> new GenerationProgress(request, idToVariableInfo, null, controllerClassToVariable, controllerFactoryPostAction, sb));
assertThrows(NullPointerException.class, () -> new GenerationProgress(request, idToVariableName, idToObject, null, controllerFactoryPostAction, sb)); assertThrows(NullPointerException.class, () -> new GenerationProgress(request, idToVariableInfo, variableNameCounters, null, controllerFactoryPostAction, sb));
assertThrows(NullPointerException.class, () -> new GenerationProgress(request, idToVariableName, idToObject, variableNameCounters, null, sb)); assertThrows(NullPointerException.class, () -> new GenerationProgress(request, idToVariableInfo, variableNameCounters, controllerClassToVariable, null, sb));
assertThrows(NullPointerException.class, () -> new GenerationProgress(request, idToVariableName, idToObject, variableNameCounters, controllerFactoryPostAction, null)); assertThrows(NullPointerException.class, () -> new GenerationProgress(request, idToVariableInfo, variableNameCounters, controllerClassToVariable, controllerFactoryPostAction, null));
} }
} }

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestHelperMethodsFormatter {
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestImageFormatter {
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestLoadMethodFormatter {
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestObjectFormatter {
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestPropertyFormatter {
}

View File

@@ -1,7 +1,13 @@
package com.github.gtache.fxml.compiler.impl.internal; package com.github.gtache.fxml.compiler.impl.internal;
import com.github.gtache.fxml.compiler.ControllerFieldInfo;
import com.github.gtache.fxml.compiler.ControllerInfo;
import com.github.gtache.fxml.compiler.GenerationException; import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.impl.WholeConstructorArgs; import com.github.gtache.fxml.compiler.GenerationRequest;
import com.github.gtache.fxml.compiler.impl.GenericTypesImpl;
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
import com.github.gtache.fxml.compiler.parsing.impl.ParsedPropertyImpl;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
@@ -9,15 +15,54 @@ import javafx.scene.control.TableCell;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; 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.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class TestReflectionHelper { class TestReflectionHelper {
private final GenerationProgress progress;
private final GenerationRequest request;
private final Map<String, ParsedProperty> attributes;
private final ControllerInfo controllerInfo;
private final ControllerFieldInfo fieldInfo;
private final ParsedObject parsedObject;
TestReflectionHelper(@Mock final GenerationProgress progress, @Mock final GenerationRequest request,
@Mock final ControllerInfo controllerInfo, @Mock final ControllerFieldInfo fieldInfo,
@Mock final ParsedObject parsedObject) {
this.progress = Objects.requireNonNull(progress);
this.request = Objects.requireNonNull(request);
this.controllerInfo = Objects.requireNonNull(controllerInfo);
this.fieldInfo = Objects.requireNonNull(fieldInfo);
this.parsedObject = Objects.requireNonNull(parsedObject);
this.attributes = new HashMap<>();
}
@BeforeEach
void beforeEach() {
when(progress.request()).thenReturn(request);
when(request.controllerInfo()).thenReturn(controllerInfo);
when(controllerInfo.fieldInfo("id")).thenReturn(fieldInfo);
when(parsedObject.attributes()).thenReturn(attributes);
when(parsedObject.className()).thenReturn("javafx.scene.control.ComboBox");
attributes.put("fx:id", new ParsedPropertyImpl("fx:id", null, "id"));
when(fieldInfo.isGeneric()).thenReturn(true);
}
@Test @Test
void testIsGeneric() { void testIsGeneric() {
assertFalse(ReflectionHelper.isGeneric(String.class)); assertFalse(ReflectionHelper.isGeneric(String.class));
@@ -127,4 +172,50 @@ class TestReflectionHelper {
void testGetDefaultProperty() throws GenerationException { void testGetDefaultProperty() throws GenerationException {
assertEquals("items", ReflectionHelper.getDefaultProperty("javafx.scene.control.ListView")); assertEquals("items", ReflectionHelper.getDefaultProperty("javafx.scene.control.ListView"));
} }
@Test
void testGetWrapperClass() {
assertEquals(Integer.class.getName(), ReflectionHelper.getWrapperClass(int.class));
assertEquals(String.class.getName(), ReflectionHelper.getWrapperClass(String.class));
assertEquals(Object.class.getName(), ReflectionHelper.getWrapperClass(Object.class));
assertEquals(Double.class.getName(), ReflectionHelper.getWrapperClass(double.class));
}
@Test
void testGetClass() throws GenerationException {
assertEquals(String.class, ReflectionHelper.getClass("java.lang.String"));
assertEquals(Object.class, ReflectionHelper.getClass("java.lang.Object"));
assertEquals(int.class, ReflectionHelper.getClass("int"));
}
@Test
void testGetGenericTypesNotGeneric() throws GenerationException {
when(parsedObject.className()).thenReturn("java.lang.String");
assertEquals("", ReflectionHelper.getGenericTypes(progress, parsedObject));
}
@Test
void testGetGenericTypesNullProperty() throws GenerationException {
attributes.clear();
assertEquals("", ReflectionHelper.getGenericTypes(progress, parsedObject));
}
@Test
void testGetGenericTypesFieldNotFound() throws GenerationException {
when(controllerInfo.fieldInfo("id")).thenReturn(null);
assertEquals("", ReflectionHelper.getGenericTypes(progress, parsedObject));
}
@Test
void testGetGenericTypesFieldNotGeneric() throws GenerationException {
when(fieldInfo.isGeneric()).thenReturn(false);
when(fieldInfo.genericTypes()).thenReturn(List.of(new GenericTypesImpl("java.lang.String", List.of()), new GenericTypesImpl("java.lang.Integer", List.of())));
assertEquals("", ReflectionHelper.getGenericTypes(progress, parsedObject));
}
@Test
void testGetGenericTypes() throws GenerationException {
when(fieldInfo.genericTypes()).thenReturn(List.of(new GenericTypesImpl("java.lang.String", List.of()), new GenericTypesImpl("java.lang.Integer", List.of())));
assertEquals("<java.lang.String, java.lang.Integer>", ReflectionHelper.getGenericTypes(progress, parsedObject));
}
} }

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestSceneFormatter {
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestTriangleMeshFormatter {
}

View File

@@ -0,0 +1,9 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestURLFormatter {
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestValueFormatter {
}

View File

@@ -0,0 +1,46 @@
package com.github.gtache.fxml.compiler.impl.internal;
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 TestVariableInfo {
private final String id;
private final ParsedObject parsedObject;
private final String variableName;
private final String className;
private final VariableInfo info;
TestVariableInfo(@Mock final ParsedObject parsedObject) {
this.id = "id";
this.parsedObject = Objects.requireNonNull(parsedObject);
this.variableName = "variableName";
this.className = "className";
this.info = new VariableInfo(id, parsedObject, variableName, className);
}
@Test
void testGetters() {
assertEquals(id, info.id());
assertEquals(parsedObject, info.parsedObject());
assertEquals(variableName, info.variableName());
assertEquals(className, info.className());
}
@Test
void testIllegal() {
assertThrows(NullPointerException.class, () -> new VariableInfo(null, parsedObject, variableName, className));
assertThrows(NullPointerException.class, () -> new VariableInfo(id, null, variableName, className));
assertThrows(NullPointerException.class, () -> new VariableInfo(id, parsedObject, null, className));
assertThrows(NullPointerException.class, () -> new VariableInfo(id, parsedObject, variableName, null));
}
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.impl.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestWebViewFormatter {
}

View File

@@ -1,4 +1,4 @@
package com.github.gtache.fxml.compiler.impl; package com.github.gtache.fxml.compiler.impl.internal;
import javafx.beans.NamedArg; import javafx.beans.NamedArg;

View File

@@ -0,0 +1,49 @@
package com.github.gtache.fxml.compiler.parsing.impl;
import com.github.gtache.fxml.compiler.parsing.ParsedDefine;
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.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ExtendWith(MockitoExtension.class)
class TestParsedDefineImpl {
private final List<ParsedObject> children;
private final ParsedDefine parsedDefine;
TestParsedDefineImpl(@Mock ParsedObject parsedObject1, @Mock ParsedObject parsedObject2) {
this.children = new ArrayList<>(List.of(parsedObject1, parsedObject2));
this.parsedDefine = new ParsedDefineImpl(children);
}
@Test
void testGetters() {
assertEquals(children, parsedDefine.children());
}
@Test
void testCopy() {
final var originalChildren = parsedDefine.children();
children.clear();
assertEquals(originalChildren, parsedDefine.children());
}
@Test
void testUnmodifiable() {
final var objectChildren = parsedDefine.children();
assertThrows(UnsupportedOperationException.class, objectChildren::clear);
}
@Test
void testIllegal() {
assertThrows(NullPointerException.class, () -> new ParsedDefineImpl(null));
}
}

View File

@@ -1,32 +1,24 @@
package com.github.gtache.fxml.compiler.maven; package com.github.gtache.fxml.compiler.maven;
import com.github.gtache.fxml.compiler.ControllerInjection; import com.github.gtache.fxml.compiler.compatibility.impl.GenerationCompatibilityImpl;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.impl.ControllerFieldInjectionTypes; import com.github.gtache.fxml.compiler.impl.ControllerFieldInjectionTypes;
import com.github.gtache.fxml.compiler.impl.ControllerInjectionImpl; import com.github.gtache.fxml.compiler.impl.ControllerInjectionTypes;
import com.github.gtache.fxml.compiler.impl.ControllerMethodsInjectionType; import com.github.gtache.fxml.compiler.impl.ControllerMethodsInjectionType;
import com.github.gtache.fxml.compiler.impl.GenerationParametersImpl; import com.github.gtache.fxml.compiler.impl.GenerationParametersImpl;
import com.github.gtache.fxml.compiler.impl.GenerationRequestImpl;
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
import com.github.gtache.fxml.compiler.impl.ResourceBundleInjectionImpl;
import com.github.gtache.fxml.compiler.impl.ResourceBundleInjectionTypes; import com.github.gtache.fxml.compiler.impl.ResourceBundleInjectionTypes;
import com.github.gtache.fxml.compiler.parsing.ParseException; import com.github.gtache.fxml.compiler.maven.internal.CompilationInfo;
import com.github.gtache.fxml.compiler.parsing.xml.DOMFXMLParser; import com.github.gtache.fxml.compiler.maven.internal.CompilationInfoProvider;
import com.github.gtache.fxml.compiler.maven.internal.Compiler;
import com.github.gtache.fxml.compiler.maven.internal.ControllerProvider;
import com.github.gtache.fxml.compiler.maven.internal.FXMLProvider;
import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -35,7 +27,6 @@ import java.util.Map;
*/ */
@Mojo(name = "compile", defaultPhase = LifecyclePhase.GENERATE_SOURCES) @Mojo(name = "compile", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
public class FXMLCompilerMojo extends AbstractMojo { public class FXMLCompilerMojo extends AbstractMojo {
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
@Parameter(defaultValue = "${project}", required = true, readonly = true) @Parameter(defaultValue = "${project}", required = true, readonly = true)
private MavenProject project; private MavenProject project;
@@ -43,146 +34,61 @@ public class FXMLCompilerMojo extends AbstractMojo {
@Parameter(property = "output-directory", defaultValue = "${project.build.directory}/generated-sources/java", required = true) @Parameter(property = "output-directory", defaultValue = "${project.build.directory}/generated-sources/java", required = true)
private Path outputDirectory; private Path outputDirectory;
@Parameter(property = "target-version", defaultValue = "21", required = true)
private int targetVersion;
@Parameter(property = "use-image-inputstream-constructor", defaultValue = "true", required = true)
private boolean useImageInputStreamConstructor;
@Parameter(property = "controller-injection", defaultValue = "INSTANCE", required = true)
private ControllerInjectionTypes controllerInjectionType;
@Parameter(property = "field-injection", defaultValue = "REFLECTION", required = true) @Parameter(property = "field-injection", defaultValue = "REFLECTION", required = true)
private ControllerFieldInjectionTypes fieldInjectionTypes; private ControllerFieldInjectionTypes fieldInjectionType;
@Parameter(property = "method-injection", defaultValue = "REFLECTION", required = true) @Parameter(property = "method-injection", defaultValue = "REFLECTION", required = true)
private ControllerMethodsInjectionType methodsInjectionType; private ControllerMethodsInjectionType methodInjectionType;
@Parameter(property = "bundle-injection", defaultValue = "CONSTRUCTOR", required = true) @Parameter(property = "resource-injection", defaultValue = "CONSTRUCTOR", required = true)
private ResourceBundleInjectionTypes bundleInjectionType; private ResourceBundleInjectionTypes resourceInjectionType;
@Parameter(property = "resource-map")
private Map<String, String> resourceMap;
@Parameter(property = "bundle-map")
private Map<String, String> bundleMap;
@Override @Override
public void execute() throws MojoExecutionException { public void execute() throws MojoExecutionException {
final var fxmls = getAllFXMLs(); if (fieldInjectionType == ControllerFieldInjectionTypes.FACTORY && controllerInjectionType != ControllerInjectionTypes.FACTORY) {
final var controllerMapping = createControllerMapping(fxmls); getLog().warn("Field injection is set to FACTORY : Forcing controller injection to FACTORY");
final var mapping = createMapping(fxmls, controllerMapping); controllerInjectionType = ControllerInjectionTypes.FACTORY;
compile(mapping);
}
private Map<Path, Path> getAllFXMLs() throws MojoExecutionException {
final var map = new HashMap<Path, Path>();
for (final var resource : project.getResources()) {
final var path = Paths.get(resource.getDirectory());
if (Files.isDirectory(path)) {
try (final var stream = Files.find(path, Integer.MAX_VALUE, (p, a) -> p.toString().endsWith(".fxml"), FileVisitOption.FOLLOW_LINKS)) {
final var curList = stream.toList();
getLog().info("Found " + curList);
for (final var p : curList) {
map.put(p, path);
}
} catch (final IOException e) {
throw new MojoExecutionException("Error reading resources", e);
}
} else {
getLog().info("Directory " + path + " does not exist");
}
} }
return map; final var fxmls = FXMLProvider.getFXMLs(project);
final var controllerMapping = createControllerMapping(fxmls);
final var compilationInfoMapping = createCompilationInfoMapping(fxmls, controllerMapping);
compile(compilationInfoMapping);
} }
private static Map<Path, String> createControllerMapping(final Map<? extends Path, ? extends Path> fxmls) throws MojoExecutionException { private static Map<Path, String> createControllerMapping(final Map<? extends Path, ? extends Path> fxmls) throws MojoExecutionException {
final var mapping = new HashMap<Path, String>(); final var mapping = new HashMap<Path, String>();
for (final var fxml : fxmls.keySet()) { for (final var fxml : fxmls.keySet()) {
mapping.put(fxml, getControllerClass(fxml)); mapping.put(fxml, ControllerProvider.getController(fxml));
} }
return mapping; return mapping;
} }
private static String getControllerClass(final Path fxml) throws MojoExecutionException { private Map<Path, CompilationInfo> createCompilationInfoMapping(final Map<? extends Path, ? extends Path> fxmls, final Map<? extends Path, String> controllerMapping) throws MojoExecutionException {
try {
final var documentBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
final var document = documentBuilder.parse(fxml.toFile());
document.getDocumentElement().normalize();
final var controller = document.getDocumentElement().getAttribute("fx:controller");
if (controller.isBlank()) {
throw new MojoExecutionException("Missing controller attribute for " + fxml);
} else {
return controller;
}
} catch (final SAXException | IOException | ParserConfigurationException e) {
throw new MojoExecutionException("Error parsing fxml at " + fxml, e);
}
}
private Map<Path, CompilationInfo> createMapping(final Map<? extends Path, ? extends Path> fxmls, final Map<? extends Path, String> controllerMapping) throws MojoExecutionException {
final var compilationInfoProvider = new CompilationInfoProvider(project, outputDirectory, getLog());
final var mapping = new HashMap<Path, CompilationInfo>(); final var mapping = new HashMap<Path, CompilationInfo>();
for (final var entry : fxmls.entrySet()) { for (final var entry : fxmls.entrySet()) {
final var info = compilationInfoProvider.getCompilationInfo(entry.getValue(), entry.getKey(), controllerMapping); final var info = CompilationInfoProvider.getCompilationInfo(entry.getValue(), entry.getKey(), controllerMapping, outputDirectory, project);
mapping.put(entry.getKey(), info); mapping.put(entry.getKey(), info);
} }
return mapping; return mapping;
} }
private void compile(final Map<Path, CompilationInfo> mapping) throws MojoExecutionException { private void compile(final Map<Path, CompilationInfo> mapping) throws MojoExecutionException {
final var generator = new GeneratorImpl(); final var parameters = new GenerationParametersImpl(new GenerationCompatibilityImpl(targetVersion), useImageInputStreamConstructor, resourceMap,
final var parser = new DOMFXMLParser(); controllerInjectionType, fieldInjectionType, methodInjectionType, resourceInjectionType);
final var controllerInfoProvider = new ControllerInfoProvider(getLog()); Compiler.compile(mapping, parameters);
try {
for (final var entry : mapping.entrySet()) {
final var inputPath = entry.getKey();
final var info = entry.getValue();
getLog().info("Parsing " + inputPath + " with " + parser.getClass().getSimpleName());
final var root = parser.parse(inputPath);
final var controllerInjection = getControllerInjection(mapping, info);
final var sourceToGeneratedClassName = getSourceToGeneratedClassName(mapping, info);
final var sourceToControllerName = getSourceToControllerName(mapping, info);
final var resourceBundleInjection = new ResourceBundleInjectionImpl(bundleInjectionType, getBundleName(info));
final var parameters = new GenerationParametersImpl(controllerInjection, sourceToGeneratedClassName, sourceToControllerName, resourceBundleInjection);
final var controllerInfo = controllerInfoProvider.getControllerInfo(info);
final var output = info.outputFile();
final var request = new GenerationRequestImpl(parameters, controllerInfo, root, info.outputClass());
getLog().info("Compiling " + inputPath);
final var content = generator.generate(request);
final var outputDir = output.getParent();
Files.createDirectories(outputDir);
Files.writeString(output, content);
getLog().info("Compiled " + inputPath + " to " + output);
}
} catch (final IOException | RuntimeException | ParseException | GenerationException e) {
throw new MojoExecutionException("Error compiling fxml", e);
}
project.addCompileSourceRoot(outputDirectory.toAbsolutePath().toString()); project.addCompileSourceRoot(outputDirectory.toAbsolutePath().toString());
} }
private String getBundleName(final CompilationInfo info) {
return bundleMap == null ? "" : bundleMap.getOrDefault(info.inputFile().toString(), "");
}
private static Map<String, String> getSourceToControllerName(final Map<Path, CompilationInfo> mapping, final CompilationInfo info) {
final var ret = new HashMap<String, String>();
for (final var entry : info.includes().entrySet()) {
ret.put(entry.getKey(), mapping.get(entry.getValue()).controllerClass());
}
return ret;
}
private static Map<String, String> getSourceToGeneratedClassName(final Map<Path, CompilationInfo> mapping, final CompilationInfo info) {
final var ret = new HashMap<String, String>();
for (final var entry : info.includes().entrySet()) {
ret.put(entry.getKey(), mapping.get(entry.getValue()).outputClass());
}
return ret;
}
private Map<String, ControllerInjection> getControllerInjection(final Map<Path, CompilationInfo> compilationInfoMapping, final CompilationInfo info) {
final var ret = new HashMap<String, ControllerInjection>();
ret.put(info.controllerClass(), getControllerInjection(info));
for (final var entry : info.includes().entrySet()) {
final var key = entry.getKey();
final var value = entry.getValue();
final var subInfo = compilationInfoMapping.get(value);
ret.put(key, getControllerInjection(subInfo));
}
return ret;
}
private ControllerInjection getControllerInjection(final CompilationInfo info) {
return new ControllerInjectionImpl(fieldInjectionTypes, methodsInjectionType, info.controllerClass());
}
} }

View File

@@ -1,4 +1,4 @@
package com.github.gtache.fxml.compiler.maven; package com.github.gtache.fxml.compiler.maven.internal;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;
@@ -10,21 +10,21 @@ import java.util.Set;
/** /**
* Info about FXML file compilation * Info about FXML file compilation
* *
* @param inputFile The input file * @param inputFile The input file
* @param outputFile The output file * @param outputFile The output file
* @param outputClass The output class name * @param outputClass The output class name
* @param controllerFile The controller file * @param controllerFile The controller file
* @param controllerClass The controller class name * @param controllerClass The controller class name
* @param injectedFields The injected fields * @param injectedFields The injected fields
* @param injectedMethods The injected methods * @param injectedMethods The injected methods
* @param includes The FXML inclusions * @param includes The FXML inclusions
* @param requiresResourceBundle True if the file requires a resource bundle
*/ */
record CompilationInfo(Path inputFile, Path outputFile, String outputClass, Path controllerFile, public record CompilationInfo(Path inputFile, Path outputFile, String outputClass, Path controllerFile,
String controllerClass, String controllerClass, Set<FieldInfo> injectedFields, Set<String> injectedMethods,
Set<FieldInfo> injectedFields, Map<String, Path> includes, boolean requiresResourceBundle) {
Set<String> injectedMethods, Map<String, Path> includes) {
CompilationInfo { public CompilationInfo {
Objects.requireNonNull(inputFile); Objects.requireNonNull(inputFile);
Objects.requireNonNull(outputFile); Objects.requireNonNull(outputFile);
Objects.requireNonNull(outputClass); Objects.requireNonNull(outputClass);
@@ -44,6 +44,7 @@ record CompilationInfo(Path inputFile, Path outputFile, String outputClass, Path
private String outputClass; private String outputClass;
private Path controllerFile; private Path controllerFile;
private String controllerClass; private String controllerClass;
private boolean requiresResourceBundle;
private final Set<FieldInfo> injectedFields; private final Set<FieldInfo> injectedFields;
private final Set<String> injectedMethods; private final Set<String> injectedMethods;
private final Map<String, Path> includes; private final Map<String, Path> includes;
@@ -98,8 +99,13 @@ record CompilationInfo(Path inputFile, Path outputFile, String outputClass, Path
return this; return this;
} }
Builder requiresResourceBundle() {
this.requiresResourceBundle = true;
return this;
}
CompilationInfo build() { CompilationInfo build() {
return new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, includes); return new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, includes, requiresResourceBundle);
} }
} }
} }

View File

@@ -1,8 +1,10 @@
package com.github.gtache.fxml.compiler.maven; package com.github.gtache.fxml.compiler.maven.internal;
import com.github.gtache.fxml.compiler.maven.FXMLCompilerMojo;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@@ -17,28 +19,32 @@ import java.nio.file.Paths;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static java.util.Objects.requireNonNull;
/** /**
* Helper class for {@link FXMLCompilerMojo} to provides {@link CompilationInfo} * Helper class for {@link FXMLCompilerMojo} to provides {@link CompilationInfo}
*/ */
class CompilationInfoProvider { public final class CompilationInfoProvider {
private static final Logger logger = LogManager.getLogger(CompilationInfoProvider.class);
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
private static final Pattern START_DOT_PATTERN = Pattern.compile("^\\."); private static final Pattern START_DOT_PATTERN = Pattern.compile("^\\.");
private final MavenProject project; private CompilationInfoProvider() {
private final Path outputDirectory;
private final Log logger;
CompilationInfoProvider(final MavenProject project, final Path outputDirectory, final Log logger) {
this.project = requireNonNull(project);
this.outputDirectory = requireNonNull(outputDirectory);
this.logger = requireNonNull(logger);
} }
CompilationInfo getCompilationInfo(final Path root, final Path inputPath, final Map<? extends Path, String> controllerMapping) throws MojoExecutionException { /**
logger.info("Parsing " + inputPath); * Gets the compilation info for the given input
*
* @param root The root path
* @param inputPath The input path
* @param controllerMapping The controller mapping
* @param outputDirectory The output directory
* @param project The Maven project
* @return The compilation info
* @throws MojoExecutionException If an error occurs
*/
public static CompilationInfo getCompilationInfo(final Path root, final Path inputPath, final Map<? extends Path, String> controllerMapping,
final Path outputDirectory, final MavenProject project) throws MojoExecutionException {
logger.info("Parsing {}", inputPath);
try { try {
final var documentBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder(); final var documentBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
final var document = documentBuilder.parse(inputPath.toFile()); final var document = documentBuilder.parse(inputPath.toFile());
@@ -52,8 +58,8 @@ class CompilationInfoProvider {
final var targetPath = Paths.get(replacedPrefixPath.replace(inputFilename, outputFilename)); final var targetPath = Paths.get(replacedPrefixPath.replace(inputFilename, outputFilename));
builder.outputFile(targetPath); builder.outputFile(targetPath);
builder.outputClass(outputClass); builder.outputClass(outputClass);
handleNode(document.getDocumentElement(), builder, controllerMapping); handleNode(document.getDocumentElement(), builder, controllerMapping, project);
logger.info(inputPath + " will be compiled to " + targetPath); logger.info("{} will be compiled to {}", inputPath, targetPath);
return builder.build(); return builder.build();
} catch (final SAXException | IOException | ParserConfigurationException e) { } catch (final SAXException | IOException | ParserConfigurationException e) {
throw new MojoExecutionException("Error parsing fxml at " + inputPath, e); throw new MojoExecutionException("Error parsing fxml at " + inputPath, e);
@@ -84,15 +90,15 @@ class CompilationInfoProvider {
return builder.toString().replace(".fxml", ".java"); return builder.toString().replace(".fxml", ".java");
} }
private void handleNode(final Node node, final CompilationInfo.Builder builder, final Map<? extends Path, String> controllerMapping) throws MojoExecutionException { private static void handleNode(final Node node, final CompilationInfo.Builder builder, final Map<? extends Path, String> controllerMapping, final MavenProject project) throws MojoExecutionException {
if (node.getNodeName().equals("fx:include")) { if (node.getNodeName().equals("fx:include")) {
handleInclude(node, builder); handleInclude(node, builder);
} }
handleAttributes(node, builder, controllerMapping); handleAttributes(node, builder, controllerMapping, project);
handleChildren(node, builder, controllerMapping); handleChildren(node, builder, controllerMapping, project);
} }
private void handleInclude(final Node node, final CompilationInfo.Builder builder) throws MojoExecutionException { private static void handleInclude(final Node node, final CompilationInfo.Builder builder) throws MojoExecutionException {
final var map = node.getAttributes(); final var map = node.getAttributes();
if (map == null) { if (map == null) {
throw new MojoExecutionException("Missing attributes for include"); throw new MojoExecutionException("Missing attributes for include");
@@ -103,7 +109,7 @@ class CompilationInfoProvider {
} else { } else {
final var source = sourceAttr.getNodeValue(); final var source = sourceAttr.getNodeValue();
final var path = getRelativePath(builder.inputFile(), source); final var path = getRelativePath(builder.inputFile(), source);
logger.info("Found include " + source); logger.info("Found include {}", source);
builder.addInclude(source, path); builder.addInclude(source, path);
} }
} }
@@ -113,14 +119,14 @@ class CompilationInfoProvider {
return base.getParent().resolve(relative).normalize(); return base.getParent().resolve(relative).normalize();
} }
private void handleChildren(final Node node, final CompilationInfo.Builder builder, final Map<? extends Path, String> controllerMapping) throws MojoExecutionException { private static void handleChildren(final Node node, final CompilationInfo.Builder builder, final Map<? extends Path, String> controllerMapping, final MavenProject project) throws MojoExecutionException {
final var nl = node.getChildNodes(); final var nl = node.getChildNodes();
for (var i = 0; i < nl.getLength(); i++) { for (var i = 0; i < nl.getLength(); i++) {
handleNode(nl.item(i), builder, controllerMapping); handleNode(nl.item(i), builder, controllerMapping, project);
} }
} }
private void handleAttributes(final Node node, final CompilationInfo.Builder builder, final Map<? extends Path, String> controllerMapping) throws MojoExecutionException { private static void handleAttributes(final Node node, final CompilationInfo.Builder builder, final Map<? extends Path, String> controllerMapping, final MavenProject project) throws MojoExecutionException {
final var map = node.getAttributes(); final var map = node.getAttributes();
if (map != null) { if (map != null) {
for (var i = 0; i < map.getLength(); i++) { for (var i = 0; i < map.getLength(); i++) {
@@ -130,20 +136,20 @@ class CompilationInfoProvider {
if (name.startsWith("on")) { if (name.startsWith("on")) {
if (value.startsWith("#")) { if (value.startsWith("#")) {
final var methodName = value.replace("#", ""); final var methodName = value.replace("#", "");
logger.debug("Found injected method " + methodName); logger.debug("Found injected method {}", methodName);
builder.addInjectedMethod(methodName); builder.addInjectedMethod(methodName);
} else if (value.startsWith("$controller.")) { } else if (value.startsWith("$controller.")) {
final var fieldName = value.replace("$controller.", ""); final var fieldName = value.replace("$controller.", "");
logger.debug("Found injected field " + fieldName); logger.debug("Found injected field {}", fieldName);
builder.addInjectedField(fieldName, EventHandler.class.getName()); builder.addInjectedField(fieldName, EventHandler.class.getName());
} else { } else {
throw new MojoExecutionException("Unexpected attribute " + name + " with value " + value); throw new MojoExecutionException("Unexpected attribute " + name + " with value " + value);
} }
} else if (name.equals("fx:controller")) { } else if (name.equals("fx:controller")) {
handleController(value, builder); handleController(value, builder, project);
} else if (name.equals("fx:id")) { } else if (name.equals("fx:id")) {
final var type = node.getNodeName(); final var type = node.getNodeName();
logger.debug("Found injected field " + value + " of type " + type); logger.debug("Found injected field {} of type {}", value, type);
if (type.equals("fx:include")) { if (type.equals("fx:include")) {
final var path = getRelativePath(builder.inputFile(), map.getNamedItem("source").getNodeValue()).normalize(); final var path = getRelativePath(builder.inputFile(), map.getNamedItem("source").getNodeValue()).normalize();
final var controllerClass = controllerMapping.get(path); final var controllerClass = controllerMapping.get(path);
@@ -154,19 +160,21 @@ class CompilationInfoProvider {
} else { } else {
builder.addInjectedField(value, type); builder.addInjectedField(value, type);
} }
} else if (value.startsWith("%")) {
builder.requiresResourceBundle();
} }
} }
} }
} }
private void handleController(final String controllerClass, final CompilationInfo.Builder builder) throws MojoExecutionException { private static void handleController(final String controllerClass, final CompilationInfo.Builder builder, final MavenProject project) throws MojoExecutionException {
final var subPath = controllerClass.replace(".", "/") + ".java"; final var subPath = controllerClass.replace(".", "/") + ".java";
final var path = project.getCompileSourceRoots().stream() final var path = project.getCompileSourceRoots().stream()
.map(s -> Paths.get(s).resolve(subPath)) .map(s -> Paths.get(s).resolve(subPath))
.filter(Files::exists) .filter(Files::exists)
.findFirst() .findFirst()
.orElseThrow(() -> new MojoExecutionException("Cannot find controller " + controllerClass)); .orElseThrow(() -> new MojoExecutionException("Cannot find controller " + controllerClass));
logger.info("Found controller " + controllerClass); logger.info("Found controller {}", controllerClass);
builder.controllerFile(path); builder.controllerFile(path);
builder.controllerClass(controllerClass); builder.controllerClass(controllerClass);
} }

View File

@@ -0,0 +1,66 @@
package com.github.gtache.fxml.compiler.maven.internal;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.GenerationParameters;
import com.github.gtache.fxml.compiler.Generator;
import com.github.gtache.fxml.compiler.impl.GenerationRequestImpl;
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
import com.github.gtache.fxml.compiler.parsing.FXMLParser;
import com.github.gtache.fxml.compiler.parsing.ParseException;
import com.github.gtache.fxml.compiler.parsing.xml.DOMFXMLParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.plugin.MojoExecutionException;
import javax.inject.Named;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
/**
* Creates compiled Java code
*/
@Named
public final class Compiler {
private static final Logger logger = LogManager.getLogger(Compiler.class);
private static final FXMLParser PARSER = new DOMFXMLParser();
private static final Generator GENERATOR = new GeneratorImpl();
private Compiler() {
}
/**
* Compiles the given files
*
* @param mapping The mapping of file to compile to compilation info
* @param parameters The generation parameters
* @throws MojoExecutionException If an error occurs
*/
public static void compile(final Map<Path, CompilationInfo> mapping, final GenerationParameters parameters) throws MojoExecutionException {
for (final var entry : mapping.entrySet()) {
compile(entry.getKey(), entry.getValue(), mapping, parameters);
}
}
private static void compile(final Path inputPath, final CompilationInfo info, final Map<Path, CompilationInfo> mapping, final GenerationParameters parameters) throws MojoExecutionException {
try {
logger.info("Parsing {} with {}", inputPath, PARSER.getClass().getSimpleName());
final var root = PARSER.parse(inputPath);
final var controllerInfo = ControllerInfoProvider.getControllerInfo(info);
final var output = info.outputFile();
final var sourceInfo = SourceInfoProvider.getSourceInfo(info, mapping);
final var request = new GenerationRequestImpl(parameters, controllerInfo, sourceInfo, root, info.outputClass());
logger.info("Compiling {}", inputPath);
final var content = GENERATOR.generate(request);
final var outputDir = output.getParent();
Files.createDirectories(outputDir);
Files.writeString(output, content);
logger.info("Compiled {} to {}", inputPath, output);
} catch (final IOException | RuntimeException | ParseException | GenerationException e) {
throw new MojoExecutionException("Error compiling fxml", e);
}
}
}

View File

@@ -1,55 +1,45 @@
package com.github.gtache.fxml.compiler.maven; package com.github.gtache.fxml.compiler.maven.internal;
import com.github.gtache.fxml.compiler.ControllerFieldInfo; import com.github.gtache.fxml.compiler.ControllerFieldInfo;
import com.github.gtache.fxml.compiler.ControllerInfo; import com.github.gtache.fxml.compiler.ControllerInfo;
import com.github.gtache.fxml.compiler.impl.ClassesFinder; import com.github.gtache.fxml.compiler.impl.ClassesFinder;
import com.github.gtache.fxml.compiler.impl.ControllerFieldInfoImpl; import com.github.gtache.fxml.compiler.impl.ControllerFieldInfoImpl;
import com.github.gtache.fxml.compiler.impl.ControllerInfoImpl; import com.github.gtache.fxml.compiler.impl.ControllerInfoImpl;
import com.github.gtache.fxml.compiler.maven.FXMLCompilerMojo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* Helper class for {@link FXMLCompilerMojo} to provides {@link ControllerInfo} * Helper class for {@link FXMLCompilerMojo} to provides {@link ControllerInfo}
*/ */
class ControllerInfoProvider { final class ControllerInfoProvider {
private static final Logger logger = LogManager.getLogger(ControllerInfoProvider.class);
private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\s+(?:static\\s+)?(?<import>[^;]+);"); private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\s+(?:static\\s+)?(?<import>[^;]+);");
private static final Pattern INITIALIZE_PATTERN = Pattern.compile("void\\s+initialize\\s*\\(\\s*\\)\\s*");
private static final Set<String> JAVA_LANG_CLASSES;
static { private ControllerInfoProvider() {
final var set = new HashSet<String>();
set.add("Object");
set.add("String");
set.add("Boolean");
set.add("Character");
set.add("Byte");
set.add("Short");
set.add("Integer");
set.add("Long");
set.add("Float");
set.add("Double");
JAVA_LANG_CLASSES = Set.copyOf(set);
} }
private final Log logger; /**
* Gets the controller info for the given compilation info
ControllerInfoProvider(final Log logger) { *
this.logger = Objects.requireNonNull(logger); * @param info The compilation info
} * @return The controller info
* @throws MojoExecutionException If an error occurs
ControllerInfo getControllerInfo(final CompilationInfo info) throws MojoExecutionException { */
static ControllerInfo getControllerInfo(final CompilationInfo info) throws MojoExecutionException {
try { try {
final var content = Files.readString(info.controllerFile()); final var content = Files.readString(info.controllerFile());
final var imports = getImports(content); final var imports = getImports(content);
@@ -58,16 +48,14 @@ class ControllerInfoProvider {
final var name = fieldInfo.name(); final var name = fieldInfo.name();
final var type = fieldInfo.type(); final var type = fieldInfo.type();
if (fillFieldInfo(type, name, content, imports, propertyGenericTypes)) { if (fillFieldInfo(type, name, content, imports, propertyGenericTypes)) {
logger.debug("Found injected field " + name + " of type " + type + " with generic types " logger.debug("Found injected field {} of type {} with generic types {} in controller {}", name, type, propertyGenericTypes.get(name), info.controllerFile());
+ propertyGenericTypes.get(name) + " in controller " + info.controllerFile());
} else if (type.contains(".")) { } else if (type.contains(".")) {
final var simpleName = type.substring(type.lastIndexOf('.') + 1); final var simpleName = type.substring(type.lastIndexOf('.') + 1);
if (fillFieldInfo(simpleName, name, content, imports, propertyGenericTypes)) { if (fillFieldInfo(simpleName, name, content, imports, propertyGenericTypes)) {
logger.debug("Found injected field " + name + " of type " + simpleName + " with generic types " logger.debug("Found injected field {} of type {} with generic types {} in controller {}", name, simpleName, propertyGenericTypes.get(name), info.controllerFile());
+ propertyGenericTypes.get(name) + " in controller " + info.controllerFile());
} }
} else { } else {
logger.info("Field " + name + "(" + type + ")" + " not found in controller " + info.controllerFile()); logger.info("Field {}({}) not found in controller {}", name, type, info.controllerFile());
} }
} }
final var handlerHasArgument = new HashMap<String, Boolean>(); final var handlerHasArgument = new HashMap<String, Boolean>();
@@ -77,12 +65,13 @@ class ControllerInfoProvider {
if (matcher.find()) { if (matcher.find()) {
final var arg = matcher.group("arg"); final var arg = matcher.group("arg");
handlerHasArgument.put(name, arg != null && !arg.isBlank()); handlerHasArgument.put(name, arg != null && !arg.isBlank());
logger.debug("Found injected method " + name + " with argument " + arg + " in controller " + info.controllerFile()); logger.debug("Found injected method {} with argument {} in controller {}", name, arg, info.controllerFile());
} else { } else {
throw new MojoExecutionException("Cannot find method " + name + " in controller " + info.controllerFile()); throw new MojoExecutionException("Cannot find method " + name + " in controller " + info.controllerFile());
} }
} }
return new ControllerInfoImpl(handlerHasArgument, propertyGenericTypes); final var hasInitialize = INITIALIZE_PATTERN.matcher(content).find();
return new ControllerInfoImpl(info.controllerClass(), handlerHasArgument, propertyGenericTypes, hasInitialize);
} catch (final IOException e) { } catch (final IOException e) {
throw new MojoExecutionException("Error reading controller " + info.controllerFile(), e); throw new MojoExecutionException("Error reading controller " + info.controllerFile(), e);
} }
@@ -115,28 +104,13 @@ class ControllerInfoProvider {
} }
private static boolean fillFieldInfo(final String type, final String name, final CharSequence content, final Imports imports, final Map<? super String, ? super ControllerFieldInfo> fieldInfos) throws MojoExecutionException { private static boolean fillFieldInfo(final String type, final String name, final CharSequence content, final Imports imports, final Map<? super String, ? super ControllerFieldInfo> fieldInfos) throws MojoExecutionException {
final var pattern = Pattern.compile(Pattern.quote(type) + "(?<type><[^>]+>)?\\s+" + Pattern.quote(name) + "\\s*;"); final var pattern = Pattern.compile(Pattern.quote(type) + "\\s*(?<type><.+>)?\\s*" + Pattern.quote(name) + "\\s*;");
final var matcher = pattern.matcher(content); final var matcher = pattern.matcher(content);
if (matcher.find()) { if (matcher.find()) {
final var genericTypes = matcher.group("type"); final var genericTypes = matcher.group("type");
if (genericTypes != null && !genericTypes.isBlank()) { if (genericTypes != null && !genericTypes.isBlank()) {
final var split = genericTypes.replace("<", "").replace(">", "").split(","); final var parsed = new GenericParser(genericTypes, imports.imports()).parse();
final var resolved = new ArrayList<String>(); fieldInfos.put(name, new ControllerFieldInfoImpl(name, parsed));
for (final var s : split) {
final var trimmed = s.trim();
if (trimmed.contains(".") || JAVA_LANG_CLASSES.contains(trimmed)) {
resolved.add(trimmed);
} else {
final var imported = imports.imports().get(trimmed);
if (imported == null) {
throw new MojoExecutionException("Cannot find class " + trimmed + " probably in one of " + imports.packages() + " ; " +
"Use non-wildcard imports, use fully qualified name or put the classes in a dependency.");
} else {
resolved.add(imported);
}
}
}
fieldInfos.put(name, new ControllerFieldInfoImpl(name, resolved));
} else { } else {
fieldInfos.put(name, new ControllerFieldInfoImpl(name, List.of())); fieldInfos.put(name, new ControllerFieldInfoImpl(name, List.of()));
} }

View File

@@ -0,0 +1,53 @@
package com.github.gtache.fxml.compiler.maven.internal;
import org.apache.maven.plugin.MojoExecutionException;
import org.xml.sax.SAXException;
import javax.inject.Named;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.nio.file.Path;
/**
* Extracts controller class from FXMLs
*/
@Named
public final class ControllerProvider {
private static final DocumentBuilder DOCUMENT_BUILDER;
static {
try {
DOCUMENT_BUILDER = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (final ParserConfigurationException e) {
throw new RuntimeException(e);
}
}
private ControllerProvider() {
}
/**
* Gets the controller class for the given FXML
*
* @param fxml The FXML
* @return The controller class
* @throws MojoExecutionException If an error occurs
*/
public static String getController(final Path fxml) throws MojoExecutionException {
try {
final var document = DOCUMENT_BUILDER.parse(fxml.toFile());
document.getDocumentElement().normalize();
final var controller = document.getDocumentElement().getAttribute("fx:controller");
if (controller.isBlank()) {
throw new MojoExecutionException("Missing controller attribute for " + fxml);
} else {
return controller;
}
} catch (final SAXException | IOException e) {
throw new MojoExecutionException("Error parsing fxml at " + fxml, e);
}
}
}

View File

@@ -0,0 +1,54 @@
package com.github.gtache.fxml.compiler.maven.internal;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import javax.inject.Named;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
/**
* Extracts FXML paths from Maven project
*/
@Named
public final class FXMLProvider {
private static final Logger logger = LogManager.getLogger(FXMLProvider.class);
private FXMLProvider() {
}
/**
* Returns all the FXML files in the project's resources
*
* @param project The Maven project
* @return A mapping of file to resource directory
* @throws MojoExecutionException If an error occurs
*/
public static Map<Path, Path> getFXMLs(final MavenProject project) throws MojoExecutionException {
final var map = new HashMap<Path, Path>();
for (final var resource : project.getResources()) {
final var path = Paths.get(resource.getDirectory());
if (Files.isDirectory(path)) {
try (final var stream = Files.find(path, Integer.MAX_VALUE, (p, a) -> p.toString().endsWith(".fxml"), FileVisitOption.FOLLOW_LINKS)) {
final var curList = stream.toList();
logger.info("Found {}", curList);
for (final var p : curList) {
map.put(p, path);
}
} catch (final IOException e) {
throw new MojoExecutionException("Error reading resources", e);
}
} else {
logger.info("Directory {} does not exist", path);
}
}
return map;
}
}

Some files were not shown because too many files have changed in this diff Show More