Adds BindingFormatter, ExpressionFormatter, ValueClassGuesser, fixes constructor args matching, fixes factory methods
This commit is contained in:
@@ -73,6 +73,7 @@ Optionally add dependencies to the plugin (e.g. when using MediaView and control
|
|||||||
- Compile-time validation
|
- Compile-time validation
|
||||||
- Faster startup speed for the application
|
- Faster startup speed for the application
|
||||||
- Possibility to use controller factories to instantiate controllers with final fields
|
- Possibility to use controller factories to instantiate controllers with final fields
|
||||||
|
- Very basic support for bidirectional bindings
|
||||||
- Easier time with JPMS
|
- Easier time with JPMS
|
||||||
- No need to open the controllers packages to javafx.fxml
|
- 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
|
- No need to open the resources packages when using use-image-inputstream-constructor (if images or resource bundles
|
||||||
@@ -81,8 +82,8 @@ Optionally add dependencies to the plugin (e.g. when using MediaView and control
|
|||||||
## Disadvantages
|
## Disadvantages
|
||||||
|
|
||||||
- `fx:script` is not supported
|
- `fx:script` is not supported
|
||||||
- Possible bugs (file an issue if you see one)
|
- Expect some bugs (file an issue if you see one)
|
||||||
- Expression binding is (very) limited
|
- Expression binding support is very basic
|
||||||
- Probably not fully compatible with all FXML features (file an issue if you need one in specific)
|
- Probably not fully compatible with all FXML features (file an issue if you need one in specific)
|
||||||
- All fxml files must have a `fx:controller` attribute
|
- All fxml files must have a `fx:controller` attribute
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package com.github.gtache.fxml.compiler.impl.internal;
|
||||||
|
|
||||||
|
import com.github.gtache.fxml.compiler.ControllerFieldInjectionType;
|
||||||
|
import com.github.gtache.fxml.compiler.GenerationException;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||||
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.beans.property.ReadOnlyProperty;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.SequencedCollection;
|
||||||
|
|
||||||
|
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatter for property bindings
|
||||||
|
*/
|
||||||
|
class BindingFormatter {
|
||||||
|
|
||||||
|
private static final String PROPERTY = "Property";
|
||||||
|
|
||||||
|
private final HelperProvider helperProvider;
|
||||||
|
private final ControllerFieldInjectionType fieldInjectionType;
|
||||||
|
private final StringBuilder sb;
|
||||||
|
private final SequencedCollection<String> controllerFactoryPostAction;
|
||||||
|
|
||||||
|
BindingFormatter(final HelperProvider helperProvider, final ControllerFieldInjectionType fieldInjectionType,
|
||||||
|
final StringBuilder sb, final SequencedCollection<String> controllerFactoryPostAction) {
|
||||||
|
this.helperProvider = requireNonNull(helperProvider);
|
||||||
|
this.fieldInjectionType = requireNonNull(fieldInjectionType);
|
||||||
|
this.sb = requireNonNull(sb);
|
||||||
|
this.controllerFactoryPostAction = requireNonNull(controllerFactoryPostAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a binding
|
||||||
|
*
|
||||||
|
* @param property The property
|
||||||
|
* @param parent The parent object
|
||||||
|
* @param parentVariable The parent variable
|
||||||
|
*/
|
||||||
|
void formatBinding(final ParsedProperty property, final ParsedObject parent, final String parentVariable) throws GenerationException {
|
||||||
|
final var value = property.value();
|
||||||
|
if (value.endsWith("}")) {
|
||||||
|
if (value.startsWith(BINDING_EXPRESSION_PREFIX)) {
|
||||||
|
formatSimpleBinding(property, parent, parentVariable);
|
||||||
|
} else if (value.startsWith(BIDIRECTIONAL_BINDING_PREFIX)) {
|
||||||
|
formatBidirectionalBinding(property, parent, parentVariable);
|
||||||
|
} else {
|
||||||
|
throw new GenerationException("Unknown binding : " + value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new GenerationException("Invalid binding : " + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void formatSimpleBinding(final ParsedProperty property, final ParsedObject parent, final String parentVariable) throws GenerationException {
|
||||||
|
formatBinding(property, parent, parentVariable, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void formatBidirectionalBinding(final ParsedProperty property, final ParsedObject parent, final String parentVariable) throws GenerationException {
|
||||||
|
formatBinding(property, parent, parentVariable, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void formatBinding(final ParsedProperty property, final ParsedObject parent, final String parentVariable, final boolean bidirectional) throws GenerationException {
|
||||||
|
final var name = property.name();
|
||||||
|
final var value = property.value();
|
||||||
|
final var className = parent.className();
|
||||||
|
final var methodName = name + PROPERTY;
|
||||||
|
final var bindMethod = bidirectional ? "bindBidirectional" : "bind";
|
||||||
|
if (bidirectional ? hasWriteProperty(className, methodName) : hasReadProperty(className, methodName)) {
|
||||||
|
final var returnType = ReflectionHelper.getReturnType(className, methodName);
|
||||||
|
final var expression = helperProvider.getExpressionFormatter().format(value, returnType);
|
||||||
|
if (isControllerWithFactory(value)) {
|
||||||
|
controllerFactoryPostAction.add(INDENT_8 + parentVariable + "." + methodName + "()." + bindMethod + "(" + expression + ");\n");
|
||||||
|
} else {
|
||||||
|
sb.append(INDENT_8).append(parentVariable).append(".").append(methodName).append("().").append(bindMethod).append("(").append(expression).append(");\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new GenerationException("Cannot bind " + name + " on " + className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isControllerWithFactory(final String expression) {
|
||||||
|
final var cleaned = expression.substring(2, expression.length() - 1).trim();
|
||||||
|
final var split = Arrays.stream(cleaned.split("\\.")).filter(s -> !s.isEmpty()).toList();
|
||||||
|
if (split.size() == 2) {
|
||||||
|
final var referenced = split.getFirst();
|
||||||
|
if (referenced.equals("controller")) {
|
||||||
|
return fieldInjectionType == ControllerFieldInjectionType.FACTORY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static boolean hasReadProperty(final String className, final String methodName) throws GenerationException {
|
||||||
|
return isPropertyReturnType(className, methodName, ReadOnlyProperty.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasWriteProperty(final String className, final String methodName) throws GenerationException {
|
||||||
|
return isPropertyReturnType(className, methodName, Property.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPropertyReturnType(final String className, final String methodName, final Class<?> expectedReturnType) throws GenerationException {
|
||||||
|
final var returnType = ReflectionHelper.getReturnType(className, methodName);
|
||||||
|
return expectedReturnType.isAssignableFrom(returnType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,12 @@ import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
|||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,6 +20,7 @@ import java.util.Set;
|
|||||||
*/
|
*/
|
||||||
final class ConstructorHelper {
|
final class ConstructorHelper {
|
||||||
|
|
||||||
|
|
||||||
private ConstructorHelper() {
|
private ConstructorHelper() {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -50,41 +55,111 @@ final class ConstructorHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the constructor arguments that best match the given property names
|
* Gets the constructor arguments that best match the given properties
|
||||||
*
|
*
|
||||||
* @param constructors The constructors
|
* @param constructors The constructors
|
||||||
* @param allPropertyNames The property names
|
* @param properties The mapping of properties name to possible types
|
||||||
* @return The matching constructor arguments, or null if no constructor matches and no default constructor exists
|
* @return The matching constructor arguments, or null if no constructor matches and no default constructor exists
|
||||||
*/
|
*/
|
||||||
static ConstructorArgs getMatchingConstructorArgs(final Constructor<?>[] constructors, final Set<String> allPropertyNames) {
|
static ConstructorArgs getMatchingConstructorArgs(final Constructor<?>[] constructors, final Map<String, List<Class<?>>> properties) {
|
||||||
ConstructorArgs matchingConstructorArgs = null;
|
final var argsDistance = getArgsDistance(constructors, properties);
|
||||||
for (final var constructor : constructors) {
|
final var distances = argsDistance.keySet().stream().sorted().toList();
|
||||||
final var constructorArgs = ReflectionHelper.getConstructorArgs(constructor);
|
for (final var distance : distances) {
|
||||||
final var matchingArgsCount = getMatchingArgsCount(constructorArgs, allPropertyNames);
|
final var matching = argsDistance.get(distance);
|
||||||
if (matchingConstructorArgs == null ? matchingArgsCount > 0 : matchingArgsCount > getMatchingArgsCount(matchingConstructorArgs, allPropertyNames)) {
|
final var argsTypeDistance = getArgsTypeDistance(matching, properties);
|
||||||
matchingConstructorArgs = constructorArgs;
|
if (!argsTypeDistance.isEmpty()) {
|
||||||
|
return argsTypeDistance.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue)
|
||||||
|
.map(s -> s.iterator().next()).findFirst().orElseThrow(() -> new IllegalStateException("Shouldn't happen"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (matchingConstructorArgs == null) {
|
//No matching constructor
|
||||||
return Arrays.stream(constructors).filter(c -> c.getParameterCount() == 0).findFirst()
|
return Arrays.stream(constructors).filter(c -> c.getParameterCount() == 0).findFirst()
|
||||||
.map(c -> new ConstructorArgs(c, new LinkedHashMap<>())).orElse(null);
|
.map(c -> new ConstructorArgs(c, new LinkedHashMap<>())).orElse(null);
|
||||||
} else {
|
|
||||||
return matchingConstructorArgs;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks how many arguments of the given constructor match the given property names
|
* Computes the mapping of distance (difference between number of properties and number of matching arguments) to constructor arguments
|
||||||
|
*
|
||||||
|
* @param constructors The constructors
|
||||||
|
* @param properties The object properties
|
||||||
|
* @return The mapping
|
||||||
|
*/
|
||||||
|
private static Map<Long, Set<ConstructorArgs>> getArgsDistance(final Constructor<?>[] constructors, final Map<String, List<Class<?>>> properties) {
|
||||||
|
final var argsDistance = HashMap.<Long, Set<ConstructorArgs>>newHashMap(constructors.length);
|
||||||
|
for (final var constructor : constructors) {
|
||||||
|
final var constructorArgs = ReflectionHelper.getConstructorArgs(constructor);
|
||||||
|
final var matchingArgsCount = getMatchingArgsCount(constructorArgs, properties);
|
||||||
|
if (matchingArgsCount != 0) {
|
||||||
|
final var difference = Math.abs(constructorArgs.namedArgs().size() - matchingArgsCount);
|
||||||
|
argsDistance.computeIfAbsent(difference, d -> new HashSet<>()).add(constructorArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return argsDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the mapping of type distance (the total of difference between best matching property type and constructor argument type) to constructor arguments.
|
||||||
|
* Also filters out constructors that don't match the properties
|
||||||
|
*
|
||||||
|
* @param matching The matching constructor arguments
|
||||||
|
* @param properties The object properties
|
||||||
|
* @return The mapping
|
||||||
|
*/
|
||||||
|
private static Map<Long, Set<ConstructorArgs>> getArgsTypeDistance(final Collection<ConstructorArgs> matching, final Map<String, ? extends List<Class<?>>> properties) {
|
||||||
|
final var argsTypeDistance = HashMap.<Long, Set<ConstructorArgs>>newHashMap(matching.size());
|
||||||
|
for (final var constructorArgs : matching) {
|
||||||
|
final var typeDistance = getTypeDistance(constructorArgs, properties);
|
||||||
|
if (typeDistance >= 0) {
|
||||||
|
//Valid constructor
|
||||||
|
argsTypeDistance.computeIfAbsent(typeDistance, d -> new HashSet<>()).add(constructorArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return argsTypeDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the type distance between the constructor arguments and the properties
|
||||||
*
|
*
|
||||||
* @param constructorArgs The constructor arguments
|
* @param constructorArgs The constructor arguments
|
||||||
* @param allPropertyNames The property names
|
* @param properties The object properties
|
||||||
|
* @return The type distance
|
||||||
|
*/
|
||||||
|
private static long getTypeDistance(final ConstructorArgs constructorArgs, final Map<String, ? extends List<Class<?>>> properties) {
|
||||||
|
var typeDistance = 0L;
|
||||||
|
for (final var namedArg : constructorArgs.namedArgs().entrySet()) {
|
||||||
|
final var name = namedArg.getKey();
|
||||||
|
final var parameter = namedArg.getValue();
|
||||||
|
final var type = parameter.type();
|
||||||
|
final var property = properties.get(name);
|
||||||
|
if (property != null) {
|
||||||
|
var distance = -1L;
|
||||||
|
for (var i = 0; i < property.size(); i++) {
|
||||||
|
final var clazz = property.get(i);
|
||||||
|
if (clazz.isAssignableFrom(type)) {
|
||||||
|
distance = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (distance < 0) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
typeDistance += distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return typeDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks how many arguments of the given constructor match the given properties
|
||||||
|
*
|
||||||
|
* @param constructorArgs The constructor arguments
|
||||||
|
* @param properties The mapping of properties name to expected type
|
||||||
* @return The number of matching arguments
|
* @return The number of matching arguments
|
||||||
*/
|
*/
|
||||||
private static long getMatchingArgsCount(final ConstructorArgs constructorArgs, final Set<String> allPropertyNames) {
|
private static long getMatchingArgsCount(final ConstructorArgs constructorArgs, final Map<String, List<Class<?>>> properties) {
|
||||||
if (constructorArgs.namedArgs().keySet().stream().anyMatch(s -> !allPropertyNames.contains(s))) {
|
return constructorArgs.namedArgs().keySet().stream().filter(properties::containsKey).count();
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return constructorArgs.namedArgs().keySet().stream().filter(allPropertyNames::contains).count();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package com.github.gtache.fxml.compiler.impl.internal;
|
||||||
|
|
||||||
|
import com.github.gtache.fxml.compiler.ControllerFieldInjectionType;
|
||||||
|
import com.github.gtache.fxml.compiler.GenerationException;
|
||||||
|
import javafx.beans.property.ReadOnlyProperty;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats binding expressions
|
||||||
|
*/
|
||||||
|
class ExpressionFormatter {
|
||||||
|
private static final String PROPERTY_METHOD = "Property()";
|
||||||
|
|
||||||
|
private final HelperProvider helperProvider;
|
||||||
|
private final ControllerFieldInjectionType fieldInjectionType;
|
||||||
|
private final StringBuilder sb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new Expression formatter
|
||||||
|
*
|
||||||
|
* @param helperProvider The helper provider
|
||||||
|
* @param fieldInjectionType The field injection type
|
||||||
|
* @param sb The string builder
|
||||||
|
*/
|
||||||
|
ExpressionFormatter(final HelperProvider helperProvider, final ControllerFieldInjectionType fieldInjectionType, final StringBuilder sb) {
|
||||||
|
this.helperProvider = requireNonNull(helperProvider);
|
||||||
|
this.fieldInjectionType = requireNonNull(fieldInjectionType);
|
||||||
|
this.sb = requireNonNull(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a binding expression
|
||||||
|
*
|
||||||
|
* @param expression The expression
|
||||||
|
* @param returnType The return type
|
||||||
|
* @return The argument to pass to the bind method (e.g. a variable, a method call...)
|
||||||
|
* @throws GenerationException If an error occurs
|
||||||
|
*/
|
||||||
|
String format(final String expression, final Class<?> returnType) throws GenerationException {
|
||||||
|
final var cleaned = expression.substring(2, expression.length() - 1).trim();
|
||||||
|
if (cleaned.contains(".")) {
|
||||||
|
return getDotExpression(expression, returnType);
|
||||||
|
} else {
|
||||||
|
return getNonDotExpression(expression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDotExpression(final String expression, final Class<?> returnType) throws GenerationException {
|
||||||
|
final var cleaned = expression.substring(2, expression.length() - 1).trim();
|
||||||
|
final var split = Arrays.stream(cleaned.split("\\.")).filter(s -> !s.isEmpty()).toList();
|
||||||
|
if (split.size() == 2) {
|
||||||
|
final var referenced = split.get(0);
|
||||||
|
final var value = split.get(1);
|
||||||
|
if (referenced.equals("controller")) {
|
||||||
|
return getControllerExpression(value, returnType);
|
||||||
|
} else {
|
||||||
|
return getNonControllerExpression(referenced, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new GenerationException("Unsupported binding : " + expression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNonDotExpression(final String expression) throws GenerationException {
|
||||||
|
final var cleaned = expression.substring(2, expression.length() - 1).trim();
|
||||||
|
final var info = helperProvider.getVariableProvider().getVariableInfo(cleaned);
|
||||||
|
if (info == null) {
|
||||||
|
throw new GenerationException("Unknown variable : " + cleaned);
|
||||||
|
} else {
|
||||||
|
return info.variableName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getControllerExpression(final String value, final Class<?> returnType) {
|
||||||
|
return switch (fieldInjectionType) {
|
||||||
|
case REFLECTION -> getControllerReflectionExpression(value, returnType);
|
||||||
|
case SETTERS, FACTORY -> "controller." + value + PROPERTY_METHOD;
|
||||||
|
case ASSIGN -> "controller." + value;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getControllerReflectionExpression(final String value, final Class<?> returnType) {
|
||||||
|
final var startVar = helperProvider.getCompatibilityHelper().getStartVar(returnType.getName());
|
||||||
|
final var variable = helperProvider.getVariableProvider().getNextVariableName("binding");
|
||||||
|
sb.append(startVar).append(variable).append(";\n");
|
||||||
|
sb.append(" try {\n");
|
||||||
|
sb.append(" ").append(helperProvider.getCompatibilityHelper().getStartVar("java.lang.reflect.Field", 0))
|
||||||
|
.append("field = controller.getClass().getDeclaredField(\"").append(value).append("\");\n");
|
||||||
|
sb.append(" field.setAccessible(true);\n");
|
||||||
|
sb.append(" ").append(variable).append(" = (").append(returnType.getName()).append(") field.get(controller);\n");
|
||||||
|
sb.append(" } catch (final NoSuchFieldException | IllegalAccessException e) {\n");
|
||||||
|
sb.append(" throw new RuntimeException(e);\n");
|
||||||
|
sb.append(" }\n");
|
||||||
|
return variable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNonControllerExpression(final String referenced, final String value) throws GenerationException {
|
||||||
|
final var info = helperProvider.getVariableProvider().getVariableInfo(referenced);
|
||||||
|
if (info == null) {
|
||||||
|
throw new GenerationException("Unknown variable : " + referenced);
|
||||||
|
} else {
|
||||||
|
final var hasReadProperty = ReadOnlyProperty.class.isAssignableFrom(ReflectionHelper.getReturnType(info.className(), value + "Property"));
|
||||||
|
if (hasReadProperty) {
|
||||||
|
return info.variableName() + "." + value + PROPERTY_METHOD;
|
||||||
|
} else {
|
||||||
|
throw new GenerationException("Cannot read " + value + " on " + info.className());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ import java.util.Map;
|
|||||||
final class GenerationHelper {
|
final class GenerationHelper {
|
||||||
|
|
||||||
static final String INDENT_8 = " ";
|
static final String INDENT_8 = " ";
|
||||||
static final String INDENT_12 = " ";
|
|
||||||
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";
|
||||||
@@ -24,7 +23,7 @@ final class GenerationHelper {
|
|||||||
static final String RESOURCE_KEY_PREFIX = "%";
|
static final String RESOURCE_KEY_PREFIX = "%";
|
||||||
static final String EXPRESSION_PREFIX = "$";
|
static final String EXPRESSION_PREFIX = "$";
|
||||||
static final String BINDING_EXPRESSION_PREFIX = "${";
|
static final String BINDING_EXPRESSION_PREFIX = "${";
|
||||||
static final String BI_DIRECTIONAL_BINDING_PREFIX = "#{";
|
static final String BIDIRECTIONAL_BINDING_PREFIX = "#{";
|
||||||
|
|
||||||
private GenerationHelper() {
|
private GenerationHelper() {
|
||||||
|
|
||||||
@@ -67,7 +66,27 @@ final class GenerationHelper {
|
|||||||
* @return The getter method name
|
* @return The getter method name
|
||||||
*/
|
*/
|
||||||
static String getGetMethod(final String propertyName) {
|
static String getGetMethod(final String propertyName) {
|
||||||
return "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
|
return getMethod(propertyName, "get");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the getter method name for the given property
|
||||||
|
*
|
||||||
|
* @param property The property
|
||||||
|
* @return The getter method name
|
||||||
|
*/
|
||||||
|
static String getIsMethod(final ParsedProperty property) {
|
||||||
|
return getIsMethod(property.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the getter method name for the given property name
|
||||||
|
*
|
||||||
|
* @param propertyName The property name
|
||||||
|
* @return The getter method name
|
||||||
|
*/
|
||||||
|
static String getIsMethod(final String propertyName) {
|
||||||
|
return getMethod(propertyName, "is");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,7 +106,11 @@ final class GenerationHelper {
|
|||||||
* @return The setter method name
|
* @return The setter method name
|
||||||
*/
|
*/
|
||||||
static String getSetMethod(final String propertyName) {
|
static String getSetMethod(final String propertyName) {
|
||||||
return "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
|
return getMethod(propertyName, "set");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getMethod(final String propertyName, final String methodPrefix) {
|
||||||
|
return methodPrefix + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ public class HelperProvider {
|
|||||||
this.helpers = new HashMap<>();
|
this.helpers = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BindingFormatter getBindingFormatter() {
|
||||||
|
return (BindingFormatter) helpers.computeIfAbsent(BindingFormatter.class, c -> {
|
||||||
|
final var fieldInjectionType = progress.request().parameters().fieldInjectionType();
|
||||||
|
final var sb = progress.stringBuilder();
|
||||||
|
final var controllerFactoryPostAction = progress.controllerFactoryPostAction();
|
||||||
|
return new BindingFormatter(this, fieldInjectionType, sb, controllerFactoryPostAction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ControllerInjector getControllerInjector() {
|
ControllerInjector getControllerInjector() {
|
||||||
return (ControllerInjector) helpers.computeIfAbsent(ControllerInjector.class, c -> {
|
return (ControllerInjector) helpers.computeIfAbsent(ControllerInjector.class, c -> {
|
||||||
final var request = progress.request();
|
final var request = progress.request();
|
||||||
@@ -35,6 +44,14 @@ public class HelperProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpressionFormatter getExpressionFormatter() {
|
||||||
|
return (ExpressionFormatter) helpers.computeIfAbsent(ExpressionFormatter.class, c -> {
|
||||||
|
final var fieldInjectionType = progress.request().parameters().fieldInjectionType();
|
||||||
|
final var sb = progress.stringBuilder();
|
||||||
|
return new ExpressionFormatter(this, fieldInjectionType, sb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
FieldSetter getFieldSetter() {
|
FieldSetter getFieldSetter() {
|
||||||
return (FieldSetter) helpers.computeIfAbsent(FieldSetter.class, c -> {
|
return (FieldSetter) helpers.computeIfAbsent(FieldSetter.class, c -> {
|
||||||
final var fieldInjectionType = progress.request().parameters().fieldInjectionType();
|
final var fieldInjectionType = progress.request().parameters().fieldInjectionType();
|
||||||
@@ -135,6 +152,10 @@ public class HelperProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ValueClassGuesser getValueClassGuesser() {
|
||||||
|
return (ValueClassGuesser) helpers.computeIfAbsent(ValueClassGuesser.class, c -> new ValueClassGuesser(this));
|
||||||
|
}
|
||||||
|
|
||||||
VariableProvider getVariableProvider() {
|
VariableProvider getVariableProvider() {
|
||||||
return (VariableProvider) helpers.computeIfAbsent(VariableProvider.class, c -> new VariableProvider());
|
return (VariableProvider) helpers.computeIfAbsent(VariableProvider.class, c -> new VariableProvider());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ public final class LoadMethodFormatter {
|
|||||||
if (fieldInjectionType == ControllerFieldInjectionType.FACTORY) {
|
if (fieldInjectionType == ControllerFieldInjectionType.FACTORY) {
|
||||||
sb.append(generationCompatibilityHelper.getStartVar("java.util.Map<String, Object>")).append("fieldMap = new java.util.HashMap<String, Object>();\n");
|
sb.append(generationCompatibilityHelper.getStartVar("java.util.Map<String, Object>")).append("fieldMap = new java.util.HashMap<String, Object>();\n");
|
||||||
} else if (controllerInjectionType == ControllerInjectionType.FACTORY) {
|
} else if (controllerInjectionType == ControllerInjectionType.FACTORY) {
|
||||||
sb.append(" controller = controllerFactory.create();\n");
|
sb.append(" controller = controllerFactory.get();\n");
|
||||||
}
|
}
|
||||||
final var variableName = helperProvider.getVariableProvider().getNextVariableName(GenerationHelper.getVariablePrefix(rootObject));
|
final var variableName = helperProvider.getVariableProvider().getNextVariableName(GenerationHelper.getVariablePrefix(rootObject));
|
||||||
helperProvider.getObjectFormatter().format(rootObject, variableName);
|
helperProvider.getObjectFormatter().format(rootObject, variableName);
|
||||||
if (fieldInjectionType == ControllerFieldInjectionType.FACTORY) {
|
if (fieldInjectionType == ControllerFieldInjectionType.FACTORY) {
|
||||||
sb.append(" controller = controllerFactory.create(fieldMap);\n");
|
sb.append(" controller = controllerFactory.apply(fieldMap);\n");
|
||||||
progress.controllerFactoryPostAction().forEach(sb::append);
|
progress.controllerFactoryPostAction().forEach(sb::append);
|
||||||
}
|
}
|
||||||
if (request.controllerInfo().hasInitialize()) {
|
if (request.controllerInfo().hasInitialize()) {
|
||||||
|
|||||||
@@ -3,18 +3,27 @@ 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.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.*;
|
import com.github.gtache.fxml.compiler.parsing.ParsedConstant;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedCopy;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedDefine;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedFactory;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedInclude;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedReference;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedText;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedValue;
|
||||||
import com.github.gtache.fxml.compiler.parsing.impl.ParsedPropertyImpl;
|
import com.github.gtache.fxml.compiler.parsing.impl.ParsedPropertyImpl;
|
||||||
|
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.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.SequencedCollection;
|
import java.util.SequencedCollection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
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.*;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
@@ -271,19 +280,26 @@ final class ObjectFormatter {
|
|||||||
final var clazz = ReflectionHelper.getClass(parsedObject.className());
|
final var clazz = ReflectionHelper.getClass(parsedObject.className());
|
||||||
final var children = parsedObject.children();
|
final var children = parsedObject.children();
|
||||||
final var notDefinedChildren = getNotDefines(children);
|
final var notDefinedChildren = getNotDefines(children);
|
||||||
final var constructors = clazz.getConstructors();
|
final var allProperties = new HashMap<String, List<Class<?>>>();
|
||||||
final var allAttributesNames = new HashSet<>(parsedObject.attributes().keySet());
|
for (final var entry : parsedObject.attributes().entrySet()) {
|
||||||
allAttributesNames.addAll(parsedObject.properties().keySet().stream().map(ParsedProperty::name).collect(Collectors.toSet()));
|
final var possibleTypes = helperProvider.getValueClassGuesser().guess(entry.getValue().value());
|
||||||
|
allProperties.put(entry.getKey(), possibleTypes);
|
||||||
|
}
|
||||||
|
for (final var entry : parsedObject.properties().entrySet()) {
|
||||||
|
allProperties.put(entry.getKey().name(), List.of(Node.class, Node[].class));
|
||||||
|
}
|
||||||
formatDefines(children);
|
formatDefines(children);
|
||||||
if (!notDefinedChildren.isEmpty()) {
|
if (!notDefinedChildren.isEmpty()) {
|
||||||
final var defaultProperty = ReflectionHelper.getDefaultProperty(parsedObject.className());
|
final var defaultProperty = ReflectionHelper.getDefaultProperty(parsedObject.className());
|
||||||
if (defaultProperty != null) {
|
if (defaultProperty != null) {
|
||||||
allAttributesNames.add(defaultProperty);
|
allProperties.put(defaultProperty, List.of(Node.class, Node[].class));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final var constructorArgs = ConstructorHelper.getMatchingConstructorArgs(constructors, allAttributesNames);
|
final var constructors = clazz.getConstructors();
|
||||||
|
final var constructorArgs = ConstructorHelper.getMatchingConstructorArgs(constructors, allProperties);
|
||||||
if (constructorArgs == null) {
|
if (constructorArgs == null) {
|
||||||
formatNoConstructor(parsedObject, variableName, allAttributesNames);
|
logger.debug("No constructor found for {} with attributes {}", clazz.getCanonicalName(), allProperties);
|
||||||
|
formatNoConstructor(parsedObject, variableName, allProperties.keySet());
|
||||||
} else {
|
} else {
|
||||||
formatConstructor(parsedObject, variableName, constructorArgs);
|
formatConstructor(parsedObject, variableName, constructorArgs);
|
||||||
}
|
}
|
||||||
@@ -296,7 +312,7 @@ final class ObjectFormatter {
|
|||||||
final var property = parsedObject.attributes().get("fx:constant");
|
final var property = parsedObject.attributes().get("fx:constant");
|
||||||
sb.append(helperProvider.getCompatibilityHelper().getStartVar(parsedObject)).append(variableName).append(" = ").append(clazz.getCanonicalName()).append(".").append(property.value()).append(";\n");
|
sb.append(helperProvider.getCompatibilityHelper().getStartVar(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 empty constructor for " + clazz.getCanonicalName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,10 +354,9 @@ final class ObjectFormatter {
|
|||||||
* @param subNodeName The sub node name
|
* @param subNodeName The sub node name
|
||||||
*/
|
*/
|
||||||
private void formatInclude(final ParsedInclude include, final String subNodeName) throws GenerationException {
|
private void formatInclude(final ParsedInclude include, final String subNodeName) throws GenerationException {
|
||||||
final var subViewVariable = helperProvider.getVariableProvider().getNextVariableName("view");
|
|
||||||
final var viewVariable = helperProvider.getInitializationFormatter().formatSubViewConstructorCall(include);
|
final var viewVariable = helperProvider.getInitializationFormatter().formatSubViewConstructorCall(include);
|
||||||
sb.append(" final javafx.scene.Parent ").append(subNodeName).append(" = ").append(viewVariable).append(".load();\n");
|
sb.append(" final javafx.scene.Parent ").append(subNodeName).append(" = ").append(viewVariable).append(".load();\n");
|
||||||
injectSubController(include, subViewVariable);
|
injectSubController(include, viewVariable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void injectSubController(final ParsedInclude include, final String subViewVariable) {
|
private void injectSubController(final ParsedInclude include, final String subViewVariable) {
|
||||||
@@ -449,7 +464,7 @@ final class ObjectFormatter {
|
|||||||
sb.append(compatibilityHelper.getStartVar(factory.className())).append(variableName).append(" = ").append(factory.className())
|
sb.append(compatibilityHelper.getStartVar(factory.className())).append(variableName).append(" = ").append(factory.className())
|
||||||
.append(".").append(factory.factory()).append("(").append(String.join(", ", variables)).append(");\n");
|
.append(".").append(factory.factory()).append("(").append(String.join(", ", variables)).append(");\n");
|
||||||
} else {
|
} else {
|
||||||
final var returnType = ReflectionHelper.getReturnType(factory.className(), factory.factory());
|
final var returnType = ReflectionHelper.getStaticReturnType(factory.className(), factory.factory()).getName();
|
||||||
sb.append(compatibilityHelper.getStartVar(returnType)).append(variableName).append(" = ").append(factory.className())
|
sb.append(compatibilityHelper.getStartVar(returnType)).append(variableName).append(" = ").append(factory.className())
|
||||||
.append(".").append(factory.factory()).append("(").append(String.join(", ", variables)).append(");\n");
|
.append(".").append(factory.factory()).append("(").append(String.join(", ", variables)).append(");\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import com.github.gtache.fxml.compiler.parsing.impl.ParsedPropertyImpl;
|
|||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.SequencedCollection;
|
import java.util.SequencedCollection;
|
||||||
|
|
||||||
@@ -41,6 +42,10 @@ final class PropertyFormatter {
|
|||||||
* @throws GenerationException if an error occurs
|
* @throws GenerationException if an error occurs
|
||||||
*/
|
*/
|
||||||
void formatProperty(final ParsedProperty property, final ParsedObject parent, final String parentVariable) throws GenerationException {
|
void formatProperty(final ParsedProperty property, final ParsedObject parent, final String parentVariable) throws GenerationException {
|
||||||
|
final var value = property.value();
|
||||||
|
if (value.endsWith("}") && (value.startsWith(BINDING_EXPRESSION_PREFIX) || value.startsWith(BIDIRECTIONAL_BINDING_PREFIX))) {
|
||||||
|
helperProvider.getBindingFormatter().formatBinding(property, parent, parentVariable);
|
||||||
|
} else {
|
||||||
final var propertyName = property.name();
|
final var propertyName = property.name();
|
||||||
if (propertyName.equals(FX_ID)) {
|
if (propertyName.equals(FX_ID)) {
|
||||||
//Do nothing
|
//Do nothing
|
||||||
@@ -54,6 +59,7 @@ final class PropertyFormatter {
|
|||||||
handleProperty(property, parent, parentVariable);
|
handleProperty(property, parent, parentVariable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a complex property (containing a list of objects).
|
* Formats a complex property (containing a list of objects).
|
||||||
@@ -93,8 +99,8 @@ final class PropertyFormatter {
|
|||||||
private void handleStaticProperty(final ParsedProperty property, final String parentVariable, final String propertyName) throws GenerationException {
|
private void handleStaticProperty(final ParsedProperty property, final String parentVariable, final String propertyName) throws GenerationException {
|
||||||
final var setMethod = getSetMethod(propertyName);
|
final var setMethod = getSetMethod(propertyName);
|
||||||
final var propertySourceTypeClass = ReflectionHelper.getClass(property.sourceType());
|
final var propertySourceTypeClass = ReflectionHelper.getClass(property.sourceType());
|
||||||
if (ReflectionHelper.hasStaticMethod(propertySourceTypeClass, setMethod)) {
|
if (ReflectionHelper.hasStaticMethod(propertySourceTypeClass, setMethod, null, null)) {
|
||||||
final var method = ReflectionHelper.getStaticMethod(propertySourceTypeClass, setMethod);
|
final var method = ReflectionHelper.getStaticMethod(propertySourceTypeClass, setMethod, null, null);
|
||||||
final var parameterType = method.getParameterTypes()[1];
|
final var parameterType = method.getParameterTypes()[1];
|
||||||
final var arg = helperProvider.getValueFormatter().getArg(property.value(), parameterType);
|
final var arg = helperProvider.getValueFormatter().getArg(property.value(), parameterType);
|
||||||
setLaterIfNeeded(property, parameterType, " " + property.sourceType() + "." + setMethod + "(" + parentVariable + ", " + arg + ");\n");
|
setLaterIfNeeded(property, parameterType, " " + property.sourceType() + "." + setMethod + "(" + parentVariable + ", " + arg + ");\n");
|
||||||
@@ -108,7 +114,7 @@ final class PropertyFormatter {
|
|||||||
final var setMethod = getSetMethod(propertyName);
|
final var setMethod = getSetMethod(propertyName);
|
||||||
final var getMethod = getGetMethod(propertyName);
|
final var getMethod = getGetMethod(propertyName);
|
||||||
final var parentClass = ReflectionHelper.getClass(parent.className());
|
final var parentClass = ReflectionHelper.getClass(parent.className());
|
||||||
if (ReflectionHelper.hasMethod(parentClass, setMethod)) {
|
if (ReflectionHelper.hasMethod(parentClass, setMethod, (Class<?>) null)) {
|
||||||
handleSetProperty(property, parentClass, parentVariable);
|
handleSetProperty(property, parentClass, parentVariable);
|
||||||
} else if (ReflectionHelper.hasMethod(parentClass, getMethod)) {
|
} else if (ReflectionHelper.hasMethod(parentClass, getMethod)) {
|
||||||
handleGetProperty(property, parentClass, parentVariable);
|
handleGetProperty(property, parentClass, parentVariable);
|
||||||
@@ -119,7 +125,7 @@ final class PropertyFormatter {
|
|||||||
|
|
||||||
private void handleSetProperty(final ParsedProperty property, final Class<?> parentClass, final String parentVariable) throws GenerationException {
|
private void handleSetProperty(final ParsedProperty property, final Class<?> parentClass, final String parentVariable) throws GenerationException {
|
||||||
final var setMethod = getSetMethod(property.name());
|
final var setMethod = getSetMethod(property.name());
|
||||||
final var method = ReflectionHelper.getMethod(parentClass, setMethod);
|
final var method = ReflectionHelper.getMethod(parentClass, setMethod, (Class<?>) null);
|
||||||
final var parameterType = method.getParameterTypes()[0];
|
final var parameterType = method.getParameterTypes()[0];
|
||||||
final var arg = helperProvider.getValueFormatter().getArg(property.value(), parameterType);
|
final var arg = helperProvider.getValueFormatter().getArg(property.value(), parameterType);
|
||||||
setLaterIfNeeded(property, parameterType, " " + parentVariable + "." + setMethod + "(" + arg + ");\n");
|
setLaterIfNeeded(property, parameterType, " " + parentVariable + "." + setMethod + "(" + arg + ");\n");
|
||||||
@@ -129,7 +135,7 @@ final class PropertyFormatter {
|
|||||||
final var getMethod = getGetMethod(property.name());
|
final var getMethod = getGetMethod(property.name());
|
||||||
final var method = ReflectionHelper.getMethod(parentClass, getMethod);
|
final var method = ReflectionHelper.getMethod(parentClass, getMethod);
|
||||||
final var returnType = method.getReturnType();
|
final var returnType = method.getReturnType();
|
||||||
if (ReflectionHelper.hasMethod(returnType, "addAll")) {
|
if (ReflectionHelper.hasMethod(returnType, "addAll", List.class)) {
|
||||||
final var arg = helperProvider.getValueFormatter().getArg(property.value(), String.class);
|
final var arg = helperProvider.getValueFormatter().getArg(property.value(), String.class);
|
||||||
setLaterIfNeeded(property, String.class, " " + parentVariable + "." + getMethod + "().addAll(" +
|
setLaterIfNeeded(property, String.class, " " + parentVariable + "." + getMethod + "().addAll(" +
|
||||||
helperProvider.getCompatibilityHelper().getListOf() + arg + "));\n");
|
helperProvider.getCompatibilityHelper().getListOf() + arg + "));\n");
|
||||||
@@ -155,7 +161,6 @@ final class PropertyFormatter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats the children objects of a property
|
* Formats the children objects of a property
|
||||||
*
|
*
|
||||||
@@ -231,7 +236,7 @@ final class PropertyFormatter {
|
|||||||
final var getMethod = getGetMethod(property);
|
final var getMethod = 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 (ReflectionHelper.hasMethod(parentClass, setMethod)) {
|
if (ReflectionHelper.hasMethod(parentClass, setMethod, (Class<?>) null)) {
|
||||||
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 (ReflectionHelper.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
|
||||||
@@ -251,7 +256,8 @@ final class PropertyFormatter {
|
|||||||
private void formatSingleChildStatic(final String variableName,
|
private void formatSingleChildStatic(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 = getSetMethod(property);
|
||||||
if (ReflectionHelper.hasStaticMethod(ReflectionHelper.getClass(property.sourceType()), setMethod)) {
|
final var clazz = ReflectionHelper.getClass(property.sourceType());
|
||||||
|
if (ReflectionHelper.hasStaticMethod(clazz, setMethod, null, null)) {
|
||||||
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 {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ 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;
|
||||||
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;
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ 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.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -30,12 +28,13 @@ 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<String, Class<?>> classMap = new HashMap<>();
|
private static final Map<String, Class<?>> classMap = new ConcurrentHashMap<>();
|
||||||
private static final Map<Class<?>, Boolean> hasValueOf = new HashMap<>();
|
private static final Map<Class<?>, Boolean> hasValueOf = new ConcurrentHashMap<>();
|
||||||
private static final Map<Class<?>, Boolean> isGeneric = new HashMap<>();
|
private static final Map<Class<?>, Boolean> isGeneric = new ConcurrentHashMap<>();
|
||||||
private static final Map<String, String> defaultProperty = new HashMap<>();
|
private static final Map<String, String> defaultProperty = new ConcurrentHashMap<>();
|
||||||
private static final Map<Class<?>, Map<String, Method>> methods = new HashMap<>();
|
private static final Map<Class<?>, Map<MethodKey, Method>> methods = new ConcurrentHashMap<>();
|
||||||
private static final Map<Class<?>, Map<String, Method>> staticMethods = new HashMap<>();
|
private static final Map<Class<?>, Map<MethodKey, Method>> staticMethods = new ConcurrentHashMap<>();
|
||||||
|
private static final Map<Class<?>, Map<MethodKey, Class<?>>> methodsReturnType = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private static final Map<String, Class<?>> PRIMITIVE_TYPES = Map.of(
|
private static final Map<String, Class<?>> PRIMITIVE_TYPES = Map.of(
|
||||||
"boolean", boolean.class,
|
"boolean", boolean.class,
|
||||||
@@ -65,60 +64,72 @@ final class ReflectionHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given class has a method with the given name
|
* Checks if the given class has a method with the given name.
|
||||||
* The result is cached
|
* The result is cached
|
||||||
*
|
*
|
||||||
* @param clazz The class
|
* @param clazz The class
|
||||||
* @param methodName The method name
|
* @param methodName The method name
|
||||||
|
* @param parameterTypes The method parameter types (null if any object is allowed)
|
||||||
* @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 Class<?>... parameterTypes) {
|
||||||
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 methodKey = new MethodKey(methodName, Arrays.asList(parameterTypes));
|
||||||
|
final var method = methodMap.computeIfAbsent(methodKey, m -> {
|
||||||
|
try {
|
||||||
|
return computeMethod(clazz, m);
|
||||||
|
} catch (final GenerationException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
return method != null;
|
return method != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the method corresponding to the given class and name
|
* Gets the method corresponding to the given class and name.
|
||||||
* The result is cached
|
* The result is cached
|
||||||
*
|
*
|
||||||
* @param clazz The class
|
* @param clazz The class
|
||||||
* @param methodName The method name
|
* @param methodName The method name
|
||||||
|
* @param parameterTypes The method parameter types
|
||||||
* @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 Class<?>... parameterTypes) throws GenerationException {
|
||||||
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));
|
try {
|
||||||
|
final var methodKey = new MethodKey(methodName, Arrays.asList(parameterTypes));
|
||||||
|
return methodMap.computeIfAbsent(methodKey, m -> {
|
||||||
|
try {
|
||||||
|
return computeMethod(clazz, m);
|
||||||
|
} catch (final GenerationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (final RuntimeException e) {
|
||||||
|
throw (GenerationException) e.getCause();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given class has a method with the given name
|
* Checks if the given class has a method with the given name
|
||||||
*
|
*
|
||||||
* @param clazz The class
|
* @param clazz The class
|
||||||
* @param methodName The method name
|
* @param methodKey The method key
|
||||||
* @return True if the class has a method with the given name
|
* @return True if the class has a method with the given name
|
||||||
*/
|
*/
|
||||||
private static Method computeMethod(final Class<?> clazz, final String methodName) {
|
private static Method computeMethod(final Class<?> clazz, final MethodKey methodKey) throws GenerationException {
|
||||||
final var matching = Arrays.stream(clazz.getMethods()).filter(m -> {
|
return computeMethod(clazz, methodKey, false);
|
||||||
if (m.getName().equals(methodName) && !Modifier.isStatic(m.getModifiers())) {
|
}
|
||||||
final var parameterTypes = m.getParameterTypes();
|
|
||||||
return methodName.startsWith("get") ? parameterTypes.length == 0 : parameterTypes.length >= 1; //TODO not very clean
|
private static boolean typesMatch(final Class<?>[] types, final List<Class<?>> parameterTypes) {
|
||||||
} else {
|
for (var i = 0; i < types.length; i++) {
|
||||||
|
final var type = types[i];
|
||||||
|
final var parameterType = parameterTypes.get(i);
|
||||||
|
if (parameterType != null && !type.isAssignableFrom(parameterType)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}).toList();
|
|
||||||
if (matching.size() > 1) {
|
|
||||||
final var varargsFilter = matching.stream().filter(Method::isVarArgs).toList();
|
|
||||||
if (varargsFilter.size() == 1) {
|
|
||||||
return varargsFilter.getFirst();
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException("Multiple matching methods not supported yet : " + clazz + " - " + methodName);
|
|
||||||
}
|
|
||||||
} else if (matching.size() == 1) {
|
|
||||||
return matching.getFirst();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,9 +140,16 @@ final class ReflectionHelper {
|
|||||||
* @param methodName The method name
|
* @param methodName The method name
|
||||||
* @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 Class<?>... parameterTypes) {
|
||||||
final var methodMap = staticMethods.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 methodKey = new MethodKey(methodName, Arrays.asList(parameterTypes));
|
||||||
|
final var method = methodMap.computeIfAbsent(methodKey, m -> {
|
||||||
|
try {
|
||||||
|
return computeStaticMethod(clazz, m);
|
||||||
|
} catch (final GenerationException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
return method != null;
|
return method != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,33 +161,74 @@ final class ReflectionHelper {
|
|||||||
* @param methodName The method name
|
* @param methodName The method name
|
||||||
* @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 Class<?>... parameterTypes) throws GenerationException {
|
||||||
final var methodMap = staticMethods.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
|
final var methodMap = staticMethods.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
|
||||||
return methodMap.computeIfAbsent(methodName, m -> computeStaticMethod(clazz, m));
|
final var methodKey = new MethodKey(methodName, Arrays.asList(parameterTypes));
|
||||||
|
try {
|
||||||
|
return methodMap.computeIfAbsent(methodKey, m -> {
|
||||||
|
try {
|
||||||
|
return computeStaticMethod(clazz, m);
|
||||||
|
} catch (final GenerationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (final RuntimeException e) {
|
||||||
|
throw (GenerationException) e.getCause();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the static method corresponding to the given class and name
|
* Gets the static method corresponding to the given class and name
|
||||||
*
|
*
|
||||||
* @param clazz The class name
|
* @param clazz The class name
|
||||||
* @param methodName The method name
|
* @param methodKey The method name
|
||||||
* @return The method, or null if not found
|
* @return The method, or null if not found
|
||||||
*/
|
*/
|
||||||
private static Method computeStaticMethod(final Class<?> clazz, final String methodName) {
|
private static Method computeStaticMethod(final Class<?> clazz, final MethodKey methodKey) throws GenerationException {
|
||||||
|
return computeMethod(clazz, methodKey, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Method computeMethod(final Class<?> clazz, final MethodKey methodKey, final boolean isStatic) throws GenerationException {
|
||||||
|
final var parameterTypes = methodKey.parameterTypes();
|
||||||
|
if (parameterTypes.stream().allMatch(Objects::nonNull)) {
|
||||||
|
return computeExactMethod(clazz, methodKey, isStatic);
|
||||||
|
} else {
|
||||||
|
return computeInexactMethod(clazz, methodKey, isStatic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Method computeExactMethod(final Class<?> clazz, final MethodKey methodKey, final boolean isStatic) throws GenerationException {
|
||||||
|
final var parameterTypes = methodKey.parameterTypes();
|
||||||
|
final var methodName = methodKey.methodName();
|
||||||
|
try {
|
||||||
|
final var method = clazz.getMethod(methodName, parameterTypes.toArray(new Class<?>[0]));
|
||||||
|
if (isStatic == Modifier.isStatic(method.getModifiers())) {
|
||||||
|
return method;
|
||||||
|
} else {
|
||||||
|
throw new GenerationException("Method not found : " + clazz + " - " + methodKey + " (found static method)");
|
||||||
|
}
|
||||||
|
} catch (final NoSuchMethodException ignored) {
|
||||||
|
return computeInexactMethod(clazz, methodKey, isStatic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Method computeInexactMethod(final Class<?> clazz, final MethodKey methodKey, final boolean isStatic) throws GenerationException {
|
||||||
|
final var parameterTypes = methodKey.parameterTypes();
|
||||||
|
final var methodName = methodKey.methodName();
|
||||||
final var matching = Arrays.stream(clazz.getMethods()).filter(m -> {
|
final var matching = Arrays.stream(clazz.getMethods()).filter(m -> {
|
||||||
if (m.getName().equals(methodName) && Modifier.isStatic(m.getModifiers())) {
|
if (m.getName().equals(methodName) && isStatic == Modifier.isStatic(m.getModifiers())) {
|
||||||
final var parameterTypes = m.getParameterTypes();
|
final var types = m.getParameterTypes();
|
||||||
return parameterTypes.length > 1 && parameterTypes[0] == Node.class;
|
return types.length == parameterTypes.size() && typesMatch(types, parameterTypes);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}).toList();
|
}).toList();
|
||||||
if (matching.size() > 1) {
|
if (matching.size() == 1) {
|
||||||
throw new UnsupportedOperationException("Multiple matching methods not supported yet : " + clazz + " - " + methodName);
|
|
||||||
} else if (matching.size() == 1) {
|
|
||||||
return matching.getFirst();
|
return matching.getFirst();
|
||||||
|
} else if (matching.isEmpty()) {
|
||||||
|
throw new GenerationException("Method not found : " + clazz + " - " + methodKey);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
throw new GenerationException("Multiple matching methods not supported yet : " + clazz + " - " + methodKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,17 +442,50 @@ final class ReflectionHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the return type of the given method for the given class
|
* Gets the return type of the given instance method for the given class
|
||||||
*
|
*
|
||||||
* @param className The class
|
* @param className The class
|
||||||
* @param methodName The method
|
* @param methodName The method
|
||||||
|
* @param parameterTypes The method parameter types (null if any object is allowed)
|
||||||
* @return The return type
|
* @return The return type
|
||||||
* @throws GenerationException if an error occurs
|
* @throws GenerationException if an error occurs
|
||||||
*/
|
*/
|
||||||
static String getReturnType(final String className, final String methodName) throws GenerationException {
|
static Class<?> getReturnType(final String className, final String methodName, final Class<?>... parameterTypes) throws GenerationException {
|
||||||
final var clazz = getClass(className);
|
final var clazz = getClass(className);
|
||||||
final var method = Arrays.stream(clazz.getMethods()).filter(m -> m.getName().equals(methodName))
|
return getReturnType(clazz, methodName, parameterTypes, false);
|
||||||
.findFirst().orElseThrow(() -> new GenerationException("Method " + methodName + " not found in class " + className));
|
}
|
||||||
return method.getReturnType().getName();
|
|
||||||
|
/**
|
||||||
|
* Gets the return type of the given static method for the given class
|
||||||
|
*
|
||||||
|
* @param className The class
|
||||||
|
* @param methodName The method
|
||||||
|
* @param parameterTypes The method parameter types (null if any object is allowed)
|
||||||
|
* @return The return type
|
||||||
|
* @throws GenerationException if an error occurs
|
||||||
|
*/
|
||||||
|
static Class<?> getStaticReturnType(final String className, final String methodName, final Class<?>... parameterTypes) throws GenerationException {
|
||||||
|
final var clazz = getClass(className);
|
||||||
|
return getReturnType(clazz, methodName, parameterTypes, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> getReturnType(final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes, final boolean isStatic) throws GenerationException {
|
||||||
|
final var returnTypes = methodsReturnType.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
|
||||||
|
try {
|
||||||
|
final var methodKey = new MethodKey(methodName, Arrays.asList(parameterTypes));
|
||||||
|
return returnTypes.computeIfAbsent(methodKey, m -> {
|
||||||
|
try {
|
||||||
|
return isStatic ? getStaticMethod(clazz, methodName, parameterTypes).getReturnType() :
|
||||||
|
getMethod(clazz, methodName, parameterTypes).getReturnType();
|
||||||
|
} catch (final GenerationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (final RuntimeException e) {
|
||||||
|
throw (GenerationException) e.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record MethodKey(String methodName, List<Class<?>> parameterTypes) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.github.gtache.fxml.compiler.impl.internal;
|
||||||
|
|
||||||
|
import com.github.gtache.fxml.compiler.GenerationException;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guesses the class of a value
|
||||||
|
*/
|
||||||
|
class ValueClassGuesser {
|
||||||
|
private final HelperProvider helperProvider;
|
||||||
|
|
||||||
|
ValueClassGuesser(final HelperProvider helperProvider) {
|
||||||
|
this.helperProvider = Objects.requireNonNull(helperProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Class<?>> guess(final String value) throws GenerationException {
|
||||||
|
if (value.startsWith("$")) {
|
||||||
|
return getPossibleVariableTypes(value.substring(1));
|
||||||
|
} else {
|
||||||
|
return getPossibleTypes(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Class<?>> getPossibleVariableTypes(final String value) throws GenerationException {
|
||||||
|
if (value.contains(".")) {
|
||||||
|
throw new GenerationException("Unsupported variable : " + value);
|
||||||
|
} else {
|
||||||
|
final var variableInfo = helperProvider.getVariableProvider().getVariableInfo(value);
|
||||||
|
if (variableInfo == null) {
|
||||||
|
throw new GenerationException("Unknown variable : " + value);
|
||||||
|
} else {
|
||||||
|
return List.of(ReflectionHelper.getClass(variableInfo.className()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Class<?>> getPossibleTypes(final String value) {
|
||||||
|
final var ret = new ArrayList<Class<?>>();
|
||||||
|
ret.add(String.class);
|
||||||
|
ret.addAll(tryParse(value, LocalDateTime::parse, LocalDateTime.class));
|
||||||
|
ret.addAll(tryParse(value, LocalDate::parse, LocalDate.class));
|
||||||
|
ret.addAll(tryParse(value, ValueClassGuesser::parseBoolean, Boolean.class, boolean.class));
|
||||||
|
ret.addAll(tryParse(value, BigDecimal::new, BigDecimal.class));
|
||||||
|
ret.addAll(tryParse(value, Double::parseDouble, Double.class, double.class));
|
||||||
|
ret.addAll(tryParse(value, Float::parseFloat, Float.class, float.class));
|
||||||
|
ret.addAll(tryParse(value, BigInteger::new, BigInteger.class));
|
||||||
|
ret.addAll(tryParse(value, Long::parseLong, Long.class, long.class));
|
||||||
|
ret.addAll(tryParse(value, Integer::parseInt, Integer.class, int.class));
|
||||||
|
ret.addAll(tryParse(value, Short::parseShort, Short.class, short.class));
|
||||||
|
ret.addAll(tryParse(value, Byte::parseByte, Byte.class, byte.class));
|
||||||
|
return ret.reversed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean parseBoolean(final String value) {
|
||||||
|
if (!value.equals("true") && !value.equals("false")) {
|
||||||
|
throw new RuntimeException("Invalid boolean value : " + value);
|
||||||
|
} else {
|
||||||
|
return Boolean.parseBoolean(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> Collection<Class<?>> tryParse(final String value, final Function<? super String, T> parseFunction, final Class<?>... classes) {
|
||||||
|
try {
|
||||||
|
parseFunction.apply(value);
|
||||||
|
return Arrays.asList(classes);
|
||||||
|
} catch (final RuntimeException ignored) {
|
||||||
|
//Do nothing
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import com.github.gtache.fxml.compiler.GenerationException;
|
|||||||
import com.github.gtache.fxml.compiler.ResourceBundleInjectionType;
|
import com.github.gtache.fxml.compiler.ResourceBundleInjectionType;
|
||||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
|
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
|
||||||
@@ -41,7 +43,9 @@ final class ValueFormatter {
|
|||||||
final var subpath = value.substring(1);
|
final var subpath = value.substring(1);
|
||||||
return getResourceValue(subpath);
|
return getResourceValue(subpath);
|
||||||
} else if (value.startsWith(BINDING_EXPRESSION_PREFIX)) {
|
} else if (value.startsWith(BINDING_EXPRESSION_PREFIX)) {
|
||||||
throw new GenerationException("Not implemented yet");
|
throw new GenerationException("Should be handled by BindingFormatter");
|
||||||
|
} else if (value.startsWith(BIDIRECTIONAL_BINDING_PREFIX)) {
|
||||||
|
throw new GenerationException("Should be handled by BindingFormatter");
|
||||||
} else if (value.startsWith(EXPRESSION_PREFIX)) {
|
} else if (value.startsWith(EXPRESSION_PREFIX)) {
|
||||||
final var variable = helperProvider.getVariableProvider().getVariableInfo(value.substring(1));
|
final var variable = helperProvider.getVariableProvider().getVariableInfo(value.substring(1));
|
||||||
if (variable == null) {
|
if (variable == null) {
|
||||||
@@ -92,6 +96,10 @@ final class ValueFormatter {
|
|||||||
return intToString(value, clazz);
|
return intToString(value, clazz);
|
||||||
} 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) {
|
||||||
return decimalToString(value, clazz);
|
return decimalToString(value, clazz);
|
||||||
|
} else if (clazz == LocalDate.class) {
|
||||||
|
return "LocalDate.parse(\"" + value + "\")";
|
||||||
|
} else if (clazz == LocalDateTime.class) {
|
||||||
|
return "LocalDateTime.parse(\"" + value + "\")";
|
||||||
} else if (ReflectionHelper.hasValueOf(clazz)) {
|
} else if (ReflectionHelper.hasValueOf(clazz)) {
|
||||||
return valueOfToString(value, clazz);
|
return valueOfToString(value, clazz);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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;
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package com.github.gtache.fxml.compiler.impl.internal;
|
||||||
|
|
||||||
|
import com.github.gtache.fxml.compiler.ControllerFieldInjectionType;
|
||||||
|
import com.github.gtache.fxml.compiler.GenerationException;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.SequencedCollection;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class TestBindingFormatter {
|
||||||
|
|
||||||
|
private final HelperProvider helperProvider;
|
||||||
|
private final ExpressionFormatter expressionFormatter;
|
||||||
|
private final ControllerFieldInjectionType fieldInjectionType;
|
||||||
|
private final StringBuilder sb;
|
||||||
|
private final SequencedCollection<String> controllerFactoryPostAction;
|
||||||
|
private final ParsedProperty property;
|
||||||
|
private final ParsedObject parent;
|
||||||
|
private final String parentVariable;
|
||||||
|
private final BindingFormatter bindingFormatter;
|
||||||
|
|
||||||
|
TestBindingFormatter(@Mock final HelperProvider helperProvider, @Mock final ExpressionFormatter expressionFormatter,
|
||||||
|
@Mock final ControllerFieldInjectionType fieldInjectionType, @Mock final ParsedProperty property, @Mock final ParsedObject parent) {
|
||||||
|
this.helperProvider = Objects.requireNonNull(helperProvider);
|
||||||
|
this.expressionFormatter = Objects.requireNonNull(expressionFormatter);
|
||||||
|
this.fieldInjectionType = Objects.requireNonNull(fieldInjectionType);
|
||||||
|
this.property = Objects.requireNonNull(property);
|
||||||
|
this.parent = Objects.requireNonNull(parent);
|
||||||
|
this.parentVariable = "parentVariable";
|
||||||
|
this.sb = new StringBuilder();
|
||||||
|
this.controllerFactoryPostAction = new ArrayList<>();
|
||||||
|
this.bindingFormatter = new BindingFormatter(helperProvider, fieldInjectionType, sb, controllerFactoryPostAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() throws GenerationException {
|
||||||
|
when(helperProvider.getExpressionFormatter()).thenReturn(expressionFormatter);
|
||||||
|
when(expressionFormatter.format(anyString(), any())).then(i -> i.getArgument(0) + "-" + i.getArgument(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatDoesntEndValid() {
|
||||||
|
when(property.value()).thenReturn("${value");
|
||||||
|
assertThrows(GenerationException.class, () -> bindingFormatter.formatBinding(property, parent, parentVariable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatDoesntStartValid() {
|
||||||
|
when(property.value()).thenReturn("value}");
|
||||||
|
assertThrows(GenerationException.class, () -> bindingFormatter.formatBinding(property, parent, parentVariable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatSimpleNoReadProperty() {
|
||||||
|
when(property.name()).thenReturn("abc");
|
||||||
|
when(property.value()).thenReturn("${value}");
|
||||||
|
when(parent.className()).thenReturn("javafx.scene.control.Label");
|
||||||
|
assertThrows(GenerationException.class, () -> bindingFormatter.formatBinding(property, parent, parentVariable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatSimple() throws GenerationException {
|
||||||
|
when(property.name()).thenReturn("text");
|
||||||
|
when(property.value()).thenReturn("${value}");
|
||||||
|
when(parent.className()).thenReturn("javafx.scene.control.Label");
|
||||||
|
bindingFormatter.formatBinding(property, parent, parentVariable);
|
||||||
|
final var expected = """
|
||||||
|
parentVariable.textProperty().bind(${value}-class javafx.beans.property.StringProperty);
|
||||||
|
""";
|
||||||
|
assertEquals(expected, sb.toString());
|
||||||
|
assertEquals(List.of(), controllerFactoryPostAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatBidirectional() throws GenerationException {
|
||||||
|
when(property.name()).thenReturn("text");
|
||||||
|
when(property.value()).thenReturn("#{value}");
|
||||||
|
when(parent.className()).thenReturn("javafx.scene.control.Label");
|
||||||
|
bindingFormatter.formatBinding(property, parent, parentVariable);
|
||||||
|
final var expected = """
|
||||||
|
parentVariable.textProperty().bindBidirectional(#{value}-class javafx.beans.property.StringProperty);
|
||||||
|
""";
|
||||||
|
assertEquals(expected, sb.toString());
|
||||||
|
assertEquals(List.of(), controllerFactoryPostAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatSimpleControllerFactory() throws GenerationException {
|
||||||
|
final var factoryFormatter = new BindingFormatter(helperProvider, ControllerFieldInjectionType.FACTORY, sb, controllerFactoryPostAction);
|
||||||
|
when(property.name()).thenReturn("text");
|
||||||
|
when(property.value()).thenReturn("${controller.value}");
|
||||||
|
when(parent.className()).thenReturn("javafx.scene.control.Label");
|
||||||
|
factoryFormatter.formatBinding(property, parent, parentVariable);
|
||||||
|
final var expected = """
|
||||||
|
parentVariable.textProperty().bind(${controller.value}-class javafx.beans.property.StringProperty);
|
||||||
|
""";
|
||||||
|
assertEquals("", sb.toString());
|
||||||
|
assertEquals(List.of(expected), controllerFactoryPostAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatBidirectionalNoWriteProperty() {
|
||||||
|
when(property.name()).thenReturn("labelPadding");
|
||||||
|
when(property.value()).thenReturn("#{value}");
|
||||||
|
when(parent.className()).thenReturn("javafx.scene.control.Label");
|
||||||
|
assertThrows(GenerationException.class, () -> bindingFormatter.formatBinding(property, parent, parentVariable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIllegal() {
|
||||||
|
assertThrows(NullPointerException.class, () -> new BindingFormatter(null, fieldInjectionType, sb, controllerFactoryPostAction));
|
||||||
|
assertThrows(NullPointerException.class, () -> new BindingFormatter(helperProvider, null, sb, controllerFactoryPostAction));
|
||||||
|
assertThrows(NullPointerException.class, () -> new BindingFormatter(helperProvider, fieldInjectionType, null, controllerFactoryPostAction));
|
||||||
|
assertThrows(NullPointerException.class, () -> new BindingFormatter(helperProvider, fieldInjectionType, sb, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ 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 com.github.gtache.fxml.compiler.parsing.impl.ParsedPropertyImpl;
|
import com.github.gtache.fxml.compiler.parsing.impl.ParsedPropertyImpl;
|
||||||
import javafx.beans.NamedArg;
|
import javafx.beans.NamedArg;
|
||||||
|
import javafx.scene.control.Spinner;
|
||||||
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;
|
||||||
@@ -12,6 +13,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -89,7 +91,93 @@ class TestConstructorHelper {
|
|||||||
new NamedArgImpl("p2", "value2")}});
|
new NamedArgImpl("p2", "value2")}});
|
||||||
when(constructors[1].getParameterTypes()).thenReturn(new Class[]{int.class, String.class});
|
when(constructors[1].getParameterTypes()).thenReturn(new Class[]{int.class, String.class});
|
||||||
final var expectedArgs = new ConstructorArgs(constructors[1], namedArgs);
|
final var expectedArgs = new ConstructorArgs(constructors[1], namedArgs);
|
||||||
assertEquals(expectedArgs, ConstructorHelper.getMatchingConstructorArgs(constructors, propertyNames));
|
assertEquals(expectedArgs, ConstructorHelper.getMatchingConstructorArgs(constructors, Map.of("p1", List.of(double.class, int.class), "p2", List.of(String.class))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetMatchingConstructorArgsSpinnerIntFull() throws NoSuchMethodException {
|
||||||
|
final var spinnerProperties = Map.of("min", List.<Class<?>>of(int.class, double.class, LocalDate.class),
|
||||||
|
"max", List.<Class<?>>of(int.class, double.class, LocalDate.class), "initialValue",
|
||||||
|
List.<Class<?>>of(int.class, double.class, LocalDate.class), "amountToStepBy", List.<Class<?>>of(int.class, double.class, LocalDate.class));
|
||||||
|
final var namedArgs = new LinkedHashMap<String, Parameter>();
|
||||||
|
namedArgs.put("min", new Parameter("min", int.class, "0"));
|
||||||
|
namedArgs.put("max", new Parameter("max", int.class, "0"));
|
||||||
|
namedArgs.put("initialValue", new Parameter("initialValue", int.class, "0"));
|
||||||
|
namedArgs.put("amountToStepBy", new Parameter("amountToStepBy", int.class, "0"));
|
||||||
|
final var spinnerConstructors = Spinner.class.getConstructors();
|
||||||
|
final var constructor = Spinner.class.getConstructor(int.class, int.class, int.class, int.class);
|
||||||
|
final var expectedArgs = new ConstructorArgs(constructor, namedArgs);
|
||||||
|
assertEquals(expectedArgs, ConstructorHelper.getMatchingConstructorArgs(spinnerConstructors, spinnerProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetMatchingConstructorArgsSpinnerMismatch() throws NoSuchMethodException {
|
||||||
|
final var spinnerProperties = Map.of("min", List.<Class<?>>of(double.class),
|
||||||
|
"max", List.<Class<?>>of(int.class), "initialValue",
|
||||||
|
List.<Class<?>>of(double.class, LocalDate.class));
|
||||||
|
final var spinnerConstructors = Spinner.class.getConstructors();
|
||||||
|
final var constructor = Spinner.class.getConstructor();
|
||||||
|
final var expectedArgs = new ConstructorArgs(constructor, new LinkedHashMap<>());
|
||||||
|
assertEquals(expectedArgs, ConstructorHelper.getMatchingConstructorArgs(spinnerConstructors, spinnerProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetMatchingConstructorArgsSpinnerDouble2() throws NoSuchMethodException {
|
||||||
|
final var spinnerProperties = Map.of("min", List.<Class<?>>of(int.class, double.class, LocalDate.class),
|
||||||
|
"max", List.<Class<?>>of(int.class, double.class), "initialValue",
|
||||||
|
List.<Class<?>>of(double.class, LocalDate.class));
|
||||||
|
final var namedArgs = new LinkedHashMap<String, Parameter>();
|
||||||
|
namedArgs.put("min", new Parameter("min", double.class, "0"));
|
||||||
|
namedArgs.put("max", new Parameter("max", double.class, "0"));
|
||||||
|
namedArgs.put("initialValue", new Parameter("initialValue", double.class, "0"));
|
||||||
|
final var spinnerConstructors = Spinner.class.getConstructors();
|
||||||
|
final var constructor = Spinner.class.getConstructor(double.class, double.class, double.class);
|
||||||
|
final var expectedArgs = new ConstructorArgs(constructor, namedArgs);
|
||||||
|
assertEquals(expectedArgs, ConstructorHelper.getMatchingConstructorArgs(spinnerConstructors, spinnerProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetMatchingConstructorArgsSpinnerInt() throws NoSuchMethodException {
|
||||||
|
final var spinnerProperties = Map.of("min", List.<Class<?>>of(int.class, double.class, LocalDate.class),
|
||||||
|
"max", List.<Class<?>>of(int.class, double.class, LocalDate.class), "initialValue",
|
||||||
|
List.<Class<?>>of(int.class, double.class, LocalDate.class));
|
||||||
|
final var namedArgs = new LinkedHashMap<String, Parameter>();
|
||||||
|
namedArgs.put("min", new Parameter("min", int.class, "0"));
|
||||||
|
namedArgs.put("max", new Parameter("max", int.class, "0"));
|
||||||
|
namedArgs.put("initialValue", new Parameter("initialValue", int.class, "0"));
|
||||||
|
final var spinnerConstructors = Spinner.class.getConstructors();
|
||||||
|
final var constructor = Spinner.class.getConstructor(int.class, int.class, int.class);
|
||||||
|
final var expectedArgs = new ConstructorArgs(constructor, namedArgs);
|
||||||
|
assertEquals(expectedArgs, ConstructorHelper.getMatchingConstructorArgs(spinnerConstructors, spinnerProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetMatchingConstructorArgsSpinnerDouble() throws NoSuchMethodException {
|
||||||
|
final var spinnerProperties = Map.of("min", List.<Class<?>>of(double.class, LocalDate.class),
|
||||||
|
"max", List.<Class<?>>of(double.class, LocalDate.class), "initialValue",
|
||||||
|
List.<Class<?>>of(double.class, LocalDate.class));
|
||||||
|
final var namedArgs = new LinkedHashMap<String, Parameter>();
|
||||||
|
namedArgs.put("min", new Parameter("min", double.class, "0"));
|
||||||
|
namedArgs.put("max", new Parameter("max", double.class, "0"));
|
||||||
|
namedArgs.put("initialValue", new Parameter("initialValue", double.class, "0"));
|
||||||
|
final var spinnerConstructors = Spinner.class.getConstructors();
|
||||||
|
final var constructor = Spinner.class.getConstructor(double.class, double.class, double.class);
|
||||||
|
final var expectedArgs = new ConstructorArgs(constructor, namedArgs);
|
||||||
|
assertEquals(expectedArgs, ConstructorHelper.getMatchingConstructorArgs(spinnerConstructors, spinnerProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetMatchingConstructorArgsSpinnerPartial() throws NoSuchMethodException {
|
||||||
|
final var spinnerProperties = Map.of("min", List.<Class<?>>of(int.class, double.class, LocalDate.class));
|
||||||
|
final var namedArgs = new LinkedHashMap<String, Parameter>();
|
||||||
|
namedArgs.put("min", new Parameter("min", int.class, "0"));
|
||||||
|
namedArgs.put("max", new Parameter("max", int.class, "0"));
|
||||||
|
namedArgs.put("initialValue", new Parameter("initialValue", int.class, "0"));
|
||||||
|
final var spinnerConstructors = Spinner.class.getConstructors();
|
||||||
|
final var constructor = Spinner.class.getConstructor(int.class, int.class, int.class);
|
||||||
|
final var expectedArgs = new ConstructorArgs(constructor, namedArgs);
|
||||||
|
assertEquals(expectedArgs, ConstructorHelper.getMatchingConstructorArgs(spinnerConstructors, spinnerProperties));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -101,7 +189,7 @@ class TestConstructorHelper {
|
|||||||
when(constructors[0].getParameterCount()).thenReturn(0);
|
when(constructors[0].getParameterCount()).thenReturn(0);
|
||||||
when(constructors[1].getParameterCount()).thenReturn(1);
|
when(constructors[1].getParameterCount()).thenReturn(1);
|
||||||
final var expectedArgs = new ConstructorArgs(constructors[0], namedArgs);
|
final var expectedArgs = new ConstructorArgs(constructors[0], namedArgs);
|
||||||
assertEquals(expectedArgs, ConstructorHelper.getMatchingConstructorArgs(constructors, propertyNames));
|
assertEquals(expectedArgs, ConstructorHelper.getMatchingConstructorArgs(constructors, Map.of("p1", List.of(int.class), "p2", List.of(int.class))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -110,10 +198,9 @@ class TestConstructorHelper {
|
|||||||
when(constructors[1].getParameterAnnotations()).thenReturn(EMPTY_ANNOTATIONS);
|
when(constructors[1].getParameterAnnotations()).thenReturn(EMPTY_ANNOTATIONS);
|
||||||
when(constructors[0].getParameterCount()).thenReturn(1);
|
when(constructors[0].getParameterCount()).thenReturn(1);
|
||||||
when(constructors[1].getParameterCount()).thenReturn(1);
|
when(constructors[1].getParameterCount()).thenReturn(1);
|
||||||
assertNull(ConstructorHelper.getMatchingConstructorArgs(constructors, propertyNames));
|
assertNull(ConstructorHelper.getMatchingConstructorArgs(constructors, Map.of("p1", List.of(int.class), "p2", List.of(int.class))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private record NamedArgImpl(String value, String defaultValue) implements NamedArg {
|
private record NamedArgImpl(String value, String defaultValue) implements NamedArg {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package com.github.gtache.fxml.compiler.impl.internal;
|
||||||
|
|
||||||
|
import com.github.gtache.fxml.compiler.ControllerFieldInjectionType;
|
||||||
|
import com.github.gtache.fxml.compiler.GenerationException;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
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;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class TestExpressionFormatter {
|
||||||
|
|
||||||
|
private final HelperProvider helperProvider;
|
||||||
|
private final GenerationCompatibilityHelper compatibilityHelper;
|
||||||
|
private final VariableProvider variableProvider;
|
||||||
|
private final VariableInfo variableInfo;
|
||||||
|
private final ControllerFieldInjectionType fieldInjectionType;
|
||||||
|
private final StringBuilder sb;
|
||||||
|
private final ExpressionFormatter formatter;
|
||||||
|
|
||||||
|
TestExpressionFormatter(@Mock final HelperProvider helperProvider, @Mock final GenerationCompatibilityHelper compatibilityHelper,
|
||||||
|
@Mock final VariableProvider variableProvider, @Mock final ControllerFieldInjectionType fieldInjectionType,
|
||||||
|
@Mock final VariableInfo variableInfo) {
|
||||||
|
this.helperProvider = Objects.requireNonNull(helperProvider);
|
||||||
|
this.compatibilityHelper = Objects.requireNonNull(compatibilityHelper);
|
||||||
|
this.variableProvider = Objects.requireNonNull(variableProvider);
|
||||||
|
this.fieldInjectionType = Objects.requireNonNull(fieldInjectionType);
|
||||||
|
this.variableInfo = Objects.requireNonNull(variableInfo);
|
||||||
|
this.sb = new StringBuilder();
|
||||||
|
this.formatter = new ExpressionFormatter(helperProvider, fieldInjectionType, sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() {
|
||||||
|
when(helperProvider.getCompatibilityHelper()).thenReturn(compatibilityHelper);
|
||||||
|
when(helperProvider.getVariableProvider()).thenReturn(variableProvider);
|
||||||
|
when(variableProvider.getVariableInfo(anyString())).thenReturn(variableInfo);
|
||||||
|
when(variableProvider.getNextVariableName(anyString())).then(i -> i.getArgument(0));
|
||||||
|
when(compatibilityHelper.getStartVar(anyString())).then(i -> i.getArgument(0));
|
||||||
|
when(compatibilityHelper.getStartVar(anyString(), anyInt())).then(i -> i.getArgument(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatNonDotNoInfo() {
|
||||||
|
when(variableProvider.getVariableInfo(anyString())).thenReturn(null);
|
||||||
|
assertThrows(GenerationException.class, () -> formatter.format("${value}", StringProperty.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatNonDot() throws GenerationException {
|
||||||
|
when(variableInfo.variableName()).thenReturn("variableName");
|
||||||
|
assertEquals("variableName", formatter.format("${value}", StringProperty.class));
|
||||||
|
assertEquals("", sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {"${controller.}", "${.value}", "${controller.value.value}"})
|
||||||
|
void testGetDotExpressionBadSplit(final String expression) {
|
||||||
|
assertThrows(GenerationException.class, () -> formatter.format(expression, StringProperty.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatNonControllerNoInfo() {
|
||||||
|
when(variableProvider.getVariableInfo(anyString())).thenReturn(null);
|
||||||
|
assertThrows(GenerationException.class, () -> formatter.format("${other.text}", StringProperty.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatNonControllerCantRead() {
|
||||||
|
when(variableInfo.className()).thenReturn("javafx.scene.control.ComboBox");
|
||||||
|
assertThrows(GenerationException.class, () -> formatter.format("${other.text}", StringProperty.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatNonController() throws GenerationException {
|
||||||
|
when(variableInfo.variableName()).thenReturn("variableName");
|
||||||
|
when(variableInfo.className()).thenReturn("javafx.scene.control.TextField");
|
||||||
|
formatter.format("${other.text}", StringProperty.class);
|
||||||
|
assertEquals("variableName.textProperty()", formatter.format("${other.text}", StringProperty.class));
|
||||||
|
assertEquals("", sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatControllerReflection() throws GenerationException {
|
||||||
|
final var reflectionFormatter = new ExpressionFormatter(helperProvider, ControllerFieldInjectionType.REFLECTION, sb);
|
||||||
|
final var expected = """
|
||||||
|
javafx.beans.property.StringPropertybinding;
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Fieldfield = controller.getClass().getDeclaredField("text");
|
||||||
|
field.setAccessible(true);
|
||||||
|
binding = (javafx.beans.property.StringProperty) field.get(controller);
|
||||||
|
} catch (final NoSuchFieldException | IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
assertEquals("binding", reflectionFormatter.format("${controller.text}", StringProperty.class));
|
||||||
|
assertEquals(expected, sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatControllerSetters() throws GenerationException {
|
||||||
|
final var settersFormatter = new ExpressionFormatter(helperProvider, ControllerFieldInjectionType.SETTERS, sb);
|
||||||
|
assertEquals("controller.textProperty()", settersFormatter.format("${controller.text}", StringProperty.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatControllerFactory() throws GenerationException {
|
||||||
|
final var factoryFormatter = new ExpressionFormatter(helperProvider, ControllerFieldInjectionType.FACTORY, sb);
|
||||||
|
assertEquals("controller.textProperty()", factoryFormatter.format("${controller.text}", StringProperty.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatControllerAssign() throws GenerationException {
|
||||||
|
final var assignFormatter = new ExpressionFormatter(helperProvider, ControllerFieldInjectionType.ASSIGN, sb);
|
||||||
|
assertEquals("controller.text", assignFormatter.format("${controller.text}", StringProperty.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIllegal() {
|
||||||
|
assertThrows(NullPointerException.class, () -> new ExpressionFormatter(null, fieldInjectionType, sb));
|
||||||
|
assertThrows(NullPointerException.class, () -> new ExpressionFormatter(helperProvider, null, sb));
|
||||||
|
assertThrows(NullPointerException.class, () -> new ExpressionFormatter(helperProvider, fieldInjectionType, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,7 +58,17 @@ class TestGenerationHelper {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetGetMethod() {
|
void testGetGetMethod() {
|
||||||
assertEquals("getSomething", GenerationHelper.getGetMethod("Something"));
|
assertEquals("getSomething", GenerationHelper.getGetMethod("something"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetIsMethodProperty() {
|
||||||
|
assertEquals("isProperty", GenerationHelper.getIsMethod(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetIsMethod() {
|
||||||
|
assertEquals("isSomething", GenerationHelper.getIsMethod("something"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -68,7 +78,7 @@ class TestGenerationHelper {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetSetMethod() {
|
void testGetSetMethod() {
|
||||||
assertEquals("setSomething", GenerationHelper.getSetMethod("Something"));
|
assertEquals("setSomething", GenerationHelper.getSetMethod("something"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -65,12 +65,24 @@ class TestHelperProvider {
|
|||||||
this.helperProvider = new HelperProvider(progress);
|
this.helperProvider = new HelperProvider(progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetBindingFormatter() {
|
||||||
|
final var bindingFormatter = helperProvider.getBindingFormatter();
|
||||||
|
assertSame(bindingFormatter, helperProvider.getBindingFormatter());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testControllerInjector() {
|
void testControllerInjector() {
|
||||||
final var injector = helperProvider.getControllerInjector();
|
final var injector = helperProvider.getControllerInjector();
|
||||||
assertSame(injector, helperProvider.getControllerInjector());
|
assertSame(injector, helperProvider.getControllerInjector());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetExpressionFormatter() {
|
||||||
|
final var expressionFormatter = helperProvider.getExpressionFormatter();
|
||||||
|
assertSame(expressionFormatter, helperProvider.getExpressionFormatter());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetFieldSetter() {
|
void testGetFieldSetter() {
|
||||||
final var fieldSetter = helperProvider.getFieldSetter();
|
final var fieldSetter = helperProvider.getFieldSetter();
|
||||||
@@ -155,6 +167,12 @@ class TestHelperProvider {
|
|||||||
assertSame(valueFormatter, helperProvider.getValueFormatter());
|
assertSame(valueFormatter, helperProvider.getValueFormatter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetValueClassGuesser() {
|
||||||
|
final var valueClassGuesser = helperProvider.getValueClassGuesser();
|
||||||
|
assertSame(valueClassGuesser, helperProvider.getValueClassGuesser());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetVariableProvider() {
|
void testGetVariableProvider() {
|
||||||
final var variableProvider = helperProvider.getVariableProvider();
|
final var variableProvider = helperProvider.getVariableProvider();
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class TestLoadMethodFormatter {
|
|||||||
throw new IllegalStateException("Already loaded");
|
throw new IllegalStateException("Already loaded");
|
||||||
}
|
}
|
||||||
java.util.ResourceBundleresourceBundle = java.util.ResourceBundle.getBundle(resourceBundleName);
|
java.util.ResourceBundleresourceBundle = java.util.ResourceBundle.getBundle(resourceBundleName);
|
||||||
controller = controllerFactory.create();
|
controller = controllerFactory.get();
|
||||||
object-class controller.initialize();
|
object-class controller.initialize();
|
||||||
loaded = true;
|
loaded = true;
|
||||||
return (T) class;
|
return (T) class;
|
||||||
@@ -150,7 +150,7 @@ class TestLoadMethodFormatter {
|
|||||||
}
|
}
|
||||||
java.util.ResourceBundleresourceBundle = java.util.ResourceBundle.getBundle("bundle");
|
java.util.ResourceBundleresourceBundle = java.util.ResourceBundle.getBundle("bundle");
|
||||||
java.util.Map<String, Object>fieldMap = new java.util.HashMap<String, Object>();
|
java.util.Map<String, Object>fieldMap = new java.util.HashMap<String, Object>();
|
||||||
object-class controller = controllerFactory.create(fieldMap);
|
object-class controller = controllerFactory.apply(fieldMap);
|
||||||
try {
|
try {
|
||||||
java.lang.reflect.Methodinitialize = controller.getClass().getDeclaredMethod("initialize");
|
java.lang.reflect.Methodinitialize = controller.getClass().getDeclaredMethod("initialize");
|
||||||
initialize.setAccessible(true);
|
initialize.setAccessible(true);
|
||||||
@@ -185,7 +185,7 @@ class TestLoadMethodFormatter {
|
|||||||
throw new IllegalStateException("Already loaded");
|
throw new IllegalStateException("Already loaded");
|
||||||
}
|
}
|
||||||
java.util.Map<String, Object>fieldMap = new java.util.HashMap<String, Object>();
|
java.util.Map<String, Object>fieldMap = new java.util.HashMap<String, Object>();
|
||||||
object-class controller = controllerFactory.create(fieldMap);
|
object-class controller = controllerFactory.apply(fieldMap);
|
||||||
loaded = true;
|
loaded = true;
|
||||||
return (T) class;
|
return (T) class;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class TestObjectFormatter {
|
|||||||
private final GenerationCompatibilityHelper compatibilityHelper;
|
private final GenerationCompatibilityHelper compatibilityHelper;
|
||||||
private final InitializationFormatter initializationFormatter;
|
private final InitializationFormatter initializationFormatter;
|
||||||
private final ReflectionHelper reflectionHelper;
|
private final ReflectionHelper reflectionHelper;
|
||||||
|
private final ValueClassGuesser valueClassGuesser;
|
||||||
private final VariableProvider variableProvider;
|
private final VariableProvider variableProvider;
|
||||||
private final GenerationRequest request;
|
private final GenerationRequest request;
|
||||||
private final ControllerInfo controllerInfo;
|
private final ControllerInfo controllerInfo;
|
||||||
@@ -49,7 +50,7 @@ class TestObjectFormatter {
|
|||||||
|
|
||||||
TestObjectFormatter(@Mock final HelperProvider helperProvider, @Mock final GenerationCompatibilityHelper compatibilityHelper,
|
TestObjectFormatter(@Mock final HelperProvider helperProvider, @Mock final GenerationCompatibilityHelper compatibilityHelper,
|
||||||
@Mock final InitializationFormatter initializationFormatter, @Mock final ReflectionHelper reflectionHelper,
|
@Mock final InitializationFormatter initializationFormatter, @Mock final ReflectionHelper reflectionHelper,
|
||||||
@Mock final VariableProvider variableProvider, @Mock final GenerationRequest request,
|
@Mock final VariableProvider variableProvider, @Mock final ValueClassGuesser valueClassGuesser, @Mock final GenerationRequest request,
|
||||||
@Mock final ControllerInfo controllerInfo, @Mock final ControllerInjector controllerInjector,
|
@Mock final ControllerInfo controllerInfo, @Mock final ControllerInjector controllerInjector,
|
||||||
@Mock final SourceInfo sourceInfo) {
|
@Mock final SourceInfo sourceInfo) {
|
||||||
this.helperProvider = Objects.requireNonNull(helperProvider);
|
this.helperProvider = Objects.requireNonNull(helperProvider);
|
||||||
@@ -57,6 +58,7 @@ class TestObjectFormatter {
|
|||||||
this.compatibilityHelper = Objects.requireNonNull(compatibilityHelper);
|
this.compatibilityHelper = Objects.requireNonNull(compatibilityHelper);
|
||||||
this.initializationFormatter = Objects.requireNonNull(initializationFormatter);
|
this.initializationFormatter = Objects.requireNonNull(initializationFormatter);
|
||||||
this.reflectionHelper = Objects.requireNonNull(reflectionHelper);
|
this.reflectionHelper = Objects.requireNonNull(reflectionHelper);
|
||||||
|
this.valueClassGuesser = Objects.requireNonNull(valueClassGuesser);
|
||||||
this.variableProvider = Objects.requireNonNull(variableProvider);
|
this.variableProvider = Objects.requireNonNull(variableProvider);
|
||||||
this.request = Objects.requireNonNull(request);
|
this.request = Objects.requireNonNull(request);
|
||||||
this.controllerInfo = Objects.requireNonNull(controllerInfo);
|
this.controllerInfo = Objects.requireNonNull(controllerInfo);
|
||||||
@@ -72,6 +74,7 @@ class TestObjectFormatter {
|
|||||||
when(helperProvider.getControllerInjector()).thenReturn(controllerInjector);
|
when(helperProvider.getControllerInjector()).thenReturn(controllerInjector);
|
||||||
when(helperProvider.getInitializationFormatter()).thenReturn(initializationFormatter);
|
when(helperProvider.getInitializationFormatter()).thenReturn(initializationFormatter);
|
||||||
when(helperProvider.getReflectionHelper()).thenReturn(reflectionHelper);
|
when(helperProvider.getReflectionHelper()).thenReturn(reflectionHelper);
|
||||||
|
when(helperProvider.getValueClassGuesser()).thenReturn(valueClassGuesser);
|
||||||
when(helperProvider.getVariableProvider()).thenReturn(variableProvider);
|
when(helperProvider.getVariableProvider()).thenReturn(variableProvider);
|
||||||
when(compatibilityHelper.getStartVar(anyString())).then(i -> i.getArgument(0));
|
when(compatibilityHelper.getStartVar(anyString())).then(i -> i.getArgument(0));
|
||||||
when(compatibilityHelper.getStartVar(anyString(), anyInt())).then(i -> i.getArgument(0));
|
when(compatibilityHelper.getStartVar(anyString(), anyInt())).then(i -> i.getArgument(0));
|
||||||
@@ -563,6 +566,10 @@ class TestObjectFormatter {
|
|||||||
@Test
|
@Test
|
||||||
void testFormatConstructorNamedArgs(@Mock final PropertyFormatter propertyFormatter) throws GenerationException {
|
void testFormatConstructorNamedArgs(@Mock final PropertyFormatter propertyFormatter) throws GenerationException {
|
||||||
when(helperProvider.getPropertyFormatter()).thenReturn(propertyFormatter);
|
when(helperProvider.getPropertyFormatter()).thenReturn(propertyFormatter);
|
||||||
|
when(valueClassGuesser.guess("1")).thenReturn(List.of(int.class));
|
||||||
|
when(valueClassGuesser.guess("2")).thenReturn(List.of(int.class));
|
||||||
|
when(valueClassGuesser.guess("3")).thenReturn(List.of(int.class));
|
||||||
|
when(valueClassGuesser.guess("false")).thenReturn(List.of(boolean.class));
|
||||||
doAnswer(i -> sb.append("property")).when(propertyFormatter).formatProperty(any(ParsedProperty.class), any(), any());
|
doAnswer(i -> sb.append("property")).when(propertyFormatter).formatProperty(any(ParsedProperty.class), any(), any());
|
||||||
final var className = "javafx.scene.control.Spinner";
|
final var className = "javafx.scene.control.Spinner";
|
||||||
final var attributes = Map.<String, ParsedProperty>of("min", new ParsedPropertyImpl("min", null, "1"),
|
final var attributes = Map.<String, ParsedProperty>of("min", new ParsedPropertyImpl("min", null, "1"),
|
||||||
@@ -585,6 +592,21 @@ class TestObjectFormatter {
|
|||||||
verify(reflectionHelper).getGenericTypes(parsedObject);
|
verify(reflectionHelper).getGenericTypes(parsedObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatConstructorNamedArgsPartial(@Mock final PropertyFormatter propertyFormatter) throws GenerationException {
|
||||||
|
when(helperProvider.getPropertyFormatter()).thenReturn(propertyFormatter);
|
||||||
|
when(valueClassGuesser.guess("2")).thenReturn(List.of(double.class));
|
||||||
|
final var className = "javafx.geometry.Insets";
|
||||||
|
final var attributes = Map.<String, ParsedProperty>of("left", new ParsedPropertyImpl("left", null, "2"));
|
||||||
|
final var properties = new LinkedHashMap<ParsedProperty, SequencedCollection<ParsedObject>>();
|
||||||
|
final var parsedObject = new ParsedObjectImpl(className, attributes, properties, List.of());
|
||||||
|
when(reflectionHelper.getGenericTypes(parsedObject)).thenReturn("");
|
||||||
|
objectFormatter.format(parsedObject, variableName);
|
||||||
|
final var expected = "startVarvariable = new javafx.geometry.Insets(0, 0, 0, 2);\n";
|
||||||
|
assertEquals(expected, sb.toString());
|
||||||
|
verify(compatibilityHelper).getStartVar(parsedObject);
|
||||||
|
verify(reflectionHelper).getGenericTypes(parsedObject);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFormatConstructorDefault(@Mock final PropertyFormatter propertyFormatter) throws GenerationException {
|
void testFormatConstructorDefault(@Mock final PropertyFormatter propertyFormatter) throws GenerationException {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import static org.mockito.Mockito.*;
|
|||||||
class TestPropertyFormatter {
|
class TestPropertyFormatter {
|
||||||
|
|
||||||
private final HelperProvider helperProvider;
|
private final HelperProvider helperProvider;
|
||||||
|
private final BindingFormatter bindingFormatter;
|
||||||
private final VariableProvider variableProvider;
|
private final VariableProvider variableProvider;
|
||||||
private final GenerationCompatibilityHelper compatibilityHelper;
|
private final GenerationCompatibilityHelper compatibilityHelper;
|
||||||
private final ControllerInjector controllerInjector;
|
private final ControllerInjector controllerInjector;
|
||||||
@@ -49,12 +50,13 @@ class TestPropertyFormatter {
|
|||||||
private final List<String> controllerFactoryPostAction;
|
private final List<String> controllerFactoryPostAction;
|
||||||
private final PropertyFormatter propertyFormatter;
|
private final PropertyFormatter propertyFormatter;
|
||||||
|
|
||||||
TestPropertyFormatter(@Mock final HelperProvider helperProvider, @Mock final VariableProvider variableProvider,
|
TestPropertyFormatter(@Mock final HelperProvider helperProvider, @Mock final BindingFormatter bindingFormatter, @Mock final VariableProvider variableProvider,
|
||||||
@Mock final GenerationCompatibilityHelper compatibilityHelper, @Mock final ControllerInjector controllerInjector,
|
@Mock final GenerationCompatibilityHelper compatibilityHelper, @Mock final ControllerInjector controllerInjector,
|
||||||
@Mock final FieldSetter fieldSetter, @Mock final ValueFormatter valueFormatter, @Mock final GenerationProgress progress,
|
@Mock final FieldSetter fieldSetter, @Mock final ValueFormatter valueFormatter, @Mock final GenerationProgress progress,
|
||||||
@Mock final GenerationRequest request, @Mock final GenerationParameters parameters,
|
@Mock final GenerationRequest request, @Mock final GenerationParameters parameters,
|
||||||
@Mock final ParsedObject rootObject, @Mock final ParsedProperty property) {
|
@Mock final ParsedObject rootObject, @Mock final ParsedProperty property) {
|
||||||
this.helperProvider = Objects.requireNonNull(helperProvider);
|
this.helperProvider = Objects.requireNonNull(helperProvider);
|
||||||
|
this.bindingFormatter = Objects.requireNonNull(bindingFormatter);
|
||||||
this.variableProvider = Objects.requireNonNull(variableProvider);
|
this.variableProvider = Objects.requireNonNull(variableProvider);
|
||||||
this.compatibilityHelper = Objects.requireNonNull(compatibilityHelper);
|
this.compatibilityHelper = Objects.requireNonNull(compatibilityHelper);
|
||||||
this.controllerInjector = Objects.requireNonNull(controllerInjector);
|
this.controllerInjector = Objects.requireNonNull(controllerInjector);
|
||||||
@@ -73,6 +75,7 @@ class TestPropertyFormatter {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void beforeEach() throws GenerationException {
|
void beforeEach() throws GenerationException {
|
||||||
|
when(helperProvider.getBindingFormatter()).thenReturn(bindingFormatter);
|
||||||
when(helperProvider.getCompatibilityHelper()).thenReturn(compatibilityHelper);
|
when(helperProvider.getCompatibilityHelper()).thenReturn(compatibilityHelper);
|
||||||
when(helperProvider.getControllerInjector()).thenReturn(controllerInjector);
|
when(helperProvider.getControllerInjector()).thenReturn(controllerInjector);
|
||||||
when(helperProvider.getFieldSetter()).thenReturn(fieldSetter);
|
when(helperProvider.getFieldSetter()).thenReturn(fieldSetter);
|
||||||
@@ -86,13 +89,35 @@ class TestPropertyFormatter {
|
|||||||
when(progress.controllerFactoryPostAction()).thenReturn(controllerFactoryPostAction);
|
when(progress.controllerFactoryPostAction()).thenReturn(controllerFactoryPostAction);
|
||||||
when(compatibilityHelper.getListOf()).thenReturn("listof(");
|
when(compatibilityHelper.getListOf()).thenReturn("listof(");
|
||||||
when(valueFormatter.getArg(anyString(), any())).then(i -> i.getArgument(0) + "-" + i.getArgument(1));
|
when(valueFormatter.getArg(anyString(), any())).then(i -> i.getArgument(0) + "-" + i.getArgument(1));
|
||||||
|
doAnswer(i -> sb.append(i.getArgument(0) + "-" + i.getArgument(2))).when(bindingFormatter).formatBinding(any(), any(), anyString());
|
||||||
doAnswer(i -> sb.append(i.getArgument(0) + "-" + i.getArgument(1))).when(controllerInjector).injectEventHandlerControllerMethod(any(), anyString());
|
doAnswer(i -> sb.append(i.getArgument(0) + "-" + i.getArgument(1))).when(controllerInjector).injectEventHandlerControllerMethod(any(), anyString());
|
||||||
doAnswer(i -> sb.append(i.getArgument(0) + "-" + i.getArgument(1))).when(fieldSetter).setEventHandler(any(), anyString());
|
doAnswer(i -> sb.append(i.getArgument(0) + "-" + i.getArgument(1))).when(fieldSetter).setEventHandler(any(), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatSimpleBinding() throws GenerationException {
|
||||||
|
when(property.name()).thenReturn("text");
|
||||||
|
when(property.value()).thenReturn("${value}");
|
||||||
|
propertyFormatter.formatProperty(property, rootObject, variableName);
|
||||||
|
final var expected = property + "-" + variableName;
|
||||||
|
assertEquals(expected, sb.toString());
|
||||||
|
verify(bindingFormatter).formatBinding(property, rootObject, variableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFormatBidirectionalBinding() throws GenerationException {
|
||||||
|
when(property.name()).thenReturn("text");
|
||||||
|
when(property.value()).thenReturn("#{value}");
|
||||||
|
propertyFormatter.formatProperty(property, rootObject, variableName);
|
||||||
|
final var expected = property + "-" + variableName;
|
||||||
|
assertEquals(expected, sb.toString());
|
||||||
|
verify(bindingFormatter).formatBinding(property, rootObject, variableName);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFormatPropertyId() throws GenerationException {
|
void testFormatPropertyId() throws GenerationException {
|
||||||
when(property.name()).thenReturn("fx:id");
|
when(property.name()).thenReturn("fx:id");
|
||||||
|
when(property.value()).thenReturn("value");
|
||||||
propertyFormatter.formatProperty(property, rootObject, variableName);
|
propertyFormatter.formatProperty(property, rootObject, variableName);
|
||||||
assertEquals("", sb.toString());
|
assertEquals("", sb.toString());
|
||||||
}
|
}
|
||||||
@@ -100,12 +125,14 @@ class TestPropertyFormatter {
|
|||||||
@Test
|
@Test
|
||||||
void testFormatControllerSame() {
|
void testFormatControllerSame() {
|
||||||
when(property.name()).thenReturn("fx:controller");
|
when(property.name()).thenReturn("fx:controller");
|
||||||
|
when(property.value()).thenReturn(variableName);
|
||||||
assertDoesNotThrow(() -> propertyFormatter.formatProperty(property, rootObject, variableName));
|
assertDoesNotThrow(() -> propertyFormatter.formatProperty(property, rootObject, variableName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFormatControllerDifferent() {
|
void testFormatControllerDifferent() {
|
||||||
when(property.name()).thenReturn("fx:controller");
|
when(property.name()).thenReturn("fx:controller");
|
||||||
|
when(property.value()).thenReturn("value");
|
||||||
assertThrows(GenerationException.class, () -> propertyFormatter.formatProperty(property, mock(ParsedObject.class), variableName));
|
assertThrows(GenerationException.class, () -> propertyFormatter.formatProperty(property, mock(ParsedObject.class), variableName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.github.gtache.fxml.compiler.impl.GenericTypesImpl;
|
|||||||
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 com.github.gtache.fxml.compiler.parsing.impl.ParsedPropertyImpl;
|
import com.github.gtache.fxml.compiler.parsing.impl.ParsedPropertyImpl;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
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;
|
||||||
@@ -21,6 +22,7 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -67,23 +69,57 @@ class TestReflectionHelper {
|
|||||||
@Test
|
@Test
|
||||||
void testHasMethod() {
|
void testHasMethod() {
|
||||||
assertFalse(ReflectionHelper.hasMethod(String.class, "bla"));
|
assertFalse(ReflectionHelper.hasMethod(String.class, "bla"));
|
||||||
assertTrue(ReflectionHelper.hasMethod(String.class, "charAt"));
|
assertTrue(ReflectionHelper.hasMethod(String.class, "charAt", int.class));
|
||||||
|
assertTrue(ReflectionHelper.hasMethod(String.class, "charAt", (Class<?>) null));
|
||||||
assertTrue(ReflectionHelper.hasMethod(StackPane.class, "getChildren"));
|
assertTrue(ReflectionHelper.hasMethod(StackPane.class, "getChildren"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetMethod() throws NoSuchMethodException {
|
void testHasMethodStatic() {
|
||||||
assertEquals(String.class.getMethod("charAt", int.class), ReflectionHelper.getMethod(String.class, "charAt"));
|
assertFalse(ReflectionHelper.hasMethod(String.class, "valueOf", char.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetMethod() throws Exception {
|
||||||
|
assertEquals(String.class.getMethod("codePointAt", int.class), ReflectionHelper.getMethod(String.class, "codePointAt", int.class));
|
||||||
|
assertEquals(String.class.getMethod("codePointAt", int.class), ReflectionHelper.getMethod(String.class, "codePointAt", (Class<?>) null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetMethodAmbiguous() {
|
||||||
|
assertThrows(GenerationException.class, () -> ReflectionHelper.getStaticMethod(String.class, "valueOf", (Class<?>) null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetMethodInexactNotFound() {
|
||||||
|
assertThrows(GenerationException.class, () -> ReflectionHelper.getStaticMethod(String.class, "abc", (Class<?>) null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetMethodStatic() {
|
||||||
|
assertThrows(GenerationException.class, () -> ReflectionHelper.getMethod(String.class, "valueOf", int.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHasStaticMethod() {
|
void testHasStaticMethod() {
|
||||||
assertTrue(ReflectionHelper.hasStaticMethod(HBox.class, "setHgrow"));
|
assertTrue(ReflectionHelper.hasStaticMethod(HBox.class, "setHgrow", Node.class, Priority.class));
|
||||||
|
assertTrue(ReflectionHelper.hasStaticMethod(HBox.class, "setHgrow", null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetStaticMethod() throws NoSuchMethodException {
|
void testHasStaticMethodInstance() {
|
||||||
assertEquals(HBox.class.getMethod("setHgrow", Node.class, Priority.class), ReflectionHelper.getStaticMethod(HBox.class, "setHgrow"));
|
assertFalse(ReflectionHelper.hasStaticMethod(String.class, "codePointAt", int.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetStaticMethod() throws Exception {
|
||||||
|
assertEquals(HBox.class.getMethod("setMargin", Node.class, Insets.class), ReflectionHelper.getStaticMethod(HBox.class, "setMargin", Node.class, Insets.class));
|
||||||
|
assertEquals(HBox.class.getMethod("setMargin", Node.class, Insets.class), ReflectionHelper.getStaticMethod(HBox.class, "setMargin", null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetStaticMethodNotStatic() {
|
||||||
|
assertThrows(GenerationException.class, () -> ReflectionHelper.getStaticMethod(String.class, "charAt", int.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -182,6 +218,11 @@ class TestReflectionHelper {
|
|||||||
assertEquals(int.class, ReflectionHelper.getClass("int"));
|
assertEquals(int.class, ReflectionHelper.getClass("int"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetClassNotFound() {
|
||||||
|
assertThrows(GenerationException.class, () -> ReflectionHelper.getClass("java.lang.ABC"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetGenericTypesNotGeneric() throws GenerationException {
|
void testGetGenericTypesNotGeneric() throws GenerationException {
|
||||||
when(parsedObject.className()).thenReturn("java.lang.String");
|
when(parsedObject.className()).thenReturn("java.lang.String");
|
||||||
@@ -226,7 +267,14 @@ class TestReflectionHelper {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetReturnType() throws GenerationException {
|
void testGetReturnType() throws GenerationException {
|
||||||
assertEquals(String.class.getName(), ReflectionHelper.getReturnType("java.lang.String", "valueOf"));
|
assertEquals(String.class, ReflectionHelper.getReturnType("java.lang.String", "substring", int.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHasMethodAssignable() {
|
||||||
|
assertTrue(ReflectionHelper.hasMethod(List.class, "addAll", Collection.class));
|
||||||
|
assertTrue(ReflectionHelper.hasMethod(List.class, "addAll", List.class));
|
||||||
|
assertFalse(ReflectionHelper.hasMethod(List.class, "addAll", Object.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.github.gtache.fxml.compiler.impl.internal;
|
||||||
|
|
||||||
|
import com.github.gtache.fxml.compiler.GenerationException;
|
||||||
|
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
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.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class TestValueClassGuesser {
|
||||||
|
|
||||||
|
private final HelperProvider helperProvider;
|
||||||
|
private final VariableProvider variableProvider;
|
||||||
|
private final ValueClassGuesser valueClassGuesser;
|
||||||
|
|
||||||
|
TestValueClassGuesser(@Mock final HelperProvider helperProvider, @Mock final VariableProvider variableProvider) {
|
||||||
|
this.helperProvider = Objects.requireNonNull(helperProvider);
|
||||||
|
this.variableProvider = Objects.requireNonNull(variableProvider);
|
||||||
|
this.valueClassGuesser = new ValueClassGuesser(helperProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() {
|
||||||
|
when(helperProvider.getVariableProvider()).thenReturn(variableProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGuessVariableUnsupported() {
|
||||||
|
assertThrows(GenerationException.class, () -> valueClassGuesser.guess("$controller.value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGuessVariableNotFound() {
|
||||||
|
assertThrows(GenerationException.class, () -> valueClassGuesser.guess("$value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGuessVariable() throws GenerationException {
|
||||||
|
final var object = mock(ParsedObject.class);
|
||||||
|
when(variableProvider.getVariableInfo("value")).thenReturn(new VariableInfo("value", object, "value", "javafx.scene.Node"));
|
||||||
|
assertEquals(List.of(Node.class), valueClassGuesser.guess("$value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGuessNotDecimal() throws GenerationException {
|
||||||
|
assertEquals(List.of(String.class), valueClassGuesser.guess("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGuessDouble() throws GenerationException {
|
||||||
|
assertEquals(List.of(float.class, Float.class, double.class, Double.class, BigDecimal.class, String.class), valueClassGuesser.guess("1.0"));
|
||||||
|
assertEquals(List.of(float.class, Float.class, double.class, Double.class, String.class), valueClassGuesser.guess("-Infinity"));
|
||||||
|
assertEquals(List.of(float.class, Float.class, double.class, Double.class, String.class), valueClassGuesser.guess("Infinity"));
|
||||||
|
assertEquals(List.of(float.class, Float.class, double.class, Double.class, String.class), valueClassGuesser.guess("NaN"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGuessInteger() throws GenerationException {
|
||||||
|
assertEquals(List.of(byte.class, Byte.class, short.class, Short.class, int.class, Integer.class, long.class, Long.class, BigInteger.class,
|
||||||
|
float.class, Float.class, double.class, Double.class, BigDecimal.class, String.class), valueClassGuesser.guess("1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGuessNotByte() throws GenerationException {
|
||||||
|
assertEquals(List.of(short.class, Short.class, int.class, Integer.class, long.class, Long.class, BigInteger.class, float.class, Float.class, double.class, Double.class, BigDecimal.class, String.class), valueClassGuesser.guess("256"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGuessLocalDate() throws GenerationException {
|
||||||
|
assertEquals(List.of(LocalDate.class, String.class), valueClassGuesser.guess("2022-01-01"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGuessLocalDateTime() throws GenerationException {
|
||||||
|
assertEquals(List.of(LocalDateTime.class, String.class), valueClassGuesser.guess("2022-01-01T00:00:00"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGuessBoolean() throws GenerationException {
|
||||||
|
assertEquals(List.of(boolean.class, Boolean.class, String.class), valueClassGuesser.guess("true"));
|
||||||
|
assertEquals(List.of(boolean.class, Boolean.class, String.class), valueClassGuesser.guess("false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIllegal() {
|
||||||
|
assertThrows(NullPointerException.class, () -> new ValueClassGuesser(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ import java.util.Set;
|
|||||||
*/
|
*/
|
||||||
public record CompilationInfo(Path inputFile, Path outputFile, String outputClass, Path controllerFile,
|
public record CompilationInfo(Path inputFile, Path outputFile, String outputClass, Path controllerFile,
|
||||||
String controllerClass, Set<FieldInfo> injectedFields, Set<String> injectedMethods,
|
String controllerClass, Set<FieldInfo> injectedFields, Set<String> injectedMethods,
|
||||||
Map<String, Path> includes, boolean requiresResourceBundle) {
|
Map<String, Inclusion> includes, boolean requiresResourceBundle) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new info
|
* Instantiates a new info
|
||||||
@@ -62,7 +62,7 @@ public record CompilationInfo(Path inputFile, Path outputFile, String outputClas
|
|||||||
private boolean requiresResourceBundle;
|
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, Inclusion> includes;
|
||||||
|
|
||||||
Builder() {
|
Builder() {
|
||||||
this.injectedFields = new HashSet<>();
|
this.injectedFields = new HashSet<>();
|
||||||
@@ -110,7 +110,14 @@ public record CompilationInfo(Path inputFile, Path outputFile, String outputClas
|
|||||||
}
|
}
|
||||||
|
|
||||||
Builder addInclude(final String key, final Path value) {
|
Builder addInclude(final String key, final Path value) {
|
||||||
this.includes.put(key, value);
|
final var current = includes.get(key);
|
||||||
|
final Inclusion newInclusion;
|
||||||
|
if (current == null) {
|
||||||
|
newInclusion = new Inclusion(value, 1);
|
||||||
|
} else {
|
||||||
|
newInclusion = new Inclusion(value, current.count() + 1);
|
||||||
|
}
|
||||||
|
this.includes.put(key, newInclusion);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ final 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 {} of type {} with generic types {} in controller {}", name, type, propertyGenericTypes.get(name), info.controllerFile());
|
logger.debug("Found injected field {} of type {} with generic types {} in controller {}", name, type, propertyGenericTypes.get(name).genericTypes(), 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 {} of type {} with generic types {} in controller {}", name, simpleName, propertyGenericTypes.get(name), info.controllerFile());
|
logger.debug("Found injected field {} of type {} with generic types {} in controller {}", name, simpleName, propertyGenericTypes.get(name).genericTypes(), info.controllerFile());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.info("Field {}({}) not found in controller {}", name, type, info.controllerFile());
|
logger.info("Field {}({}) not found in controller {}", name, type, info.controllerFile());
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.github.gtache.fxml.compiler.maven.internal;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an fx:include info
|
||||||
|
*
|
||||||
|
* @param path The path to the included file
|
||||||
|
* @param count The number of times the file is included
|
||||||
|
*/
|
||||||
|
record Inclusion(Path path, int count) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new Inclusion
|
||||||
|
*
|
||||||
|
* @param path The path to the included file
|
||||||
|
* @param count The number of times the file is included
|
||||||
|
* @throws NullPointerException if path is null
|
||||||
|
* @throws IllegalArgumentException if count < 1
|
||||||
|
*/
|
||||||
|
Inclusion {
|
||||||
|
Objects.requireNonNull(path);
|
||||||
|
if (count < 1) {
|
||||||
|
throw new IllegalArgumentException("count must be >= 1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@ import com.github.gtache.fxml.compiler.impl.SourceInfoImpl;
|
|||||||
import com.github.gtache.fxml.compiler.maven.FXMLCompilerMojo;
|
import com.github.gtache.fxml.compiler.maven.FXMLCompilerMojo;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,8 +31,13 @@ final class SourceInfoProvider {
|
|||||||
final var includes = info.includes();
|
final var includes = info.includes();
|
||||||
final var requiresResourceBundle = info.requiresResourceBundle();
|
final var requiresResourceBundle = info.requiresResourceBundle();
|
||||||
final var includesMapping = new HashMap<String, SourceInfo>();
|
final var includesMapping = new HashMap<String, SourceInfo>();
|
||||||
includes.forEach((k, v) -> includesMapping.put(k, getSourceInfo(mapping.get(v), mapping)));
|
includes.forEach((k, v) -> includesMapping.put(k, getSourceInfo(mapping.get(v.path()), mapping)));
|
||||||
//FIXME mutliple same includes
|
final var includesSources = new ArrayList<SourceInfo>();
|
||||||
return new SourceInfoImpl(outputClass, controllerClass, inputFile, List.copyOf(includesMapping.values()), includesMapping, requiresResourceBundle);
|
includes.forEach((key, value) -> {
|
||||||
|
for (var i = 0; i < value.count(); ++i) {
|
||||||
|
includesSources.add(includesMapping.get(key));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return new SourceInfoImpl(outputClass, controllerClass, inputFile, includesSources, includesMapping, requiresResourceBundle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ class TestCompilationInfo {
|
|||||||
private final String controllerClass;
|
private final String controllerClass;
|
||||||
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, Inclusion> includes;
|
||||||
private final boolean requiresResourceBundle;
|
private final boolean requiresResourceBundle;
|
||||||
private final CompilationInfo info;
|
private final CompilationInfo info;
|
||||||
|
|
||||||
TestCompilationInfo(@Mock final Path inputFile, @Mock final Path outputFile, @Mock final Path controllerFile, @Mock final FieldInfo fieldInfo) {
|
TestCompilationInfo(@Mock final Path inputFile, @Mock final Path outputFile, @Mock final Path controllerFile, @Mock final Inclusion inclusion, @Mock final FieldInfo fieldInfo) {
|
||||||
this.inputFile = Objects.requireNonNull(inputFile);
|
this.inputFile = Objects.requireNonNull(inputFile);
|
||||||
this.outputFile = Objects.requireNonNull(outputFile);
|
this.outputFile = Objects.requireNonNull(outputFile);
|
||||||
this.outputClass = "outputClass";
|
this.outputClass = "outputClass";
|
||||||
@@ -36,7 +36,7 @@ class TestCompilationInfo {
|
|||||||
this.controllerClass = "controllerClass";
|
this.controllerClass = "controllerClass";
|
||||||
this.injectedFields = new HashSet<>(Set.of(fieldInfo));
|
this.injectedFields = new HashSet<>(Set.of(fieldInfo));
|
||||||
this.injectedMethods = new HashSet<>(Set.of("one", "two"));
|
this.injectedMethods = new HashSet<>(Set.of("one", "two"));
|
||||||
this.includes = new HashMap<>(Map.of("one", Objects.requireNonNull(inputFile)));
|
this.includes = new HashMap<>(Map.of("one", Objects.requireNonNull(inclusion)));
|
||||||
this.requiresResourceBundle = true;
|
this.requiresResourceBundle = true;
|
||||||
this.info = new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, includes, requiresResourceBundle);
|
this.info = new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, includes, requiresResourceBundle);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ class TestCompilationInfoBuilder {
|
|||||||
private final String controllerClass;
|
private final String controllerClass;
|
||||||
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, Inclusion> includes;
|
||||||
private final CompilationInfo info;
|
private final CompilationInfo info;
|
||||||
|
|
||||||
TestCompilationInfoBuilder(@Mock final Path inputFile, @Mock final Path outputFile, @Mock final Path controllerFile, @Mock final FieldInfo fieldInfo) {
|
TestCompilationInfoBuilder(@Mock final Path inputFile, @Mock final Path outputFile, @Mock final Path controllerFile) {
|
||||||
this.inputFile = Objects.requireNonNull(inputFile);
|
this.inputFile = Objects.requireNonNull(inputFile);
|
||||||
this.outputFile = Objects.requireNonNull(outputFile);
|
this.outputFile = Objects.requireNonNull(outputFile);
|
||||||
this.outputClass = "outputClass";
|
this.outputClass = "outputClass";
|
||||||
@@ -32,7 +32,7 @@ class TestCompilationInfoBuilder {
|
|||||||
this.controllerClass = "controllerClass";
|
this.controllerClass = "controllerClass";
|
||||||
this.injectedFields = Set.of(new FieldInfo("type", "name"));
|
this.injectedFields = Set.of(new FieldInfo("type", "name"));
|
||||||
this.injectedMethods = Set.of("one", "two");
|
this.injectedMethods = Set.of("one", "two");
|
||||||
this.includes = Map.of("one", Objects.requireNonNull(inputFile));
|
this.includes = Map.of("one", new Inclusion(inputFile, 1));
|
||||||
this.info = new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, includes, true);
|
this.info = new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, includes, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,9 +47,14 @@ class TestCompilationInfoBuilder {
|
|||||||
builder.controllerClass(controllerClass);
|
builder.controllerClass(controllerClass);
|
||||||
injectedFields.forEach(f -> builder.addInjectedField(f.name(), f.type()));
|
injectedFields.forEach(f -> builder.addInjectedField(f.name(), f.type()));
|
||||||
injectedMethods.forEach(builder::addInjectedMethod);
|
injectedMethods.forEach(builder::addInjectedMethod);
|
||||||
includes.forEach(builder::addInclude);
|
builder.addInclude("one", inputFile);
|
||||||
builder.requiresResourceBundle();
|
builder.requiresResourceBundle();
|
||||||
final var actual = builder.build();
|
final var actual = builder.build();
|
||||||
assertEquals(info, actual);
|
assertEquals(info, actual);
|
||||||
|
|
||||||
|
builder.addInclude("one", inputFile);
|
||||||
|
final var newIncludes = Map.of("one", new Inclusion(inputFile, 2));
|
||||||
|
final var newInfo = new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, newIncludes, true);
|
||||||
|
assertEquals(newInfo, builder.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class TestCompilationInfoProvider {
|
|||||||
"com.github.gtache.fxml.compiler.maven.internal.InfoView", controllerPath, controllerClass,
|
"com.github.gtache.fxml.compiler.maven.internal.InfoView", controllerPath, controllerClass,
|
||||||
Set.of(new FieldInfo("javafx.event.EventHandler", "onContextMenuRequested"), new FieldInfo("Button", "button"),
|
Set.of(new FieldInfo("javafx.event.EventHandler", "onContextMenuRequested"), new FieldInfo("Button", "button"),
|
||||||
new FieldInfo("com.github.gtache.fxml.compiler.maven.internal.IncludeController", "includeViewController")),
|
new FieldInfo("com.github.gtache.fxml.compiler.maven.internal.IncludeController", "includeViewController")),
|
||||||
Set.of("onAction"), Map.of("includeView.fxml", path.getParent().resolve("includeView.fxml")), true);
|
Set.of("onAction"), Map.of("includeView.fxml", new Inclusion(path.getParent().resolve("includeView.fxml"), 1)), true);
|
||||||
final var compilationInfoProvider = new CompilationInfoProvider(project, tempDir);
|
final var compilationInfoProvider = new CompilationInfoProvider(project, tempDir);
|
||||||
final var actual = compilationInfoProvider.getCompilationInfo(tempDir, path, Map.of(includedPath, "com.github.gtache.fxml.compiler.maven.internal.IncludeController"));
|
final var actual = compilationInfoProvider.getCompilationInfo(tempDir, path, Map.of(includedPath, "com.github.gtache.fxml.compiler.maven.internal.IncludeController"));
|
||||||
assertEquals(expected, actual);
|
assertEquals(expected, actual);
|
||||||
@@ -115,7 +115,7 @@ class TestCompilationInfoProvider {
|
|||||||
"com.github.gtache.fxml.compiler.maven.internal.NoResourceBundle", controllerPath, controllerClass,
|
"com.github.gtache.fxml.compiler.maven.internal.NoResourceBundle", controllerPath, controllerClass,
|
||||||
Set.of(new FieldInfo("javafx.event.EventHandler", "onContextMenuRequested"), new FieldInfo("Button", "button"),
|
Set.of(new FieldInfo("javafx.event.EventHandler", "onContextMenuRequested"), new FieldInfo("Button", "button"),
|
||||||
new FieldInfo("com.github.gtache.fxml.compiler.maven.internal.IncludeController", "includeViewController")),
|
new FieldInfo("com.github.gtache.fxml.compiler.maven.internal.IncludeController", "includeViewController")),
|
||||||
Set.of("onAction"), Map.of("includeView.fxml", path.getParent().resolve("includeView.fxml")), false);
|
Set.of("onAction"), Map.of("includeView.fxml", new Inclusion(path.getParent().resolve("includeView.fxml"), 1)), false);
|
||||||
final var compilationInfoProvider = new CompilationInfoProvider(project, tempDir);
|
final var compilationInfoProvider = new CompilationInfoProvider(project, tempDir);
|
||||||
final var actual = compilationInfoProvider.getCompilationInfo(tempDir, path, Map.of(includedPath, "com.github.gtache.fxml.compiler.maven.internal.IncludeController"));
|
final var actual = compilationInfoProvider.getCompilationInfo(tempDir, path, Map.of(includedPath, "com.github.gtache.fxml.compiler.maven.internal.IncludeController"));
|
||||||
assertEquals(expected, actual);
|
assertEquals(expected, actual);
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.github.gtache.fxml.compiler.maven.internal;
|
||||||
|
|
||||||
|
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.util.Objects;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class TestInclusion {
|
||||||
|
|
||||||
|
private final Path path;
|
||||||
|
private final int count;
|
||||||
|
private final Inclusion inclusion;
|
||||||
|
|
||||||
|
TestInclusion(@Mock final Path path) {
|
||||||
|
this.path = Objects.requireNonNull(path);
|
||||||
|
this.count = 1;
|
||||||
|
this.inclusion = new Inclusion(path, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetters() {
|
||||||
|
assertEquals(path, inclusion.path());
|
||||||
|
assertEquals(count, inclusion.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIllegal() {
|
||||||
|
assertThrows(NullPointerException.class, () -> new Inclusion(null, count));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Inclusion(path, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ class TestSourceInfoProvider {
|
|||||||
final var controllerClass = "controllerClass";
|
final var controllerClass = "controllerClass";
|
||||||
final var inputFile = Path.of("inputFile");
|
final var inputFile = Path.of("inputFile");
|
||||||
final var includeFile = Path.of("includeFile");
|
final var includeFile = Path.of("includeFile");
|
||||||
final var includes = Map.of("one", includeFile);
|
final var includes = Map.of("one", new Inclusion(includeFile, 3));
|
||||||
when(compilationInfo.outputClass()).thenReturn(outputClass);
|
when(compilationInfo.outputClass()).thenReturn(outputClass);
|
||||||
when(compilationInfo.controllerClass()).thenReturn(controllerClass);
|
when(compilationInfo.controllerClass()).thenReturn(controllerClass);
|
||||||
when(compilationInfo.inputFile()).thenReturn(inputFile);
|
when(compilationInfo.inputFile()).thenReturn(inputFile);
|
||||||
@@ -42,7 +42,7 @@ class TestSourceInfoProvider {
|
|||||||
|
|
||||||
final var expectedIncludeSourceInfo = new SourceInfoImpl(includeOutputClass, includeControllerClass, includeInputFile, List.of(), Map.of(), false);
|
final var expectedIncludeSourceInfo = new SourceInfoImpl(includeOutputClass, includeControllerClass, includeInputFile, List.of(), Map.of(), false);
|
||||||
assertEquals(expectedIncludeSourceInfo, SourceInfoProvider.getSourceInfo(includeCompilationInfo, mapping));
|
assertEquals(expectedIncludeSourceInfo, SourceInfoProvider.getSourceInfo(includeCompilationInfo, mapping));
|
||||||
final var expected = new SourceInfoImpl(outputClass, controllerClass, inputFile, List.of(expectedIncludeSourceInfo),
|
final var expected = new SourceInfoImpl(outputClass, controllerClass, inputFile, List.of(expectedIncludeSourceInfo, expectedIncludeSourceInfo, expectedIncludeSourceInfo),
|
||||||
Map.of("one", expectedIncludeSourceInfo), true);
|
Map.of("one", expectedIncludeSourceInfo), true);
|
||||||
assertEquals(expected, SourceInfoProvider.getSourceInfo(compilationInfo, mapping));
|
assertEquals(expected, SourceInfoProvider.getSourceInfo(compilationInfo, mapping));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user