/**
   * Normally called by the 'Yes' button listener of the virtual entry dialog. This method opens the
   * New Entry dialog in simple mode (or Change Classes dialog). If the user selects one or more
   * object classes they are added to the entry and displayed in the table editor.
   */
  public void processVirtualEntry() {

    ChangeObjectClassWin userData = null;
    if (dataSource.getSchemaOps() == null) {
      JOptionPane.showMessageDialog(
          owner,
          CBIntText.get(
              "Because there is no schema currently published by the\ndirectory, changing an entry's object class is unavailable."),
          CBIntText.get("No Schema"),
          JOptionPane.INFORMATION_MESSAGE);
      return;
    } else {
      shutVirtualEntryDialog(); // TE: kill the prompt window.
      userData =
          new ChangeObjectClassWin(dataSource, currentEntry.getDN(), null, this, owner, true);
      userData.setSize(400, 250);
      CBUtility.center(userData, owner); // TE: centres window.
      userData.setVisible(true);

      while (userData.isVisible()) // TE: don't do anything until the New Entry window is closed.
      {
        try {
          wait();
        } catch (Exception e) {
          userData.dispose();
        }
      }
    }

    if (userData.newObjectClasses
        != null) // TE: if the user has selected one or more object classes - add them to the entry
                 // in the directory.
    {
      try {
        DXOps dxOps = new DXOps(dataSource.getLdapContext());
        dxOps.addAttribute(currentEntry.getDN(), userData.newObjectClasses);
        dataSource.getEntry(
            currentEntry
                .getDN()); // TE: hack??  forces the entry to be read again - otherwise we don't
                           // display the naming value.
      } catch (NamingException e) {
        CBUtility.error(
            TableAttributeEditor.this,
            CBIntText.get(
                "Unable to add new object classes to {0}.",
                new String[] {currentEntry.getDN().toString()}),
            e);
      }
    }
  }
  /** Opens a dialog that displays the operational attributes of the current entry. */
  public void displayOperationalAttributes() {
    JXplorerBrowser jx = null;

    if (owner instanceof JXplorerBrowser) jx = (JXplorerBrowser) owner;
    else return;

    showingOperationalAttributes = !showingOperationalAttributes;

    // EJP 17 August 2010.
    // CB 14 August 2012 - some directories (looking at you Active Directory) don't support the '+'
    // operator... so do it manually as well...
    String[] opAttrs = {
      "+",
      "createTimeStamp",
      "creatorsName",
      "entryFlags",
      "federationBoundary",
      "localEntryID",
      "modifiersName",
      "modifyTimeStamp",
      "structuralObjectClass",
      "subordinateCount",
      "subschemaSubentry"
    };
    DXEntry entry = null;

    if (showingOperationalAttributes) {
      try {
        entry = (jx.getSearchBroker()).unthreadedReadEntry(currentDN, opAttrs);
        StringBuffer buffy = new StringBuffer("DN: " + currentDN.toString() + "\n\n");

        // Get the attribute values...
        // EJP 17 August 2010: use the actual attributes returned.
        NamingEnumeration ne = null;

        try {
          ne = entry.getAll();
          while (ne.hasMore()) {
            DXAttribute att = (DXAttribute) ne.next();
            buffy.append(att.getName() + ": " + att.get().toString() + "\n");

            tableData.insertOperationalAttribute(att);
          }
        } finally {
          if (ne != null) ne.close();
        }

        tableData.fireTableDataChanged();
      } catch (NamingException e) {
        CBUtility.error(
            TableAttributeEditor.this, CBIntText.get("Unable to read entry " + currentDN), e);
      }
    } else {
      tableData.removeOperationalAttributes();
      tableData.fireTableDataChanged();
    }
  }
  /** Kicks off the entry modify/update & checks for manditory attributes. */
  public void doSubmit() {
    if (dataSource == null) {
      CBUtility.error("No dataSource available to write changes to in Table Attribute Editor");
      return;
    }

    myEditor.stopCellEditing();

    // If schema checking is on, make sure that all mandatory attributes are filled in.
    if ("false".equalsIgnoreCase(JXConfig.getProperty("option.ignoreSchemaOnSubmission"))
        && (tableData.checkMandatoryAttributesSet() == false)) {
      CBUtility.error(
          TableAttributeEditor.this,
          CBIntText.get("All Mandatory Attributes must have values!"),
          null);
      return;
    }

    writeTableData();
  }
  /** Writes the data currently in the table editor to the directory. */
  public void writeTableData() {

    myEditor.stopCellEditing();

    if (dataSource == null) // if ds is null, data is not modifiable...
    {
      CBUtility.error("no datasource to write data to in writeTableData()");
      return;
    } // shouldn't happen

    DXEntry oldEntry = tableData.getOldEntry();

    DXEntry newEntry = tableData.getNewEntry();

    /*   Check to see if major surgery is needed - whether the user has been
     *   messing with the object class list. */

    if (classChangedOriginalEntry != null) {

      // use the saved state of the pre-class-changed entry as the 'old entry'
      // state.
      oldEntry = classChangedOriginalEntry;
      classChangedOriginalEntry =
          null; // this is only used once! (either the object class change will
      // now succeed, or fail - either way, the entry state is reset to
      // match what's in the directory.)

      if (objectClassesChanged(oldEntry, newEntry)) {
        oldEntry.removeEmptyAttributes();

        newEntry.setStatus(oldEntry.getStatus());

        Object[] delSet =
            CBArray.difference(oldEntry.toIDStringArray(), newEntry.toIDStringArray());

        /* if there *are* attributes that should no longer exist, delete them by adding them (blanked)
         * to the complete 'newAtts' set of *all* known attributes. */

        if ((delSet != null) && (delSet.length > 0)) {
          for (int i = 0; i < delSet.length; i++) {
            newEntry.put(
                new DXAttribute(
                    delSet[i].toString())); // overwrite old values with an empty attribute
          }
        }
      }
    }

    dataSource.modifyEntry(oldEntry, newEntry);
  }
  /**
   * Opens a dialog that asks the user if they want to make a virtual entry a non virtual entry. If
   * the user clicks 'Yes' the 'change class' dialog opens.
   */
  public void doVirtualEntryDisplay() {
    virtualEntryDialog = new JDialog(owner, CBIntText.get("Virtual Entry"), true);

    CBButton btnYes =
        new CBButton(CBIntText.get("Yes"), CBIntText.get("Click yes to make a Virtual Entry."));
    CBButton btnNo =
        new CBButton(
            CBIntText.get("No"),
            CBIntText.get("Click no to cancel without making a Virtual Entry."));

    // TE: layout stuff...
    Container pane = virtualEntryDialog.getContentPane();
    pane.setLayout(new BorderLayout());
    CBPanel panel1 = new CBPanel();
    CBPanel panel2 = new CBPanel();
    CBPanel panel3 = new CBPanel();

    panel1.add(
        new JLabel(
            CBIntText.get(
                "This entry is a Virtual Entry.  Are you sure you want to give this entry an object class?")));
    panel2.add(btnYes);
    panel2.add(btnNo);

    panel3.makeWide();
    panel3.addln(panel1);
    panel3.addln(panel2);

    pane.add(panel3);

    btnYes.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            processVirtualEntry();
          }
        });

    btnNo.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            shutVirtualEntryDialog();
          }
        });
    virtualEntryDialog.setSize(475, 125);
    CBUtility.center(virtualEntryDialog, owner);
    virtualEntryDialog.setVisible(true);
  }
  /** Opens the change class dialog. */
  public void changeClass() { // JPanel mainPanel
    /*
     *    MINOR MAGIC
     *
     *    This code reuses the 'new entry window'.  In order to make things
     *    sane, we prompt the user to save any serious changes before continuing.
     *    (Things can get really wierd if the user changes the name and then
     *    tries to change the objectclass - best to avoid the whole issue.)
     */
    myEditor.stopCellEditing();

    if (virtualEntry) {
      doVirtualEntryDisplay();
      return;
    }

    /*
     *    classChangedOriginalEntry saves the original state of the entry
     *    between visits to NewEntryWin.  (- I wonder if it would be neater
     *    to just reset the 'oldEntry' state of the table every time? ).
     *    Check it's not been set already (i.e. Pathological User is paying
     *    multiple visits to the NewEntryWin.)
     */
    if (classChangedOriginalEntry == null) classChangedOriginalEntry = tableData.getOldEntry();

    DXEntry newEntry = tableData.getNewEntry();
    DN newDN = newEntry.getDN();

    /*
     *    Pathalogical user has messed with the name, *and* wants to
     *    change the object classes...
     */
    if (newDN.equals(classChangedOriginalEntry.getDN()) == false) {
      checkForUnsavedChanges();
      /*
      if (promptForSave() == false)  // we may need to reset the 'newEntry' data
      {                                   // if the user discards their changes.

          tableData.reset();              // resets the table before going on.

          newEntry = tableData.getNewEntry();
          newDN = newEntry.getDN();
      }
      else // user has saved data - so now we need to reset the 'classChangedOriginalEntry'
      {
      */
      // to the changed (and hopefully saved!) data.
      // NB: If the directory write fails, then the change classes will also fail...
      classChangedOriginalEntry = tableData.getNewEntry();
    }

    /*
     *    Open NewEntryWin, allowing the user to reset the objectclass attribute.
     */

    /*
            NewEntryWin userData = new NewEntryWin(newDN.parentDN(), newDN,
                                    dataSource,
                                    newEntry.getAsNonNullAttributes(),
                                    newDN.getLowestRDN().toString(), TableAttributeEditor.this,
                                    CBUtility.getParentFrame(mainPanel));
    */
    if (dataSource.getSchemaOps() == null) {
      JOptionPane.showMessageDialog(
          owner,
          CBIntText.get(
              "Because there is no schema currently published by the\ndirectory, changing an entry's object class is unavailable."),
          CBIntText.get("No Schema"),
          JOptionPane.INFORMATION_MESSAGE);
      return;
    } else {
      ChangeObjectClassWin userData =
          new ChangeObjectClassWin(
              dataSource,
              newDN,
              newEntry.getAsNonNullAttributes(),
              this,
              CBUtility.getParentFrame(this),
              false);
      userData.setSize(400, 250);
      CBUtility.center(userData, owner); // TE: centres window.
      userData.setVisible(true);
    }
  }