/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (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.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2002-03 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.dynamo.xmlrpc;

import java.util.*;
import org.apache.commons.lang.CharSet;
import org.apache.commons.lang.CharSetUtils;
import org.apache.log4j.Logger;
import org.w3c.dom.*;
import com.silverwrist.util.xml.*;
import com.silverwrist.dynamo.Namespaces;
import com.silverwrist.dynamo.app.ApplicationContainer;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;

public class XmlRpcSubSystem
  implements NamedObject, ComponentInitialize, ComponentShutdown, XmlRpcCreateSession, XmlRpcConfiguration
{
  /*--------------------------------------------------------------------------------
   * Internal class implementing background sweep
   *--------------------------------------------------------------------------------
   */

  private class SessionSweeper implements BackgroundTask
  {
    /*====================================================================
     * Constructor
     *====================================================================
     */

    SessionSweeper()
    { // do nothing
    } // end constructor

    /*====================================================================
     * Implementations from interface BackgroundTask
     *====================================================================
     */

    public void run(ServiceProvider services)
    {
      LinkedList dead_sessions = new LinkedList();
      long timer_tick = System.currentTimeMillis();
      synchronized (m_sessions)
      { // loop over the session list and add things to it
	Iterator it = m_sessions.values().iterator();
	while (it.hasNext())
	{ // get each session and test its expiration status
	  XmlRpcSessionInfo session = (XmlRpcSessionInfo)(it.next());
	  if (session.isExpired(timer_tick))
	    dead_sessions.addLast(session);

	} // end while

      } // end synchronizedBlock

      while (!(dead_sessions.isEmpty()))
      { // invalidate all the dead sessions
	XmlRpcSessionInfo session = (XmlRpcSessionInfo)(dead_sessions.removeFirst());
	session.invalidate();

      } // end while

    } // end run

  } // end class SessionSweeper

  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static Logger logger = Logger.getLogger(XmlRpcSubSystem.class);

  private static final long SWEEP_INTERVAL = 60000L;  // every minute
  private static final int DEFAULT_MAX_AGE = 3600;    // last for one hour

  private static final String[] METHOD_CHARS = { "A-Z", "a-z", "0-9", "_.:/" };

  private static final CharSet s_method_chars;

  private static final String PREFIX = "xmlrpcsess:";
  private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
  private static final int ID_LENGTH = 40;

  private static Random rng = new Random();  // random number generator

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private ApplicationContainer m_appcon = null;          // application container
  private LinkedList m_shutdown_list = new LinkedList(); // objects to be shut down
  private String m_name;                                 // name of this object
  private Capabilities m_capas = new Capabilities();     // capabilities manager
  private int m_default_maxage = DEFAULT_MAX_AGE;        // default maximum age of sessions
  private Hashtable m_sessions = new Hashtable();        // sessions we manage
  private ArrayList m_internal_disp = new ArrayList();   // internal dispatchers
  private Vector m_installed_disp = new Vector();        // dispatchers installed via the API
  private ArrayList m_config_disp = new ArrayList();     // dispatchers configured via the config data
  private ArrayList m_internal_fmap = new ArrayList();   // internal fault mappers
  private Vector m_installed_fmap = new Vector();        // fault mappers installed via the API
  private ArrayList m_config_fmap = new ArrayList();     // fault mappers configured via the config data
  private ComponentShutdown m_init_hook;                 // initialization services hook
  private ComponentShutdown m_sweep_task;                // sweep task

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  public XmlRpcSubSystem()
  {
    try
    { // Add internal dispatchers to the first list
      m_internal_disp.add(new RegisteredDispatcher("system\\.getCapabilities",m_capas));
      m_internal_disp.add(new RegisteredDispatcher("system\\.multicall",new MultiCallDispatcher(this)));
      IntrospectionDispatcher id = new IntrospectionDispatcher(this);
      m_internal_disp.add(new RegisteredDispatcher("system\\.listMethods",id));
      m_internal_disp.add(new RegisteredDispatcher("system\\.methodSignature",id));
      m_internal_disp.add(new RegisteredDispatcher("system\\.methodHelp",id));

    } // end try
    catch (ConfigException e)
    { // this shouldn't happen....
      logger.fatal("ACK! internal dispatcher add failed!",e);

    } // end catch

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal operations
   *--------------------------------------------------------------------------------
   */

  private static final ComponentInitialize getComponentInitialize(Object obj)
  {
    if (obj instanceof ComponentInitialize)
      return (ComponentInitialize)obj;
    if (obj instanceof ServiceProvider)
    { // it may be a service provider interface
      try
      { // look for ComponentInitialize as a service
	ServiceProvider sp = (ServiceProvider)obj;
	ComponentInitialize rc = (ComponentInitialize)(sp.queryService(ComponentInitialize.class));
	return rc;

      } // end try
      catch (NoSuchServiceException e)
      { // do nothing here
      } // end catch

    } // end if

    return null;  // no dice!

  } // end getComponentInitialize

  private static final ComponentShutdown getComponentShutdown(Object obj)
  {
    if (obj instanceof ComponentShutdown)
      return (ComponentShutdown)obj;
    if (obj instanceof ServiceProvider)
    { // it may be a service provider interface
      try
      { // look for ComponentShutdown as a service
	ServiceProvider sp = (ServiceProvider)obj;
	ComponentShutdown rc = (ComponentShutdown)(sp.queryService(ComponentShutdown.class));
	return rc;

      } // end try
      catch (NoSuchServiceException e)
      { // do nothing here
      } // end catch

    } // end if

    return null;  // no dice!

  } // end getComponentShutdown

  private static final boolean isValidMethodName(String name)
  {
    if ((name==null) || (name.length()==0))
      return false;
    for (int i=0; i<name.length(); i++)
    { // test each character in turn
      if (!(s_method_chars.contains(name.charAt(i))))
	return false;  // found a bogus character

    } // end for

    return true;   // all tests passed - ship it!

  } // end isValidMethodName

  private static final XmlRpcDispatcher scanDispatcherList(List l, String method_name)
  {
    Iterator it = l.iterator();
    while (it.hasNext())
    { // locate a dispatcher by matching the method name
      RegisteredDispatcher disp = (RegisteredDispatcher)(it.next());
      if (disp.match(method_name))
	return disp.getDispatcher();

    } // end while

    return null;

  } // end scanDispatcherList

  private static final void copyDispatchers(Set dest, List source)
  {
    Iterator it = source.iterator();
    while (it.hasNext())
    { // strip out all dispatchers and return them
      RegisteredDispatcher disp = (RegisteredDispatcher)(it.next());
      dest.add(disp.getDispatcher());
      
    } // end while

  } // end copyDispatchers

  private static final XmlRpcFaultMapper scanFaultMapperList(List l, Class klass)
  {
    Iterator it = l.iterator();
    while (it.hasNext())
    { // locate a fault mapper by matching the class
      RegisteredFaultMapper fmap = (RegisteredFaultMapper)(it.next());
      if (fmap.match(klass))
	return fmap.getFaultMapper();

    } // end while

    return null;

  } // end scanFaultMapperList

  private static final String generateID()
  {
    StringBuffer buf = new StringBuffer(PREFIX);
    for (int i=0; i<ID_LENGTH; i++)
      buf.append(ALPHABET.charAt(rng.nextInt(ALPHABET.length())));
    return buf.toString();

  } // end generateID

  private final XmlRpcDispatcher createDispatcherObject(Element config_root, ServiceProvider services)
      throws ConfigException
  {
    XMLLoader loader = XMLLoader.get();
    String klassname = null;
    try
    { // get the classname and load it, and create an object
      klassname = loader.getAttribute(config_root,"classname");
      if (logger.isDebugEnabled())
	logger.debug("createDispatcherObject: Creating new XmlRpcDispatcher of class " + klassname);

      Class klass = Class.forName(klassname);
      if (!(XmlRpcDispatcher.class.isAssignableFrom(klass)))
      { // this object is not a valid XmlRpcDispatcher
	logger.error("object " + klassname + " does not implement XmlRpcDispatcher");
	ConfigException ce = new ConfigException(XmlRpcSubSystem.class,"XmlRpcMessages","xdisp.notXD");
	ce.setParameter(0,klassname);
	throw ce;

      } // end if

      // instantiate the object, and initialize it as needed
      Object rc = klass.newInstance();
      ComponentInitialize init = getComponentInitialize(rc);
      if (init!=null)
	init.initialize(config_root,services);

      synchronized (this)
      { // save its shutdown hook for later
	ComponentShutdown sd = getComponentShutdown(rc);
	if (sd!=null)
	  m_shutdown_list.addFirst(sd);

      } // end synchronized block

      if (logger.isDebugEnabled())
	logger.debug("XML-RPC dispatcher (of class " + klassname + ") created");

      return (XmlRpcDispatcher)rc;

    } // end try
    catch (XMLLoadException e)
    { // error getting class name - throw the ConfigException
      logger.error("XML loader failure",e);
      throw new ConfigException(e);

    } // end catch
    catch (ClassNotFoundException e)
    { // the class was not found
      ConfigException ce = new ConfigException(XmlRpcSubSystem.class,"XmlRpcMessages","xdisp.classNotFound");
      ce.setParameter(0,klassname);
      throw ce;

    } // end catch
    catch (IllegalAccessException e)
    { // unable to create this object
      ConfigException ce = new ConfigException(XmlRpcSubSystem.class,"XmlRpcMessages","xdisp.createError");
      ce.setParameter(0,klassname);
      throw ce;

    } // end catch
    catch (InstantiationException e)
    { // unable to create this object
      ConfigException ce = new ConfigException(XmlRpcSubSystem.class,"XmlRpcMessages","xdisp.createError");
      ce.setParameter(0,klassname);
      throw ce;

    } // end catch

  } // end createDispatcherObject

  private final XmlRpcFaultMapper createFaultMapperObject(Element config_root, ServiceProvider services)
      throws ConfigException
  {
    XMLLoader loader = XMLLoader.get();
    String klassname = null;
    try
    { // get the classname and load it, and create an object
      klassname = loader.getAttribute(config_root,"classname");
      if (logger.isDebugEnabled())
	logger.debug("createFaultMapperObject: Creating new XmlRpcFaultMapper of class " + klassname);

      Class klass = Class.forName(klassname);
      if (!(XmlRpcFaultMapper.class.isAssignableFrom(klass)))
      { // this object is not a valid XmlRpcFaultMapper
	logger.error("object " + klassname + " does not implement XmlRpcFaultMapper");
	ConfigException ce = new ConfigException(XmlRpcSubSystem.class,"XmlRpcMessages","xfmap.notXFM");
	ce.setParameter(0,klassname);
	throw ce;

      } // end if

      // instantiate the object, and initialize it as needed
      Object rc = klass.newInstance();
      ComponentInitialize init = getComponentInitialize(rc);
      if (init!=null)
	init.initialize(config_root,services);

      synchronized (this)
      { // save its shutdown hook for later
	ComponentShutdown sd = getComponentShutdown(rc);
	if (sd!=null)
	  m_shutdown_list.addFirst(sd);

      } // end synchronized block

      if (logger.isDebugEnabled())
	logger.debug("XML-RPC fault mapper (of class " + klassname + ") created");

      return (XmlRpcFaultMapper)rc;

    } // end try
    catch (XMLLoadException e)
    { // error getting class name - throw the ConfigException
      logger.error("XML loader failure",e);
      throw new ConfigException(e);

    } // end catch
    catch (ClassNotFoundException e)
    { // the class was not found
      ConfigException ce = new ConfigException(XmlRpcSubSystem.class,"XmlRpcMessages","xfmap.classNotFound");
      ce.setParameter(0,klassname);
      throw ce;

    } // end catch
    catch (IllegalAccessException e)
    { // unable to create this object
      ConfigException ce = new ConfigException(XmlRpcSubSystem.class,"XmlRpcMessages","xfmap.createError");
      ce.setParameter(0,klassname);
      throw ce;

    } // end catch
    catch (InstantiationException e)
    { // unable to create this object
      ConfigException ce = new ConfigException(XmlRpcSubSystem.class,"XmlRpcMessages","xfmap.createError");
      ce.setParameter(0,klassname);
      throw ce;

    } // end catch

  } // end createFaultMapperObject

  private final SessionInfo lookupSession(String id, Request r)
  {
    XmlRpcSessionInfo rc = null;
    boolean is_new = false;
    synchronized (m_sessions)
    { // look up a session...
      rc = (XmlRpcSessionInfo)(m_sessions.get(id));
      if (rc==null)
      { // not present - add it with this ID, which must have been valid at one point
	rc = new XmlRpcSessionInfo(this,m_appcon,id);
	m_sessions.put(id,rc);
	is_new = true;

      } // end if

    } // end synchronized block

    if (is_new)
      rc.newSession(r);  // yay!  new session!
    else
      rc.touch();  // update last access time

    return rc;

  } // end lookupSession

  private final Object execDispatcher(XmlRpcDispatcher disp, Request r, Application app)
      throws Exception, FaultCode
  {
    Request my_req = r;
    String session_id = disp.getSessionIDParamValue(r,app);
    if (session_id!=null)
    { // get the session and "tack it" onto the request
      SessionInfo session = lookupSession(session_id,r);
      if (session!=null)
	my_req = new ReplaceSessionRequest(r,session);

    } // end if
    
    return disp.dispatchXmlRpcCall(my_req,app);

  } // end execDispatcher

  /*--------------------------------------------------------------------------------
   * Implementations from interface NamedObject
   *--------------------------------------------------------------------------------
   */

  /**
   * Returns the name of this object.
   *
   * @return The name of this object.
   */
  public String getName()
  {
    return m_name;

  } // end getName

  /*--------------------------------------------------------------------------------
   * Implementations from interface ComponentInitialize
   *--------------------------------------------------------------------------------
   */

  /**
   * Initialize the XML-RPC subsystem.
   *
   * @param config_root Pointer to the section of the Dynamo XML configuration file that configures this
   *                    particular component.  This is to be considered "read-only" by the component.
   * @param services An implementation of {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider}
   *                 which provides initialization services to the component.  This will include an implementation
   *                 of {@link com.silverwrist.dynamo.iface.ObjectProvider ObjectProvider} which may be used to
   *                 get information about other objects previously initialized by the application.
   * @exception com.silverwrist.dynamo.except.ConfigException If an error is encountered in the component
   *            configuration.
   */
  public void initialize(Element config_root, ServiceProvider services) throws ConfigException
  {
    XMLLoader loader = XMLLoader.get();
    try
    { // verify the right node name
      loader.verifyNodeName(config_root,"object");

      // get the object's name
      m_name = loader.getAttribute(config_root,"name");

      // get the default maximum age
      m_default_maxage = loader.getAttributeInt(config_root,"maxAge",DEFAULT_MAX_AGE);

      // load up a list of all dispatch objects to be initialized
      List l = loader.getMatchingSubElements(config_root,"dispatch");
      Iterator it = l.iterator();
      while (it.hasNext())
      { // load each dispatch object in turn
	Element d_elt = (Element)(it.next());
	String method_name = loader.getAttribute(d_elt,"method");
	XmlRpcDispatcher disp_obj = createDispatcherObject(d_elt,services);
	m_config_disp.add(new RegisteredDispatcher(method_name,disp_obj));

      } // end while

      // load up the list of all fault mapper objects to be initialized
      l = loader.getMatchingSubElements(config_root,"fault-mapper");
      it = l.iterator();
      while (it.hasNext())
      { // load each fault mapper object in turn
	Element fm_elt = (Element)(it.next());
	String klassname = loader.getAttribute(fm_elt,"for");
	Class klass = null;
	try
	{ // get the class for which this will translate
	  klass = Class.forName(klassname);
	  if (!(Exception.class.isAssignableFrom(klass)))
	  { // it's not an Exception - we can't have that
	    ConfigException ce = new ConfigException(XmlRpcSubSystem.class,"XmlRpcMessages",
						     "xfmap.badExceptClass");
	    ce.setParameter(0,klassname);
	    throw ce;

	  } // end if

	} // end try
	catch (ClassNotFoundException e)
	{ // class was not found
	  ConfigException ce = new ConfigException(XmlRpcSubSystem.class,"XmlRpcMessages",
						   "xfmap.noExceptClass");
	  ce.setParameter(0,klassname);
	  throw ce;

	} // end catch

	XmlRpcFaultMapper fm_obj = createFaultMapperObject(fm_elt,services);
	m_config_fmap.add(new RegisteredFaultMapper(klass,fm_obj));

      } // end while

      // load up the list of all capabilities to be added
      l = loader.getMatchingSubElements(config_root,"capability");
      it = l.iterator();
      while (it.hasNext())
      { // get each capability in turn and push it in
	Element capa_elt = (Element)(it.next());
	String name = loader.getAttribute(capa_elt,"name");
	String url = loader.getAttribute(capa_elt,"url");
	int version = loader.getAttributeInt(capa_elt,"version");
	m_capas.insertCapability(name,url,version);

      } // end while

    } // end try
    catch (XMLLoadException e)
    { // error loading XML config data
      throw new ConfigException(e);

    } // end catch

    // Get the application container.
    ObjectProvider op = (ObjectProvider)(services.queryService(ObjectProvider.class));
    m_appcon = (ApplicationContainer)(op.getObject(Namespaces.DYNAMO_APPLICATION_NAMESPACE,"__container__"));

    // Add the XmlRpcConfiguration service to Dynamo's init services.
    HookServiceProviders hooker = (HookServiceProviders)(services.queryService(HookServiceProviders.class));
    SingletonServiceProvider ssp = new SingletonServiceProvider("XmlRpcSubSystem",XmlRpcConfiguration.class,
								(XmlRpcConfiguration)this);
    m_init_hook = hooker.hookInitServiceProvider(ssp);

    // Set up the sweeper to run at a fixed rate.
    BackgroundScheduler sched = (BackgroundScheduler)(services.queryService(BackgroundScheduler.class));
    m_sweep_task = sched.runTaskFixedRate(new SessionSweeper(),SWEEP_INTERVAL,SWEEP_INTERVAL);

  } // end initialize

  /*--------------------------------------------------------------------------------
   * Implementations from interface ComponentShutdown
   *--------------------------------------------------------------------------------
   */

  /**
   * Shuts down the XML-RPC subsystem.
   */
  public void shutdown()
  {
    m_sweep_task.shutdown();  // shut down the sweep task
    m_sweep_task = null;

    m_init_hook.shutdown();   // shut down the initialization hook
    m_init_hook = null;

    if (logger.isDebugEnabled())
      logger.debug("XmlRpcSubSystem.shutdown(): " + m_shutdown_list.size() + " objects to blow away");

    while (m_shutdown_list.size()>0)
    { // shut down all components in reverse order of creation
      ComponentShutdown sd = (ComponentShutdown)(m_shutdown_list.removeFirst());
      sd.shutdown();

    } // end while

    m_sessions.clear();  // sessions already shut down by application container
    m_internal_disp.clear();
    m_installed_disp.clear();
    m_config_disp.clear();
    m_appcon = null;

  } // end shutdown

  /*--------------------------------------------------------------------------------
   * Implementations from interface XmlRpcCreateSession
   *--------------------------------------------------------------------------------
   */

  public SessionInfo createNewSession(Request r)
  {
    XmlRpcSessionInfo rc = null;
    synchronized (m_sessions)
    { // generate a new session ID and add it to the table
      String new_id = generateID();
      while (m_sessions.containsKey(new_id))
	new_id = generateID();
      rc = new XmlRpcSessionInfo(this,m_appcon,new_id);
      m_sessions.put(new_id,rc);

    } // end synchronized block

    rc.newSession(r);  // initiate me!
    return rc;

  } // end createNewSession

  /*--------------------------------------------------------------------------------
   * Implementations from interface XmlRpcConfiguration
   *--------------------------------------------------------------------------------
   */

  public ComponentShutdown addDispatcher(String method, XmlRpcDispatcher disp) throws ConfigException
  {
    RegisteredDispatcher rd = new RegisteredDispatcher(method,disp);
    m_installed_disp.add(rd);
    return new ShutdownVectorRemove(m_installed_disp,rd);

  } // end addDispatcher

  public ComponentShutdown addFaultMapper(Class klass, XmlRpcFaultMapper fmap) throws ConfigException
  {
    RegisteredFaultMapper rfm = new RegisteredFaultMapper(klass,fmap);
    m_installed_fmap.add(rfm);
    return new ShutdownVectorRemove(m_installed_fmap,rfm);

  } // end addFaultMapper

  public ComponentShutdown addCapability(String name, String url, int version)
  {
    return m_capas.addCapability(name,url,version);

  } // end addCapability

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  /**
   * Returns the default maximum inactive interval for XML-RPC sessions, in seconds.
   *
   * @return The default maximum inactive interval for XML-RPC sessions, in seconds.
   */
  int getDefaultMaxAge()
  {
    return m_default_maxage;

  } // end getDefaultMaxAge

  /**
   * Removes a session from the list of sessions we manage.
   *
   * @param id ID of the session to be removed.
   */
  void zapSession(String id)
  {
    m_sessions.remove(id);

  } // end zapSession

  XmlRpcDispatcher getDispatcher(String method_name)
  {
    XmlRpcDispatcher disp = scanDispatcherList(m_internal_disp,method_name);
    if (disp==null)
      disp = scanDispatcherList(m_installed_disp,method_name);
    if (disp==null)
      disp = scanDispatcherList(m_config_disp,method_name);
    return disp;

  } // end getDispatcher

  Set getAllDispatchers()
  {
    HashSet rc = new HashSet();
    copyDispatchers(rc,m_internal_disp);
    copyDispatchers(rc,m_installed_disp);
    copyDispatchers(rc,m_config_disp);
    return rc;
    
  } // end getAllDispatchers

  Object dispatch(Request r, Application app) throws Exception, FaultCode
  {
    // Get method name and make sure it's valid.
    String method_name = r.getQueryString();
    if (!isValidMethodName(method_name))
      throw new SystemFaultCode(SystemFaultCode.METHOD_NOT_FOUND,"Invalid method name: " + method_name);

    // Look for the appropriate dispatcher object.
    XmlRpcDispatcher disp = getDispatcher(method_name);
    if (disp==null)  // dispatcher not found!
      throw new XmlRpcMethodNotFound(method_name);

    // Execute the dispatcher!
    return execDispatcher(disp,r,app);

  } // end dispatch

  FaultCode translateException(Request r, Exception e)
  {
    Class klass = e.getClass();
    int code = 0;
    do
    { // scan upwards through the list of fault mappers and through class hierarchy
      XmlRpcFaultMapper fmap = scanFaultMapperList(m_internal_fmap,klass);
      if (fmap!=null)
      { // try the fault mapper
	code = fmap.getXmlRpcFaultCodeForException(r,e);
	if (code!=0)
	  break;
	else
	  fmap = null;

      } // end if

      if (fmap==null)
	fmap = scanFaultMapperList(m_installed_fmap,klass);
      if (fmap!=null)
      { // try the fault mapper
	code = fmap.getXmlRpcFaultCodeForException(r,e);
	if (code!=0)
	  break;
	else
	  fmap = null;

      } // end if

      if (fmap==null)
	fmap = scanFaultMapperList(m_config_fmap,klass);
      if (fmap!=null)
      { // try the fault mapper
	code = fmap.getXmlRpcFaultCodeForException(r,e);
	if (code!=0)
	  break;
	else
	  fmap = null;

      } // end if

      if (fmap==null)
	klass = klass.getSuperclass();  // try the superclass and see if that gets us anywhere

    } while (klass!=Throwable.class); // end do

    return new FaultCode(code,e);

  } // end translateException

  /*--------------------------------------------------------------------------------
   * Static initializer
   *--------------------------------------------------------------------------------
   */

  static
  { // evaluate the character set
    s_method_chars = CharSetUtils.evaluateSet(METHOD_CHARS);

  } // end static initializer

} // end class XmlRpcSubSystem
