/* 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.common.utils; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.EventObject; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.biomart.builder.model.Column; import org.biomart.builder.model.DataSet; import org.biomart.builder.model.Key; import org.biomart.builder.model.Relation; import org.biomart.builder.model.Schema; import org.biomart.builder.model.Table; import org.biomart.builder.model.DataSet.DataSetColumn; import org.biomart.builder.model.DataSet.DataSetTable; import org.biomart.builder.view.gui.diagrams.Diagram; import org.biomart.builder.view.gui.diagrams.components.DiagramComponent; import org.biomart.common.exceptions.TransactionException; import org.biomart.common.view.gui.dialogs.StackTrace; /** * This static class provides methods which signal the beginning and end of a * transaction. It has no data - it is merely a pair of events and an associated * event handler queue. * * @author Richard Holland <holland@ebi.ac.uk> * @version $Revision: 1.18 $, $Date: 2008-03-03 12:16:15 $, modified by * $Author: rh4 $ * @since 0.7 */ public class Transaction { private final static String LOCK = "__TRANSACTION__LOCK__"; /** * Listener are notified when a transaction starts and ends. */ public static interface TransactionListener { /** * Reset the modified flags on this object, possibly before a new * tranaction starts, or after a transaction which caused changes which * are not considered important. */ public void transactionResetDirectModified(); /** * Reset the modified flags on this object, possibly before a new * tranaction starts, or after a transaction which caused changes which * are not considered important. */ public void transactionResetVisibleModified(); /** * A transaction has begun. * * @param evt * the event describing the transaction. */ public void transactionStarted(final TransactionEvent evt); /** * A transaction has ended. * * @param evt * the event describing the transaction. This will be the * exact same object passed to * {@link #transactionStarted(org.biomart.common.utils.Transaction.TransactionEvent)} * if it refers to the same transaction. * @throws TransactionException * if anything went wrong. */ public void transactionEnded(final TransactionEvent evt) throws TransactionException; /** * Indicate that some aspect of this object has been directly affected * by the current transaction and it needs visibly highlighting. * * @param modified * whether or not it has been affected. */ public void setVisibleModified(final boolean modified); /** * Has this object been directly affected by this transaction and needs * visibly highlighting? * * @return <tt>true</tt> if it has. */ public boolean isVisibleModified(); /** * Indicate that some aspect of this object has been directly affected * by the current transaction. * * @param modified * whether or not it has been affected. */ public void setDirectModified(final boolean modified); /** * Has this object been directly affected by this transaction? * * @return <tt>true</tt> if it has. */ public boolean isDirectModified(); } private final static Set listeners = Collections .synchronizedSet(new HashSet()); /** * Adds a listener to the queue. Listeners are not stored in any particular * order, so you cannot guarantee that one listener will be called before * the next. * * @param listener * the listener to add. */ public static void addTransactionListener(final TransactionListener listener) { Transaction.listeners.add(new WeakReference(listener)); } private static int inProgress = 0; private static Transaction currentTransaction; /** * Reset all transaction listeners ready for a new transaction. */ public static void resetVisibleModified() { for (final Iterator i = Transaction.getOrderedListeners().iterator(); i .hasNext();) ((TransactionListener) i.next()).transactionResetVisibleModified(); } /** * Reset all transaction listeners ready for a new transaction. */ public static void resetDirectModified() { for (final Iterator i = Transaction.getOrderedListeners().iterator(); i .hasNext();) ((TransactionListener) i.next()).transactionResetDirectModified(); } /** * Flag that a transaction has started. If another transaction is already * underway, this call is simply ignored and the existing transaction * continues. If no other transactions are underway, a new one is created * and * {@link TransactionListener#transactionStarted(org.biomart.common.utils.Transaction.TransactionEvent)} * is called. * * @param allowVisModChange * does this transaction change visible modifiers? */ public static void start(final boolean allowVisModChange) { synchronized (Transaction.LOCK) { if (++Transaction.inProgress == 1) { Transaction.currentTransaction = new Transaction( allowVisModChange); final TransactionEvent event = new TransactionEvent( Transaction.currentTransaction); for (final Iterator i = Transaction.getOrderedListeners() .iterator(); i.hasNext();) { final TransactionListener listener = (TransactionListener) i .next(); listener.transactionResetDirectModified(); listener.transactionStarted(event); } } } } /** * Flag that a transaction has ended. If other transactions were started in * the meantime, the last one to end will trigger a * {@link TransactionListener#transactionEnded(org.biomart.common.utils.Transaction.TransactionEvent)} * event. */ public synchronized static void end() { synchronized (Transaction.LOCK) { if (Transaction.inProgress == 0) return; if (--Transaction.inProgress == 0 && Transaction.currentTransaction != null) { try { final TransactionEvent event = new TransactionEvent( Transaction.currentTransaction); for (final Iterator i = Transaction.getOrderedListeners() .iterator(); i.hasNext();) ((TransactionListener) i.next()) .transactionEnded(event); } catch (final TransactionException te) { StackTrace.showStackTrace(te); } Transaction.currentTransaction = null; } } } private static List getOrderedListeners() { // Clear out dead refs first. System.gc(); // Now get the list. synchronized (Transaction.LOCK) { final List sch = new ArrayList(); final List schComp = new ArrayList(); final List schRel = new ArrayList(); final List dsComp = new ArrayList(); final List dsRel = new ArrayList(); final List pts = new ArrayList(); final List ds = new ArrayList(); final List diag = new ArrayList(); final List diagComp = new ArrayList(); final List rest = new ArrayList(); synchronized (Transaction.listeners) { for (final Iterator i = Transaction.listeners.iterator(); i .hasNext();) { final TransactionListener tl = (TransactionListener) ((WeakReference) i .next()).get(); if (tl == null) { i.remove(); continue; } else if (tl instanceof DataSet) { if (((DataSet) tl).isPartitionTable()) pts.add(tl); else ds.add(tl); } else if (tl instanceof Schema) sch.add(tl); else if (tl instanceof DiagramComponent) diagComp.add(tl); else if (tl instanceof Diagram) diag.add(tl); else if (tl instanceof Relation) { if (((Relation) tl).getFirstKey().getTable() .getSchema() instanceof DataSet) dsRel.add(tl); else schRel.add(tl); } else if (tl instanceof Key) { if (((Key) tl).getTable().getSchema() instanceof DataSet) dsComp.add(tl); else schComp.add(tl); } else if (tl instanceof Column) { if (tl instanceof DataSetColumn) dsComp.add(tl); else schComp.add(tl); } else if (tl instanceof Table) if (tl instanceof DataSetTable) dsComp.add(tl); else schComp.add(tl); else rest.add(tl); } } final List list = new ArrayList(); // schemas. list.addAll(sch); // non-relation schema components. list.addAll(schComp); // relations. list.addAll(schRel); // partition tables. list.addAll(pts); // dataset. list.addAll(ds); // non-relation dataset components. list.addAll(dsComp); // dataset relations. list.addAll(dsRel); // diagram components. list.addAll(diagComp); // diagrams. list.addAll(diag); // anything else that is interested. list.addAll(rest); return list; } } /** * Checks to see if there is currently a transaction in progress. * * @return the current transaction, or <tt>null</tt> if there isn't one. */ public static Transaction getCurrentTransaction() { return Transaction.currentTransaction; } /** * Describes a transaction start/end event. Currently this class has no * useful properties and the event objects that use it can safely be * ignored. */ public static class TransactionEvent extends EventObject { private static final long serialVersionUID = 1L; /** * Create a new event fired from the specified source. * * @param source * the source that fired the event. This will be a * Transaction object. */ public TransactionEvent(final Object source) { super(source); } } private boolean allowVisModChange; /** * Create a new, empty transaction. * * @param allowVisModChange * Does this transaction modify visible modification flags? */ public Transaction(final boolean allowVisModChange) { this.allowVisModChange = allowVisModChange; } /** * Does this transaction modify visible modification flags? * * @param allowVisModChange <tt>true</tt> if it does. */ public void setAllowVisModChange(final boolean allowVisModChange) { this.allowVisModChange = allowVisModChange; } /** * Does this transaction modify visible modification flags? * * @return <tt>true</tt> if it does. */ public boolean isAllowVisModChange() { return this.allowVisModChange; } }