/* Copyright (C) 2006 EBI This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the itmplied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.biomart.builder.view.gui.dialogs; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.swing.Box; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ListCellRenderer; import javax.swing.SwingConstants; import javax.swing.WindowConstants; import javax.swing.border.EmptyBorder; import javax.swing.border.EtchedBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelListener; import javax.swing.plaf.basic.BasicArrowButton; import javax.swing.table.DefaultTableModel; import org.biomart.builder.exceptions.PartitionException; import org.biomart.builder.model.Column; import org.biomart.builder.model.DataSet; import org.biomart.builder.model.Mart; import org.biomart.builder.model.PartitionTable; import org.biomart.builder.model.Relation; import org.biomart.builder.model.TransformationUnit; import org.biomart.builder.model.DataSet.DataSetColumn; import org.biomart.builder.model.DataSet.DataSetTable; import org.biomart.builder.model.DataSet.DataSetTableType; import org.biomart.builder.model.DataSet.DataSetColumn.InheritedColumn; import org.biomart.builder.model.DataSet.DataSetColumn.WrappedColumn; import org.biomart.builder.model.PartitionTable.PartitionColumn; import org.biomart.builder.model.PartitionTable.PartitionRow; import org.biomart.builder.model.PartitionTable.PartitionTableApplication; import org.biomart.builder.model.PartitionTable.PartitionTableApplication.PartitionAppliedRow; import org.biomart.builder.model.TransformationUnit.JoinTable; import org.biomart.builder.view.gui.MartTabSet.MartTab; import org.biomart.common.exceptions.BioMartError; import org.biomart.common.resources.Resources; import org.biomart.common.view.gui.LongProcess; import org.biomart.common.view.gui.dialogs.StackTrace; import org.biomart.common.view.gui.dialogs.TransactionalDialog; /** * A dialog which allows the user to turn a dataset into a partition table and * modify the way it behaves as such. * * @author Richard Holland <holland@ebi.ac.uk> * @version $Revision: 1.33 $, $Date: 2008-04-11 16:30:31 $, modified by * $Author: syed $ * @since 0.7 */ public class PartitionTableDialog extends TransactionalDialog { private static final long serialVersionUID = 1; /** * How many rows to show in the preview panel. */ public static int PREVIEW_ROWS = 10; private DefaultListModel availableColumns; private DefaultListModel selectedColumns; private DefaultTableModel previewData; private JCheckBox partition; private JTextField previewRowCount; private JComboBox appliedList; private WizardPanel wizardPanel; /** * Pop up a dialog to define or edit a dataset partition table data. * * @param dataset * the dataset. * @param martTab * the mart tab the dataset lives in. */ public PartitionTableDialog(final MartTab martTab, final DataSet dataset) { this(martTab, dataset, null); } private PartitionTableDialog(final MartTab martTab, final DataSet dataset, final Object preselect) { // Create the base dialog. super(); this.setTitle(Resources.get("partitionTableDialogTitle", dataset .getName())); this.setModal(true); this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); // Create the content pane to store the create dialog panel. final JPanel content = new JPanel(new GridBagLayout()); this.setContentPane(content); // Create constraints for labels that are not in the last row. final GridBagConstraints labelConstraints = new GridBagConstraints(); labelConstraints.gridwidth = GridBagConstraints.RELATIVE; labelConstraints.fill = GridBagConstraints.HORIZONTAL; labelConstraints.anchor = GridBagConstraints.LINE_END; labelConstraints.insets = new Insets(0, 2, 0, 0); // Create constraints for fields that are not in the last row. final GridBagConstraints fieldConstraints = new GridBagConstraints(); fieldConstraints.gridwidth = GridBagConstraints.REMAINDER; fieldConstraints.fill = GridBagConstraints.NONE; fieldConstraints.anchor = GridBagConstraints.LINE_START; fieldConstraints.insets = new Insets(0, 1, 0, 2); // Create constraints for labels that are in the last row. final GridBagConstraints labelLastRowConstraints = (GridBagConstraints) labelConstraints .clone(); labelLastRowConstraints.gridheight = GridBagConstraints.REMAINDER; // Create constraints for fields that are in the last row. final GridBagConstraints fieldLastRowConstraints = (GridBagConstraints) fieldConstraints .clone(); fieldLastRowConstraints.gridheight = GridBagConstraints.REMAINDER; // Stuff that needs to be available early. final JPanel wizardHolder = new JPanel(); wizardHolder.setBorder(new EtchedBorder(EtchedBorder.LOWERED)); this.appliedList = new JComboBox(); // On select from list on left, update edit panel with edit wizard // for that application. Then pack(). this.appliedList.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { final Object sel = PartitionTableDialog.this.appliedList .getSelectedItem(); wizardHolder.removeAll(); PartitionTableDialog.this.wizardPanel = null; if (sel != null) if (sel instanceof DataSet) PartitionTableDialog.this.wizardPanel = new WizardPanel( ((DataSet) sel).getPartitionTableApplication(), ((DataSet) sel).getMainTable()); else if (sel instanceof DataSetTable) PartitionTableDialog.this.wizardPanel = new WizardPanel( ((DataSetTable) sel) .getPartitionTableApplication(), (DataSetTable) sel); if (PartitionTableDialog.this.wizardPanel != null) wizardHolder.add(PartitionTableDialog.this.wizardPanel); PartitionTableDialog.this.pack(); } }); // Show/hide pane. final JPanel showHide = new JPanel(new GridBagLayout()); showHide.setVisible(false); // First line is a checkbox. If unchecked, partitioning is off // on this table. If checked, then it is on. This shows/hides // the remaining two panels. this.partition = new JCheckBox(Resources.get("partitionTableCheckbox")); content.add(this.partition, fieldConstraints); content.add(showHide, fieldLastRowConstraints); // Put the two halves of the dialog side-by-side in a horizontal box. final Box colPanel = Box.createHorizontalBox(); showHide.add(new JLabel(Resources.get("partitionTableColumnLabel")), labelConstraints); showHide.add(colPanel, fieldConstraints); // Create the available column list, and the buttons // to move columns to/from the selected column list. this.availableColumns = new DefaultListModel(); final JList tabColList = new JList(this.availableColumns); final JButton insertButton = new BasicArrowButton(SwingConstants.EAST); final JButton removeButton = new BasicArrowButton(SwingConstants.WEST); // Create the selected column list, and the buttons to // move columns to/from the table columns list. this.selectedColumns = new DefaultListModel(); final JList keyColList = new JList(this.selectedColumns); final JButton upButton = new BasicArrowButton(SwingConstants.NORTH); final JButton downButton = new BasicArrowButton(SwingConstants.SOUTH); // Create the regex fields. final JTextField matchRegex = new JTextField(20); final JTextField replaceRegex = new JTextField(20); final JButton regexUpdateButton = new JButton(Resources .get("updateButton")); final JButton regexResetButton = new JButton(Resources .get("resetButton")); // Left-hand side goes the table columns that are unused. final JPanel leftPanel = new JPanel(new BorderLayout()); // Label at the top. leftPanel.add(new JLabel(Resources.get("columnsAvailableLabel")), BorderLayout.PAGE_START); // Table columns list in the middle. leftPanel.add(new JScrollPane(tabColList), BorderLayout.CENTER); leftPanel.setBorder(new EmptyBorder(2, 2, 2, 2)); // Buttons down the right-hand-side, vertically. final Box leftButtonPanel = Box.createVerticalBox(); leftButtonPanel.add(insertButton); leftButtonPanel.add(removeButton); leftButtonPanel.setBorder(new EmptyBorder(2, 2, 2, 2)); leftPanel.add(leftButtonPanel, BorderLayout.LINE_END); colPanel.add(leftPanel); // Right-hand side goes the key columns that are used. final JPanel rightPanel = new JPanel(new BorderLayout()); // Label at the top. rightPanel.add(new JLabel(Resources.get("selectedColumnsLabel")), BorderLayout.PAGE_START); // Key columns in the middle. rightPanel.add(new JScrollPane(keyColList), BorderLayout.CENTER); rightPanel.setBorder(new EmptyBorder(2, 2, 2, 2)); // Buttons down the right-hand-side, vertically. final Box rightButtonPanel = Box.createVerticalBox(); rightButtonPanel.add(upButton); rightButtonPanel.add(downButton); rightButtonPanel.setBorder(new EmptyBorder(2, 2, 2, 2)); rightPanel.add(rightButtonPanel, BorderLayout.LINE_END); colPanel.add(rightPanel); // Far side lists regex info. final JPanel regexPanel = new JPanel(new BorderLayout()); // Label at the top. regexPanel.add(new JLabel(Resources.get("regexColumnsLabel")), BorderLayout.PAGE_START); regexPanel.setBorder(new EmptyBorder(2, 2, 2, 2)); // Regex fields in their own sub panel. final JPanel subRegexPanel = new JPanel(new GridBagLayout()); subRegexPanel.add(new JLabel(Resources .get("regexColumnMatchRegexLabel")), labelConstraints); subRegexPanel.add(matchRegex, fieldConstraints); subRegexPanel.add(new JLabel(Resources .get("regexColumnReplaceRegexLabel")), labelLastRowConstraints); subRegexPanel.add(replaceRegex, fieldLastRowConstraints); regexPanel.add(subRegexPanel, BorderLayout.CENTER); // Buttons at bottom. final JPanel regexButtonPanel = new JPanel(); regexButtonPanel.add(regexResetButton); regexButtonPanel.add(regexUpdateButton); regexButtonPanel.setBorder(new EmptyBorder(2, 2, 2, 2)); regexPanel.add(regexButtonPanel, BorderLayout.PAGE_END); colPanel.add(regexPanel); // On select of column in third column, update regex data, // or disable regex fields. keyColList.getSelectionModel().addListSelectionListener( new ListSelectionListener() { public void valueChanged(final ListSelectionEvent e) { // Ignore multiple events. if (e.getValueIsAdjusting()) return; // Check if selection valid (single selection only). final boolean valid = keyColList.getSelectedValue() != null && !keyColList.getSelectedValue().equals( PartitionTable.DIV_COLUMN); matchRegex.setEnabled(valid); replaceRegex.setEnabled(valid); regexResetButton.setEnabled(valid); regexUpdateButton.setEnabled(valid); // If selection valid, update details by clicking on // reset button. if (valid) regexResetButton.doClick(); else { // If selection invalid, clear fields. matchRegex.setText(null); replaceRegex.setText(null); } } }); // Default is all disabled. matchRegex.setEnabled(false); replaceRegex.setEnabled(false); regexResetButton.setEnabled(false); regexUpdateButton.setEnabled(false); // Intercept the insert/remove buttons insertButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { final Object selected = tabColList.getSelectedValue(); if (selected != null) { // Move a column from table to key. PartitionTableDialog.this.selectedColumns .addElement(selected); try { dataset.asPartitionTable().setSelectedColumnNames( PartitionTableDialog.this .getNewSelectedColumns()); } catch (final PartitionException pe) { StackTrace.showStackTrace(pe); } PartitionTableDialog.this.updateAvailableColumns(dataset); PartitionTableDialog.this.updatePreviewPanel(martTab, dataset); } } }); removeButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { final Object selected = keyColList.getSelectedValue(); if (selected != null) { // Move a column from key to table. PartitionTableDialog.this.selectedColumns .removeElement(selected); try { dataset.asPartitionTable().setSelectedColumnNames( PartitionTableDialog.this .getNewSelectedColumns()); } catch (final PartitionException pe) { StackTrace.showStackTrace(pe); } PartitionTableDialog.this.updateAvailableColumns(dataset); PartitionTableDialog.this.updatePreviewPanel(martTab, dataset); } } }); // Intercept the up/down buttons upButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { final Object selected = keyColList.getSelectedValue(); if (selected != null) { final int currIndex = keyColList.getSelectedIndex(); if (currIndex > 0) { // Swap the selected item with the one above it. final Object swap = PartitionTableDialog.this.selectedColumns .get(currIndex - 1); PartitionTableDialog.this.selectedColumns.setElementAt( selected, currIndex - 1); PartitionTableDialog.this.selectedColumns.setElementAt( swap, currIndex); // Select the selected item again, as it will // have moved. keyColList.setSelectedIndex(currIndex - 1); PartitionTableDialog.this.updatePreviewPanel(martTab, dataset); } } } }); downButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { final Object selected = keyColList.getSelectedValue(); if (selected != null) { final int currIndex = keyColList.getSelectedIndex(); if (currIndex < PartitionTableDialog.this.selectedColumns .size() - 1) { // Swap the selected item with the one below it. final Object swap = PartitionTableDialog.this.selectedColumns .get(currIndex + 1); PartitionTableDialog.this.selectedColumns.setElementAt( selected, currIndex + 1); PartitionTableDialog.this.selectedColumns.setElementAt( swap, currIndex); // Select the selected item again, as it will // have moved. keyColList.setSelectedIndex(currIndex + 1); PartitionTableDialog.this.updatePreviewPanel(martTab, dataset); } } } }); // Regex update/reset buttons. regexUpdateButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { final PartitionColumn col = (PartitionColumn) dataset .asPartitionTable().getColumns().get( (String) keyColList.getSelectedValue()); col.setRegexMatch(matchRegex.getText()); col.setRegexReplace(replaceRegex.getText()); // Regex update button also updates preview. PartitionTableDialog.this.updatePreviewPanel(martTab, dataset); } }); regexResetButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { final PartitionColumn col = (PartitionColumn) dataset .asPartitionTable().getColumns().get( (String) keyColList.getSelectedValue()); matchRegex.setText(col.getRegexMatch()); replaceRegex.setText(col.getRegexReplace()); } }); // Next area is preview row count. final JPanel previewCountPanel = new JPanel(); this.previewRowCount = new JTextField(5); this.previewRowCount.setText("" + PartitionTableDialog.PREVIEW_ROWS); showHide.add(new JLabel(Resources.get("previewRowCountLabel")), labelConstraints); previewCountPanel.add(this.previewRowCount); final JButton previewUpdate = new JButton(Resources.get("updateButton")); previewCountPanel.add(previewUpdate); showHide.add(previewCountPanel, fieldConstraints); previewUpdate.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { PartitionTableDialog.this.updatePreviewPanel(martTab, dataset); } }); // Second area (bottom) is preview panel. Populated on opening, // and on each change. this.previewData = new DefaultTableModel(); final JTable previewTable = new JTable(this.previewData); previewTable.setGridColor(Color.LIGHT_GRAY); // Mac OSX. previewTable.setEnabled(false); previewTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); previewTable.setPreferredScrollableViewportSize(colPanel .getPreferredSize()); showHide.add(new JLabel(Resources.get("previewLabel")), labelConstraints); showHide.add(new JScrollPane(previewTable), fieldConstraints); // If the user rearranges the columns in the preview data, // mirror in the selected columns. previewTable.getColumnModel().addColumnModelListener( new TableColumnModelListener() { public void columnAdded(final TableColumnModelEvent e) { // Don't care. } public void columnMarginChanged(final ChangeEvent e) { // Don't care. } public void columnMoved(final TableColumnModelEvent e) { if (e.getFromIndex() == e.getToIndex()) return; PartitionTableDialog.this.selectedColumns.clear(); for (int i = 0; i < previewTable.getColumnCount(); i++) PartitionTableDialog.this.selectedColumns .addElement(previewTable.getColumnName(i)); try { dataset.asPartitionTable().setSelectedColumnNames( PartitionTableDialog.this .getNewSelectedColumns()); } catch (final PartitionException pe) { StackTrace.showStackTrace(pe); } } public void columnRemoved(final TableColumnModelEvent e) { // Don't care. } public void columnSelectionChanged( final ListSelectionEvent e) { // Don't care. } }); // Intercept the partition checkbox to update the available cols // list. this.partition.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { showHide.setVisible(PartitionTableDialog.this.partition .isSelected()); try { dataset .setPartitionTable(PartitionTableDialog.this.partition .isSelected()); if (PartitionTableDialog.this.partition.isSelected()) { // Update available/selected cols and preview. PartitionTableDialog.this .updateSelectedColumns(dataset); PartitionTableDialog.this .updateAvailableColumns(dataset); PartitionTableDialog.this.updatePreviewPanel(martTab, dataset); PartitionTableDialog.this.appliedList.removeAllItems(); } } catch (final PartitionException pe) { StackTrace.showStackTrace(pe); try { dataset.setPartitionTable(false); } catch (final PartitionException pe2) { StackTrace.showStackTrace(pe2); } finally { PartitionTableDialog.this.setVisible(false); } } finally { PartitionTableDialog.this.pack(); } } }); // Default view of data. if (dataset.isPartitionTable()) { this.partition.setSelected(true); showHide.setVisible(true); this.updateSelectedColumns(dataset); this.updateAvailableColumns(dataset); this.updatePreviewPanel(martTab, dataset); for (final Iterator i = dataset.asPartitionTable() .getAllApplications().entrySet().iterator(); i.hasNext();) { final Map.Entry entry = (Map.Entry) i.next(); final DataSet ds = (DataSet) entry.getKey(); for (final Iterator j = ((Map) entry.getValue()).keySet() .iterator(); j.hasNext();) { final String dimName = (String) j.next(); if (dimName.equals(PartitionTable.NO_DIMENSION)) this.appliedList.addItem(ds); else this.appliedList.addItem(ds.getTables().get(dimName)); } } if (this.appliedList.getItemCount() > 0) this.appliedList.setSelectedIndex(0); } // Last section is the partition-applied panel. showHide.add(new JLabel(Resources.get("partitionAppliedLabel")), labelLastRowConstraints); final JPanel appliedPanel = new JPanel(); final JPanel listHolder = new JPanel(new BorderLayout()); appliedPanel.add(listHolder); appliedPanel.add(wizardHolder); // On the left we have a drop-down of places it is applied at. listHolder.add(this.appliedList, BorderLayout.CENTER); // Below the list are add/remove buttons for selected item. final JPanel listButtons = new JPanel(); final JButton addAppl = new JButton(Resources.get("addButton")); final JButton removeAppl = new JButton(Resources.get("removeButton")); listButtons.add(removeAppl); listButtons.add(addAppl); listHolder.add(listButtons, BorderLayout.PAGE_END); // Add button calculates list of valid datasets/dimensions, // prompts user to choose one, then adds it to the list and selects it. addAppl.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { // Lookup valid dataset/dimension options. final List choices = new ArrayList(dataset.getMart() .getDataSets().values()); // Remove those that are partition tables themselves. for (final Iterator i = choices.iterator(); i.hasNext();) if (((DataSet) i.next()).isPartitionTable()) i.remove(); // Remove all invisible and masked datasets. for (final Iterator i = choices.iterator(); i.hasNext();) { final DataSet ds = (DataSet) i.next(); if (ds.isMasked() || ds.isInvisible()) i.remove(); } // Only remove those applied to NO_DIMENSION. for (final Iterator i = dataset.asPartitionTable() .getAllApplications().entrySet().iterator(); i .hasNext();) { final Map.Entry entry = (Map.Entry) i.next(); final DataSet ds = (DataSet) entry.getKey(); final Map map = (Map) entry.getValue(); if (map.containsKey(PartitionTable.NO_DIMENSION)) choices.remove(ds); } // Add all dimensions from remaining dses then remove // used dimensions and parent dses. for (final Iterator i = new ArrayList(choices).iterator(); i .hasNext();) { final DataSet ds = (DataSet) i.next(); // Add all dimension tables. for (final Iterator j = ds.getTables().values().iterator(); j .hasNext();) { final DataSetTable dst = (DataSetTable) j.next(); if (dst.getType().equals(DataSetTableType.DIMENSION)) choices.add(dst); } final Map dims = (Map) dataset.asPartitionTable() .getAllApplications().get(ds); if (dims != null) { choices.remove(ds); for (final Iterator j = dims.keySet().iterator(); j .hasNext();) choices.remove(ds.getTables().get(j.next())); } } // Prompt user to choose one. final Object sel = JOptionPane.showInputDialog( PartitionTableDialog.this, Resources .get("partitionSelectApplyTarget"), Resources .get("questionTitle"), JOptionPane.QUESTION_MESSAGE, null, choices.toArray(), null); // Create a default PartitionTableApplication and add it. if (sel == null) return; else if (sel instanceof DataSet) dataset.asPartitionTable().applyTo( (DataSet) sel, PartitionTable.NO_DIMENSION, PartitionTableApplication.createDefault(dataset .asPartitionTable(), (DataSet) sel)); else if (sel instanceof DataSetTable) dataset.asPartitionTable().applyTo( (DataSet) ((DataSetTable) sel).getSchema(), ((DataSetTable) sel).getName(), PartitionTableApplication.createDefault(dataset .asPartitionTable(), ((DataSetTable) sel) .getDataSet(), ((DataSetTable) sel) .getName())); else throw new BioMartError(); // Eh? // Add the selected item to the list and select it. PartitionTableDialog.this.appliedList.addItem(sel); PartitionTableDialog.this.appliedList.setSelectedItem(sel); } }); // Remove button removes current item from list. removeAppl.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { final Object sel = PartitionTableDialog.this.appliedList .getSelectedItem(); if (sel == null) return; wizardHolder.removeAll(); if (sel instanceof DataSet) dataset.asPartitionTable().removeFrom((DataSet) sel, PartitionTable.NO_DIMENSION); else if (sel instanceof DataSetTable) dataset.asPartitionTable().removeFrom( ((DataSetTable) sel).getDataSet(), ((DataSetTable) sel).getName()); PartitionTableDialog.this.appliedList.removeItem(sel); } }); showHide.add(appliedPanel, fieldLastRowConstraints); // Select an applied item. if (preselect != null) this.appliedList.setSelectedItem(preselect); // Set size of window. this.pack(); // Move ourselves. this.setLocationRelativeTo(null); } private void updateAvailableColumns(final DataSet ds) { this.availableColumns.clear(); if (ds.isPartitionTable()) for (final Iterator i = ds.asPartitionTable() .getAvailableColumnNames().iterator(); i.hasNext();) this.availableColumns.addElement(i.next()); // Only allow div if not already used. this.availableColumns.addElement(PartitionTable.DIV_COLUMN); for (final Iterator i = this.getNewSelectedColumns().iterator(); i .hasNext();) this.availableColumns.removeElement(i.next()); } private void updateSelectedColumns(final DataSet ds) { this.selectedColumns.clear(); if (ds.isPartitionTable()) { for (final Iterator i = ds.asPartitionTable() .getSelectedColumnNames().iterator(); i.hasNext();) this.selectedColumns.addElement(i.next()); this.appliedList.setSelectedIndex(-1); } } private List getNewSelectedColumns() { return Arrays.asList(this.selectedColumns.toArray()); } private void updatePreviewPanel(final MartTab martTab, final DataSet ds) { new LongProcess() { public void run() throws Exception { // Update ds with new ones. final List selectedCols = PartitionTableDialog.this .getNewSelectedColumns(); if (!ds.asPartitionTable().getSelectedColumnNames().equals( selectedCols)) { ds.asPartitionTable().setSelectedColumnNames(selectedCols); for (final Iterator i = ds.asPartitionTable() .getAllApplications().values().iterator(); i .hasNext();) for (final Iterator j = ((Map) i.next()).values() .iterator(); j.hasNext();) ((PartitionTableApplication) ((WeakReference) j .next()).get()).syncCounts(); } // Update preview data column headers. PartitionTableDialog.this.previewData .setColumnIdentifiers(selectedCols.toArray()); // Clear preview data model. while (PartitionTableDialog.this.previewData.getRowCount() > 0) PartitionTableDialog.this.previewData.removeRow(0); // No new cols? Don't go any further. final List trueSelectedCols = new ArrayList(); for (final Iterator i = selectedCols.iterator(); i.hasNext();) { final String col = (String) i.next(); if (!col.equals(PartitionTable.DIV_COLUMN)) trueSelectedCols.add(col); } if (trueSelectedCols.size() < 1) return; // Get the rows. try { ds .asPartitionTable() .prepareRows( martTab.getPartitionViewSelection(), Integer .parseInt(PartitionTableDialog.this.previewRowCount .getText())); } catch (final NumberFormatException nfe) { ds.asPartitionTable().prepareRows(null, PartitionTableDialog.PREVIEW_ROWS); } while (ds.asPartitionTable().nudgeRow()) { final PartitionRow row = ds.asPartitionTable().currentRow(); final List rowData = new ArrayList(); for (final Iterator i = selectedCols.iterator(); i .hasNext();) { final String col = (String) i.next(); if (col.equals(PartitionTable.DIV_COLUMN)) rowData.add("->"); else rowData.add(((PartitionColumn) row .getPartitionTable().getColumns().get(col)) .getValueForRow(row)); } // Add an entry to the data model using list.toArray(); PartitionTableDialog.this.previewData.addRow(rowData .toArray()); } // Re-select the current applied item in order // to update available columns. if (PartitionTableDialog.this.wizardPanel != null) { PartitionTableDialog.this.wizardPanel.recalculate(); PartitionTableDialog.this.pack(); } } }.start(); } private static class WizardPanel extends JPanel { private static final long serialVersionUID = 1L; private final List ptLevels = new ArrayList(); private final List dsLevels = new ArrayList(); private final List nameLevels = new ArrayList(); private final DataSetTable dsTable; private final PartitionTableApplication pta; private Map dsRelMap = new HashMap(); private WizardPanel(final PartitionTableApplication pta, final DataSetTable dsTable) { super(new GridBagLayout()); this.pta = pta; this.dsTable = dsTable; this.recalculate(); } private void recalculate() { this.removeAll(); this.ptLevels.clear(); this.dsLevels.clear(); this.nameLevels.clear(); this.dsRelMap.clear(); // Convert column list to include blank. // Make drop-down display modified names, with // prefix stripped if necessary. But, it selects real // names (with stripped prefixes) in background. final Map dsColMap = new TreeMap(); for (final Iterator j = this.dsTable.getTransformationUnits() .iterator(); j.hasNext();) { final TransformationUnit tu = (TransformationUnit) j.next(); final Relation rel = tu instanceof JoinTable ? ((JoinTable) tu) .getSchemaRelation() : null; for (final Iterator i = tu.getNewColumnNameMap().values() .iterator(); i.hasNext();) { final DataSetColumn dsCol = (DataSetColumn) i.next(); if (dsCol instanceof WrappedColumn || dsCol instanceof InheritedColumn) { String root = dsCol.getName(); if (root.indexOf("__") >= 0) root = root.substring(root.lastIndexOf("__") + 2); String display = dsCol.getModifiedName(); if (display.indexOf("__") >= 0) display = display.substring(display .lastIndexOf("__") + 2); if (!dsColMap.containsKey(root)) { dsColMap.put(root, display); this.dsRelMap.put(root, rel); } } } } final List dsColList = new ArrayList(dsColMap.keySet()); dsColList.add(0, null); // Create constraints for labels that are not in the last row. final GridBagConstraints labelConstraints = new GridBagConstraints(); labelConstraints.gridwidth = GridBagConstraints.RELATIVE; labelConstraints.fill = GridBagConstraints.HORIZONTAL; labelConstraints.anchor = GridBagConstraints.LINE_END; labelConstraints.insets = new Insets(0, 2, 0, 0); // Create constraints for fields that are not in the last row. final GridBagConstraints fieldConstraints = new GridBagConstraints(); fieldConstraints.gridwidth = GridBagConstraints.REMAINDER; fieldConstraints.fill = GridBagConstraints.NONE; fieldConstraints.anchor = GridBagConstraints.LINE_START; fieldConstraints.insets = new Insets(0, 1, 0, 2); // Create constraints for labels that are in the last row. final GridBagConstraints labelLastRowConstraints = (GridBagConstraints) labelConstraints .clone(); labelLastRowConstraints.gridheight = GridBagConstraints.REMAINDER; // Create constraints for fields that are in the last row. final GridBagConstraints fieldLastRowConstraints = (GridBagConstraints) fieldConstraints .clone(); fieldLastRowConstraints.gridheight = GridBagConstraints.REMAINDER; // On partition table change listener. // Load partition table. final PartitionTable pt = this.pta.getPartitionTable(); // Identify column names. final Iterator colNames = pt.getSelectedColumnNames().iterator(); // Reinsert one pair of entries per remaining subdivision. int currLevel = 0; for (List currLevelNames = this.getNextSubdivision(colNames); !currLevelNames .isEmpty(); currLevelNames = this .getNextSubdivision(colNames)) { // Add combos to apply-levels. final JComboBox ptCombo = new JComboBox(currLevelNames .toArray()); this.ptLevels.add(ptCombo); final JComboBox dsCombo = new JComboBox(dsColList.toArray()); // Remove null option from first row. if (currLevel == 0) dsCombo.removeItemAt(0); dsCombo.setRenderer(new ListCellRenderer() { public Component getListCellRendererComponent( final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) { final String key = (String) value; final JLabel label = new JLabel(); if (key != null) label.setText((String) dsColMap.get(key)); else label.setText(Resources .get("partitionDSUnselected")); label.setOpaque(true); label.setFont(list.getFont()); if (isSelected) { label.setBackground(list.getSelectionBackground()); label.setForeground(list.getSelectionForeground()); } else { label.setBackground(list.getBackground()); label.setForeground(list.getForeground()); } return label; } }); this.dsLevels.add(dsCombo); final JComboBox nameCombo = new JComboBox(currLevelNames .toArray()); this.nameLevels.add(nameCombo); final JPanel subpanel = new JPanel(new GridBagLayout()); subpanel.add(new JLabel(Resources.get("wizardPTColLabel")), labelConstraints); subpanel.add(ptCombo, fieldConstraints); subpanel.add(new JLabel(Resources.get("wizardDSColLabel")), labelConstraints); subpanel.add(dsCombo, fieldConstraints); subpanel.add(new JLabel(currLevel == 0 ? Resources .get("wizardTableNameColLabel") : Resources .get("wizardColumnNameColLabel")), labelLastRowConstraints); subpanel.add(nameCombo, fieldLastRowConstraints); this.add(subpanel, fieldConstraints); // Disable subsequent combos if this one is // set to null. dsCombo.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { for (int i = WizardPanel.this.dsLevels.indexOf(dsCombo) + 1; i < WizardPanel.this.dsLevels .size(); i++) { final JComboBox currName = (JComboBox) WizardPanel.this.nameLevels .get(i); final JComboBox currPT = (JComboBox) WizardPanel.this.ptLevels .get(i); final JComboBox currDS = (JComboBox) WizardPanel.this.dsLevels .get(i); final JComboBox prevDS = (JComboBox) WizardPanel.this.dsLevels .get(i - 1); if (prevDS.getSelectedItem() == null) { currDS.setSelectedItem(null); currName.setSelectedItem(null); } currDS.setEnabled(prevDS.getSelectedItem() != null); currPT.setEnabled(prevDS.getSelectedItem() != null); currName .setEnabled(prevDS.getSelectedItem() != null); } } }); // Restrict to first box only at first. // Take into account existing settings from application. final PartitionAppliedRow selected = currLevel < this.pta .getPartitionAppliedRows().size() ? (PartitionAppliedRow) this.pta .getPartitionAppliedRows().get(currLevel) : null; if (selected != null) { ptCombo.setSelectedItem(selected.getPartitionCol()); dsCombo.setSelectedItem(selected.getRootDataSetCol()); nameCombo.setSelectedItem(selected.getNamePartitionCol()); } else { dsCombo.setSelectedItem(null); nameCombo.setSelectedItem(null); } currLevel++; // Update model on change. final ActionListener al = new ActionListener() { public void actionPerformed(final ActionEvent e) { WizardPanel.this.updateModel(); } }; ptCombo.addActionListener(al); dsCombo.addActionListener(al); nameCombo.addActionListener(al); } } private void updateModel() { if (WizardPanel.this.validateFields()) { // How many rows? If less, remove extra ones. final int rowCount = WizardPanel.this.ptLevels.size(); while (rowCount < this.pta.getPartitionAppliedRows().size()) this.pta.getPartitionAppliedRows().remove( this.pta.getPartitionAppliedRows().size() - 1); // Update or add existing rows. for (int i = 0; i < WizardPanel.this.ptLevels.size() && ((JComboBox) WizardPanel.this.dsLevels.get(i)) .getSelectedItem() != null; i++) { final String ptCol = (String) ((JComboBox) WizardPanel.this.ptLevels .get(i)).getSelectedItem(); final String dsCol = (String) ((JComboBox) WizardPanel.this.dsLevels .get(i)).getSelectedItem(); final String nameCol = (String) ((JComboBox) WizardPanel.this.nameLevels .get(i)).getSelectedItem(); if (i >= this.pta.getPartitionAppliedRows().size()) this.pta.getPartitionAppliedRows().add( new PartitionAppliedRow(ptCol, dsCol, nameCol, (Relation) this.dsRelMap.get(dsCol))); else { final PartitionAppliedRow ptar = (PartitionAppliedRow) this.pta .getPartitionAppliedRows().get(i); ptar.setPartitionCol(ptCol); ptar.setRootDataSetCol(dsCol); ptar.setNamePartitionCol(nameCol); ptar.setRelation((Relation) this.dsRelMap.get(dsCol)); } } } } private boolean validateFields() { // List of messages to display, if any are necessary. final List messages = new ArrayList(); // Must have at least one column selected. if (((JComboBox) this.dsLevels.get(0)).getSelectedItem() == null) messages.add(Resources.get("wizardMissingFirstMapping")); // Any messages to display? Show them. if (!messages.isEmpty()) JOptionPane.showMessageDialog(null, messages .toArray(new String[0]), Resources .get("validationTitle"), JOptionPane.INFORMATION_MESSAGE); // Validation succeeds if there are no messages. return messages.isEmpty(); } private List getNextSubdivision(final Iterator cols) { if (!cols.hasNext()) return Collections.EMPTY_LIST; final List nextcols = new ArrayList(); while (cols.hasNext()) { final String colname = (String) cols.next(); if (!colname.equals(PartitionTable.DIV_COLUMN)) nextcols.add(colname); else break; } return nextcols; } } /** * Open a wizard which asks user which partition table they want to apply to * the dimension, or which dataset they want to use to make such a partition * table. It then applies a default application to the dimension and opens * the editor with that application selected. * * @param martTab * the mart tab the dataset lives in. * @param dimension * the dimension we want to run the wizard on. * @throws PartitionException * if the table is invalid. */ public static void showForDimension(final MartTab martTab, final DataSetTable dimension) throws PartitionException { final Mart mart = martTab.getMart(); // Does it already have a partition table? Select that one. PartitionTableApplication pta = dimension .getPartitionTableApplication(); if (pta == null) { // If not, select an existing one. final List options = new ArrayList(mart.getPartitionTables()); options.add(0, Resources.get("createNewPartitionTable")); Object ptSelected = JOptionPane.showInputDialog(null, Resources .get("wizardSelectPartitionTable"), Resources .get("questionTitle"), JOptionPane.QUESTION_MESSAGE, null, options.toArray(), null); WrappedColumn autoCol = null; Column sourceCol = null; PartitionTable pt = null; if (ptSelected == null) return; else if (ptSelected .equals(Resources.get("createNewPartitionTable"))) { // New dialog asking for a column to use from // the currently selected table. final Map newOptions = new TreeMap(); for (final Iterator i = dimension.getColumns().values() .iterator(); i.hasNext();) { final DataSetColumn dsCol = (DataSetColumn) i.next(); if (dsCol instanceof WrappedColumn) newOptions.put(dsCol.getModifiedName(), dsCol); } final String colName = (String) JOptionPane.showInputDialog( null, Resources.get("wizardCreatePartitionTable"), Resources.get("questionTitle"), JOptionPane.QUESTION_MESSAGE, null, newOptions.keySet() .toArray(), null); if (colName == null) return; // Magically build a dataset and new partition table // based on the selected column. autoCol = (WrappedColumn) newOptions.get(colName); sourceCol = autoCol.getWrappedColumn(); try { final Collection candidates = mart .suggestDataSets(Collections .singletonList(sourceCol.getTable())); final DataSet ds = (DataSet) candidates.iterator().next(); ds.setPartitionTable(true); pt = ds.asPartitionTable(); // Find ds col for source col and select // ds col's name - NOT source col name as may be // different (e.g. in _key case). DataSetColumn realSourceCol = null; for (final Iterator i = ds.getMainTable().getColumns() .values().iterator(); i.hasNext() && realSourceCol == null;) { final DataSetColumn cand = (DataSetColumn) i.next(); if (cand instanceof WrappedColumn && ((WrappedColumn) cand).getWrappedColumn() .equals(sourceCol)) realSourceCol = cand; } if (realSourceCol == null) throw new BioMartError(); // Should never happen. ds.asPartitionTable().setSelectedColumnNames( Collections.singletonList(realSourceCol .getModifiedName())); } catch (final Exception e) { StackTrace.showStackTrace(e); return; } } else pt = (PartitionTable) ptSelected; // Make a default definition and add to selected partition table. pta = PartitionTableApplication.createDefault(pt, dimension .getDataSet(), dimension.getName()); // If autoCol has been set, use it to modify the // partition table application. if (autoCol != null) { Relation rel = null; for (final Iterator j = ((DataSetTable) autoCol.getTable()) .getTransformationUnits().iterator(); j.hasNext() && rel == null;) { final TransformationUnit tu = (TransformationUnit) j.next(); final Relation candRel = tu instanceof JoinTable ? ((JoinTable) tu) .getSchemaRelation() : null; for (final Iterator i = tu.getNewColumnNameMap().values() .iterator(); i.hasNext() && rel == null;) { final DataSetColumn dsCol = (DataSetColumn) i.next(); if (dsCol == autoCol) rel = candRel; } } final PartitionAppliedRow row = new PartitionAppliedRow( autoCol.getModifiedName(), autoCol.getName(), autoCol.getModifiedName(), rel); pta.getPartitionAppliedRows().clear(); pta.getPartitionAppliedRows().add(row); } pt.applyTo(dimension.getDataSet(), dimension.getName(), pta); pta.syncCounts(); } // Open selected table with dimension selected in appliedList. final PartitionTableDialog dialog = new PartitionTableDialog(martTab, (DataSet) mart.getDataSets().get( pta.getPartitionTable().getOriginalName()), dimension); dialog.setVisible(true); dialog.dispose(); } /** * Open a wizard which asks user which partition table they want to apply to * the dataset, or which dataset they want to use to make such a partition * table. It then applies a default application to the dataset and opens the * editor with that application selected. * * @param martTab * the mart tab the dataset lives in. * @param dataset * the dataset we want to run the wizard on. * @throws PartitionException * if the table is invalid. */ public static void showForDataSet(final MartTab martTab, final DataSet dataset) throws PartitionException { final Mart mart = martTab.getMart(); // Does it already have a partition table? Select that one. PartitionTableApplication pta = dataset.getPartitionTableApplication(); if (pta == null) { // If not, select an existing one. final List options = new ArrayList(mart.getPartitionTables()); options.add(0, Resources.get("createNewPartitionTable")); Object ptSelected = JOptionPane.showInputDialog(null, Resources .get("wizardSelectPartitionTable"), Resources .get("questionTitle"), JOptionPane.QUESTION_MESSAGE, null, options.toArray(), null); if (ptSelected == null) return; WrappedColumn autoCol = null; Column sourceCol = null; PartitionTable pt = null; if (ptSelected == null) return; else if (ptSelected .equals(Resources.get("createNewPartitionTable"))) { // New dialog asking for a column to use from // the currently selected table. final Map newOptions = new TreeMap(); for (final Iterator i = dataset.getMainTable().getColumns() .values().iterator(); i.hasNext();) { final DataSetColumn dsCol = (DataSetColumn) i.next(); if (dsCol instanceof WrappedColumn) newOptions.put(dsCol.getModifiedName(), dsCol); } final String colName = (String) JOptionPane.showInputDialog( null, Resources.get("wizardCreatePartitionTable"), Resources.get("questionTitle"), JOptionPane.QUESTION_MESSAGE, null, newOptions.keySet() .toArray(), null); if (colName == null) return; // Magically build a dataset and new partition table // based on the selected column. autoCol = (WrappedColumn) newOptions.get(colName); sourceCol = autoCol.getWrappedColumn(); try { final Collection candidates = mart .suggestDataSets(Collections .singletonList(sourceCol.getTable())); final DataSet ds = (DataSet) candidates.iterator().next(); ds.setPartitionTable(true); pt = ds.asPartitionTable(); // Find ds col for source col and select // ds col's name - NOT source col name as may be // different (e.g. in _key case). DataSetColumn realSourceCol = null; for (final Iterator i = ds.getMainTable().getColumns() .values().iterator(); i.hasNext() && realSourceCol == null;) { final DataSetColumn cand = (DataSetColumn) i.next(); if (cand instanceof WrappedColumn && ((WrappedColumn) cand).getWrappedColumn() .equals(sourceCol)) realSourceCol = cand; } if (realSourceCol == null) throw new BioMartError(); // Should never happen. ds.asPartitionTable().setSelectedColumnNames( Collections.singletonList(realSourceCol .getModifiedName())); } catch (final Exception e) { StackTrace.showStackTrace(e); return; } } else pt = (PartitionTable) ptSelected; // Make a default definition and add to selected partition table. pta = PartitionTableApplication.createDefault(pt, dataset); // If autoCol has been set, use it to modify the // partition table application. if (autoCol != null) { Relation rel = null; for (final Iterator j = ((DataSetTable) autoCol.getTable()) .getTransformationUnits().iterator(); j.hasNext() && rel == null;) { final TransformationUnit tu = (TransformationUnit) j.next(); final Relation candRel = tu instanceof JoinTable ? ((JoinTable) tu) .getSchemaRelation() : null; for (final Iterator i = tu.getNewColumnNameMap().values() .iterator(); i.hasNext() && rel == null;) { final DataSetColumn dsCol = (DataSetColumn) i.next(); if (dsCol == autoCol) rel = candRel; } } final PartitionAppliedRow row = new PartitionAppliedRow( sourceCol.getName(), autoCol.getName(), sourceCol .getName(), rel); pta.getPartitionAppliedRows().clear(); pta.getPartitionAppliedRows().add(row); } pt.applyTo(dataset, PartitionTable.NO_DIMENSION, pta); pta.syncCounts(); } // Open selected table with dimension selected in appliedList. final PartitionTableDialog dialog = new PartitionTableDialog(martTab, (DataSet) mart.getDataSets().get( pta.getPartitionTable().getOriginalName()), dataset); dialog.setVisible(true); dialog.dispose(); } }