http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e8d71b55/modules/schema-import-ui/src/main/java/org/apache/ignite/schema/ui/SchemaImportApp.java ---------------------------------------------------------------------- diff --git a/modules/schema-import-ui/src/main/java/org/apache/ignite/schema/ui/SchemaImportApp.java b/modules/schema-import-ui/src/main/java/org/apache/ignite/schema/ui/SchemaImportApp.java new file mode 100644 index 0000000..8779dac --- /dev/null +++ b/modules/schema-import-ui/src/main/java/org/apache/ignite/schema/ui/SchemaImportApp.java @@ -0,0 +1,1768 @@ +/* + * 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.Insets; +import javafx.geometry.*; +import javafx.scene.*; +import javafx.scene.control.Button; +import javafx.scene.control.*; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.*; +import javafx.stage.*; +import javafx.util.*; +import org.apache.ignite.internal.util.typedef.internal.*; +import org.apache.ignite.schema.generator.*; +import org.apache.ignite.schema.model.*; +import org.apache.ignite.schema.parser.*; + +import java.awt.*; +import java.io.*; +import java.net.*; +import java.sql.*; +import java.util.*; +import java.util.List; +import java.util.concurrent.*; +import java.util.logging.*; + +import static javafx.embed.swing.SwingFXUtils.*; +import static org.apache.ignite.schema.ui.Controls.*; + +/** + * Schema Import utility application. + */ +@SuppressWarnings("UnnecessaryFullyQualifiedName") +public class SchemaImportApp extends Application { + /** Logger. */ + private static final Logger log = Logger.getLogger(SchemaImportApp.class.getName()); + + /** 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("postgresql", "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 static final String PREF_WINDOW_X = "window.x"; + /** */ + private static final String PREF_WINDOW_Y = "window.y"; + /** */ + private static final String PREF_WINDOW_WIDTH = "window.width"; + /** */ + private static final String PREF_WINDOW_HEIGHT = "window.height"; + + /** */ + private static final String PREF_JDBC_DB_PRESET = "jdbc.db.preset"; + /** */ + private static final String PREF_JDBC_DRIVER_JAR = "jdbc.driver.jar"; + /** */ + private static final String PREF_JDBC_DRIVER_CLASS = "jdbc.driver.class"; + /** */ + private static final String PREF_JDBC_URL = "jdbc.url"; + /** */ + private static final String PREF_JDBC_USER = "jdbc.user"; + + /** */ + private static final String PREF_OUT_FOLDER = "out.folder"; + + /** */ + private static final String PREF_POJO_PACKAGE = "pojo.package"; + /** */ + private static final String PREF_POJO_INCLUDE = "pojo.include"; + /** */ + private static final String PREF_POJO_CONSTRUCTOR = "pojo.constructor"; + + /** */ + private static final String PREF_XML_SINGLE = "xml.single"; + + /** */ + private static final String PREF_NAMING_PATTERN = "naming.pattern"; + /** */ + private static final String PREF_NAMING_REPLACE = "naming.replace"; + + /** */ + 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<Preset> rdbmsCb; + + /** */ + private TextField jdbcDrvJarTf; + + /** */ + private TextField jdbcDrvClsTf; + + /** */ + private TextField jdbcUrlTf; + + /** */ + private TextField userTf; + + /** */ + private PasswordField pwdTf; + + /** */ + private ComboBox<String> parseCb; + + /** */ + private GridPaneEx connPnl; + + /** */ + private StackPane connLayerPnl; + + /** */ + private TableView<PojoDescriptor> pojosTbl; + + /** */ + private TableView<PojoField> 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<PojoDescriptor> pojos = FXCollections.emptyObservableList(); + + /** Currently selected POJO. */ + private PojoDescriptor curPojo; + + /** */ + private final Map<String, Driver> 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-import"); + + /** Empty POJO fields model. */ + private static final ObservableList<PojoField> 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-import-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<Void>() { + /** {@inheritDoc} */ + @Override protected Void call() throws Exception { + long started = System.currentTimeMillis(); + + try (Connection conn = connect(jdbcDrvJarPath, jdbcDrvCls, jdbcUrl, jdbcInfo)) { + pojos = FXCollections.observableList(DatabaseMetadataParser.parse(conn, tblsOnly)); + } + + perceptualDelay(started); + + return null; + } + + /** {@inheritDoc} */ + @Override protected void succeeded() { + try { + super.succeeded(); + + pojosTbl.setItems(pojos); + + if (pojos.isEmpty()) { + MessageBox.warningDialog(owner, "No tables found in database. Recheck JDBC URL.\n" + + "JDBC URL: " + jdbcUrl); + + return; + } + else + pojosTbl.getSelectionModel().clearAndSelect(0); + + curTbl = pojosTbl; + + pojosTbl.requestFocus(); + + + 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"); + } + finally { + unlockUI(connLayerPnl, connPnl, nextBtn); + } + } + + /** {@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<PojoDescriptor> selPojos = checkedPojos(); + + if (selPojos.isEmpty()) { + MessageBox.warningDialog(owner, "Please select tables to generate XML and POJOs files!"); + + return; + } + + if (checkInput(outFolderTf, true, "Output folder should not be empty!")) + 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<Void>() { + /** + * @param pojo POJO descriptor to check. + * @param selected Selected flag. + * @param msg Message to show in case of check failed. + */ + private void checkEmpty(final PojoDescriptor pojo, boolean selected, String msg) { + if (!selected) { + Platform.runLater(new Runnable() { + @Override public void run() { + TableView.TableViewSelectionModel<PojoDescriptor> 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<PojoDescriptor> 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); + + CodeGenerator.pojos(pojo, outFolder, pkg, constructor, includeKeys, askOverwrite); + } + + if (singleXml) + XmlGenerator.generate(pkg, all, includeKeys, new File(outFolder, "ignite-type-metadata.xml"), askOverwrite); + + CodeGenerator.snippet(all, pkg, includeKeys, outFolder, askOverwrite); + + perceptualDelay(started); + + return null; + } + + /** {@inheritDoc} */ + @Override protected void succeeded() { + super.succeeded(); + + unlockUI(genLayerPnl, genPnl, prevBtn, nextBtn); + + if (Desktop.isDesktopSupported() && 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<ActionEvent>() { + @Override public void handle(ActionEvent evt) { + prev(); + } + }); + + nextBtn = button("Next", "Go to \"POJO and XML generation\" page", new EventHandler<ActionEvent>() { + @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 (Exception 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. + * + * @return Pane with connection 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<ActionEvent>() { + @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<ActionEvent>() { + /** {@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 (Exception 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<Preset>() { + @Override public void changed(ObservableValue<? extends Preset> 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 (newVal.trim().isEmpty()) { + MessageBox.warningDialog(owner, (key ? "Key" : "Value") + " class name must be non empty!"); + + return false; + } + + 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<PojoDescriptor, Boolean> useCol = customColumn("Schema / Table", "use", + "If checked then this table will be used for XML and POJOs generation", PojoDescriptorCell.cellFactory()); + + TableColumn<PojoDescriptor, String> keyClsCol = textColumn("Key Class Name", "keyClassName", "Key class name", + new TextColumnValidator<PojoDescriptor>() { + @Override public boolean valid(PojoDescriptor rowVal, String newVal) { + boolean valid = checkClassName(rowVal, newVal, true); + + if (valid) + rowVal.keyClassName(newVal); + + return valid; + } + }); + + TableColumn<PojoDescriptor, String> valClsCol = textColumn("Value Class Name", "valueClassName", "Value class name", + new TextColumnValidator<PojoDescriptor>() { + @Override public boolean valid(PojoDescriptor rowVal, String newVal) { + boolean valid = checkClassName(rowVal, newVal, false); + + if (valid) + rowVal.valueClassName(newVal); + + return valid; + } + }); + + pojosTbl = tableView("Tables not found in database", useCol, keyClsCol, valClsCol); + + TableColumn<PojoField, Boolean> 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<PojoField, Boolean> keyCol = booleanColumn("Key", "key", + "Check to include this field into key object"); + + TableColumn<PojoField, Boolean> akCol = booleanColumn("AK", "affinityKey", + "Check to annotate key filed with @AffinityKeyMapped annotation in generated POJO class\n" + + "Note that a class can have only ONE key field annotated with @AffinityKeyMapped annotation"); + + TableColumn<PojoField, String> dbNameCol = tableColumn("DB Name", "dbName", "Field name in database"); + + TableColumn<PojoField, String> dbTypeNameCol = tableColumn("DB Type", "dbTypeName", "Field type in database"); + + TableColumn<PojoField, String> javaNameCol = textColumn("Java Name", "javaName", "Field name in POJO class", + new TextColumnValidator<PojoField>() { + @Override public boolean valid(PojoField rowVal, String newVal) { + if (newVal.trim().isEmpty()) { + MessageBox.warningDialog(owner, "Java name must be non empty!"); + + return false; + } + + 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<PojoField, String> 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<ActionEvent>() { + @Override public void handle(ActionEvent evt) { + DirectoryChooser dc = new DirectoryChooser(); + + try { + File outFolder = new File(outFolderTf.getText()); + + if (outFolder.exists()) + dc.setInitialDirectory(outFolder); + } + catch (Exception 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-type-metadata.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<String> 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<ActionEvent>() { + @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<PojoDescriptor> selPojos = pojosTbl.getSelectionModel().getSelectedItems(); + + Collection<PojoField> 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<ActionEvent>() { + @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<PojoDescriptor> selPojos = pojosTbl.getSelectionModel().getSelectedItems(); + + Collection<PojoField> 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<PojoDescriptor>() { + @Override public void changed(ObservableValue<? extends PojoDescriptor> val, + PojoDescriptor oldVal, PojoDescriptor newItem) { + if (newItem != null && newItem.parent() != null) { + curPojo = newItem; + + fieldsTbl.setItems(FXCollections.observableList(curPojo.fields())); + fieldsTbl.getSelectionModel().clearSelection(); + + keyValPnl.setDisable(false); + } + else { + curPojo = null; + fieldsTbl.setItems(NO_FIELDS); + + keyValPnl.setDisable(true); + } + } + }); + + pojosTbl.focusedProperty().addListener(new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> val, Boolean oldVal, Boolean newVal) { + if (newVal) + curTbl = pojosTbl; + } + }); + + fieldsTbl.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>() { + @Override public void changed(ObservableValue<? extends Number> val, Number oldVal, Number newVal) { + if (curPojo != null) { + TableView.TableViewSelectionModel<PojoDescriptor> selMdl = pojosTbl.getSelectionModel(); + + List<Integer> selIndices = new ArrayList<>(selMdl.getSelectedIndices()); + + if (selIndices.size() > 1) { + for (Integer idx : selIndices) { + if (pojos.get(idx) != curPojo) + selMdl.clearSelection(idx); + } + } + } + } + }); + + fieldsTbl.focusedProperty().addListener(new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> 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<PojoDescriptor> 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<PojoDescriptor> 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<PojoDescriptor> 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<PojoField> 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<PojoDescriptor> 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<PojoDescriptor> 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<PojoDescriptor> 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<PojoField> selFields) { + for (PojoField field : selFields) + field.resetJavaName(); + } + + /** + * @return POJOs checked in table-tree-view. + */ + private Collection<PojoDescriptor> checkedPojos() { + Collection<PojoDescriptor> 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. + * @return Property value as string. + */ + 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. + * @return Property value as int. + */ + 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. + * @return Property value as boolean. + */ + 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)); + } + + /** + * Resolve path. + * + * @param key Preferences key. + * @param dflt Default value. + * @return String with full file path or default value. + */ + private String resolveFilePath(String key, String dflt) { + String path = prefs.getProperty(key); + + if (path != null) { + File file = U.resolveIgnitePath(path); + + if (file != null) + return file.getAbsolutePath(); + } + + return dflt; + } + + /** {@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 e) { + log.log(Level.SEVERE, "Failed to load preferences. Default preferences will be used", e); + } + + // Load custom preferences. + List<String> params = getParameters().getRaw(); + + if (!params.isEmpty()) { + String customPrefsFileName = params.get(0); + + if (customPrefsFileName.isEmpty()) + log.log(Level.WARNING, "Path to file with custom preferences is not specified."); + else { + File customPrefsFile = U.resolveIgnitePath(customPrefsFileName); + + if (customPrefsFile == null) + log.log(Level.WARNING, "Failed to resolve path to file with custom preferences: " + + customPrefsFile); + else { + Properties customPrefs = new Properties(); + + try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(customPrefsFile))) { + customPrefs.load(in); + } + catch (IOException e) { + log.log(Level.SEVERE, "Failed to load custom preferences.", e); + } + + prefs.putAll(customPrefs); + } + } + } + + // 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 Import 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(PREF_WINDOW_X) != null) { + int x = getIntProp(PREF_WINDOW_X, 100); + int y = getIntProp(PREF_WINDOW_Y, 100); + int w = getIntProp(PREF_WINDOW_WIDTH, 650); + int h = getIntProp(PREF_WINDOW_HEIGHT, 650); + + // Ensure that window fit any available screen. + if (!Screen.getScreensForRectangle(x, y, w, h).isEmpty()) { + primaryStage.setX(x); + 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(PREF_JDBC_DB_PRESET, 0)); + jdbcDrvJarTf.setText(resolveFilePath(PREF_JDBC_DRIVER_JAR, "h2.jar")); + jdbcDrvClsTf.setText(getStringProp(PREF_JDBC_DRIVER_CLASS, "org.h2.Driver")); + jdbcUrlTf.setText(getStringProp(PREF_JDBC_URL, "jdbc:h2:" + userHome + "/ignite-schema-import/db")); + userTf.setText(getStringProp(PREF_JDBC_USER, "sa")); + + // Restore generation pane settings. + outFolderTf.setText(resolveFilePath(PREF_OUT_FOLDER, userHome + "/ignite-schema-import/out")); + + pkgTf.setText(getStringProp(PREF_POJO_PACKAGE, "org.apache.ignite")); + pojoIncludeKeysCh.setSelected(getBoolProp(PREF_POJO_INCLUDE, true)); + pojoConstructorCh.setSelected(getBoolProp(PREF_POJO_CONSTRUCTOR, false)); + + xmlSingleFileCh.setSelected(getBoolProp(PREF_XML_SINGLE, true)); + + regexTf.setText(getStringProp(PREF_NAMING_PATTERN, "(\\w+)")); + replaceTf.setText(getStringProp(PREF_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 Import 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(PREF_WINDOW_X, (int)owner.getX()); + setIntProp(PREF_WINDOW_Y, (int)owner.getY()); + setIntProp(PREF_WINDOW_WIDTH, (int)owner.getWidth()); + setIntProp(PREF_WINDOW_HEIGHT, (int)owner.getHeight()); + + // Save connection pane settings. + setIntProp(PREF_JDBC_DB_PRESET, rdbmsCb.getSelectionModel().getSelectedIndex()); + setStringProp(PREF_JDBC_DRIVER_JAR, jdbcDrvJarTf.getText()); + setStringProp(PREF_JDBC_DRIVER_CLASS, jdbcDrvClsTf.getText()); + setStringProp(PREF_JDBC_URL, jdbcUrlTf.getText()); + setStringProp(PREF_JDBC_USER, userTf.getText()); + + // Save generation pane settings. + setStringProp(PREF_OUT_FOLDER, outFolderTf.getText()); + + setStringProp(PREF_POJO_PACKAGE, pkgTf.getText()); + setBoolProp(PREF_POJO_INCLUDE, pojoIncludeKeysCh.isSelected()); + setBoolProp(PREF_POJO_CONSTRUCTOR, pojoConstructorCh.isSelected()); + + setBoolProp(PREF_XML_SINGLE, xmlSingleFileCh.isSelected()); + + setStringProp(PREF_NAMING_PATTERN, regexTf.getText()); + setStringProp(PREF_NAMING_REPLACE, replaceTf.getText()); + + savePreferences(); + } + + /** + * Schema Import 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 (Exception ignore) { + // No-op. + } + } + + launch(args); + } + + /** + * Special table cell to select possible java type conversions. + */ + private static class JavaTypeCell extends TableCell<PojoField, String> { + /** Combo box. */ + private final ComboBox<String> comboBox; + + /** + * Creates a ComboBox cell factory for use in TableColumn controls. + * + * @return Cell factory for cell with java types combobox. + */ + public static Callback<TableColumn<PojoField, String>, TableCell<PojoField, String>> cellFactory() { + return new Callback<TableColumn<PojoField, String>, TableCell<PojoField, String>>() { + @Override public TableCell<PojoField, String> call(TableColumn<PojoField, String> col) { + return new JavaTypeCell(); + } + }; + } + + /** + * Default constructor. + */ + private JavaTypeCell() { + comboBox = new ComboBox<>(FXCollections.<String>emptyObservableList()); + + comboBox.valueProperty().addListener(new ChangeListener<String>() { + @Override public void changed(ObservableValue<? extends String> 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); + setText(null); + + if (!empty) { + setText(item); + + TableRow row = getTableRow(); + + if (row != null) { + PojoField pojo = (PojoField)row.getItem(); + + if (pojo != null) { + comboBox.setItems(FXCollections.observableList(pojo.conversions())); + comboBox.getSelectionModel().select(pojo.javaTypeName()); + } + } + } + } + } + + /** + * Special table cell to select schema or table. + */ + private static class PojoDescriptorCell extends TableCell<PojoDescriptor, Boolean> { + /** + * Creates a ComboBox cell factory for use in TableColumn controls. + * + * @return Cell factory for schema / table selection. + */ + public static Callback<TableColumn<PojoDescriptor, Boolean>, TableCell<PojoDescriptor, Boolean>> cellFactory() { + return new Callback<TableColumn<PojoDescriptor, Boolean>, TableCell<PojoDescriptor, Boolean>>() { + @Override public TableCell<PojoDescriptor, Boolean> call(TableColumn<PojoDescriptor, Boolean> col) { + return new PojoDescriptorCell(); + } + }; + } + + /** Previous POJO bound to cell. */ + private PojoDescriptor prevPojo; + + /** Previous cell graphic. */ + private Pane prevGraphic; + + /** {@inheritDoc} */ + @Override public void updateItem(Boolean item, boolean empty) { + super.updateItem(item, empty); + + setGraphic(null); + + if (!empty) { + TableRow row = getTableRow(); + + if (row != null) { + final PojoDescriptor pojo = (PojoDescriptor)row.getItem(); + + if (pojo != null) { + if (prevGraphic == null || pojo != prevPojo) { + boolean isTbl = pojo.parent() != null; + + CheckBox ch = new CheckBox(); + ch.setAllowIndeterminate(false); + ch.indeterminateProperty().bindBidirectional(JavaFxUtils.booleanProperty(pojo.indeterminate())); + ch.selectedProperty().bindBidirectional(JavaFxUtils.booleanProperty(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); + + prevPojo = pojo; + prevGraphic = pnl; + } + + setGraphic(prevGraphic); + } + } + } + } + } + + /** + * Special table cell to select "used" fields for code generation. + */ + private static class PojoFieldUseCell extends TableCell<PojoField, Boolean> { + /** + * Creates a ComboBox cell factory for use in TableColumn controls. + * + * @return Cell factory for used fields selection. + */ + public static Callback<TableColumn<PojoField, Boolean>, TableCell<PojoField, Boolean>> cellFactory() { + return new Callback<TableColumn<PojoField, Boolean>, TableCell<PojoField, Boolean>>() { + @Override public TableCell<PojoField, Boolean> call(TableColumn<PojoField, Boolean> col) { + return new PojoFieldUseCell(); + } + }; + } + + /** Previous POJO field bound to cell. */ + private PojoField prevField; + + /** Previous cell graphic. */ + private CheckBox prevGraphic; + + /** {@inheritDoc} */ + @Override public void updateItem(Boolean item, boolean empty) { + super.updateItem(item, empty); + + setGraphic(null); + + if (!empty) { + TableRow row = getTableRow(); + + if (row != null) { + final PojoField field = (PojoField)row.getItem(); + + if (field != null) { + if (prevGraphic == null || prevField != field) { + setAlignment(Pos.CENTER); + + CheckBox ch = new CheckBox(); + ch.setDisable(!field.nullable()); + ch.selectedProperty().bindBidirectional(JavaFxUtils.booleanProperty(field.useProperty())); + + prevField = field; + prevGraphic = ch; + } + + setGraphic(prevGraphic); + } + } + } + } + } +}
http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e8d71b55/modules/schema-import-ui/src/main/java/org/apache/ignite/schema/ui/TextColumnValidator.java ---------------------------------------------------------------------- diff --git a/modules/schema-import-ui/src/main/java/org/apache/ignite/schema/ui/TextColumnValidator.java b/modules/schema-import-ui/src/main/java/org/apache/ignite/schema/ui/TextColumnValidator.java new file mode 100644 index 0000000..d92ae08 --- /dev/null +++ b/modules/schema-import-ui/src/main/java/org/apache/ignite/schema/ui/TextColumnValidator.java @@ -0,0 +1,32 @@ +/* + * 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; + +/** + * Validator for editable table view text column. + */ +public interface TextColumnValidator<T> { + /** + * Validate new value of text. + * + * @param rowVal Row value. + * @param newVal New value of text. + * @return {@code true} if text is valid. + */ + public boolean valid(T rowVal, String newVal); +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e8d71b55/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/AbstractSchemaImportTest.java ---------------------------------------------------------------------- diff --git a/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/AbstractSchemaImportTest.java b/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/AbstractSchemaImportTest.java new file mode 100644 index 0000000..191f929 --- /dev/null +++ b/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/AbstractSchemaImportTest.java @@ -0,0 +1,132 @@ +/* + * 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.test; + +import junit.framework.*; +import org.apache.ignite.internal.util.typedef.internal.*; +import org.apache.ignite.schema.model.*; +import org.apache.ignite.schema.parser.*; +import org.apache.ignite.schema.ui.*; + +import java.io.*; +import java.sql.*; +import java.util.*; + +/** + * Base functional for Ignite Schema Import utility tests. + */ +public abstract class AbstractSchemaImportTest extends TestCase { + /** DB connection URL. */ + private static final String CONN_URL = "jdbc:h2:mem:autoCacheStore;DB_CLOSE_DELAY=-1"; + + /** Path to temp folder where generated POJOs will be saved. */ + protected static final String OUT_DIR_PATH = System.getProperty("java.io.tmpdir") + "/ignite-schema-import/out"; + + /** Auto confirmation of file conflicts. */ + protected ConfirmCallable askOverwrite = new ConfirmCallable(null, "") { + @Override public Result confirm(String msg) { + return Result.OVERRIDE; + } + }; + + /** List of generated for test database POJO objects. */ + protected List<PojoDescriptor> pojos; + + /** {@inheritDoc} */ + @Override public void setUp() throws Exception { + Class.forName("org.h2.Driver"); + + Connection conn = DriverManager.getConnection(CONN_URL, "sa", ""); + + Statement stmt = conn.createStatement(); + + stmt.executeUpdate("CREATE TABLE IF NOT EXISTS PRIMITIVES (pk INTEGER PRIMARY KEY, " + + " boolCol BOOLEAN NOT NULL," + + " byteCol TINYINT NOT NULL," + + " shortCol SMALLINT NOT NULL," + + " intCol INTEGER NOT NULL, " + + " longCol BIGINT NOT NULL," + + " floatCol REAL NOT NULL," + + " doubleCol DOUBLE NOT NULL," + + " doubleCol2 DOUBLE NOT NULL, " + + " bigDecimalCol DECIMAL(10, 0)," + + " strCol VARCHAR(10)," + + " dateCol DATE," + + " timeCol TIME," + + " tsCol TIMESTAMP, " + + " arrCol BINARY(10))"); + + stmt.executeUpdate("CREATE TABLE IF NOT EXISTS OBJECTS (pk INTEGER PRIMARY KEY, " + + " boolCol BOOLEAN," + + " byteCol TINYINT," + + " shortCol SMALLINT," + + " intCol INTEGER," + + " longCol BIGINT," + + " floatCol REAL," + + " doubleCol DOUBLE," + + " doubleCol2 DOUBLE," + + " bigDecimalCol DECIMAL(10, 0)," + + " strCol VARCHAR(10), " + + " dateCol DATE," + + " timeCol TIME," + + " tsCol TIMESTAMP," + + " arrCol BINARY(10))"); + + conn.commit(); + + U.closeQuiet(stmt); + + pojos = DatabaseMetadataParser.parse(conn, false); + + U.closeQuiet(conn); + } + + /** + * Compare files by lines. + * + * @param exp Stream to read of expected file from test resources. + * @param generated Generated file instance. + * @param excludePtrn Marker string to exclude lines from comparing. + * @return true if generated file correspond to expected. + */ + protected boolean compareFilesInt(InputStream exp, File generated, String excludePtrn) { + try (BufferedReader baseReader = new BufferedReader(new InputStreamReader(exp))) { + try (BufferedReader generatedReader = new BufferedReader(new FileReader(generated))) { + String baseLine; + + while ((baseLine = baseReader.readLine()) != null) { + String generatedLine = generatedReader.readLine(); + + if (!baseLine.equals(generatedLine) && !baseLine.contains(excludePtrn) + && !generatedLine.contains(excludePtrn)) { + System.out.println("Expected: " + baseLine); + System.out.println("Generated: " + generatedLine); + + return false; + } + } + + return true; + } + } catch (IOException e) { + e.printStackTrace(); + + return false; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e8d71b55/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/generator/CodeGeneratorTest.java ---------------------------------------------------------------------- diff --git a/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/generator/CodeGeneratorTest.java b/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/generator/CodeGeneratorTest.java new file mode 100644 index 0000000..d758e65 --- /dev/null +++ b/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/generator/CodeGeneratorTest.java @@ -0,0 +1,70 @@ +/* + * 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.test.generator; + +import org.apache.ignite.schema.generator.*; +import org.apache.ignite.schema.model.*; +import org.apache.ignite.schema.test.*; + +import java.io.*; + +/** + * Tests for POJO generator. + */ +public class CodeGeneratorTest extends AbstractSchemaImportTest { + /** Marker string to skip date generation while comparing.*/ + private static final String GEN_PTRN = "Code generated by Apache Ignite Schema Import utility"; + + /** + * Test that POJOs generated correctly. + */ + public void testPojoGeneration() throws Exception { + String pkg = "org.apache.ignite.schema.test.model"; + String intPath = "org/apache/ignite/schema/test/model"; + + Boolean containsSchema = false; + + for (PojoDescriptor pojo : pojos) { + if (pojo.valueClassName().isEmpty()) + containsSchema = true; + else { + CodeGenerator.pojos(pojo, OUT_DIR_PATH, pkg, true, true, askOverwrite); + + assertTrue("Generated key class POJO content is differ from expected for type " + pojo.keyClassName(), + compareFiles(pojo.keyClassName(), intPath, GEN_PTRN)); + + assertTrue("Generated value class POJO content is differ from expected for type " + pojo.valueClassName(), + compareFiles(pojo.valueClassName(), intPath, GEN_PTRN)); + } + } + + assertTrue("Generated POJOs does not contains schema.", containsSchema); + } + + /** + * @param typeName Type name. + * @param intPath Internal path. + * @return {@code true} if generated POJO as expected. + */ + private boolean compareFiles(String typeName, String intPath, String excludePtrn) { + String relPath = intPath + "/" + typeName; + + return compareFilesInt(getClass().getResourceAsStream("/" + relPath + ".txt"), + new File(OUT_DIR_PATH + "/" + relPath + ".java"), excludePtrn); + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e8d71b55/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/generator/XmlGeneratorTest.java ---------------------------------------------------------------------- diff --git a/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/generator/XmlGeneratorTest.java b/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/generator/XmlGeneratorTest.java new file mode 100644 index 0000000..af6469d --- /dev/null +++ b/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/generator/XmlGeneratorTest.java @@ -0,0 +1,50 @@ +/* + * 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.test.generator; + +import org.apache.ignite.schema.generator.*; +import org.apache.ignite.schema.model.*; +import org.apache.ignite.schema.test.*; + +import java.io.*; +import java.util.*; + +/** + * Tests for XML generator. + */ +public class XmlGeneratorTest extends AbstractSchemaImportTest { + /** + * Test that XML generated correctly. + */ + public void testXmlGeneration() throws Exception { + Collection<PojoDescriptor> all = new ArrayList<>(); + + for (PojoDescriptor pojo : pojos) + if (pojo.parent() != null) + all.add(pojo); + + String fileName = "ignite-type-metadata.xml"; + + XmlGenerator.generate("org.apache.ignite.schema.test.model", all, true, new File(OUT_DIR_PATH, fileName), + askOverwrite); + + assertTrue("Generated XML file content is differ from expected one", + compareFilesInt(getClass().getResourceAsStream("/org/apache/ignite/schema/test/model/" + fileName), + new File(OUT_DIR_PATH + "/" + fileName), "XML generated by Apache Ignite Schema Import utility")); + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e8d71b55/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/model/Objects.txt ---------------------------------------------------------------------- diff --git a/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/model/Objects.txt b/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/model/Objects.txt new file mode 100644 index 0000000..c448ab5 --- /dev/null +++ b/modules/schema-import-ui/src/test/java/org/apache/ignite/schema/test/model/Objects.txt @@ -0,0 +1,502 @@ +/* + * 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.test.model; + +import java.io.*; + +/** + * Objects definition. + * + * Code generated by Apache Ignite Schema Import utility: 01/27/2015. + */ +public class Objects implements Serializable { + /** */ + private static final long serialVersionUID = 0L; + + /** Value for pk. */ + private int pk; + + /** Value for boolcol. */ + private Boolean boolcol; + + /** Value for bytecol. */ + private Byte bytecol; + + /** Value for shortcol. */ + private Short shortcol; + + /** Value for intcol. */ + private Integer intcol; + + /** Value for longcol. */ + private Long longcol; + + /** Value for floatcol. */ + private Float floatcol; + + /** Value for doublecol. */ + private Double doublecol; + + /** Value for doublecol2. */ + private Double doublecol2; + + /** Value for bigdecimalcol. */ + private java.math.BigDecimal bigdecimalcol; + + /** Value for strcol. */ + private String strcol; + + /** Value for datecol. */ + private java.sql.Date datecol; + + /** Value for timecol. */ + private java.sql.Time timecol; + + /** Value for tscol. */ + private java.sql.Timestamp tscol; + + /** Value for arrcol. */ + private Object arrcol; + + /** + * Empty constructor. + */ + public Objects() { + // No-op. + } + + /** + * Full constructor. + */ + public Objects( + int pk, + Boolean boolcol, + Byte bytecol, + Short shortcol, + Integer intcol, + Long longcol, + Float floatcol, + Double doublecol, + Double doublecol2, + java.math.BigDecimal bigdecimalcol, + String strcol, + java.sql.Date datecol, + java.sql.Time timecol, + java.sql.Timestamp tscol, + Object arrcol + ) { + this.pk = pk; + this.boolcol = boolcol; + this.bytecol = bytecol; + this.shortcol = shortcol; + this.intcol = intcol; + this.longcol = longcol; + this.floatcol = floatcol; + this.doublecol = doublecol; + this.doublecol2 = doublecol2; + this.bigdecimalcol = bigdecimalcol; + this.strcol = strcol; + this.datecol = datecol; + this.timecol = timecol; + this.tscol = tscol; + this.arrcol = arrcol; + } + + /** + * Gets pk. + * + * @return Value for pk. + */ + public int getPk() { + return pk; + } + + /** + * Sets pk. + * + * @param pk New value for pk. + */ + public void setPk(int pk) { + this.pk = pk; + } + + /** + * Gets boolcol. + * + * @return Value for boolcol. + */ + public Boolean getBoolcol() { + return boolcol; + } + + /** + * Sets boolcol. + * + * @param boolcol New value for boolcol. + */ + public void setBoolcol(Boolean boolcol) { + this.boolcol = boolcol; + } + + /** + * Gets bytecol. + * + * @return Value for bytecol. + */ + public Byte getBytecol() { + return bytecol; + } + + /** + * Sets bytecol. + * + * @param bytecol New value for bytecol. + */ + public void setBytecol(Byte bytecol) { + this.bytecol = bytecol; + } + + /** + * Gets shortcol. + * + * @return Value for shortcol. + */ + public Short getShortcol() { + return shortcol; + } + + /** + * Sets shortcol. + * + * @param shortcol New value for shortcol. + */ + public void setShortcol(Short shortcol) { + this.shortcol = shortcol; + } + + /** + * Gets intcol. + * + * @return Value for intcol. + */ + public Integer getIntcol() { + return intcol; + } + + /** + * Sets intcol. + * + * @param intcol New value for intcol. + */ + public void setIntcol(Integer intcol) { + this.intcol = intcol; + } + + /** + * Gets longcol. + * + * @return Value for longcol. + */ + public Long getLongcol() { + return longcol; + } + + /** + * Sets longcol. + * + * @param longcol New value for longcol. + */ + public void setLongcol(Long longcol) { + this.longcol = longcol; + } + + /** + * Gets floatcol. + * + * @return Value for floatcol. + */ + public Float getFloatcol() { + return floatcol; + } + + /** + * Sets floatcol. + * + * @param floatcol New value for floatcol. + */ + public void setFloatcol(Float floatcol) { + this.floatcol = floatcol; + } + + /** + * Gets doublecol. + * + * @return Value for doublecol. + */ + public Double getDoublecol() { + return doublecol; + } + + /** + * Sets doublecol. + * + * @param doublecol New value for doublecol. + */ + public void setDoublecol(Double doublecol) { + this.doublecol = doublecol; + } + + /** + * Gets doublecol2. + * + * @return Value for doublecol2. + */ + public Double getDoublecol2() { + return doublecol2; + } + + /** + * Sets doublecol2. + * + * @param doublecol2 New value for doublecol2. + */ + public void setDoublecol2(Double doublecol2) { + this.doublecol2 = doublecol2; + } + + /** + * Gets bigdecimalcol. + * + * @return Value for bigdecimalcol. + */ + public java.math.BigDecimal getBigdecimalcol() { + return bigdecimalcol; + } + + /** + * Sets bigdecimalcol. + * + * @param bigdecimalcol New value for bigdecimalcol. + */ + public void setBigdecimalcol(java.math.BigDecimal bigdecimalcol) { + this.bigdecimalcol = bigdecimalcol; + } + + /** + * Gets strcol. + * + * @return Value for strcol. + */ + public String getStrcol() { + return strcol; + } + + /** + * Sets strcol. + * + * @param strcol New value for strcol. + */ + public void setStrcol(String strcol) { + this.strcol = strcol; + } + + /** + * Gets datecol. + * + * @return Value for datecol. + */ + public java.sql.Date getDatecol() { + return datecol; + } + + /** + * Sets datecol. + * + * @param datecol New value for datecol. + */ + public void setDatecol(java.sql.Date datecol) { + this.datecol = datecol; + } + + /** + * Gets timecol. + * + * @return Value for timecol. + */ + public java.sql.Time getTimecol() { + return timecol; + } + + /** + * Sets timecol. + * + * @param timecol New value for timecol. + */ + public void setTimecol(java.sql.Time timecol) { + this.timecol = timecol; + } + + /** + * Gets tscol. + * + * @return Value for tscol. + */ + public java.sql.Timestamp getTscol() { + return tscol; + } + + /** + * Sets tscol. + * + * @param tscol New value for tscol. + */ + public void setTscol(java.sql.Timestamp tscol) { + this.tscol = tscol; + } + + /** + * Gets arrcol. + * + * @return Value for arrcol. + */ + public Object getArrcol() { + return arrcol; + } + + /** + * Sets arrcol. + * + * @param arrcol New value for arrcol. + */ + public void setArrcol(Object arrcol) { + this.arrcol = arrcol; + } + + /** {@inheritDoc} */ + @Override public boolean equals(Object o) { + if (this == o) + return true; + + if (!(o instanceof Objects)) + return false; + + Objects that = (Objects)o; + + if (pk != that.pk) + return false; + + if (boolcol != null ? !boolcol.equals(that.boolcol) : that.boolcol != null) + return false; + + if (bytecol != null ? !bytecol.equals(that.bytecol) : that.bytecol != null) + return false; + + if (shortcol != null ? !shortcol.equals(that.shortcol) : that.shortcol != null) + return false; + + if (intcol != null ? !intcol.equals(that.intcol) : that.intcol != null) + return false; + + if (longcol != null ? !longcol.equals(that.longcol) : that.longcol != null) + return false; + + if (floatcol != null ? !floatcol.equals(that.floatcol) : that.floatcol != null) + return false; + + if (doublecol != null ? !doublecol.equals(that.doublecol) : that.doublecol != null) + return false; + + if (doublecol2 != null ? !doublecol2.equals(that.doublecol2) : that.doublecol2 != null) + return false; + + if (bigdecimalcol != null ? !bigdecimalcol.equals(that.bigdecimalcol) : that.bigdecimalcol != null) + return false; + + if (strcol != null ? !strcol.equals(that.strcol) : that.strcol != null) + return false; + + if (datecol != null ? !datecol.equals(that.datecol) : that.datecol != null) + return false; + + if (timecol != null ? !timecol.equals(that.timecol) : that.timecol != null) + return false; + + if (tscol != null ? !tscol.equals(that.tscol) : that.tscol != null) + return false; + + if (arrcol != null ? !arrcol.equals(that.arrcol) : that.arrcol != null) + return false; + + return true; + } + + /** {@inheritDoc} */ + @Override public int hashCode() { + int res = pk; + + res = 31 * res + (boolcol != null ? boolcol.hashCode() : 0); + + res = 31 * res + (bytecol != null ? bytecol.hashCode() : 0); + + res = 31 * res + (shortcol != null ? shortcol.hashCode() : 0); + + res = 31 * res + (intcol != null ? intcol.hashCode() : 0); + + res = 31 * res + (longcol != null ? longcol.hashCode() : 0); + + res = 31 * res + (floatcol != null ? floatcol.hashCode() : 0); + + res = 31 * res + (doublecol != null ? doublecol.hashCode() : 0); + + res = 31 * res + (doublecol2 != null ? doublecol2.hashCode() : 0); + + res = 31 * res + (bigdecimalcol != null ? bigdecimalcol.hashCode() : 0); + + res = 31 * res + (strcol != null ? strcol.hashCode() : 0); + + res = 31 * res + (datecol != null ? datecol.hashCode() : 0); + + res = 31 * res + (timecol != null ? timecol.hashCode() : 0); + + res = 31 * res + (tscol != null ? tscol.hashCode() : 0); + + res = 31 * res + (arrcol != null ? arrcol.hashCode() : 0); + + return res; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return "Objects [pk=" + pk + + ", boolcol=" + boolcol + + ", bytecol=" + bytecol + + ", shortcol=" + shortcol + + ", intcol=" + intcol + + ", longcol=" + longcol + + ", floatcol=" + floatcol + + ", doublecol=" + doublecol + + ", doublecol2=" + doublecol2 + + ", bigdecimalcol=" + bigdecimalcol + + ", strcol=" + strcol + + ", datecol=" + datecol + + ", timecol=" + timecol + + ", tscol=" + tscol + + ", arrcol=" + arrcol + + "]"; + } +}