All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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