package com.insightfullogic.honest_profiler.ports.javafx.controller; import static javafx.beans.binding.Bindings.createObjectBinding; import java.util.function.Function; import com.insightfullogic.honest_profiler.core.aggregation.grouping.CombinedGrouping; import com.insightfullogic.honest_profiler.core.aggregation.result.ItemType; import com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext; import javafx.beans.binding.ObjectBinding; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableObjectValue; /** * Superclass for all View Controllers in the application which provide a view on a "target", a data structure of type T * which is stored in the target property. This target is extracted from a source {@link ObservableObjectValue} using an * extractor function. * <p> * This superclass also serves as a repository for the {@link ProfileContext} associated to the profile for which the * View is shown. * <p> * This superclass ensures that subclass refresh() implementations are called when the source * {@link ObservableObjectValue} or the {@link CombinedGrouping} from the {@link AbstractViewController} superclass are * updated. The extractor function is then used to extract a new target from the source, using the new * {@link CombinedGrouping} if available. * <p> * By binding or unbinding the local target, it is possible to start and stop all tracking of changes to the target in * the UI. This has been provided to make it possible to stop executing refresh() and other UI updates when the view * associated to the controller is hidden. * <p> * @param <T> the data type of the target * @param <U> the type of the items contained in the View */ public abstract class AbstractProfileViewController<T, U> extends AbstractViewController<U> { // Instance Properties private ProfileContext profileContext; private ObjectProperty<T> target; private ObjectBinding<T> sourceBinding; // FXML Implementation /** * Initialize method for subclasses which sets the basic properties needed by this superclass. This method must be * called by such subclasses in their FXML initialize(). * <p> * @param type the {@link ItemType} of the items shown in the view */ @Override protected void initialize(ItemType type) { super.initialize(type); target = new SimpleObjectProperty<>(); target.addListener((property, oldValue, newValue) -> refresh()); } // Instance Accessors /** * Returns the {@link ProfileContext}. The name has been shortened to unclutter code in subclasses. * <p> * @return the {@link ProfileContext} encapsulating the target. */ protected ProfileContext prfCtx() { return profileContext; } /** * Sets the {@link ProfileContext} encapsulating the target. * <p> * @param profileContext the {@link ProfileContext} encapsulating the target. */ public void setProfileContext(ProfileContext profileContext) { this.profileContext = profileContext; initializeTable(); } /** * Returns the current target instance. * <p> * @return the current target instance */ protected T getTarget() { return target.get(); } // Source-Target Binding /** * Bind the supplied extractor function which extracts the target data structure T from the source to the source * {@link ObservableObjectValue}, and optionally to the {@link CombinedGrouping} {@link ObservableObjectValue} from * the {@link AbstractViewController} superclass if present. * <p> * @param source the {@link ObservableObjectValue} encapsulating the source from which the target data structure can * be extracted * @param targetExtractor a function which extracts the target from the source Object */ public void bind(ObservableObjectValue<? extends Object> source, Function<Object, T> targetExtractor) { // The createObjectBinding() dependency varargs parameter specifies a number of Observables. If the value of any // of those changes, the Binding is triggered and the specified function is executed. This is IMHO not so // clearly documented in the createObjectBinding() javadoc. // The View does not support CombinedGrouping. if (getGrouping() == null) { sourceBinding = createObjectBinding(() -> targetExtractor.apply(source.get()), source); } // The View supports CombinedGrouping. else { sourceBinding = createObjectBinding( () -> targetExtractor.apply(source.get()), source, getGrouping()); } } // Activation Methods /** * Activate or deactivate the current view. When activated, the view tracks changes in the target. * <p> * @param active a boolean indicating whether to activate or deactivate the view. */ public void setActive(boolean active) { if (active) { // Binds the local target ObjectProperty to the sourceBinding created with the bind() method. The net effect // is that the controller will start tracking changes to the target instance. target.bind(sourceBinding); } else { // Unbinds the local target ObjectProperty. The controller no longer tracks changes to the source // ObservableObjectvalue. target.unbind(); } } // AbstractViewController Implementation /** * Override doing nothing. The {@link AbstractProfileViewController} implementations have fixed column headings * defined in the FXML. */ @Override protected <C> void setColumnHeader(C column, String title, ProfileContext context) { // NOOP } }