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.editor;
  
  import javax.swing.BorderFactory;
  import javax.swing.DefaultCellEditor;
  import javax.swing.Icon;
  import javax.swing.ImageIcon;
  import javax.swing.JComboBox;
  import javax.swing.JComponent;
  import javax.swing.JLabel;
  import javax.swing.JMenuItem;
  import javax.swing.JPanel;
  import javax.swing.JPopupMenu;
  import javax.swing.JTable;
  import javax.swing.ListSelectionModel;
  import javax.swing.UIManager;
  import javax.swing.event.ListSelectionEvent;
  import javax.swing.event.ListSelectionListener;
  import javax.swing.table.DefaultTableCellRenderer;
  import javax.swing.table.TableCellRenderer;
  import javax.swing.table.TableColumn;
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Component;
  import java.awt.Point;
  import java.awt.Rectangle;
  import java.awt.event.ActionListener;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.util.List;
  
  import org.apache.cayenne.map.DataMap;
  import org.apache.cayenne.map.DeleteRule;
  import org.apache.cayenne.map.ObjEntity;
  import org.apache.cayenne.map.ObjRelationship;
  import org.apache.cayenne.map.event.EntityEvent;
  import org.apache.cayenne.map.event.ObjEntityListener;
  import org.apache.cayenne.map.event.ObjRelationshipListener;
  import org.apache.cayenne.map.event.RelationshipEvent;
  import org.apache.cayenne.modeler.Application;
  import org.apache.cayenne.modeler.ProjectController;
  import org.apache.cayenne.modeler.action.ActionManager;
  import org.apache.cayenne.modeler.action.CopyAttributeRelationshipAction;
  import org.apache.cayenne.modeler.action.CutAttributeRelationshipAction;
  import org.apache.cayenne.modeler.action.ObjEntityToSuperEntityAction;
  import org.apache.cayenne.modeler.action.PasteAction;
  import org.apache.cayenne.modeler.action.RemoveAttributeRelationshipAction;
  import org.apache.cayenne.modeler.dialog.objentity.ObjRelationshipInfo;
  import org.apache.cayenne.modeler.event.EntityDisplayEvent;
  import org.apache.cayenne.modeler.event.ObjEntityDisplayListener;
  import org.apache.cayenne.modeler.event.TablePopupHandler;
  import org.apache.cayenne.modeler.pref.TableColumnPreferences;
  import org.apache.cayenne.modeler.util.CayenneAction;
  import org.apache.cayenne.modeler.util.CayenneTable;
  import org.apache.cayenne.modeler.util.CellRenderers;
  import org.apache.cayenne.modeler.util.Comparators;
  import org.apache.cayenne.modeler.util.DbRelationshipPathComboBoxEditor;
  import org.apache.cayenne.modeler.util.ModelerUtil;
  import org.apache.cayenne.modeler.util.PanelFactory;
  import org.apache.cayenne.modeler.util.UIUtil;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * Displays ObjRelationships for the edited ObjEntity.
   */
  public class ObjEntityRelationshipPanel extends JPanel implements ObjEntityDisplayListener,
          ObjEntityListener, ObjRelationshipListener {
  
      private static Logger logObj = LoggerFactory.getLogger(ObjEntityRelationshipPanel.class);
  
      private static final ImageIcon INHERITANCE_ICON = ModelerUtil.buildIcon("icon-inheritance.png");
  
      private static final Object[] DELETE_RULES = new Object[]{
              DeleteRule.deleteRuleName(DeleteRule.NO_ACTION),
              DeleteRule.deleteRuleName(DeleteRule.NULLIFY),
              DeleteRule.deleteRuleName(DeleteRule.CASCADE),
              DeleteRule.deleteRuleName(DeleteRule.DENY),
      };
  
      private ProjectController mediator;
      private CayenneTable table;
      private TableColumnPreferences tablePreferences;
      private ActionListener resolver;
      private ObjEntityAttributeRelationshipTab parentPanel;
      private boolean enabledResolve;//for JBottom "resolve" in ObjEntityAttrRelationshipTab
  
      /**
       * By now popup menu item is made similar to toolbar button. (i.e. all functionality
       * is here) This should be probably refactored as Action.
       */
      private JMenuItem resolveMenu;
  
      public ObjEntityRelationshipPanel(ProjectController mediator, ObjEntityAttributeRelationshipTab parentPanel) {
          this.mediator = mediator;
          this.parentPanel = parentPanel;
  
          init();
          initController();
      }
  
      public CayenneTable getTable() {
          return table;
      }
  
      public void setTable(CayenneTable table) {
          this.table = table;
      }
  
      private void init() {
          this.setLayout(new BorderLayout());
  
          ActionManager actionManager = Application.getInstance().getActionManager();
  
          table = new CayenneTable();
          table.setDefaultRenderer(String.class, new StringRenderer());
          table.setDefaultRenderer(ObjEntity.class, new EntityRenderer());
          tablePreferences = new TableColumnPreferences(
                  ObjRelationshipTableModel.class,
                  "objEntity/relationshipTable");
  
          table.addMouseListener(new MouseAdapter() {
              @Override
              public void mouseReleased(MouseEvent e) {
                  int row = table.rowAtPoint(e.getPoint());
                  int col = table.columnAtPoint(e.getPoint());
                  ObjRelationshipTableModel tableModel = ((ObjRelationshipTableModel) table.getModel());
                  ObjRelationship relationship = tableModel.getRelationship(row);
                  int columnFromModel = table.getColumnModel().getColumn(col).getModelIndex();
                  if (row >= 0 && columnFromModel == ObjRelationshipTableModel.REL_NAME) {
                      if(relationship.getSourceEntity() != tableModel.getEntity()) {
                          TableCellRenderer renderer = table.getCellRenderer(row, col);
                          Rectangle rectangle = table.getCellRect(row, col, false);
                          ((StringRenderer) renderer).mouseClicked(e, rectangle.x);
                      }
                  }
              }
          });
  
          // Create and install a popup
          Icon ico = ModelerUtil.buildIcon("icon-edit.png");
          resolveMenu = new CayenneAction.CayenneMenuItem("Database Mapping", ico);
  
          JPopupMenu popup = new JPopupMenu();
          popup.add(resolveMenu);
          popup.add(actionManager.getAction(RemoveAttributeRelationshipAction.class).buildMenu());
  
          popup.addSeparator();
          popup.add(actionManager.getAction(CutAttributeRelationshipAction.class).buildMenu());
          popup.add(actionManager.getAction(CopyAttributeRelationshipAction.class).buildMenu());
          popup.add(actionManager.getAction(PasteAction.class).buildMenu());
  
          TablePopupHandler.install(table, popup);
          add(PanelFactory.createTablePanel(table, null), BorderLayout.CENTER);
      }
  
      private void initController() {
          mediator.addObjEntityDisplayListener(this);
          mediator.addObjEntityListener(this);
          mediator.addObjRelationshipListener(this);
  
          resolver = e -> {
              int row = table.getSelectedRow();
              if (row < 0) {
                  return;
              }
  
              ObjRelationshipTableModel model = (ObjRelationshipTableModel) table.getModel();
              new ObjRelationshipInfo(mediator)
                      .modifyRelationship(model.getRelationship(row))
                      .startupAction();
  
              // This is required for a table to be updated properly
              table.cancelEditing();
  
              // need to refresh selected row... do this by unselecting/selecting the row
              table.getSelectionModel().clearSelection();
              table.select(row);
              enabledResolve = false;
          };
          resolveMenu.addActionListener(resolver);
  
          table.getSelectionModel().addListSelectionListener(new ObjRelationshipListSelectionListener());
  
          mediator.getApplication().getActionManager().setupCutCopyPaste(
                  table,
                  CutAttributeRelationshipAction.class,
                  CopyAttributeRelationshipAction.class);
      }
  
      /**
       * Selects a specified relationship in the relationships table.
       */
      public void selectRelationships(ObjRelationship[] rels) {
          ObjRelationshipTableModel model = (ObjRelationshipTableModel) table.getModel();
  
          List listRels = model.getObjectList();
          int[] newSel = new int[rels.length];
  
          parentPanel.updateActions(rels);
  
          for (int i = 0; i < rels.length; i++) {
              newSel[i] = listRels.indexOf(rels[i]);
          }
  
          table.select(newSel);
          parentPanel.getResolve().removeActionListener(getResolver());
          parentPanel.getResolve().addActionListener(getResolver());
      }
  
      /**
       * Loads obj relationships into table.
       */
      public void currentObjEntityChanged(EntityDisplayEvent e) {
          if (e.getSource() == this) {
              return;
          }
  
          ObjEntity entity = (ObjEntity) e.getEntity();
  
          // Important: process event even if this is the same entity,
          // since the inheritance structure might have changed
          if (entity != null) {
              rebuildTable(entity);
          }
  
          // if an entity was selected on a tree,
          // unselect currently selected row
          if (e.isUnselectAttributes()) {
              table.clearSelection();
          }
  
          ObjEntity objEntity = (ObjEntity) e.getEntity();
          if (objEntity.getSuperEntity() != null) {
              parentPanel.getToolBar().getComponentAtIndex(2).setEnabled(false);
          } else {
              parentPanel.getToolBar().getComponentAtIndex(2).setEnabled(true);
          }
      }
  
      /**
       * Creates a list of ObjEntity names.
       */
      private Object[] createObjEntityComboModel() {
          DataMap map = mediator.getCurrentDataMap();
  
          // this actually happens per CAY-221... can't reproduce though
          if (map == null) {
              logObj.warn("createObjEntityComboModel:: Null DataMap.");
              return new Object[0];
          }
  
          if (map.getNamespace() == null) {
              logObj.warn("createObjEntityComboModel:: Null DataMap namespace - " + map);
              return new Object[0];
          }
  
          return map.getNamespace().getObjEntities().stream()
                  .sorted(Comparators.getDataMapChildrenComparator())
                  .toArray();
      }
  
      public void objEntityChanged(EntityEvent e) {
      }
  
      public void objEntityAdded(EntityEvent e) {
          reloadEntityList(e);
      }
  
      public void objEntityRemoved(EntityEvent e) {
          reloadEntityList(e);
      }
  
      public void objRelationshipChanged(RelationshipEvent e) {
          table.select(e.getRelationship());
      }
  
      public void objRelationshipAdded(RelationshipEvent e) {
          rebuildTable((ObjEntity) e.getEntity());
          table.select(e.getRelationship());
      }
  
      public void objRelationshipRemoved(RelationshipEvent e) {
          ObjRelationshipTableModel model = (ObjRelationshipTableModel) table.getModel();
          int ind = model.getObjectList().indexOf(e.getRelationship());
          model.removeRow((ObjRelationship) e.getRelationship());
          table.select(ind);
      }
  
      /**
       * Refresh the list of ObjEntity targets. Also refresh the table in case some
       * ObjRelationships were deleted.
       */
      private void reloadEntityList(EntityEvent e) {
          if (e.getSource() != this) {
              return;
          }
  
          // If current model added/removed, do nothing.
          ObjEntity entity = mediator.getCurrentObjEntity();
          if (entity == e.getEntity() || entity == null) {
              return;
          }
  
          TableColumn col = table.getColumnModel().getColumn(
                  ObjRelationshipTableModel.REL_TARGET);
          DefaultCellEditor editor = (DefaultCellEditor) col.getCellEditor();
  
          JComboBox combo = (JComboBox) editor.getComponent();
          combo.setRenderer(CellRenderers.entityListRendererWithIcons(entity.getDataMap()));
  
          ObjRelationshipTableModel model = (ObjRelationshipTableModel) table.getModel();
          model.fireTableDataChanged();
      }
  
      protected void rebuildTable(ObjEntity entity) {
          final ObjRelationshipTableModel model = new ObjRelationshipTableModel(
                  entity,
                  mediator,
                  this);
  
          model.addTableModelListener(e -> {
              if (table.getSelectedRow() >= 0) {
                  ObjRelationship rel = model.getRelationship(table.getSelectedRow());
                  enabledResolve = rel.getSourceEntity().getDbEntity() != null;
                  resolveMenu.setEnabled(enabledResolve);
              }
          });
  
          table.setModel(model);
          table.setRowHeight(25);
          table.setRowMargin(3);
  
          TableColumn col = table.getColumnModel().getColumn(ObjRelationshipTableModel.REL_TARGET_PATH);
          col.setCellEditor(new DbRelationshipPathComboBoxEditor());
          col.setCellRenderer(new DefaultTableCellRenderer(){
              @Override
              public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
                  super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
                  setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
                  setToolTipText("To choose relationship press enter two times.To choose next relationship press dot.");
                  return this;
              }
          });
  
          col = table.getColumnModel().getColumn(ObjRelationshipTableModel.REL_DELETE_RULE);
          JComboBox deleteRulesCombo = Application.getWidgetFactory().createComboBox(
                  DELETE_RULES,
                  false);
          deleteRulesCombo.setFocusable(false);
          deleteRulesCombo.setEditable(true);
          ((JComponent) deleteRulesCombo.getEditor().getEditorComponent()).setBorder(null);
          deleteRulesCombo.setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
          deleteRulesCombo.setSelectedIndex(0); // Default to the first value
          col.setCellEditor(Application.getWidgetFactory().createCellEditor(
                  deleteRulesCombo));
  
          tablePreferences.bind(
                  table,
                  null,
                  null,
                  null,
                  ObjRelationshipTableModel.REL_NAME,
                  true);
      }
  
      class EntityRenderer extends StringRenderer {
  
          @Override
          public Component getTableCellRendererComponent(
                  JTable table,
                  Object value,
                  boolean isSelected,
                  boolean hasFocus,
                  int row,
                  int column) {
  
              Object oldValue = value;
              value = CellRenderers.asString(value);
  
              super.getTableCellRendererComponent(
                      table,
                      value,
                      isSelected,
                      hasFocus,
                      row,
                      column);
  
              Icon icon = CellRenderers.iconForObject(oldValue);
              if(isSelected) {
                  setForeground(UIManager.getColor("Table.selectionForeground"));
              }
              setIcon(icon);
              return this;
          }
      }
  
      class StringRenderer extends DefaultTableCellRenderer {
  
          @Override
          public Component getTableCellRendererComponent(
                  JTable table,
                  Object value,
                  boolean isSelected,
                  boolean hasFocus,
                  int row,
                  int column) {
  
              // center cardinality column
              int align = column == ObjRelationshipTableModel.REL_SEMANTICS
                      ? JLabel.CENTER
                      : JLabel.LEFT;
              super.setHorizontalAlignment(align);
  
              super.getTableCellRendererComponent(
                      table,
                      value,
                      isSelected,
                      hasFocus,
                      row,
                      column);
  
              ObjRelationshipTableModel model = (ObjRelationshipTableModel) table
                      .getModel();
              ObjRelationship relationship = model.getRelationship(row);
  
              setIcon(null);
  
              column = table.getColumnModel().getColumn(column).getModelIndex();
              if (relationship != null
                      && relationship.getSourceEntity() != model.getEntity()) {
                  setForeground(isSelected ? new Color(0xEEEEEE) : Color.GRAY);
                  if(column == ObjRelationshipTableModel.REL_NAME) {
                      setIcon(INHERITANCE_ICON);
                  }
              } else {
                  setForeground(isSelected && !hasFocus
                          ? table.getSelectionForeground()
                          : table.getForeground());
              }
  
              setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
              setFont(UIManager.getFont("Label.font"));
  
              return this;
          }
  
          public void mouseClicked(MouseEvent event, int x) {
              Point point = event.getPoint();
              if(point.x - x <= INHERITANCE_ICON.getIconWidth()) {
                  ActionManager actionManager = Application.getInstance().getActionManager();
                  actionManager.getAction(ObjEntityToSuperEntityAction.class).performAction(null);
              }
          }
      }
  
      private class ObjRelationshipListSelectionListener implements ListSelectionListener {
  
          public void valueChanged(ListSelectionEvent e) {
              ObjRelationship[] rels = new ObjRelationship[0];
  
              if (!e.getValueIsAdjusting() && !((ListSelectionModel) e.getSource()).isSelectionEmpty()) {
  
                  parentPanel.getAttributePanel().getTable().getSelectionModel().clearSelection();
                  if (parentPanel.getAttributePanel().getTable().getCellEditor() != null) {
                      parentPanel.getAttributePanel().getTable().getCellEditor().stopCellEditing();
                  }
                  Application.getInstance().getActionManager().getAction(RemoveAttributeRelationshipAction.class).setCurrentSelectedPanel(parentPanel.getRelationshipPanel());
                  Application.getInstance().getActionManager().getAction(CutAttributeRelationshipAction.class).setCurrentSelectedPanel(parentPanel.getRelationshipPanel());
                  Application.getInstance().getActionManager().getAction(CopyAttributeRelationshipAction.class).setCurrentSelectedPanel(parentPanel.getRelationshipPanel());
                  parentPanel.getResolve().removeActionListener(parentPanel.getAttributePanel().getResolver());
                  parentPanel.getResolve().removeActionListener(getResolver());
                  parentPanel.getResolve().addActionListener(getResolver());
                  parentPanel.getResolve().setToolTipText("Edit Relationship");
                  parentPanel.getResolve().setEnabled(true);
  
                  if (table.getSelectedRow() >= 0) {
                      ObjRelationshipTableModel model = (ObjRelationshipTableModel) table.getModel();
  
                      int[] sel = table.getSelectedRows();
                      rels = new ObjRelationship[sel.length];
  
                      for (int i = 0; i < sel.length; i++) {
                          rels[i] = model.getRelationship(sel[i]);
                      }
  
                      if (sel.length == 1) {
                          UIUtil.scrollToSelectedRow(table);
                      }
  
                      enabledResolve = true;
                  } else {
                      enabledResolve = false;
                  }
                  resolveMenu.setEnabled(enabledResolve);
              }
  
              mediator.setCurrentObjRelationships(rels);
              parentPanel.updateActions(rels);
          }
      }
  
      public boolean isEnabledResolve() {
          return enabledResolve;
      }
  
      public ActionListener getResolver() {
          return resolver;
      }
  
  }