net.rgielen.fxweaver.core.FxWeaver Maven / Gradle / Ivy
package net.rgielen.fxweaver.core;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
/**
* FxWeaver is the core weaving facility, enabling Controllers and Views to be instantiated by a dependency injection
* framework such as (but not limited to) Spring.
*
* The following example requires a Spring ConfigurableApplicationContext to be instantiated. If now MainController is
* declared as Spring managed bean, it will get created and injected by Spring.
*
* If a managed Controller class contains an {@link FxmlView} annotation, attached FXML views are injected as well.
*
*
* ConfigurableApplicationContext applicationContext = ...
* FxWeaver fxWeaver = new FxWeaver(applicationContext::getBean, applicationContext::close);
* Scene scene = new Scene(fxWeaver.loadView(MainController.class), 400, 300);
* ...
* @FxmlView
* public class MainController {
* ...
* }
*
*
* @author Rene Gielen
* @noinspection unused, WeakerAccess
* @see FxmlView
*/
public class FxWeaver {
private static final Logger LOG = LoggerFactory.getLogger(FxWeaver.class);
private final Callback, Object> beanFactory;
private final Runnable closeCommand;
/**
* Create a FxWeaver instance.
*
* Example:
*
* ConfigurableApplicationContext applicationContext = ...
* FxWeaver fxWeaver = new FxWeaver(applicationContext::getBean, applicationContext::close);
*
*
* @param beanFactory The beanFactory callback to be called for requesting a bean of given class when e.g. {@link
* #loadView(Class)} is called.
* @param closeCommand The function to close a bean factory attached to FxWeaver
* @see #loadView(Class)
* @see #loadView(Class, ResourceBundle)
*/
public FxWeaver(Callback, Object> beanFactory, Runnable closeCommand) {
this.beanFactory = beanFactory;
this.closeCommand = closeCommand;
}
/**
* Load FXML-defined view instance, weaved with its controller declared in fx:controller as a bean produced by the
* bean factory provided in {@link #FxWeaver(Callback, Runnable)}.
*
* The possible FXML resource is inferred from a {@link FxmlView} annotation at the controller class or the simple
* classname and package of said class if it was not annotated like this. If the FXML file is resolvable, the
* defined view within will be loaded by {@link FXMLLoader}. The controller will then be instantiated based on the
* fx:controller attribute, using the bean factory from {@link #FxWeaver(Callback, Runnable)}. If the bean factory
* is based on a dependency management framework such as Spring, Guice or CDI, this means that the instance will be
* fully managed and injected as declared.
*
* If you are interested in the controller instance, you might instead use the loadController methods, e.g.
* {@link #loadController(Class)}
*
* @param controllerClass The controller class of which a weaved instance should be provided
* @param The controller type
* @param The view type
* @return An instance of the requested view, weaved with its managed controller as defined in {@link
* FXMLLoader#getController()}.
* @see #loadController(Class)
* @see #loadController(Class, ResourceBundle)
* @see #loadController(Class, String)
* @see #loadController(Class, String, ResourceBundle)
* @see #FxWeaver(Callback, Runnable)
* @see FXMLLoader
*/
public V loadView(Class controllerClass) {
return loadView(controllerClass, (ResourceBundle) null);
}
/**
* Load FXML-defined view instance, weaved with its controller declared in fx:controller as a bean produced by the
* bean factory provided in {@link #FxWeaver(Callback, Runnable)}.
*
* The possible FXML resource is inferred from a {@link FxmlView} annotation at the controller class or the simple
* classname and package of said class if it was not annotated like this. If the FXML file is resolvable, the
* defined view within will be loaded by {@link FXMLLoader}. The controller will then be instantiated based on the
* fx:controller attribute, using the bean factory from {@link #FxWeaver(Callback, Runnable)}. If the bean factory
* is based on a dependency management framework such as Spring, Guice or CDI, this means that the instance will be
* fully managed and injected as declared.
*
* If you are interested in the controller instance, you might instead use the loadController methods, e.g.
* {@link #loadController(Class)}
*
* @param controllerClass The controller class of which a weaved instance should be provided
* @param resourceBundle The optional {@link ResourceBundle} to use for view creation. May be null
* @param The controller type
* @param The view type
* @return An instance of the requested view, weaved with its managed controller as defined in {@link
* FXMLLoader#getController()}.
* @see #loadController(Class)
* @see #loadController(Class, ResourceBundle)
* @see #loadController(Class, String)
* @see #loadController(Class, String, ResourceBundle)
* @see #FxWeaver(Callback, Runnable)
* @see FXMLLoader
*/
public V loadView(Class controllerClass, ResourceBundle resourceBundle) {
return loadView(controllerClass, buildFxmlReference(controllerClass), resourceBundle);
}
/**
* Load FXML-defined view instance, weaved with its controller declared in fx:controller as a bean produced by the
* bean factory provided in {@link #FxWeaver(Callback, Runnable)}.
*
* The possible FXML resource may be given as a location in the classpath. If the FXML file is resolvable, the
* defined view within will be loaded by {@link FXMLLoader}. The controller will then be instantiated based on the
* fx:controller attribute, using the bean factory from {@link #FxWeaver(Callback, Runnable)}. If the bean factory
* is based on a dependency management framework such as Spring, Guice or CDI, this means that the instance will be
* fully managed and injected as declared.
*
* If you are interested in the controller instance, you might instead use the loadController methods, e.g.
* {@link #loadController(Class)}
*
* @param controllerClass The controller class of which a weaved instance should be provided
* @param location The location of the FXML view to load as a classloader resource.
* @param The controller type
* @param The view type
* @return An instance of the requested view, weaved with its managed controller as defined in {@link
* FXMLLoader#getController()}.
* @see #load(Class)
* @see #load(Class, ResourceBundle)
* @see #loadController(Class)
* @see #loadController(Class, ResourceBundle)
* @see #FxWeaver(Callback, Runnable)
* @see FXMLLoader
*/
public V loadView(Class controllerClass, String location) {
return loadView(controllerClass, location, null);
}
/**
* Load FXML-defined view instance, weaved with its controller declared in fx:controller as a bean produced by the
* bean factory provided in {@link #FxWeaver(Callback, Runnable)}.
*
* The possible FXML resource may be given as a location in the classpath. If the FXML file is resolvable, the
* defined view within will be loaded by {@link FXMLLoader}. The controller will then be instantiated based on the
* fx:controller attribute, using the bean factory from {@link #FxWeaver(Callback, Runnable)}. If the bean factory
* is based on a dependency management framework such as Spring, Guice or CDI, this means that the instance will be
* fully managed and injected as declared.
*
* If you are interested in the controller instance, you might instead use the loadController methods, e.g.
* {@link #loadController(Class)}
*
* @param controllerClass The controller class of which a weaved instance should be provided
* @param location The location of the FXML view to load as a classloader resource.
* @param resourceBundle The optional {@link ResourceBundle} to use for view creation. May be null
* @param The controller type
* @param The view type
* @return An instance of the requested view, weaved with its managed controller as defined in {@link
* FXMLLoader#getController()}.
* @see #loadController(Class)
* @see #loadController(Class, ResourceBundle)
* @see #FxWeaver(Callback, Runnable)
* @see FXMLLoader
*/
public V loadView(Class controllerClass, String location, ResourceBundle resourceBundle) {
return this.load(controllerClass, location, resourceBundle)
.getView()
.orElse(null);
}
/**
* Load controller instance, potentially weaved with a FXML view declaring the given class as fx:controller.
*
* The possible FXML resource may be given as a location in the classpath. If the FXML file is resolvable, the
* defined view within will be loaded by {@link FXMLLoader}. The controller will then be instantiated based on the
* fx:controller attribute, using the bean factory from {@link #FxWeaver(Callback, Runnable)}. If the bean factory
* is based on a dependency management framework such as Spring, Guice or CDI, this means that the instance will be
* fully managed and injected as declared.
*
* If the controller class does not come with a resolvable FXML view resource, the controller will be instantiated
* by the given bean factory directly.
*
* @param controllerClass The controller class of which a weaved instance should be provided
* @param location The location of the FXML view to load as a classloader resource. May be null or
* not resolvable, in which case the controller will be directly instantiated by the given
* bean factory.
* @param The controller type
* @return A managed instance of the requested controller, potentially weaved with its view
* @see #FxWeaver(Callback, Runnable)
* @see FXMLLoader
*/
public C loadController(Class controllerClass, String location) {
return loadController(controllerClass, location, null);
}
/**
* Load controller instance, potentially weaved with a FXML view declaring the given class as fx:controller.
*
* The possible FXML resource may be given as a location in the classpath. If the FXML file is resolvable, the
* defined view within will be loaded by {@link FXMLLoader}. The controller will then be instantiated based on the
* fx:controller attribute, using the bean factory from {@link #FxWeaver(Callback, Runnable)}. If the bean factory
* is based on a dependency management framework such as Spring, Guice or CDI, this means that the instance will be
* fully managed and injected as declared.
*
* If the controller class does not come with a resolvable FXML view resource, the controller will be instantiated
* by the given bean factory directly.
*
* @param controllerClass The controller class of which a weaved instance should be provided
* @param location The location of the FXML view to load as a classloader resource. May be null or
* not resolvable, in which case the controller will be directly instantiated by the given
* bean factory.
* @param resourceBundle The optional {@link ResourceBundle} to use for view creation. May be null
* @param The controller type
* @return A managed instance of the requested controller, potentially weaved with its view
* @see #FxWeaver(Callback, Runnable)
* @see FXMLLoader
*/
public C loadController(Class controllerClass, String location, ResourceBundle resourceBundle) {
return load(controllerClass, location, resourceBundle).getController();
}
/**
* Load controller instance, potentially weaved with a FXML view declaring the given class as fx:controller.
*
* The possible FXML resource is inferred from a {@link FxmlView} annotation at the controller class or the simple
* classname and package of said class if it was not annotated like this. If the FXML file is resolvable, the
* defined view within will be loaded by {@link FXMLLoader}. The controller will then be instantiated based on the
* fx:controller attribute, using the bean factory from {@link #FxWeaver(Callback, Runnable)}. If the bean factory
* is based on a dependency management framework such as Spring, Guice or CDI, this means that the instance will be
* fully managed and injected as declared.
*
* If the controller class does not come with a resolvable FXML view resource, the controller will be instantiated
* by the given bean factory directly.
*
* @param controllerClass The controller class of which a weaved instance should be provided
* @param The controller type
* @return A managed instance of the requested controller, potentially weaved with its view
* @see #FxWeaver(Callback, Runnable)
* @see FXMLLoader
*/
public C loadController(Class controllerClass) {
return loadController(controllerClass, (ResourceBundle) null);
}
/**
* Load controller instance, potentially weaved with a FXML view declaring the given class as fx:controller.
*
* The possible FXML resource is inferred from a {@link FxmlView} annotation at the controller class or the simple
* classname and package of said class if it was not annotated like this. If the FXML file is resolvable, the
* defined view within will be loaded by {@link FXMLLoader}. The controller will then be instantiated based on the
* fx:controller attribute, using the bean factory from {@link #FxWeaver(Callback, Runnable)}. If the bean factory
* is based on a dependency management framework such as Spring, Guice or CDI, this means that the instance will be
* fully managed and injected as declared.
*
* If the controller class does not come with a resolvable FXML view resource, the controller will be instantiated
* by the given bean factory directly.
*
* @param controllerClass The controller class of which a weaved instance should be provided
* @param resourceBundle The optional {@link ResourceBundle} to use for view creation. May be null
* @param The controller type
* @return A managed instance of the requested controller, potentially weaved with its view
* @see #FxWeaver(Callback, Runnable)
* @see FXMLLoader
*/
public C loadController(Class controllerClass, ResourceBundle resourceBundle) {
return load(controllerClass, resourceBundle).getController();
}
/**
* Get managed bean instance from bean factory provided in {@link #FxWeaver(Callback, Runnable)}.
*
* @param beanType The type of the bean to be instantiated.
* @param The bean type.
* @return The bean as defined in and returned from the bean factory
*/
public C getBean(Class beanType) {
return beanType.cast(beanFactory.call(beanType));
}
/**
* Load controller instance, potentially weaved with a FXML view declaring the given class as fx:controller.
*
* The possible FXML resource is inferred from a {@link FxmlView} annotation at the controller class or the simple
* classname and package of said class if it was not annotated like this. If the FXML file is resolvable, the
* defined view within will be loaded by {@link FXMLLoader}. The controller will then be instantiated based on the
* fx:controller attribute, using the bean factory from {@link #FxWeaver(Callback, Runnable)}. If the bean factory
* is based on a dependency management framework such as Spring, Guice or CDI, this means that the instance will be
* fully managed and injected as declared.
*
* If the controller class does not come with a resolvable FXML view resource, the controller will be instantiated
* by the given bean factory directly.
*
* @param controllerClass The controller class of which a weaved instance should be provided
* @param The view type
* @param The controller type
* @return A {@link SimpleFxControllerAndView} container with the managed instance of the requested controller and the
* corresponding view, if applicable
* @see #FxWeaver(Callback, Runnable)
* @see FXMLLoader
*/
public FxControllerAndView load(Class controllerClass) {
return load(controllerClass, null);
}
/**
* Load controller instance, potentially weaved with a FXML view declaring the given class as fx:controller.
*
* The possible FXML resource is inferred from a {@link FxmlView} annotation at the controller class or the simple
* classname and package of said class if it was not annotated like this. If the FXML file is resolvable, the
* defined view within will be loaded by {@link FXMLLoader}. The controller will then be instantiated based on the
* fx:controller attribute, using the bean factory from {@link #FxWeaver(Callback, Runnable)}. If the bean factory
* is based on a dependency management framework such as Spring, Guice or CDI, this means that the instance will be
* fully managed and injected as declared.
*
* If the controller class does not come with a resolvable FXML view resource, the controller will be instantiated
* by the given bean factory directly.
*
* @param controllerClass The controller class of which a weaved instance should be provided
* @param resourceBundle The optional {@link ResourceBundle} to use for view creation. May be null
* @param The view type
* @param The controller type
* @return A {@link SimpleFxControllerAndView} container with the managed instance of the requested controller and the
* corresponding view, if applicable
* @see #FxWeaver(Callback, Runnable)
* @see FXMLLoader
*/
public FxControllerAndView load(Class controllerClass,
ResourceBundle resourceBundle) {
return load(controllerClass, buildFxmlReference(controllerClass), resourceBundle);
}
/**
* Load controller instance, potentially weaved with a FXML view declaring the given class as fx:controller.
*
* The possible FXML resource is inferred from a {@link FxmlView} annotation at the controller class or the simple
* classname and package of said class if it was not annotated like this. If the FXML file is resolvable, the
* defined view within will be loaded by {@link FXMLLoader}. The controller will then be instantiated based on the
* fx:controller attribute, using the bean factory from {@link #FxWeaver(Callback, Runnable)}. If the bean factory
* is based on a dependency management framework such as Spring, Guice or CDI, this means that the instance will be
* fully managed and injected as declared.
*
* If the controller class does not come with a resolvable FXML view resource, the controller will be instantiated
* by the given bean factory directly.
*
* @param controllerClass The controller class of which a weaved instance should be provided
* @param location The location of the FXML view to load as a classloader resource. May be null or
* not resolvable, in which case the controller will be directly instantiated by the given
* bean factory.
* @param resourceBundle The optional {@link ResourceBundle} to use for view creation. May be null
* @param The view type
* @param The controller type
* @return A {@link SimpleFxControllerAndView} container with the managed instance of the requested controller and the
* corresponding view, if applicable
* @see #FxWeaver(Callback, Runnable)
* @see FXMLLoader
*/
protected FxControllerAndView load(Class controllerClass,
String location,
ResourceBundle resourceBundle) {
return Optional.ofNullable(location)
.map(controllerClass::getResource)
.map(url -> this.loadByView(url, resourceBundle))
.orElseGet(() -> SimpleFxControllerAndView.ofController(getBean(controllerClass)));
}
private FxControllerAndView loadByView(URL url, ResourceBundle resourceBundle) {
return loadByViewUsingFxmlLoader(new FXMLLoader(), url, resourceBundle);
}
FxControllerAndView loadByViewUsingFxmlLoader(FXMLLoader loader, URL url,
ResourceBundle resourceBundle) {
try (InputStream fxmlStream = url.openStream()) {
LOG.debug("Loading FXML resource at {}", url);
loader.setLocation(url);
loader.setControllerFactory(beanFactory);
if (resourceBundle != null) {
loader.setResources(resourceBundle);
}
V view = loader.load(fxmlStream);
return SimpleFxControllerAndView.of(loader.getController(), view);
} catch (IOException e) {
throw new FxLoadException("Unable to load FXML file " + url, e);
}
}
/**
* Build a FXML view location reference for controller classes, based on {@link FxmlView} annotation or simple
* classname.
*
* @param c The class to build a FXML location for. If it does not contain a {@link FxmlView} annotation to specify
* resource to load, it is assumed that the view resides in the same package, named
* {c.getSimpleName()}.fxml
* @return a resource location suitable for loading by {@link Class#getResource(String)}
*/
protected String buildFxmlReference(Class> c) {
return Optional.ofNullable(c.getAnnotation(FxmlView.class)).map(FxmlView::value)
.map(s -> s.isEmpty() ? null : s)
.orElse(c.getSimpleName() + ".fxml");
}
/**
* Perform the provided close method and call {@link Platform#exit()}.
*/
public void shutdown() {
closeCommand.run();
Platform.exit();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy