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

com.speedment.tool.core.internal.component.UserInterfaceComponentImpl Maven / Gradle / Ivy

Go to download

A Speedment bundle that shades all dependencies into one jar. This is useful when deploying an application on a server.

The newest version!
/*
 *
 * Copyright (c) 2006-2019, Speedment, Inc. All Rights Reserved.
 *
 * 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 com.speedment.tool.core.internal.component;

import com.speedment.common.injector.InjectBundle;
import com.speedment.common.injector.Injector;
import com.speedment.common.injector.annotation.Inject;
import com.speedment.common.logger.Logger;
import com.speedment.common.logger.LoggerManager;
import com.speedment.generator.translator.TranslatorSupport;
import com.speedment.runtime.config.Dbms;
import com.speedment.runtime.config.Project;
import com.speedment.runtime.config.Schema;
import com.speedment.runtime.config.internal.immutable.ImmutableProject;
import com.speedment.runtime.core.component.InfoComponent;
import com.speedment.runtime.core.component.PasswordComponent;
import com.speedment.runtime.core.component.ProjectComponent;
import com.speedment.runtime.core.internal.util.Statistics;
import com.speedment.runtime.core.util.ProgressMeasure;
import com.speedment.tool.config.DbmsProperty;
import com.speedment.tool.config.DocumentProperty;
import com.speedment.tool.config.ProjectProperty;
import com.speedment.tool.config.component.DocumentPropertyComponent;
import com.speedment.tool.config.internal.component.DocumentPropertyComponentImpl;
import com.speedment.tool.core.MainApp;
import com.speedment.tool.core.brand.Palette;
import com.speedment.tool.core.component.RuleComponent;
import com.speedment.tool.core.component.UserInterfaceComponent;
import com.speedment.tool.core.internal.brand.SpeedmentBrand;
import com.speedment.tool.core.internal.notification.NotificationImpl;
import com.speedment.tool.core.internal.util.ConfigFileHelper;
import com.speedment.tool.core.internal.util.InjectionLoaderImpl;
import com.speedment.tool.core.internal.util.Throttler;
import com.speedment.tool.core.notification.Notification;
import com.speedment.tool.core.resource.FontAwesome;
import com.speedment.tool.core.resource.Icon;
import com.speedment.tool.core.util.BrandUtil;
import com.speedment.tool.core.util.InjectionLoader;
import com.speedment.tool.core.util.OutputUtil;
import com.speedment.tool.propertyeditor.PropertyEditor;
import com.speedment.tool.propertyeditor.internal.component.PropertyEditorComponentImpl;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Pair;

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;

import static com.speedment.runtime.core.internal.util.Statistics.Event.GUI_PROJECT_LOADED;
import static com.speedment.runtime.core.internal.util.Statistics.Event.GUI_STARTED;
import static java.util.Objects.requireNonNull;
import static javafx.application.Platform.runLater;

/**
 *
 * @author Emil Forslund
 */
public final class UserInterfaceComponentImpl implements UserInterfaceComponent {
    
    private static final Logger LOGGER = LoggerManager.getLogger(UserInterfaceComponentImpl.class);

    private static final String MANUAL_URI = "https://speedment.github.io/speedment-doc/index.html";
    private static final String GITTER_URI = "https://gitter.im/speedment/speedment/";
    private static final String ISSUE_URI = "https://github.com/speedment/speedment/issues/new";
    private static final String GITHUB_URI = "https://github.com/speedment/speedment/";

    private static final Predicate> NO_PASSWORD_SPECIFIED
        = pass -> !pass.isPresent() || pass.get().length == 0;

    private final BooleanProperty projectTreeVisible = new SimpleBooleanProperty(true);
    private final BooleanProperty workspaceVisible   = new SimpleBooleanProperty(true);
    private final BooleanProperty outputVisible      = new SimpleBooleanProperty(false);

    private final ObservableList notifications;
    private final ObservableList outputMessages;
    private final ObservableList> selectedTreeItems;
    private final ObservableList properties;
    
    private final AtomicBoolean canGenerate;
    
    @Inject private DocumentPropertyComponent documentPropertyComponent;
    @Inject private PasswordComponent passwordComponent;
    @Inject private ProjectComponent projectComponent;
    @Inject private ConfigFileHelper configFileHelper;
    @Inject private InjectionLoader loader;
    @Inject private RuleComponent rules;
    @Inject private InfoComponent info;
    
    @Inject private Injector injector;
    
    private Stage stage;
    private Application application;
    private ProjectProperty project;

    private UserInterfaceComponentImpl() {
        notifications     = FXCollections.observableArrayList();
        outputMessages    = FXCollections.observableArrayList();
        selectedTreeItems = FXCollections.observableArrayList();
        properties        = FXCollections.observableArrayList();
        canGenerate       = new AtomicBoolean(true);
    }

    public static InjectBundle include() {
        return InjectBundle.of(
            DocumentPropertyComponentImpl.class,
            SpeedmentBrand.class,
            InjectionLoaderImpl.class,
            ConfigFileHelper.class,
            PropertyEditorComponentImpl.class,
            RuleComponentImpl.class,
            IssueComponentImpl.class
        );
    }
    
    public void start(Application application, Stage stage) {
        this.stage       = requireNonNull(stage);
        this.application = requireNonNull(application);
        this.project     = new ProjectProperty();

        final Throttler throttler = Throttler.limitToOnceEvery(2_000);
        LoggerManager.getFactory().addListener(ev -> {
            switch (ev.getLevel()) {
                case DEBUG : case TRACE : case INFO : {
                    addToOutputMessages(OutputUtil.info(ev.getMessage()));
                    break;
                }
                case WARN : {
                    addToOutputMessages(OutputUtil.warning(ev.getMessage()));
                    final String title = "There are warnings. See output.";
                    throttler.call(title, () ->
                        showNotification(title,
                            FontAwesome.EXCLAMATION_CIRCLE,
                            Palette.WARNING,
                            () -> outputVisible.set(true)
                        )
                    );
                    break;
                }
                case ERROR : case FATAL : {
                    addToOutputMessages(OutputUtil.error(ev.getMessage()));
                    break;
                }
            }
        });
        
        final Project loaded = projectComponent.getProject();
        if (loaded != null) {
            project.merge(documentPropertyComponent, loaded);
        }

        Statistics.report(info, projectComponent, GUI_STARTED);
    }
    
    private void addToOutputMessages(Node node) {
        runLater(() -> outputMessages.add(node));
    }

    ////////////////////////////////////////////////////////////////////////////
    //                            Global Properties                           //
    ////////////////////////////////////////////////////////////////////////////
 
    @Override
    public ProjectProperty projectProperty() {
        return project;
    }

    @Override
    public Application getApplication() {
        return application;
    }

    @Override
    public Stage getStage() {
        return stage;
    }

    @Override
    public ObservableList notifications() {
        return notifications;
    }

    @Override
    public ObservableList outputMessages() {
        return outputMessages;
    }

    @Override
    public ObservableList> getSelectedTreeItems() {
        return selectedTreeItems;
    }

    @Override
    public ObservableList getProperties() {
        return properties;
    }
    
    ////////////////////////////////////////////////////////////////////////////
    //                            Menubar actions                             //
    ////////////////////////////////////////////////////////////////////////////

    @Override
    public void newProject() {
        try {
            MainApp.setInjector(injector.newBuilder().build());
            final MainApp app    = new MainApp();
            final Stage newStage = new Stage();
            app.start(newStage);
        } catch (final Exception e) {
            LOGGER.error(e);
            showError("Could not create empty project", e.getMessage(), e);
        }
    }

    @Override
    public void openProject() {
        openProject(ReuseStage.CREATE_A_NEW_STAGE);
    }

    @Override
    public void openProject(ReuseStage reuse) {
        final FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Open .json File");
        fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("JSON files (*.json)", "*.json"));

        final File file = fileChooser.showOpenDialog(stage);
        if (file != null) {
            configFileHelper.loadConfigFile(file, reuse);
        }
    }

    @Override
    public void saveProject() {
        if (configFileHelper.isFileOpen()) {
            configFileHelper.saveCurrentlyOpenConfigFile();
        } else {
            configFileHelper.saveConfigFile();
        }
    }

    @Override
    public void saveProjectAs() {
        configFileHelper.saveConfigFile();
    }

    @Override
    public void quit() {
        stage.close();
    }

    @Override
    public void reload() {
        if (showWarning(
            "Do you really want to do this?",
            "Reloading the project will remove any changes you have done "
            + "to the project. Are you sure you want to continue?"
        ).filter(ButtonType.OK::equals).isPresent()) {

            project.dbmses()
                .filter(dbms -> NO_PASSWORD_SPECIFIED.test(passwordComponent.get(dbms)))
                .map(DbmsProperty.class::cast)
                .forEach(this::showPasswordDialog);

            final Optional schemaName = project
                .dbmses().flatMap(Dbms::schemas)
                .map(Schema::getId)
                .findAny();

            if (schemaName.isPresent()) {
                project.dbmses()
                    .map(DbmsProperty.class::cast)
                    .forEach(dbms -> configFileHelper.loadFromDatabase(dbms, schemaName.get()));

                Statistics.report(info, projectComponent, GUI_PROJECT_LOADED);
            } else {
                showError(
                    "No Schema Found",
                    "Can not connect to the database without at least one schema specified."
                );
            }
        }
    }

    @Override
    public void generate() {
        //Make sure that no more than one attemp of generating occurs concurrently
        final boolean allowed = canGenerate.getAndSet(false);
        if( !allowed ){
            return;
        }
        
        clearLog();
        
        final TranslatorSupport support = new TranslatorSupport<>(injector, project);

        log(OutputUtil.info("Preparing for generating classes " + support.basePackageName() + "." + project.getId() + ".*"));
        log(OutputUtil.info("Target directory is " + project.getPackageLocation()));
        log(OutputUtil.info("Performing rule verifications..."));

        final Project immutableProject = ImmutableProject.wrap(project);
        projectComponent.setProject(immutableProject);
        
        CompletableFuture future = rules.verify();
        future.handleAsync((bool, ex) -> {
            if (ex != null) {
                final String err = 
                    "An error occurred while the error checker was looking " +
                    "for issues in the project configuration.";
                LOGGER.error(ex, err);
                runLater(() -> {
                    showError("Error Creating Report", err, ex);
                    canGenerate.set(true);
                });
            } else {
                if (!bool) {
                    runLater( () -> {
                        showIssues();
                        canGenerate.set(true);
                    } );
                } else {
                    runLater(() -> log(OutputUtil.info("Rule verifications completed")));

                    if (!configFileHelper.isFileOpen()) {
                        configFileHelper.setCurrentlyOpenFile(
                            new File(ConfigFileHelper.DEFAULT_CONFIG_LOCATION)
                        );
                    }
                    configFileHelper.saveCurrentlyOpenConfigFile();
                   
                    configFileHelper.generateSources();
                    canGenerate.set(true);
                }
            }
            return bool;
        });
    }

    @Override
    public void showManual() {
        browse(MANUAL_URI);
    }

    @Override
    public void showGitter() {
        browse(GITTER_URI);
    }

    @Override
    public void reportIssue() {
        browse(ISSUE_URI);
    }

    @Override
    public void showGithub() {
        browse(GITHUB_URI);
    }

    @Override
    public BooleanProperty projectTreeVisibleProperty() {
        return projectTreeVisible;
    }

    @Override
    public BooleanProperty workspaceVisibleProperty() {
        return workspaceVisible;
    }

    @Override
    public BooleanProperty outputVisibleProperty() {
        return outputVisible;
    }

    @Override
    public void prepareProjectTree(SplitPane parent, Node projectTree) {
        if (!projectTreeVisible.get()) {
            parent.getItems().remove(projectTree);
        }

        projectTreeVisible.addListener((ob, o, visible) -> {
            if (visible) {
                parent.getItems().add(0, projectTree);
            } else {
                parent.getItems().remove(projectTree);
            }
        });
    }

    @Override
    public void prepareWorkspace(SplitPane parent, Node workspace) {
        if (!workspaceVisible.get()) {
            parent.getItems().remove(workspace);
        }

        workspaceVisible.addListener((ob, o, visible) -> {
            if (visible) {
                parent.getItems().add(0, workspace);
            } else {
                parent.getItems().remove(workspace);
            }
        });
    }

    @Override
    public void prepareOutput(SplitPane parent, Node output) {
        if (!outputVisible.get()) {
            parent.getItems().remove(output);
        }

        outputVisible.addListener((ob, o, visible) -> {
            if (visible) {
                parent.getItems().add(output);
            } else {
                parent.getItems().remove(output);
            }
        });
    }

    ////////////////////////////////////////////////////////////////////////////
    //                             Dialog Messages                            //
    ////////////////////////////////////////////////////////////////////////////

    @Override
    public void showError(String title, String message) {
        showError(title, message, null);
    }

    @Override
    public void showError(String title, String message, Throwable ex) {
        final Alert alert = new Alert(Alert.AlertType.ERROR);
        final Scene scene = alert.getDialogPane().getScene();

        BrandUtil.applyBrand(injector, stage, scene);

        alert.setHeaderText(title);
        alert.setContentText(message);
        alert.setGraphic(FontAwesome.EXCLAMATION_TRIANGLE.view());
        alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);

        if (ex == null) {
            alert.setTitle("Error");
        } else {
            alert.setTitle(ex.getClass().getSimpleName());

            final StringWriter sw = new StringWriter();
            final PrintWriter pw = new PrintWriter(sw);

            ex.printStackTrace(pw);

            final Label label = new Label("The exception stacktrace was:");

            final String exceptionText = sw.toString();
            final TextArea textArea = new TextArea(exceptionText);
            textArea.setEditable(false);
            textArea.setWrapText(true);

            textArea.setMaxWidth(Double.MAX_VALUE);
            textArea.setMaxHeight(Double.MAX_VALUE);
            GridPane.setVgrow(textArea, Priority.ALWAYS);
            GridPane.setHgrow(textArea, Priority.ALWAYS);

            final GridPane expContent = new GridPane();
            expContent.setMaxWidth(Double.MAX_VALUE);
            expContent.add(label, 0, 0);
            expContent.add(textArea, 0, 1);

            alert.getDialogPane().setExpandableContent(expContent);
        }

        alert.showAndWait();
    }

    @Override
    public Optional showWarning(String title, String message) {
        final Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
        final Scene scene = alert.getDialogPane().getScene();

        BrandUtil.applyBrand(injector, stage, scene);

        alert.setTitle("Confirmation");
        alert.setHeaderText(title);
        alert.setContentText(message);
        alert.setGraphic(FontAwesome.EXCLAMATION_TRIANGLE.view());

        return alert.showAndWait();
    }

    @Override
    public void showPasswordDialog(DbmsProperty dbms) {
        final Dialog> dialog = new Dialog<>();
        dialog.setTitle("Authentication Required");
        dialog.setHeaderText("Enter password for " + dbms.getName());
        dialog.setGraphic(FontAwesome.LOCK.view());
        final DialogPane pane = dialog.getDialogPane();
        pane.getStyleClass().add("authentication");

        final Scene scene = pane.getScene();
        BrandUtil.applyBrand(injector, stage, scene);

        final ButtonType authButtonType = new ButtonType("OK", ButtonData.OK_DONE);
        pane.getButtonTypes().addAll(ButtonType.CANCEL, authButtonType);

        final GridPane grid = new GridPane();
        grid.setHgap(10);
        grid.setVgap(10);
        grid.setPadding(new Insets(20, 150, 10, 10));

        final TextField username = new TextField(dbms.getUsername().orElse("Root"));
        username.setPromptText("Username");
        final PasswordField password = new PasswordField();
        password.setPromptText("Password");

        grid.add(new Label("Username:"), 0, 0);
        grid.add(username, 1, 0);
        grid.add(new Label("Password:"), 0, 1);
        grid.add(password, 1, 1);

        final Node loginButton = pane.lookupButton(authButtonType);

        username.textProperty().addListener((ob, o, n)
            -> loginButton.setDisable(n.trim().isEmpty())
        );

        pane.setContent(grid);
        Platform.runLater(username::requestFocus);

        dialog.setResultConverter(dialogButton -> {
            if (dialogButton == authButtonType) {
                return new Pair<>(username.getText(), password.getText().toCharArray());
            }
            return null;
        });

        Optional> result = dialog.showAndWait();

        result.ifPresent(usernamePassword -> {
            dbms.mutator().setUsername(usernamePassword.getKey());
            passwordComponent.put(dbms, usernamePassword.getValue());
        });
    }

    @Override
    public void showProgressDialog(String title, ProgressMeasure progress, CompletableFuture task) {
        final Dialog dialog = new Dialog<>();
        dialog.setTitle("Progress Tracker");
        dialog.setHeaderText(title);
        dialog.setGraphic(FontAwesome.SPINNER.view());

        final DialogPane pane = dialog.getDialogPane();
        pane.getStyleClass().add("progress");

        final VBox box = new VBox();
        final ProgressBar bar = new ProgressBar();
        final Label message = new Label();
        final Button cancel = new Button("Cancel", FontAwesome.TIMES.view());

        final Pane filler = new Pane();
        final HBox fillerContainer = new HBox(filler, cancel);
        HBox.setHgrow(filler, Priority.ALWAYS);
        HBox.setHgrow(cancel, Priority.SOMETIMES);
        filler.setMaxWidth(Double.MAX_VALUE);
        fillerContainer.setMaxWidth(Double.MAX_VALUE);

        box.getChildren().addAll(bar, message, fillerContainer);
        box.setMaxWidth(Double.MAX_VALUE);
        bar.setMaxWidth(Double.MAX_VALUE);
        message.setMaxWidth(Double.MAX_VALUE);
        cancel.setMaxWidth(128);
        VBox.setVgrow(message, Priority.ALWAYS);

        box.setFillWidth(true);
        box.setSpacing(8);

        progress.addListener(measure -> {
            final String msg   = measure.getCurrentAction();
            final double prg   = measure.getProgress();
            final boolean done = measure.isDone();
            
            runLater(() -> {
                if (done) {
                    dialog.setResult(true);
                    dialog.close();
                } else {
                    message.setText(msg);
                    bar.setProgress(prg);
                }
            });
        });

        cancel.setOnAction(ev -> {
            if (!task.cancel(true)) {
                LOGGER.error("Failed to cancel task.");
            }
            
            progress.setCurrentAction("Cancelling...");
            progress.setProgress(ProgressMeasure.DONE);
        });

        pane.setContent(box);
        pane.setMaxWidth(Double.MAX_VALUE);

        final Scene scene = pane.getScene();
        BrandUtil.applyBrand(injector, stage, scene);

        if (!progress.isDone()) {
            dialog.showAndWait();
        }
    }

    @Override
    public void showIssues() {
        loader.loadAsModal("ProjectProblem");
    }

    @Override
    public void showNotification(String message) {
        showNotification(message, FontAwesome.EXCLAMATION_CIRCLE);
    }

    @Override
    public void showNotification(String message, Icon icon) {
        showNotification(message, icon, Palette.INFO);
    }

    @Override
    public void showNotification(String message, Runnable action) {
        showNotification(message, FontAwesome.EXCLAMATION_CIRCLE, Palette.INFO, action);
    }

    @Override
    public void showNotification(String message, Palette palette) {
        showNotification(message, FontAwesome.EXCLAMATION_CIRCLE, palette);
    }

    @Override
    public void showNotification(String message, Icon icon, Palette palette) {
        showNotification(message, icon, palette, () -> {});
    }

    @Override
    public void showNotification(String message, Icon icon, Palette palette, Runnable action) {
        runLater(() -> 
            notifications.add(new NotificationImpl(message, icon, palette, action))
        );
    }

    ////////////////////////////////////////////////////////////////////////////
    //                                  Other                                 //
    ////////////////////////////////////////////////////////////////////////////

    @Override
    public void clearLog() {
        runLater(outputMessages::clear);
    }

    @Override
    public void log(Label line) {
        runLater(() -> outputMessages.add(line));
    }

    @Override
    public void browse(String url) {
        application.getHostServices().showDocument(url);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy