/* 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.diagrams; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import org.biomart.builder.model.Column; import org.biomart.builder.model.Key; import org.biomart.builder.model.Mart; import org.biomart.builder.model.Relation; import org.biomart.builder.model.Schema; import org.biomart.builder.model.Table; 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.Key.ForeignKey; import org.biomart.builder.model.Key.PrimaryKey; import org.biomart.builder.model.TransformationUnit.JoinTable; import org.biomart.builder.model.TransformationUnit.SelectFromTable; import org.biomart.builder.model.TransformationUnit.SkipTable; import org.biomart.builder.model.TransformationUnit.UnrollTable; import org.biomart.builder.view.gui.MartTabSet.MartTab; import org.biomart.builder.view.gui.diagrams.SchemaLayoutManager.SchemaLayoutConstraint; import org.biomart.builder.view.gui.diagrams.components.RelationComponent; import org.biomart.builder.view.gui.diagrams.components.TableComponent; import org.biomart.builder.view.gui.diagrams.contexts.ExplainContext; import org.biomart.common.exceptions.AssociationException; import org.biomart.common.exceptions.BioMartError; import org.biomart.common.resources.Resources; /** * Displays a transformation step, depending on what is passed to the * constructor. The results is always a diagram containing only those components * which are involved in the current transformation. * <p> * Note how diagrams do not have contexts, in order to prevent user interaction * with them. * * @author Richard Holland <holland@ebi.ac.uk> * @version $Revision: 1.46 $, $Date: 2008-02-01 10:17:56 $, modified by * $Author: rh4 $ * @since 0.5 */ public abstract class ExplainTransformationDiagram extends Diagram { private static final long serialVersionUID = 1; private final List tableComponents = new ArrayList(); private final int step; private final ExplainContext explainContext; private final Map shownTables; /** * Creates an empty diagram, using the single-parameter constructor from * {@link Diagram}. * * @param martTab * the tabset to communicate with when (if) context menus are * selected. * @param step * the step of the transformation this diagram represents. * @param explainContext * the context used to provide the relation contexts, which are * the same as those that appear in the explain diagram in the * other tab to the transform view. * @param shownTables * name to state map for initial table states. */ protected ExplainTransformationDiagram(final MartTab martTab, final int step, final ExplainContext explainContext, final Map shownTables) { super(new SchemaLayoutManager(), martTab); this.step = step; this.explainContext = explainContext; this.shownTables = shownTables; // No listener required as diagram gets redone from // scratch if underlying tables change. } protected boolean isUseHideMasked() { return false; } /** * Get which step this diagram is representing. * * @return the step of the transformation. */ protected int getStep() { return this.step; } /** * Get the state for a particular table component. * * @param comp * the component. * @return <tt>null</tt> for no state, an object otherwise. */ protected Object getState(final TableComponent comp) { return this.shownTables.get(comp.getTable().getName()); } /** * Find out what table components we have. * * @return the list of components. */ public TableComponent[] getTableComponents() { final TableComponent[] comps = new TableComponent[this.tableComponents .size()]; for (int i = 0; i < comps.length; i++) comps[i] = (TableComponent) this.tableComponents.get(i); return comps; } /** * Add a table component to this diagram. * * @param component * the component to add. */ protected void addTableComponent(final TableComponent component) { this.tableComponents.add(component); } /** * Get the explain context which appears in the other tab, which is to be * used for providing contexts for relations in this diagram. * * @return the context. */ public ExplainContext getExplainContext() { return this.explainContext; } public void doRecalculateDiagram() { this.tableComponents.clear(); } /** * This version of the class shows a single table. */ public static class SingleTable extends ExplainTransformationDiagram { private static final long serialVersionUID = 1; private final SelectFromTable stu; /** * Creates a diagram showing the given table. * * @param martTab * the mart tab to pass menu events onto. * @param stu * the transformation unit to show. * @param step * the step of the transformation this diagram represents. * @param explainContext * the context used to provide the relation contexts, which * are the same as those that appear in the explain diagram * in the other tab to the transform view. * @param shownTables * name to state map for initial table states. */ public SingleTable(final MartTab martTab, final SelectFromTable stu, final int step, final ExplainContext explainContext, final Map shownTables) { super(martTab, step, explainContext, shownTables); // Remember the params, and calculate the diagram. this.stu = stu; this.recalculateDiagram(); } public void doRecalculateDiagram() { // Removes all existing components. super.doRecalculateDiagram(); // Replicate the table in an empty schema then add the columns // requested. final FakeSchema tempSourceSchema = new FakeSchema(this.stu .getTable().getSchema().getName()); final Table tempSource = new RealisedTable( this.stu.getTable() instanceof DataSetTable ? ((DataSetTable) this.stu .getTable()).getModifiedName() : this.stu.getTable().getName(), tempSourceSchema, this.stu.getTable(), this.getExplainContext()); tempSourceSchema.getTables().put(tempSource.getName(), tempSource); for (final Iterator i = this.stu.getNewColumnNameMap().values() .iterator(); i.hasNext();) { final DataSetColumn col = (DataSetColumn) i.next(); tempSource.getColumns().put(col.getName(), col); } final TableComponent tc = new TableComponent(tempSource, this); this.add(tc, new SchemaLayoutConstraint(0), Diagram.TABLE_LAYER); this.addTableComponent(tc); final Object tcState = this.getState(tc); if (tcState != null) tc.setState(tcState); } } /** * This version of the class shows a temp table on the left and a real table * on the right. */ public static class TempUnrollReal extends ExplainTransformationDiagram { private static final long serialVersionUID = 1; private final UnrollTable utu; private final Collection lIncludeCols; /** * Creates a diagram showing the given pair of tables and a relation * between them. * * @param martTab * the mart tab to pass menu events onto. * @param utu * the transformation to explain. * @param lIncludeCols * the columns to show in the temp table. * @param step * the step of the transformation this diagram represents. * @param explainContext * the context used to provide the relation contexts, which * are the same as those that appear in the explain diagram * in the other tab to the transform view. * @param shownTables * name to state map for initial table states. */ public TempUnrollReal(final MartTab martTab, final UnrollTable utu, final List lIncludeCols, final int step, final ExplainContext explainContext, final Map shownTables) { super(martTab, step, explainContext, shownTables); // Remember the columns, and calculate the diagram. this.utu = utu; this.lIncludeCols = new ArrayList(lIncludeCols); this.recalculateDiagram(); } public void doRecalculateDiagram() { // Removes all existing components. super.doRecalculateDiagram(); // Create a temp table called TEMP with the given columns // and given foreign key. final FakeSchema tempSourceSchema = new FakeSchema(Resources .get("dummyTempSchemaName")); final Table tempSource = new FakeTable(Resources .get("dummyTempTableName") + " " + this.getStep(), tempSourceSchema); tempSourceSchema.getTables().put(tempSource.getName(), tempSource); for (final Iterator i = this.lIncludeCols.iterator(); i.hasNext();) { final Column col = (Column) i.next(); tempSource.getColumns().put(col.getName(), col); } final Key tempSourceKey = new ForeignKey(this.utu.getRelation() .getManyKey().getColumns()); tempSource.getForeignKeys().add(tempSourceKey); tempSourceKey.transactionResetVisibleModified(); // Create a copy of the target table complete with target key. final Key realTargetKey = this.utu.getRelation().getOneKey(); final Table realTarget = realTargetKey.getTable(); final FakeSchema tempTargetSchema = new FakeSchema(realTarget .getSchema().getName()); final Table tempTarget = new RealisedTable(realTarget.getName(), tempTargetSchema, realTarget, this.getExplainContext()); tempTargetSchema.getTables().put(tempTarget.getName(), tempTarget); tempTarget.getColumns().put( this.utu.getUnrolledIDColumn().getName(), this.utu.getUnrolledIDColumn()); tempTarget.getColumns().put( this.utu.getUnrolledNameColumn().getName(), this.utu.getUnrolledNameColumn()); final Key tempTargetKey = new PrimaryKey(realTargetKey.getColumns()); tempTarget.setPrimaryKey((PrimaryKey) tempTargetKey); tempTargetKey.transactionResetVisibleModified(); // Create a copy of the relation but change to be between the // two fake keys. Relation tempRelation; try { tempRelation = new RealisedRelation(tempSourceKey, tempTargetKey, this.utu.getRelation().getCardinality(), this.utu.getRelation(), 0, this.getExplainContext()); // DON'T add to keys else it causes trouble with // the caching system! } catch (final AssociationException e) { // Really should never happen. throw new BioMartError(e); } // Add source and target tables. final TableComponent tc1 = new TableComponent(tempSource, this); this.add(tc1, new SchemaLayoutConstraint(1), Diagram.TABLE_LAYER); this.addTableComponent(tc1); final Object tc1State = this.getState(tc1); if (tc1State != null) tc1.setState(tc1State); final TableComponent tc2 = new TableComponent(tempTarget, this); this.add(tc2, new SchemaLayoutConstraint(1), Diagram.TABLE_LAYER); this.addTableComponent(tc2); final Object tc2State = this.getState(tc2); if (tc2State != null) tc2.setState(tc2State); // Add relation. final RelationComponent relationComponent = new RelationComponent( tempRelation, this); this.add(relationComponent, new SchemaLayoutConstraint(0), Diagram.RELATION_LAYER); } } /** * This version of the class shows a temp table on the left and a real table * on the right. */ public static class TempReal extends ExplainTransformationDiagram { private static final long serialVersionUID = 1; private final JoinTable ltu; private final Collection lIncludeCols; /** * Creates a diagram showing the given pair of tables and a relation * between them. * * @param martTab * the mart tab to pass menu events onto. * @param ltu * the transformation to explain. * @param lIncludeCols * the columns to show in the temp table. * @param step * the step of the transformation this diagram represents. * @param explainContext * the context used to provide the relation contexts, which * are the same as those that appear in the explain diagram * in the other tab to the transform view. * @param shownTables * name to state map for initial table states. */ public TempReal(final MartTab martTab, final JoinTable ltu, final List lIncludeCols, final int step, final ExplainContext explainContext, final Map shownTables) { super(martTab, step, explainContext, shownTables); // Remember the columns, and calculate the diagram. this.ltu = ltu; this.lIncludeCols = new ArrayList(lIncludeCols); this.recalculateDiagram(); } public void doRecalculateDiagram() { // Removes all existing components. super.doRecalculateDiagram(); // Create a temp table called TEMP with the given columns // and given foreign key. final FakeSchema tempSourceSchema = new FakeSchema(Resources .get("dummyTempSchemaName")); final Table tempSource = new FakeTable(Resources .get("dummyTempTableName") + " " + this.getStep(), tempSourceSchema); tempSourceSchema.getTables().put(tempSource.getName(), tempSource); for (final Iterator i = this.lIncludeCols.iterator(); i.hasNext();) { final Column col = (Column) i.next(); tempSource.getColumns().put(col.getName(), col); } Key tempSourceKey; if (this.ltu.getSchemaSourceKey() instanceof ForeignKey) { tempSourceKey = new ForeignKey((Column[]) this.ltu .getSourceDataSetColumns().toArray(new Column[0])); tempSource.getForeignKeys().add(tempSourceKey); } else { tempSourceKey = new PrimaryKey((Column[]) this.ltu .getSourceDataSetColumns().toArray(new Column[0])); tempSource.setPrimaryKey((PrimaryKey) tempSourceKey); } tempSourceKey.transactionResetVisibleModified(); // Create a copy of the target table complete with target key. final Key realTargetKey = this.ltu.getSchemaRelation().getOtherKey( this.ltu.getSchemaSourceKey()); final Table realTarget = this.ltu.getTable(); final FakeSchema tempTargetSchema = new FakeSchema(realTarget .getSchema().getName()); final Table tempTarget = new RealisedTable(realTarget.getName(), tempTargetSchema, realTarget, this.getExplainContext()); tempTargetSchema.getTables().put(tempTarget.getName(), tempTarget); for (final Iterator i = this.ltu.getNewColumnNameMap().values() .iterator(); i.hasNext();) { final DataSetColumn col = (DataSetColumn) i.next(); tempTarget.getColumns().put(col.getName(), col); } Key tempTargetKey; if (realTargetKey instanceof ForeignKey) { tempTargetKey = new ForeignKey(realTargetKey.getColumns()); tempTarget.getForeignKeys().add(tempTargetKey); } else { tempTargetKey = new PrimaryKey(realTargetKey.getColumns()); tempTarget.setPrimaryKey((PrimaryKey) tempTargetKey); } tempTargetKey.transactionResetVisibleModified(); // Create a copy of the relation but change to be between the // two fake keys. Relation tempRelation; try { tempRelation = new RealisedRelation(tempSourceKey, tempTargetKey, this.ltu.getSchemaRelation() .getCardinality(), this.ltu.getSchemaRelation(), this.ltu .getSchemaRelationIteration(), this .getExplainContext()); // DON'T add to keys else it causes trouble with // the caching system! } catch (final AssociationException e) { // Really should never happen. throw new BioMartError(e); } // Add source and target tables. final TableComponent tc1 = new TableComponent(tempSource, this); this.add(tc1, new SchemaLayoutConstraint(1), Diagram.TABLE_LAYER); this.addTableComponent(tc1); final Object tc1State = this.getState(tc1); if (tc1State != null) tc1.setState(tc1State); final TableComponent tc2 = new TableComponent(tempTarget, this); this.add(tc2, new SchemaLayoutConstraint(1), Diagram.TABLE_LAYER); this.addTableComponent(tc2); final Object tc2State = this.getState(tc2); if (tc2State != null) tc2.setState(tc2State); // Add relation. final RelationComponent relationComponent = new RelationComponent( tempRelation, this); this.add(relationComponent, new SchemaLayoutConstraint(0), Diagram.RELATION_LAYER); } } /** * This version of the class shows a temp table on the left and a real table * on the right. */ public static class SkipTempReal extends ExplainTransformationDiagram { private static final long serialVersionUID = 1; private final SkipTable ltu; private final Collection lIncludeCols; /** * Creates a diagram showing the given pair of tables and a relation * between them. This is a faded-out 'fake' relation. * * @param martTab * the mart tab to pass menu events onto. * @param ltu * the transformation to explain. * @param lIncludeCols * the columns to show in the temp table. * @param step * the step of the transformation this diagram represents. * @param explainContext * the context used to provide the relation contexts, which * are the same as those that appear in the explain diagram * in the other tab to the transform view. * @param shownTables * name to state map for initial table states. */ public SkipTempReal(final MartTab martTab, final SkipTable ltu, final List lIncludeCols, final int step, final ExplainContext explainContext, final Map shownTables) { super(martTab, step, explainContext, shownTables); // Remember the columns, and calculate the diagram. this.ltu = ltu; this.lIncludeCols = new ArrayList(lIncludeCols); this.recalculateDiagram(); } public void doRecalculateDiagram() { // Removes all existing components. super.doRecalculateDiagram(); // Create a temp table called TEMP with the given columns // and given foreign key. final FakeSchema tempSourceSchema = new FakeSchema(Resources .get("dummyTempSchemaName")); final Table tempSource = new FakeTable(Resources .get("dummyTempTableName") + " " + this.getStep(), tempSourceSchema); tempSourceSchema.getTables().put(tempSource.getName(), tempSource); for (final Iterator i = this.lIncludeCols.iterator(); i.hasNext();) { final Column col = (Column) i.next(); tempSource.getColumns().put(col.getName(), col); } Key tempSourceKey; if (this.ltu.getSchemaSourceKey() instanceof ForeignKey) { tempSourceKey = new ForeignKey((Column[]) this.ltu .getSourceDataSetColumns().toArray(new Column[0])); tempSource.getForeignKeys().add(tempSourceKey); } else { tempSourceKey = new PrimaryKey((Column[]) this.ltu .getSourceDataSetColumns().toArray(new Column[0])); tempSource.setPrimaryKey((PrimaryKey) tempSourceKey); } tempSourceKey.transactionResetVisibleModified(); // Create a copy of the target table complete with target key. final Key realTargetKey = this.ltu.getSchemaRelation().getOtherKey( this.ltu.getSchemaSourceKey()); final Table realTarget = realTargetKey.getTable(); final FakeSchema tempTargetSchema = new FakeSchema(realTarget .getSchema().getName()); final Table tempTarget = new RealisedTable(realTarget.getName(), tempTargetSchema, realTarget, this.getExplainContext()); tempTargetSchema.getTables().put(tempTarget.getName(), tempTarget); // Target table has no columns. Key tempTargetKey; if (realTargetKey instanceof ForeignKey) { tempTargetKey = new ForeignKey(realTargetKey.getColumns()); tempTarget.getForeignKeys().add(tempTargetKey); } else { tempTargetKey = new PrimaryKey(realTargetKey.getColumns()); tempTarget.setPrimaryKey((PrimaryKey) tempTargetKey); } tempTargetKey.transactionResetVisibleModified(); // Create a copy of the relation but change to be between the // two fake keys. Relation tempRelation; try { tempRelation = new RealisedRelation(tempSourceKey, tempTargetKey, this.ltu.getSchemaRelation() .getCardinality(), this.ltu.getSchemaRelation(), this.ltu .getSchemaRelationIteration(), this .getExplainContext()); // DON'T add to keys else it causes trouble with // the caching system! } catch (final AssociationException e) { // Really should never happen. throw new BioMartError(e); } // Add source and target tables. final TableComponent tc1 = new TableComponent(tempSource, this); this.add(tc1, new SchemaLayoutConstraint(1), Diagram.TABLE_LAYER); this.addTableComponent(tc1); final Object tc1State = this.getState(tc1); if (tc1State != null) tc1.setState(tc1State); final TableComponent tc2 = new TableComponent(tempTarget, this); this.add(tc2, new SchemaLayoutConstraint(1), Diagram.TABLE_LAYER); this.addTableComponent(tc2); final Object tc2State = this.getState(tc2); if (tc2State != null) tc2.setState(tc2State); // Add relation. final RelationComponent relationComponent = new RelationComponent( tempRelation, this); this.add(relationComponent, new SchemaLayoutConstraint(0), Diagram.RELATION_LAYER); } } /** * This version of the class shows a bunch of additional columns added in * the last transformation steps. */ public static class AdditionalColumns extends ExplainTransformationDiagram { private static final long serialVersionUID = 1; private final TransformationUnit etu; /** * Creates a diagram showing the given table. * * @param martTab * the mart tab to pass menu events onto. * @param etu * the transformation unit to show. * @param step * the step of the transformation this diagram represents. * @param explainContext * the context used to provide the relation contexts, which * are the same as those that appear in the explain diagram * in the other tab to the transform view. * @param shownTables * name to state map for initial table states. */ public AdditionalColumns(final MartTab martTab, final TransformationUnit etu, final int step, final ExplainContext explainContext, final Map shownTables) { super(martTab, step, explainContext, shownTables); // Remember the params, and calculate the diagram. this.etu = etu; this.recalculateDiagram(); } public void doRecalculateDiagram() { // Removes all existing components. super.doRecalculateDiagram(); // Replicate the table in an empty schema then add the columns // requested. final FakeSchema tempSourceSchema = new FakeSchema(Resources .get("dummyTempSchemaName")); final Table tempSource = new FakeTable(Resources .get("dummyTempTableName") + " " + this.getStep(), tempSourceSchema); tempSourceSchema.getTables().put(tempSource.getName(), tempSource); for (final Iterator i = this.etu.getNewColumnNameMap().values() .iterator(); i.hasNext();) { final DataSetColumn col = (DataSetColumn) i.next(); tempSource.getColumns().put(col.getName(), col); } final TableComponent tc = new TableComponent(tempSource, this); this.add(tc, new SchemaLayoutConstraint(0), Diagram.TABLE_LAYER); this.addTableComponent(tc); final Object tcState = this.getState(tc); if (tcState != null) tc.setState(tcState); } } /** * A realised relation is a generic relation with a specific iteration. */ public static class RealisedRelation extends Relation { private static final long serialVersionUID = 1L; private final Relation relation; private final int relationIteration; private final ExplainContext explainContext; /** * Use this constant to refer to a relation that covers all iterations, * not just the realised one. */ public static final int NO_ITERATION = -1; private final PropertyChangeListener listener = new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent e) { final PropertyChangeEvent ours = new PropertyChangeEvent( RealisedRelation.this, e.getPropertyName(), e .getOldValue(), e.getNewValue()); ours.setPropagationId(e.getPropagationId()); RealisedRelation.this.pcs.firePropertyChange(ours); } }; /** * Constructs a realised relation. * * @param sourceKey * the realised source key. * @param targetKey * the realised target key. * @param cardinality * the realised cardinality. * @param relation * the original relation. * @param relationIteration * the original relation iteration. * @param explainContext * the explain context for displaying this realised relation. * @throws AssociationException * if the relation could not be established. */ public RealisedRelation(final Key sourceKey, final Key targetKey, final Relation.Cardinality cardinality, final Relation relation, final int relationIteration, final ExplainContext explainContext) throws AssociationException { super(sourceKey, targetKey, cardinality); this.relation = relation; this.relationIteration = relationIteration; this.explainContext = explainContext; relation.addPropertyChangeListener(this.listener); } /** * @return the explainContext */ public ExplainContext getExplainContext() { return this.explainContext; } /** * @return the relation */ public Relation getRelation() { return this.relation; } /** * @return the relationIteration */ public int getRelationIteration() { return this.relationIteration; } } /** * Realised tables are copies of those found in real schemas. */ public static class RealisedTable extends Table { private static final long serialVersionUID = 1L; private final Table table; private final ExplainContext explainContext; private final PropertyChangeListener listener = new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent e) { final PropertyChangeEvent ours = new PropertyChangeEvent( RealisedTable.this, e.getPropertyName(), e .getOldValue(), e.getNewValue()); ours.setPropagationId(e.getPropagationId()); RealisedTable.this.pcs.firePropertyChange(ours); } }; /** * Creates a realised table. * * @param name * the name to give the realised table. * @param schema * the schema to put it in. * @param table * the actual table we are referring to. * @param explainContext * the context for displaying it. */ public RealisedTable(final String name, final FakeSchema schema, final Table table, final ExplainContext explainContext) { super(schema, name); this.table = table; this.explainContext = explainContext; table.addPropertyChangeListener(this.listener); } /** * @return the explainContext */ public ExplainContext getExplainContext() { return this.explainContext; } /** * @return the table */ public Table getTable() { return this.table; } } /** * A fake mart does not really exist. */ public static class FakeMart extends Mart { private static final long serialVersionUID = 1L; /** * Construct a fake mart. */ public FakeMart() { super(); } } /** * A fake schema does not really exist. */ public static class FakeSchema extends Schema { private static final long serialVersionUID = 1L; /** * Construct a fake schema with the given name. * * @param name * the name. */ public FakeSchema(final String name) { super(new FakeMart(), name, name, name, null, null); } } /** * A fake table does not really exist. */ public static class FakeTable extends Table { private static final long serialVersionUID = 1L; /** * Construct a fake table with the given name in the given schema. * * @param name * the name. * @param schema * the schema. */ public FakeTable(final String name, final FakeSchema schema) { super(schema, name); } } }