add auto suggestion DbAttributePath to table. reason : reduse DatabaseMapping dialog
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/681a1fac Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/681a1fac Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/681a1fac Branch: refs/heads/master Commit: 681a1fac58559ef9f315da02903c4451d227cffd Parents: 7bf68c6 Author: AlexandrShestak <shestakalexa...@mail.ru> Authored: Tue Nov 24 12:26:36 2015 +0300 Committer: Savva Kolbachev <s.kolbac...@gmail.com> Committed: Mon Dec 7 13:21:41 2015 +0300 ---------------------------------------------------------------------- .../modeler/editor/ObjAttributeTableModel.java | 11 +- .../modeler/editor/ObjEntityAttributePanel.java | 378 ++++++++++++++++++- 2 files changed, 369 insertions(+), 20 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cayenne/blob/681a1fac/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjAttributeTableModel.java ---------------------------------------------------------------------- diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjAttributeTableModel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjAttributeTableModel.java index cfd4fdb..0cee45a 100644 --- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjAttributeTableModel.java +++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjAttributeTableModel.java @@ -164,7 +164,7 @@ public class ObjAttributeTableModel extends CayenneTableModel<ObjAttributeWrappe case OBJ_ATTRIBUTE_TYPE: return "Java Type"; case DB_ATTRIBUTE: - return "DbAttribute"; + return "DbAttributePath"; case DB_ATTRIBUTE_TYPE: return "DB Type"; case LOCKING: @@ -281,9 +281,9 @@ public class ObjAttributeTableModel extends CayenneTableModel<ObjAttributeWrappe return true; } - + + @Override public void setUpdatedValueAt(Object value, int row, int column) { - ObjAttributeWrapper attribute = getAttribute(row); attribute.resetEdits(); AttributeEvent event = new AttributeEvent(eventSource, attribute.getValue(), entity); @@ -438,11 +438,6 @@ public class ObjAttributeTableModel extends CayenneTableModel<ObjAttributeWrappe else if (attribute.getDbAttribute() != null) { attribute.setDbAttributePath(null); } - - // If path is flattened attribute, update the JComboBox - if (path != null && path.split("\\.").length > 1 && dbEntity != null) { - setComboBoxes(nameAttr, column); - } } fireTableRowsUpdated(row, row); } http://git-wip-us.apache.org/repos/asf/cayenne/blob/681a1fac/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjEntityAttributePanel.java ---------------------------------------------------------------------- diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjEntityAttributePanel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjEntityAttributePanel.java index fa980e6..d3496e8 100644 --- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjEntityAttributePanel.java +++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjEntityAttributePanel.java @@ -19,10 +19,16 @@ package org.apache.cayenne.modeler.editor; import org.apache.cayenne.configuration.DataChannelDescriptor; +import org.apache.cayenne.map.Attribute; import org.apache.cayenne.map.DataMap; +import org.apache.cayenne.map.DbAttribute; +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.DbRelationship; import org.apache.cayenne.map.Embeddable; +import org.apache.cayenne.map.Entity; import org.apache.cayenne.map.ObjAttribute; import org.apache.cayenne.map.ObjEntity; +import org.apache.cayenne.map.Relationship; import org.apache.cayenne.map.event.AttributeEvent; import org.apache.cayenne.map.event.EntityEvent; import org.apache.cayenne.map.event.ObjAttributeListener; @@ -44,36 +50,50 @@ import org.apache.cayenne.modeler.event.TablePopupHandler; import org.apache.cayenne.modeler.pref.TableColumnPreferences; import org.apache.cayenne.modeler.util.CayenneTable; import org.apache.cayenne.modeler.util.CayenneTableModel; +import org.apache.cayenne.modeler.util.EntityTreeFilter; +import org.apache.cayenne.modeler.util.EntityTreeModel; import org.apache.cayenne.modeler.util.ModelerUtil; import org.apache.cayenne.modeler.util.PanelFactory; import org.apache.cayenne.modeler.util.UIUtil; import org.apache.cayenne.modeler.util.combo.AutoCompletion; +import org.apache.cayenne.util.CayenneMapEntry; +import org.apache.commons.lang.StringUtils; +import javax.swing.AbstractCellEditor; +import javax.swing.DefaultComboBoxModel; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JList; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JTable; +import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; +import javax.swing.text.JTextComponent; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; /** * Detail view of the ObjEntity attributes. @@ -108,7 +128,16 @@ public class ObjEntityAttributePanel extends JPanel implements ObjEntityDisplayL ActionManager actionManager = Application.getInstance().getActionManager(); - table = new CayenneTable(); + table = new CayenneTable(){ + @Override + public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { + Component component = super.prepareRenderer(renderer, row, column); + int rendererWidth = component.getPreferredSize().width; + TableColumn tableColumn = getColumnModel().getColumn(column); + tableColumn.setPreferredWidth(Math.max(rendererWidth + getIntercellSpacing().width, tableColumn.getPreferredWidth())); + return component; + } + }; table.setDefaultRenderer(String.class, new CellRenderer()); tablePreferences = new TableColumnPreferences( ObjAttributeTableModel.class, @@ -205,16 +234,6 @@ public class ObjEntityAttributePanel extends JPanel implements ObjEntityDisplayL AutoCompletion.enable(javaTypesCombo, false, true); typeColumn.setCellEditor(Application.getWidgetFactory().createCellEditor( javaTypesCombo)); - - if (model.getEntity().getDbEntity() != null) { - Collection<String> nameAttr = ModelerUtil.getDbAttributeNames(mediator, model - .getEntity() - .getDbEntity()); - - model.setCellEditor(nameAttr, table); - model.setComboBoxes(nameAttr, ObjAttributeTableModel.DB_ATTRIBUTE); - } - } /** @@ -347,6 +366,9 @@ public class ObjEntityAttributePanel extends JPanel implements ObjEntityDisplayL initComboBoxes(model); + table.getColumnModel().getColumn(3).setCellRenderer(new JTableDbAttributeComboBoxRenderer()); + table.getColumnModel().getColumn(3).setCellEditor(new JTableDbAttributeComboBoxEditor()); + tablePreferences.bind( table, minSizes, @@ -502,4 +524,336 @@ public class ObjEntityAttributePanel extends JPanel implements ObjEntityDisplayL public ActionListener getResolver() { return resolver; } -} \ No newline at end of file + + + private static final class JTableDbAttributeComboBoxRenderer extends DefaultTableCellRenderer { + + public JTableDbAttributeComboBoxRenderer() { + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, + boolean hasFocus, int row, int column) { + if (value instanceof DbAttribute){ + JLabel jLabel = new JLabel(ModelerUtil.getObjectName(value)); + jLabel.setFont(new Font("Verdana", Font.PLAIN , 12)); + return jLabel; + } + if (value !=null){ + JLabel jLabel = new JLabel(value.toString()); + jLabel.setFont(new Font("Verdana", Font.PLAIN , 12)); + return jLabel; + } + return new JLabel(""); + } + } + + private final static class JTableDbAttributeComboBoxEditor extends AbstractCellEditor implements TableCellEditor { + + private int row; + private int column; + private JComboBox dbAttributePathCombo; + private EntityTreeModel treeModel; + private int previousEmbededLevel = 0; + private ObjAttributeTableModel model; + + private JTableDbAttributeComboBoxEditor() { + } + + @Override + public Object getCellEditorValue() { + return model.getValueAt(row,column); + } + + @Override + public Component getTableCellEditorComponent(final JTable table, Object o, boolean b, int i, int i1) { + this.model = (ObjAttributeTableModel) table.getModel(); + row = i; + column = i1; + treeModel = createTreeModelForComboBoxBrowser(row); + if (treeModel == null) + return new JLabel("You need select table to this ObjectEntity"); + initializeCombo(model , row); + + String dbAttributePath = ((JTextComponent) (dbAttributePathCombo). + getEditor().getEditorComponent()).getText(); + previousEmbededLevel = StringUtils.countMatches(dbAttributePath,"."); + + dbAttributePathCombo.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() { + private void enterPressed(){ + String dbAttributePath = ((JTextComponent) (dbAttributePathCombo). + getEditor().getEditorComponent()).getText(); + Object currentNode = getCurrentNode(dbAttributePath); + if (currentNode instanceof DbAttribute) { + // in this case choose is made.. we save data + + if (table.getCellEditor() != null) { + table.getCellEditor().stopCellEditing(); + model.getAttribute(row).setDbAttributePath(dbAttributePath); + model.setUpdatedValueAt(dbAttributePath, row, column); + } + }else if (currentNode instanceof DbRelationship) { + // in this case we add dot to pathString (if it is missing) and show variants for currentNode + + if (dbAttributePath.charAt(dbAttributePath.length()-1) != '.') { + dbAttributePath = dbAttributePath + "."; + previousEmbededLevel = StringUtils.countMatches(dbAttributePath,"."); + ((JTextComponent) (dbAttributePathCombo). + getEditor().getEditorComponent()).setText(dbAttributePath); + } + List<String> currentNodeChildren = new ArrayList<>(); + currentNodeChildren.add(dbAttributePath + ""); + currentNodeChildren.addAll(getChildren(getCurrentNode(dbAttributePath), dbAttributePath)); + dbAttributePathCombo.setModel(new DefaultComboBoxModel(currentNodeChildren.toArray())); + dbAttributePathCombo.showPopup(); + dbAttributePathCombo.setPopupVisible(true); + } + } + + @Override + public void keyReleased(KeyEvent event) { + if(event.getKeyCode() == KeyEvent.VK_ENTER){ + enterPressed(); + return; + } + parseDbAttributeString(event.getKeyChar()); + } + }); + return dbAttributePathCombo; + } + + private void initializeCombo(ObjAttributeTableModel model , int row){ + String dbAttributePath = model.getAttribute(row).getValue().getDbAttributePath(); + Object currentNode; + if (dbAttributePath == null){ + //case if it is new attribute or for some reason dbAttributePath is null + currentNode = getCurrentNode(dbAttributePath); + dbAttributePath = ""; + + }else{ + //case if dbAttributePath isn't null and we must change it to find auto completion list + String[] pathStrings = dbAttributePath.split(Pattern.quote(".")); + String lastStringInPath = pathStrings[pathStrings.length - 1]; + dbAttributePath = dbAttributePath.replaceAll(lastStringInPath + "$", ""); + currentNode = getCurrentNode(dbAttributePath); + } + List<String> nodeChildren = getChildren(currentNode , dbAttributePath); + dbAttributePathCombo = Application.getWidgetFactory().createComboBox( + nodeChildren, + false); + AutoCompletion.enable(dbAttributePathCombo, false, true); + ((JTextComponent) (dbAttributePathCombo). + getEditor().getEditorComponent()).setText(model.getAttribute(row).getValue().getDbAttributePath()); + return; + } + + private void parseDbAttributeString(char lastEnteredCharacter){ + String dbAttributePath = ((JTextComponent) (dbAttributePathCombo). + getEditor().getEditorComponent()).getText(); + + if (dbAttributePath.equals("")){ + List<String> currentNodeChildren = new ArrayList<>(); + currentNodeChildren.add(""); + currentNodeChildren.addAll(getChildren(getCurrentNode(dbAttributePath),"")); + dbAttributePathCombo.setModel(new DefaultComboBoxModel(currentNodeChildren.toArray())); + dbAttributePathCombo.showPopup(); + dbAttributePathCombo.setPopupVisible(true); + return; + } + + if (lastEnteredCharacter == '.') { + processDotEntered(); + return; + } + int currentEmbededLevel = StringUtils.countMatches(dbAttributePath,"."); + if (previousEmbededLevel != currentEmbededLevel){ + previousEmbededLevel = currentEmbededLevel; + List<String> currentNodeChildren = new ArrayList<>(); + String[] pathStrings = dbAttributePath.split(Pattern.quote(".")); + String lastStringInPath = pathStrings[pathStrings.length - 1]; + String saveDbAttributePath = dbAttributePath; + dbAttributePath = dbAttributePath.replaceAll(lastStringInPath + "$", ""); + currentNodeChildren.addAll(getChildren(getCurrentNode(dbAttributePath), dbAttributePath)); + + dbAttributePathCombo.setModel(new DefaultComboBoxModel(currentNodeChildren.toArray())); + ((JTextComponent) (dbAttributePathCombo). + getEditor().getEditorComponent()).setText(saveDbAttributePath); + dbAttributePathCombo.showPopup(); + dbAttributePathCombo.setPopupVisible(true); + return; + } + } + + private void processDotEntered(){ + String dbAttributePath = ((JTextComponent) (dbAttributePathCombo). + getEditor().getEditorComponent()).getText(); + if (dbAttributePath.equals(".")){ + List<String> currentNodeChildren = new ArrayList<>(); + currentNodeChildren.add(""); + currentNodeChildren.addAll(getChildren(getCurrentNode(""),"")); + dbAttributePathCombo.setModel(new DefaultComboBoxModel(currentNodeChildren.toArray())); + dbAttributePathCombo.showPopup(); + dbAttributePathCombo.setPopupVisible(true); + return; + }else { + char secondFromEndCharacter = dbAttributePath.charAt(dbAttributePath.length()-2); + if(secondFromEndCharacter == '.') { + // two dots entered one by one , we replace it by one dot + ((JTextComponent) (dbAttributePathCombo). + getEditor().getEditorComponent()).setText(dbAttributePath.substring(0,dbAttributePath.length()-1)); + return; + }else{ + String[] pathStrings = dbAttributePath.split(Pattern.quote(".")); + String lastStringInPath = pathStrings[pathStrings.length - 1]; + + //we will check if lastStringInPath is correct name of DbAttribute or DbRelationship + //for appropriate previous node in path. if it is not we won't add entered dot to dbAttributePath + String dbAttributePathForPreviousNode; + if (pathStrings.length == 1){ + //previous root is treeModel.getRoot() + dbAttributePathForPreviousNode = null; + }else { + dbAttributePathForPreviousNode = dbAttributePath.replace("."+lastStringInPath,""); + } + List<String> potentialVariantsToChoose = getChildren(getCurrentNode(dbAttributePathForPreviousNode),""); + if (potentialVariantsToChoose.contains(lastStringInPath)){ + List<String> currentNodeChildren = new ArrayList<>(); + currentNodeChildren.add(dbAttributePath + ""); + currentNodeChildren.addAll(getChildren(getCurrentNode(dbAttributePath), dbAttributePath)); + dbAttributePathCombo.setModel(new DefaultComboBoxModel(currentNodeChildren.toArray())); + dbAttributePathCombo.showPopup(); + dbAttributePathCombo.setPopupVisible(true); + }else{ + ((JTextComponent) (dbAttributePathCombo). + getEditor().getEditorComponent()).setText(dbAttributePath.substring(0,dbAttributePath.length()-1)); + } + } + } + previousEmbededLevel = StringUtils.countMatches(dbAttributePath,"."); + return; + } + + /** + * find current node by dbAttributePath + * @param dbAttributePath + * @return last node in dbAttributePath which matches DbRelationship or DbAttribute + */ + private final Object getCurrentNode(String dbAttributePath) { + try { + //case for new attribute + if(dbAttributePath == null){ + return treeModel.getRoot(); + } + String[] pathStrings = dbAttributePath.split(Pattern.quote(".")); + Object root = treeModel.getRoot(); + for (int i = 0 ; i < pathStrings.length ; i ++) { + String rootChildText = pathStrings[i]; + for (int j = 0; j < treeModel.getChildCount(root); j++) { + Object child = treeModel.getChild(root, j); + String objectName = ModelerUtil.getObjectName(child); + if (objectName.equals(rootChildText)) { + root = child; + break; + } + } + } + return root; + }catch (Exception e){ + return treeModel.getRoot(); + } + } + + /** + * @param node for which we will find children + * @param dbAttributePath string which will be added to each child to make right autocomplete + * @return list with children , which will be used to autocomplete + */ + private final List<String> getChildren(Object node , String dbAttributePath){ + List<String> currentNodeChildren = new ArrayList<>(); + for(int j = 0 ; j < treeModel.getChildCount(node) ; j++){ + Object child = treeModel.getChild(node, j); + String objectName = ModelerUtil.getObjectName(child); + currentNodeChildren.add(dbAttributePath+objectName); + } + return currentNodeChildren; + } + + /** + * @param attributeIndexInTable index of attribute for which now we will create cell editor + * @return treeModel for nessesary for us attribute + */ + private EntityTreeModel createTreeModelForComboBoxBrowser(int attributeIndexInTable){ + ObjAttribute attribute = model.getAttribute(attributeIndexInTable).getValue(); + Entity firstEntity = null; + if (attribute.getDbAttribute() == null) { + + if (attribute.getParent() instanceof ObjEntity) { + DbEntity dbEnt = ((ObjEntity) attribute.getParent()).getDbEntity(); + + if (dbEnt != null) { + Collection<DbAttribute> attributes = dbEnt.getAttributes(); + Collection<DbRelationship> rel = dbEnt.getRelationships(); + + if (attributes.size() > 0) { + Iterator<DbAttribute> iterator = attributes.iterator(); + firstEntity = iterator.next().getEntity(); + } else if (rel.size() > 0) { + Iterator<DbRelationship> iterator = rel.iterator(); + firstEntity = iterator.next().getSourceEntity(); + } + } + } + } else { + firstEntity = getFirstEntity(attribute); + } + + if (firstEntity != null) { + EntityTreeModel treeModel = new EntityTreeModel(firstEntity); + treeModel.setFilter(new EntityTreeFilter() { + + public boolean attributeMatch(Object node, Attribute attr) { + if (!(node instanceof Attribute)) { + return true; + } + return false; + } + + public boolean relationshipMatch(Object node, Relationship rel) { + if (!(node instanceof Relationship)) { + return true; + } + + /** + * We do not allow A->B->A chains, where relationships + * are to-one + */ + DbRelationship prev = (DbRelationship) node; + return !(!rel.isToMany() && prev.getReverseRelationship() == rel); + } + }); + return treeModel; + } + return null; + } + private Entity getFirstEntity(ObjAttribute attribute) { + Iterator<CayenneMapEntry> it = attribute.getDbPathIterator(); + Entity firstEnt = attribute.getDbAttribute().getEntity(); + boolean setEnt = false; + + while (it.hasNext()) { + Object ob = it.next(); + if (ob instanceof DbRelationship) { + if (!setEnt) { + firstEnt = ((DbRelationship) ob).getSourceEntity(); + setEnt = true; + } + } else if (ob instanceof DbAttribute) { + if (!setEnt) { + firstEnt = ((DbAttribute) ob).getEntity(); + } + } + } + return firstEnt; + } + } +}