/***************************************
 *                                     *
 *  JBoss: The OpenSource J2EE WebOS   *
 *                                     *
 *  Distributable under LGPL license.  *
 *  See terms of license at gnu.org.   *
 *                                     *
 ***************************************/
package org.jboss.deployment;

import java.util.ArrayList;
import java.util.StringTokenizer;


/**
 * SuffixOrderHelper.
 * 
 * This class wraps the SuffixOrder and EnhandedSuffixOrder attributes
 * of MainDeployer.
 * 
 * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
 * @version $Revision: 1.1.2.2 $
 */
public class SuffixOrderHelper
{
   // Constants -----------------------------------------------------
   
   /**
    * Default EnhancedSuffixOrder
    * 
    * Those values are indicative - we just know they'll work with
    * the compiled order of subdeployers like the aop or ejb3,
    * but any order can be set using the EnhancedSuffixOrder
    * attribute and/or the individual subdeployer's relative order.
    */
   public static final String[] DEFAULT_ENHANCED_SUFFIX_ORDER =
   {
         SubDeployer.RELATIVE_ORDER_100 + ":.deployer",
         SubDeployer.RELATIVE_ORDER_100 + ":-deployer.xml",
       //SubDeployer.RELATIVE_ORDER_100 + ":.aop",
       //SubDeployer.RELATIVE_ORDER_100 + ":-aop.xml",
         SubDeployer.RELATIVE_ORDER_200 + ":.sar",
         SubDeployer.RELATIVE_ORDER_200 + ":-service.xml",
         SubDeployer.RELATIVE_ORDER_300 + ":.rar",
         SubDeployer.RELATIVE_ORDER_300 + ":-ds.xml",
       //SubDeployer.RELATIVE_ORDER_400 + ":.har",
         SubDeployer.RELATIVE_ORDER_500 + ":.jar",
       //SubDeployer.RELATIVE_ORDER_500 + ":.ejb3",
         SubDeployer.RELATIVE_ORDER_600 + ":.war",
         SubDeployer.RELATIVE_ORDER_600 + ":.wsr",
         SubDeployer.RELATIVE_ORDER_600 + ":.ear",
         SubDeployer.RELATIVE_ORDER_700 + ":.zip",
       //SubDeployer.RELATIVE_ORDER_800 + ":.bsh",
         SubDeployer.RELATIVE_ORDER_900 + ":.last"
   };
   
   // Private Data --------------------------------------------------
   
   /** wrapped DeploymentSorter that stores the value for SuffixOrder attribute */
   private final DeploymentSorter sorter;
   
   /** the actual value of EnhancedSuffixOrder attribute */
   private String[] enhancedSuffixOrder;
   
   /** array of sorted SuffixEntry instances */
   private ArrayList suffixes;
   
   // Constructor ---------------------------------------------------
   
   public SuffixOrderHelper(DeploymentSorter sorter)
   {
      this.sorter = sorter;
      this.suffixes = new ArrayList();
   }

   // Accessors -----------------------------------------------------
   
   /**
    * Getter only for the SuffixOrder as known by the MainDeployer and the Scanners
    * 
    * The value is updated during init() with suffixes that remain constant.
    * After that suffixes are added/removed using the corresponding methods.
    * 
    * @return the SuffixOrder string array
    */
   public String[] getSuffixOrder()
   {
      return sorter.getSuffixOrder();
   }
   
   /**
    * Getter for the EnhancedSuffixOrder attribute
    * 
    * @return the EnhancedSuffixOrder string array
    */
   public String[] getEnhancedSuffixOrder()
   {
      return enhancedSuffixOrder;
   }
   
   /**
    * Setter for the EnhancedSuffixOrder attribute. Individual entries may contain
    * an additional order element of the form [order:]suffix e.g. 070:sar
    * 
    * During init() the value of SuffixOrder will be updated based on the
    * EnhancedSuffixOrder value.
    *  
    * @param enhancedSuffixOrder String array
    */
   public void setEnhancedSuffixOrder(String[] enhancedSuffixOrder)
   {
      this.enhancedSuffixOrder = enhancedSuffixOrder;
   }
   
   /**
    * Initialise the SuffixOrder from the EnhancedSuffixOrder
    */
   public void init()
   {
      // if enhancedSuffixOrder has not been set, use the default
      if (enhancedSuffixOrder == null)
      {
         enhancedSuffixOrder = DEFAULT_ENHANCED_SUFFIX_ORDER;
      }
      
      // add all enhanced suffixes 
      for (int i = 0; i < enhancedSuffixOrder.length; i++)
      {
         addSuffix(new SuffixEntry(enhancedSuffixOrder[i], true));
      }
      
      // set the resulting SuffixOrder
      sorter.setSuffixOrder(produceSuffixOrder());
   }

   /**
    * Insert the specified suffixes in the correct position
    * and regenerate the SuffixOrder array, if needed.
    * 
    * If a suffix exists, skip it
    * 
    * @param suffixes
    * @param order
    */
   public void addSuffixes(String[] suffixes, int order)
   {
      if (suffixes != null)
      {
         // remember the initial size of the list
         int size = this.suffixes.size();
         
         for (int i = 0; i < suffixes.length; i++)
         {
            try
            {
               addSuffix(new SuffixEntry(suffixes[i], order, false));
            }
            catch (RuntimeException e)
            {
               // suffix already set - ignore
            }
         }
         
         if (this.suffixes.size() > size)
         {
            // recreate the resulting SuffixOrder
            sorter.setSuffixOrder(produceSuffixOrder());
         }
      }
   }
   
   /**
    * Remove the specified suffixes if they are not marked as fixed
    * and regenerate the SuffixOrder, if needed.
    * 
    * @param suffixes
    * @param order
    */
   public void removeSuffixes(String[] suffixes, int order)
   {
      if (suffixes != null)
      {
         // remember the initial size of the list
         int size = this.suffixes.size();

         for (int i = 0; i < suffixes.length; i++)
         {         
            // check if suffix entry exists
            int pos = this.suffixes.indexOf(new SuffixEntry(suffixes[i], order, false));
            
            if (pos >= 0)
            {
               SuffixEntry entry = (SuffixEntry)this.suffixes.get(pos);
               if (!entry.fixed)
               {
                  // remove if entry not marked as fixed
                  this.suffixes.remove(pos);
               }
            }
         }
         
         if (this.suffixes.size() < size)
         {
            // recreate the resulting SuffixOrder
            sorter.setSuffixOrder(produceSuffixOrder());            
         }
      }
   }
   
   // Private -------------------------------------------------------
   
   /**
    * Produce the SuffixOrder from the sorted suffixes ArrayList
    */
   private String[] produceSuffixOrder()
   {
      String[] suffixOrder = new String[suffixes.size()];
      
      for (int i = 0; i < suffixes.size(); i++)
         suffixOrder[i] = ((SuffixEntry)suffixes.get(i)).suffix;
      
      return suffixOrder;
   }

   /**
    * Add a SuffixEntry at the correct position in the sorted ArrayList.
    * 
    * Sorting is based on SuffixEntry.order. A new entry with an equal
    * order value to an existing entry is placed after the existing entry. 
    * 
    * If SuffixEntry.suffix already exists a RuntimeException will be thrown.
    * 
    * @param suffix
    */
   private void addSuffix(SuffixEntry suffix)
   {
      // make sure the suffix is not there already
      if (suffixes.indexOf(suffix) >= 0)
      {
         throw new RuntimeException("suffix exists: " + suffix);
      }
      else
      {
         int size = suffixes.size();
         
         // if ArrayList empty, just add the suffix
         if (size == 0)
         {
            suffixes.add(suffix);
         }
         else
         {
            // insertion sort starting from the last element
            for (int i = size - 1; i > -1; i--)
            {
               SuffixEntry entry = (SuffixEntry)suffixes.get(i);
               if (suffix.order >= entry.order)
               {
                  // add the suffix after the entry and stop
                  suffixes.add(i + 1, suffix);
                  break;
               }
               else if (i == 0)
               {
                  // reached the beginning so add the suffix right there
                  suffixes.add(0, suffix);
               }
            }
         }
      }
   }
   
   /**
    * Inner class to encapsulate a SuffixEntry
    * consisting of suffix + order
    */
   private static class SuffixEntry
   {
      /** the suffix, e.g. sar */
      public String suffix;
      
      /** the order, by convention a 3 digit number, e.g. 070 */
      public int    order;
      
      /** mark those initial entries that are not supposed to be altered */
      public boolean fixed;
      
      /**
       * Simple CTOR
       */
      public SuffixEntry(String suffix, int order, boolean fixed)
      {
         this.suffix = suffix;
         this.order  = order;
         this.fixed  = fixed;
      }
      
      /**
       * CTOR that parses and enhancedSuffix string of the form: [order:]suffix
       * If order is missing, SubDeployer.RELATIVE_ORDER_500 is assumed
       * 
       * @param enhancedSuffix
       */
      public SuffixEntry(String enhancedSuffix, boolean fixed)
      {
         StringTokenizer tokenizer = new StringTokenizer(enhancedSuffix, ":");
         int tokens = tokenizer.countTokens();
         
         switch (tokens)
         {
            case 1:
               this.order  = SubDeployer.RELATIVE_ORDER_500;               
               this.suffix = enhancedSuffix;
               break;
             
            case 2:
               this.order  = Integer.parseInt(tokenizer.nextToken());               
               this.suffix = tokenizer.nextToken();
               break;
               
            default:
               throw new RuntimeException("cannot parse enhancedSuffix: " + enhancedSuffix);
         }
         this.fixed = fixed;
      }

      /**
       * Override equals to allow SuffixEntry to be searchable
       * using ArrayList.indexOf()/ArrayList.lastIndexOf()
       * 
       * Base equality on suffix only
       */
      public boolean equals(Object other)
      {
         if (other == null)
            return false;
         
         if (this == other)
            return true;
         
         if (!(other instanceof SuffixEntry))
            return false;
         
         SuffixEntry that = (SuffixEntry)other;
         
         // this.suffix shouldn't be null
         return this.suffix.equals(that.suffix);
      }
      
      /**
       * Use suffix hashCode
       */
      public int hashCode()
      {
         return this.suffix.hashCode();
      }
      
      /**
       * Pretty print
       */
      public String toString()
      {
         return order + ':' + suffix;
      }
   }
}