/*
* Copyright [2014] [Christian Loehnert, krampenschiesser@gmail.com]
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.ks.activity.initialization;
import de.ks.activity.ActivityCfg;
import de.ks.activity.ActivityController;
import de.ks.activity.context.ActivityScoped;
import de.ks.application.fxml.DefaultLoader;
import de.ks.eventsystem.bus.EventBus;
import de.ks.executor.JavaFXExecutorService;
import javafx.fxml.LoadException;
import javafx.scene.Node;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ActivityScoped
public class ActivityInitialization {
private static final Logger log = LoggerFactory.getLogger(ActivityInitialization.class);
protected final ConcurrentHashMap<Class<?>, LinkedList<Pair<Object, Node>>> controllers = new ConcurrentHashMap<>();
protected final Map<Class<?>, CompletableFuture<DefaultLoader<Node, Object>>> preloads = new HashMap<>();
protected final ThreadLocal<List<Object>> currentlyLoadedControllers = ThreadLocal.withInitial(ArrayList::new);
protected final List<DatasourceCallback> dataStoreCallbacks = new ArrayList<>();
protected final List<ActivityCallback> activityCallbacks = new ArrayList<>();
@Inject
ActivityController controller;
@Inject
EventBus eventBus;
public void loadActivity(ActivityCfg activityCfg) {
currentlyLoadedControllers.get().clear();
loadControllers(activityCfg);
initalizeControllers();
}
protected void loadControllers(ActivityCfg activityCfg) {
loadController(activityCfg.getInitialController());
activityCfg.getAdditionalControllers().forEach(this::loadController);
preloads.values().forEach(CompletableFuture::join);
}
private boolean shouldLoadInFXThread(Class<?> clazz) {
return clazz.isAnnotationPresent(LoadInFXThread.class);
}
@SuppressWarnings("unchecked")
public <T> DefaultLoader<Node, T> loadAdditionalController(Class<T> controllerClass) {
DefaultLoader<Node, T> loader = new DefaultLoader<>(controllerClass);
if (shouldLoadInFXThread(controllerClass)) {
loader = controller.getJavaFXExecutor().invokeInJavaFXThread(loader::load);
log.debug("Loaded additional controller {} in fx thread", controllerClass);
} else {
loader.load();
log.info("Loaded additional controller {} in current thread", controllerClass);
}
currentlyLoadedControllers.get().add(loader.getController());
return loader;
}
public <T> CompletableFuture<DefaultLoader<Node, T>> loadAdditionalControllerWithFuture(Class<T> controllerClass) {
@SuppressWarnings("unchecked") DefaultLoader<Node, T> loader = loadAdditionalController(controllerClass);
CompletableFuture completed = CompletableFuture.completedFuture(loader);
return completed;
}
private void loadController(Class<?> controllerClass) {
JavaFXExecutorService javaFXExecutor = controller.getJavaFXExecutor();
ExecutorService executorService;
if (shouldLoadInFXThread(controllerClass)) {
executorService = javaFXExecutor;
} else {
executorService = controller.getExecutorService();
}
if (!preloads.containsKey(controllerClass)) {
CompletableFuture<DefaultLoader<Node, Object>> loaderFuture = CompletableFuture.supplyAsync(getDefaultLoaderSupplier(controllerClass), executorService).exceptionally((t) -> {
if (t.getCause() instanceof RuntimeException && t.getCause().getCause() instanceof LoadException) {
currentlyLoadedControllers.get().forEach(eventBus::unregister);
currentlyLoadedControllers.get().clear();
log.info("Last load of {} failed, will try again in JavaFX Thread", new DefaultLoader<>(controllerClass).getFxmlFile());
return javaFXExecutor.invokeInJavaFXThread(() -> getDefaultLoaderSupplier(controllerClass).get());
}
throw new RuntimeException(t);
});
preloads.put(controllerClass, loaderFuture);
}
}
private Supplier<DefaultLoader<Node, Object>> getDefaultLoaderSupplier(Class<?> controllerClass) {
return () -> {
DefaultLoader<Node, Object> loader = new DefaultLoader<>(controllerClass);
loader.load();
Node view = loader.getView();
currentlyLoadedControllers.get().forEach((c) -> {
assert c != null;
log.debug("Registering controller {} with node {}", c, view);
controllers.putIfAbsent(c.getClass(), new LinkedList<>());
LinkedList<Pair<Object, Node>> registered = controllers.get(c.getClass());
boolean contained = registered.stream().filter(p -> p.getLeft().equals(c)).findAny().isPresent();
if (!contained) {
registered.add(Pair.of(c, view));
}
});
currentlyLoadedControllers.get().clear();
return loader;
};
}
public void addControllerToInitialize(Object controller) {
currentlyLoadedControllers.get().add(controller);
}
public void initalizeControllers() {
dataStoreCallbacks.clear();
List<DatasourceCallback> dsCallbacks = controllers.values().stream().map(l -> l.stream().map(Pair::getLeft).filter(o -> o instanceof DatasourceCallback).map(o -> (DatasourceCallback) o).collect(Collectors.toList())).reduce(new LinkedList<>(), (l, o) -> {
l.addAll(o);
return l;
});
dataStoreCallbacks.addAll(dsCallbacks);
activityCallbacks.clear();
List<ActivityCallback> acCallbacks = controllers.values().stream().map(l -> l.stream().map(Pair::getLeft).filter(o -> o instanceof ActivityCallback).map(o -> (ActivityCallback) o).collect(Collectors.toList())).reduce(new LinkedList<>(), (l, o) -> {
l.addAll(o);
return l;
});
activityCallbacks.addAll(acCallbacks);
Collections.sort(dataStoreCallbacks);
}
public Node getViewForController(Class<?> targetController) {
if (!controllers.containsKey(targetController)) {
throw new IllegalArgumentException("Controller " + targetController.getName() + " is not registered. Registered are " + controllers.keySet());
}
LinkedList<Pair<Object, Node>> ctrls = controllers.get(targetController);
if (ctrls.isEmpty()) {
return null;
} else if (ctrls.size() == 1) {
return ctrls.get(0).getRight();
} else {
throw new IllegalArgumentException("There are " + ctrls.size() + " instances registered for the given controller");
}
}
@SuppressWarnings("unchecked")
public <T> T getControllerInstance(Class<T> targetController) {
if (!controllers.containsKey(targetController)) {
throw new IllegalArgumentException("Controller " + targetController + " is not registered. Registered are " + controllers.keySet());
}
LinkedList<Pair<Object, Node>> ctrls = controllers.get(targetController);
if (ctrls.isEmpty()) {
return null;
} else if (ctrls.size() == 1) {
return (T) ctrls.get(0).getLeft();
} else {
throw new IllegalArgumentException("There are " + ctrls.size() + " instances registered for the given controller " + targetController.getName());
}
}
public <T> List<T> getControllerInstances(Class<T> targetController) {
if (!controllers.containsKey(targetController)) {
throw new IllegalArgumentException("Controller " + targetController + " is not registered. Registered are " + controllers.keySet());
}
return controllers.get(targetController).stream().map(Pair::getLeft).map(c -> (T) c).collect(Collectors.toList());
}
public Collection<Object> getControllers() {
return controllers.values().stream().map(l -> l.stream().map(Pair::getLeft).collect(Collectors.toList())).reduce(new LinkedList<>(), (l, o) -> {
l.addAll(o);
return l;
});
}
public List<DatasourceCallback> getDataStoreCallbacks() {
return dataStoreCallbacks;
}
public List<ActivityCallback> getActivityCallbacks() {
return activityCallbacks;
}
}