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

org.apache.ignite.schema.ui.SchemaLoadApp Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.ignite.schema.ui;

import javafx.application.*;
import javafx.beans.value.*;
import javafx.collections.*;
import javafx.concurrent.*;
import javafx.event.*;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;
import javafx.util.*;
import org.apache.ignite.schema.generator.*;
import org.apache.ignite.schema.model.*;
import org.apache.ignite.schema.parser.*;

import java.io.*;
import java.net.*;
import java.sql.*;
import java.util.*;
import java.util.concurrent.*;

import static javafx.embed.swing.SwingFXUtils.*;
import static org.apache.ignite.schema.ui.Controls.*;

/**
 * Schema load application.
 */
@SuppressWarnings("UnnecessaryFullyQualifiedName")
public class SchemaLoadApp extends Application {
    /** Presets for database settings. */
    private static class Preset {
        /** Name in preferences. */
        private String pref;

        /** RDBMS name to show on screen. */
        private String name;

        /** Path to JDBC driver jar. */
        private String jar;

        /** JDBC driver class name. */
        private String drv;

        /** JDBC URL. */
        private String url;

        /** User name. */
        private String user;

        /**
         * Preset constructor.
         *
         * @param pref Name in preferences.
         * @param name RDBMS name to show on screen.
         * @param jar Path to JDBC driver jar..
         * @param drv JDBC driver class name.
         * @param url JDBC URL.
         * @param user User name.
         */
        Preset(String pref, String name, String jar, String drv, String url, String user) {
            this.pref = pref;
            this.name = name;
            this.jar = jar;
            this.drv = drv;
            this.url = url;
            this.user = user;
        }

        /** {@inheritDoc} */
        @Override public String toString() {
            return name;
        }
    }

    /** Default presets for popular databases. */
    private final Preset[] presets = {
        new Preset("h2", "H2 Database", "h2.jar", "org.h2.Driver", "jdbc:h2:[database]", "sa"),
        new Preset("db2", "DB2", "db2jcc4.jar", "com.ibm.db2.jcc.DB2Driver", "jdbc:db2://[host]:[port]/[database]",
            "db2admin"),
        new Preset("oracle", "Oracle", "ojdbc6.jar", "oracle.jdbc.OracleDriver",
            "jdbc:oracle:thin:@[host]:[port]:[database]", "system"),
        new Preset("mysql", "MySQL", "mysql-connector-java-5-bin.jar", "com.mysql.jdbc.Driver",
            "jdbc:mysql://[host]:[port]/[database]", "root"),
        new Preset("mssql", "Microsoft SQL Server", "sqljdbc41.jar", "com.microsoft.sqlserver.jdbc.SQLServerDriver",
            "jdbc:sqlserver://[host]:[port][;databaseName=database]", "sa"),
        new Preset("posgresql", "PostgreSQL", "postgresql-9.3.jdbc4.jar", "org.postgresql.Driver",
            "jdbc:postgresql://[host]:[port]/[database]", "sa"),
        new Preset("custom", "Custom server...", "custom-jdbc.jar", "org.custom.Driver", "jdbc:custom", "sa")
    };

    /** */
    private Stage owner;

    /** */
    private BorderPane rootPane;

    /** Header pane. */
    private BorderPane hdrPane;

    /** */
    private HBox dbIcon;

    /** */
    private HBox genIcon;

    /** */
    private Label titleLb;

    /** */
    private Label subTitleLb;

    /** */
    private Button prevBtn;

    /** */
    private Button nextBtn;

    /** */
    private ComboBox rdbmsCb;

    /** */
    private TextField jdbcDrvJarTf;

    /** */
    private TextField jdbcDrvClsTf;

    /** */
    private TextField jdbcUrlTf;

    /** */
    private TextField userTf;

    /** */
    private PasswordField pwdTf;

    /** */
    private ComboBox parseCb;

    /** */
    private GridPaneEx connPnl;

    /** */
    private StackPane connLayerPnl;

    /** */
    private TableView pojosTbl;

    /** */
    private TableView fieldsTbl;

    /** */
    private Node curTbl;

    /** */
    private TextField outFolderTf;

    /** */
    private TextField pkgTf;

    /** */
    private CheckBox pojoConstructorCh;

    /** */
    private CheckBox pojoIncludeKeysCh;

    /** */
    private CheckBox xmlSingleFileCh;

    /** */
    private TextField regexTf;

    /** */
    private TextField replaceTf;

    /** */
    private GridPaneEx genPnl;

    /** */
    private StackPane genLayerPnl;

    /** */
    private ProgressIndicator pi;

    /** List with POJOs descriptors. */
    private ObservableList pojos = FXCollections.emptyObservableList();

    /** Currently selected POJO. */
    private PojoDescriptor curPojo;

    /** */
    private final Map drivers = new HashMap<>();

    /** Application preferences. */
    private final Properties prefs = new Properties();

    /** File path for storing on local file system. */
    private final File prefsFile = new File(System.getProperty("user.home"), ".ignite-schema-load");

    /** Empty POJO fields model. */
    private static final ObservableList NO_FIELDS = FXCollections.emptyObservableList();

    /** */
    private final ExecutorService exec = Executors.newSingleThreadExecutor(new ThreadFactory() {
        @Override public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "ignite-schema-load-worker");

            t.setDaemon(true);

            return t;
        }
    });

    /**
     * Lock UI before start long task.
     *
     * @param layer Stack pane to add progress indicator.
     * @param controls Controls to disable.
     */
    private void lockUI(StackPane layer, Node... controls) {
        for (Node control : controls)
            control.setDisable(true);

        layer.getChildren().add(pi);
    }

    /**
     * Unlock UI after long task finished.
     *
     * @param layer Stack pane to remove progress indicator.
     * @param controls Controls to enable.
     */
    private void unlockUI(StackPane layer, Node... controls) {
        for (Node control : controls)
            control.setDisable(false);

        layer.getChildren().remove(pi);
    }

    /**
     * Perceptual delay to avoid UI flickering.
     *
     * @param started Time when background progress started.
     */
    private void perceptualDelay(long started) {
        long delta = System.currentTimeMillis() - started;

        if (delta < 500)
            try {
                Thread.sleep(500 - delta);
            }
            catch (InterruptedException ignored) {
                Thread.currentThread().interrupt();
            }
    }

    /**
     * Fill tree with database metadata.
     */
    private void fill() {
        lockUI(connLayerPnl, connPnl, nextBtn);

        final String jdbcDrvJarPath = jdbcDrvJarTf.getText().trim();

        final String jdbcDrvCls = jdbcDrvClsTf.getText();

        final String jdbcUrl = jdbcUrlTf.getText();

        String user = userTf.getText().trim();

        String pwd = pwdTf.getText().trim();

        final Properties jdbcInfo = new Properties();

        if (!user.isEmpty())
            jdbcInfo.put("user", user);

        if (!pwd.isEmpty())
            jdbcInfo.put("password", pwd);

        final boolean tblsOnly = parseCb.getSelectionModel().getSelectedIndex() == 0;

        Runnable task = new Task() {
            /** {@inheritDoc} */
            @Override protected Void call() throws Exception {
                long started = System.currentTimeMillis();

                try (Connection conn = connect(jdbcDrvJarPath, jdbcDrvCls, jdbcUrl, jdbcInfo)) {
                    pojos = DatabaseMetadataParser.parse(conn, tblsOnly);
                }

                perceptualDelay(started);

                return null;
            }

            /** {@inheritDoc} */
            @Override protected void succeeded() {
                super.succeeded();

                pojosTbl.setItems(pojos);

                if (!pojos.isEmpty())
                    pojosTbl.getSelectionModel().select(pojos.get(0));

                curTbl = pojosTbl;

                pojosTbl.requestFocus();

                unlockUI(connLayerPnl, connPnl, nextBtn);

                hdrPane.setLeft(genIcon);

                titleLb.setText("Generate XML And POJOs");
                subTitleLb.setText(jdbcUrlTf.getText());

                rootPane.setCenter(genLayerPnl);

                prevBtn.setDisable(false);
                nextBtn.setText("Generate");
                tooltip(nextBtn, "Generate XML and POJO files");
            }

            /** {@inheritDoc} */
            @Override protected void cancelled() {
                super.cancelled();

                unlockUI(connLayerPnl, connPnl, nextBtn);
            }

            /** {@inheritDoc} */
            @Override protected void failed() {
                super.succeeded();

                unlockUI(connLayerPnl, connPnl, nextBtn);

                MessageBox.errorDialog(owner, "Failed to get tables list from database.", getException());
            }
        };

        exec.submit(task);
    }

    /**
     * Generate XML and POJOs.
     */
    private void generate() {
        final Collection selPojos = checkedPojos();

        if (selPojos.isEmpty()) {
            MessageBox.warningDialog(owner, "Please select tables to generate XML and POJOs files!");

            return;
        }

        lockUI(genLayerPnl, genPnl, prevBtn, nextBtn);

        final String outFolder = outFolderTf.getText();

        final String pkg = pkgTf.getText();

        final File destFolder = new File(outFolder);

        final boolean constructor = pojoConstructorCh.isSelected();

        final boolean includeKeys = pojoIncludeKeysCh.isSelected();

        final boolean singleXml = xmlSingleFileCh.isSelected();

        Runnable task = new Task() {
            private void checkEmpty(final PojoDescriptor pojo, boolean selected, String msg) {
                if (!selected) {
                    Platform.runLater(new Runnable() {
                        @Override public void run() {
                            TableView.TableViewSelectionModel selMdl = pojosTbl.getSelectionModel();

                            selMdl.clearSelection();
                            selMdl.select(pojo);
                            pojosTbl.scrollTo(selMdl.getSelectedIndex());
                        }
                    });

                    throw new IllegalStateException(msg + pojo.table());
                }
            }

            /** {@inheritDoc} */
            @Override protected Void call() throws Exception {
                long started = System.currentTimeMillis();

                if (!destFolder.exists() && !destFolder.mkdirs())
                    throw new IOException("Failed to create output folder: " + destFolder);

                Collection all = new ArrayList<>();

                ConfirmCallable askOverwrite = new ConfirmCallable(owner, "File already exists: %s\nOverwrite?");

                // Generate XML and POJO.
                for (PojoDescriptor pojo : selPojos) {
                    if (pojo.checked()) {
                        checkEmpty(pojo, pojo.hasFields(), "No fields selected for type: ");
                        checkEmpty(pojo, pojo.hasKeyFields(), "No key fields selected for type: ");
                        checkEmpty(pojo, pojo.hasValueFields(includeKeys), "No value fields selected for type: ");

                        all.add(pojo);
                    }
                }

                for (PojoDescriptor pojo : all) {
                    if (!singleXml)
                        XmlGenerator.generate(pkg, pojo, includeKeys, new File(destFolder, pojo.table() + ".xml"),
                            askOverwrite);

                    PojoGenerator.generate(pojo, outFolder, pkg, constructor, includeKeys, askOverwrite);
                }

                if (singleXml)
                    XmlGenerator.generate(pkg, all, includeKeys, new File(outFolder, "Ignite.xml"), askOverwrite);

                perceptualDelay(started);

                return null;
            }

            /** {@inheritDoc} */
            @Override protected void succeeded() {
                super.succeeded();

                unlockUI(genLayerPnl, genPnl, prevBtn, nextBtn);

                if (MessageBox.confirmDialog(owner, "Generation complete!\n\n" +
                    "Reveal output folder in system default file browser?"))
                    try {
                        java.awt.Desktop.getDesktop().open(destFolder);
                    }
                    catch (IOException e) {
                        MessageBox.errorDialog(owner, "Failed to open folder with results.", e);
                    }
            }

            /** {@inheritDoc} */
            @Override protected void cancelled() {
                super.cancelled();

                unlockUI(genLayerPnl, genPnl, prevBtn, nextBtn);

                MessageBox.warningDialog(owner, "Generation canceled.");
            }

            /** {@inheritDoc} */
            @Override protected void failed() {
                super.succeeded();

                unlockUI(genLayerPnl, genPnl, prevBtn, nextBtn);

                MessageBox.errorDialog(owner, "Generation failed.", getException());
            }
        };

        exec.submit(task);
    }

    /**
     * @return Header pane with title label.
     */
    private BorderPane createHeaderPane() {
        dbIcon = hBox(0, true, imageView("data_connection", 48));
        genIcon = hBox(0, true, imageView("text_tree", 48));

        titleLb = label("");
        titleLb.setId("banner");

        subTitleLb = label("");

        BorderPane bp = borderPane(null, vBox(5, titleLb, subTitleLb), null, dbIcon, hBox(0, true, imageView("ignite", 48)));
        bp.setId("banner");

        return bp;
    }

    /**
     * @return Panel with control buttons.
     */
    private Pane createButtonsPane() {
        prevBtn = button("Prev", "Go to \"Database connection\" page", new EventHandler() {
            @Override public void handle(ActionEvent evt) {
                prev();
            }
        });

        nextBtn = button("Next", "Go to \"POJO and XML generation\" page", new EventHandler() {
            @Override public void handle(ActionEvent evt) {
                next();
            }
        });

        return buttonsPane(Pos.BOTTOM_RIGHT, true, prevBtn, nextBtn);
    }

    /**
     * @return {@code true} if some changes were made to fields metadata.
     */
    private boolean changed() {
        for (PojoDescriptor pojo : pojos)
            if (pojo.changed())
                return true;

        return false;
    }

    /**
     * Go to "Connect To Database" panel.
     */
    private void prev() {
        if (changed() && !MessageBox.confirmDialog(owner, "Are you sure you want to return to previous page?\n" +
            "This will discard all your changes."))
            return;

        hdrPane.setLeft(dbIcon);

        titleLb.setText("Connect To Database");
        subTitleLb.setText("Specify database connection properties...");

        rootPane.setCenter(connLayerPnl);

        prevBtn.setDisable(true);
        nextBtn.setText("Next");
        tooltip(nextBtn, "Go to \"XML and POJO generation\" page");
    }

    /**
     * Check that text field is non empty.
     *
     * @param tf Text field check.
     * @param trim If {@code true} then
     * @param msg Warning message.
     * @return {@code true} If text field is empty.
     */
    private boolean checkInput(TextField tf, boolean trim, String msg) {
        String s = tf.getText();

        s = trim ? s.trim() : s;

        if (s.isEmpty()) {
            tf.requestFocus();

            MessageBox.warningDialog(owner, msg);

            return true;
        }

        return false;
    }

    /**
     * Go to "Generate XML And POJOs" panel or generate XML and POJOs.
     */
    private void next() {
        if (rootPane.getCenter() == connLayerPnl) {
            if (checkInput(jdbcDrvJarTf, true, "Path to JDBC driver is not specified!") ||
                checkInput(jdbcDrvClsTf, true, "JDBC driver class name is not specified!") ||
                checkInput(jdbcUrlTf, true, "JDBC URL connection string is not specified!") ||
                checkInput(userTf, true, "User name is not specified!"))
                return;

            fill();
        }
        else
            generate();
    }

    /**
     * Connect to database.
     *
     * @param jdbcDrvJarPath Path to JDBC driver.
     * @param jdbcDrvCls JDBC class name.
     * @param jdbcUrl JDBC connection URL.
     * @param jdbcInfo Connection properties.
     * @return Connection to database.
     * @throws SQLException if connection failed.
     */
    private Connection connect(String jdbcDrvJarPath, String jdbcDrvCls, String jdbcUrl, Properties jdbcInfo)
        throws SQLException {
        Driver drv = drivers.get(jdbcDrvCls);

        if (drv == null) {
            if (jdbcDrvJarPath.isEmpty())
                throw new IllegalStateException("Driver jar file name is not specified");

            File drvJar = new File(jdbcDrvJarPath);

            if (!drvJar.exists())
                throw new IllegalStateException("Driver jar file is not found");

            try {
                URL u = new URL("jar:" + drvJar.toURI() + "!/");

                URLClassLoader ucl = URLClassLoader.newInstance(new URL[] {u});

                drv = (Driver)Class.forName(jdbcDrvCls, true, ucl).newInstance();

                drivers.put(jdbcDrvCls, drv);
            }
            catch (Throwable e) {
                throw new IllegalStateException(e);
            }
        }

        Connection conn = drv.connect(jdbcUrl, jdbcInfo);

        if (conn == null)
            throw new IllegalStateException("Connection was not established (JDBC driver returned null value).");

        return conn;
    }

    /**
     * Create connection pane with controls.
     */
    private Pane createConnectionPane() {
        connPnl = paneEx(10, 10, 0, 10);

        connPnl.addColumn();
        connPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
        connPnl.addColumn(35, 35, 35, Priority.NEVER);

        connPnl.add(text("This utility is designed to automatically generate configuration XML files and" +
            " POJO classes from database schema information.", 550), 3);

        connPnl.wrap();

        GridPaneEx presetPnl = paneEx(0, 0, 0, 0);
        presetPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
        presetPnl.addColumn();

        rdbmsCb = presetPnl.add(comboBox("Select database server to get predefined settings", presets));

        presetPnl.add(button("Save preset", "Save current settings in preferences", new EventHandler() {
            @Override public void handle(ActionEvent evt) {
                Preset preset = rdbmsCb.getSelectionModel().getSelectedItem();

                savePreset(preset);
            }
        }));

        connPnl.add(label("DB Preset:"));
        connPnl.add(presetPnl, 2);

        jdbcDrvJarTf = connPnl.addLabeled("Driver JAR:", textField("Path to driver jar"));

        connPnl.add(button("...", "Select JDBC driver jar or zip", new EventHandler() {
            /** {@inheritDoc} */
            @Override public void handle(ActionEvent evt) {
                FileChooser fc = new FileChooser();

                try {
                    File jarFolder = new File(jdbcDrvJarTf.getText()).getParentFile();

                    if (jarFolder.exists())
                        fc.setInitialDirectory(jarFolder);
                }
                catch (Throwable ignored) {
                    // No-op.
                }

                jdbcDrvJarTf.getText();

                fc.getExtensionFilters().addAll(
                    new FileChooser.ExtensionFilter("JDBC Drivers (*.jar)", "*.jar"),
                    new FileChooser.ExtensionFilter("ZIP archives (*.zip)", "*.zip"));

                File drvJar = fc.showOpenDialog(owner);

                if (drvJar != null)
                    jdbcDrvJarTf.setText(drvJar.getAbsolutePath());
            }
        }));

        jdbcDrvClsTf = connPnl.addLabeled("JDBC Driver:", textField("Enter class name for JDBC driver"), 2);

        jdbcUrlTf = connPnl.addLabeled("JDBC URL:", textField("JDBC URL of the database connection string"), 2);

        rdbmsCb.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
            @Override public void changed(ObservableValue val, Preset oldVal, Preset newVal) {
                jdbcDrvJarTf.setText(newVal.jar);
                jdbcDrvClsTf.setText(newVal.drv);
                jdbcUrlTf.setText(newVal.url);
                userTf.setText(newVal.user);
            }
        });

        userTf = connPnl.addLabeled("User:", textField("User name"), 2);

        pwdTf = connPnl.addLabeled("Password:", passwordField("User password"), 2);

        parseCb = connPnl.addLabeled("Parse:", comboBox("Type of tables to parse", "Tables only", "Tables and Views"), 2);

        connLayerPnl = stackPane(connPnl);

        return connLayerPnl;
    }

    /**
     * Check if new class name is unique.
     *
     * @param pojo Current edited POJO.
     * @param newVal New value for class name.
     * @param key {@code true} if key class name is checked.
     * @return {@code true} if class name is valid.
     */
    private boolean checkClassNameUnique(PojoDescriptor pojo, String newVal, boolean key) {
        for (PojoDescriptor otherPojo : pojos)
            if (pojo != otherPojo) {
                String otherKeyCls = otherPojo.keyClassName();
                String otherValCls = otherPojo.valueClassName();

                if (newVal.equals(otherKeyCls) || newVal.equals(otherValCls)) {
                    MessageBox.warningDialog(owner, (key ? "Key" : "Value") + " class name must be unique!");

                    return false;
                }
            }

        return true;
    }

    /**
     * Check if new class name is valid.
     *
     * @param pojo Current edited POJO.
     * @param newVal New value for class name.
     * @param key {@code true} if key class name is checked.
     * @return {@code true} if class name is valid.
     */
    private boolean checkClassName(PojoDescriptor pojo, String newVal, boolean key) {
        if (key) {
            if (newVal.equals(pojo.valueClassName())) {
                MessageBox.warningDialog(owner, "Key class name must be different from value class name!");

                return false;
            }
        }
        else if (newVal.equals(pojo.keyClassName())) {
            MessageBox.warningDialog(owner, "Value class name must be different from key class name!");

            return false;
        }

        return checkClassNameUnique(pojo, newVal, key);
    }

    /**
     * Create generate pane with controls.
     */
    private void createGeneratePane() {
        genPnl = paneEx(10, 10, 0, 10);

        genPnl.addColumn();
        genPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
        genPnl.addColumn(35, 35, 35, Priority.NEVER);

        genPnl.addRow(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
        genPnl.addRows(7);

        TableColumn useCol = customColumn("Schema / Table", "use",
            "If checked then this table will be used for XML and POJOs generation", PojoDescriptorCell.cellFactory());

        TableColumn keyClsCol = textColumn("Key Class Name", "keyClassName", "Key class name",
            new TextColumnValidator() {
                @Override public boolean valid(PojoDescriptor rowVal, String newVal) {
                    boolean valid = checkClassName(rowVal, newVal, true);

                    if (valid)
                        rowVal.keyClassName(newVal);

                    return valid;
                }
            });

        TableColumn valClsCol = textColumn("Value Class Name", "valueClassName", "Value class name",
            new TextColumnValidator() {
                @Override public boolean valid(PojoDescriptor rowVal, String newVal) {
                    boolean valid = checkClassName(rowVal, newVal, true);

                    if (valid)
                        rowVal.valueClassName(newVal);

                    return valid;
                }
            });

        pojosTbl = tableView("Tables not found in database", useCol, keyClsCol, valClsCol);

        TableColumn useFldCol = customColumn("Use", "use",
            "Check to use this field for XML and POJO generation\n" +
            "Note that NOT NULL columns cannot be unchecked", PojoFieldUseCell.cellFactory());
        useFldCol.setMinWidth(50);
        useFldCol.setMaxWidth(50);

        TableColumn keyCol = booleanColumn("Key", "key",
            "Check to include this field into key object");

        TableColumn akCol = booleanColumn("AK", "affinityKey",
            "Check to annotate key filed with @CacheAffinityKeyMapped annotation in generated POJO class\n" +
            "Note that a class can have only ONE key field annotated with @CacheAffinityKeyMapped annotation");

        TableColumn dbNameCol = tableColumn("DB Name", "dbName", "Field name in database");

        TableColumn dbTypeNameCol = tableColumn("DB Type", "dbTypeName", "Field type in database");

        TableColumn javaNameCol = textColumn("Java Name", "javaName", "Field name in POJO class",
            new TextColumnValidator() {
                @Override public boolean valid(PojoField rowVal, String newVal) {
                    for (PojoField field : curPojo.fields())
                        if (rowVal != field && newVal.equals(field.javaName())) {
                            MessageBox.warningDialog(owner, "Java name must be unique!");

                            return false;
                        }

                    rowVal.javaName(newVal);

                    return true;
                }
            });

        TableColumn javaTypeNameCol = customColumn("Java Type", "javaTypeName",
            "Field java type in POJO class", JavaTypeCell.cellFactory());

        fieldsTbl = tableView("Select table to see table columns",
            useFldCol, keyCol, akCol, dbNameCol, dbTypeNameCol, javaNameCol, javaTypeNameCol);

        genPnl.add(splitPane(pojosTbl, fieldsTbl, 0.6), 3);

        final GridPaneEx keyValPnl = paneEx(0, 0, 0, 0);
        keyValPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
        keyValPnl.addColumn();
        keyValPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
        keyValPnl.addColumn();

        pkgTf = genPnl.addLabeled("Package:", textField("Package that will be used for POJOs generation"), 2);

        outFolderTf = genPnl.addLabeled("Output Folder:", textField("Output folder for XML and POJOs files"));

        genPnl.add(button("...", "Select output folder", new EventHandler() {
            @Override public void handle(ActionEvent evt) {
                DirectoryChooser dc = new DirectoryChooser();

                try {
                    File outFolder = new File(outFolderTf.getText());

                    if (outFolder.exists())
                        dc.setInitialDirectory(outFolder);
                }
                catch (Throwable ignored) {
                    // No-op.
                }

                File folder = dc.showDialog(owner);

                if (folder != null)
                    outFolderTf.setText(folder.getAbsolutePath());
            }
        }));

        pojoIncludeKeysCh = genPnl.add(checkBox("Include key fields into value POJOs",
            "If selected then include key fields into value object", true), 3);

        pojoConstructorCh = genPnl.add(checkBox("Generate constructors for POJOs",
            "If selected then generate empty and full constructors for POJOs", false), 3);

        xmlSingleFileCh = genPnl.add(checkBox("Write all configurations to a single XML file",
            "If selected then all configurations will be saved into the file 'Ignite.xml'", true), 3);

        GridPaneEx regexPnl = paneEx(5, 5, 5, 5);
        regexPnl.addColumn();
        regexPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
        regexPnl.addColumn();
        regexPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);

        regexTf = regexPnl.addLabeled("  Regexp:", textField("Regular expression. For example: (\\w+)"));

        replaceTf = regexPnl.addLabeled("  Replace with:", textField("Replace text. For example: $1_SomeText"));

        final ComboBox replaceCb = regexPnl.addLabeled("  Replace:", comboBox("Replacement target",
            "Key class names", "Value class names", "Java names"));

        regexPnl.add(buttonsPane(Pos.CENTER_LEFT, false,
            button("Rename Selected", "Replaces each substring of this string that matches the given regular expression" +
                    " with the given replacement.",
                new EventHandler() {
                    @Override public void handle(ActionEvent evt) {
                        if (checkInput(regexTf, false, "Regular expression should not be empty!"))
                            return;

                        String sel = replaceCb.getSelectionModel().getSelectedItem();

                        boolean isFields = "Java names".equals(sel) && curTbl == fieldsTbl;

                        String src = isFields ? "fields" : "tables";

                        String target = "\"" + sel + "\"";

                        Collection selPojos = pojosTbl.getSelectionModel().getSelectedItems();

                        Collection selFields = fieldsTbl.getSelectionModel().getSelectedItems();

                        boolean isEmpty = isFields ? selFields.isEmpty() : selPojos.isEmpty();

                        if (isEmpty) {
                            MessageBox.warningDialog(owner, "Please select " + src + " to rename " + target + "!");

                            return;
                        }

                        if (!MessageBox.confirmDialog(owner, "Are you sure you want to rename " + target +
                            " for all selected " + src + "?"))
                            return;

                        String regex = regexTf.getText();

                        String replace = replaceTf.getText();

                        try {
                            switch (replaceCb.getSelectionModel().getSelectedIndex()) {
                                case 0:
                                    renameKeyClassNames(selPojos, regex, replace);
                                    break;

                                case 1:
                                    renameValueClassNames(selPojos, regex, replace);
                                    break;

                                default:
                                    if (isFields)
                                        renameFieldsJavaNames(selFields, regex, replace);
                                    else
                                        renamePojosJavaNames(selPojos, regex, replace);
                            }
                        }
                        catch (Exception e) {
                            MessageBox.errorDialog(owner, "Failed to rename " + target + "!", e);
                        }
                    }
                }),
            button("Reset Selected", "Revert changes for selected items to initial auto-generated values", new EventHandler() {
                @Override public void handle(ActionEvent evt) {
                    String sel = replaceCb.getSelectionModel().getSelectedItem();

                    boolean isFields = "Java names".equals(sel) && curTbl == fieldsTbl;

                    String src = isFields ? "fields" : "tables";

                    String target = "\"" + sel + "\"";

                    Collection selPojos = pojosTbl.getSelectionModel().getSelectedItems();

                    Collection selFields = fieldsTbl.getSelectionModel().getSelectedItems();

                    boolean isEmpty = isFields ? selFields.isEmpty() : selPojos.isEmpty();

                    if (isEmpty) {
                        MessageBox.warningDialog(owner, "Please select " + src + "to revert " + target + "!");

                        return;
                    }

                    if (!MessageBox.confirmDialog(owner,
                        "Are you sure you want to revert " + target + " for all selected " + src + "?"))
                        return;

                    switch (replaceCb.getSelectionModel().getSelectedIndex()) {
                        case 0:
                            revertKeyClassNames(selPojos);
                            break;

                        case 1:
                            revertValueClassNames(selPojos);
                            break;

                        default:
                            if (isFields)
                                revertFieldsJavaNames(selFields);
                            else
                                revertPojosJavaNames(selPojos);
                    }
                }
            })
        ), 2).setPadding(new Insets(0, 0, 0, 10));

        pojosTbl.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
            @Override public void changed(ObservableValue val,
                PojoDescriptor oldVal, PojoDescriptor newItem) {
                if (newItem != null && newItem.parent() != null) {
                    curPojo = newItem;

                    fieldsTbl.setItems(curPojo.fields());
                    fieldsTbl.getSelectionModel().clearSelection();

                    keyValPnl.setDisable(false);
                }
                else {
                    curPojo = null;
                    fieldsTbl.setItems(NO_FIELDS);

                    keyValPnl.setDisable(true);
                }
            }
        });

        pojosTbl.focusedProperty().addListener(new ChangeListener() {
            @Override public void changed(ObservableValue val, Boolean oldVal, Boolean newVal) {
                if (newVal)
                    curTbl = pojosTbl;
            }
        });

        fieldsTbl.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener() {
            @Override public void changed(ObservableValue val, Number oldVal, Number newVal) {
                if (curPojo != null) {
                    TableView.TableViewSelectionModel selMdl = pojosTbl.getSelectionModel();

                    List idxs = new ArrayList<>(selMdl.getSelectedIndices());

                    if (idxs.size() > 1) {
                        for (Integer idx : idxs) {
                            if (pojos.get(idx) != curPojo)
                                selMdl.clearSelection(idx);
                        }
                    }
                }
            }
        });

        fieldsTbl.focusedProperty().addListener(new ChangeListener() {
            @Override public void changed(ObservableValue val, Boolean oldVal, Boolean newVal) {
                if (newVal)
                    curTbl = fieldsTbl;
            }
        });

        genPnl.add(titledPane("Rename \"Key class name\", \"Value class name\" or  \"Java name\" for selected tables",
            regexPnl), 3);

        genLayerPnl = stackPane(genPnl);
    }

    /**
     * Rename key class name for selected POJOs.
     *
     * @param selPojos Selected POJOs to rename.
     * @param regex Regex to search.
     * @param replace Text for replacement.
     */
    private void renameKeyClassNames(Collection selPojos, String regex, String replace) {
        for (PojoDescriptor pojo : selPojos)
            pojo.keyClassName(pojo.keyClassName().replaceAll(regex, replace));
    }

    /**
     * Rename value class name for selected POJOs.
     *
     * @param selPojos Selected POJOs to rename.
     * @param regex Regex to search.
     * @param replace Text for replacement.
     */
    private void renameValueClassNames(Collection selPojos, String regex, String replace) {
        for (PojoDescriptor pojo : selPojos)
            pojo.valueClassName(pojo.valueClassName().replaceAll(regex, replace));
    }

    /**
     * Rename fields java name for selected POJOs.
     *
     * @param selPojos Selected POJOs to rename.
     * @param regex Regex to search.
     * @param replace Text for replacement.
     */
    private void renamePojosJavaNames(Collection selPojos, String regex, String replace) {
        for (PojoDescriptor pojo : selPojos)
            for (PojoField field : pojo.fields())
                field.javaName(field.javaName().replaceAll(regex, replace));
    }

    /**
     * Rename fields java name for current POJO.
     *
     * @param selFields Selected fields for current POJO to rename.
     * @param regex Regex to search.
     * @param replace Text for replacement.
     */
    private void renameFieldsJavaNames(Collection selFields, String regex, String replace) {
        for (PojoField field : selFields)
            field.javaName(field.javaName().replaceAll(regex, replace));
    }

    /**
     * Revert key class name for selected POJOs to initial value.
     *
     * @param selPojos Selected POJOs to revert.
     */
    private void revertKeyClassNames(Collection selPojos) {
        for (PojoDescriptor pojo : selPojos)
            pojo.revertKeyClassName();
    }

    /**
     * Revert value class name for selected POJOs to initial value.
     *
     * @param selPojos Selected POJOs to revert.
     */
    private void revertValueClassNames(Collection selPojos) {
        for (PojoDescriptor pojo : selPojos)
            pojo.revertValueClassName();
    }

    /**
     * Revert fields java name for selected POJOs to initial value.
     *
     * @param selPojos Selected POJOs to revert.
     */
    private void revertPojosJavaNames(Collection selPojos) {
        for (PojoDescriptor pojo : selPojos)
            pojo.revertJavaNames();
    }

    /**
     * Revert fields java name for current POJO to initial value.
     *
     * @param selFields Selected POJO fields to revert.
     */
    private void revertFieldsJavaNames(Collection selFields) {
        for (PojoField field : selFields)
            field.resetJavaName();
    }

    /**
     * @return POJOs checked in table-tree-view.
     */
    private Collection checkedPojos() {
        Collection res = new ArrayList<>();

        for (PojoDescriptor pojo : pojos)
            if (pojo.checked())
                res.add(pojo);

        return res;
    }

    /**
     * Gets string property.
     *
     * @param key Property key.
     * @param dflt Default value.
     */
    private String getStringProp(String key, String dflt) {
        String val = prefs.getProperty(key);

        if (val != null)
            return val;

        return dflt;
    }

    /**
     * Sets string property.
     *
     * @param key Property key.
     * @param val Value to set.
     */
    private void setStringProp(String key, String val) {
        prefs.put(key, val);
    }

    /**
     * Gets int property.
     *
     * @param key Property key.
     * @param dflt Default value.
     */
    private int getIntProp(String key, int dflt) {
        String val = prefs.getProperty(key);

        if (val != null)
            try {
                return Integer.parseInt(val);
            }
            catch (NumberFormatException ignored) {
                return dflt;
            }

        return dflt;
    }

    /**
     * Sets int property.
     *
     * @param key Property key.
     * @param val Value to set.
     */
    private void setIntProp(String key, int val) {
        prefs.put(key, String.valueOf(val));
    }

    /**
     * Gets boolean property.
     *
     * @param key Property key.
     * @param dflt Default value.
     */
    private boolean getBoolProp(String key, boolean dflt) {
        String val = prefs.getProperty(key);

        if (val != null)
            return Boolean.parseBoolean(val);

        return dflt;
    }

    /**
     * Sets boolean property.
     *
     * @param key Property key.
     * @param val Value to set.
     */
    private void setBoolProp(String key, boolean val) {
        prefs.put(key, String.valueOf(val));
    }

    /** {@inheritDoc} */
    @Override public void start(Stage primaryStage) {
        owner = primaryStage;

        if (prefsFile.exists())
            try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(prefsFile))) {
                prefs.load(in);
            }
            catch (IOException ignore) {
                // No-op.
            }

        // Restore presets.
        for (Preset preset : presets) {
            String key = "presets." + preset.pref + ".";

            preset.jar = getStringProp(key + "jar", preset.jar);
            preset.drv = getStringProp(key + "drv", preset.drv);
            preset.url = getStringProp(key + "url", preset.url);
            preset.user = getStringProp(key + "user", preset.user);
        }

        primaryStage.setTitle("Apache Ignite Auto Schema Load Utility");

        primaryStage.getIcons().addAll(
            image("ignite", 16),
            image("ignite", 24),
            image("ignite", 32),
            image("ignite", 48),
            image("ignite", 64),
            image("ignite", 128));

        pi = progressIndicator(50);

        createGeneratePane();

        hdrPane = createHeaderPane();
        rootPane = borderPane(hdrPane, createConnectionPane(), createButtonsPane(), null, null);

        primaryStage.setScene(scene(rootPane));

        primaryStage.setWidth(650);
        primaryStage.setMinWidth(650);

        primaryStage.setHeight(650);
        primaryStage.setMinHeight(650);

        prev();

        // Restore window pos and size.
        if (prefs.getProperty("window.x") != null) {
            int x = getIntProp("window.x", 100);
            int y = getIntProp("window.y", 100);
            int w = getIntProp("window.width", 650);
            int h = getIntProp("window.height", 650);

            // Ensure that window fit any available screen.
            if (!Screen.getScreensForRectangle(x, y, w, h).isEmpty()) {
                if (x > 0)
                    primaryStage.setX(x);

                if (y > 0)
                    primaryStage.setY(y);

                primaryStage.setWidth(w);
                primaryStage.setHeight(h);
            }
        }
        else
            primaryStage.centerOnScreen();

        String userHome = System.getProperty("user.home").replace('\\', '/');

        // Restore connection pane settings.
        rdbmsCb.getSelectionModel().select(getIntProp("jdbc.db.preset", 0));
        jdbcDrvJarTf.setText(getStringProp("jdbc.driver.jar", "h2.jar"));
        jdbcDrvClsTf.setText(getStringProp("jdbc.driver.class", "org.h2.Driver"));
        jdbcUrlTf.setText(getStringProp("jdbc.url", "jdbc:h2:" + userHome + "/ignite-schema-load/db"));
        userTf.setText(getStringProp("jdbc.user", "sa"));

        // Restore generation pane settings.
        outFolderTf.setText(getStringProp("out.folder", userHome + "/ignite-schema-load/out"));

        pkgTf.setText(getStringProp("pojo.package", "org.apache.ignite"));
        pojoIncludeKeysCh.setSelected(getBoolProp("pojo.include", true));
        pojoConstructorCh.setSelected(getBoolProp("pojo.constructor", false));

        xmlSingleFileCh.setSelected(getBoolProp("xml.single", true));

        regexTf.setText(getStringProp("naming.pattern", "(\\w+)"));
        replaceTf.setText(getStringProp("naming.replace", "$1_SomeText"));

        primaryStage.show();
    }

    /**
     * Save preset.
     *
     * @param preset Preset to save.
     */
    private void savePreset(Preset preset) {
        String key = "presets." + preset.pref + ".";

        preset.jar = jdbcDrvJarTf.getText();
        setStringProp(key + "jar", preset.jar);

        preset.drv = jdbcDrvClsTf.getText();
        setStringProp(key + "drv", preset.drv);

        preset.url = jdbcUrlTf.getText();
        setStringProp(key + "url", preset.url);

        preset.user = userTf.getText();
        setStringProp(key + "user", preset.user);

        savePreferences();
    }

    /**
     * Save user preferences.
     */
    private void savePreferences() {
        try (FileOutputStream out = new FileOutputStream(prefsFile)) {
            prefs.store(out, "Apache Ignite Schema Load Utility");
        }
        catch (IOException e) {
            MessageBox.errorDialog(owner, "Failed to save preferences!", e);
        }
    }

    /** {@inheritDoc} */
    @Override public void stop() throws Exception {
        // Save window pos and size.
        setIntProp("window.x", (int)owner.getX());
        setIntProp("window.y", (int)owner.getY());
        setIntProp("window.width", (int)owner.getWidth());
        setIntProp("window.height", (int)owner.getHeight());

        // Save connection pane settings.
        setIntProp("jdbc.db.preset", rdbmsCb.getSelectionModel().getSelectedIndex());
        setStringProp("jdbc.driver.jar", jdbcDrvJarTf.getText());
        setStringProp("jdbc.driver.class", jdbcDrvClsTf.getText());
        setStringProp("jdbc.url", jdbcUrlTf.getText());
        setStringProp("jdbc.user", userTf.getText());

        // Save generation pane settings.
        setStringProp("out.folder", outFolderTf.getText());

        setStringProp("pojo.package", pkgTf.getText());
        setBoolProp("pojo.include", pojoIncludeKeysCh.isSelected());
        setBoolProp("pojo.constructor", pojoConstructorCh.isSelected());

        setBoolProp("xml.single", xmlSingleFileCh.isSelected());

        setStringProp("naming.pattern", regexTf.getText());
        setStringProp("naming.replace", replaceTf.getText());

        savePreferences();
    }

    /**
     * Schema load utility launcher.
     *
     * @param args Command line arguments passed to the application.
     */
    public static void main(String[] args) {
        // Workaround for JavaFX ugly text AA.
        System.setProperty("prism.lcdtext", "false");
        System.setProperty("prism.text", "t2k");

        // Workaround for AWT + JavaFX: we should initialize AWT before JavaFX.
        java.awt.Toolkit.getDefaultToolkit();

        // Workaround for JavaFX + Mac OS dock icon.
        if (System.getProperty("os.name").toLowerCase().contains("mac os")) {
            System.setProperty("javafx.macosx.embedded", "true");

            try {
                Class appCls = Class.forName("com.apple.eawt.Application");

                Object osxApp = appCls.getDeclaredMethod("getApplication").invoke(null);

                appCls.getDeclaredMethod("setDockIconImage", java.awt.Image.class)
                    .invoke(osxApp, fromFXImage(image("ignite", 128), null));
            }
            catch (Throwable ignore) {
                // No-op.
            }
        }

        launch(args);
    }

    /**
     * Special table cell to select possible java type conversions.
     */
    private static class JavaTypeCell extends TableCell {
        /** Combo box. */
        private final ComboBox comboBox;

        /** Creates a ComboBox cell factory for use in TableColumn controls. */
        public static Callback, TableCell> cellFactory() {
            return new Callback, TableCell>() {
                @Override public TableCell call(TableColumn col) {
                    return new JavaTypeCell();
                }
            };
        }

        /**
         * Default constructor.
         */
        private JavaTypeCell() {
            comboBox = new ComboBox<>(FXCollections.emptyObservableList());

            comboBox.valueProperty().addListener(new ChangeListener() {
                @Override public void changed(ObservableValue val, String oldVal, String newVal) {
                    if (isEditing())
                        commitEdit(newVal);
                }
            });

            getStyleClass().add("combo-box-table-cell");
        }

        /** {@inheritDoc} */
        @Override public void startEdit() {
            if (comboBox.getItems().size() > 1) {
                comboBox.getSelectionModel().select(getItem());

                super.startEdit();

                setText(null);
                setGraphic(comboBox);
            }
        }

        /** {@inheritDoc} */
        @Override public void cancelEdit() {
            super.cancelEdit();

            setText(getItem());

            setGraphic(null);
        }

        /** {@inheritDoc} */
        @Override public void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);

            setGraphic(null);

            if (!empty) {
                setText(item);

                TableRow row = getTableRow();

                if (row != null) {
                    PojoField pojo = (PojoField)row.getItem();

                    if (pojo != null) {
                        comboBox.setItems(pojo.conversions());

                        comboBox.getSelectionModel().select(pojo.javaTypeName());
                    }
                }
            }
        }
    }

    /**
     * Special table cell to select schema or table.
     */
    private static class PojoDescriptorCell extends TableCell {
        /** Creates a ComboBox cell factory for use in TableColumn controls. */
        public static Callback, TableCell> cellFactory() {
            return new Callback, TableCell>() {
                @Override public TableCell call(TableColumn col) {
                    return new PojoDescriptorCell();
                }
            };
        }

        /** Previous POJO bound to cell. */
        private PojoDescriptor prevPojo;

        /** {@inheritDoc} */
        @Override public void updateItem(Boolean item, boolean empty) {
            super.updateItem(item, empty);

            if (!empty) {
                TableRow row = getTableRow();

                if (row != null) {
                    final PojoDescriptor pojo = (PojoDescriptor)row.getItem();

                    if (pojo != prevPojo) {
                        prevPojo = pojo;

                        boolean isTbl = pojo.parent() != null;

                        CheckBox ch = new CheckBox();

                        ch.setAllowIndeterminate(false);

                        ch.indeterminateProperty().bindBidirectional(pojo.indeterminate());

                        ch.selectedProperty().bindBidirectional(pojo.useProperty());

                        Label lb = new Label(isTbl ? pojo.table() : pojo.schema());

                        Pane pnl = new HBox(5);
                        pnl.setPadding(new Insets(0, 0, 0, isTbl ? 25 : 5));
                        pnl.getChildren().addAll(ch, lb);

                        setGraphic(pnl);
                    }
                }
            }
        }
    }

    /**
     * Special table cell to select "used" fields for code generation.
     */
    private static class PojoFieldUseCell extends TableCell {
        /** Creates a ComboBox cell factory for use in TableColumn controls. */
        public static Callback, TableCell> cellFactory() {
            return new Callback, TableCell>() {
                @Override public TableCell call(TableColumn col) {
                    return new PojoFieldUseCell();
                }
            };
        }

        /** Previous POJO field bound to cell. */
        private PojoField prevField;

        /** {@inheritDoc} */
        @Override public void updateItem(Boolean item, boolean empty) {
            super.updateItem(item, empty);

            if (!empty) {
                TableRow row = getTableRow();

                if (row != null) {
                    final PojoField field = (PojoField)row.getItem();

                    if (field != prevField) {
                        prevField = field;

                        setAlignment(Pos.CENTER);

                        CheckBox ch = new CheckBox();

                        ch.setDisable(!field.nullable());

                        ch.selectedProperty().bindBidirectional(field.useProperty());

                        setGraphic(ch);
                    }
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy