/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 * Created on March 25 2003
 */
package org.jboss.cache.eviction;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.Region;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Timer threads to do periodic node clean up by running the eviction policy.
 *
 * @author Ben Wang 2-2004
 * @author Daniel Huang (dhuang@jboss.org)
 * @version $Revision: 5831 $
 */
public class EvictionTimerTask
{
   private Log log = LogFactory.getLog(EvictionTimerTask.class);

   private final Set<Region> processedRegions;
   private static AtomicInteger tcount = new AtomicInteger();
   private int wakeupIntervalSeconds;
   private Timer evictionThread;

   public EvictionTimerTask()
   {
      // synchronized set because we need to maintain thread safety
      // for dynamic configuration purposes.
      processedRegions = Collections.synchronizedSet(new HashSet<Region>());
   }

   public void init(int wakeupIntervalSeconds)
   {
      if (log.isTraceEnabled())
         log.trace("Creating a new eviction listener with wakeupIntervalSeconds set at " + wakeupIntervalSeconds);
      this.wakeupIntervalSeconds = wakeupIntervalSeconds;
      start();
   }

   /**
    * Add a MarshRegion to process by the Eviction Thread.
    *
    * @param region MarshRegion to process.
    */
   public void addRegionToProcess(Region region)
   {
      processedRegions.add(region);
   }

   /**
    * Remove a MarshRegion to process from the Eviction thread.
    *
    * @param region
    */
   public void removeRegionToProcess(Region region)
   {
      processedRegions.remove(region);
   }

   public boolean isRegionRegisteredForProcessing(Region region)
   {
      return processedRegions.contains(region);
   }

   public Set<Region> getProcessedRegions()
   {
      return processedRegions;
   }

   public void stop()
   {
      log.debug("Stopping eviction timer");

      if (evictionThread != null)
      {
         evictionThread.cancel();
      }
      evictionThread = null;
   }

   private void start()
   {
      evictionThread = new Timer("EvictionTimer-" + tcount.getAndIncrement(), true);
      TimerTask tt = new TimerTask()
      {
         /**
          * Run the eviction thread.
          * <p/>
          * This thread will synchronize the set of regions and iterate through every MarshRegion registered w/ the
          * Eviction thread. It also synchronizes on each individual region as it is being processed.
          */
         public void run()
         {
            processRegions();
         }
      };
      evictionThread.schedule(tt, wakeupIntervalSeconds * 1000, wakeupIntervalSeconds * 1000);
   }

   private void processRegions()
   {
      synchronized (processedRegions)
      {
         for (Region region : processedRegions)
         {
            handleRegion(region);
         }
      }
   }

   private void handleRegion(Region region)
   {
      synchronized (region)
      {
         final EvictionPolicy policy = region.getEvictionPolicy();
         final EvictionAlgorithm algo = policy.getEvictionAlgorithm();
         if (algo == null)
            throw new NullPointerException("algorithm null");
         try
         {
            algo.process(region);
         }
         catch (EvictionException e)
         {
            log.error("run(): error processing eviction with exception: " + e.toString()
                  + " will reset the eviction queue list.");
            region.resetEvictionQueues();
            log.debug("trace", e);
         }
      }
   }
}


