Skip to content

Commit

Permalink
Merge pull request #40 from Paullo612/non-reflective-controller-insta…
Browse files Browse the repository at this point in the history
…ntiation

Implement non-reflective controllers instantiation
  • Loading branch information
Paullo612 committed Jun 3, 2023
2 parents 416f45a + 1055f03 commit 81e7477
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,11 @@ public Result<R, C> load(
}

accessor = controllerAccessorFactory.createControllerAccessor(controllerClass);
// Controller is required, but none provided. Create one.
controllerInstance = accessor.newControllerInstance();
// Controller is required, but none provided. Create one. Create it directly if we're able to do so and
// there is no controller factory specified. Fallback to accessor otherwise.
controllerInstance = canCreateController() && !controllerAccessorFactory.isBackedByControllerFactory()
? createController()
: accessor.newControllerInstance();
}

return doLoad(controllerAccessorFactory, resourceBundle, castRoot, accessor, controllerInstance);
Expand All @@ -151,8 +154,8 @@ public Result<R, C> load(
* @param rootInstance instance of root element specified by user or {@code null} if document's root element is not
* fx:root
* @param accessor controller accessor or {@code null} if there is no controller
* @param controller controller specified by user or {@code null} if controller is specified on document's
* root element
* @param controller controller specified by user or {@code null} if controller is specified on document's root
* element
* @return load result
*
* @throws CompiledLoadException in case of load failure
Expand All @@ -174,12 +177,12 @@ protected abstract Result<R, C> doLoad(
public abstract int getABIVersion();

/**
* Returns original URL of compiled FXML file.
* Returns original URI of compiled FXML file.
*
* <p>Intended to be implemented by generated code.</p>
*
* @return original URL
* @throws CompiledLoadException in case URL cannot be constructed.
* @return original URI
* @throws CompiledLoadException in case URI cannot be constructed
*/
public abstract URI getURI() throws CompiledLoadException;

Expand Down Expand Up @@ -228,6 +231,32 @@ public boolean requiresRootInstance() {
return getRootInstanceClass().isPresent();
}

/**
* Whether this loader can create new controller instance.
*
* <p>Intended to be implemented by generated code.</p>
*
* @return {@code true} if new controller instance can be created by this loader, {@code false} otherwise
*/
public boolean canCreateController() {
return false;
}

/**
* Creates controller.
*
* <p>Intended to be implemented by generated code.</p>
*
* @return new controller instance
* @throws CompiledLoadException if there is no controller specified, or it does not have accessible no-args
* constructor
*/
public C createController() throws CompiledLoadException {
throw new CompiledLoadException(
"There is no controller, or controller does not have accessible no-args constructor"
);
}

/**
* Creates {@link Result} instance.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,16 @@ public interface ControllerAccessorFactory {
* @param <C> controller type
*/
<C> ControllerAccessor<C> createControllerAccessor(Class<C> controllerClass);

/**
* Indicates that this controller accessor factory is backed by controller factory.
*
* <p>If factory is backed by controller factory, controller factory is used to construct new controller
* instances. Otherwise controller is constructed by controller accessor directly.</p>
*
* @return {@code true} if this factory is backed by controller factory, {@code false} if it is not
*/
default boolean isBackedByControllerFactory() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,11 @@ public <C> ControllerAccessor<C> createControllerAccessor(Class<C> controllerCla
DELEGATE.createControllerAccessor(controllerClass), controllerFactory
);
}

@Override
public boolean isBackedByControllerFactory() {
return true;
}
};
} else {
factory = DELEGATE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,15 @@ private void handleParserEvent(
controllerClassElement = context.getClassElement(Object.class);
}

boolean canCreateController = hasController
&& !context.requiresExternalController()
&& controllerClassElement.getEnclosedElement(
ElementQuery.CONSTRUCTORS
.filter(c -> c.getParameters().length == 0)
.filter(c -> !c.isReflectionRequired(context.getTargetType()))
)
.isPresent();

LoadableFXMLElement<?> loadableFXMLElement = element.asLoadableFXMLElement();
assert loadableFXMLElement != null;

Expand All @@ -358,7 +367,8 @@ private void handleParserEvent(
controllerClassElement,
hasFxRoot,
hasController,
context.requiresExternalController()
context.requiresExternalController(),
canCreateController
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class RootRenderer implements CompilerContext.Renderer {
private static final String REQUIRES_EXTERNAL_CONTROLLER_METHOD_NAME = "requiresExternalController";
private static final String GET_ROOT_INSTANCE_CLASS_METHOD_NAME = "getRootInstanceClass";
private static final String GET_CONTROLLER_METHOD_NAME = "getControllerClass";
private static final String CAN_CREATE_CONTROLLER_METHOD_NAME = "canCreateController";
private static final String CREATE_CONTROLLER_METHOD_NAME = "createController";

private static final String CREATE_RESULT_METHOD_NAME = "createResult";

Expand Down Expand Up @@ -123,6 +125,7 @@ static void loadController(GeneratorAdapter methodVisitor) {
private boolean hasFxRoot;
private boolean hasController;
private boolean requiresExternalController;
private boolean canCreateController;

private String internalClassName;

Expand All @@ -133,21 +136,23 @@ static void loadController(GeneratorAdapter methodVisitor) {
}

void initialize(
ClassElement targetType,
ClassElement targetClassElement,
ClassElement rootClassElement,
ClassElement controllerClassElement,
boolean hasFxRoot,
boolean hasController,
boolean requiresExternalController) {
boolean requiresExternalController,
boolean canCreateController) {
this.rootClassElement = rootClassElement;
this.controllerClassElement = controllerClassElement;
this.rootType = RenderUtils.type(rootClassElement);
this.controllerType = RenderUtils.type(controllerClassElement);
this.hasFxRoot = hasFxRoot;
this.hasController = hasController;
this.canCreateController = canCreateController;
this.requiresExternalController = requiresExternalController;

this.internalClassName = RenderUtils.type(targetType).getInternalName();
this.internalClassName = RenderUtils.type(targetClassElement).getInternalName();

startLoaderClass();

Expand Down Expand Up @@ -519,6 +524,78 @@ private void renderGetControllerClassMethod() {
getControllerClassMethod.visitEnd();
}

private void renderCanCreateControllerMethod() {
MethodVisitor requiresExternalControllerMethod = loaderWriter.visitMethod(
Opcodes.ACC_PUBLIC,
CAN_CREATE_CONTROLLER_METHOD_NAME,
"()Z",
null,
null
);
requiresExternalControllerMethod.visitCode();
requiresExternalControllerMethod.visitInsn(Opcodes.ICONST_1);
requiresExternalControllerMethod.visitInsn(Opcodes.IRETURN);

// MAXSTACK = 1 (result)
// MAXLOCALS = 1 (this)
requiresExternalControllerMethod.visitMaxs(1, 1);
requiresExternalControllerMethod.visitEnd();
}

private void renderCreateControllerMethod() {
// Generate bridge method first.
MethodVisitor bridgeMethod = loaderWriter.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC,
CREATE_CONTROLLER_METHOD_NAME,
"()" + RenderUtils.OBJECT_D,
null,
new String[] { Type.getType(CompiledLoadException.class).getInternalName() }
);

bridgeMethod.visitCode();
// ALOAD 0 (this)
bridgeMethod.visitVarInsn(Opcodes.ALOAD, 0);

bridgeMethod.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
internalClassName,
CREATE_CONTROLLER_METHOD_NAME,
"()" + controllerType.getDescriptor(),
false
);

// ARETURN
bridgeMethod.visitInsn(Opcodes.ARETURN);
// MAXSTACK = 1 (this)
// MAXLOCALS = 1 (this)
bridgeMethod.visitMaxs(1, 1);

MethodVisitor createControllerMethod = loaderWriter.visitMethod(
Opcodes.ACC_PUBLIC,
CREATE_CONTROLLER_METHOD_NAME,
"()" + controllerType.getDescriptor(),
null,
new String[] { Type.getType(CompiledLoadException.class).getInternalName() }
);

createControllerMethod.visitCode();
createControllerMethod.visitTypeInsn(Opcodes.NEW, controllerType.getInternalName());
createControllerMethod.visitInsn(Opcodes.DUP);
createControllerMethod.visitMethodInsn(
Opcodes.INVOKESPECIAL,
controllerType.getInternalName(),
RenderUtils.CONSTRUCTOR_N,
"()V",
false
);

// ARETURN
createControllerMethod.visitInsn(Opcodes.ARETURN);
// MAXSTACK = 2 (controller, controller)
// MAXLOCALS = 1 (controller)
createControllerMethod.visitMaxs(2, 1);
}

byte[] dispose() {
loadMethodVisitor.loadThis();

Expand Down Expand Up @@ -591,6 +668,11 @@ byte[] dispose() {
renderGetRootInstanceClassMethod();
renderGetControllerClassMethod();

if (canCreateController) {
renderCanCreateControllerMethod();
renderCreateControllerMethod();
}

loaderWriter.visitEnd();

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package io.github.paullo612.mlfx.compiler
import io.github.paullo612.mlfx.api.CompiledFXMLLoader
import io.github.paullo612.mlfx.api.CompiledLoadException
import io.github.paullo612.mlfx.api.ControllerAccessor
import io.github.paullo612.mlfx.api.ControllerAccessorFactory
import io.github.paullo612.mlfx.api.Result
import javafx.fxml.FXMLLoader
import org.opentest4j.AssertionFailedError
Expand Down Expand Up @@ -94,6 +95,14 @@ class ComplianceSpec extends CompileSpec {
}
}

private static class ControllerAccessorFactoryImpl implements ControllerAccessorFactory {

@Override
<C> ControllerAccessor<C> createControllerAccessor(Class<C> controllerClass) {
new ControllerAccessorImpl<C>(controllerClass)
}
}

private static class LoadResult {

private final Object root
Expand Down Expand Up @@ -241,8 +250,7 @@ class ComplianceSpec extends CompileSpec {
Class<? extends CompiledFXMLLoader> compiledLoaderClass = loaderClass.asSubclass(CompiledFXMLLoader.class)

CompiledFXMLLoader<?, ?> loader = compiledLoaderClass.getDeclaredConstructor().newInstance()
Result<?, ?> result =
loader.load(c -> new ControllerAccessorImpl<>(c), controller, root, resourceBundle)
Result<?, ?> result = loader.load(new ControllerAccessorFactoryImpl(), controller, root, resourceBundle)

new LoadResult(result.rootInstance, result.controller)
}
Expand Down

0 comments on commit 81e7477

Please sign in to comment.