/* * Copyright 2007 - 2017 the original author or authors. * * Licensed 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 * * http://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 net.sf.jailer.ui; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.TreeSet; import java.util.Vector; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JSeparator; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import net.sf.jailer.datamodel.Association; import net.sf.jailer.datamodel.DataModel; import net.sf.jailer.datamodel.Table; import net.sf.jailer.ui.scrollmenu.JScrollMenu; import net.sf.jailer.ui.scrollmenu.JScrollPopupMenu; import net.sf.jailer.util.Pair; import net.sf.jailer.util.SqlUtil; /** * Dialog for browsing through the closure of a table. * * @author Ralf Wisser */ public abstract class ClosureView extends javax.swing.JDialog { /** * Number of tables in a closure-table's line. */ private int tablesPerLine = 8; /** * The extraction model editor. */ private final ExtractionModelEditor extractionModelEditor; /** * Currently selected table (in closure-table). */ private String selectedTable; /** * Background colors per row. */ private final List<Color> bgColor = new ArrayList<Color>(); /** * Holds infos about a cell in the closure-table. */ private class CellInfo { public int row, column; boolean ignored = false; Set<CellInfo> parents = new HashSet<CellInfo>(4); boolean selected; void select() { if (!selected) { selected = true; for (CellInfo parent: parents) { parent.select(); } } } Table table; }; /** * Holds infos about a cell in the closure-table. */ private Map<String, CellInfo> cellInfo = new HashMap<String, CellInfo>(); private Set<Pair<String, String>> dependencies = new HashSet<Pair<String,String>>(); private final JComboBox rootTable; /** Creates new form FindDialog * @param rootTable */ public ClosureView(ExtractionModelEditor extractionModelEditor, JComboBox rootTable) { super(); this.extractionModelEditor = extractionModelEditor; this.rootTable = rootTable; initComponents(); columnsComboBox.setModel(new DefaultComboBoxModel<Integer>(new Integer[] { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 })); columnsComboBox.setSelectedItem(new Integer(tablesPerLine)); columnsComboBox.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { if (evt.getItem() instanceof Integer) { tablesPerLine = (Integer) evt.getItem(); refresh(); } } }); showOnlyEnabledCheckBox.setMnemonic(KeyEvent.VK_S); // disableAssocButton.setMnemonic(KeyEvent.VK_D); // disableAssocButton.setEnabled(false); // tableSelection.setMaximumRowCount(22); closureTable = new JTable() { private static final long serialVersionUID = 8960056200057023368L; /** * Paints selected path. */ @Override public void paint(Graphics graphics) { super.paint(graphics); if (!(graphics instanceof Graphics2D)) return; Graphics2D g2d = (Graphics2D) graphics; CellInfo selectionInfo = cellInfo.get(selectedTable); if (selectionInfo == null) return; paint(g2d, selectionInfo, false, new HashSet<CellInfo>()); paint(g2d, selectionInfo, true, new HashSet<CellInfo>()); } private void paint(Graphics2D g2d, CellInfo selectionInfo, boolean drawDependencies, Set<CellInfo> painted) { if (painted.contains(selectionInfo)) { return; } painted.add(selectionInfo); for (CellInfo parent: selectionInfo.parents) { boolean isDependency = dependencies.contains(new Pair<String, String>( getDataModel().getDisplayName(parent.table), getDataModel().getDisplayName(selectionInfo.table))); if (isDependency == drawDependencies) { int[] x = new int[2]; int[] y = new int[2]; Rectangle r = closureTable.getCellRect(parent.row, parent.column, false); x[0] = ((int) r.getCenterX()); y[0] = ((int) r.getCenterY()); CellInfo posInfo = selectionInfo; r = closureTable.getCellRect(posInfo.row, posInfo.column, false); x[1] = ((int) r.getCenterX()); y[1] = ((int) r.getCenterY()); Color color = new Color(0, 0, 245, 60); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (selectionInfo.ignored) { BasicStroke stroke = new BasicStroke(3); g2d.setStroke(new BasicStroke(stroke.getLineWidth(), stroke.getEndCap(), stroke.getLineJoin(), stroke.getMiterLimit(), new float[] { 2f, 6f }, 1.0f)); color = new Color(0, 0, 0, 60); } else { g2d.setStroke(new BasicStroke(3)); } if (isDependency) { color = new Color(245, 0, 0, 60); } g2d.setColor(color); g2d.drawPolyline(x, y, 2); } paint(g2d, parent, drawDependencies, painted); } } }; closureTable.setShowGrid(false); closureTable.setSurrendersFocusOnKeystroke(true); jScrollPane1.setViewportView(closureTable); closureTable.addMouseListener(new MouseListener() { public void mouseClicked(final MouseEvent e) { // context menu if (SwingUtilities.isRightMouseButton(e)) { int row = closureTable.rowAtPoint(e.getPoint()); int column = closureTable.columnAtPoint(e.getPoint()); if (row < 0 || column < 0) return; Object value = closureTable.getModel().getValueAt(row, column); if (value == null || !(value instanceof String)) return; Table table = getDataModel().getTableByDisplayName((String) value); if (table != null) { JPopupMenu popup = ClosureView.this.extractionModelEditor.graphView.createPopupMenu(table, true); popup.show(e.getComponent(), e.getX(), e.getY()); } } if (SwingUtilities.isLeftMouseButton(e)) { int row = closureTable.rowAtPoint(e.getPoint()); int column = closureTable.columnAtPoint(e.getPoint()); if (row < 0 || column < 0) return; final Rectangle cellRect = closureTable.getCellRect(row, column, false); Object value = closureTable.getModel().getValueAt(row, column); if (value == null || !(value instanceof String)) return; final Table table = getDataModel().getTableByDisplayName((String) value); if (table != null) { JMenuItem restrictAll = new JMenuItem("Disable all associations"); restrictAll.setToolTipText("disables every non-dependent (child) association with this table"); restrictAll.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { ClosureView.this.extractionModelEditor.ignoreAll(table); } }); restrictAll.setEnabled(ClosureView.this.extractionModelEditor.isIgnoreAllApplicable(table)); JMenuItem removeRestrictions = new JMenuItem("Remove all restrictions"); removeRestrictions.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { ClosureView.this.extractionModelEditor.removeAllRestrictions(table); } }); removeRestrictions.setEnabled(ClosureView.this.extractionModelEditor.isRemovalOfAllRestrictionsApplicable(table)); removeRestrictions.setToolTipText("removes all restrictions on all associations with this table"); JMenu restrict = new JScrollMenu("Disable association..."); restrict.setToolTipText("disables an association with this table"); JMenu remove = new JScrollMenu("Remove restrictions on..."); remove.setToolTipText("removes all restrictions on an association with this table"); List<Association> aLDisable = new ArrayList<Association>(); List<Association> aLEnable = new ArrayList<Association>(); for (Association association: table.associations) { if (!association.isInsertDestinationBeforeSource()) { aLDisable.add(association); } aLEnable.add(association); } Map<String, Association> namedAssoc; namedAssoc = sortedNamed(aLDisable); boolean enab; enab = false; for (Map.Entry<String, Association> entry: namedAssoc.entrySet()) { JMenuItem menuItem = new JMenuItem(entry.getKey()); final Association association = entry.getValue(); if (association.isIgnored()) { menuItem.setEnabled(false); } enab = true; menuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { ClosureView.this.extractionModelEditor.ignorAssociation(association); } }); restrict.getPopupMenu().add(menuItem); } restrict.setEnabled(enab); namedAssoc = sortedNamed(aLEnable); enab = false; for (Map.Entry<String, Association> entry: namedAssoc.entrySet()) { JMenuItem menuItem = new JMenuItem(entry.getKey()); remove.getPopupMenu().add(menuItem); final Association association = entry.getValue(); if (!association.isRestricted()) { menuItem.setEnabled(false); } enab = true; menuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { ClosureView.this.extractionModelEditor.removeRestriction(association); } }); } remove.setEnabled(enab); final JPopupMenu popup = new JScrollPopupMenu(); popup.add(restrictAll); popup.add(removeRestrictions); popup.add(new JSeparator()); popup.add(restrict); popup.add(remove); if (cellInfo.containsKey(value)) { selectTableCell(column, row); } ClosureView.this.extractionModelEditor.graphView.createPopupMenu(table, false); popup.show(e.getComponent(), cellRect.x, cellRect.y + cellRect.height + 4); } } } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } }); searchComboBox.setMaximumRowCount(30); final TableCellRenderer defaultTableCellRenderer = closureTable.getDefaultRenderer(String.class); closureTable.setDefaultRenderer(Object.class, new TableCellRenderer() { private Font font = new JLabel("normal").getFont(); private Font normal = new Font(font.getName(), font.getStyle() & ~Font.BOLD, font.getSize()); private Font bold = new Font(font.getName(), font.getStyle() | Font.BOLD, font.getSize()); public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { isSelected = selectedTable != null && selectedTable.equals(value); hasFocus = false; Component render = defaultTableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (render instanceof JLabel && !isSelected) { if (row < bgColor.size()) { ((JLabel) render).setBackground(bgColor.get(row)); } } CellInfo cellInfo = ClosureView.this.cellInfo.get(value); if (render instanceof JLabel) { ((JLabel) render).setForeground(Color.BLACK); ((JLabel) render).setFont(normal); String text = ((JLabel) render).getText(); if (!"".equals(text)) { ((JLabel) render).setToolTipText(text); } else { ((JLabel) render).setToolTipText(null); } if (row == 0) { ((JLabel) render).setFont(bold); } if (cellInfo != null && selectedTable != null) { if (selectedTable.equals(value) || cellInfo.selected) { ((JLabel) render).setFont(bold); ((JLabel) render).setBackground(new Color(255, 230, 200)); } } Table t = getDataModel().getTableByDisplayName((String) value); if (t != null) { boolean allDisabled = true; boolean someRestricted = false; for (Association association: t.associations) { if (!association.isInsertDestinationBeforeSource()) { if (!association.isIgnored()) { allDisabled = false; } } if (association.isRestricted()) { someRestricted = true; } } if (allDisabled && someRestricted) { ((JLabel) render).setForeground(new Color(160, 80, 0)); } else if (!allDisabled && someRestricted) { ((JLabel) render).setForeground(new Color(0, 80, 160)); } } } return render; } }); // closureTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); closureTable.setRowSelectionAllowed(false); rootTable.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { Table table = getDataModel().getTableByDisplayName((String) ClosureView.this.rootTable.getSelectedItem()); refresh(table); } }); initTabbedPane(); setLocation(100, 100); setSize(500, 500); setAlwaysOnTop(true); } protected SortedMap<String, Association> sortedNamed(List<Association> aList) { SortedMap<String, Association> result = new TreeMap<String, Association>(); for (Association a: aList) { boolean isDup = false; for (Association a2: aList) { if (a2 != a && a2.destination.equals(a.destination)) { isDup = true; break; } } result.put(getDataModel().getDisplayName(a.destination) + (isDup? " (" + a.getName() + ")" : ""), a); } return result; } private void selectTableCell(int col, int row) { if (col >= 1 && row >= 0) { String displayName = (String) closureTable.getModel().getValueAt(row, col); closureTable.getSelectionModel().clearSelection(); for (CellInfo c: cellInfo.values()) { c.selected = false; } if (displayName != null && !"".equals(displayName)) { selectedTable = displayName; searchComboBox.setSelectedItem(selectedTable); repaintClosureView(); Table table = getDataModel().getTableByDisplayName(selectedTable); if (table != null) { CellInfo selectionInfo = cellInfo.get(selectedTable); selectionInfo.select(); if (selectionInfo != null) { Association association = null; for (CellInfo parent: selectionInfo.parents) { Table pre = parent.table; if (pre != null) { for (Association a: pre.associations) { if (!a.isIgnored()) { if (a.destination.equals(table)) { association = a; break; } } } } } if (association != null && ClosureView.this.extractionModelEditor.select(association)) { return; } } if (!ClosureView.this.extractionModelEditor.select(table)) { ClosureView.this.extractionModelEditor.setRootSelection(table); } } } } } /** * Gets current data model. * * @return current data model */ private DataModel getDataModel() { return extractionModelEditor.dataModel; } /** * Make {@link #refresh(Table)} reentrant. */ private boolean refreshing = false; /** * Refreshes the dialog. * * @param tableToSelect the table to select initially or <code>null</code> to keep the current selection */ public void refresh(Table tableToSelect) { if (refreshing) { return; } refreshing = true; selectedTable = null; // table list model if (tableToSelect == null) { Object currentSelection = rootTable.getSelectedItem(); if (currentSelection instanceof String) { tableToSelect = getDataModel().getTableByDisplayName((String) currentSelection); } } Vector<String> tableNames = new Vector<String>(); for (Table table: getDataModel().getTables()) { tableNames.add(getDataModel().getDisplayName(table)); } Collections.sort(tableNames); // DefaultComboBoxModel model = new DefaultComboBoxModel(tableNames); // tableSelection.setModel(model); // if (tableToSelect != null) { // tableSelection.setSelectedItem(getDataModel().getDisplayName(tableToSelect)); // } else { // tableSelection.setSelectedItem(0); // } // table model refreshTableModel(); refreshing = false; } /** * Refreshes the dialog after the model has been changed. */ public void refresh() { String prevSelection = selectedTable; refreshTableModel(); if (cellInfo.containsKey(prevSelection)) { selectedTable = prevSelection; } else { selectedTable = null; } // disableAssocButton.setEnabled(false); repaintClosureView(); } /** * Refreshes the table model. */ private void refreshTableModel() { cellInfo.clear(); dependencies.clear(); Table selectedTable = getSelectedTable(); refreshAssociationView(selectedTable); Object[] columns = new Object[tablesPerLine + 1]; for (int i = 0; i < columns.length; ++i) { columns[i] = ""; } columns[0] = "Distance"; columns[1] = "Table"; List<Object[]> data = new ArrayList<Object[]>(); Set<String> visited = new TreeSet<String>(); List<String> currentLine = new ArrayList<String>(); if (selectedTable != null) { String displayName = getDataModel().getDisplayName(selectedTable); currentLine.add(displayName); visited.add(displayName); CellInfo cellInfo = new CellInfo(); cellInfo.column = 1; cellInfo.row = 0; cellInfo.table = selectedTable; this.cellInfo.put(displayName, cellInfo); } int distance = 0; final int OMEGA = Integer.MAX_VALUE / 2; boolean isolated = false; final Color BG1 = new Color(255, 255, 255); final Color BG2 = new Color(230, 255, 255); final Color BG3 = new Color(255, 255, 240); final Color BG4 = new Color(220, 220, 220); final Color BG5 = new Color(255, 240, 240); bgColor.clear(); TreeSet<String> nonIsolated = new TreeSet<String>(); while (!currentLine.isEmpty()) { // add current line to table model if (distance == OMEGA || isolated) { Object[] lineAsObjects = new Object[tablesPerLine + 1]; Arrays.fill(lineAsObjects, ""); data.add(lineAsObjects); bgColor.add(BG4); } Collections.sort(currentLine); Object[] lineAsObjects = new Object[tablesPerLine + 1]; Arrays.fill(lineAsObjects, ""); int col = 0; lineAsObjects[col++] = isolated? "isolated" : distance > OMEGA? "" : distance == OMEGA ? "infinite" : distance > 0? ("" + distance) : ""; Color color = distance >= OMEGA? (distance % 2 == 0? BG5 : BG3) : distance % 2 == 0? BG1 : BG2; for (String t: currentLine) { CellInfo cellInfo = this.cellInfo.get(t); if (col <= tablesPerLine) { if (cellInfo != null) { cellInfo.column = col; } lineAsObjects[col++] = t; } else { data.add(lineAsObjects); bgColor.add(color); lineAsObjects = new Object[tablesPerLine + 1]; Arrays.fill(lineAsObjects, ""); col = 1; if (cellInfo != null) { cellInfo.column = col; } lineAsObjects[col++] = t; } if (cellInfo != null) { cellInfo.row = data.size(); } } if (col > 1) { data.add(lineAsObjects); bgColor.add(color); } // get next line List<String> nextLine = new ArrayList<String>(); for (String t: currentLine) { Table table = getDataModel().getTableByDisplayName(t); if (table != null) { CellInfo cellInfoT = this.cellInfo.get(t); for (Association association: table.associations) { String displayName = getDataModel().getDisplayName(association.destination); if (!association.isIgnored()) { if (!visited.contains(displayName)) { nextLine.add(displayName); visited.add(displayName); CellInfo cellInfo = new CellInfo(); cellInfo.parents.add(cellInfoT); cellInfo.table = association.destination; if (association.isInsertDestinationBeforeSource()) { dependencies.add(new Pair<String, String>(t, displayName)); } this.cellInfo.put(displayName, cellInfo); } else { if (nextLine.contains(displayName)) { this.cellInfo.get(displayName).parents.add(cellInfoT); if (association.isInsertDestinationBeforeSource()) { dependencies.add(new Pair<String, String>(t, displayName)); } } } } } } } ++distance; if (nextLine.isEmpty()) { if (distance < OMEGA) { distance = OMEGA; } Set<String> preVisited = new TreeSet<String>(visited); for (Table table: getDataModel().getTables()) { String displayName = getDataModel().getDisplayName(table); if (!visited.contains(displayName)) { CellInfo cellInfoT = null; String destName = null; for (Association a: table.associations) { destName = getDataModel().getDisplayName(a.destination); if (preVisited.contains(destName)) { cellInfoT = this.cellInfo.get(destName); CellInfo cellInfo = this.cellInfo.get(displayName); if (cellInfo == null) { cellInfo = new CellInfo(); cellInfo.table = table; nextLine.add(displayName); visited.add(displayName); } cellInfo.ignored = true; cellInfo.parents.add(cellInfoT); if (a.isInsertDestinationBeforeSource()) { dependencies.add(new Pair<String, String>(displayName, destName)); } cellInfo.table = table; this.cellInfo.put(displayName, cellInfo); } } } } if (nextLine.isEmpty()) { if (!isolated) { isolated = true; nonIsolated = new TreeSet<String>(visited); } for (Table table: getDataModel().getTables()) { String displayName = getDataModel().getDisplayName(table); if (!visited.contains(displayName)) { nextLine.add(displayName); visited.add(displayName); } } } } currentLine = nextLine; } if (selectedTable != null) { CellInfo cInfo = cellInfo.get(this.selectedTable); if (cInfo != null) { cInfo.select(); } } Object[][] dataArray = (Object[][]) data.toArray(new Object[data.size()][]); DefaultTableModel tableModel = new DefaultTableModel(dataArray, columns) { public boolean isCellEditable(int row, int column) { return false; } private static final long serialVersionUID = -6639310191624899380L; }; closureTable.setModel(tableModel); for (int i = 0; i < closureTable.getColumnCount(); i++) { TableColumn column = closureTable.getColumnModel().getColumn(i); int width = 1; Component comp = closureTable.getDefaultRenderer(String.class). getTableCellRendererComponent( closureTable, column.getHeaderValue(), false, false, 0, i); width = Math.max(width, comp.getPreferredSize().width); for (int line = 0; line < dataArray.length; ++line) { comp = closureTable.getDefaultRenderer(String.class). getTableCellRendererComponent( closureTable, dataArray[line][i], false, false, line, i); width = Math.max(width, comp.getPreferredSize().width); } column.setPreferredWidth(width); } closureTable.setIntercellSpacing(new Dimension(0, 0)); // disableAssocButton.setEnabled(false); Vector<String> vector = new Vector<String>(); vector.add(""); vector.addAll(nonIsolated); searchComboBox.setModel(new DefaultComboBoxModel<String>(vector)); searchComboBox.setSelectedItem(""); } private Table getSelectedTable() { Table selectedTable = null; Object currentSelection = rootTable.getSelectedItem(); if (currentSelection instanceof String) { selectedTable = getDataModel().getTableByDisplayName((String) currentSelection); } return selectedTable; } /** * Names of associations which have been recently disabled and therefore are still be visible. */ private Set<String> editedAssociations = new TreeSet<String>(); /** * Names of associations in the closure ordered by distance. */ private List<List<String>> associationClosure = new ArrayList<List<String>>(); /** * All components of the association-closure render. */ private Collection<JComponent> associationClosureRender = new ArrayList<JComponent>(); /** * Name of focused table for which no association closure size limit exists. */ private String noAssocLimitTableName; private final int MAX_ASSOC_CLOSURE_SIZE_LIMIT = 200; /** * Refreshes the associations view. * * @param selectedTable selected table (focus) or <code>null</code> */ private void refreshAssociationView(Table selectedTable) { jLabel2.setFont(nonbold); jLabel3.setFont(nonbold); jLabel4.setFont(nonbold); jLabel5.setFont(nonbold); jLabel6.setFont(nonbold); for (JComponent c: associationClosureRender) { assocViewPanel.remove(c); } associationClosureRender.clear(); createAssociationClosure(selectedTable); int y = 3; int surplus = 0; GridBagConstraints gridBagConstraints; int distance = 0; boolean limitExceeded = false; final Table st = getSelectedTable(); boolean unlimited = noAssocLimitTableName != null && st != null && noAssocLimitTableName.equals(st.getName()); Color bgColor = Color.WHITE; for (List<String> assocList: associationClosure) { boolean firstTime = true; if (distance % 2 == 0) { bgColor = new java.awt.Color(240, 255, 255); } else { bgColor = Color.WHITE; } for (final String assocName: assocList) { Association association = getDataModel().namedAssociations.get(assocName); if (association == null || association.isInsertDestinationBeforeSource()) { continue; } if (y - 3 > MAX_ASSOC_CLOSURE_SIZE_LIMIT && !unlimited) { limitExceeded = true; } if (limitExceeded) { ++surplus; continue; } Font font = nonbold; Color bgc = bgColor; if (extractionModelEditor.graphView.selectedAssociation != null && assocName.equals(extractionModelEditor.graphView.selectedAssociation.getName())) { bgc = new Color(255, 230, 220); } JLabel l = createLabel(y, null, assocName, bgc, distance == 0 || !firstTime? " " : (" " + distance + " "), false); firstTime = false; l.setOpaque(true); l.setFont(font); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = y; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; assocViewPanel.add(l, gridBagConstraints); l = createLabel(y, association.source.getName(), assocName, bgc, " " + getDataModel().getDisplayName(association.source), false); l.setOpaque(true); l.setFont(font); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = y; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; assocViewPanel.add(l, gridBagConstraints); l = createLabel(y, association.destination.getName(), assocName, bgc, " " + getDataModel().getDisplayName(association.destination), false); l.setOpaque(true); l.setFont(font); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 2; gridBagConstraints.gridy = y; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; assocViewPanel.add(l, gridBagConstraints); final JCheckBox checkBox = new JCheckBox(); checkBox.setSelected(!association.isIgnored()); checkBox.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { editedAssociations.add(assocName); Association association = getDataModel().namedAssociations.get(assocName); if (association != null) { if (!ClosureView.this.extractionModelEditor.select(association)) { ClosureView.this.extractionModelEditor.setRootSelection(association.destination); ClosureView.this.extractionModelEditor.select(association); } extractionModelEditor.graphView.setRestriction(association, !checkBox.isSelected()); } } }); associationClosureRender.add(checkBox); checkBox.setBackground(bgc); checkBox.setOpaque(true); checkBox.setFont(nonbold); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 3; gridBagConstraints.gridy = y; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; assocViewPanel.add(checkBox, gridBagConstraints); String unrestrictedJoinCondition = association.getUnrestrictedJoinCondition(); if (association.reversed) { unrestrictedJoinCondition = SqlUtil.reversRestrictionCondition(unrestrictedJoinCondition); } l = createLabel(y, null, assocName, bgc, " " + unrestrictedJoinCondition, true); l.setOpaque(true); l.setFont(nonbold); l.setForeground(Color.GRAY); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 4; gridBagConstraints.gridy = y; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; assocViewPanel.add(l, gridBagConstraints); ++y; } if (firstTime && !limitExceeded) { JLabel l = createLabel(y, null, null, bgColor, distance == 0 || !firstTime? " " : (" " + distance + " "), false); firstTime = false; l.setOpaque(true); l.setFont(nonbold); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = y; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; assocViewPanel.add(l, gridBagConstraints); l = createLabel(y, null, null, bgColor, " no non-dependency association", true); l.setOpaque(true); l.setFont(nonbold); l.setForeground(Color.GRAY); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = y; gridBagConstraints.gridwidth = 4; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; assocViewPanel.add(l, gridBagConstraints); ++y; } ++distance; } if (limitExceeded) { JLabel l = createLabel(y, null, null, null, " " + surplus + " more...", false); l.setOpaque(true); l.setFont(nonbold); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridwidth = 4; gridBagConstraints.gridy = y; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; assocViewPanel.add(l, gridBagConstraints); JButton showAll = new JButton("Show all"); associationClosureRender.add(showAll); showAll.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { noAssocLimitTableName = st != null? st.getName() : null; refreshAssociationView(getSelectedTable()); repaintClosureView(); } }); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = y+1; gridBagConstraints.gridwidth = 4; gridBagConstraints.fill = java.awt.GridBagConstraints.NONE; gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; assocViewPanel.add(showAll, gridBagConstraints); ++y; } JLabel l = new JLabel(""); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = y; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.weighty = 1.0; assocViewPanel.add(l, gridBagConstraints); associationClosureRender.add(l); } private JLabel createLabel(final int y, final String tableName, final String assocName, Color bgColor, String text, boolean unbounded) { final int MAX_TEXT_LENGTH = 22; final JLabel label = new JLabel(); if (text.length() > MAX_TEXT_LENGTH && !unbounded) { label.setText(text.substring(0, MAX_TEXT_LENGTH) + "..."); label.setToolTipText(text); } else { label.setText(text); } associationClosureRender.add(label); if (bgColor != null) { label.setBackground(bgColor); } if (assocName != null) { label.addMouseListener(new MouseListener() { @Override public void mouseReleased(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseClicked(MouseEvent e) { if (SwingUtilities.isRightMouseButton(e)) { if (tableName != null) { Table table = getDataModel().getTable(tableName); if (table != null) { JPopupMenu popup = ClosureView.this.extractionModelEditor.graphView.createPopupMenu(table, false); popup.show(e.getComponent(), e.getX(), e.getY()); } } } else if (SwingUtilities.isLeftMouseButton(e)) { Association association = getDataModel().namedAssociations.get(assocName); if (association != null) { if (!ClosureView.this.extractionModelEditor.select(association)) { ClosureView.this.extractionModelEditor.setRootSelection(association.destination); ClosureView.this.extractionModelEditor.select(association); } refreshAssociationView(getSelectedTable()); repaintClosureView(); } } } }); } return label; } /** * Collects names of associations in the closure ordered by distance into {@link #associationClosure}. * * @param selectedTable focus */ private void createAssociationClosure(Table selectedTable) { associationClosure.clear(); if (selectedTable != null) { Set<Association> seen = new HashSet<Association>(); Set<Association> next = new HashSet<Association>(); next.addAll(selectedTable.associations); for (Association a: selectedTable.associations) { next.add(a.reversalAssociation); } Set<Table> closure = selectedTable.closure(true); while (!next.isEmpty()) { Set<Association> neighbors = new HashSet<Association>(); List<String> assocList = new ArrayList<String>(); seen.addAll(next); for (Association a: next) { if (closure.contains(a.source) /* && closure.contains(a.destination) */ || editedAssociations.contains(a.getName())) { if (!a.isIgnored() || !showOnlyEnabledCheckBox.isSelected()) { assocList.add(a.getName()); } if (!a.isIgnored()) { for (Association n: a.destination.associations) { if (!seen.contains(n)) { neighbors.add(n); } if (!seen.contains(n.reversalAssociation)) { neighbors.add(n.reversalAssociation); } } for (Association n: a.source.associations) { if (!seen.contains(n)) { neighbors.add(n); } if (!seen.contains(n.reversalAssociation)) { neighbors.add(n.reversalAssociation); } } } } } Collections.sort(assocList, new Comparator<String>() { @Override public int compare(String o1, String o2) { Association a1 = getDataModel().namedAssociations.get(o1); Association a2 = getDataModel().namedAssociations.get(o2); String n1 = "", n2 = ""; if (a1 != null) { n1 = a1.source.getName() + "," + a1.destination.getName(); } if (a2 != null) { n2 = a2.source.getName() + "," + a2.destination.getName(); } return n1.compareTo(n2); } }); if (!assocList.isEmpty()) { associationClosure.add(assocList); } next = neighbors; } } } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { java.awt.GridBagConstraints gridBagConstraints; contentPanel = new javax.swing.JPanel(); tabbedPane = new javax.swing.JTabbedPane(); tablePane = new javax.swing.JPanel(); tablePanel = new javax.swing.JPanel(); jScrollPane1 = new javax.swing.JScrollPane(); closureTable = new javax.swing.JTable(); jLabel1 = new javax.swing.JLabel(); searchComboBox = new net.sf.jailer.ui.JComboBox(); jLabel7 = new javax.swing.JLabel(); columnsComboBox = new net.sf.jailer.ui.JComboBox(); associationPane = new javax.swing.JPanel(); associationPanel = new javax.swing.JPanel(); jScrollPane2 = new javax.swing.JScrollPane(); assocViewPanel = new javax.swing.JPanel(); jLabel2 = new javax.swing.JLabel(); jLabel3 = new javax.swing.JLabel(); jLabel4 = new javax.swing.JLabel(); jLabel5 = new javax.swing.JLabel(); jLabel6 = new javax.swing.JLabel(); jSeparator1 = new javax.swing.JSeparator(); showOnlyEnabledCheckBox = new javax.swing.JCheckBox(); tableAssociationPane = new javax.swing.JPanel(); tabAssPanel = new javax.swing.JPanel(); tabAssTabPanel = new javax.swing.JPanel(); tabAssAssPanel = new javax.swing.JPanel(); contentPanel.setLayout(new java.awt.GridBagLayout()); setTitle("Closure Browser"); getContentPane().setLayout(new java.awt.GridBagLayout()); tabbedPane.setMinimumSize(new java.awt.Dimension(182, 190)); tabbedPane.addChangeListener(new javax.swing.event.ChangeListener() { public void stateChanged(javax.swing.event.ChangeEvent evt) { tabbedPaneStateChanged(evt); } }); tablePane.setLayout(new javax.swing.BoxLayout(tablePane, javax.swing.BoxLayout.LINE_AXIS)); tablePanel.setLayout(new java.awt.GridBagLayout()); jScrollPane1.setMinimumSize(new java.awt.Dimension(23, 64)); closureTable.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0)); closureTable.setModel(new javax.swing.table.DefaultTableModel( new Object [][] { {null, null, null, null}, {null, null, null, null}, {null, null, null, null}, {null, null, null, null} }, new String [] { "Titel 1", "Titel 2", "Titel 3", "Titel 4" } )); closureTable.setMinimumSize(new java.awt.Dimension(60, 80)); closureTable.setPreferredSize(new java.awt.Dimension(300, 80)); closureTable.setSurrendersFocusOnKeystroke(true); jScrollPane1.setViewportView(closureTable); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 1; gridBagConstraints.gridwidth = 30; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.weightx = 1.0; gridBagConstraints.weighty = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 2, 0, 2); tablePanel.add(jScrollPane1, gridBagConstraints); jLabel1.setText(" Search "); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 2; tablePanel.add(jLabel1, gridBagConstraints); searchComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); searchComboBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { searchComboBoxActionPerformed(evt); } }); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 2; gridBagConstraints.gridy = 2; tablePanel.add(searchComboBox, gridBagConstraints); jLabel7.setText("Columns "); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 3; gridBagConstraints.gridy = 2; gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; gridBagConstraints.weightx = 1.0; tablePanel.add(jLabel7, gridBagConstraints); columnsComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); columnsComboBox.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { columnsComboBoxItemStateChanged(evt); } }); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 4; gridBagConstraints.gridy = 2; tablePanel.add(columnsComboBox, gridBagConstraints); tablePane.add(tablePanel); tabbedPane.addTab("Table", tablePane); associationPane.setLayout(new javax.swing.BoxLayout(associationPane, javax.swing.BoxLayout.LINE_AXIS)); associationPanel.setLayout(new java.awt.GridBagLayout()); jScrollPane2.setBorder(javax.swing.BorderFactory.createEtchedBorder()); assocViewPanel.setLayout(new java.awt.GridBagLayout()); jLabel2.setText(" Distance "); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 1; gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; assocViewPanel.add(jLabel2, gridBagConstraints); jLabel3.setText(" to "); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 2; gridBagConstraints.gridy = 1; gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; assocViewPanel.add(jLabel3, gridBagConstraints); jLabel4.setText(" "); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 3; gridBagConstraints.gridy = 1; gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; assocViewPanel.add(jLabel4, gridBagConstraints); jLabel5.setText(" on"); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 4; gridBagConstraints.gridy = 1; gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints.weightx = 1.0; assocViewPanel.add(jLabel5, gridBagConstraints); jLabel6.setText(" from "); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 1; gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 8); assocViewPanel.add(jLabel6, gridBagConstraints); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 2; gridBagConstraints.gridwidth = 5; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; assocViewPanel.add(jSeparator1, gridBagConstraints); jScrollPane2.setViewportView(assocViewPanel); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 0; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.weightx = 1.0; gridBagConstraints.weighty = 1.0; associationPanel.add(jScrollPane2, gridBagConstraints); showOnlyEnabledCheckBox.setText("Show only enabled associations"); showOnlyEnabledCheckBox.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { showOnlyEnabledCheckBoxItemStateChanged(evt); } }); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 1; gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; associationPanel.add(showOnlyEnabledCheckBox, gridBagConstraints); associationPane.add(associationPanel); tabbedPane.addTab("Association", associationPane); tableAssociationPane.setLayout(new javax.swing.BoxLayout(tableAssociationPane, javax.swing.BoxLayout.LINE_AXIS)); tabAssPanel.setLayout(new java.awt.GridBagLayout()); tabAssTabPanel.setLayout(new javax.swing.BoxLayout(tabAssTabPanel, javax.swing.BoxLayout.LINE_AXIS)); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 0; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.weightx = 1.0; gridBagConstraints.weighty = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 4); tabAssPanel.add(tabAssTabPanel, gridBagConstraints); tabAssAssPanel.setLayout(new javax.swing.BoxLayout(tabAssAssPanel, javax.swing.BoxLayout.LINE_AXIS)); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 0; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.weightx = 1.0; gridBagConstraints.weighty = 1.0; tabAssPanel.add(tabAssAssPanel, gridBagConstraints); tableAssociationPane.add(tabAssPanel); tabbedPane.addTab("Table + Association", tableAssociationPane); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 10; gridBagConstraints.gridy = 16; gridBagConstraints.gridwidth = 20; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.weightx = 1.0; gridBagConstraints.weighty = 1.0; getContentPane().add(tabbedPane, gridBagConstraints); pack(); }// </editor-fold>//GEN-END:initComponents private void showOnlyEnabledCheckBoxItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_showOnlyEnabledCheckBoxItemStateChanged clearView(); }//GEN-LAST:event_showOnlyEnabledCheckBoxItemStateChanged private void tabbedPaneStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_tabbedPaneStateChanged initTabbedPane(); }//GEN-LAST:event_tabbedPaneStateChanged private void searchComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchComboBoxActionPerformed Object toFind = searchComboBox.getSelectedItem(); if (toFind != null && !toFind.equals(selectedTable)) { CellInfo cellInfo = this.cellInfo.get(toFind); if (cellInfo != null) { selectTableCell(cellInfo.column, cellInfo.row); } } }//GEN-LAST:event_searchComboBoxActionPerformed private void columnsComboBoxItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_columnsComboBoxItemStateChanged }//GEN-LAST:event_columnsComboBoxItemStateChanged private void initTabbedPane() { if (tabbedPane.getSelectedIndex() == 2) { tabAssTabPanel.removeAll(); tabAssTabPanel.add(tablePanel); tabAssAssPanel.removeAll(); tabAssAssPanel.add(associationPanel); } else { tablePane.removeAll(); tablePane.add(tablePanel); associationPane.removeAll(); associationPane.add(associationPanel); } } private void clearView() { editedAssociations.clear(); refreshAssociationView(getSelectedTable()); repaintClosureView(); } protected abstract void repaintClosureView(); // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JPanel assocViewPanel; private javax.swing.JPanel associationPane; private javax.swing.JPanel associationPanel; private javax.swing.JTable closureTable; private net.sf.jailer.ui.JComboBox columnsComboBox; public javax.swing.JPanel contentPanel; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; private javax.swing.JLabel jLabel5; private javax.swing.JLabel jLabel6; private javax.swing.JLabel jLabel7; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JScrollPane jScrollPane2; private javax.swing.JSeparator jSeparator1; private net.sf.jailer.ui.JComboBox searchComboBox; private javax.swing.JCheckBox showOnlyEnabledCheckBox; private javax.swing.JPanel tabAssAssPanel; private javax.swing.JPanel tabAssPanel; private javax.swing.JPanel tabAssTabPanel; private javax.swing.JTabbedPane tabbedPane; private javax.swing.JPanel tableAssociationPane; private javax.swing.JPanel tablePane; private javax.swing.JPanel tablePanel; // End of variables declaration//GEN-END:variables private static final long serialVersionUID = 5485949274233292142L; private Font normalfont = new JLabel("normal").getFont(); private Font nonbold = new Font(normalfont.getName(), normalfont.getStyle() & ~Font.BOLD, normalfont.getSize()); public void addTabComponent(String titel, Container tab) { tabbedPane.addTab(titel, tab); } }