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

bayern.steinbrecher.wizard.WizardPage Maven / Gradle / Ivy

Go to download

Contains a library to create dynamic and branching JavaFX wizards in an abstract way. Comes with a predefined collection of typical wizard pages.

There is a newer version: 1.60
Show newest version
package bayern.steinbrecher.wizard;

import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.fxml.FXMLLoader;
import javafx.fxml.LoadException;
import javafx.scene.Parent;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Represents a class which can be in a {@link Wizard}.
 *
 * @param  The type of the result of the {@link EmbeddedWizardPage}.
 * @param  The type of the controller used by the {@link WizardPage}.
 * @author Stefan Huber
 * @since 1.2
 */
public abstract class WizardPage, C extends WizardPageController> {

    /**
     * The key of the page to be used as first one.
     */
    public static final String FIRST_PAGE_KEY = "first";
    private static final Logger LOGGER = Logger.getLogger(WizardPage.class.getName());
    private final String fxmlPath;
    private final ResourceBundle bundle;
    private final ReadOnlyObjectWrapper> nextFunction = new ReadOnlyObjectWrapper<>();
    private final ReadOnlyBooleanWrapper finish = new ReadOnlyBooleanWrapper();
    private C controller;

    /**
     * @since 1.13
     */
    protected WizardPage(@NotNull String fxmlPath, @Nullable ResourceBundle bundle) {
        this.fxmlPath = Objects.requireNonNull(fxmlPath);
        this.bundle = bundle;
    }

    Parent loadFXML() throws LoadException {
        URL resource = getClass().getResource(fxmlPath);
        if (resource == null) {
            throw new LoadException(
                    new FileNotFoundException(
                            String.format("The class %s can not find the resource %s", getClass().getName(), fxmlPath)
                    )
            );
        } else {
            FXMLLoader fxmlLoader = new FXMLLoader(resource, bundle);
            Parent root;
            try {
                root = fxmlLoader.load();
            } catch (IOException ex) {
                throw new LoadException(ex);
            }
            controller = fxmlLoader.getController();
            afterControllerInitialized();
            return root;
        }
    }

    /**
     * This method is executed after the FXML is loaded and right after the corresponding controller is set. This
     * function represents an equivalent to a FXML controllers {@code initialize()} method. The FXML itself is loaded
     * when and only if this page gets embedded into a {@link Wizard} (see {@link #generateEmbeddableWizardPage()}.
     *
     * @since 1.8
     */
    protected void afterControllerInitialized() {
        // No op
    }

    /**
     * Creates a {@link EmbeddedWizardPage}. The nextFunction returns always {@code null} and isFinish is set to
     * {@code true}.
     *
     * @return The newly created {@link EmbeddedWizardPage}.
     */
    @NotNull
    @Contract("-> new")
    final EmbeddedWizardPage generateEmbeddableWizardPage() throws LoadException {
        return new EmbeddedWizardPage<>(this);
    }

    public T getResult() {
        if (getController() == null) {
            throw new IllegalStateException(
                    "The controller is not available yet. Was the page embedded and at least shown once?");
        }
        return getController()
                .getResult();
    }

    @NotNull
    public ReadOnlyObjectProperty> nextFunctionProperty() {
        return nextFunction.getReadOnlyProperty();
    }

    /**
     * Returns the function calculating the key of the next page.
     *
     * @return The function calculating the key of the next page. Returns {@code null} if this page has no next one.
     */
    @Nullable
    public Supplier getNextFunction() {
        return nextFunctionProperty().getValue();
    }

    public ReadOnlyBooleanProperty finishProperty() {
        return finish.getReadOnlyProperty();
    }

    public boolean isFinish() {
        return finishProperty().get();
    }

    /**
     * @param finish       {@code true} only if this page is a last one.
     * @param nextFunction The function calculating the name of the next page. In case {@code finish} is
     *                     {@code true} this value is allowed to be {@code null}.
     * @since 1.27
     */
    public void setFinishAndNext(boolean finish, @Nullable Supplier nextFunction) {
        if (!finish) {
            Objects.requireNonNull(nextFunction,
                    "A non-last page must define a function which calculates the next page.");
        }
        this.finish.set(finish);
        this.nextFunction.setValue(nextFunction);
    }

    /**
     * This method allows to use the result of one {@link WizardPage} as the input for another {@link WizardPage}.
     * However, if there are multiple {@link WizardPage}s that depend on previous {@link WizardPage}s then using this
     * method results in heavily nested and thus hard to read source code. Instead if a {@link WizardPage} depends on
     * input of other {@link WizardPage}s it should either provide setters that can be called in the next-function or
     * provide a constructor that has parameters of type {@link Future} only. This allows to avoid this method entirely
     * clarify the structure of the {@link Wizard}.
     *
     * @return A {@link CompletableFuture} object which contains the dynamically generated next page as soon as the
     * {@link Wizard} this page belongs to tries to access its next page.
     * @see #setFinishAndNext(boolean, Supplier)
     * @since 1.27
     */
    @Deprecated(forRemoval = true, since = "1.52")
    public , C extends WizardPageController> CompletableFuture> setFinishAndDynamicNext(
            boolean finish, @Nullable Supplier> dynamicNextFunction, @NotNull String pageID,
            @NotNull Wizard containingWizard) {
        CompletableFuture> wizardPageCreation = new CompletableFuture<>();
        Supplier nextFunction;
        if (dynamicNextFunction == null) {
            nextFunction = null;
            wizardPageCreation.completeExceptionally(
                    new NoSuchElementException(
                            "This wizard page has no next function which could have dynamically created the next "
                                    + "wizard page."));
        } else {
            nextFunction = () -> {
                WizardPage wizardPage = dynamicNextFunction.get();
                containingWizard.putPage(pageID, wizardPage);
                wizardPageCreation.complete(wizardPage);
                if (wizardPageCreation.isCompletedExceptionally()) {
                    Throwable creationException;
                    try {
                        creationException = wizardPageCreation.handle((noResult, ex) -> ex)
                                .get();
                    } catch (InterruptedException | ExecutionException ex) {
                        creationException = ex;
                    }
                    LOGGER.log(Level.SEVERE, "The dynamic creation of a wizard page failed", creationException);
                }
                return pageID;
            };
        }
        setFinishAndNext(finish, nextFunction);
        return wizardPageCreation;
    }

    public ReadOnlyBooleanProperty validProperty() {
        if (getController() == null) {
            throw new IllegalStateException(
                    "The controller is not available yet. Was the page embedded and at least shown once?");
        }
        return getController()
                .validProperty();
    }

    public boolean isValid() {
        return validProperty()
                .get();
    }

    protected C getController() {
        return controller;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy