Initial commit

This commit is contained in:
Guillaume Tâche
2024-11-15 19:17:27 +01:00
commit f583c9942b
65 changed files with 7069 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
package com.github.gtache.fxml.compiler;
import java.util.Map;
/**
* Factory for creating controllers
*
* @param <T> The type of the controller
*/
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

@@ -0,0 +1,40 @@
package com.github.gtache.fxml.compiler;
import java.util.List;
import java.util.Map;
/**
* Info about a controller for code generation
*/
public interface ControllerInfo {
/**
* @return A mapping of method name to true if the method has an argument
*/
Map<String, Boolean> handlerHasArgument();
/**
* Returns whether the given event handler method has an argument
*
* @param methodName The method name
* @return A mapping of method name to true if the method has an event
*/
default boolean handlerHasArgument(final String methodName) {
return handlerHasArgument().getOrDefault(methodName, true);
}
/**
* @return A mapping of property name to generic types
*/
Map<String, List<String>> propertyGenericTypes();
/**
* Returns the generic types for the given property (null if not generic)
*
* @param property The property
* @return The generic types
*/
default List<String> propertyGenericTypes(final String property) {
return propertyGenericTypes().get(property);
}
}

View File

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

View File

@@ -0,0 +1,29 @@
package com.github.gtache.fxml.compiler;
import java.util.Map;
/**
* Parameters for FXML generation
*/
public interface GenerationParameters {
/**
* @return The mapping of controller class name to controller injection
*/
Map<String, ControllerInjection> controllerInjections();
/**
* @return The mapping of fx:include source to generated class name
*/
Map<String, String> sourceToGeneratedClassName();
/**
* @return The mapping of fx:include source to controller class name
*/
Map<String, String> sourceToControllerName();
/**
* @return The resource bundle injection to use
*/
ResourceBundleInjection resourceBundleInjection();
}

View File

@@ -0,0 +1,29 @@
package com.github.gtache.fxml.compiler;
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
/**
* Represents a request for a code generation
*/
public interface GenerationRequest {
/**
* @return The main controller info
*/
ControllerInfo controllerInfo();
/**
* @return The request parameters
*/
GenerationParameters parameters();
/**
* @return The object to generate code for
*/
ParsedObject rootObject();
/**
* @return The output class name
*/
String outputClassName();
}

View File

@@ -0,0 +1,15 @@
package com.github.gtache.fxml.compiler;
/**
* Generates compiled FXML code
*/
public interface Generator {
/**
* Generates the java code
*
* @param request The request
* @return The java code
*/
String generate(GenerationRequest request);
}

View File

@@ -0,0 +1,13 @@
package com.github.gtache.fxml.compiler;
/**
* A type of injection for controllers
*/
@FunctionalInterface
public interface InjectionType {
/**
* @return The name of the type
*/
String name();
}

View File

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

View File

@@ -0,0 +1,58 @@
package com.github.gtache.fxml.compiler.parsing;
import java.util.LinkedHashMap;
import java.util.SequencedCollection;
import java.util.SequencedMap;
/**
* Special {@link ParsedObject} for fx:include
*/
@FunctionalInterface
public interface ParsedInclude extends ParsedObject {
/**
* @return The controller id if present
*/
default String controllerId() {
final var property = properties().get("fx:id");
if (property == null) {
return null;
} else {
return property.value() + "Controller";
}
}
/**
* @return The resources if present
*/
default String resources() {
final var property = properties().get("resources");
if (property == null) {
return null;
} else {
return property.value().replace("/", ".");
}
}
/**
* @return The source
*/
default String source() {
final var property = properties().get("source");
if (property == null) {
throw new IllegalStateException("Missing source");
} else {
return property.value();
}
}
@Override
default Class<?> clazz() {
return ParsedInclude.class;
}
@Override
default SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> children() {
return new LinkedHashMap<>();
}
}

View File

@@ -0,0 +1,25 @@
package com.github.gtache.fxml.compiler.parsing;
import java.util.SequencedCollection;
import java.util.SequencedMap;
/**
* Parsed object from FXML
*/
public interface ParsedObject {
/**
* @return The object class
*/
Class<?> clazz();
/**
* @return The object properties
*/
SequencedMap<String, ParsedProperty> properties();
/**
* @return The object children (complex properties)
*/
SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> children();
}

View File

@@ -0,0 +1,22 @@
package com.github.gtache.fxml.compiler.parsing;
/**
* Parsed property/attribute from FXML
*/
public interface ParsedProperty {
/**
* @return The property name
*/
String name();
/**
* @return The property source type (in case of static property)
*/
Class<?> sourceType();
/**
* @return The property value
*/
String value();
}

View File

@@ -0,0 +1,7 @@
/**
* API module for FXML compiler
*/
module com.github.gtache.fxml.compiler.api {
exports com.github.gtache.fxml.compiler;
exports com.github.gtache.fxml.compiler.parsing;
}

View File

@@ -0,0 +1,63 @@
package com.github.gtache.fxml.compiler;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
class TestControllerInfo {
private final String string;
private final Map<String, Boolean> handlerHasArgument;
private final Map<String, List<String>> propertyGenericTypes;
private final List<String> genericTypes;
private final ControllerInfo controllerInfo;
TestControllerInfo() {
this.string = "string";
this.handlerHasArgument = new HashMap<>();
this.propertyGenericTypes = new HashMap<>();
this.genericTypes = List.of("a", "b");
this.controllerInfo = spy(ControllerInfo.class);
}
@BeforeEach
void beforeEach() {
when(controllerInfo.handlerHasArgument()).thenReturn(handlerHasArgument);
when(controllerInfo.propertyGenericTypes()).thenReturn(propertyGenericTypes);
}
@Test
void testHandlerHasArgumentNull() {
assertTrue(controllerInfo.handlerHasArgument(string));
}
@Test
void testHandlerHasArgumentFalse() {
handlerHasArgument.put(string, false);
assertFalse(controllerInfo.handlerHasArgument(string));
}
@Test
void testHandlerHasArgumentTrue() {
handlerHasArgument.put(string, true);
assertTrue(controllerInfo.handlerHasArgument(string));
}
@Test
void testPropertyGenericTypesNull() {
assertNull(controllerInfo.propertyGenericTypes(string));
}
@Test
void testPropertyGenericTypes() {
propertyGenericTypes.put(string, genericTypes);
assertEquals(genericTypes, controllerInfo.propertyGenericTypes(string));
}
}

View File

@@ -0,0 +1,81 @@
package com.github.gtache.fxml.compiler.parsing;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.SequencedMap;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class TestParsedInclude {
private final SequencedMap<String, ParsedProperty> properties;
private final ParsedProperty property;
private final String string;
private final ParsedInclude include;
TestParsedInclude(@Mock final ParsedProperty property) {
this.properties = new LinkedHashMap<>();
this.property = Objects.requireNonNull(property);
this.string = "str/ing";
this.include = spy(ParsedInclude.class);
}
@BeforeEach
void beforeEach() {
when(include.properties()).thenReturn(properties);
when(property.value()).thenReturn(string);
}
@Test
void testControllerIdNull() {
assertNull(include.controllerId());
}
@Test
void testControllerId() {
properties.put("fx:id", property);
assertEquals(string + "Controller", include.controllerId());
}
@Test
void testResourcesNull() {
assertNull(include.resources());
}
@Test
void testResources() {
properties.put("resources", property);
assertEquals(string.replace("/", "."), include.resources());
}
@Test
void testSourceNull() {
assertThrows(IllegalStateException.class, include::source);
}
@Test
void testSource() {
properties.put("source", property);
assertEquals(string, include.source());
}
@Test
void testClazz() {
assertEquals(ParsedInclude.class, include.clazz());
}
@Test
void testChildren() {
assertEquals(new LinkedHashMap<>(), include.children());
}
}