/* 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; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.swing.ImageIcon; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; import org.biomart.builder.exceptions.PartitionException; import org.biomart.builder.exceptions.ValidationException; import org.biomart.builder.model.Column; import org.biomart.builder.model.DataSet; import org.biomart.builder.model.PartitionTable; import org.biomart.builder.model.Relation; import org.biomart.builder.model.Table; import org.biomart.builder.model.DataSet.DataSetColumn; import org.biomart.builder.model.DataSet.DataSetOptimiserType; import org.biomart.builder.model.DataSet.DataSetTable; import org.biomart.builder.model.DataSet.DataSetTableType; import org.biomart.builder.model.DataSet.ExpressionColumnDefinition; import org.biomart.builder.model.DataSet.SplitOptimiserColumnDef; import org.biomart.builder.model.DataSet.DataSetColumn.ExpressionColumn; import org.biomart.builder.model.PartitionTable.PartitionTableApplication; import org.biomart.builder.model.Relation.CompoundRelationDefinition; import org.biomart.builder.model.Relation.RestrictedRelationDefinition; import org.biomart.builder.model.Relation.UnrolledRelationDefinition; import org.biomart.builder.model.Table.RestrictedTableDefinition; import org.biomart.builder.view.gui.MartTabSet.MartTab; import org.biomart.builder.view.gui.diagrams.AllDataSetsDiagram; import org.biomart.builder.view.gui.diagrams.DataSetDiagram; import org.biomart.builder.view.gui.diagrams.Diagram; import org.biomart.builder.view.gui.diagrams.ExplainTransformationDiagram.RealisedRelation; import org.biomart.builder.view.gui.diagrams.contexts.AllDataSetsContext; import org.biomart.builder.view.gui.diagrams.contexts.DataSetContext; import org.biomart.builder.view.gui.diagrams.contexts.DiagramContext; import org.biomart.builder.view.gui.dialogs.CompoundRelationDialog; import org.biomart.builder.view.gui.dialogs.ExplainDataSetDialog; import org.biomart.builder.view.gui.dialogs.ExplainTableDialog; import org.biomart.builder.view.gui.dialogs.ExpressionColumnDialog; import org.biomart.builder.view.gui.dialogs.LoopbackRelationDialog; import org.biomart.builder.view.gui.dialogs.LoopbackWizardDialog; import org.biomart.builder.view.gui.dialogs.PartitionTableDialog; import org.biomart.builder.view.gui.dialogs.RestrictedRelationDialog; import org.biomart.builder.view.gui.dialogs.RestrictedTableDialog; import org.biomart.builder.view.gui.dialogs.SaveDDLDialog; import org.biomart.builder.view.gui.dialogs.SplitOptimiserColumnDialog; import org.biomart.builder.view.gui.dialogs.SuggestDataSetDialog; import org.biomart.builder.view.gui.dialogs.SuggestInvisibleDataSetDialog; import org.biomart.builder.view.gui.dialogs.SuggestUnrolledDataSetDialog; import org.biomart.builder.view.gui.dialogs.UnrolledRelationDialog; import org.biomart.common.resources.Resources; import org.biomart.common.utils.Transaction; import org.biomart.common.view.gui.LongProcess; import org.biomart.common.view.gui.dialogs.StackTrace; /** * This tabset contains most of the core functionality of the entire GUI. It has * one tab per dataset defined, plus an overview tab which displays an overview * of all the datasets in the mart. It handles all changes to any of the * datasets in the mart, and handles the assignment of {@link DiagramContext}s * to the various {@link Diagram}s inside it, including the schema tabset. * * @author Richard Holland <holland@ebi.ac.uk> * @version $Revision: 1.170 $, $Date: 2008-03-20 11:13:08 $, modified by * $Author: rh4 $ * @since 0.5 */ public class DataSetTabSet extends JTabbedPane { private static final long serialVersionUID = 1; private static final int DEFAULT_BIG_TABLE_SIZE = 100000000; private AllDataSetsDiagram allDataSetsDiagram; private final Map datasetToDiagram = new HashMap(); private MartTab martTab; // Make a listener which knows how to handle masking and // renaming. private final PropertyChangeListener renameListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { final DataSet ds = (DataSet) evt.getSource(); if (evt.getPropertyName().equals("name")) { // Rename in diagram set. DataSetTabSet.this.datasetToDiagram.put(evt.getNewValue(), DataSetTabSet.this.datasetToDiagram.remove(evt .getOldValue())); DataSetTabSet.this.setTitleAt(DataSetTabSet.this .indexOfTab((String) evt.getOldValue()), (String) evt .getNewValue()); } else if (evt.getPropertyName().equals("masked")) { // For masks, if unmasking, add a tab, otherwise // remove the tab. final boolean masked = ((Boolean) evt.getNewValue()) .booleanValue(); if (masked) DataSetTabSet.this.removeDataSetTab(ds.getName(), true); else DataSetTabSet.this.addDataSetTab(ds, false); } } }; // Listen to changes on tabs. private final PropertyChangeListener tabListener = new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent evt) { // Listen to masked schema and rename // schema events on each new schema added // regardless of tab presence. // Mass change. Copy to prevent concurrent mods. final Set oldDSs = new HashSet(DataSetTabSet.this.datasetToDiagram .keySet()); for (final Iterator i = DataSetTabSet.this.martTab.getMart() .getDataSets().values().iterator(); i.hasNext();) { final DataSet ds = (DataSet) i.next(); if (!oldDSs.remove(ds.getName())) { // Single-add. if (!ds.isMasked()) DataSetTabSet.this.addDataSetTab(ds, true); ds.addPropertyChangeListener("masked", DataSetTabSet.this.renameListener); ds.addPropertyChangeListener("name", DataSetTabSet.this.renameListener); } } for (final Iterator i = oldDSs.iterator(); i.hasNext();) DataSetTabSet.this.removeDataSetTab((String) i.next(), true); } }; /** * The constructor sets up a new set of tabs which represent all the * datasets in the given mart, plus an overview tab to represent all the * datasets in the mart. * * @param martTab * the mart tab to represent the datasets for. */ public DataSetTabSet(final MartTab martTab) { super(); // Remember the settings. this.martTab = martTab; // Add the datasets overview tab. This tab displays a diagram // in which all datasets appear. This diagram could be quite large, // so it is held inside a scrollpane. this.allDataSetsDiagram = new AllDataSetsDiagram(this.martTab); this.allDataSetsDiagram.setDiagramContext(new AllDataSetsContext( martTab)); final JScrollPane scroller = new JScrollPane(this.allDataSetsDiagram); scroller.getViewport().setBackground( this.allDataSetsDiagram.getBackground()); scroller.getHorizontalScrollBar().addAdjustmentListener( this.allDataSetsDiagram); scroller.getVerticalScrollBar().addAdjustmentListener( this.allDataSetsDiagram); this.addTab(Resources.get("multiDataSetOverviewTab"), scroller); // Populate the map to hold the relation between schemas and the // diagrams representing them. for (final Iterator i = martTab.getMart().getDataSets().values() .iterator(); i.hasNext();) { final DataSet ds = (DataSet) i.next(); // Don't add schemas which are initially masked. if (!ds.isMasked()) this.addDataSetTab(ds, false); ds.addPropertyChangeListener("masked", this.renameListener); ds.addPropertyChangeListener("name", this.renameListener); } // Listen to add/remove/mass change schema events. martTab.getMart().getDataSets().addPropertyChangeListener( this.tabListener); } /** * Works out which dataset tab is selected, and return it. * * @return the currently selected dataset tab, or <tt>null</tt> if none is * selected. */ public DataSet getSelectedDataSet() { if (this.getSelectedIndex() <= 0 || !this.isShowing()) return null; final DataSetDiagram selectedDiagram = (DataSetDiagram) ((JScrollPane) this .getSelectedComponent()).getViewport().getView(); return selectedDiagram.getDataSet(); } private synchronized void addDataSetTab(final DataSet dataset, final boolean selectDataset) { // Create the diagram to represent this dataset. final DataSetDiagram datasetDiagram = new DataSetDiagram(this.martTab, dataset); // Create a scroller to contain the diagram. final JScrollPane scroller = new JScrollPane(datasetDiagram); scroller.getViewport().setBackground(datasetDiagram.getBackground()); scroller.getHorizontalScrollBar().addAdjustmentListener(datasetDiagram); scroller.getVerticalScrollBar().addAdjustmentListener(datasetDiagram); // Add a tab containing the scroller, with the same name as the dataset. this.addTab(dataset.getName(), scroller); // Remember which diagram the dataset is connected with. this.datasetToDiagram.put(dataset.getName(), datasetDiagram); // Set the current context on the diagram to be the same as the // current context on this dataset tabset. datasetDiagram.setDiagramContext(new DataSetContext(this.martTab, dataset)); if (selectDataset) { // Fake a click on the dataset tab. this.setSelectedIndex(this.indexOfTab(dataset.getName())); this.martTab.selectDataSetEditor(); } } private String askUserForName(final String message, final String defaultResponse) { // Ask the user for a name. Use the default response // as the default value in the input field. String name = (String) JOptionPane.showInputDialog(null, message, Resources.get("questionTitle"), JOptionPane.QUESTION_MESSAGE, null, null, defaultResponse); // If they cancelled the request, return null. if (name == null) return null; // If they didn't enter anything, use the default response // as though they hadn't changed it. else if (name.trim().length() == 0) name = defaultResponse; // Return the response. return name; } private JPopupMenu getDataSetTabContextMenu(final DataSet dataset) { // Start with an empty menu. final JPopupMenu contextMenu = new JPopupMenu(); // This item allows the user to rename the dataset. final JMenuItem rename = new JMenuItem(Resources .get("renameDataSetTitle")); rename.setMnemonic(Resources.get("renameDataSetMnemonic").charAt(0)); rename.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent evt) { DataSetTabSet.this.requestRenameDataSet(dataset); } }); contextMenu.add(rename); // This item allows the user to remove the dataset from the mart. final JMenuItem close = new JMenuItem(Resources .get("removeDataSetTitle"), new ImageIcon(Resources .getResourceAsURL("cut.gif"))); close.setMnemonic(Resources.get("removeDataSetMnemonic").charAt(0)); close.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent evt) { DataSetTabSet.this.requestRemoveDataSet(dataset); } }); contextMenu.add(close); contextMenu.addSeparator(); // This item allows the user to replicate the dataset. final JMenuItem replicate = new JMenuItem(Resources .get("replicateDataSetTitle")); replicate.setMnemonic(Resources.get("replicateDataSetMnemonic").charAt( 0)); replicate.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent evt) { DataSetTabSet.this.requestReplicateDataSet(dataset); } }); contextMenu.add(replicate); // Return the menu. return contextMenu; } private synchronized void removeDataSetTab(final String datasetName, final boolean select) { // Work out the currently selected tab. final int currentTab = this.getSelectedIndex(); // Work out the tab index. final int tabIndex = this.indexOfTab(datasetName); // Remove the tab, and it's mapping from the dataset-to-tab map. this.remove(tabIndex); this.datasetToDiagram.remove(datasetName); if (select) // Fake a click on the last tab before this one to ensure // at least one tab remains visible and up-to-date. this.setSelectedIndex(currentTab == 0 ? 0 : Math.max(tabIndex - 1, 0)); } protected void processMouseEvent(final MouseEvent evt) { boolean eventProcessed = false; // Is it a right-click? if (evt.isPopupTrigger()) { // Where was the click? final int selectedIndex = this.indexAtLocation(evt.getX(), evt .getY()); if (selectedIndex >= 0) { // Work out which tab was selected and which diagram // is displayed in that tab. final Component selectedComponent = this .getComponentAt(selectedIndex); if (selectedComponent instanceof JScrollPane) { final Component selectedDiagram = ((JScrollPane) selectedComponent) .getViewport().getView(); if (selectedDiagram instanceof DataSetDiagram) { // Set the dataset diagram as the currently selected // one. this.setSelectedIndex(selectedIndex); // Work out the dataset inside the diagram. final DataSet dataset = ((DataSetDiagram) selectedDiagram) .getDataSet(); // Show the context-menu for the tab for this schema. this.getDataSetTabContextMenu(dataset).show(this, evt.getX(), evt.getY()); // We've handled the event so mark it as processed. eventProcessed = true; } } } } // Pass it on up if we're not interested. if (!eventProcessed) super.processMouseEvent(evt); } /** * Returns the mart tab that this dataset tabset is displaying the contents * of. * * @return the mart tab that this dataset tabset is viewing. */ public MartTab getMartTab() { return this.martTab; } /** * Request that the optimiser type used post-construction of a dataset be * changed. * * @param dataset * the dataset we are working with. * @param type * the type of optimiser to use post-construction. */ public void requestChangeOptimiserType(final DataSet dataset, final DataSetOptimiserType type) { new LongProcess() { public void run() { Transaction.start(false); dataset.setDataSetOptimiserType(type); Transaction.end(); } }.start(); } /** * Request that all changes on this dataset table associated with this * target are accepted. See {@link DataSetTable#acceptChanges(Table)}. * * @param dsTable * the dataset table to work with. * @param targetTable * the (optional) target table to accept changes from. */ public void requestAcceptAll(final DataSetTable dsTable, final Table targetTable) { new LongProcess() { public void run() { Transaction.start(true); dsTable.acceptChanges(targetTable); Transaction.end(); } }.start(); } /** * Request that all changes on this dataset table associated with this * target are rejected. See {@link DataSetTable#rejectChanges(Table)}. * * @param dsTable * the dataset table to work with. * @param targetTable * the (optional) target table to reject changes from. */ public void requestRejectAll(final DataSetTable dsTable, final Table targetTable) { new LongProcess() { public void run() { Transaction.start(true); dsTable.rejectChanges(targetTable); Transaction.end(); } }.start(); } /** * Request that all changes on this dataset associated with this target are * accepted. * * @param ds * the dataset to work with. * @param targetTable * the (optional) target table to accept changes from. */ public void requestAcceptAll(final DataSet ds, final Table targetTable) { new LongProcess() { public void run() { Transaction.start(true); for (final Iterator i = ds.getTables().values().iterator(); i .hasNext();) ((DataSetTable) i.next()).acceptChanges(targetTable); Transaction.end(); } }.start(); } /** * Request that all changes on this dataset associated with this target are * rejected. * * @param ds * the dataset to work with. * @param targetTable * the (optional) target table to reject changes from. */ public void requestRejectAll(final DataSet ds, final Table targetTable) { new LongProcess() { public void run() { Transaction.start(true); for (final Iterator i = ds.getTables().values().iterator(); i .hasNext();) ((DataSetTable) i.next()).rejectChanges(targetTable); Transaction.end(); } }.start(); } /** * On a request to create DDL for the current dataset, open the DDL creation * window with all this dataset selected. * * @param dataset * the dataset to show the dialog for. */ public void requestCreateDDL(final DataSet dataset) { // If it is a partition table dataset, refuse. if (dataset.isPartitionTable()) JOptionPane.showMessageDialog(this, Resources .get("noDDLForPartitionTable"), Resources .get("messageTitle"), JOptionPane.INFORMATION_MESSAGE); // Open the DDL creation dialog and let it do it's stuff. else (new SaveDDLDialog( this.martTab, Collections.singleton(dataset), this.martTab.getPartitionViewSelection() == null ? this.martTab .getAllSchemaPrefixes() : Collections.singleton(this.martTab .getPartitionViewSelection()), SaveDDLDialog.VIEW_DDL)).setVisible(true); } /** * Ask that an explanation dialog be opened that explains how the given * dataset table was constructed. * * @param dsTable * the dataset table that needs to be explained. */ public void requestExplainTable(final DataSetTable dsTable) { ExplainTableDialog.showTableExplanation(this.martTab, dsTable); } /** * Ask that an explanation dialog be opened that explains how the given * dataset was constructed. * * @param dataset * the dataset that needs to be explained. */ public void requestExplainDataSet(final DataSet dataset) { ExplainDataSetDialog.showDataSetExplanation(this.martTab, dataset); } /** * Requests that the dataset be made invisible. * * @param dataset * the dataset to make invisible. * @param invisible * whether to do it. */ public void requestInvisibleDataSet(final DataSet dataset, final boolean invisible) { new LongProcess() { public void run() { Transaction.start(false); dataset.setInvisible(invisible); Transaction.end(); } }.start(); } /** * Run the partition dimension wizard. * * @param dim * the dimension to apply the wizard to. */ public void requestDimensionPartitionWizard(final DataSetTable dim) { // Create wizard dialog (specify dimension version) try { PartitionTableDialog.showForDimension(this.getMartTab(), dim); } catch (final PartitionException pe) { StackTrace.showStackTrace(pe); } } /** * Run the partition dataset wizard. * * @param ds * the dataset to apply the wizard to. */ public void requestDataSetPartitionWizard(final DataSet ds) { try { PartitionTableDialog.showForDataSet(this.getMartTab(), ds); } catch (final PartitionException pe) { StackTrace.showStackTrace(pe); } } /** * Requests that a column be masked. * * @param ds * the dataset we are working with. * @param column * the column to mask. * @param masked * whether to mask it. */ public void requestMaskColumn(final DataSet ds, final DataSetColumn column, final boolean masked) { this.requestMaskColumns(ds, Collections.singleton(column), masked); } /** * Requests that a set of columns be masked. * * @param ds * the dataset we are working with. * @param columns * the columns to mask. * @param masked * whether to mask it. */ public void requestMaskColumns(final DataSet ds, final Collection columns, final boolean masked) { new LongProcess() { public void run() throws Exception { try { Transaction.start(false); for (final Iterator i = columns.iterator(); i.hasNext();) ((DataSetColumn) i.next()).setColumnMasked(masked); } finally { Transaction.end(); } } }.start(); } /** * Requests that a column be indexed. * * @param ds * the dataset we are working with. * @param column * the column to index. * @param index * whether to index it. */ public void requestIndexColumn(final DataSet ds, final DataSetColumn column, final boolean index) { new LongProcess() { public void run() { Transaction.start(false); column.setColumnIndexed(index); Transaction.end(); } }.start(); } /** * Requests that a table be made distinct. * * @param ds * the dataset we are working with. * @param dst * the table to make distinct. * @param distinct * make it distinct? */ public void requestDistinctTable(final DataSet ds, final DataSetTable dst, final boolean distinct) { new LongProcess() { public void run() { Transaction.start(false); dst.setDistinctTable(distinct); Transaction.end(); } }.start(); } /** * Requests that a column be split optimisered. * * @param col * the column we are working with. */ public void requestSplitOptimiserColumn(final DataSetColumn col) { // Work out if it is already compounded. final DataSetTable dst = col.getDataSetTable(); final SplitOptimiserColumnDef split = col.getSplitOptimiserColumn(); final boolean isSplit = split != null; // Pop up a dialog and update 'compound'. final SplitOptimiserColumnDialog dialog = new SplitOptimiserColumnDialog( isSplit, split, dst.getColumns()); dialog.setLocationRelativeTo(null); dialog.setVisible(true); final SplitOptimiserColumnDef newSplit = dialog .getSplitOptimiserColumnDef(); dialog.dispose(); new LongProcess() { public void run() throws Exception { try { Transaction.start(false); // Do the work. col.setSplitOptimiserColumn(newSplit); } finally { Transaction.end(); } } }.start(); } /** * Requests that a table be hide-masked. * * @param ds * the dataset we are working with. * @param dst * the table to make hide-masked. * @param tableHideMasked * whether to do it. */ public void requestTableHideMasked(final DataSet ds, final DataSetTable dst, final boolean tableHideMasked) { new LongProcess() { public void run() { Transaction.start(false); dst.setTableHideMasked(tableHideMasked); Transaction.end(); } }.start(); } /** * Requests that a table be not optimisered. * * @param ds * the dataset we are working with. * @param dst * the table to make not optimisered. * @param skipOptimiser * whether to do it. */ public void requestSkipOptimiser(final DataSet ds, final DataSetTable dst, final boolean skipOptimiser) { new LongProcess() { public void run() { Transaction.start(false); dst.setSkipOptimiser(skipOptimiser); Transaction.end(); } }.start(); } /** * Requests that a table be not index optimisered. * * @param ds * the dataset we are working with. * @param dst * the table to make not index optimisered. * @param skipIndexOptimiser * whether to do it. */ public void requestSkipIndexOptimiser(final DataSet ds, final DataSetTable dst, final boolean skipIndexOptimiser) { new LongProcess() { public void run() { Transaction.start(false); dst.setSkipIndexOptimiser(skipIndexOptimiser); Transaction.end(); } }.start(); } /** * Requests that a table be made no-left-join. * * @param ds * the dataset we are working with. * @param dst * the table to make no-left-join. * @param noLeftJoin * whether to do it. */ public void requestNoFinalLeftJoinTable(final DataSet ds, final DataSetTable dst, final boolean noLeftJoin) { new LongProcess() { public void run() { Transaction.start(false); dst.setNoFinalLeftJoin(noLeftJoin); Transaction.end(); } }.start(); } /** * Requests that a dimension be masked. * * @param ds * the dataset we are working with. * @param dim * the dimension to mask. * @param masked * whether to mask it. */ public void requestMaskDimension(final DataSet ds, final DataSetTable dim, final boolean masked) { new LongProcess() { public void run() throws Exception { try { Transaction.start(false); dim.setDimensionMasked(masked); } finally { Transaction.end(); } } }.start(); } /** * Requests that a loopback wizard be opened. * * @param ds * the dataset we are working with. * @param dim * the dimension to work with. */ public void requestLoopbackWizard(final DataSet ds, final DataSetTable dim) { LoopbackWizardDialog dialog = null; try { // Ask user for info. dialog = new LoopbackWizardDialog(dim); dialog.setVisible(true); // Cancelled? if (dialog.isCancelled()) return; // Get user input. final Table loopbackTable = dialog.getLoopbackTable(); final Column diffCol = dialog.getDiffColumn(); // Make the changes. new LongProcess() { public void run() throws Exception { try { Transaction.start(false); dim.runLoopbackWizard(loopbackTable, diffCol); } finally { Transaction.end(); } } }.start(); } catch (final Throwable t) { StackTrace.showStackTrace(t); } finally { if (dialog != null) dialog.dispose(); } } /** * Asks that a dimension be merged. * * @param ds * the dataset we are working with. * @param dst * the dimension to merge. * @param merged * whether to merge it. */ public void requestMergeDimension(final DataSet ds, final DataSetTable dst, final boolean merged) { new LongProcess() { public void run() { Transaction.start(false); dst.getFocusRelation().setMergeRelation(ds, merged); Transaction.end(); } }.start(); } /** * Asks that a relation be recursively subclassed. * * @param ds * the dataset we are working with. * @param dst * the table to recursively subclass. */ public void requestRecurseSubclass(final DataSet ds, final DataSetTable dst) { // Work out if it is already compounded. final Relation relation = dst.getFocusRelation(); if (!relation.isSubclassRelation(ds)) return; final CompoundRelationDefinition interimDef = relation .getCompoundRelation(ds); final CompoundRelationDefinition def = interimDef == null ? new CompoundRelationDefinition( 1, false) : interimDef; // Pop up a dialog and update 'compound'. final CompoundRelationDialog dialog = new CompoundRelationDialog(def, Resources.get("recurseSubclassDialogTitle"), Resources .get("recurseSubclassNLabel"), true, ds.getMart() .getPartitionColumnNames()); dialog.setLocationRelativeTo(null); dialog.setVisible(true); final int newN = dialog.getArity(); final boolean newParallel = dialog.getParallel(); dialog.dispose(); // Skip altogether if no change. if (newN == def.getN() && newParallel == def.isParallel()) return; new LongProcess() { public void run() { Transaction.start(false); if (newN <= 1) // Uncompound the relation. relation.setCompoundRelation(ds, null); else { // Compound the relation. def.setN(newN); def.setParallel(newParallel); if (relation.getCompoundRelation(ds) == null) relation.setCompoundRelation(ds, def); } Transaction.end(); } }.start(); } /** * Asks that a dimension be replicated by compounding a relation. * * @param ds * the dataset we are working with. * @param dst * the table to replicate. */ public void requestReplicateDimension(final DataSet ds, final DataSetTable dst) { // Work out if it is already compounded. final Relation relation = dst.getFocusRelation(); final CompoundRelationDefinition interimDef = relation .getCompoundRelation(ds); final CompoundRelationDefinition def = interimDef == null ? new CompoundRelationDefinition( 1, false) : interimDef; // Pop up a dialog and update 'compound'. final CompoundRelationDialog dialog = new CompoundRelationDialog(def, Resources.get("replicateDimensionDialogTitle"), Resources .get("replicateDimensionNLabel"), true, ds.getMart() .getPartitionColumnNames()); dialog.setLocationRelativeTo(null); dialog.setVisible(true); final int newN = dialog.getArity(); final boolean newParallel = dialog.getParallel(); dialog.dispose(); // Skip altogether if no change. if (newN == def.getN() && newParallel == def.isParallel()) return; new LongProcess() { public void run() { Transaction.start(false); if (newN <= 1) // Uncompound the relation. relation.setCompoundRelation(ds, null); else { // Compound the relation. def.setN(newN); def.setParallel(newParallel); if (relation.getCompoundRelation(ds) == null) relation.setCompoundRelation(ds, def); } Transaction.end(); } }.start(); } /** * Asks that a relation be unrolled. * * @param ds * the dataset to work with. * @param dim * the dimension to make unrolled. */ public void requestUnrolledDimension(final DataSet ds, final DataSetTable dim) { // Work out if it is already directional. final Relation relation = dim.getFocusRelation(); final UnrolledRelationDefinition def = relation.getUnrolledRelation(ds); try { Relation otherRel = null; for (final Iterator i = relation.getOneKey().getRelations() .iterator(); i.hasNext() && otherRel == null;) { final Relation candRel = (Relation) i.next(); if (candRel.equals(this)) continue; if (!candRel.isOneToMany()) continue; if (candRel.getManyKey().getTable().equals( relation.getManyKey().getTable()) && candRel.isMergeRelation(ds)) otherRel = candRel; } if (otherRel == null) throw new ValidationException(Resources .get("cannotUnrollWithoutMerge")); } catch (final ValidationException e) { StackTrace.showStackTrace(e); return; } // Pop up a dialog and update 'direction'. final UnrolledRelationDialog dialog = new UnrolledRelationDialog(def, relation); dialog.setLocationRelativeTo(null); dialog.setVisible(true); new LongProcess() { public void run() { Transaction.start(false); if (dialog.getChosenColumn() == null) relation.setUnrolledRelation(ds, null); else if (def != null) { def.setNameColumn(dialog.getChosenColumn()); def.setReversed(dialog.isReversed()); } else relation.setUnrolledRelation(ds, new UnrolledRelationDefinition(dialog .getChosenColumn(), dialog.isReversed())); dialog.dispose(); Transaction.end(); } }.start(); } /** * Asks that a relation be compounded. * * @param ds * the dataset we are working with. * @param dst * the table to work with. * @param relation * the schema relation to compound. */ public void requestCompoundRelation(final DataSet ds, final DataSetTable dst, final Relation relation) { // Work out if it is already compounded. final CompoundRelationDefinition interimDef = dst == null ? relation .getCompoundRelation(ds) : relation.getCompoundRelation(ds, dst .getName()); final CompoundRelationDefinition def = interimDef == null ? new CompoundRelationDefinition( 1, false) : interimDef; // Pop up a dialog and update 'compound'. final CompoundRelationDialog dialog = new CompoundRelationDialog(def, Resources.get("compoundRelationDialogTitle"), Resources .get("compoundRelationNLabel"), false, ds.getMart() .getPartitionColumnNames()); dialog.setLocationRelativeTo(null); dialog.setVisible(true); final int newN = dialog.getArity(); final boolean newParallel = dialog.getParallel(); dialog.dispose(); // Skip altogether if no change. if (newN == def.getN() && newParallel == def.isParallel()) return; new LongProcess() { public void run() { Transaction.start(false); // Do the work. if (newN <= 1) { // Uncompound the relation. if (dst != null) relation.setCompoundRelation(ds, dst.getName(), null); else relation.setCompoundRelation(ds, null); } else { // Compound the relation. def.setN(newN); def.setParallel(newParallel); if (dst != null) { if (relation.getCompoundRelation(ds, dst.getName()) == null) relation .setCompoundRelation(ds, dst.getName(), def); } else if (relation.getCompoundRelation(ds) == null) relation.setCompoundRelation(ds, def); } Transaction.end(); } }.start(); } private int askUserForCompoundRelationIndex(final DataSet dataset, final DataSetTable dsTable, final Relation relation) { // Is the relation compound? If not, return 0. if (relation.getCompoundRelation(dataset) == null && (dsTable == null || relation.getCompoundRelation(dataset, dsTable.getName()) == null)) return RealisedRelation.NO_ITERATION; // Work out possible options. final int maxIndex = (dsTable == null ? relation .getCompoundRelation(dataset) : relation.getCompoundRelation( dataset, dsTable.getName())).getN(); final Integer[] options = new Integer[maxIndex]; for (int i = 0; i < options.length; i++) options[i] = new Integer(i + 1); // Return -1 if cancelled. final Integer selIndex = (Integer) JOptionPane.showInputDialog(null, Resources.get("compoundRelationIndex"), Resources .get("questionTitle"), JOptionPane.QUESTION_MESSAGE, null, options, options[0]); // If they cancelled the request, return -2. if (selIndex == null) return -2; else return selIndex.intValue() - 1; } /** * Asks that a relation be restricted. * * @param dataset * the dataset we are working with. * @param dsTable * the table to work with. * @param relation * the schema relation to restrict. * @param iteration * the iteration to apply this to, or * {@link RealisedRelation#NO_ITERATION} to prompt the user. */ public void requestRestrictRelation(final DataSet dataset, final DataSetTable dsTable, final Relation relation, final int iteration) { // Get index offset into compound relation. final int index = iteration != RealisedRelation.NO_ITERATION ? iteration : relation.getCompoundRelation(dataset, dsTable.getName()) != null ? this .askUserForCompoundRelationIndex(dataset, dsTable, relation) : RealisedRelation.NO_ITERATION; // Cancelled? if (index == -2) return; final RestrictedRelationDialog dialog = new RestrictedRelationDialog( relation, relation.getRestrictRelation(dataset, dsTable .getName(), index)); dialog.setVisible(true); // Cancelled? if (dialog.getCancelled()) return; // Get updated details from the user. final Map aliasesLHS = dialog.getLHSColumnAliases(); final Map aliasesRHS = dialog.getRHSColumnAliases(); final String expression = dialog.getExpression(); dialog.dispose(); new LongProcess() { public void run() { Transaction.start(false); RestrictedRelationDefinition def = relation .getRestrictRelation(dataset, dsTable.getName(), index); if (def == null) { def = new RestrictedRelationDefinition(expression, aliasesLHS, aliasesRHS); relation.setRestrictRelation(dataset, dsTable.getName(), def, index); // Make it alternative join if dataset, // or NOT alternative if main/subclass. relation.setAlternativeJoin(dataset, dsTable.getName(), dsTable.getType() .equals(DataSetTableType.DIMENSION)); } else { def.setExpression(expression); def.getLeftAliases().clear(); def.getLeftAliases().putAll(aliasesLHS); def.getRightAliases().clear(); def.getRightAliases().putAll(aliasesRHS); } Transaction.end(); } }.start(); } /** * Asks for a relation restriction to be removed. * * @param dataset * the dataset we are working with. * @param dsTable * the table to work with. * @param relation * the relation to unrestrict. * @param iteration * the iteration to apply this to, or * {@link RealisedRelation#NO_ITERATION} to prompt the user. */ public void requestUnrestrictRelation(final DataSet dataset, final DataSetTable dsTable, final Relation relation, final int iteration) { // Get index offset into compound relation. final int index = iteration != RealisedRelation.NO_ITERATION ? iteration : relation.getCompoundRelation(dataset, dsTable.getName()) != null ? this .askUserForCompoundRelationIndex(dataset, dsTable, relation) : RealisedRelation.NO_ITERATION; // Cancelled? if (index == -2) return; new LongProcess() { public void run() { Transaction.start(false); relation.setRestrictRelation(dataset, dsTable.getName(), null, index); // Make it alternative join if dataset, // or NOT alternative if main/subclass. relation.setAlternativeJoin(dataset, dsTable.getName(), !dsTable.getType().equals(DataSetTableType.DIMENSION)); Transaction.end(); } }.start(); } /** * Asks that a relation be alternative-joined. * * @param ds * the dataset we are working with. * @param dst * the table to work with. * @param relation * the schema relation to alternative-join. * @param join * whether to do it. */ public void requestAlternativeJoin(final DataSet ds, final DataSetTable dst, final Relation relation, final boolean join) { new LongProcess() { public void run() { Transaction.start(false); relation.setAlternativeJoin(ds, dst.getName(), join); Transaction.end(); } }.start(); } /** * Asks that a relation be transform-start. * * @param ds * the dataset we are working with. * @param dst * the table to work with. * @param table * the schema table to transform-start. * @param doIt * whether to set the flag. */ public void requestTransformStart(final DataSet ds, final DataSetTable dst, final Table table, final boolean doIt) { new LongProcess() { public void run() { Transaction.start(false); for (final Iterator i = dst.getIncludedTables().iterator(); i .hasNext();) { final Table tbl = (Table) i.next(); tbl.setTransformStart(ds, dst.getName(), tbl.equals(table) && doIt); } Transaction.end(); } }.start(); } /** * Asks that a relation be masked. * * @param ds * the dataset we are working with. * @param dst * the table to work with. * @param relation * the schema relation to mask. * @param masked * whether to mask it. */ public void requestMaskRelation(final DataSet ds, final DataSetTable dst, final Relation relation, final boolean masked) { new LongProcess() { public void run() { Transaction.start(false); if (dst != null) relation.setMaskRelation(ds, dst.getName(), masked); else relation.setMaskRelation(ds, masked); Transaction.end(); } }.start(); } /** * Asks that a relation be loopbacked. * * @param ds * the dataset we are working with. * @param dst * the table to work with. * @param relation * the schema relation to loopback. */ public void requestLoopbackRelation(final DataSet ds, final DataSetTable dst, final Relation relation) { // Work out if it is already compounded. final Column loopedCol = dst == null ? relation.getLoopbackRelation(ds) : relation.getLoopbackRelation(ds, dst.getName()); final boolean isLooped = loopedCol != null; // Pop up a dialog and update 'compound'. final LoopbackRelationDialog dialog = new LoopbackRelationDialog( isLooped, loopedCol, relation.getManyKey().getTable() .getColumns().values()); dialog.setLocationRelativeTo(null); dialog.setVisible(true); final boolean newIsLooped = dialog.isLoopback(); final Column newLoopedCol = newIsLooped ? dialog .getLoopbackDiffColumn() : null; dialog.dispose(); // Skip altogether if no change. if (newLoopedCol == loopedCol && newIsLooped) return; new LongProcess() { public void run() throws Exception { try { Transaction.start(false); // Do the work. if (dst != null) relation.setLoopbackRelation(ds, dst.getName(), newLoopedCol); else relation.setLoopbackRelation(ds, newLoopedCol); } finally { Transaction.end(); } } }.start(); } /** * Update the counts on all applications of this partition table. */ public void requestUpdateAllPTCounts() { // In the background, do the synchronisation. new LongProcess() { public void run() throws Exception { Transaction.start(false); for (final Iterator k = DataSetTabSet.this.getMartTab() .getMart().getPartitionTables().iterator(); k.hasNext();) for (final Iterator i = ((PartitionTable) k.next()) .getAllApplications().values().iterator(); i .hasNext();) for (final Iterator j = ((Map) i.next()).values() .iterator(); j.hasNext();) try { final Object ref = ((WeakReference) j.next()) .get(); if (ref != null) ((PartitionTableApplication) ref) .syncCounts(); } catch (final Exception e) { SwingUtilities.invokeLater(new Runnable() { public void run() { StackTrace.showStackTrace(e); } }); } Transaction.end(); } }.start(); } /** * Update the counts on all applications of this partition table. * * @param pt * the partition table to update counts on. */ public void requestUpdatePTCounts(final PartitionTable pt) { // In the background, do the synchronisation. new LongProcess() { public void run() throws Exception { Transaction.start(false); for (final Iterator i = pt.getAllApplications().values() .iterator(); i.hasNext();) for (final Iterator j = ((Map) i.next()).values() .iterator(); j.hasNext();) try { final Object ref = ((WeakReference) j.next()).get(); if (ref != null) ((PartitionTableApplication) ref).syncCounts(); } catch (final Exception e) { SwingUtilities.invokeLater(new Runnable() { public void run() { StackTrace.showStackTrace(e); } }); } Transaction.end(); } }.start(); } /** * Pop up a dialog explaining the current partition table conversion status * of this dataset. The dialog will do any updating necessary and return a * flag indicating this. * * @param ds * the dataset to modify partition table info for. */ public void requestConvertPartitionTable(final DataSet ds) { if (ds.isConvertableToPartitionTable()) new PartitionTableDialog(this.getMartTab(), ds).setVisible(true); else StackTrace.showStackTrace(new PartitionException(Resources .get("partitionTypesLimited"))); } /** * Asks that a dataset be (un)masked. * * @param ds * the dataset we are working with. * @param masked * mask it? */ public void requestMaskDataSet(final DataSet ds, final boolean masked) { new LongProcess() { public void run() { Transaction.start(false); ds.setMasked(masked); Transaction.end(); } }.start(); } /** * Asks that all datasets be (un)masked. * * @param masked * mask it? */ public void requestMaskAllDataSets(final boolean masked) { new LongProcess() { public void run() { Transaction.start(false); for (final Iterator i = DataSetTabSet.this.getMartTab() .getMart().getDataSets().values().iterator(); i .hasNext();) ((DataSet) i.next()).setMasked(masked); Transaction.end(); } }.start(); } /** * Asks that a relation be forced. * * @param ds * the dataset we are working with. * @param dst * the table to work with. * @param relation * the schema relation to force. * @param force * whether to force it. */ public void requestForceRelation(final DataSet ds, final DataSetTable dst, final Relation relation, final boolean force) { new LongProcess() { public void run() { Transaction.start(false); if (dst != null) relation.setForceRelation(ds, dst.getName(), force); else relation.setForceRelation(ds, force); Transaction.end(); } }.start(); } /** * Asks that all relations on a table be masked. * * @param ds * the dataset we are working with. * @param dst * the table to work with. * @param table * the schema table to mask all relations for. */ public void requestMaskAllRelations(final DataSet ds, final DataSetTable dst, final Table table) { new LongProcess() { public void run() { Transaction.start(false); for (final Iterator i = table.getRelations().iterator(); i .hasNext();) { final Relation rel = (Relation) i.next(); if (dst != null) rel.setMaskRelation(ds, dst.getName(), true); else rel.setMaskRelation(ds, true); } Transaction.end(); } }.start(); } /** * Asks for a expression column to be modified. * * @param dsTable * the table to work with. * @param column * the existing expression. */ public void requestExpressionColumn(final DataSetTable dsTable, final ExpressionColumn column) { final ExpressionColumnDialog dialog = new ExpressionColumnDialog( dsTable, column == null ? null : column.getDefinition(), column); dialog.setVisible(true); // Cancelled? if (dialog.getCancelled()) return; // Get updated details from the user. final Map aliases = dialog.getColumnAliases(); final String expression = dialog.getExpression(); final boolean groupBy = dialog.getGroupBy(); dialog.dispose(); new LongProcess() { public void run() { Transaction.start(false); ExpressionColumn col = column; if (col == null) { final String name = dsTable.getNextExpressionColumn(); col = new ExpressionColumn(name, dsTable, new ExpressionColumnDefinition(expression, aliases, groupBy, name)); dsTable.getColumns().put(col.getName(), col); } else { col.getDefinition().getAliases().clear(); col.getDefinition().getAliases().putAll(aliases); col.getDefinition().setExpression(expression); col.getDefinition().setGroupBy(groupBy); } Transaction.end(); } }.start(); } /** * Asks for a expression column to be removed. * * @param dsTable * the table to work with. * @param column * the existing expression. */ public void requestRemoveExpressionColumn(final DataSetTable dsTable, final ExpressionColumn column) { new LongProcess() { public void run() { Transaction.start(false); dsTable.getColumns().remove(column.getName()); Transaction.end(); } }.start(); } /** * Asks for a table restriction to be modified. * * @param dataset * the dataset we are working with. * @param dsTable * the table to work with. * @param table * the table to modify the restriction for. */ public void requestRestrictTable(final DataSet dataset, final DataSetTable dsTable, final Table table) { final RestrictedTableDialog dialog = new RestrictedTableDialog(table, table.getRestrictTable(dataset, dsTable.getName())); dialog.setVisible(true); // Cancelled? if (dialog.getCancelled()) return; // Get updated details from the user. final Map aliases = dialog.getColumnAliases(); final String expression = dialog.getExpression(); dialog.dispose(); new LongProcess() { public void run() { Transaction.start(false); RestrictedTableDefinition def = table.getRestrictTable(dataset, dsTable.getName()); if (def != null) { def.getAliases().clear(); def.getAliases().putAll(aliases); def.setExpression(expression); } else { def = new RestrictedTableDefinition(expression, aliases); table.setRestrictTable(dataset, dsTable.getName(), def); // Make it alternative join if dataset, // or NOT alternative if main/subclass. for (final Iterator i = dsTable.getIncludedRelations() .iterator(); i.hasNext();) { final Relation relation = (Relation) i.next(); if (relation.getKeyForTable(table) != null) relation.setAlternativeJoin(dataset, dsTable .getName(), dsTable.getType().equals( DataSetTableType.DIMENSION)); } } Transaction.end(); } }.start(); } /** * Asks for a table bigness to be modified. * * @param dataset * the dataset we are working with. * @param dsTable * the table to work with. * @param table * the table to modify the bigness for. */ public void requestBigTable(final DataSet dataset, final DataSetTable dsTable, final Table table) { final int currVal = dsTable == null ? table.getBigTable(dataset) : table.getBigTable(dataset, dsTable.getName()); // Get new val from user. final String newValStr = (String) JOptionPane.showInputDialog(null, Resources.get("bigTableSize"), Resources.get("questionTitle"), JOptionPane.QUESTION_MESSAGE, null, null, "" + (currVal == 0 ? DataSetTabSet.DEFAULT_BIG_TABLE_SIZE : currVal)); // Cancelled? if (newValStr == null) return; // Get updated details from the user. final int newVal; try { newVal = "".equals(newValStr) ? 0 : Integer.parseInt(newValStr); } catch (final NumberFormatException e) { StackTrace.showStackTrace(e); return; } if (newVal == currVal) return; // Change it. new LongProcess() { public void run() { Transaction.start(false); if (dsTable != null) table.setBigTable(dataset, dsTable.getName(), newVal); else table.setBigTable(dataset, newVal); Transaction.end(); } }.start(); } /** * Asks the user if they are sure they want to remove all datasets, then * removes them from the mart (and the tabs) if they agree. */ public void requestRemoveAllDataSets() { // Confirm the decision first. final int choice = JOptionPane.showConfirmDialog(null, Resources .get("confirmDelAllDatasets"), Resources.get("questionTitle"), JOptionPane.YES_NO_OPTION); // Refuse to do it if they said no. if (choice != JOptionPane.YES_OPTION) return; new LongProcess() { public void run() { Transaction.start(false); DataSetTabSet.this.martTab.getMart().getDataSets().clear(); Transaction.end(); } }.start(); } /** * Asks the user if they are sure they want to remove the dataset, then * removes it from the mart (and the tabs) if they agree. * * @param dataset * the dataset to remove. */ public void requestRemoveDataSet(final DataSet dataset) { // Confirm the decision first. final int choice = JOptionPane.showConfirmDialog(null, Resources .get("confirmDelDataset"), Resources.get("questionTitle"), JOptionPane.YES_NO_OPTION); // Refuse to do it if they said no. if (choice != JOptionPane.YES_OPTION) return; new LongProcess() { public void run() { Transaction.start(false); DataSetTabSet.this.martTab.getMart().getDataSets().remove( dataset.getOriginalName()); Transaction.end(); } }.start(); } /** * Asks for a table restriction to be removed. * * @param dataset * the dataset we are working with. * @param dsTable * the table to work with. * @param table * the table to unrestrict. */ public void requestUnrestrictTable(final DataSet dataset, final DataSetTable dsTable, final Table table) { new LongProcess() { public void run() { Transaction.start(false); table.setRestrictTable(dataset, dsTable.getName(), null); // Make it alternative join if dataset, // or NOT alternative if main/subclass. for (final Iterator i = dsTable.getIncludedRelations() .iterator(); i.hasNext();) { final Relation relation = (Relation) i.next(); if (relation.getKeyForTable(table) != null) relation.setAlternativeJoin(dataset, dsTable.getName(), !dsTable.getType().equals( DataSetTableType.DIMENSION)); } Transaction.end(); } }.start(); } /** * Renames a dataset, then renames the tab too. * * @param dataset * the dataset to rename. */ public void requestRenameDataSet(final DataSet dataset) { // Ask user for the new name. this.requestRenameDataSet(dataset, this.askUserForName(Resources .get("requestDataSetName"), dataset.getName())); } /** * Renames a dataset, then renames the tab too. * * @param dataset * the dataset to rename. * @param name * the new name to give it. */ public void requestRenameDataSet(final DataSet dataset, final String name) { // If the new name is null (user cancelled), or has // not changed, don't rename it. final String newName = name == null ? "" : name.trim(); if (newName.length() == 0) return; new LongProcess() { public void run() { Transaction.start(false); dataset.setName(newName); Transaction.end(); } }.start(); } /** * Renames a column, after prompting the user to enter a new name. By * default, the existing name is used. If the name entered is blank or * matches the existing name, no change is made. * * @param dsColumn * the column to rename. */ public void requestRenameDataSetColumn(final DataSetColumn dsColumn) { // Ask user for the new name. this.requestRenameDataSetColumn(dsColumn, this.askUserForName(Resources .get("requestDataSetColumnName"), dsColumn.getModifiedName())); } /** * Renames a dataset column to have the given name. * * @param dsColumn * the column to rename. * @param name * the new name to give it. */ public void requestRenameDataSetColumn(final DataSetColumn dsColumn, final String name) { // Ask user for the new name. final String newName = name == null ? "" : name.trim(); // If the new name is null (user cancelled), or has // not changed, don't rename it. if (newName.length() == 0) return; new LongProcess() { public void run() throws Exception { try { Transaction.start(false); dsColumn.setColumnRename(newName, true); } finally { Transaction.end(); } } }.start(); } /** * Renames a table, after prompting the user to enter a new name. By * default, the existing name is used. If the name entered is blank or * matches the existing name, no change is made. * * @param dsTable * the table to rename. */ public void requestRenameDataSetTable(final DataSetTable dsTable) { // Ask user for the new name. this.requestRenameDataSetTable(dsTable, this.askUserForName(Resources .get("requestDataSetTableName"), dsTable.getModifiedName())); } /** * Renames a table. * * @param dsTable * the table to rename. * @param name * the new name to give it. */ public void requestRenameDataSetTable(final DataSetTable dsTable, final String name) { // If the new name is null (user cancelled), or has // not changed, don't rename it. final String newName = name == null ? "" : name.trim(); if (newName.length() == 0) return; new LongProcess() { public void run() { Transaction.start(false); dsTable.setTableRename(newName); Transaction.end(); } }.start(); } /** * Requests that a relation be flagged as a subclass relation. * * @param ds * the dataset we are working with. * @param relation * the relation to subclass. * @param subclassed * whether to do it. */ public void requestSubclassRelation(final DataSet ds, final Relation relation, final boolean subclassed) { new LongProcess() { public void run() throws Exception { try { Transaction.start(false); relation.setSubclassRelation(ds, subclassed); } finally { Transaction.end(); } } }.start(); } /** * Given a table, suggest a possible unrolled ontology dataset. Refuse if * the table does not have at least two 1:M relations leading from its PK to * FKs on the same second table. This will modify the source schema to match * the selected columns. * * @param nTable * the table to suggest datasets for. */ public void requestSuggestUnrolledDataSets(final Table nTable) { SuggestUnrolledDataSetDialog dialog = null; try { // Ask user for candidate (dialog will switch other // choices based on list contents), parent rel, child rel, // and naming column. dialog = new SuggestUnrolledDataSetDialog(nTable); dialog.setVisible(true); // Cancelled? if (dialog.isCancelled()) return; final Column nIDCol = dialog.getNIDColumn(); final Column nNamingCol = dialog.getNNamingColumn(); final Table nrTable = dialog.getNRTable(); final Column nrParentIDCol = dialog.getNRParentIDColumn(); final Column nrChildIDCol = dialog.getNRChildIDColumn(); final boolean reversed = dialog.isReversed(); final Table actualNTable = dialog.getNTable(); new LongProcess() { public void run() throws Exception { try { Transaction.start(true); final DataSet ds = DataSetTabSet.this.getMartTab() .getMart().suggestUnrolledDataSets( actualNTable, nIDCol, nNamingCol, nrTable, nrParentIDCol, nrChildIDCol, reversed); DataSetTabSet.this.getMartTab().getMart().getDataSets() .put(ds.getOriginalName(), ds); } finally { Transaction.end(); } } }.start(); } catch (final Throwable t) { StackTrace.showStackTrace(t); } finally { if (dialog != null) dialog.dispose(); } } /** * Replicate a dataset. * * @param dataset * the dataset to replicate. */ public void requestReplicateDataSet(final DataSet dataset) { new LongProcess() { public void run() throws Exception { try { Transaction.start(false); final DataSet copy = dataset.replicate(); DataSetTabSet.this.getMartTab().getMart().getDataSets() .put(copy.getOriginalName(), copy); } finally { Transaction.end(); } } }.start(); } /** * Given a table, suggest a series of synchronised datasets that may be * possible for that table. * * @param table * the table to suggest datasets for. If <tt>null</tt>, no * default table is used. */ public void requestSuggestDataSets(final Table table) { // Ask the user what tables they want to work with and what // mode they want. final SuggestDataSetDialog dialog = new SuggestDataSetDialog( this.martTab.getMart().getSchemas().values(), table); dialog.setVisible(true); // If they cancelled it, return without doing anything. if (dialog.getSelectedTables().isEmpty()) return; new LongProcess() { public void run() throws Exception { try { Transaction.start(false); DataSetTabSet.this.martTab.getMart().suggestDataSets( dialog.getSelectedTables()); } finally { dialog.dispose(); Transaction.end(); } } }.start(); } /** * Given a table, suggest a synchronised dataset that may be possible for * that table and automatically make it into a partition table. * * @param table * the table to suggest datasets for. */ public void requestSuggestPartitionTable(final Table table) { new LongProcess() { public void run() throws Exception { DataSet ds = null; try { Transaction.start(false); ds = (DataSet) DataSetTabSet.this.martTab.getMart() .suggestDataSets(Collections.singleton(table)) .iterator().next(); ds.setPartitionTable(true); } finally { Transaction.end(); if (ds != null) DataSetTabSet.this.requestConvertPartitionTable(ds); } } }.start(); } /** * Given a table, suggest a series of synchronised invisible datasets that * may be possible for that table. This requires a set of columns, for which * the user will be prompted as necessary. * * @param dataset * the dataset we are working with. * @param table * the table to use to obtain columns to suggest invisible * datasets for. */ public void requestSuggestInvisibleDatasets(final DataSet dataset, final DataSetTable table) { // Ask the user what tables they want to work with and what // mode they want. final SuggestInvisibleDataSetDialog dialog = new SuggestInvisibleDataSetDialog( table); dialog.setVisible(true); // If they cancelled it, return without doing anything. if (dialog.getSelectedColumns().isEmpty()) return; new LongProcess() { public void run() throws Exception { try { Transaction.start(false); DataSetTabSet.this.martTab.getMart() .suggestInvisibleDataSets(dataset, dialog.getSelectedColumns()); } finally { dialog.dispose(); Transaction.end(); } } }.start(); } /** * Asks that all relations on a table be unmasked. * * @param ds * the dataset we are working with. * @param dst * the table to work with. * @param table * the schema table to unmask all relations for. */ public void requestUnmaskAllRelations(final DataSet ds, final DataSetTable dst, final Table table) { new LongProcess() { public void run() { Transaction.start(false); for (final Iterator i = table.getRelations().iterator(); i .hasNext();) { final Relation rel = (Relation) i.next(); if (dst != null) rel.setMaskRelation(ds, dst.getName(), false); else rel.setMaskRelation(ds, false); } Transaction.end(); } }.start(); } /** * Requests that the dataset be index optimised. * * @param dataset * the dataset to do this to. * @param indexOptimiser * whether to do it. */ public void requestIndexOptimiser(final DataSet dataset, final boolean indexOptimiser) { new LongProcess() { public void run() { Transaction.start(false); dataset.setIndexOptimiser(indexOptimiser); Transaction.end(); } }.start(); } }