/**
 * @author [email protected]
 * @since Jun 4, 2003
 */
public abstract class AbstractViperTable extends JPanel implements ViperTableTabComponent {
  private Logger logger = Logger.getLogger("edu.umd.cfar.lamp.viper.gui.table");
  private ViperViewMediator mediator;

  public abstract Descriptor getSelectedRow();

  protected JPopupMenu popup;
  private AttributeRenderer ar;
  private AttributeEditor ae;
  private TablePanel outerTablePanel;

  /**
   * Get the model of the currently selected table (since a vipertable may have more than one table
   * model, like the content pane).
   *
   * @return the table model that has the user focus
   */
  public ViperTableModel getCurrentModel() {
    TableModel mod = getTable() == null ? null : getTable().getModel();
    if (mod instanceof ViperTableModel) {
      return (ViperTableModel) getTable().getModel();
    } else {
      return null;
    }
  }

  public void setCurrentModel(ViperTableModel model) {
    getTable().setModel(model);
  }

  private ChangeListener hiddenNodesChangeListener =
      new ChangeListener() {
        public void stateChanged(ChangeEvent e) {
          AbstractViperTable.this.getTable().getTableHeader().repaint();
        }
      };

  private class ProxyTableCellRenderer implements TableCellRenderer {
    private TableCellRenderer candidate;

    public ProxyTableCellRenderer(TableCellRenderer delegate) {
      this.candidate = delegate;
    }

    /** @inheritDoc */
    public boolean equals(Object arg0) {
      return candidate.equals(arg0);
    }
    /** @inheritDoc */
    public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
      Component c =
          candidate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
      if (c instanceof JLabel && table != null) {
        ViperTableModel m = getCurrentModel();
        int modelIndex = table.convertColumnIndexToModel(column);
        AttrConfig ac = m.getAttributeForColumn(modelIndex);
        JLabel l = (JLabel) c;
        if (ac != null) {
          int visibility = mediator.getHiders().getAttrConfigVisibility(ac);
          l.setIcon(outerTablePanel.visibilityIcons[visibility]);
        } else if (m.getInternalColumn(modelIndex) == ViperTableModel.BY_VALID) {
          Config config = m.getConfig();
          int visibility = mediator.getHiders().getConfigVisibility(m.getConfig());
          if (visibility == NodeVisibilityManager.RANGE_LOCKED) {
            visibility = NodeVisibilityManager.LOCKED;
          }
          l.setIcon(outerTablePanel.visibilityIcons[visibility]);
        } else {
          l.setIcon(null);
        }
      }
      return c;
    }
    /** @inheritDoc */
    public int hashCode() {
      return candidate.hashCode();
    }
    /** @inheritDoc */
    public String toString() {
      return candidate.toString();
    }
  }

  /**
   * Adds the default renderers and editors for all known data types
   *
   * @param table
   */
  private void initAttributeTable(final EnhancedTable table) {
    ar = new AttributeRenderer(this);
    ae = new AttributeEditor(this);
    ae.setEditClickCount(2);
    TableCellRenderer r = table.getTableHeader().getDefaultRenderer();
    table.getTableHeader().setDefaultRenderer(new ProxyTableCellRenderer(r));
    table.setDefaultRenderer(Descriptor.class, ar);
    table.setDefaultRenderer(Attribute.class, ar);
    table.setDefaultEditor(Attribute.class, ae);
    table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    table.addTableListener(
        new TableListener() {
          public void contextClick(TableEvent e) {
            // TODO: Should display context menu offering: sort ascending/descending; show/hide/lock
          }

          public void actionClick(TableEvent e) {}

          public void click(TableEvent e) {
            if (e.getRow() == -1) {
              ViperTableModel m = getCurrentModel();
              int modelIndex = table.convertColumnIndexToModel(e.getColumn());
              AttrConfig ac = m.getAttributeForColumn(modelIndex);
              NodeVisibilityManager H = mediator.getHiders();
              if (ac != null) {
                int oldV = H.getAttrConfigVisibility(ac);
                H.setVisibilityByAttrConfig(ac, NodeVisibilityManager.ROTATE_VISIBILITY[oldV]);
              } else if (m.getInternalColumn(modelIndex) == ViperTableModel.BY_VALID) {
                Config config = m.getConfig();
                int oldV = H.getConfigVisibility(config);
                H.setVisibilityByConfig(
                    config, NodeVisibilityManager.ROTATE_RANGE_VISIBILITY[oldV]);
              }
            }
          }

          public void altClick(TableEvent e) {}
        });
  }

  private int rowEditPolicy = ALLOW_ROW_EDIT;

  public int getRowEditPolicy() {
    return rowEditPolicy;
  }

  public void setRowEditPolicy(int policy) {
    rowEditPolicy = policy;
  }

  public static int NO_ROW_EDIT = 0;
  public static int ALLOW_ROW_EDIT = 1;
  // added by Ping on 10/31/2000
  // for toggle through objects
  public static boolean ENABLE = true;
  public static boolean DISABLE = false;
  // Handle some of the common steps between creating the content
  // and object tables.
  public AbstractViperTable(TablePanel tp) {
    super();
    this.outerTablePanel = tp;
    setLayout(new BorderLayout());
    EnhancedTable table =
        new EnhancedTable() {
          public void changeSelection(
              int rowIndex, int columnIndex, boolean toggle, boolean extend) {
            ViperTableModel currModel = AbstractViperTable.this.getCurrentModel();
            columnIndex = convertColumnIndexToModel(columnIndex);
            AttrConfig ac = currModel.getAttributeForColumn(columnIndex);
            Descriptor d = currModel.getDescriptorAtRow(rowIndex);
            Node n = null;
            if (ac != null) {
              Attribute a = d.getAttribute(ac);
              n = a;
            } else if (currModel.getInternalColumn(columnIndex) == ViperTableModel.BY_ID) {
              n = d;
            }
            if (n != null) {
              if (extend) {
                mediator.getSelection().addNode(n);
              } else {
                mediator.getSelection().setTo(n);
              }
            }
          }

          public boolean isCellSelected(int row, int column) {
            ViperTableModel currModel = AbstractViperTable.this.getCurrentModel();
            column = convertColumnIndexToModel(column);
            AttrConfig ac = currModel.getAttributeForColumn(column);
            Descriptor d = currModel.getDescriptorAtRow(row);
            if (ac != null) {
              Attribute a = d.getAttribute(ac);
              return mediator.getSelection().isSelected(a);
            } else if (currModel.getInternalColumn(column) == ViperTableModel.BY_ID) {
              return mediator.getSelection().isSelected(d);
            }
            return false;
          }
        };
    table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    table.resizeAllColumnsToNaturalWidth();
    table.setCellSelectionBackground(
        table.getSelectionBackground().brighter().brighter().brighter());
    table.setCellSelectionForeground(table.getForeground().darker());
    initAttributeTable(table);
    JScrollPane scrollPane = new JScrollPane(table);
    this.add(scrollPane);
    popup = new DescPropPopup();
    popup.setInvoker(getTable());
    getTable()
        .addMouseListener(
            new MouseAdapter() {
              public void mousePressed(MouseEvent e) {
                maybeShowPopup(e);
              }

              public void mouseReleased(MouseEvent e) {
                maybeShowPopup(e);
              }
            });
  }

  protected abstract void maybeShowPopup(MouseEvent e);

  protected EnhancedTable getTable() {
    JScrollPane scrollPane = (JScrollPane) this.getComponent(0);
    return (EnhancedTable) scrollPane.getViewport().getView();
  }

  public ViperViewMediator getMediator() {
    return mediator;
  }

  public void setMediator(ViperViewMediator mediator) {
    if (this.mediator != mediator) {
      if (this.mediator != null) {
        this.mediator.getHiders().removeChangeListener(hiddenNodesChangeListener);
      }
      this.mediator = mediator;
      if (this.mediator != null) {
        this.mediator.getHiders().addChangeListener(hiddenNodesChangeListener);
      }
    }
  }

  public void scrollToAttribute(Attribute a) {
    if (!a.getDescriptor().getConfig().equals(getConfig())) {
      logger.fine("Cannot scroll to attribute that isn't attached to this type of descriptor");
      return;
    }
    int rowIndex = getCurrentModel().getRowForDescriptor(a.getDescriptor());
    int colIndex = getCurrentModel().getColumnForAttribute(a);
    JScrollPane scrollPane = (JScrollPane) this.getComponent(0);
    JViewport viewport = (JViewport) scrollPane.getViewport();
    EnhancedTable table = (EnhancedTable) viewport.getView();

    // This rectangle is relative to the table where the
    // northwest corner of cell (0,0) is always (0,0).
    Rectangle rect = table.getCellRect(rowIndex, colIndex, true);

    // The location of the viewport relative to the table
    Point pt = viewport.getViewPosition();

    // Translate the cell location so that it is relative
    // to the view, assuming the northwest corner of the
    // view is (0,0)
    rect.setLocation(rect.x - pt.x, rect.y - pt.y);

    // Scroll the area into view
    viewport.scrollRectToVisible(rect);
  }

  // XXX Move to TablePanel - here there is one copy for each descriptor
  // config
  private class DescPropPopup extends JPopupMenu {
    private JCheckBoxMenuItem v;
    private JCheckBoxMenuItem p;
    private JMenuItem delete;
    private JMenuItem duplicate;
    private JMenuItem interp;
    private JCheckBoxMenuItem wrt;
    private JMenu interpToMark;
    private JMenuItem shift;
    private JMenu shiftToMark;
    private ShiftToMarkAction stmAction;
    private InterpToMarkAction itmAction;
    private Descriptor desc;
    private Attribute attr;

    private class WithRespectToAction implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        if (attr == null) {
          return;
        }
        ViperViewMediator m = getMediator();
        Attribute oldWRT = m.getDisplayWRTManager().getAttribute();
        if (attr.equals(oldWRT)) {
          m.getDisplayWRTManager().setAttribute(null, null);
        } else {
          m.getDisplayWRTManager().setAttribute(attr, m.getCurrentFrame());
        }
      }
    }

    private class ValidAction implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        boolean makeValid = v.isSelected();
        InstantRange oldRange = (InstantRange) desc.getValidRange().clone();
        boolean frame = oldRange.isFrameBased();
        InstantInterval toAlter = getMediator().getCurrInterval(frame);
        if (!makeValid) {
          oldRange.remove(toAlter);
        } else {
          oldRange.add(toAlter);
        }
        desc.setValidRange(oldRange);
        v.setSelected(!makeValid);
      }
    }

    private class PropAction implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        boolean propagate = p.isSelected();
        ViperViewMediator m = getMediator();
        PropagateInterpolateModule proper = m.getPropagator();
        if (propagate) {
          proper.startPropagating(desc);
        } else {
          proper.stopPropagating(desc);
        }
        p.setSelected(proper.getPropagatingDescriptors().contains(desc));
      }
    }

    private class DeleteAction implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        desc.getParent().removeChild(desc);
      }
    }

    private class DuplicateAction implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        getMediator().duplicateDescriptor(desc);
      }
    }

    private class InterpAction implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        Iterator toInterp = Collections.singleton(desc).iterator();
        ViperViewMediator m = getMediator();
        InterpQuery iq = new InterpQuery(toInterp, m);
        iq.setVisible(true);
      }
    }

    private class ShiftAction implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        ViperViewMediator m = getMediator();
        ShiftQuery sq = new ShiftQuery(new Descriptor[] {desc}, m);
        sq.setVisible(true);
      }
    }

    private class InterpToMarkAction implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        Iterator toInterp = Collections.singleton(desc).iterator();
        JMenuItem jmi = (JMenuItem) e.getSource();
        Iterator marks = mediator.getMarkerModel().getMarkersWithLabel(jmi.getText());
        if (marks.hasNext()) {
          ChronicleMarker marker = (ChronicleMarker) marks.next();
          Instant to = marker.getWhen();
          Instant from = mediator.getMajorMoment();
          mediator.getPropagator().interpolateDescriptors(toInterp, from, to);
        }
      }
    }

    private class ShiftToMarkAction implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        JMenuItem jmi = (JMenuItem) e.getSource();
        Iterator marks = mediator.getMarkerModel().getMarkersWithLabel(jmi.getText());
        if (marks.hasNext()) {
          ChronicleMarker marker = (ChronicleMarker) marks.next();
          Instant to = marker.getWhen();
          Instant from = mediator.getMajorMoment();
          viper.api.impl.Util.shiftDescriptors(new Descriptor[] {desc}, from, to);
        }
      }
    }

    private JMenuItem occlusions;
    private TextlineOcclusionEditor occWindow = new TextlineOcclusionEditor();

    private class OccAction implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        ViperViewMediator med = getMediator();
        TextlineModel tlm = (TextlineModel) med.getAttributeValueAtCurrentFrame(attr);
        if (tlm != null) {
          occWindow.setVisible(true);
          occWindow.setModelAndRefresh(tlm, med, attr);
        }
      }
    }

    private OccAction occAction;
    private JSeparator occSeparator;

    public DescPropPopup() {
      super("Descriptor Properties");
      v = new JCheckBoxMenuItem("Valid");
      v.addActionListener(new ValidAction());
      p = new JCheckBoxMenuItem("Propagating");
      p.addActionListener(new PropAction());
      delete = new JMenuItem("Delete");
      delete.addActionListener(new DeleteAction());
      duplicate = new JMenuItem("Duplicate");
      duplicate.addActionListener(new DuplicateAction());
      interp = new JMenuItem("Interpolate...");
      interp.addActionListener(new InterpAction());
      interpToMark = new JMenu("Interpolate to Mark");
      interpToMark.setEnabled(false);
      itmAction = new InterpToMarkAction();
      shift = new JMenuItem("Shift...");
      shift.addActionListener(new ShiftAction());
      shiftToMark = new JMenu("Shift to Mark");
      shiftToMark.setEnabled(false);
      stmAction = new ShiftToMarkAction();

      occlusions = new JMenuItem("Occlusions...");
      occAction = new OccAction();
      occlusions.addActionListener(occAction);
      occSeparator = new JSeparator();

      wrt = new JCheckBoxMenuItem("Display with Respect To", false);
      wrt.addActionListener(new WithRespectToAction());

      add(occlusions);
      add(occSeparator);
      add(v);
      add(p);
      add(occSeparator);
      add(delete);
      add(duplicate);
      add(occSeparator);
      add(interp);
      add(interpToMark);
      add(occSeparator);
      add(shift);
      add(shiftToMark);
      add(occSeparator);
      add(wrt);
    }

    public void show(Component invoker, int x, int y) {
      ViperViewMediator mediator = getMediator();
      Point pnt = new Point(x, y);
      EnhancedTable tab = getTable();
      int row = tab.rowAtPoint(pnt);
      desc = getCurrentModel().getDescriptorAtRow(row);
      int col = tab.columnAtPoint(pnt);
      Object cellValue = tab.getValueAt(row, col);
      if (cellValue instanceof Attribute) {
        attr = (Attribute) cellValue;

        // hide the "Occlusions..." option when we're not dealing with a Textline object
        boolean isTextline = attr.getAttrConfig().getAttrType().endsWith("textline");
        occlusions.setVisible(isTextline);
        occSeparator.setVisible(isTextline);

        Instant now = mediator.getCurrentFrame();
        if (now == null) {
          mediator.getDisplayWRTManager().setAttribute(null, null);
          wrt.setEnabled(false);
          wrt.setSelected(false);
        } else {
          boolean isDwrt = attr == mediator.getDisplayWRTManager().getAttribute();
          boolean dwrtable =
              (attr.getAttrValueAtInstant(now) instanceof HasCentroid
                  && attr.getDescriptor().getValidRange().contains(now));
          wrt.setEnabled(dwrtable);
          wrt.setSelected(isDwrt);
        }
      } else {
        attr = null;
        wrt.setEnabled(false);
        wrt.setSelected(false);
      }
      if (null != desc) {
        PropagateInterpolateModule proper = getMediator().getPropagator();
        p.setSelected(proper.isPropagatingThis(desc));
        v.setSelected(mediator.isThisValidNow(desc));
        resetMarks();
        super.show(invoker, x, y);
      }
    }

    private void resetMarks() {
      interpToMark.removeAll();
      shiftToMark.removeAll();
      Iterator marks = mediator.getMarkerModel().getLabels().iterator();
      boolean hasMark = false;
      while (marks.hasNext()) {
        String mark = (String) marks.next();
        if (!ChronicleViewer.CURR_FRAME_LABEL.equals(mark)) {
          JMenuItem mi = new JMenuItem(mark);
          mi.addActionListener(itmAction);
          interpToMark.add(mi);
          mi = new JMenuItem(mark);
          mi.addActionListener(stmAction);
          shiftToMark.add(mi);
          hasMark = true;
        }
      }
      shiftToMark.setEnabled(hasMark);
      interpToMark.setEnabled(hasMark);
    }
  }

  public abstract void redoSelectionModel();

  public abstract void redoDataModel();

  public abstract Config getConfig();

  public void redoPropagateModel() {
    ViperTableModel m = (ViperTableModel) AbstractViperTable.this.getTable().getModel();
    m.fireTableDataChanged();
  }
}
/**
 * A property sheet, allowing the user to edit different properties. The default implementation uses
 * the javabean standard naming conventions to extract property names and types. The user/system can
 * override this with the application preferences.
 */
public class PropertySheet extends JScrollPane {
  private DescriberBasedProperties props;
  private Logger logger = Logger.getLogger("edu.umd.cfar.lamp.apploader.propertysheets");

  /**
   * Gets the data model associated with the property sheet.
   *
   * @return the table model
   */
  private MyTableModel getTableModel() {
    return (MyTableModel) getTable().getModel();
  }

  /**
   * Gets the table contained within the property sheet. I'm not sure that this is the best way to
   * present properties, and may stop using tables in the future.
   *
   * @return the table
   */
  public EnhancedTable getTable() {
    return (EnhancedTable) getViewport().getView();
  }

  private class MyTableModel extends AbstractTableModel {
    /** @return 2 */
    public int getColumnCount() {
      return 2;
    }
    /** @return the number of properties */
    public int getRowCount() {
      return props.size();
    }
    /**
     * Gets the property descriptor for the row.
     *
     * @param r the row number (0-indexed)
     * @return the corresponding property
     */
    public InstancePropertyDescriptor getRow(int r) {
      return (InstancePropertyDescriptor) props.get(r);
    }

    /**
     * Determines if the value at a cell is editable
     *
     * @param rowIndex the property
     * @param columnIndex whether the property name (column zero) or property value (column one)
     * @return true, if the corresponding property is settable and the column index is 1
     */
    public boolean isCellEditable(int rowIndex, int columnIndex) {
      InstancePropertyDescriptor row = getRow(rowIndex);
      if (columnIndex == 0) {
        return false;
      } else if (columnIndex == 1) {
        return row.isSettable(props.getObject());
      } else {
        throw new IndexOutOfBoundsException("Not a valid cell: " + rowIndex + "x" + columnIndex);
      }
    }

    /**
     * Gets the appropriate property name or property value.
     *
     * @param rowIndex the row corresponding to a property
     * @param columnIndex either the name column (zero) or value column (one)
     * @return the name or value of the appropriate property
     */
    public Object getValueAt(int rowIndex, int columnIndex) {
      InstancePropertyDescriptor row = getRow(rowIndex);
      if (columnIndex == 0) {
        return row.getName();
      } else if (columnIndex == 1) {
        return row;
      } else {
        throw new IndexOutOfBoundsException("Not a valid cell: " + rowIndex + "x" + columnIndex);
      }
    }

    /**
     * Either "property" or "value". XXX: this should be localizable.
     *
     * @param columnIndex either the name column (zero) or value column (one)
     * @return "property" or "value"
     */
    public String getColumnName(int columnIndex) {
      if (columnIndex == 0) {
        return "Property";
      } else if (columnIndex == 1) {
        return "Value";
      } else {
        throw new IndexOutOfBoundsException("Not a valid column: " + columnIndex);
      }
    }

    /**
     * Class corresponding to the column
     *
     * @param columnIndex either the name column (zero) or value column (one)
     * @return <code>String.class</code> or <code>{@link InstancePropertyDescriptor}.class</code>
     */
    public Class getColumnClass(int columnIndex) {
      if (columnIndex == 0) {
        return String.class;
      } else if (columnIndex == 1) {
        return InstancePropertyDescriptor.class;
      } else {
        throw new IndexOutOfBoundsException("Not a valid column: " + columnIndex);
      }
    }

    /**
     * Sets the appropriate property value, if possible. Otherwise, this silently fails.
     *
     * @param value the new value for the appropriate property
     * @param rowIndex the row corresponding to a property
     * @param columnIndex only the value column (column one) works
     */
    public void setValueAt(Object value, int rowIndex, int columnIndex) {
      if (columnIndex == 1) {
        InstancePropertyDescriptor row = getRow(rowIndex);
        if (row.isSettable(props.getObject())) {
          row.applySetter(props.getObject(), value);
        }
      }
    }

    /**
     * Gets the primary property table corresponding to this model.
     *
     * @return the table
     */
    public EnhancedTable getTable() {
      return (EnhancedTable) getViewport().getView();
    }
  }

  /** Create a new, empty property sheet. */
  public PropertySheet() {
    super(new EnhancedTable());
    this.getPreferredSize();
    props = new ForClassPropertyList();

    // Add bean change event listener
    props.addChangeListener(
        new ChangeListener() {
          public void stateChanged(ChangeEvent e) {
            // XXX Should I fire a 'stop editing' event here?
            refresh();
          }
        });

    // Add table
    JTable table = getTable();
    MyTableModel tableModel = new MyTableModel();
    table.setModel(tableModel);
    table.setDefaultRenderer(InstancePropertyDescriptor.class, new MyCellRenderer());
    table.setDefaultEditor(InstancePropertyDescriptor.class, new MyCellEditor());
  }

  private class MyCellRenderer implements TableCellRenderer {
    private JComponent lastEditor;
    /**
     * @see javax.swing.table.TableCellRenderer#getTableCellRendererComponent(javax.swing.JTable,
     *     java.lang.Object, boolean, boolean, int, int)
     */
    public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
      InstancePropertyDescriptor v = (InstancePropertyDescriptor) value;
      try {
        lastEditor = v.getRenderer(props.getObject(), props.getPrefs().getCore());
        return lastEditor;
      } catch (PreferenceException e) {
        e.printStackTrace();
        return null;
      }
    }
  }

  private class MyCellEditor extends AbstractCellEditor implements TableCellEditor {
    private CellEditor lastEditor;
    /** @see javax.swing.CellEditor#getCellEditorValue() */
    public Object getCellEditorValue() {
      if (lastEditor != null) {
        return lastEditor.getCellEditorValue();
      }
      return null;
    }

    /**
     * @see javax.swing.table.TableCellEditor#getTableCellEditorComponent(javax.swing.JTable,
     *     java.lang.Object, boolean, int, int)
     */
    public Component getTableCellEditorComponent(
        JTable table, Object value, boolean isSelected, int row, int column) {
      InstancePropertyDescriptor v = (InstancePropertyDescriptor) value;
      JComponent c;
      try {
        c = v.getEditor(props.getObject(), props.getPrefs().getCore());
        lastEditor = (CellEditor) c;
        return c;
      } catch (PreferenceException e) {
        e.printStackTrace();
        return null;
      }
    }
  }

  /**
   * Gets the preference manager associated with this property sheet.
   *
   * @return the preference manager
   */
  public PrefsManager getPrefs() {
    return props.getPrefs();
  }

  /**
   * Sets the preference manager associated with this property sheet. This is necessary for
   * localization and determining extended properties.
   *
   * @param manager the preference manager
   */
  public void setPrefs(PrefsManager manager) {
    props.setPrefs(manager);
  }

  /** Sort the descriptors by their display name using the current lexicographical ordering. */
  private static class PropertySorter implements Comparator {
    /**
     * Sort the descriptors by their display name using the current lexicographical ordering
     *
     * @param a a property descriptor
     * @param b the other ObjectPropertyDescriptor
     * @return <code>getName().compareto(b.getName())</code>
     */
    public int compare(Object a, Object b) {
      InstancePropertyDescriptor A = (InstancePropertyDescriptor) a;
      InstancePropertyDescriptor B = (InstancePropertyDescriptor) b;

      return A.getName().compareTo(B.getName());
    }
  }

  static final PropertySorter SORT_BY_PROPERTY_NAME = new PropertySorter();

  /**
   * Set the subject bean to check for properties.
   *
   * @param o the subject bean
   */
  public void setObject(Object o) {
    if (o != props.getObject()) {
      if (getTable().isEditing()) {
        getTable().editingStopped(new ChangeEvent(this));
      }
      getTable().editingCanceled(new ChangeEvent(this));
      props.setObject(o);
      getTableModel().fireTableStructureChanged();
      getTable().setRowHeightToMaximumPreferredHeight();
    }
  }

  /**
   * Gets the bound subject bean.
   *
   * @return the subject
   */
  public Object getObject() {
    return props.getObject();
  }

  /** Refresh the properties from the bound object. */
  public void refresh() {
    props.refresh();
    getTableModel().fireTableDataChanged();
  }

  /** @return */
  public Logger getLogger() {
    return logger;
  }

  /** @param logger */
  public void setLogger(Logger logger) {
    this.logger = logger;
  }
}