/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.cache;


import org.jboss.cache.lock.IdentityLock;
import org.jboss.logging.Logger;
import org.jgroups.blocks.MethodCall;

import javax.transaction.Transaction;
import java.util.*;

import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;


/**
 * Maintains the mapping between local (Transaction) and global transactions
 * (GlobalTransaction). Also keys modifications and undo-operations) under a given TX
 *
 * @author <a href="mailto:bela@jboss.org">Bela Ban</a> Apr 14, 2003
 * @version $Revision: 1.9.2.8 $
 */
public class TransactionTable {

   /** HashMap<Transaction,GlobalTransaction>, mapping between local (javax.transaction.Transaction)
    * and GlobalTransactions. New: a local TX can have a number of GTXs */
   protected ConcurrentReaderHashMap tx_map=new ConcurrentReaderHashMap();

   /** HashMap<GlobalTransaction,TransactionEntry>, mappings between GlobalTransactions and modifications */
   protected HashMap txs=new HashMap();

   /** our logger */
   private static final Logger log=Logger.getLogger(TransactionTable.class);



   public TransactionTable() {
      ;
   }


   public int getNumLocalTransactions() {
      return tx_map.size();
   }

   public int getNumGlobalTransactions() {
      return txs.size();
   }

   public GlobalTransaction get(Transaction tx) {
      return tx != null ? (GlobalTransaction)tx_map.get(tx) : null;
   }

   /** Returns the local transaction associated with a GlobalTransaction. Not very efficient as the values have to
    * be iterated over, don't use frequently
    * @param gtx The GlobalTransaction
    * @return Transaction. The local transaction associated with
    * a given GlobalTransaction). This will be null if no local transaction is associated with a given GTX
    */
   public Transaction getLocalTransaction(GlobalTransaction gtx) {
      Map.Entry entry;
      Transaction local_tx=null;
      GlobalTransaction global_tx;

      if(gtx == null)
         return null;
      synchronized(tx_map) {
         for(Iterator it=tx_map.entrySet().iterator(); it.hasNext();) {
            entry=(Map.Entry)it.next();
            local_tx=(Transaction)entry.getKey();
            global_tx=(GlobalTransaction)entry.getValue();
            if(gtx.equals(global_tx)) {
               return local_tx;
            }
         }
      }
      return null;
   }

   public void put(Transaction tx, GlobalTransaction gtx) {
      if(tx == null) {
         log.error("key (Transaction) is null");
         return;
      }
      tx_map.put(tx, gtx);
   }

   public TransactionEntry get(GlobalTransaction gtx) {
      return gtx != null ? (TransactionEntry)txs.get(gtx) : null;
   }

   public void put(GlobalTransaction tx, TransactionEntry entry) {
      if(tx == null) {
         log.error("key (GlobalTransaction) is null");
         return;
      }
      txs.put(tx, entry);
   }

   public TransactionEntry remove(GlobalTransaction tx) {
      return (TransactionEntry)txs.remove(tx);
   }

   public GlobalTransaction remove(Transaction tx) {
      if(tx == null)
         return null;
      return (GlobalTransaction)tx_map.remove(tx);
   }

   public void addModification(GlobalTransaction gtx, MethodCall m) {
      TransactionEntry entry=get(gtx);
      if(entry == null) {
         log.error("transaction not found (gtx=" + gtx + ")");
         return;
      }
      entry.addModification(m);
   }

   public void addUndoOperation(GlobalTransaction gtx, MethodCall m) {
      TransactionEntry entry=get(gtx);
      if(entry == null) {
         log.error("transaction not found (gtx=" + gtx + ")");
         return;
      }
      entry.addUndoOperation(m);
   }

   public void addNode(GlobalTransaction gtx, Fqn node) {
      TransactionEntry entry=get(gtx);
      if(entry == null) {
         log.error("transaction not found (gtx=" + gtx + ")");
         return;
      }
      entry.addNode(node);
   }

   public void addLock(GlobalTransaction gtx, IdentityLock l) {
      TransactionEntry entry=get(gtx);
      if(entry == null) {
         log.error("transaction entry not found for (gtx=" + gtx + ")");
         return;
      }
      entry.addLock(l);
   }

   public void addLocks(GlobalTransaction gtx, Collection locks) {
      TransactionEntry entry=get(gtx);
      if(entry == null) {
         log.error("transaction entry not found for (gtx=" + gtx + ")");
         return;
      }
      entry.addLocks(locks);
   }

   public String toString() {
      StringBuffer sb=new StringBuffer();
      sb.append(tx_map.size()).append(" mappings, ");
      sb.append(txs.size()).append(" transactions");
      return sb.toString();
   }

   public String toString(boolean print_details) {
      if(!print_details)
         return toString();
      StringBuffer sb=new StringBuffer();
      Map.Entry entry;
      sb.append("LocalTransactions: ").append(tx_map.size()).append("\n");
      sb.append("GlobalTransactions: ").append(txs.size()).append("\n");
      sb.append("tx_map:\n");
      for(Iterator it=tx_map.entrySet().iterator(); it.hasNext();) {
         entry=(Map.Entry)it.next();
         sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
      }
      sb.append("txs:\n");
      for(Iterator it=txs.entrySet().iterator(); it.hasNext();) {
         entry=(Map.Entry)it.next();
         sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
      }
      return sb.toString();
   }
}
