Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.tentackle.fx.DefaultFxFactory Maven / Gradle / Ivy
/*
* Tentackle - https://tentackle.org.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package org.tentackle.fx;
import javafx.concurrent.Task;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.image.Image;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;
import javafx.util.BuilderFactory;
import org.tentackle.common.BundleFactory;
import org.tentackle.common.Constants;
import org.tentackle.common.LocaleProvider;
import org.tentackle.common.Service;
import org.tentackle.common.ServiceFactory;
import org.tentackle.fx.table.DefaultTableConfiguration;
import org.tentackle.fx.table.TableConfiguration;
import org.tentackle.log.Logger;
import org.tentackle.reflect.DefaultClassMapper;
import org.tentackle.reflect.ReflectionHelper;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
/**
* Default implementation of an fx factory.
*
* @author harald
*/
@Service(FxFactory.class)
public class DefaultFxFactory implements FxFactory {
private static final Logger LOGGER = Logger.get(DefaultFxFactory.class);
/**
* The builder factory.
*/
private final BuilderFactory builderFactory;
/**
* All configurators mapped by the classname served.
*/
private final Map> configurators;
/**
* Map of view's class to classmapper of value-translators.
*/
private final Map, DefaultClassMapper> viewTranslatorMap;
/**
* Map of FX control class to evaluated configurator.
*/
private final Map, Optional>> fxToConfiguratorMap; // Optional to denote: not present, null if not determined yet
/**
* all FX controller classes.
*/
private final Collection> controllerClasses;
/**
* FX controller singletons.
*/
private final Map, FxController> controllers;
/**
* Map of realm to image providers.
*/
private final Map imageProviders;
/**
* Creates the default factory.
*/
@SuppressWarnings("rawtypes")
public DefaultFxFactory() {
builderFactory = createBuilderFactory();
try {
controllerClasses = ServiceFactory.getServiceFinder().findServiceProviders(FxController.class);
}
catch (ClassNotFoundException ex) {
throw new FxRuntimeException("loading FX controller classes failed", ex);
}
controllers = new HashMap<>();
configurators = new HashMap<>();
Map serviceMap = ServiceFactory.getServiceFinder().createNameMap(Configurator.class.getName());
for (Map.Entry entry: serviceMap.entrySet()) {
try {
@SuppressWarnings("unchecked")
Class extends Configurator>> configuratorClass = (Class extends Configurator>>) Class.forName(entry.getValue());
configurators.put(entry.getKey(), configuratorClass.getDeclaredConstructor().newInstance());
}
catch (ClassNotFoundException | IllegalAccessException | InstantiationException |
InvocationTargetException | NoSuchMethodException ex) {
throw new FxRuntimeException(ex);
}
}
fxToConfiguratorMap = new HashMap<>();
imageProviders = new HashMap<>();
try {
for (Class clazz: ServiceFactory.getServiceFinder().findServiceProviders(ImageProvider.class)) {
ImageProviderService svc = clazz.getAnnotation(ImageProviderService.class);
if (svc != null) {
imageProviders.putIfAbsent(svc.value(), clazz.getDeclaredConstructor().newInstance());
}
else {
LOGGER.severe("{0} not annotated with @ImageProviderService", clazz);
}
}
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException |
InvocationTargetException | NoSuchMethodException ex) {
throw new FxRuntimeException("loading image providers failed", ex);
}
Map, Class> translatorMap = new HashMap<>();
try {
for (Class clazz: ServiceFactory.getServiceFinder().findServiceProviders(ValueTranslator.class)) {
ValueTranslatorService svc = clazz.getAnnotation(ValueTranslatorService.class);
if (svc != null) {
ValueTranslatorKey,?> key = new ValueTranslatorKey<>(svc.modelClass(), svc.viewClass());
translatorMap.putIfAbsent(key, clazz);
}
else {
LOGGER.severe("{0} not annotated with @ValueTranslatorService", clazz);
}
}
}
catch (ClassNotFoundException ex) {
throw new FxRuntimeException("loading value translators failed", ex);
}
viewTranslatorMap = new HashMap<>();
for (Map.Entry, Class> entry: translatorMap.entrySet()) {
Class> viewClass = entry.getKey().getViewClass();
DefaultClassMapper translators = viewTranslatorMap.computeIfAbsent(viewClass, k ->
new DefaultClassMapper(
ReflectionHelper.getClassBaseName(viewClass) + "-translator",
ServiceFactory.getClassLoader(Constants.DEFAULT_SERVICE_PATH, ValueTranslator.class.getName()),
new HashMap<>(), null));
translators.getNameMap().put(entry.getKey().getModelClass().getName(), entry.getValue().getName());
}
}
@Override
public BuilderFactory getBuilderFactory() {
return builderFactory;
}
@Override
@SuppressWarnings("unchecked")
public Configurator getConfigurator(Class clazz) {
Configurator> configurator = null;
Optional> optional = fxToConfiguratorMap.get(clazz);
if (optional != null) {
configurator = optional.orElse(null);
}
else {
Class> cls = clazz;
while (cls != Object.class) {
configurator = configurators.get(cls.getName());
if (configurator != null) {
break;
}
cls = cls.getSuperclass();
}
fxToConfiguratorMap.put(clazz, Optional.ofNullable(configurator));
}
return (Configurator) configurator;
}
@Override
@SuppressWarnings("unchecked")
public ValueTranslator createValueTranslator(Class modelClass, Class viewClass, FxComponent component) {
if (modelClass.isPrimitive()) {
modelClass = (Class) ReflectionHelper.primitiveToWrapperClass(modelClass);
}
DefaultClassMapper mapper = viewTranslatorMap.get(viewClass);
if (mapper == null) {
throw new FxRuntimeException("no value translators for view " + viewClass);
}
try {
Class> clazz = (Class>) mapper.mapLenient(modelClass);
for (Constructor> cons: clazz.getConstructors()) {
if (cons.getParameterCount() == 1 &&
FxComponent.class.isAssignableFrom(cons.getParameters()[0].getType())) {
return (ValueTranslator) cons.newInstance(component);
}
if (cons.getParameterCount() == 2 &&
FxComponent.class.isAssignableFrom(cons.getParameters()[0].getType()) &&
cons.getParameters()[1].getType() == Class.class) {
return (ValueTranslator) cons.newInstance(component, modelClass);
}
}
throw new ClassNotFoundException("no matching constructor found for " + clazz);
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
throw new FxRuntimeException("could not create value translator for view " + viewClass + " to model " + modelClass, ex);
}
}
@Override
public Stage createStage(StageStyle stageStyle, Modality modality) {
Stage stage = new Stage(stageStyle);
stage.initModality(modality);
getConfigurator(Window.class).configure(stage);
return stage;
}
@Override
public Scene createScene(Parent root) {
Scene scene = new Scene(root);
FxUtilities.getInstance().applyStylesheets(scene);
return scene;
}
@Override
public Alert createAlert(Alert.AlertType alertType) {
Alert alert = new Alert(alertType);
FxUtilities.getInstance().applyStylesheets(alert.getDialogPane().getScene());
return alert;
}
@Override
public synchronized T createController(
Class controllerClass, URL fxmlUrl, ResourceBundle resources, URL cssUrl) {
@SuppressWarnings("unchecked")
T controller = (T) controllers.get(controllerClass);
if (controller != null) {
if (controller.getView().isVisible()) {
throw new FxRuntimeException(controllerClass + " is a singleton and already visible");
}
return controller;
}
FxControllerService service = null;
Class super T> annotatedClass = controllerClass;
while (annotatedClass != null) {
service = annotatedClass.getDeclaredAnnotation(FxControllerService.class);
if (service != null) {
break;
}
annotatedClass = annotatedClass.getSuperclass();
}
if (service == null) {
throw new FxRuntimeException("missing annotation @FxControllerService for controller " + controllerClass.getName());
}
boolean singleton = service.caching() != FxControllerService.CACHING.NO;
if (fxmlUrl == null) {
String urlStr = service.url();
if (!FxControllerService.FXML_NONE.equals(urlStr)) {
if (!urlStr.isEmpty()) {
fxmlUrl = annotatedClass.getResource(urlStr);
if (fxmlUrl == null) {
throw new FxRuntimeException("no such URL '" + urlStr +
"' -> check @FxControllerService of " + annotatedClass.getName());
}
}
else {
urlStr = ReflectionHelper.getClassBaseName(annotatedClass) + ".fxml";
fxmlUrl = annotatedClass.getResource(urlStr);
if (fxmlUrl == null) {
throw new FxRuntimeException("no such default URL '" + urlStr + "'");
}
}
}
}
if (resources == null) {
String resourcesStr = service.resources();
if (resourcesStr.isEmpty()) {
resourcesStr = annotatedClass.getName();
}
if (!FxControllerService.RESOURCES_NONE.equals(resourcesStr)) {
resources = BundleFactory.getBundle(resourcesStr, LocaleProvider.getInstance().getLocale());
}
}
try {
Parent view;
if (fxmlUrl == null) {
controller = resources == null ?
controllerClass.getConstructor().newInstance() :
controllerClass.getConstructor(ResourceBundle.class).newInstance(resources);
view = controller.getView();
}
else {
FXMLLoader loader = new FXMLLoader(fxmlUrl, resources, getBuilderFactory());
if (annotatedClass != controllerClass) {
loader.setControllerFactory(cls -> {
try {
return controllerClass.getConstructor().newInstance();
}
catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException cx) {
throw new FxRuntimeException("cannot create controller for " + controllerClass, cx);
}
});
}
view = loader.load();
controller = loader.getController();
controller.setView(view);
}
if (view instanceof FxContainer) {
((FxContainer) view).setController(controller);
}
if (singleton) {
controllers.put(controllerClass, controller);
LOGGER.info("controller {0} added to singletons", () -> ReflectionHelper.getClassBaseName(controllerClass));
}
if (cssUrl == null) {
String cssName = service.css();
if (cssName.isEmpty()) {
cssName = ReflectionHelper.getClassBaseName(controllerClass) + ".css";
}
cssUrl = controllerClass.getResource(cssName);
}
if (cssUrl != null) {
view.getStylesheets().add(cssUrl.toExternalForm());
}
switch (service.binding()) {
case YES:
controller.getBinder().bind();
break;
case COMPONENT_INHERITED:
controller.getBinder().bindWithInheritedComponents();
break;
case BINDABLE_INHERITED:
controller.getBinder().bindWithInheritedBindables();
break;
case ALL_INHERITED:
controller.getBinder().bindAllInherited();
break;
default:
// NO
}
controller.configure();
return controller;
}
catch (IOException iox) {
throw new FxRuntimeException("loading controller " + controllerClass + " failed", iox);
}
catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
throw new FxRuntimeException("creating controller instance " + controllerClass + " failed", ex);
}
catch (Throwable t) {
LOGGER.severe("loading controller failed", t);
throw t;
}
}
@Override
public Collection> getControllerClasses() {
return controllerClasses;
}
@Override
public void preloadControllers() {
Collection> preloadClasses = new ArrayList<>();
for (Class clazz: getControllerClasses()) {
FxControllerService service = clazz.getAnnotation(FxControllerService.class); // cannot be null!
if (service.caching() == FxControllerService.CACHING.PRELOAD) {
preloadClasses.add(clazz);
}
}
javafx.concurrent.Service preloadSvc = new javafx.concurrent.Service<>() {
@Override
protected Task createTask() {
return new Task<>() {
@Override
protected Void call() throws Exception {
for (Class clazz : preloadClasses) {
createController(clazz, null, null, null);
}
return null;
}
};
}
};
preloadSvc.start();
}
@Override
public Image getImage(String realm, String name) {
if (realm == null) {
realm = "";
}
ImageProvider provider = imageProviders.get(realm);
if (provider == null) {
throw new IllegalArgumentException("no image provider for realm '" + realm + "'");
}
return provider.getImage(name);
}
@Override
public TableConfiguration createTableConfiguration(S template, String name) {
return new DefaultTableConfiguration<>(template, name);
}
@Override
public TableConfiguration createTableConfiguration(Class objectClass, String name) {
return new DefaultTableConfiguration<>(objectClass, name);
}
/**
* Creates the builder factory.
*
* @return the factory
*/
protected BuilderFactory createBuilderFactory() {
return new FxBuilderFactory();
}
}