/*
 * 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 java.util.*;
import org.jboss.logging.Logger;
//import org.jboss.cache.Fqn;
//import org.jboss.cache.Fqn;

/**
 * Factory to create region from configuration, to track region,
 * and to resolve naming conflict for regions. Note that in addition to
 * user-specified regions, there is also a global cache <code>_default_</code>
 * region that covers everything else.
 *
 * @author Ben Wang 02-2004
 */
public class RegionManager
{
   private Logger log_ = Logger.getLogger(RegionManager.class);
   public final static int CAPACITY = 200000;
   private Map regionMap_ = new HashMap();
   // optimization
   private Region[] regions_;
   private EvictionPolicy policy_;
   // There is global cache wide default values if no region is found.
   final static String DEFAULT_REGION = "/_default_";

   public RegionManager(EvictionPolicy policy) {
      policy_ = policy;
      regions_ = null;
   }

   /**
    * Create a region based on fqn.
    * @param fqn The region identifier.
    * @param algorithm EvictionAlgorithm that associates with this region.
    * @throws RegionNameConflictException
    */
   public Region createRegion(String fqn, EvictionAlgorithm algorithm)
         throws RegionNameConflictException
   {
      if(log_.isDebugEnabled()) {
         log_.debug("createRegion(): creating region for fqn- " +fqn);
      }

      checkConflict(fqn);
      Region region = new Region(fqn, policy_, algorithm);
      regionMap_.put(fqn, region);
      return region;
   }

   public Region getRegion(String myFqn)
   {
      Region[] regions = getRegions();
      // TODO need further optimization in regex matching
      for(int i=0; i < regions.length; i++) {
         String fqn = regions[i].getFqn();
         if(myFqn.startsWith(fqn)) return regions[i];
      }

      if(log_.isDebugEnabled()) {
         log_.debug("getRegion(): not user-specified region found for this fqn- " +myFqn
            + " will use the global default region");
      }
      return (Region)regionMap_.get(DEFAULT_REGION);
   }

   public Region[] getRegions() {
      // optimization
      if(regions_ != null && regions_.length == regionMap_.size())
         return regions_;

      Object[] objs = regionMap_.values().toArray();
      Region[] regions = new Region[objs.length];
      for(int i=0; i < objs.length; i++) {
         regions[i] = (Region)objs[i];
      }

      if(log_.isDebugEnabled()) {
         log_.debug("getRegions(): size of region " +regions.length);
      }

      regions_ = regions;
      return regions;
   }

   /**
    * Check for conflict in the current regions. There is a conflict
    * if fqn is any parent fqn of the current regions.
    * @param myFqn Current fqn for potential new region.
    * @throws RegionNameConflictException to indicate a region name conflict has ocurred.
    */
   public void checkConflict(String myFqn) throws RegionNameConflictException {
      // Step x. Loop thru the region map and compare the fqn. Order is important.
      // Not very efficient if there is a lot of regions.
      // First comer wins. E.g., /a/b/c and then /a/b/c will have /a/b in different
      // region than /a/b. But if /a/b and /a/b/c, then we will /a/b/c will be
      // the same region as /a/b. That is, we check if a fqn is within a region
      // by looping thru regions and then check if my fqn is a child of region
      // fqn. It it is, viola, and take this and exit.
      Region[] regions = getRegions();
      for(int i=0; i < regions.length; i++) {
         String fqn = regions[i].getFqn();
         if(myFqn.startsWith(fqn)) { // fqn is a child of myFqn.
            throw new RegionNameConflictException("RegionManager.checkConflict(): new region fqn "
                  +myFqn + " is in conflict with current region fqn- " +fqn);
         }
      }
      // We are clear then.
   }
}
