package org.intellimate.izou.output; import com.google.common.reflect.TypeToken; import org.intellimate.izou.events.EventModel; import org.intellimate.izou.identification.Identification; import org.intellimate.izou.identification.IdentificationManager; import org.intellimate.izou.identification.IdentificationManagerM; import org.intellimate.izou.identification.IllegalIDException; import org.intellimate.izou.main.Main; import org.intellimate.izou.resource.ResourceMinimalImpl; import org.intellimate.izou.util.AddonThreadPoolUser; import org.intellimate.izou.util.IdentifiableSet; import org.intellimate.izou.util.IzouModule; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; /** * OutputManager manages all output plugins and is the main class anyone outside the output package should talk to. * It can register/remove new output-plugins and add/delete output-extensions */ public class OutputManager extends IzouModule implements AddonThreadPoolUser { /** * a list that contains all the registered output-plugins of Jarvis */ private IdentifiableSet<OutputPluginModel<?, ?>> outputPlugins; /** * HashMap that stores OutputPlugins and the Future objects representing the Task */ private HashMap<String, Future> futureHashMap; /** * this HashMap stores all added OutputExtensions */ private HashMap<String, IdentifiableSet<OutputExtensionModel<?, ?>>> outputExtensions; /** * Creates a new output-manager with a list of output-plugins * * @param main the main instance started from */ public OutputManager(Main main) { super(main); outputPlugins = new IdentifiableSet<>(); futureHashMap = new HashMap<>(); outputExtensions = new HashMap<>(); } /** * adds outputPlugin to outputPluginList, starts a new thread for the outputPlugin, and stores the future object in a HashMap * * @param outputPlugin OutputPlugin to add * @throws IllegalIDException not yet implemented */ public void addOutputPlugin(OutputPluginModel<?, ?> outputPlugin) throws IllegalIDException { if (!futureHashMap.containsKey(outputPlugin.getID())) { outputPlugins.add(outputPlugin); futureHashMap.put(outputPlugin.getID(), submit(outputPlugin)); } else { if (futureHashMap.get(outputPlugin.getID()).isDone()) { futureHashMap.remove(outputPlugin.getID()); futureHashMap.put(outputPlugin.getID(), submit(outputPlugin)); } } } /** * removes the OutputPlugin and stops the thread * * @param outputPlugin the outputPlugin to remove */ public void removeOutputPlugin(OutputPluginModel outputPlugin) { Future future = futureHashMap.remove(outputPlugin.getID()); if (future != null) { future.cancel(true); } outputPlugins.remove(outputPlugin); } /** * adds output extension to desired outputPlugin * <p> * adds output extension to desired outputPlugin, so that the output-plugin can start and stop the outputExtension * task as needed. The outputExtension is specific to the output-plugin * * @param outputExtension the outputExtension to be added * @throws IllegalIDException not yet implemented */ public void addOutputExtension(OutputExtensionModel<?, ?> outputExtension) throws IllegalIDException { if (outputExtensions.containsKey(outputExtension.getPluginId())) { outputExtensions.get(outputExtension.getPluginId()).add(outputExtension); } else { IdentifiableSet<OutputExtensionModel<?, ?>> outputExtensionList = new IdentifiableSet<>(); outputExtensionList.add(outputExtension); outputExtensions.put(outputExtension.getPluginId(), outputExtensionList); } IdentificationManager.getInstance().getIdentification(outputExtension) .ifPresent(id -> outputPlugins.stream() .filter(outputPlugin -> outputPlugin.getID().equals(outputExtension.getPluginId())) .forEach(outputPlugin -> outputPlugin.outputExtensionAdded(id))); } /** * removes the output-extension of id: extensionId from outputPluginList * * @param outputExtension the OutputExtension to remove */ public void removeOutputExtension(OutputExtensionModel<?, ?> outputExtension) { IdentifiableSet<OutputExtensionModel<?, ?>> outputExtensions = this.outputExtensions.get(outputExtension.getPluginId()); if (outputExtensions != null) outputExtensions.remove(outputExtension); IdentificationManager.getInstance().getIdentification(outputExtension) .ifPresent(id -> outputPlugins.stream() .filter(outputPlugin -> outputPlugin.getID().equals(outputExtension.getPluginId())) .forEach(outputPlugin -> outputPlugin.outputExtensionRemoved(id))); } /** * gets the Event and sends it to the right outputPlugin for further processing * <p> * passDataToOutputPlugins is the main method of OutputManger. It is called whenever the output process has to be started * * @param event an Instance of Event */ public void passDataToOutputPlugins(EventModel event) { IdentificationManagerM identificationManager = IdentificationManager.getInstance(); List<Identification> allIds = outputPlugins.stream() .map(identificationManager::getIdentification) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); HashMap<Integer, List<Identification>> outputPluginBehaviour = event.getEventBehaviourController() .getOutputPluginBehaviour(allIds); @SuppressWarnings("unchecked") Set<OutputPluginModel> outputPluginsCopy = (Set<OutputPluginModel>) this.outputPlugins.clone(); Function<List<Identification>, List<OutputPluginModel>> getOutputPlugin = ids -> ids.stream() .map(id -> outputPluginsCopy.stream() .filter(outputPlugin -> outputPlugin.isOwner(id)) .findFirst() .orElseGet(null)) .filter(Objects::nonNull) .peek(outputPluginsCopy::remove) .collect(Collectors.toList()); outputPluginBehaviour.entrySet().stream() .sorted(Comparator.comparingInt((Map.Entry<Integer, List<Identification>> x) -> x.getKey()).reversed()) .flatMap(entry -> getOutputPlugin.apply(entry.getValue()).stream()) .distinct() .forEach(op -> processOutputPlugin(event, op)); outputPluginsCopy.forEach(op -> processOutputPlugin(event, op)); } private void processOutputPlugin(EventModel event, OutputPluginModel outputPlugin) { //debug("processing outputPlugin: " + outputPlugin.getID() + " for event: " + event.getDescriptors().toString()); final Lock lock = new ReentrantLock(); final Condition processing = lock.newCondition(); Consumer<Boolean> consumer = noParam -> { lock.lock(); processing.signal(); lock.unlock(); }; ResourceMinimalImpl<Consumer<Boolean>> resource = IdentificationManager.getInstance().getIdentification(this) .map(id -> new ResourceMinimalImpl<>(outputPlugin.getID(), id, consumer, null)) .orElse(new ResourceMinimalImpl<>(outputPlugin.getID(), null, consumer, null)); event.getListResourceContainer().addResource(resource); outputPlugin.addToEventList(event); boolean finished = false; try { lock.lock(); finished = processing.await(100, TimeUnit.SECONDS); } catch (InterruptedException e) { error("Waiting for OutputPlugins interrupted", e); } finally { lock.unlock(); } if (finished) { //debug("OutputPlugin: " + outputPlugin.getID() + " finished"); } else { error("OutputPlugin: " + outputPlugin.getID() + " timed out"); } } /** * returns all the associated OutputExtensions * * @param outputPlugin the OutputPlugin to search for * @return a List of Identifications */ public List<Identification> getAssociatedOutputExtension(OutputPluginModel<?, ?> outputPlugin) { IdentifiableSet<OutputExtensionModel<?, ?>> outputExtensions = this.outputExtensions.get(outputPlugin.getID()); IdentificationManagerM identificationManager = IdentificationManager.getInstance(); return filterType(outputExtensions, outputPlugin).stream() .map(identificationManager::getIdentification) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); } /** * checks for the right type * * @param outputExtensions the OutputExtensions to check * @param outputPlugin the OutputPlugin to check against * @return a List of filtered OutputExtensions */ //TODO: TEST!!!! @SuppressWarnings("SimplifiableIfStatement") private List<OutputExtensionModel<?, ?>> filterType(Collection<OutputExtensionModel<?, ?>> outputExtensions, OutputPluginModel<?, ?> outputPlugin) { BiPredicate<TypeToken<?>, TypeToken<?>> isAssignable = (first, second) -> { if (first == null) { return second == null; } else if (second != null) { return first.isAssignableFrom(second); } else { return false; } }; return outputExtensions.stream() .filter(outputExtension -> isAssignable.test(outputExtension.getArgumentType(), outputPlugin.getArgumentType())) .filter(outputExtension -> isAssignable.test(outputExtension.getReturnType(), outputPlugin.getReceivingType())) .collect(Collectors.toList()); } /** * Starts every associated OutputExtension * * @param outputPlugin the OutputPlugin to generate the Data for * @param t the argument or null * @param event the Event to generate for * @param <T> the type of the argument * @param <X> the return type * @return a List of Future-Objects */ public <T, X> List<CompletableFuture<X>> generateAllOutputExtensions(OutputPluginModel<T, X> outputPlugin, T t, EventModel event) { IdentifiableSet<OutputExtensionModel<?, ?>> extensions = outputExtensions.get(outputPlugin.getID()); if (extensions == null) return new ArrayList<>(); return filterType(extensions, outputPlugin).stream() .map(extension -> { try { //noinspection unchecked return (OutputExtensionModel<X, T>) extension; } catch (ClassCastException e) { return null; } }) .filter(Objects::nonNull) .filter(outputExtension -> outputExtension.canRun(event)) .map(extension -> submit(() -> extension.generate(event, t))) .collect(Collectors.toList()); } }