package org.jboss.cache;

import net.jcip.annotations.ThreadSafe;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import static org.jboss.cache.Region.Type.*;
import org.jboss.cache.buddyreplication.BuddyFqnTransformer;
import org.jboss.cache.buddyreplication.BuddyManager;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.ConfigurationException;
import org.jboss.cache.config.EvictionConfig;
import org.jboss.cache.config.EvictionPolicyConfig;
import org.jboss.cache.config.EvictionRegionConfig;
import org.jboss.cache.eviction.EvictionTimerTask;
import org.jboss.cache.factories.annotations.Destroy;
import org.jboss.cache.factories.annotations.Inject;
import org.jboss.cache.factories.annotations.NonVolatile;
import org.jboss.cache.factories.annotations.Start;
import org.jboss.cache.factories.annotations.Stop;
import org.jboss.cache.lock.LockManager;
import static org.jboss.cache.lock.LockType.WRITE;
import org.jgroups.Address;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Manages multiple {@link Region}s for a Cache instance.
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani</a>
 * @since 2.0.0
 */
@ThreadSafe
@NonVolatile
public class RegionManager
{
   /**
    * The default region used in XML configuration files when defining eviction policies.  Any
    * eviction settings bound under this 'default' Fqn is appplied to {@link org.jboss.cache.Fqn#ROOT} internally so
    * any region that is not explicitly defined comes under the settings defined for this default.
    */
   public static final Fqn<?> DEFAULT_REGION = Fqn.fromString("/_default_");

   /**
    * A registry of regions that have been defined.
    */
   private final Map<Fqn, Region> regionsRegistry = new ConcurrentHashMap<Fqn, Region>();
   private boolean defaultInactive;
   private final Log log = LogFactory.getLog(RegionManager.class);
   private CacheSPI cache;
   private boolean usingEvictions;
   private EvictionConfig evictionConfig;
   private final EvictionTimerTask evictionTimerTask = new EvictionTimerTask();

   protected final Set<Fqn> activationChangeNodes = Collections.synchronizedSet(new HashSet<Fqn>());
   protected Configuration configuration;
   protected RPCManager rpcManager;
   private LockManager lockManager;
   private BuddyFqnTransformer buddyFqnTransformer;
   private boolean isUsingBR;

   @Inject
   void injectDependencies(CacheSPI cache, Configuration configuration, RPCManager rpcManager, LockManager lockManager,
                           BuddyFqnTransformer transformer)
   {
      this.cache = cache;
      this.rpcManager = rpcManager;
      this.configuration = configuration;
      this.lockManager = lockManager;
      this.buddyFqnTransformer = transformer;
   }

   @Start
   protected void start()
   {
      log.trace("Starting region manager");
      isUsingBR = configuration.getBuddyReplicationConfig() != null && configuration.getBuddyReplicationConfig().isEnabled();
      if (configuration.getEvictionConfig() != null
            && configuration.getEvictionConfig().isValidConfig())
      {
         // validate individual region configs now
         for (EvictionRegionConfig erc : configuration.getEvictionConfig().getEvictionRegionConfigs())
         {
            EvictionPolicyConfig epc = erc.getEvictionPolicyConfig();
            if (epc != null) epc.validate();
         }

         setEvictionConfig(configuration.getEvictionConfig());
         setUsingEvictions(true);
      }
      else
      {
         setUsingEvictions(false);
         log.debug("Not using an EvictionPolicy");
      }

      setDefaultInactive(configuration.isInactiveOnStartup());

      if (isUsingEvictions()) startEvictionThread();
   }

   @Stop
   protected void stop()
   {
      if (isUsingEvictions()) stopEvictionThread();
   }

   @Destroy
   protected void destroy()
   {
      regionsRegistry.clear();
      activationChangeNodes.clear();
   }

   /**
    * @return true if evictions are being processed.
    */
   public boolean isUsingEvictions()
   {
      return usingEvictions;
   }

   /**
    * @return true if replication is by default inactive for new {@link Region}s.
    */
   public boolean isDefaultInactive()
   {
      return defaultInactive;
   }

   /**
    * Sets if replication for new {@link Region}s is by default inactive.
    */
   public void setDefaultInactive(boolean defaultInactive)
   {
      this.defaultInactive = defaultInactive;
      Region defaultRegion = regionsRegistry.get(Fqn.ROOT);
      if (defaultRegion != null) defaultRegion.setActive(!defaultInactive);
   }

   /**
    * Helper utility that checks for a {@link ClassLoader} registered for the
    * given {@link Fqn}, and if found sets it as the TCCL. If the given Fqn is
    * under the _BUDDY_BACKUP_ region, the equivalent region in the main
    * cache is used to find the {@link ClassLoader}.
    *
    * @param fqn Fqn pointing to a region for which a special classloader
    *            may have been registered.
    */
   public void setContextClassLoaderAsCurrent(Fqn fqn)
   {
      if (fqn.isChildOf(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN))
      {
         if (fqn.size() <= 2)
         {
            fqn = Fqn.ROOT;
         }
         else
         {
            fqn = fqn.getSubFqn(2, fqn.size());
         }
      }
      Region region = getRegion(fqn, false);
      ClassLoader regionCL = (region == null) ? null : region.getClassLoader();
      if (regionCL != null)
      {
         Thread.currentThread().setContextClassLoader(regionCL);
      }

   }

   /**
    * Returns a region by {@link Fqn}, creating it optionally if absent.  If the region does not exist and <tt>createIfAbsent</tt>
    * is <tt>false</tt>, a parent region which may apply to the {@link Fqn} is sought.
    * <p/>
    * Note that this takes into account the fact that this may be a Buddy Backup Fqn.  If it is, the actual Fqn is calculated
    * and used instead.
    */
   public Region getRegion(Fqn fqn, boolean createIfAbsent)
   {
      return getRegion(fqn, ANY, createIfAbsent);
   }

   /**
    * Retrieves a valid marshalling {@link Region} after taking into account that this may be a Buddy Backup Fqn.
    * If the fqn passed in is null, the region has been deactivated or if a region cannot be found, this method returns a null.
    *
    * @param fqn of the region to locate
    * @return a region
    */
   public Region getValidMarshallingRegion(Fqn fqn)
   {
      if (fqn == null) return null;
      return getRegion(fqn, Region.Type.MARSHALLING, false);
   }

   /**
    * An overloaded form of {@link #getRegion(Fqn,boolean)} that takes an additional {@link org.jboss.cache.Region.Type}
    * parameter to force regions of a specific type.
    * <p/>
    * Note that this takes into account the fact that this may be a Buddy Backup Fqn.  If it is, the actual Fqn is calculated
    * and used instead.
    *
    * @see org.jboss.cache.Region.Type
    */
   public Region getRegion(Fqn fqn, Region.Type type, boolean createIfAbsent)
   {
      if (isUsingBR && fqn != null && buddyFqnTransformer.isBackupFqn(fqn))
      {
         fqn = buddyFqnTransformer.getActualFqn(fqn);
      }

      if (log.isTraceEnabled()) log.trace("Contents of RegionsRegistry: " + regionsRegistry);
      Fqn fqnToUse = fqn;
      if (DEFAULT_REGION.equals(fqnToUse)) fqnToUse = Fqn.ROOT;
      // first see if a region for this specific Fqn exists
      if (regionsRegistry.containsKey(fqnToUse))
      {
         Region r = regionsRegistry.get(fqnToUse);

         // this is a very poor way of telling whether a region is a marshalling one or an eviction one.  :-(
         // mandates that class loaders be registered for marshalling regions.
         if (type == ANY
               || (type == MARSHALLING && r.getClassLoader() != null)
               || (type == EVICTION && r.getEvictionPolicyConfig() != null))
         {
            return r;
         }
      }

      // if not, attempt to create one ...
      if (createIfAbsent)
      {
         Region r = new RegionImpl(fqnToUse, this);
         regionsRegistry.put(fqnToUse, r);
         if (type == MARSHALLING)
         {
            // insert current class loader into region so at least it is recognised as a marshalling region
            r.registerContextClassLoader(getClass().getClassLoader());
         }
         return r;
      }

      // else try and find a parent which has a defined region, may return null if nothing is defined.
      Region nextBestThing = null;
      Fqn nextFqn = fqnToUse;

      while (nextBestThing == null)
      {
         nextFqn = nextFqn.getParent();
         if (regionsRegistry.containsKey(nextFqn))
         {
            Region r = regionsRegistry.get(nextFqn);
            if (log.isTraceEnabled()) log.trace("Trying next region " + nextFqn + " and got " + r);

            // this is a very poor way of telling whether a region is a marshalling one or an eviction one.  :-(
            // mandates that class loaders be registered for marshalling regions.
            if (type == ANY
                  || (type == MARSHALLING && r.getClassLoader() != null)
                  || (type == EVICTION && r.getEvictionPolicyConfig() != null))
            {
               nextBestThing = r;
            }
         }
         if (nextFqn.isRoot()) break;
      }

      // test if the default region has been defined.  If not, and if the request
      // is for an eviction region, return null
      if (type == EVICTION && nextBestThing != null && nextBestThing.getFqn().isRoot() && !regionsRegistry.containsKey(Fqn.ROOT))
      {
         log.trace("No default eviction region; returning null");
         nextBestThing = null;
      }

      return nextBestThing;
   }

   /**
    * Returns a region using Fqn.fromString(fqn), calling {@link #getRegion(Fqn,boolean)}
    *
    * @param fqn
    * @param createIfAbsent
    * @see #getRegion(Fqn,boolean)
    */
   public Region getRegion(String fqn, boolean createIfAbsent)
   {
      return getRegion(Fqn.fromString(fqn), createIfAbsent);
   }

   /**
    * Removes a {@link org.jboss.cache.Region} identified by the given fqn.
    *
    * @param fqn fqn of the region to remove
    * @return true if such a region existed and was removed.
    */
   public boolean removeRegion(Fqn fqn)
   {
      Region r = regionsRegistry.remove(fqn);
      if (r == null) return false;

      if (isUsingEvictions() && r.getEvictionPolicy() != null)
      {
         evictionTimerTask.removeRegionToProcess(r);
      }
      return true;
   }

   /**
    * @return the eviction timer task object associated with this Region Manager.
    */
   protected EvictionTimerTask getEvictionTimerTask()
   {
      return evictionTimerTask;
   }

   /**
    * Activates unmarshalling of replication messages for the region
    * rooted in the given Fqn.
    * <p/>
    * <strong>NOTE:</strong> This method will cause the creation of a node
    * in the local cache at <code>subtreeFqn</code> whether or not that
    * node exists anywhere else in the cluster.  If the node does not exist
    * elsewhere, the local node will be empty.  The creation of this node will
    * not be replicated.
    * <p/>
    *
    * @param fqn representing the region to be activated.
    * @throws RegionNotEmptyException if the node <code>fqn</code>
    *                                 exists and already has either data or children
    */
   public void activate(Fqn fqn) throws RegionNotEmptyException
   {
      activate(fqn, false);
   }

   /**
    * Attempts to activate a given region rooted at a given Fqn, similar to {@link #activate(Fqn)} except
    * that if the fqn is currently already in use (probably already been activated) this method is a no-op.
    *
    * @param fqn which represents the region to activate
    */
   public void activateIfEmpty(Fqn fqn)
   {
      activate(fqn, true);
   }

   private void activate(Fqn fqn, boolean suppressRegionNotEmptyException)
   {
      try
      {
         if (log.isTraceEnabled()) log.trace("Activating region " + fqn);
         Region r = getRegion(fqn, false);
         if (r != null)
         {
            if (!defaultInactive && r.getClassLoader() == null)
            {
               // This region's state will no match that of a non-existent one
               // So, there is no reason to keep this region any more

               // (Brian) We shouldn't do this anymore; now outside code
               // can have a ref to the region!!
               removeRegion(fqn);
            }
            else
            {
               //r.activate();
               r.setStatus(Region.Status.ACTIVATING);
               if (configuration.isFetchInMemoryState())
               {
                  activateRegion(r.getFqn(), suppressRegionNotEmptyException);
               }
               r.setActive(true);
            }
         }
         else if (defaultInactive)
         {
            // "Active" region is not the default, so create a region
            r = getRegion(fqn, true);
            // FIXME - persistent state transfer counts too!
            r.setStatus(Region.Status.ACTIVATING);
            if (configuration.isFetchInMemoryState())
            {
               activateRegion(r.getFqn(), suppressRegionNotEmptyException);
            }
            r.setActive(true);
         }
      }
      catch (RuntimeException re)
      {
         throw re;
      }
      catch (Exception e)
      {
         throw new RuntimeException(e);
      }
   }

   /**
    * Causes the cache to transfer state for the subtree rooted at
    * <code>subtreeFqn</code> and to begin accepting replication messages
    * for that subtree.
    * <p/>
    * <strong>NOTE:</strong> This method will cause the creation of a node
    * in the local cache at <code>subtreeFqn</code> whether or not that
    * node exists anywhere else in the cluster.  If the node does not exist
    * elsewhere, the local node will be empty.  The creation of this node will
    * not be replicated.
    *
    * @param fqn Fqn string indicating the uppermost node in the
    *            portion of the cache that should be activated.
    * @throws RegionNotEmptyException if the node <code>subtreeFqn</code>
    *                                 exists and has either data or children
    */
   private void activateRegion(Fqn fqn, boolean suppressRegionNotEmptyException)
   {
      // Check whether the node already exists and has data
      Node subtreeRoot = cache.peek(fqn, false, false);

      /*
       * Commented out on Nov 16,2006 Manik&Vladimir
       *
       * if (!(cache.isNodeEmpty(subtreeRoot)))
      {
         throw new RegionNotEmptyException("Node " + subtreeRoot.getFqn() + " already exists and is not empty");
      }*/

      if (isActivatingDeactivating(fqn))
      {
         throw new CacheException("Region " + subtreeRoot.getFqn() + " is already being activated/deactivated");
      }

      if (log.isDebugEnabled())
      {
         log.debug("activating " + fqn);
      }

      try
      {

         // Add this fqn to the set of those we are activating
         // so calls to _getState for the fqn can return quickly
         activationChangeNodes.add(fqn);

         BuddyManager buddyManager = cache.getBuddyManager();
         // Request partial state from the cluster and integrate it
         if (buddyManager == null)
         {
            // Get the state from any node that has it and put it
            // in the main cache
            if (subtreeRoot == null)
            {
               // We'll update this node with the state we receive
               // need to obtain all necessary locks.
               cache.getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
               subtreeRoot = cache.getRoot().addChild(fqn);
               cache.getInvocationContext().getOptionOverrides().setCacheModeLocal(false);
            }

            List<Address> members = cache.getMembers();

            // Don't bother trying to fetch state if we are in LOCAL mode
            if (members != null && !members.isEmpty())
               rpcManager.fetchPartialState(members, subtreeRoot.getFqn());
         }
         else if (!buddyFqnTransformer.isBackupFqn(fqn))
         {
            // Get the state from each DataOwner and integrate in their
            // respective buddy backup cache
            List<Address> buddies = buddyManager.getBackupDataOwners();
            for (Address buddy : buddies)
            {
               List<Address> sources = new ArrayList<Address>(1);
               if (!cache.getMembers().contains(buddy))
                  continue;
               sources.add(buddy);
               Fqn buddyRoot = buddyFqnTransformer.getBackupFqn(buddy, fqn);
               subtreeRoot = cache.peek(buddyRoot, false, false);
               if (subtreeRoot == null)
               {
                  // We'll update this node with the state we receive
                  // need to obtain all necessary locks.
                  // needs to be a LOCAL call!
                  cache.getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
                  subtreeRoot = cache.getRoot().addChild(buddyRoot);
                  cache.getInvocationContext().getOptionOverrides().setCacheModeLocal(false);
               }
               rpcManager.fetchPartialState(sources, fqn, subtreeRoot.getFqn());
            }
         }
         else
         {
            log.info("Attempting to activate a backup region.  Not attempting to retrieve any state as this will be pushed.");
         }
      }
      catch (Throwable t)
      {
         log.error("failed to activate " + fqn, t);

         // "Re-deactivate" the region
         try
         {
            inactivateRegion(fqn);
         }
         catch (Exception e)
         {
            log.error("failed inactivating " + fqn, e);
            // just swallow this one and throw the first one
         }

         // Throw the exception on, wrapping if necessary
         if (t instanceof RegionNotEmptyException)
         {
            if (!suppressRegionNotEmptyException) throw (RegionNotEmptyException) t;
         }
         else if (t instanceof CacheException)
         {
            throw (CacheException) t;
         }
         else
         {
            throw new CacheException(t.getClass().getName() + " " +
                  t.getLocalizedMessage(), t);
         }
      }
      finally
      {
         activationChangeNodes.remove(fqn);
      }
   }

   /**
    * Convenienve method.  If the region defined by fqn does not exist, {@link #isDefaultInactive()} is returned, otherwise
    * !{@link Region#isActive()} is returned.
    *
    * @param fqn fqn to test
    * @return true if inactive
    */
   public boolean isInactive(Fqn fqn)
   {
      Region region = getRegion(fqn, false);
      return region == null ? defaultInactive : !region.isActive();
   }


   /**
    * Causes the cache to stop accepting replication events for the subtree
    * rooted at <code>subtreeFqn</code> and evict all nodes in that subtree.
    * <p/>
    * This is legacy code and should not be called directly.  This is a private method for now and will be refactored out.
    * You should be using {@link #activate(Fqn)} and {@link #deactivate(Fqn)}
    * <p/>
    *
    * @param fqn Fqn string indicating the uppermost node in the
    *            portion of the cache that should be activated.
    * @throws RegionNameConflictException if <code>subtreeFqn</code> indicates
    *                                     a node that is part of another
    *                                     subtree that is being specially
    *                                     managed (either by activate/inactiveRegion()
    *                                     or by registerClassLoader())
    * @throws CacheException              if there is a problem evicting nodes
    * @throws IllegalStateException       if {@link org.jboss.cache.config.Configuration#isUseRegionBasedMarshalling()} is <code>false</code>
    */
   private void inactivateRegion(Fqn fqn) throws CacheException
   {
      if (isActivatingDeactivating(fqn))
      {
         throw new CacheException("Region " + fqn + " is already being activated/deactivated");
      }

      NodeSPI parent = null;
      NodeSPI subtreeRoot = null;
      boolean parentLocked = false;
      boolean subtreeLocked = false;

      try
      {
         // Record that this fqn is in status change, so can't provide state
         activationChangeNodes.add(fqn);

         if (!isInactive(fqn))
         {
            deactivate(fqn);
         }

         // Create a list with the Fqn in the main cache and any buddy backup trees
         BuddyManager buddyManager = cache.getBuddyManager();
         ArrayList<Fqn> list = new ArrayList<Fqn>();
         list.add(fqn);

         if (buddyManager != null)
         {
            Set buddies = cache.peek(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN, false, false).getChildrenNames();
            if (buddies != null)
            {
               for (Object buddy : buddies)
               {
                  list.add(buddyFqnTransformer.getBackupFqn((String) buddy, fqn));
               }
            }
         }

         long stateFetchTimeout = cache.getConfiguration().getLockAcquisitionTimeout() + 5000;
         // Remove the subtree from the main cache  and any buddy backup trees
         for (Fqn subtree : list)
         {
            subtreeRoot = cache.peek(subtree, false, false);
            if (subtreeRoot != null)
            {
               // Acquire locks

               Object owner = getOwnerForLock();
               subtreeLocked = lockManager.lockAll(subtreeRoot, WRITE, owner, stateFetchTimeout);

               // Lock the parent, as we're about to write to it
               parent = subtreeRoot.getParent();
               if (parent != null) parentLocked = lockManager.lock(parent.getFqn(), WRITE, owner, stateFetchTimeout);

               // Remove the subtree
               cache.evict(subtree, true);
               //cache._evictSubtree(subtree);

               // Release locks
               if (parent != null)
               {
                  log.debug("forcing release of locks in parent");
                  lockManager.unlockAll(parent);
               }

               parentLocked = false;

               log.debug("forcing release of all locks in subtree");
               lockManager.unlockAll(subtreeRoot);
               subtreeLocked = false;
            }
         }
      }
      finally
      {
         // If we didn't succeed, undo the marshalling change
         // NO. Since we inactivated, we may have missed changes
         //if (!success && !inactive)
         //   marshaller_.activate(subtreeFqn);

         // If necessary, release locks
         if (parentLocked)
         {
            log.debug("forcing release of locks in parent");
            try
            {
               if (parent != null) lockManager.unlockAll(parent);
            }
            catch (Throwable t)
            {
               log.error("failed releasing locks", t);
            }
         }
         if (subtreeLocked)
         {
            log.debug("forcing release of all locks in subtree");
            try
            {
               if (subtreeRoot != null) lockManager.unlockAll(subtreeRoot);
            }
            catch (Throwable t)
            {
               log.error("failed releasing locks", t);
            }
         }

         activationChangeNodes.remove(fqn);
      }
   }

   private Object getOwnerForLock()
   {
      Object owner = cache.getCurrentTransaction();
      return owner == null ? Thread.currentThread() : owner;
   }


   /**
    * <p/>
    * This is legacy code and should not be called directly.  This is a private method for now and will be refactored out.
    * You should be using {@link #activate(Fqn)} and {@link #deactivate(Fqn)}
    * <p/>
    *
    * @param fqn fqn of the region
    * @return true if the region defined by the fqn is in the process of activating/deactivating
    */
   private boolean isActivatingDeactivating(Fqn fqn)
   {
      return activationChangeNodes.contains(fqn);
   }

   /**
    * Returns true if the region exists
    *
    * @param fqn  FQN of the region
    * @param type type of region to search for
    * @return true if the region exists
    */
   public boolean hasRegion(Fqn fqn, Region.Type type)
   {
      Region r = regionsRegistry.get(fqn);
      if (r == null) return false;
      switch (type)
      {
         case ANY:
            return true;
         case EVICTION:
            return r.getEvictionPolicy() != null && evictionTimerTask.isRegionRegisteredForProcessing(r);
         case MARSHALLING:
            return r.isActive() && r.getClassLoader() != null;
      }
      // should never reach here?
      return false;
   }

   /**
    * Disables unmarshalling of replication messages for the region
    * rooted in the given Fqn.
    *
    * @param fqn
    */
   public void deactivate(Fqn fqn)
   {
      try
      {
         Region region = getRegion(fqn, false);

         if (region != null)
         {
            if (defaultInactive && region.getClassLoader() == null)
            {
               // This region's state will no match that of a non-existent one
               // So, there is no reason to keep this region any more

               // FIXME (Brian) We shouldn't do this anymore; now outside code can have a ref to the region!!
               removeRegion(fqn);
            }
            else
            {
               //region.deactivate();
               region.setActive(false);
               // FIXME - we should always clean up; otherwise stale data is in memory!
               if (cache.getConfiguration().isFetchInMemoryState())
               {
                  inactivateRegion(fqn);
               }
            }
         }
         else if (!defaultInactive)
         {
            region = getRegion(fqn, true);
            region.setActive(false);
            // FIXME - we should always clean up; otherwise stale data is in memory!
            if (cache.getConfiguration().isFetchInMemoryState())
            {
               inactivateRegion(fqn);
            }
         }
      }
      catch (RuntimeException re)
      {
         throw re;
      }
      catch (Exception e)
      {
         throw new RuntimeException(e);
      }
   }

   /**
    * Resets the region manager's regions registry
    */
   public void reset()
   {
      regionsRegistry.clear();
   }

   /**
    * Returns an ordered list of all regions.
    * Note that the ordered list returned is sorted according to the natural order defined in the {@link Comparable} interface, which {@link org.jboss.cache.Region} extends.
    *
    * @param type Type of region to return
    * @return an ordered list of all regions, based on the type requested.
    */
   public List<Region> getAllRegions(Region.Type type)
   {
      List<Region> regions;

      if (type != ANY)
      {
         regions = new ArrayList<Region>();
         // we need to loop thru the regions and only select specific regions to rtn.
         for (Region r : regionsRegistry.values())
         {
            if ((type == EVICTION && r.getEvictionPolicy() != null && evictionTimerTask.isRegionRegisteredForProcessing(r)) ||
                  (type == MARSHALLING && r.isActive() && r.getClassLoader() != null))
               regions.add(r);
         }
      }
      else
      {
         // put all regions
         regions = new ArrayList<Region>(regionsRegistry.values());
      }

      Collections.sort(regions);

      return regions;
   }

   /**
    * Sets if evictions are processed.
    */
   public void setUsingEvictions(boolean usingEvictions)
   {
      this.usingEvictions = usingEvictions;
   }

   /**
    * Sets the eviction configuration.
    */
   public void setEvictionConfig(EvictionConfig evictionConfig)
   {
      this.evictionConfig = evictionConfig;

      // JBAS-1288
      // Try to establish a default region if there isn't one already
      boolean needDefault;
      List<EvictionRegionConfig> ercs = evictionConfig.getEvictionRegionConfigs();

      // APPROACH 1: Scan for a default region, try to add if not there.
      // This will try to add the region if it is missing but seems to break 
      // some unit tests that configure one or more non-default regions and
      // no default region (e.g. the eviction.minttl tests). So, doing this 
      // seems to add a new semantic. For now comment this out and use APPROACH 2
//      for (EvictionRegionConfig erc : ercs)
//      {
//         if (DEFAULT_REGION.equals(erc.getRegionFqn()))
//         {
//            needDefault = false;
//            break;
//         }
//      }
      // APPROACH 2: Only add a default region if there are no regions. This is
      // contrary to the idea that there *must* be a default region, but some
      // unit tests fail w/ APPROACH 1, so for now we go with this approach.
      needDefault = ercs.size() == 0;

      if (needDefault)
      {
         // This may throw ConfigurationException if there is no default
         // eviction policy class
         EvictionRegionConfig dflt = evictionConfig.createDefaultEvictionRegionConfig();
         ercs.add(0, dflt); // put it first
         // Need to pass this back into the evictionConfig so it knows
         // about the new region
         evictionConfig.setEvictionRegionConfigs(ercs);
      }

      // create regions for the regions defined in the evictionConfig.
      // scan to be sure the _default_ region isn't added twice
      boolean setDefault = false;
      for (EvictionRegionConfig erc : ercs)
      {
         Fqn fqn = erc.getRegionFqn();
         if (log.isTraceEnabled()) log.trace("Creating eviction region " + fqn);

         if (fqn.equals(DEFAULT_REGION))
         {
            if (setDefault)
            {
               throw new ConfigurationException("A default region for evictions has already been set for this cache");
            }
            if (log.isTraceEnabled()) log.trace("Applying settings for " + DEFAULT_REGION + " to Fqn.ROOT");
            fqn = Fqn.ROOT;
            setDefault = true;
         }
         Region r = getRegion(fqn, true);
         r.setEvictionPolicy(erc.getEvictionPolicyConfig());
      }
   }

   /**
    * Starts the eviction processing thread.
    */
   public void startEvictionThread()
   {
      evictionTimerTask.init(evictionConfig.getWakeupIntervalSeconds());
   }

   /**
    * Stops the eviction processing thread
    */
   public void stopEvictionThread()
   {
      evictionTimerTask.stop();
   }


   /**
    * Returns a string containing debug information on every region.
    *
    * @return Regions as a string
    */
   public String dumpRegions()
   {
      StringBuilder sb = new StringBuilder();
      for (Region r : regionsRegistry.values())
      {
         sb.append("\tRegion ").append(r);
         sb.append("\n");
      }
      return sb.toString();
   }

   /**
    * Returns a string containing debug information on every region.
    */
   @Override
   public String toString()
   {
      return "RegionManager " + dumpRegions();
   }

   public CacheSPI getCache()
   {
      return cache;
   }
}
