content: /*****************************************************************
   *   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
   *
   *    https://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.cayenne.modeler.dialog.db.gen;
  
  import org.apache.cayenne.access.DbGenerator;
  import org.apache.cayenne.dba.DbAdapter;
  import org.apache.cayenne.log.NoopJdbcEventLogger;
  import org.apache.cayenne.map.DataMap;
  import org.apache.cayenne.modeler.ProjectController;
  import org.apache.cayenne.modeler.dialog.ValidationResultBrowser;
  import org.apache.cayenne.modeler.dialog.db.DataSourceWizard;
  import org.apache.cayenne.modeler.pref.DBConnectionInfo;
  import org.apache.cayenne.modeler.pref.DBGeneratorDefaults;
  import org.apache.cayenne.modeler.util.CayenneController;
  import org.apache.cayenne.modeler.util.DbAdapterInfo;
  import org.apache.cayenne.swing.BindingBuilder;
  import org.apache.cayenne.swing.ObjectBinding;
  import org.apache.cayenne.validation.ValidationResult;
  
  import javax.swing.DefaultComboBoxModel;
  import javax.swing.JFileChooser;
  import javax.swing.JOptionPane;
  import javax.swing.WindowConstants;
  import javax.swing.event.ChangeEvent;
  import javax.swing.event.ChangeListener;
  import java.awt.Component;
  import java.io.File;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Iterator;
  
  /**
   */
  public class DBGeneratorOptions extends CayenneController {
  
      protected DBGeneratorOptionsView view;
      protected ObjectBinding[] optionBindings;
      protected ObjectBinding sqlBinding;
      protected ObjectBinding adapterBinding;
  
      protected DBConnectionInfo connectionInfo;
      protected Collection<DataMap> dataMaps;
      protected DBGeneratorDefaults generatorDefaults;
      protected Collection<DbGenerator> generators;
      protected String textForSQL;
  
      protected TableSelectorController tables;
  
      public DBGeneratorOptions(ProjectController parent, String title, Collection<DataMap> dataMaps) {
          super(parent);
  
          this.dataMaps = dataMaps;
          this.tables = new TableSelectorController(parent);
          this.view = new DBGeneratorOptionsView(tables.getView());
          this.connectionInfo = new DBConnectionInfo();
          // DataSource may not be initialized, so warn connection wizard
          this.connectionInfo.setAllowDataSourceFailure(true);
          this.generatorDefaults = new DBGeneratorDefaults(parent
                  .getPreferenceForProject()
                  .node("DbGenerator"));
  
          this.view.setTitle(title);
          initController();
          connectionInfo.setDbAdapter((String) view.getAdapters().getSelectedItem());
  
          tables.updateTables(dataMaps);
          prepareGenerator();
          generatorDefaults.configureGenerator(generators);
          createSQL();
          refreshView();
      }
  
      public Component getView() {
          return view;
      }
  
      public DBGeneratorDefaults getGeneratorDefaults() {
          return generatorDefaults;
      }
  
      public String getTextForSQL() {
          return textForSQL;
      }
  
      protected void initController() {
  
          DefaultComboBoxModel<String> adapterModel = new DefaultComboBoxModel<>(
                  DbAdapterInfo.getStandardAdapters());
          view.getAdapters().setModel(adapterModel);
          view.getAdapters().setSelectedIndex(0);
  
          BindingBuilder builder = new BindingBuilder(
                  getApplication().getBindingFactory(),
                  this);
  
          sqlBinding = builder.bindToTextArea(view.getSql(), "textForSQL");
          adapterBinding = builder.bindToComboSelection(
                  view.getAdapters(),
                  "connectionInfo.dbAdapter",
                  "refreshSQLAction()",
                  "org.apache.cayenne.dba.JdbcAdapter");
  
          optionBindings = new ObjectBinding[5];
          optionBindings[0] = builder.bindToStateChangeAndAction(
                  view.getCreateFK(),
                  "generatorDefaults.createFK",
                  "refreshSQLAction()");
          optionBindings[1] = builder.bindToStateChangeAndAction(
                  view.getCreatePK(),
                  "generatorDefaults.createPK",
                  "refreshSQLAction()");
          optionBindings[2] = builder.bindToStateChangeAndAction(
                  view.getCreateTables(),
                  "generatorDefaults.createTables",
                  "refreshSQLAction()");
          optionBindings[3] = builder.bindToStateChangeAndAction(
                  view.getDropPK(),
                  "generatorDefaults.dropPK",
                  "refreshSQLAction()");
          optionBindings[4] = builder.bindToStateChangeAndAction(
                  view.getDropTables(),
                  "generatorDefaults.dropTables",
                  "refreshSQLAction()");
  
          builder.bindToAction(view.getGenerateButton(), "generateSchemaAction()");
          builder.bindToAction(view.getSaveSqlButton(), "storeSQLAction()");
          builder.bindToAction(view.getCancelButton(), "closeAction()");
  
          // refresh SQL if different tables were selected
          view.getTabs().addChangeListener(new ChangeListener() {
  
              public void stateChanged(ChangeEvent e) {
                  if (view.getTabs().getSelectedIndex() == 0) {
                      // this assumes that some tables where checked/unchecked... not very
                      // efficient
                      refreshGeneratorAction();
                  }
              }
          });
      }
  
      /**
       * Creates new internal DbGenerator instance.
       */
      protected void prepareGenerator() {
          try {
              DbAdapter adapter = connectionInfo.makeAdapter(getApplication().getClassLoadingService());
              generators = new ArrayList<>();
              for (DataMap dataMap : dataMaps) {
                  this.generators.add(new DbGenerator(
                          adapter,
                          dataMap,
                          tables.getExcludedTables(),
                          null,
                          NoopJdbcEventLogger.getInstance()));
              }
          } catch (Exception ex) {
              reportError("Error loading adapter", ex);
          }
      }
  
      /**
       * Returns SQL statements generated for selected schema generation options.
       */
      protected void createSQL() {
          // convert them to string representation for display
          StringBuilder buf = new StringBuilder();
          for (DbGenerator generator : generators) {
              Iterator<String> it = generator.configuredStatements().iterator();
              String batchTerminator = generator.getAdapter().getBatchTerminator();
  
              String lineEnd = (batchTerminator != null)
                      ? "\n" + batchTerminator + "\n\n"
                      : "\n\n";
  
              while (it.hasNext()) {
                  buf.append(it.next()).append(lineEnd);
              }
          }
  
          textForSQL = buf.toString();
      }
  
      protected void refreshView() {
          getView().setEnabled(connectionInfo != null);
  
          for (ObjectBinding optionBinding : optionBindings) {
              optionBinding.updateView();
          }
  
          sqlBinding.updateView();
      }
  
      // ===============
      // Actions
      // ===============
  
      /**
       * Starts options dialog.
       */
      public void startupAction() {
          view.pack();
          view.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
          view.setModal(true);
          makeCloseableOnEscape();
          centerView();
          view.setVisible(true);
      }
  
      public void refreshGeneratorAction() {
          prepareGenerator();
          refreshSQLAction();
      }
  
      /**
       * Updates a text area showing generated SQL.
       */
      public void refreshSQLAction() {
          // sync generator with defaults, make SQL, then sync the view...
          adapterBinding.updateView();
          connectionInfo.setDbAdapter((String) view.getAdapters().getSelectedItem());
          prepareGenerator();
          generatorDefaults.configureGenerator(generators);
          createSQL();
          sqlBinding.updateView();
      }
  
      /**
       * Performs configured schema operations via DbGenerator.
       */
      public void generateSchemaAction() {
  
          DataSourceWizard connectWizard = new DataSourceWizard((ProjectController) this.getParent(),
                  "Generate DB Schema: Connect to Database");
          if (!connectWizard.startupAction()) {
              return;
          }
  
          this.connectionInfo = connectWizard.getConnectionInfo();
  
          refreshGeneratorAction();
  
          Collection<ValidationResult> failures = new ArrayList<ValidationResult>();
  
          // sanity check...
          for (DbGenerator generator : generators) {
              if (generator.isEmpty(true)) {
                  JOptionPane.showMessageDialog(getView(), "Nothing to generate.");
                  return;
              }
  
              try {
                  generator.runGenerator(connectWizard.getDataSource());
                  failures.add(generator.getFailures());
              } catch (Throwable th) {
                  reportError("Schema Generation Error", th);
              }
          }
  
          if (failures.size() == 0) {
              JOptionPane.showMessageDialog(getView(), "Schema Generation Complete.");
          } else {
              new ValidationResultBrowser(this)
                      .startupAction(
                              "Schema Generation Complete",
                              "Schema generation finished. The following problem(s) were ignored.",
                              failures);
          }
      }
  
      /**
       * Allows user to save generated SQL in a file.
       */
      public void storeSQLAction() {
          JFileChooser fc = new JFileChooser();
          fc.setDialogType(JFileChooser.SAVE_DIALOG);
          fc.setDialogTitle("Save SQL Script");
  
          File projectDir = new File(getApplication()
                  .getProject()
                  .getConfigurationResource()
                  .getURL()
                  .getPath());
          fc.setCurrentDirectory(projectDir);
          if (fc.showSaveDialog(getView()) == JFileChooser.APPROVE_OPTION) {
              refreshGeneratorAction();
              try {
                  File file = fc.getSelectedFile();
                  FileWriter fw = new FileWriter(file);
                  PrintWriter pw = new PrintWriter(fw);
                  pw.print(textForSQL);
                  pw.flush();
                  pw.close();
              } catch (IOException ex) {
                  reportError("Error Saving SQL", ex);
              }
          }
      }
  
      public void closeAction() {
          view.dispose();
      }
  
      public DBConnectionInfo getConnectionInfo() {
          return this.connectionInfo;
      }
  
      public void setConnectionInfo(DBConnectionInfo connectionInfo) {
          this.connectionInfo = connectionInfo;
          refreshView();
      }
  }
  