/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util.naming;

import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.RefAddr;
import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;

/**
 * A utility class that allows one to bind a non-serializable object into a
 * local JNDI context. The binding will only be valid for the lifetime of the
 * VM in which the JNDI InitialContext lives. An example usage code snippet is:
 *

    // The non-Serializable object to bind
    Object nonserializable = ...;
    // An arbitrary key to use in the StringRefAddr. The best key is the jndi
    // name that the object will be bound under.
    String key = ...;
    // This places nonserializable into the NonSerializableFactory hashmap under key
    NonSerializableFactory.rebind(key, nonserializable);

    Context ctx = new InitialContext();
    // Bind a reference to nonserializable using NonSerializableFactory as the ObjectFactory
    String className = nonserializable.getClass().getName();
    String factory = NonSerializableFactory.class.getName();
    StringRefAddr addr = new StringRefAddr("nns", key);
    Reference memoryRef = new Reference(className, addr, factory, null);
    ctx.rebind(key, memoryRef);

 *
 * Or you can use the {@link #rebind(Context, String, Object)} convenience
 * method to simplify the number of steps to:
 *

    Context ctx = new InitialContext();
    // The non-Serializable object to bind
    Object nonserializable = ...;
    // The jndiName that the object will be bound into ctx with
    String jndiName = ...;
    // This places nonserializable into the NonSerializableFactory hashmap under key
    NonSerializableFactory.rebind(ctx, jndiName, nonserializable);

 *
 * To unbind the object, use the following code snippet:
 *

	new InitialContext().unbind(key);
	NonSerializableFactory.unbind(key);

 *
 * @see javax.naming.spi.ObjectFactory
 * @see #rebind(Context, String, Object)
 *
 * @author Scott Stark.
 * @version $Revision$
*/
@SuppressWarnings("unchecked")
public class NonSerializableFactory implements ObjectFactory
{
    private static Map wrapperMap = Collections.synchronizedMap(new HashMap());

    /** Place an object into the NonSerializableFactory namespace for subsequent
    access by getObject. There cannot be an already existing binding for key.

    @param key the name to bind target under. This should typically be the
    name that will be used to bind target in the JNDI namespace, but it does
    not have to be.
    @param target the non-Serializable object to bind.
    @throws NameAlreadyBoundException thrown if key already exists in the
     NonSerializableFactory map
    */
    public static synchronized void bind(String key, Object target) throws NameAlreadyBoundException
    {
        if( wrapperMap.containsKey(key) == true )
            throw new NameAlreadyBoundException(key+" already exists in the NonSerializableFactory map");
        wrapperMap.put(key, target);
    }
    /** Place or replace an object in the NonSerializableFactory namespce
     for subsequent access by getObject. Any existing binding for key will be
     replaced by target.

    @param key the name to bind target under. This should typically be the
    name that will be used to bind target in the JNDI namespace, but it does
    not have to be.
    @param target the non-Serializable object to bind.
    */
    public static void rebind(String key, Object target)
    {
        wrapperMap.put(key, target);
    }

    /** Remove a binding from the NonSerializableFactory map.

    @param key the key into the NonSerializableFactory map to remove.
    @throws NameNotFoundException thrown if key does not exist in the
     NonSerializableFactory map
    */
    public static void unbind(String key) throws NameNotFoundException
    {
        if( wrapperMap.remove(key) == null )
            throw new NameNotFoundException(key+" was not found in the NonSerializableFactory map");
    }
    /** Remove a binding from the NonSerializableFactory map.

    @param name the name for the key into NonSerializableFactory map to remove.
     The key is obtained as name.toString().
    @throws NameNotFoundException thrown if key does not exist in the
     NonSerializableFactory map
    */
    public static void unbind(Name name) throws NameNotFoundException
    {
        String key = name.toString();
        if( wrapperMap.remove(key) == null )
            throw new NameNotFoundException(key+" was not found in the NonSerializableFactory map");
    }

    /** Lookup a value from the NonSerializableFactory map.
    * @param key
    @return the object bound to key is one exists, null otherwise.
    */
    public static Object lookup(String key)
    {
        Object value = wrapperMap.get(key);
        return value;
    }
    /** Lookup a value from the NonSerializableFactory map.
    * @param name
    @return the object bound to key is one exists, null otherwise.
    */
    public static Object lookup(Name name)
    {
        String key = name.toString();
        Object value = wrapperMap.get(key);
        return value;
    }

    /** A convenience method that simplifies the process of rebinding a
     non-serializable object into a JNDI context.

    @param ctx the JNDI context to rebind to.
    @param key the key to use in both the NonSerializableFactory map and JNDI. It
        must be a valid name for use in ctx.bind().
    @param target the non-Serializable object to bind.
    @throws NamingException thrown on failure to rebind key into ctx.
    */
    public static synchronized void rebind(Context ctx, String key, Object target) throws NamingException
    {
        NonSerializableFactory.rebind(key, target);
        // Bind a reference to target using NonSerializableFactory as the ObjectFactory
        String className = target.getClass().getName();
        String factory = NonSerializableFactory.class.getName();
        StringRefAddr addr = new StringRefAddr("nns", key);
        Reference memoryRef = new Reference(className, addr, factory, null);
        ctx.rebind(key, memoryRef);
    }

   /** A convenience method that simplifies the process of rebinding a
    non-serializable object into a JNDI context. This version binds the
    target object into the default IntitialContext using name path.

   @param name the name to use as JNDI path name. The key into the
    NonSerializableFactory map is obtained from the toString() value of name.
    The name parameter cannot be a 0 length name.
    Any subcontexts between the root and the name must exist.
   @param target the non-Serializable object to bind.
   @throws NamingException thrown on failure to rebind key into ctx.
   */
   public static synchronized void rebind(Name name, Object target) throws NamingException
   {
      rebind(name, target, false);
   }

   /** A convenience method that simplifies the process of rebinding a
    non-serializable object into a JNDI context.

   @param ctx the JNDI context to rebind to.
   @param key the key to use in both the NonSerializableFactory map and JNDI. It
        must be a valid name for use in ctx.bind().
   @param target the non-Serializable object to bind.
   @param createSubcontexts a flag indicating if subcontexts of name should
    be created if they do not already exist.
   @throws NamingException thrown on failure to rebind key into ctx.
   */
   public static synchronized void rebind(Context ctx, String key,
      Object target, boolean createSubcontexts) throws NamingException
   {
	   Name name = ctx.getNameParser("").parse(key);
       if( createSubcontexts == true && name.size() > 1 )
       {
          int size = name.size() - 1;
          Util.createSubcontext(ctx, name.getPrefix(size));
       }
       rebind(ctx, key, target);
   }

   /**
    * A convenience method that simplifies the process of rebinding a
    * non-serializable object into a JNDI context. This version binds the
    * target object into the default IntitialContext using name path.
    *
    * @param name the name to use as JNDI path name. The key into the
    *   NonSerializableFactory map is obtained from the toString() value of name.
    *   The name parameter cannot be a 0 length name.
    * @param target the non-Serializable object to bind.
    * @param createSubcontexts a flag indicating if subcontexts of name should
    *   be created if they do not already exist.
    * @throws NamingException thrown on failure to rebind key into ctx.
    * @author Matt Carter
    **/
   public static synchronized void rebind(Name name, Object target, boolean createSubcontexts) throws NamingException
   {
      String key = name.toString();
      InitialContext ctx = new InitialContext();
      if (createSubcontexts == true && name.size() > 1)
      {
         int size = name.size() - 1;
         Util.createSubcontext(ctx, name.getPrefix(size));
      }
      rebind(ctx, key, target);
   }

// --- Begin ObjectFactory interface methods
    /** Transform the obj Reference bound into the JNDI namespace into the
    actual non-Serializable object.

    @param obj the object bound in the JNDI namespace. This must be an implementation
    of javax.naming.Reference with a javax.naming.RefAddr of type "nns" whose
    content is the String key used to location the non-Serializable object in the
    NonSerializableFactory map.
    @param name ignored.
    @param nameCtx ignored.
    @param env ignored.

    @return the non-Serializable object associated with the obj Reference if one
    exists, null if one does not.
    */
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable env)
        throws Exception
    {	// Get the nns value from the Reference obj and use it as the map key
        Reference ref = (Reference) obj;
        RefAddr addr = ref.get("nns");
        String key = (String) addr.getContent();
        Object target = wrapperMap.get(key);
        return target;
    }
// --- End ObjectFactory interface methods
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.List;
import java.util.ArrayList;
import java.util.Locale;

/**
 * A collection of String utilities.
 *
 * @author Jason Dillon
 * @author Scott Stark
 * @author Claudio Vesco
 * @author Dimitris Andreadis
 * @version <tt>$Revision$</tt>
 */
@SuppressWarnings("unchecked")
public final class Strings
{
   /** An empty string constant */
   public static final String EMPTY = "";

   /** Millisecond conversion constants */
   private static final long MSEC = 1;
   private static final long SECS = 1000;
   private static final long MINS = 60 * 1000;
   private static final long HOUR = 60 * 60 * 1000;

   /**
    * List of valid Java keywords, see The Java Language Specification
    * Second Edition Section 3.9, 3.10.3 and 3.10.7
    */
   private static final String[] keywords =
      {
         "abstract",
         "boolean",
         "break",
         "byte",
         "case",
         "catch",
         "char",
         "class",
         "const",
         "continue",
         "default",
         "do",
         "double",
         "else",
         "extends",
         "final",
         "finally",
         "float",
         "for",
         "goto",
         "if",
         "implements",
         "import",
         "instanceof",
         "int",
         "interface",
         "long",
         "native",
         "new",
         "package",
         "private",
         "protected",
         "public",
         "return",
         "short",
         "static",
         "strictfp",
         "super",
         "switch",
         "synchronized",
         "this",
         "throw",
         "throws",
         "transient",
         "try",
         "void",
         "volatile",
         "while",

         "true", // technically no keywords but we are not picky
         "false",
         "null"
      };

   /**
    * List of EJB-QL Identifiers as defined in the EJB 2.0 Specification
    * Section 11.2.6.1
    */
   private static final String[] ejbQlIdentifiers =
      {
         "AND",
         "AS",
         "BETWEEN",
         "DISTINCT",
         "EMPTY",
         "FALSE",
         "FROM",
         "IN",
         "IS",
         "LIKE",
         "MEMBER",
         "NOT",
         "NULL",
         "OBJECT",
         "OF",
         "OR",
         "SELECT",
         "UNKNOWN",
         "TRUE",
         "WHERE",
      };

   /////////////////////////////////////////////////////////////////////////
   //                         Substitution Methods                        //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Substitute sub-strings in side of a string.
    *
    * @param buff    Stirng buffer to use for substitution (buffer is not reset)
    * @param from    String to substitute from
    * @param to      String to substitute to
    * @param string  String to look for from in
    * @return        Substituted string
    */
   public static String subst(final StringBuffer buff, final String from,
      final String to, final String string)
   {
      int begin = 0, end = 0;

      while ((end = string.indexOf(from, end)) != -1)
      {
         // append the first part of the string
         buff.append(string.substring(begin, end));

         // append the replaced string
         buff.append(to);

         // update positions
         begin = end + from.length();
         end = begin;
      }

      // append the rest of the string
      buff.append(string.substring(begin, string.length()));

      return buff.toString();
   }

   /**
    * Substitute sub-strings in side of a string.
    *
    * @param from    String to substitute from
    * @param to      String to substitute to
    * @param string  String to look for from in
    * @return        Substituted string
    */
   public static String subst(final String from, final String to,
      final String string)
   {
      return subst(new StringBuffer(), from, to, string);
   }

   /**
    * Substitute sub-strings in side of a string.
    *
    * @param buff       String buffer to use for substitution (buffer is not reset)
    * @param string     String to subst mappings in
    * @param map        Map of from->to strings
    * @param beginToken Beginning token
    * @param endToken   Ending token
    * @return           Substituted string
    */
   public static String subst(final StringBuffer buff, final String string,
      final Map map, final String beginToken,
      final String endToken)
   {
      int begin = 0, rangeEnd = 0;
      Range range;

      while ((range = rangeOf(beginToken, endToken, string, rangeEnd)) != null)
      {
         // append the first part of the string
         buff.append(string.substring(begin, range.begin));

         // Get the string to replace from the map
         String key = string.substring(range.begin + beginToken.length(),
            range.end);
         Object value = map.get(key);
         // if mapping does not exist then use empty;
         if (value == null) value = EMPTY;

         // append the replaced string
         buff.append(value);

         // update positions
         begin = range.end + endToken.length();
         rangeEnd = begin;
      }

      // append the rest of the string
      buff.append(string.substring(begin, string.length()));

      return buff.toString();
   }

   /**
    * Substitute sub-strings in side of a string.
    *
    * @param string     String to subst mappings in
    * @param map        Map of from->to strings
    * @param beginToken Beginning token
    * @param endToken   Ending token
    * @return           Substituted string
    */
   public static String subst(final String string, final Map map,
      final String beginToken, final String endToken)
   {
      return subst(new StringBuffer(), string, map, beginToken, endToken);
   }

   /**
    * Substitute index identifiers with the replacement value from the
    * given array for the corresponding index.
    *
    * @param buff       The string buffer used for the substitution
    *                   (buffer is not reset).
    * @param string     String substitution format.
    * @param replace    Array of strings whose values will be used as
    *                   replacements in the given string when a token with
    *                   their index is found.
    * @param token      The character token to specify the start of an index
    *                   reference.
    * @return           Substituted string.
    */
   public static String subst(final StringBuffer buff, final String string,
      final String replace[], final char token)
   {
      int i = string.length();
      for (int j = 0; j >= 0 && j < i; j++)
      {
         char c = string.charAt(j);

         // if the char is the token, then get the index
         if (c == token)
         {

            // if we aren't at the end of the string, get the index
            if (j != i)
            {
               int k = Character.digit(string.charAt(j + 1), 10);

               if (k == -1)
               {
                  buff.append(string.charAt(j + 1));
               }
               else if (k < replace.length)
               {
                  buff.append(replace[k]);
               }

               j++;
            }
         }
         else
         {
            buff.append(c);
         }
      }

      return buff.toString();
   }

   /**
    * Substitute index identifiers with the replacement value from the
    * given array for the corresponding index.
    *
    * @param string     String substitution format.
    * @param replace    Array of strings whose values will be used as
    *                   replacements in the given string when a token with
    *                   their index is found.
    * @param token      The character token to specify the start of an index
    *                   reference.
    * @return           Substituted string.
    */
   public static String subst(final String string, final String replace[],
      final char token)
   {
      return subst(new StringBuffer(), string, replace, token);
   }

   /**
    * Substitute index identifiers (with % for the index token)
    * with the replacement value from the given array for the corresponding
    * index.
    *
    * @param string     String substitution format.
    * @param replace    Array of strings whose values will be used as
    *                   replacements in the given string when a token with
    *                   their index is found.
    * @return           Substituted string.
    */
   public static String subst(final String string, final String replace[])
   {
      return subst(new StringBuffer(), string, replace, '%');
   }

   /////////////////////////////////////////////////////////////////////////
   //                             Range Methods                           //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Represents a range between two integers.
    */
   public static class Range
   {
      /** The beginning of the range. */
      public int begin;

      /** The end of the range. */
      public int end;

      /**
       * Construct a new range.
       *
       * @param begin   The beginning of the range.
       * @param end     The end of the range.
       */
      public Range(int begin, int end)
      {
         this.begin = begin;
         this.end = end;
      }

      /**
       * Default constructor.
       */
      public Range()
      {
      }
   }

   /**
    * Return the range from a begining token to an ending token.
    *
    * @param beginToken String to indicate begining of range.
    * @param endToken   String to indicate ending of range.
    * @param string     String to look for range in.
    * @param fromIndex  Beginning index.
    * @return           (begin index, end index) or null.
    */
   public static Range rangeOf(final String beginToken, final String endToken,
      final String string, final int fromIndex)
   {
      int begin = string.indexOf(beginToken, fromIndex);

      if (begin != -1)
      {
         int end = string.indexOf(endToken, begin + 1);
         if (end != -1)
         {
            return new Range(begin, end);
         }
      }

      return null;
   }

   /**
    * Return the range from a begining token to an ending token.
    *
    * @param beginToken String to indicate begining of range.
    * @param endToken   String to indicate ending of range.
    * @param string     String to look for range in.
    * @return           (begin index, end index) or null.
    */
   public static Range rangeOf(final String beginToken, final String endToken,
      final String string)
   {
      return rangeOf(beginToken, endToken, string, 0);
   }

   /////////////////////////////////////////////////////////////////////////
   //                           Spliting Methods                          //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Split up a string into multiple strings based on a delimiter.
    *
    * @param string  String to split up.
    * @param delim   Delimiter.
    * @param limit   Limit the number of strings to split into
    *                (-1 for no limit).
    * @return        Array of strings.
    */
   public static String[] split(final String string, final String delim,
      final int limit)
   {
      // get the count of delim in string, if count is > limit
      // then use limit for count.  The number of delimiters is less by one
      // than the number of elements, so add one to count.
      int count = count(string, delim) + 1;
      if (limit > 0 && count > limit)
      {
         count = limit;
      }

      String strings[] = new String[count];
      int begin = 0;

      for (int i = 0; i < count; i++)
      {
         // get the next index of delim
         int end = string.indexOf(delim, begin);

         // if the end index is -1 or if this is the last element
         // then use the string's length for the end index
         if (end == -1 || i + 1 == count)
            end = string.length();

         // if end is 0, then the first element is empty
         if (end == 0)
            strings[i] = EMPTY;
         else
            strings[i] = string.substring(begin, end);

         // update the begining index
         begin = end + 1;
      }

      return strings;
   }

   /**
    * Split up a string into multiple strings based on a delimiter.
    *
    * @param string  String to split up.
    * @param delim   Delimiter.
    * @return        Array of strings.
    */
   public static String[] split(final String string, final String delim)
   {
      return split(string, delim, -1);
   }

   /////////////////////////////////////////////////////////////////////////
   //                    Joining/Concatenation Methods                    //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Join an array of strings into one delimited string.
    *
    * @param buff    String buffered used for join (buffer is not reset).
    * @param array   Array of objects to join as strings.
    * @param delim   Delimiter to join strings with or null.
    * @return        Joined string.
    */
   public static String join(final StringBuffer buff, final Object array[],
      final String delim)
   {
      boolean haveDelim = (delim != null);

      for (int i = 0; i < array.length; i++)
      {
         buff.append(array[i]);

         // if this is the last element then don't append delim
         if (haveDelim && (i + 1) < array.length)
         {
            buff.append(delim);
         }
      }

      return buff.toString();
   }

   /**
    * Join an array of strings into one delimited string.
    *
    * @param array   Array of objects to join as strings.
    * @param delim   Delimiter to join strings with or null.
    * @return        Joined string.
    */
   public static String join(final Object array[], final String delim)
   {
      return join(new StringBuffer(), array, delim);
   }

   /**
    * Convert and join an array of objects into one string.
    *
    * @param array   Array of objects to join as strings.
    * @return        Converted and joined objects.
    */
   public static String join(final Object array[])
   {
      return join(array, null);
   }

   /**
    * Convert and join an array of bytes into one string.
    *
    * @param array   Array of objects to join as strings.
    * @return        Converted and joined objects.
    */
   public static String join(final byte array[])
   {
      Byte bytes[] = new Byte[array.length];
      for (int i = 0; i < bytes.length; i++)
      {
         bytes[i] = new Byte(array[i]);
      }

      return join(bytes, null);
   }

   /**
    * Return a string composed of the given array.
    *
    * @param buff       Buffer used to construct string value (not reset).
    * @param array      Array of objects.
    * @param prefix     String prefix.
    * @param separator  Element sepearator.
    * @param suffix     String suffix.
    * @return           String in the format of:
    *                   prefix + n ( + separator + n+i)* + suffix.
    */
   public static String join(final StringBuffer buff, final Object[] array,
      final String prefix, final String separator,
      final String suffix)
   {
      buff.append(prefix);
      join(buff, array, separator);
      buff.append(suffix);

      return buff.toString();
   }

   /**
    * Return a string composed of the given array.
    *
    * @param array      Array of objects.
    * @param prefix     String prefix.
    * @param separator  Element sepearator.
    * @param suffix     String suffix.
    * @return           String in the format of:
    *                   prefix + n ( + separator + n+i)* + suffix.
    */
   public static String join(final Object[] array, final String prefix,
      final String separator, final String suffix)
   {
      return join(new StringBuffer(), array, prefix, separator, suffix);
   }

   /////////////////////////////////////////////////////////////////////////
   //                          Counting Methods                           //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Count the number of instances of substring within a string.
    *
    * @param string     String to look for substring in.
    * @param substring  Sub-string to look for.
    * @return           Count of substrings in string.
    */
   public static int count(final String string, final String substring)
   {
      int count = 0;
      int idx = 0;

      while ((idx = string.indexOf(substring, idx)) != -1)
      {
         idx++;
         count++;
      }

      return count;
   }

   /**
    * Count the number of instances of character within a string.
    *
    * @param string     String to look for substring in.
    * @param c          Character to look for.
    * @return           Count of substrings in string.
    */
   public static int count(final String string, final char c)
   {
      return count(string, String.valueOf(c));
   }

   /////////////////////////////////////////////////////////////////////////
   //                            Padding Methods                          //
   /////////////////////////////////////////////////////////////////////////

   /**
    * Return a string padded with the given string for the given count.
    *
    * @param buff       String buffer used for padding (buffer is not reset).
    * @param string     Pad element.
    * @param count      Pad count.
    * @return           Padded string.
    */
   public static String pad(final StringBuffer buff, final String string,
      final int count)
   {
      for (int i = 0; i < count; i++)
      {
         buff.append(string);
      }

      return buff.toString();
   }

   /**
    * Return a string padded with the given string for the given count.
    *
    * @param string     Pad element.
    * @param count      Pad count.
    * @return           Padded string.
    */
   public static String pad(final String string, final int count)
   {
      return pad(new StringBuffer(), string, count);
   }

   /**
    * Return a string padded with the given string value of an object
    * for the given count.
    *
    * @param obj     Object to convert to a string.
    * @param count   Pad count.
    * @return        Padded string.
    */
   public static String pad(final Object obj, final int count)
   {
      return pad(new StringBuffer(), String.valueOf(obj), count);
   }

   /////////////////////////////////////////////////////////////////////////
   //                              Misc Methods                           //
   /////////////////////////////////////////////////////////////////////////

   /**
    *
Compare two strings.
    *
    *
Both or one of them may be null.
    * @param me
    * @param you
    *
    * @return true if object equals or intern ==, else false.
    */
   public static boolean compare(final String me, final String you)
   {
      // If both null or intern equals
      if (me == you)
         return true;

      // if me null and you are not
      if (me == null && you != null)
         return false;

      // me will not be null, test for equality
      return me.equals(you);
   }

   /**
    * Check if the given string is empty.
    *
    * @param string     String to check
    * @return           True if string is empty
    */
   public static boolean isEmpty(final String string)
   {
      return string.equals(EMPTY);
   }

   /**
    * Return the nth index of the given token occurring in the given string.
    *
    * @param string     String to search.
    * @param token      Token to match.
    * @param index      Nth index.
    * @return           Index of nth item or -1.
    */
   public static int nthIndexOf(final String string, final String token,
      final int index)
   {
      int j = 0;

      for (int i = 0; i < index; i++)
      {
         j = string.indexOf(token, j + 1);
         if (j == -1) break;
      }

      return j;
   }

   /**
    * Capitalize the first character of the given string.
    *
    * @param string     String to capitalize.
    * @return           Capitalized string.
    *
    * @throws IllegalArgumentException    String is <kk>null</kk> or empty.
    */
   public static String capitalize(final String string)
   {
      if (string == null)
         throw new NullArgumentException("string");
      if (string.equals(""))
         throw new EmptyStringException("string");

      return Character.toUpperCase(string.charAt(0)) + string.substring(1);
   }

   /**
    * Trim each string in the given string array.
    *
    *
This modifies the string array.
    *
    * @param strings    String array to trim.
    * @return           String array with each element trimmed.
    */
   public static String[] trim(final String[] strings)
   {
      for (int i = 0; i < strings.length; i++)
      {
         strings[i] = strings[i].trim();
      }

      return strings;
   }

   /**
    * Make a URL from the given string.
    *
    *

    * If the string is a properly formatted file URL, then the file
    * portion will be made canonical.
    *
    *

    * If the string is an invalid URL then it will be converted into a
    * file URL.
    *
    * @param urlspec           The string to construct a URL for.
    * @param relativePrefix    The string to prepend to relative file
    *                          paths, or null to disable prepending.
    * @return                  A URL for the given string.
    *
    * @throws MalformedURLException  Could not make a URL for the given string.
    */
   public static URL toURL(String urlspec, final String relativePrefix) throws MalformedURLException
   {
      urlspec = urlspec.trim();

      URL url;

      try
      {
         url = new URL(urlspec);
         if (url.getProtocol().equals("file"))
         {
            url = makeURLFromFilespec(url.getFile(), relativePrefix);
         }
      }
      catch (Exception e)
      {
         // make sure we have a absolute & canonical file url
         try
         {
            url = makeURLFromFilespec(urlspec, relativePrefix);
         }
         catch (IOException n)
         {
            //
            // jason: or should we rethrow e?
            //
            throw new MalformedURLException(n.toString());
         }
      }

      return url;
   }

   public static URI toURI(String urispec, final String relativePrefix)
      throws URISyntaxException
   {
      urispec = urispec.trim();

      URI uri;

      if( urispec.startsWith("file:") )
      {
         uri = makeURIFromFilespec(urispec.substring(5), relativePrefix);
      }
      else
      {
         uri = new URI(urispec);
      }

      return uri;
   }

   /** A helper to make a URL from a filespec. */
   private static URL makeURLFromFilespec(final String filespec, final String relativePrefix)
      throws IOException
   {
      // make sure the file is absolute & canonical file url
      File file = new File(decode(filespec));

      // if we have a prefix and the file is not abs then prepend
      if (relativePrefix != null && !file.isAbsolute())
      {
         file = new File(relativePrefix, filespec);
      }

      // make sure it is canonical (no ../ and such)
      file = file.getCanonicalFile();

      return file.toURI().toURL();
   }

   private static String decode(String filespec)
   {
      try
      {
         return URLDecoder.decode(filespec, "UTF-8");
      }
      catch (UnsupportedEncodingException e)
      {
         throw new RuntimeException("Error decoding filespec: " + filespec, e);
      }
   }

   private static URI makeURIFromFilespec(final String filespec, final String relativePrefix)
   {
      // make sure the file is absolute & canonical file url
      File file = new File(decode(filespec));

      // if we have a prefix and the file is not abs then prepend
      if (relativePrefix != null && !file.isAbsolute())
      {
         file = new File(relativePrefix, filespec);
      }

      return file.toURI();
   }

   /**
    * Make a URL from the given string.
    *
    * @see #toURL(String,String)
    *
    * @param urlspec    The string to construct a URL for.
    * @return           A URL for the given string.
    *
    * @throws MalformedURLException  Could not make a URL for the given string.
    */
   public static URL toURL(final String urlspec) throws MalformedURLException
   {
      return toURL(urlspec, null);
   }

   /**
    *
    * @param urispec
    * @return the uri
    * @throws URISyntaxException for any error
    */
   public static URI toURI(final String urispec)
      throws URISyntaxException
   {
      return toURI(urispec, null);
   }

   /**
    * Check whether the given String is a reserved Java Keyword
    * according to the Java Language Specifications.
    *
    * @param s String to check
    *
    * @return true if the given String is a reserved Java
    *         keyword, false otherwise.
    */
   public final static boolean isJavaKeyword(String s)
   {
      if (s == null || s.length() == 0)
      {
         return false;
      }

      for (int i = 0; i < keywords.length; i++)
      {
         if (keywords[i].equals(s))
         {
            return true;
         }
      }

      return false;
   }

   /**
    * Check whether the given String is an identifier according to the
    * EJB-QL definition. See The EJB 2.0 Documentation Section 11.2.6.1.
    *
    * @param s String to check
    *
    * @return true if the given String is a reserved
    *         identifier in EJB-QL, false otherwise.
    */
   public final static boolean isEjbQlIdentifier(String s)
   {
      if (s == null || s.length() == 0)
      {
         return false;
      }

      for (int i = 0; i < ejbQlIdentifiers.length; i++)
      {
         if (ejbQlIdentifiers[i].equalsIgnoreCase(s))
         {
            return true;
         }
      }

      return false;
   }

   /**
    * Check whether the given String is a valid identifier according
    * to the Java Language specifications.
    *
    * See The Java Language Specification Second Edition, Section 3.8
    * for the definition of what is a valid identifier.
    *
    * @param s String to check
    *
    * @return true if the given String is a valid Java
    *         identifier, false otherwise.
    */
   public final static boolean isValidJavaIdentifier(String s)
   {
      // an empty or null string cannot be a valid identifier
      if (s == null || s.length() == 0)
      {
         return false;
      }

      char[] c = s.toCharArray();
      if (!Character.isJavaIdentifierStart(c[0]))
      {
         return false;
      }

      for (int i = 1; i < c.length; i++)
      {
         if (!Character.isJavaIdentifierPart(c[i]))
         {
            return false;
         }
      }

      return true;
   }

   /**
    * Returns a new string with all the whitespace removed
    *
    * @param s the source string
    * @return the string without whitespace or null
    */
   public static String removeWhiteSpace(String s)
   {
      String retn = null;

      if (s != null)
      {
         int len = s.length();
         StringBuffer sbuf = new StringBuffer(len);

         for (int i = 0; i < len; i++)
         {
            char c = s.charAt(i);

            if (!Character.isWhitespace(c))
                sbuf.append(c);
         }
         retn = sbuf.toString();
      }
      return retn;
   }

   /**
    * The default toString implementation of an object
    *
    * @param object the object
    * @return a string in the form className@hexHashCode
    */
   public static final String defaultToString(Object object)
   {
      if (object == null)
         return "null";
      else
         return object.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(object));
   }

   /**
    * The default toString implementation of an object
    *
    * @param object the object
    * @param buffer the string builder
    */
   public static final void defaultToString(JBossStringBuilder buffer, Object object)
   {
      if (object == null)
         buffer.append("null");
      else
      {
         buffer.append(object.getClass().getName());
         buffer.append('@');
         buffer.append(Integer.toHexString(System.identityHashCode(object)));
      }
   }

   /**
    * The default toString implementation of an object
    *
    * @param object the object
    * @param buffer the string buffer
    */
   public static final void defaultToString(StringBuffer buffer, Object object)
   {
      if (object == null)
         buffer.append("null");
      else
      {
         buffer.append(object.getClass().getName());
         buffer.append('@');
         buffer.append(Integer.toHexString(System.identityHashCode(object)));
      }
   }

   /**
    * Parses a time period into a long.
    *
    * Translates possible [msec|sec|min|h] suffixes
    *
    * For example:
    *   "1"      ->  1 (msec)
    *   "1msec   ->  1 (msec)
    *   "1sec"   ->  1000 (msecs)
    *   "1min"   ->  60000 (msecs)
    *   "1h"     ->  3600000 (msecs)
    *
    * Accepts negative periods, e.g. "-1"
    *
    * @param period the stringfied time period
    * @return the parsed time period as long
    * @throws NumberFormatException
    */
   public static long parseTimePeriod(String period)
   {
      try
      {
         String s = period.toLowerCase();
         long factor;

         // look for suffix
         if (s.endsWith("msec"))
         {
            s = s.substring(0, s.lastIndexOf("msec"));
            factor = MSEC;
         }
         else if (s.endsWith("sec"))
         {
            s = s.substring(0, s.lastIndexOf("sec"));
            factor = SECS;
         }
         else if (s.endsWith("min"))
         {
            s = s.substring(0, s.lastIndexOf("min"));
            factor = MINS;
         }
         else if (s.endsWith("h"))
         {
            s = s.substring(0, s.lastIndexOf("h"));
            factor = HOUR;
         }
         else
         {
            factor = 1;
         }
         return Long.parseLong(s) * factor;
      }
      catch (RuntimeException e)
      {
         // thrown in addition when period is 'null'
         throw new NumberFormatException("For input time period: '" + period + "'");
      }
   }

   /**
    * Same like parseTimePeriod(), but guards for negative entries.
    *
    * @param period the stringfied time period
    * @return the parsed time period as long
    * @throws NumberFormatException
    */
   public static long parsePositiveTimePeriod(String period)
   {
      long retval = parseTimePeriod(period);
      if (retval < 0)
      {
         throw new NumberFormatException("Negative input time period: '" + period + "'");
      }
      return retval;
   }

   /**
    * Tokenize the given String into a String array via a StringTokenizer.
    *
    * The given delimiters string is supposed to consist of any number of
    * delimiter characters. Each of those characters can be used to separate
    * tokens. A delimiter is always a single character; for multi-character
    * delimiters, consider using delimitedListToStringArray
    *
    * @param str               the String to tokenize
    * @param delimiters        the delimiter characters, assembled as String
    *                          (each of those characters is individually considered as delimiter)
    * @param trimTokens        trim the tokens via String's trim
    * @param ignoreEmptyTokens omit empty tokens from the result array
    *                          (only applies to tokens that are empty after trimming; StringTokenizer
    *                          will not consider subsequent delimiters as token in the first place).
    * @return an array of the tokens (null if the input String was null)
    */
   public static String[] tokenizeToStringArray(String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens)
   {
      if (str == null)
      {
         return null;
      }
      StringTokenizer st = new StringTokenizer(str, delimiters);
      List<String> tokens = new ArrayList<String>();
      while (st.hasMoreTokens())
      {
         String token = st.nextToken();
         if (trimTokens)
         {
            token = token.trim();
         }
         if (!ignoreEmptyTokens || token.length() > 0)
         {
            tokens.add(token);
         }
      }
      return tokens.toArray(new String[tokens.size()]);
   }

   /**
    * Trim leading whitespace from the given String.
    *
    * @param str the string to check
    * @return the trimmed String
    * @see java.lang.Character#isWhitespace(char)
    */
   public static String trimLeadingWhitespace(String str)
   {
      return trimLeadingCharacter(str, CharacterChecker.WHITESPACE);
   }

   /**
    * Trim all occurences of the supplied leading character from the given String.
    *
    * @param str the string to check
    * @param leadingCharacter the leading character to be trimmed
    * @return the trimmed String
    */
   public static String trimLeadingCharacter(String str, final char leadingCharacter)
   {
      return trimLeadingCharacter(str, new CharacterChecker()
      {
         public boolean isCharacterLegal(char character)
         {
            return character == leadingCharacter;
         }
      });
   }

   /**
    * Trim all occurences of the supplied leading character from the given String.
    *
    * @param str the string to check
    * @param checker the character checker
    * @return the trimmed String
    */
   public static String trimLeadingCharacter(String str, CharacterChecker checker)
   {
      if (hasLength(str) == false)
      {
         return str;
      }

      if (checker == null)
         throw new IllegalArgumentException("Null character checker");

      StringBuffer buf = new StringBuffer(str);
      while (buf.length() > 0 && checker.isCharacterLegal(buf.charAt(0)))
      {
         buf.deleteCharAt(0);
      }
      return buf.toString();
   }

   /**
    * Check that the given string param is neither null nor of length 0.
    *
    * @param string the string
    * @return true if the String is not null and has length
    */
   public static boolean hasLength(String string)
   {
      return (string != null && string.length() > 0);
   }

   /**
    * Parse the given localeString into a {@link java.util.Locale}.
    *
    * This is the inverse operation of {@link java.util.Locale#toString Locale's toString}.
    *
    * @param localeString the locale string
    * @return a corresponding Locale instance
    */
   public static Locale parseLocaleString(String localeString)
   {
      String[] parts = tokenizeToStringArray(localeString, "_ ", false, false);
      String language = (parts.length > 0 ? parts[0] : "");
      String country = (parts.length > 1 ? parts[1] : "");
      String variant = "";
      if (parts.length >= 2)
      {
         // There is definitely a variant, and it is everything after the country
         // code sans the separator between the country code and the variant.
         int endIndexOfCountryCode = localeString.indexOf(country) + country.length();
         // Strip off any leading '_' and whitespace, what's left is the variant.
         variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode));
         if (variant.startsWith("_"))
         {
            variant = trimLeadingCharacter(variant, '_');
         }
      }
      return (language.length() > 0 ? new Locale(language, country, variant) : null);
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util.propertyeditor;

import org.jboss.util.threadpool.BlockingMode;

/**
 * A property editor for {@link org.jboss.util.threadpool.BlockingMode} enum.
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision$
 */
public class BlockingModeEditor extends TextPropertyEditorSupport
{
   /**
    * Returns a BlockingMode for the input object converted to a string.
    *
    * @return a BlockingMode enum instance
    *
    */
   public Object getValue()
   {
      String text = getAsText();
      BlockingMode mode = BlockingMode.toBlockingMode(text);
      return mode;
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util.file;

import java.io.File;
import java.io.FilenameFilter;

/**
 * A suffix based filename filter.
 *
 * @version <tt>$Revision$</tt>
 * @author  Jason Dillon
 */
public class FilenameSuffixFilter
   implements FilenameFilter
{
   /** The suffix which files must have to be accepted. */
   protected final String suffix;

   /** Flag to signal that we want to ignore the case. */
   protected final boolean ignoreCase;

   /**
    * Construct a <tt>FilenameSuffixFilter</tt>.
    *
    * @param suffix     The suffix which files must have to be accepted.
    * @param ignoreCase <tt>True</tt> if the filter should be case-insensitive.
    */
   public FilenameSuffixFilter(final String suffix,
                               final boolean ignoreCase)
   {
      this.ignoreCase = ignoreCase;
      this.suffix = (ignoreCase ? suffix.toLowerCase() : suffix);
   }

   /**
    * Construct a case sensitive <tt>FilenameSuffixFilter</tt>.
    *
    * @param suffix  The suffix which files must have to be accepted.
    */
   public FilenameSuffixFilter(final String suffix) {
      this(suffix, false);
   }

   /**
    * Check if a file is acceptible.
    *
    * @param dir  The directory the file resides in.
    * @param name The name of the file.
    * @return     <tt>true</tt> if the file is acceptable.
    */
   public boolean accept(final File dir, final String name) {
      if (ignoreCase) {
         return name.toLowerCase().endsWith(suffix);
      }
      else {
         return name.endsWith(suffix);
      }
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import org.jboss.logging.Logger;
import org.jboss.util.loading.ContextClassLoaderSwitcher;

/** An implementation of a timed cache. This is a cache whose entries have a
    limited lifetime with the ability to refresh their lifetime. The entries
    managed by the cache implement the TimedCachePolicy.TimedEntry interface. If
    an object inserted into the cache does not implement this interface, it will
    be wrapped in a DefaultTimedEntry and will expire without the possibility of
    refresh after getDefaultLifetime() seconds.

    This is a lazy cache policy in that objects are not checked for expiration
    until they are accessed.

    @author Scott Stark.
    @version $Revision$
*/
@SuppressWarnings("unchecked")
public class TimedCachePolicy
   extends TimerTask /* A legacy base class that is no longer used as this level */
   implements CachePolicy
{
   /**
    * Name of system property that this class consults to determine what
    * classloader to assign to its static {@link Timer}'s thread.
    */
   public static final String TIMER_CLASSLOADER_PROPERTY = "jboss.common.timedcachepolicy.timer.classloader";

   /**
    * Value for {@link #TIMER_CLASSLOADER_PROPERTY} indicating the
    * {@link ClassLoader#getSystemClassLoader() system classloader} should
    * be used. This is the default value if the system property is not set.
    */
   public static final String TIMER_CLASSLOADER_SYSTEM = "system";
   /**
    * Value for {@link #TIMER_CLASSLOADER_PROPERTY} indicating the
    * {@link Class#getClassLoader() classloader that loaded this class} should
    * be used.
    */
   public static final String TIMER_CLASSLOADER_CURRENT = "current";
   /**
    * Value for {@link #TIMER_CLASSLOADER_PROPERTY} indicating the
    * {@link Thread#getContextClassLoader() thread context classloader}
    * in effect when this class is loaded should be used.
    */
   public static final String TIMER_CLASSLOADER_CONTEXT = "context";

   /** The interface that cache entries support.
    */
   public static interface TimedEntry
   {
      /** Initializes an entry with the current cache time. This is called when
          the entry is first inserted into the cache so that entries do not
          have to know the absolute system time.
       * @param now
      */
      public void init(long now);

      /** Is the entry still valid basis the current time
       * @param now
          @return true if the entry is within its lifetime, false if it is expired.
      */
      public boolean isCurrent(long now);

      /** Attempt to extend the entry lifetime by refreshing it.
          @return true if the entry was refreshed successfully, false otherwise.
      */
      public boolean refresh();

      /** Notify the entry that it has been removed from the cache.
      */
      public void destroy();

      /** @return the value component of the TimedEntry. This may or may not
          be the TimedEntry implementation.
      */
      public Object getValue();
   }

   private static final Logger log = Logger.getLogger(TimedCachePolicy.class);

   protected static Timer resolutionTimer;

   static
   {
      // Don't leak the TCCL to the resolutionTimer thread
      ContextClassLoaderSwitcher.SwitchContext clSwitchContext = null;
      try
      {
         // See if the user configured what classloader they want
         String timerCl = AccessController.doPrivileged(new PrivilegedAction<String>()
         {
            public String run()
            {
               return System.getProperty(TIMER_CLASSLOADER_PROPERTY, TIMER_CLASSLOADER_SYSTEM);
            }
         });

         if (TIMER_CLASSLOADER_CONTEXT.equalsIgnoreCase(timerCl) == false)
         {
            ContextClassLoaderSwitcher clSwitcher = (ContextClassLoaderSwitcher) AccessController.doPrivileged(ContextClassLoaderSwitcher.INSTANTIATOR);
            if (TIMER_CLASSLOADER_CURRENT.equalsIgnoreCase(timerCl))
            {
               // Switches the TCCL to this class' classloader
               clSwitchContext = clSwitcher.getSwitchContext(TimedCachePolicy.class.getClassLoader());
            }
            else
            {
               if (TIMER_CLASSLOADER_SYSTEM.equalsIgnoreCase(timerCl) == false)
               {
                  log.warn("Unknown value " + timerCl + " found for property " +
                        TIMER_CLASSLOADER_PROPERTY + " -- using the system classloader");
               }
               clSwitchContext = clSwitcher.getSwitchContext(ClassLoader.getSystemClassLoader());
            }
         }
         resolutionTimer = new Timer(true);
      }
      catch (SecurityException e)
      {
         // For backward compatibility, don't blow up, just risk leaking the TCCL
         // TODO log a WARN or something?
         resolutionTimer = new Timer(true);
      }
      finally
      {
         // Restores the TCCL
         if (clSwitchContext != null)
            clSwitchContext.reset();
      }
   }

   /** The map of cached TimedEntry objects. */
   protected Map entryMap;
   /** The lifetime in seconds to use for objects inserted
       that do not implement the TimedEntry interface. */
   protected int defaultLifetime;
   /** A flag indicating if entryMap should be synchronized */
   protected boolean threadSafe;
   /** The caches notion of the current time */
   protected long now;
   /** The resolution in seconds of the cach current time */
   protected int resolution;
   /** */
   protected ResolutionTimer theTimer;

   /** Creates a new TimedCachePolicy with a default entry lifetime of 30 mins
       that does not synchronized access to its policy store and uses a 60
       second resolution.
   */
   public TimedCachePolicy()
   {
      this(30*60, false, 0);
   }
   /** Creates a new TimedCachePolicy with the given default entry lifetime
       that does not synchronized access to its policy store and uses a 60
       second resolution.
    * @param defaultLifetime
   */
   public TimedCachePolicy(int defaultLifetime)
   {
      this(defaultLifetime, false, 0);
   }
   /** Creates a new TimedCachePolicy with the given default entry lifetime
       that does/does not synchronized access to its policy store depending
       on the value of threadSafe.
       @param defaultLifetime - the lifetime in seconds to use for objects inserted
       that do not implement the TimedEntry interface.
       @param threadSafe - a flag indicating if the cach store should be synchronized
       to allow correct operation under multi-threaded access. If true, the
       cache store is synchronized. If false the cache store is unsynchronized and
       the cache is not thread safe.
       @param resolution - the resolution in seconds of the cache timer. A cache does
       not query the system time on every get() invocation. Rather the cache
       updates its notion of the current time every 'resolution' seconds.
   */
   public TimedCachePolicy(int defaultLifetime, boolean threadSafe, int resolution)
   {
      this.defaultLifetime = defaultLifetime;
      this.threadSafe = threadSafe;
      if( resolution <= 0 )
         resolution = 60;
      this.resolution = resolution;
   }

   // Service implementation ----------------------------------------------
   /** Initializes the cache for use. Prior to this the cache has no store.
    */
   public void create()
   {
      if( threadSafe )
         entryMap = Collections.synchronizedMap(new HashMap());
      else
         entryMap = new HashMap();
      now = System.currentTimeMillis();
   }
   /** Schedules this with the class resolutionTimer Timer object for
       execution every resolution seconds.
   */
   public void start()
   {
      theTimer = new ResolutionTimer();
      resolutionTimer.scheduleAtFixedRate(theTimer, 0, 1000*resolution);
   }
   /** Stop cancels the resolution timer and flush()es the cache.
    */
   public void stop()
   {
      theTimer.cancel();
      flush();
   }
   /** Clears the cache of all entries.
    */
   public void destroy()
   {
      entryMap.clear();
   }

   // --- Begin CachePolicy interface methods
   /** Get the cache value for key if it has not expired. If the TimedEntry
    is expired its destroy method is called and then removed from the cache.
    @return the TimedEntry value or the original value if it was not an
       instance of TimedEntry if key is in the cache, null otherwise.
   */
   public Object get(Object key)
   {
      TimedEntry entry = (TimedEntry) entryMap.get(key);
      if( entry == null )
         return null;

      if( entry.isCurrent(now) == false )
      {   // Try to refresh the entry
         if( entry.refresh() == false )
         {   // Failed, remove the entry and return null
            entry.destroy();
            entryMap.remove(key);
            return null;
         }
      }
      Object value = entry.getValue();
      return value;
   }
   /** Get the cache value for key. This method does not check to see if
       the entry has expired.
       @return the TimedEntry value or the original value if it was not an
       instancee of TimedEntry if key is in the cache, null otherwise.
   */
   public Object peek(Object key)
   {
      TimedEntry entry = (TimedEntry) entryMap.get(key);
      Object value = null;
      if( entry != null )
         value = entry.getValue();
      return value;
   }
   /** Insert a value into the cache. In order to have the cache entry
       reshresh itself value would have to implement TimedEntry and
       implement the required refresh() method logic.
       @param key - the key for the cache entry
       @param value - Either an instance of TimedEntry that will be inserted without
       change, or an abitrary value that will be wrapped in a non-refreshing
       TimedEntry.
   */
   public void insert(Object key, Object value)
   {
      if( entryMap.containsKey(key) )
         throw new IllegalStateException("Attempt to insert duplicate entry");
      TimedEntry entry = null;
      if( (value instanceof TimedEntry) == false )
      {   // Wrap the value in a DefaultTimedEntry
         entry = new DefaultTimedEntry(defaultLifetime, value);
      }
      else
      {
         entry = (TimedEntry) value;
      }
      entry.init(now);
      entryMap.put(key, entry);
   }
   /** Remove the entry associated with key and call destroy on the entry
    if found.
    */
   public void remove(Object key)
   {
      TimedEntry entry = (TimedEntry) entryMap.remove(key);
      if( entry != null )
         entry.destroy();
   }
   /** Remove all entries from the cache.
    */
   public void flush()
   {
      Map tmpMap = null;
      synchronized( this )
      {
         tmpMap = entryMap;
         if( threadSafe )
            entryMap = Collections.synchronizedMap(new HashMap());
         else
            entryMap = new HashMap();
      }

      // Notify the entries of their removal
      Iterator iter = tmpMap.values().iterator();
      while( iter.hasNext() )
      {
         TimedEntry entry = (TimedEntry) iter.next();
         entry.destroy();
      }
      tmpMap.clear();
   }

   public int size()
   {
      return entryMap.size();
   }
   // --- End CachePolicy interface methods

   /** Get the list of keys for entries that are not expired.
    *
    * @return A List of the keys corresponding to valid entries
    */
   public List getValidKeys()
   {
      ArrayList validKeys = new ArrayList();
      synchronized( entryMap )
      {
         Iterator iter = entryMap.entrySet().iterator();
         while( iter.hasNext() )
         {
            Map.Entry entry = (Map.Entry) iter.next();
            TimedEntry value = (TimedEntry) entry.getValue();
            if( value.isCurrent(now) == true )
               validKeys.add(entry.getKey());
         }
      }
      return validKeys;
   }

   /** Get the default lifetime of cache entries.
    @return default lifetime in seconds of cache entries.
    */
   public int getDefaultLifetime()
   {
      return defaultLifetime;
   }
   /** Set the default lifetime of cache entries for new values added to the cache.
    @param defaultLifetime - lifetime in seconds of cache values that do
    not implement TimedEntry.
    */
   public synchronized void setDefaultLifetime(int defaultLifetime)
   {
      this.defaultLifetime = defaultLifetime;
   }

   /**
    * Get the frequency of the current time snapshot.
    * @return the current timer resolution in seconds.
    */
   public int getResolution()
   {
      return resolution;
   }
   /** Set the cache timer resolution
    *
    @param resolution - the resolution in seconds of the cache timer. A cache does
    not query the system time on every get() invocation. Rather the cache
    updates its notion of the current time every 'resolution' seconds.
    */
   public synchronized void setResolution(int resolution)
   {
      if( resolution <= 0 )
         resolution = 60;
      if( resolution != this.resolution )
      {
         this.resolution = resolution;
         theTimer.cancel();
         theTimer = new ResolutionTimer();
         resolutionTimer.scheduleAtFixedRate(theTimer, 0, 1000*resolution);
      }
   }

   /** The TimerTask run method. It updates the cache time to the
       current system time.
   */
   public void run()
   {
      now = System.currentTimeMillis();
   }

   /** Get the cache time.
       @return the cache time last obtained from System.currentTimeMillis()
   */
   public long currentTimeMillis()
   {
      return now;
   }

   /** Get the raw TimedEntry for key without performing any expiration check.
    * @param key
       @return the TimedEntry value associated with key if one exists, null otherwise.
   */
   public TimedEntry peekEntry(Object key)
   {
      TimedEntry entry = (TimedEntry) entryMap.get(key);
      return entry;
   }

   /** The default implementation of TimedEntry used to wrap non-TimedEntry
       objects inserted into the cache.
   */
   static class DefaultTimedEntry implements TimedEntry
   {
      long expirationTime;
      Object value;

      DefaultTimedEntry(long lifetime, Object value)
      {
         this.expirationTime = 1000 * lifetime;
         this.value = value;
      }
      public void init(long now)
      {
         expirationTime += now;
      }
      public boolean isCurrent(long now)
      {
         return expirationTime > now;
      }
      public boolean refresh()
      {
         return false;
      }
      public void destroy()
      {
      }
      public Object getValue()
      {
         return value;
      }
   }

   /**

    */
   private class ResolutionTimer extends TimerTask
   {
      public void run()
      {
         TimedCachePolicy.this.run();
      }
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util.xml;

import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * A utility class to cover up the rough bits of xml parsing
 *
 * @author Chris Kimpton
 * @version $Revision$
 */
@SuppressWarnings("unchecked")
public class XmlHelper
{
   public static void write(Writer out, Document dom)
      throws Exception
   {
      new DOMWriter(out).setPrettyprint(true).print(dom);
   }

   /**
    * Returns an iterator over the children of the given element with
    * the given tag name.
    *
    * @param element    The parent element
    * @param tagName    The name of the desired child
    * @return           An interator of children or null if element is null.
    */
   public static Iterator getChildrenByTagName(Element element,
                                               String tagName)
   {
      if (element == null) return null;
      // getElementsByTagName gives the corresponding elements in the whole
      // descendance. We want only children

      NodeList children = element.getChildNodes();
      ArrayList goodChildren = new ArrayList();
      for (int i=0; i<children.getLength(); i++) {
         Node currentChild = children.item(i);
         if (currentChild.getNodeType() == Node.ELEMENT_NODE &&
             ((Element)currentChild).getTagName().equals(tagName)) {
            goodChildren.add(currentChild);
         }
      }
      return goodChildren.iterator();
   }

   /**
    * Gets the child of the specified element having the specified unique
    * name.  If there are more than one children elements with the same name
    * and exception is thrown.
    *
    * @param element    The parent element
    * @param tagName    The name of the desired child
    * @return           The named child.
    *
    * @throws Exception   Child was not found or was not unique.
    */
   public static Element getUniqueChild(Element element, String tagName)
      throws Exception
   {
      Iterator goodChildren = getChildrenByTagName(element, tagName);

      if (goodChildren != null && goodChildren.hasNext()) {
         Element child = (Element)goodChildren.next();
         if (goodChildren.hasNext()) {
            throw new Exception
               ("expected only one " + tagName + " tag");
         }
         return child;
      } else {
         throw new Exception
            ("expected one " + tagName + " tag");
      }
   }

   /**
    * Gets the child of the specified element having the
    * specified name. If the child with this name doesn't exist
    * then null is returned instead.
    *
    * @param element the parent element
    * @param tagName the name of the desired child
    * @return either the named child or null
    * @throws Exception
    */
   public static Element getOptionalChild(Element element, String tagName)
      throws Exception
   {
      return getOptionalChild(element, tagName, null);
   }

   /**
    * Gets the child of the specified element having the
    * specified name. If the child with this name doesn't exist
    * then the supplied default element is returned instead.
    *
    * @param element the parent element
    * @param tagName the name of the desired child
    * @param defaultElement the element to return if the child
    *                       doesn't exist
    * @return either the named child or the supplied default
    * @throws Exception
    */
   public static Element getOptionalChild(Element element,
                                          String tagName,
                                          Element defaultElement)
      throws Exception
   {
      Iterator goodChildren = getChildrenByTagName(element, tagName);

      if (goodChildren != null && goodChildren.hasNext()) {
         Element child = (Element)goodChildren.next();
         if (goodChildren.hasNext()) {
            throw new Exception
               ("expected only one " + tagName + " tag");
         }
         return child;
      } else {
         return defaultElement;
      }
   }

   /**
    * Get the content of the given element.
    *
    * @param element    The element to get the content for.
    * @return           The content of the element or null.
    * @throws Exception
    */
   public static String getElementContent(final Element element)
      throws Exception
   {
      return getElementContent(element, null);
   }

   /**
    * Get the content of the given element.
    *
    * @param element       The element to get the content for.
    * @param defaultStr    The default to return when there is no content.
    * @return              The content of the element or the default.
    * @throws Exception
    */
   public static String getElementContent(Element element, String defaultStr)
      throws Exception
   {
      if (element == null)
         return defaultStr;

      NodeList children = element.getChildNodes();
      String result = "";
      for (int i = 0; i < children.getLength(); i++)
      {
         if (children.item(i).getNodeType() == Node.TEXT_NODE ||
             children.item(i).getNodeType() == Node.CDATA_SECTION_NODE)
         {
            result += children.item(i).getNodeValue();
         }
         else if( children.item(i).getNodeType() == Node.COMMENT_NODE )
         {
            // Ignore comment nodes
         }
      }
      return result.trim();
   }

   /**
    * Macro to get the content of a unique child element.
    *
    * @param element    The parent element.
    * @param tagName    The name of the desired child.
    * @return           The element content or null.
    * @throws Exception
    */
   public static String getUniqueChildContent(Element element,
                                              String tagName)
      throws Exception
   {
      return getElementContent(getUniqueChild(element, tagName));
   }

   /**
    * Macro to get the content of an optional child element.
    *
    * @param element    The parent element.
    * @param tagName    The name of the desired child.
    * @return           The element content or null.
    * @throws Exception
    */
   public static String getOptionalChildContent(Element element,
                                                String tagName)
      throws Exception
   {
      return getElementContent(getOptionalChild(element, tagName));
   }

   public static boolean getOptionalChildBooleanContent(Element element, String name) throws Exception
   {
      Element child = getOptionalChild(element, name);
      if(child != null)
      {
         String value = getElementContent(child).toLowerCase();
         return value.equals("true") || value.equals("yes");
      }

      return false;
   }

}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.util.threadpool;

import java.security.AccessController;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import org.jboss.logging.Logger;
import org.jboss.util.collection.WeakValueHashMap;
import org.jboss.util.loading.ClassLoaderSource;
import org.jboss.util.loading.ContextClassLoaderSwitcher;

/**
 * A basic thread pool.
 * TODO: this port to jdk concurrent still needs to be tested.
 *
 * @author Adrian Brock
 * @author Scott.Stark@jboss.org
 * @version $Revision$
 */
@SuppressWarnings("unchecked")
public class BasicThreadPool implements ThreadPool, BasicThreadPoolMBean
{
   // Constants -----------------------------------------------------

   /** The jboss thread group */
   private static final ThreadGroup JBOSS_THREAD_GROUP = new ThreadGroup("JBoss Pooled Threads");

   /** The thread groups */
   private static final Map threadGroups = Collections.synchronizedMap(new WeakValueHashMap());

   /** The internal pool number */
   private static final AtomicInteger lastPoolNumber = new AtomicInteger(0);

   private static Logger log = Logger.getLogger(BasicThreadPool.class);

   // Attributes ----------------------------------------------------

   /** The thread pool name */
   private String name;

   /** The internal pool number */
   private int poolNumber;

   /** The blocking mode */
   private BlockingMode blockingMode = BlockingMode.ABORT;

   /** The pooled executor */
   private ThreadPoolExecutor executor;

   /** The queue */
   private LinkedBlockingQueue queue;

   /** The thread group */
   private ThreadGroup threadGroup;

   /** Source for the thread contrext classloader to assign to threads */
   private ClassLoaderSource classLoaderSource;

   private ContextClassLoaderSwitcher classLoaderSwitcher;

   /** The last thread number */
   private AtomicInteger lastThreadNumber = new AtomicInteger(0);

   /** Has the pool been stopped? */
   private AtomicBoolean stopped = new AtomicBoolean(false);
   /** The Heap<TimeoutInfo> of tasks ordered by their completion timeout */
   private PriorityBlockingQueue<TimeoutInfo> tasksWithTimeouts = new PriorityBlockingQueue<TimeoutInfo>(13);
   /** The task completion timeout monitor runnable */
   private TimeoutMonitor timeoutTask;
   /** The trace level logging flag */
   private boolean trace;

   // Static --------------------------------------------------------

   // Constructors --------------------------------------------------

   /**
    * Create a new thread pool
    */
   public BasicThreadPool()
   {
      this("ThreadPool");
   }

   /**
    * Create a new thread pool with a default queue size of 1024, max pool
    * size of 100, min pool size of 4, and a keep alive of 60 seconds.
    *
    * @param name the pool name
    */
   public BasicThreadPool(String name)
   {
      this(name, JBOSS_THREAD_GROUP);
   }

   /**
    * Create a new thread pool with a default queue size of 1024, max pool
    * size of 100, min pool size of 4, and a keep alive of 60 seconds.
    *
    * @param name the pool name
    * @param threadGroup threadGroup
    */
   public BasicThreadPool(String name, ThreadGroup threadGroup)
   {
      trace = log.isTraceEnabled();
      ThreadFactory factory = new ThreadPoolThreadFactory();

      queue = new LinkedBlockingQueue(1024);

      executor = new RestoreTCCLThreadPoolExecutor(4, 4, 60, TimeUnit.SECONDS, queue);
      executor.setThreadFactory(factory);
      executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());

      poolNumber = lastPoolNumber.incrementAndGet();
      setName(name);
      this.threadGroup = threadGroup;
   }

   // Public --------------------------------------------------------

   // ThreadPool ----------------------------------------------------

   public void stop(boolean immediate)
   {
      log.debug("stop, immediate="+immediate);
      stopped.set(true);
      if (immediate)
         executor.shutdownNow();
      else
         executor.shutdown();
   }

   public void waitForTasks() throws InterruptedException
   {
      executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
   }
   public void waitForTasks(long maxWaitTime) throws InterruptedException
   {
      executor.awaitTermination(maxWaitTime, TimeUnit.MILLISECONDS);
   }

   public void runTaskWrapper(TaskWrapper wrapper)
   {
      if( trace )
         log.trace("runTaskWrapper, wrapper="+wrapper);
      if (stopped.get())
      {
         wrapper.rejectTask(new ThreadPoolStoppedException("Thread pool has been stopped"));
         return;
      }

      wrapper.acceptTask();

      long completionTimeout = wrapper.getTaskCompletionTimeout();
      TimeoutInfo info;
      if(completionTimeout > 0)
      {
         checkTimeoutMonitor();
         // Install the task in the
         info = new TimeoutInfo(wrapper, completionTimeout);
         tasksWithTimeouts.add(info);
      }
      int waitType = wrapper.getTaskWaitType();
      switch (waitType)
      {
         case Task.WAIT_FOR_COMPLETE:
         {
            executeOnThread(wrapper);
            break;
         }
         default:
         {
            execute(wrapper);
         }
      }
      waitForTask(wrapper);
   }

   public void runTask(Task task)
   {
      BasicTaskWrapper wrapper = new BasicTaskWrapper(task);
      runTaskWrapper(wrapper);
   }

   public void run(Runnable runnable)
   {
      run(runnable, 0, 0);
   }

   public void run(Runnable runnable, long startTimeout, long completeTimeout)
   {
      RunnableTaskWrapper wrapper = new RunnableTaskWrapper(runnable, startTimeout, completeTimeout);
      runTaskWrapper(wrapper);
   }

   public ThreadGroup getThreadGroup()
   {
      return threadGroup;
   }

   // ThreadPoolMBean implementation --------------------------------

   public String getName()
   {
      return name;
   }

   public void setName(String name)
   {
      this.name = name;
   }

   public int getPoolNumber()
   {
      return poolNumber;
   }

   public String getThreadGroupName()
   {
      return threadGroup.getName();
   }

   public void setThreadGroupName(String threadGroupName)
   {
      ThreadGroup group;
      synchronized(threadGroups)
      {
         group = (ThreadGroup) threadGroups.get(threadGroupName);
         if (group == null)
         {
            group = new ThreadGroup(JBOSS_THREAD_GROUP, threadGroupName);
            threadGroups.put(threadGroupName, group);
         }
      }
      threadGroup = group;
   }

   public int getQueueSize()
   {
      return queue.size();
   }

   public int getMaximumQueueSize()
   {
      return queue.remainingCapacity() + queue.size();
   }

   /**
    * This resets the work queue capacity. This requires recreating the
    * work queue and ThreadPoolExecutor, so this needs to be called
    * before doing any work with the pool.
    *
    * @param size new work queue capacity
    */
   public void setMaximumQueueSize(int size)
   {
      // Reset the executor work queue
      ArrayList tmp = new ArrayList();
      queue.drainTo(tmp);
      queue = new LinkedBlockingQueue(size);
      queue.addAll(tmp);

      ThreadFactory tf = executor.getThreadFactory();
      RejectedExecutionHandler handler = executor.getRejectedExecutionHandler();
      long keepAlive = executor.getKeepAliveTime(TimeUnit.SECONDS);
      int cs = executor.getCorePoolSize();
      int mcs = executor.getMaximumPoolSize();
      executor = new ThreadPoolExecutor(cs, mcs, keepAlive, TimeUnit.SECONDS, queue);
      executor.setThreadFactory(tf);
      executor.setRejectedExecutionHandler(handler);
   }

   public int getPoolSize()
   {
      return executor.getPoolSize();
   }

   public int getMinimumPoolSize()
   {
      return executor.getCorePoolSize();
   }

   public void setMinimumPoolSize(int size)
   {
      synchronized (executor)
      {
         // Don't let the min size > max size
         if (executor.getMaximumPoolSize() < size)
         {
            executor.setCorePoolSize(size);
            executor.setMaximumPoolSize(size);
         }
      }
   }

   public int getMaximumPoolSize()
   {
      return executor.getMaximumPoolSize();
   }

   public void setMaximumPoolSize(int size)
   {
      synchronized (executor)
      {
         executor.setCorePoolSize(size);
         executor.setMaximumPoolSize(size);
      }
   }

   public long getKeepAliveTime()
   {
      return executor.getKeepAliveTime(TimeUnit.MILLISECONDS);
   }

   public void setKeepAliveTime(long time)
   {
      executor.setKeepAliveTime(time, TimeUnit.MILLISECONDS);
   }

   public BlockingMode getBlockingMode()
   {
      return blockingMode;
   }

   public void setBlockingMode(BlockingMode mode)
   {
      blockingMode = mode;

      if( blockingMode == BlockingMode.RUN )
      {
         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
      }
      else if( blockingMode == BlockingMode.WAIT )
      {
         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
      }
      else if( blockingMode == BlockingMode.DISCARD )
      {
         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
      }
      else if( blockingMode == BlockingMode.DISCARD_OLDEST )
      {
         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
      }
      else if( blockingMode == BlockingMode.ABORT )
      {
         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
      }
      else
      {
         throw new IllegalArgumentException("Failed to recognize mode: "+mode);
      }
   }

   /**
    * For backward compatibility with the previous string based mode
    * @param name - the string form of the mode enum
    */
   public void setBlockingMode(String name)
   {
      blockingMode = BlockingMode.toBlockingMode(name);
      if( blockingMode == null )
         blockingMode = BlockingMode.ABORT;
   }

   /**
    * For backward compatibility with the previous string based mode
    * This is needed for microcontainer as it gets confused with overloaded
    * setters.
    * @param name - the string form of the mode enum
    */
   public void setBlockingModeString(String name)
   {
      blockingMode = BlockingMode.toBlockingMode(name);
      if( blockingMode == null )
         blockingMode = BlockingMode.ABORT;
   }

   public ClassLoaderSource getClassLoaderSource()
   {
      return classLoaderSource;
   }

   public void setClassLoaderSource(ClassLoaderSource classLoaderSource)
   {
      if (classLoaderSource == null)
      {
         this.classLoaderSource = null;
         this.classLoaderSwitcher = null;
      }
      else if (classLoaderSwitcher == null)
      {
         try
         {
            this.classLoaderSwitcher = (ContextClassLoaderSwitcher) AccessController.doPrivileged(ContextClassLoaderSwitcher.INSTANTIATOR);
            this.classLoaderSource = classLoaderSource;
         }
         catch (SecurityException e)
         {
            log.error("Cannot manage context classloader for pool threads; " +
                      "Do not have setContextClassLoader permission");
         }
      }
      else
      {
         this.classLoaderSource = classLoaderSource;
      }
   }

   public ThreadPool getInstance()
   {
      return this;
   }

   public void stop()
   {
      stop(false);
   }

   // Object overrides ----------------------------------------------

   public String toString()
   {
      return name + '(' + poolNumber + ')';
   }

   // Package protected ---------------------------------------------

   // Protected -----------------------------------------------------

   /**
    * Execute a task on the same thread
    *
    * @param wrapper the task wrapper
    */
   protected void executeOnThread(TaskWrapper wrapper)
   {
      if( trace )
         log.trace("executeOnThread, wrapper="+wrapper);
      wrapper.run();
   }

   /**
    * Execute a task
    *
    * @param wrapper the task wrapper
    */
   protected void execute(TaskWrapper wrapper)
   {
      if( trace )
         log.trace("execute, wrapper="+wrapper);
      try
      {
         executor.execute(wrapper);
      }
      catch (Throwable t)
      {
         wrapper.rejectTask(new ThreadPoolFullException("Error scheduling work: " + wrapper, t));
      }
   }

   /**
    * Wait for a task
    *
    * @param wrapper the task wrapper
    */
   protected void waitForTask(TaskWrapper wrapper)
   {
      wrapper.waitForTask();
   }

   /**
    * Used to lazily create the task completion timeout thread and monitor
    */
   protected synchronized void checkTimeoutMonitor()
   {
      if( timeoutTask == null )
         timeoutTask = new TimeoutMonitor(name, log);
   }
   protected TimeoutInfo getNextTimeout()
   {
      return this.tasksWithTimeouts.poll();
   }

   protected void setDefaultThreadContextClassLoader(Thread thread)
   {
      if (classLoaderSwitcher != null)
      {
         ClassLoader cl = classLoaderSource == null ? null : classLoaderSource.getClassLoader();
         classLoaderSwitcher.setContextClassLoader(thread, cl);
      }
   }

   // Private -------------------------------------------------------

   // Inner classes -------------------------------------------------

   /**
    * A factory for threads
    */
   private class ThreadPoolThreadFactory implements ThreadFactory
   {
      public Thread newThread(Runnable runnable)
      {
         String threadName = BasicThreadPool.this.toString() + "-" + lastThreadNumber.incrementAndGet();
         Thread thread = new Thread(threadGroup, runnable, threadName);
         thread.setDaemon(true);
         BasicThreadPool.this.setDefaultThreadContextClassLoader(thread);
         return thread;
      }
   }

   private class RestoreTCCLThreadPoolExecutor extends ThreadPoolExecutor
   {
      public RestoreTCCLThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
                                           long keepAliveTime, TimeUnit unit,
                                           BlockingQueue<Runnable> workQueue)
      {
         super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
      }

      @Override
      protected void afterExecute(Runnable r, Throwable t)
      {
         try
         {
            super.afterExecute(r, t);
         }
         finally
         {

            BasicThreadPool.this.setDefaultThreadContextClassLoader(Thread.currentThread());
         }
      }

   }

   /** An encapsulation of a task and its completion timeout
    */
   private static class TimeoutInfo implements Comparable
   {
      long start;
      long timeoutMS;
      TaskWrapper wrapper;
      boolean firstStop;
      TimeoutInfo(TaskWrapper wrapper, long timeout)
      {
         this.start = System.currentTimeMillis();
         this.timeoutMS = start + timeout;
         this.wrapper = wrapper;
      }
      public void setTimeout(long timeout)
      {
         this.start = System.currentTimeMillis();
         this.timeoutMS = start + timeout;
      }
      /** Order TimeoutInfo based on the timestamp at which the task needs to
       * be completed by.
       * @param o a TimeoutInfo
       * @return the diff between this timeoutMS and the argument timeoutMS
       */
      public int compareTo(Object o)
      {
         TimeoutInfo ti = (TimeoutInfo) o;
         long to0 = timeoutMS;
         long to1 = ti.timeoutMS;
         return (int) (to0 - to1);
      }
      TaskWrapper getTaskWrapper()
      {
         return wrapper;
      }
      public long getTaskCompletionTimeout()
      {
         return wrapper.getTaskCompletionTimeout();
      }
      /** Get the time remaining to the complete timeout timestamp in MS.
       * @param now - the current System.currentTimeMillis value
       * @return the time remaining to the complete timeout timestamp in MS.
       */
      public long getTaskCompletionTimeout(long now)
      {
         return timeoutMS - now;
      }
      /** Invoke stopTask on the wrapper and indicate whether this was the first
       * time the task has been notified to stop.
       * @return true if this is the first stopTask, false on the second.
       */
      public boolean stopTask()
      {
         wrapper.stopTask();
         boolean wasFirstStop = firstStop == false;
         firstStop = true;
         return wasFirstStop;
      }
   }
   /**
    * The monitor runnable which validates that threads are completing within
    * the task completion timeout limits.
    */
   private class TimeoutMonitor implements Runnable
   {
      @SuppressWarnings("hiding")
      final Logger log;
      TimeoutMonitor(String name, Logger log)
      {
         this.log = log;
         Thread t = new Thread(this, name+" TimeoutMonitor");
         t.setDaemon(true);
         t.start();
      }
      /** The monitor thread loops until the pool is shutdown. It waits for
       * tasks with completion timeouts and sleeps until the next completion
       * timeout and then interrupts the associated task thread, and invokes
       * stopTask on the TaskWrapper. A new timeout check is then inserted with
       * a 1 second timeout to validate that the TaskWrapper has exited the
       * run method. If it has not, then the associated task thread is stopped
       * using the deprecated Thread.stop method since this is the only way to
       * abort a thread that is in spin loop for example.
       *
       * @todo this is not responsive to new tasks with timeouts smaller than
       * the current shortest completion expiration. We probably should interrupt
       * the thread on each insertion into the timeout heap to ensure better
       * responsiveness.
       */
      public void run()
      {
         boolean isStopped = stopped.get();
         while( isStopped == false )
         {
            boolean trace = log.isTraceEnabled();
            try
            {
               TimeoutInfo info = getNextTimeout();
               if( info != null )
               {
                  long now = System.currentTimeMillis();
                  long timeToTimeout = info.getTaskCompletionTimeout(now);
                  if( timeToTimeout > 0 )
                  {
                     if( trace )
                     {
                        log.trace("Will check wrapper="+info.getTaskWrapper()
                           +" after "+timeToTimeout);
                     }
                     Thread.sleep(timeToTimeout);
                  }
                  // Check the status of the task
                  TaskWrapper wrapper = info.getTaskWrapper();
                  if( wrapper.isComplete() == false )
                  {
                     if( trace )
                        log.trace("Failed completion check for wrapper="+wrapper);
                     if( info.stopTask() == true )
                     {
                        // Requeue the TimeoutInfo to see that the task exits run
                        info.setTimeout(1000);
                        tasksWithTimeouts.add(info);
                        if( trace )
                           log.trace("Rescheduled completion check for wrapper="+wrapper);
                     }
                  }
               }
               else
               {
                  Thread.sleep(1000);
               }
            }
            catch(InterruptedException e)
            {
               log.debug("Timeout monitor has been interrupted", e);
            }
            catch(Throwable e)
            {
               log.debug("Timeout monitor saw unexpected error", e);
            }
            isStopped = stopped.get();
         }
      }
   }
}