/*
 * This software is distributed under following license based on modified BSD
 * style license.
 * ----------------------------------------------------------------------
 * 
 * Copyright 2009 The Nimbus2 Project. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE NIMBUS PROJECT ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE NIMBUS PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the Nimbus2 Project.
 */
package jp.ossc.nimbus.servlet;

import java.io.*;
import java.util.*;
import java.beans.*;
import java.net.*;
import java.lang.reflect.*;
import javax.servlet.*;
import javax.servlet.http.*;

import jp.ossc.nimbus.beans.*;
import jp.ossc.nimbus.core.*;
import jp.ossc.nimbus.core.ServiceLoader;
import jp.ossc.nimbus.util.ClassMappingTree;

/**
 * ServiceManagerFactoryサーブレット。<p>
 * サーブレットコンテナ上でのサービスのロードをサポートするサーブレットである。<br>
 * また、JMXサーバが存在しない環境での、サービスの管理をサポートするために、
 * HTTP経由でのサービスの管理をサポートする管理コンソールを提供する。<br>
 * このサーブレットには、以下の初期化パラメータがある。<br>
 * <table border="1" width="90%">
 *     <tr bgcolor="#cccccc"><th>#</th><th>パラメータ名</th><th>値の説明</th><th>デフォルト</th></tr>
 *     <tr><td>1</td><td>ServicePaths</td><td>ロードするサービス定義ファイルのパスをカンマ区切りで指定する。<br>パスは、絶対パス、相対パス、クラスパスが指定できる。<br>ここで指定されたサービス定義は、サーブレットの初期化時に指定された順番にロードされ、サーブレットの破棄時に指定された順と逆順でアンロードされる。</td><td></td></tr>
 *     <tr><td>2</td><td>CheckLoadManagerCompleted</td><td>サービス定義のロード完了チェックを行うかどうかを指定する。<br>チェックを行う場合は、trueを指定する。</td><td>false</td></tr>
 *     <tr><td>3</td><td>CheckLoadManagerCompletedBy</td><td>サービス定義のロード完了チェックをServiceManager単位で行いたい場合に、ServiceManagerの名前を指定する。<br>初期化パラメータCheckLoadManagerCompletedがtrueの場合だけ、有効である。</td><td></td></tr>
 *     <tr><td>4</td><td>Validate</td><td>サービス定義の検証を行うかどうかを指定する。<br>検証を行う場合は、trueを指定する。</td><td>false</td></tr>
 *     <tr><td>5</td><td>ConsoleEnabled</td><td>このサーブレットが提供する管理コンソールを有効にするかどうかを指定する。<br>有効にする場合は、trueを指定する。</td><td>false</td></tr>
 * </table>
 * <p>
 * 以下に、サーブレットのweb.xml定義例を示す。<br>
 * <pre>
 * &lt;servlet&gt;
 *     &lt;servlet-name&gt;NimbusServlet&lt;/servlet-name&gt;
 *     &lt;servlet-class&gt;jp.ossc.nimbus.servlet.ServiceManagerFactoryServlet&lt;/servlet-class&gt;
 *     &lt;init-param&gt;
 *         &lt;param-name&gt;ServicePaths&lt;/param-name&gt;
 *         &lt;param-value&gt;sample1-service.xml,sample2-service.xml&lt;/param-value&gt;
 *     &lt;/init-param&gt;
 *     &lt;init-param&gt;
 *         &lt;param-name&gt;CheckLoadManagerCompleted&lt;/param-name&gt;
 *         &lt;param-value&gt;true&lt;/param-value&gt;
 *     &lt;/init-param&gt;
 *     &lt;init-param&gt;
 *         &lt;param-name&gt;ConsoleEnabled&lt;/param-name&gt;
 *         &lt;param-value&gt;true&lt;/param-value&gt;
 *     &lt;/init-param&gt;
 *     &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
 * &lt;/servlet&gt;
 * 
 * &lt;servlet-mapping&gt;
 *     &lt;servlet-name&gt;NimbusServlet&lt;/servlet-name&gt;
 *     &lt;url-pattern&gt;/nimbus&lt;/url-pattern&gt;
 * &lt;/servlet-mapping&gt;
 * </pre>
 * 
 * @author M.Takata
 */
public class ServiceManagerFactoryServlet extends HttpServlet{
    
    private static final long serialVersionUID = 5668270241695101050L;
    
    /**
     * ロードするサービス定義ファイルを指定するための初期化パラメータ名。<p>
     */
    protected static final String SERVICE_PATHS = "ServicePaths";
    
    /**
     * サービス定義ロード完了チェックを行うかどうかを指定するための初期化パラメータ名。<p>
     */
    protected static final String CHECK_LOAD_MNG_CMP = "CheckLoadManagerCompleted";
    
    /**
     * サービス定義ロード完了チェックをServiceManager単位で行う場合のServiceManager名を指定するための初期化パラメータ名。<p>
     */
    protected static final String CHECK_LOAD_MNG_CMP_BY = "CheckLoadManagerCompletedBy";
    
    /**
     * サービス定義ファイルを検証するかどうかための初期化パラメータ名。<p>
     */
    protected static final String VALIDATE = "Validate";
    
    /**
     * 管理コンソールを有効にするかどうかを指定するための初期化パラメータ名。<p>
     */
    protected static final String CONSOLE_ENABLED = "ConsoleEnabled";
    
    /**
     * 管理コンソールからの属性設定を有効にするかどうかを指定するための初期化パラメータ名。<p>
     */
    protected static final String ATTR_SET_ENABLED = "AttributeSetEnabled";
    
    /**
     * 管理コンソールからのメソッド呼び出しを有効にするかどうかを指定するための初期化パラメータ名。<p>
     */
    protected static final String METHOD_CALL_ENABLED = "MethodCallEnabled";
    
    /**
     * 無視するメソッドのシグニチャ配列。<p>
     */
    protected static final String IGNORE_METHODS = "IgnoreMethods";
    
    private static final String ATTRIBUTE_READ_ONLY = "r";
    private static final String ATTRIBUTE_WRITE_ONLY = "w";
    private static final String ATTRIBUTE_READ_AND_WRITE = "rw";
    
    private static Method[] DEFAULT_IGNORE_METHODS;
    
    static{
        List<Method> methods = new ArrayList<Method>();
        try{
            methods.add(
                jp.ossc.nimbus.service.semaphore.Semaphore.class.getMethod(
                    "getResource"
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.semaphore.Semaphore.class.getMethod(
                    "getResource", Integer.TYPE
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.semaphore.Semaphore.class.getMethod(
                    "getResource", Long.TYPE
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.semaphore.Semaphore.class.getMethod(
                    "getResource", Long.TYPE, Integer.TYPE
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.semaphore.Semaphore.class.getMethod(
                    "getResource",
                    Long.TYPE, Integer.TYPE, Long.TYPE
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.resource.ResourceFactory.class.getMethod(
                    "makeResource",
                    Object.class
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.sql.ConnectionFactory.class.getMethod(
                    "getConnection"
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.jms.JMSConnectionFactory.class.getMethod(
                    "getConnection"
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.jms.JMSConnectionFactory.class.getMethod(
                    "getConnection", String.class, String.class
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.jms.JMSSessionFactory.class.getMethod(
                    "getSession"
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.jms.JMSSessionFactory.class.getMethod("getSession", Boolean.TYPE, Integer.TYPE)
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.jms.JMSSessionFactory.class.getMethod(
                    "getSession",
                    javax.jms.Connection.class
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.jms.JMSSessionFactory.class.getMethod(
                    "getSession",
                    javax.jms.Connection.class,
                    Boolean.TYPE,
                    Integer.TYPE
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        try{
            methods.add(
                jp.ossc.nimbus.service.resource.ResourceFactory.class.getMethod(
                    "makeResource",
                    java.lang.Object.class
                )
            );
        }catch(NoClassDefFoundError e){
        }catch(NoSuchMethodException e){
            // 起こらないはず
            e.printStackTrace();
        }
        if(methods.size() != 0){
            DEFAULT_IGNORE_METHODS = (Method[])methods.toArray(
                new Method[methods.size()]
            );
        }
    }
    
    private ClassMappingTree<Method> ignoreMethodMap;
    
    /**
     * サーブレットの初期化を行う。<p>
     * サービス定義のロード及びロード完了チェックを行う。
     *
     * @exception ServletException サーブレットの初期化に失敗した場合
     */
    public void init() throws ServletException{
        final String[] servicePaths = getServicePaths();
        
        final boolean isValidate = isValidate();
        if(servicePaths != null && servicePaths.length != 0){
            for(int i = 0; i < servicePaths.length; i++){
                ServiceManagerFactory.loadManager(
                    servicePaths[i],
                    true,
                    isValidate
                );
            }
        }
        
        if(isCheckLoadManagerCompleted()){
            final String[] managerNames = getCheckLoadManagerCompletedBy();
            if(managerNames == null || managerNames.length == 0){
                ServiceManagerFactory.checkLoadManagerCompleted();
            }else{
                for(int i = 0; i < managerNames.length; i++){
                    ServiceManagerFactory.checkLoadManagerCompletedBy(
                        managerNames[i]
                    );
                }
            }
        }
        
        if(DEFAULT_IGNORE_METHODS != null){
            ignoreMethodMap = new ClassMappingTree<Method>();
            for(int i = 0; i < DEFAULT_IGNORE_METHODS.length; i++){
                ignoreMethodMap.add(
                    DEFAULT_IGNORE_METHODS[i].getDeclaringClass(),
                    DEFAULT_IGNORE_METHODS[i]
                );
            }
        }
        
        final Method[] ignoreMethods = getIgnoreMethods();
        if(ignoreMethods != null && ignoreMethods.length != 0){
            if(ignoreMethodMap == null){
                ignoreMethodMap = new ClassMappingTree<Method>();
            }
            for(int i = 0; i < ignoreMethods.length; i++){
                ignoreMethodMap.add(
                    ignoreMethods[i].getDeclaringClass(),
                    ignoreMethods[i]
                );
            }
        }
    }
    
    private boolean isConsoleEnabled(){
        final ServletConfig config = getServletConfig();
        final String isEnabled = config.getInitParameter(CONSOLE_ENABLED);
        return isEnabled == null ? false : Boolean.valueOf(isEnabled).booleanValue();
    }
    
    private boolean isAttributeSetEnabled(){
        final ServletConfig config = getServletConfig();
        final String isEnabled = config.getInitParameter(ATTR_SET_ENABLED);
        return isEnabled == null ? false : Boolean.valueOf(isEnabled).booleanValue();
    }
    
    private boolean isMethodCallEnabled(){
        final ServletConfig config = getServletConfig();
        final String isEnabled = config.getInitParameter(METHOD_CALL_ENABLED);
        return isEnabled == null ? false : Boolean.valueOf(isEnabled).booleanValue();
    }
    
    private String getServicePath(String path){
        final ServletContext context
             = getServletConfig().getServletContext();
        String result = context.getRealPath(path);
        if(result == null){
            result = path;
        }
        return result;
    }
    
    private String[] getServicePaths(){
        final ServletConfig config = getServletConfig();
        final String servicePathString = config.getInitParameter(SERVICE_PATHS);
        if(servicePathString == null){
            return null;
        }
        final StringArrayEditor editor = new StringArrayEditor();
        editor.setAsText(servicePathString);
        String[] servicePaths = (String[])editor.getValue();
        for(int i = 0; i < servicePaths.length; i++){
            servicePaths[i] = getServicePath(servicePaths[i]);
        }
        return servicePaths;
    }
    
    private boolean isCheckLoadManagerCompleted(){
        final ServletConfig config = getServletConfig();
        final String isCheck = config.getInitParameter(CHECK_LOAD_MNG_CMP);
        return isCheck == null ? false : Boolean.valueOf(isCheck).booleanValue();
    }
    
    private boolean isValidate(){
        final ServletConfig config = getServletConfig();
        final String isValidate = config.getInitParameter(VALIDATE);
        return isValidate == null ?
             false : Boolean.valueOf(isValidate).booleanValue();
    }
    
    private String[] getCheckLoadManagerCompletedBy(){
        final ServletConfig config = getServletConfig();
        final String managerNames
            = config.getInitParameter(CHECK_LOAD_MNG_CMP_BY);
        final StringArrayEditor editor = new StringArrayEditor();
        editor.setAsText(managerNames);
        return (String[])editor.getValue();
    }
    
    private Method[] getIgnoreMethods(){
        final ServletConfig config = getServletConfig();
        final String ignoreMethodsStr = config.getInitParameter(IGNORE_METHODS);
        if(ignoreMethodsStr == null){
            return null;
        }
        final MethodArrayEditor editor = new MethodArrayEditor();
        editor.setAsText(ignoreMethodsStr);
        return (Method[])editor.getValue();
    }
    
    /**
     * POSTリクエスト処理を行う。<p>
     *
     * @param req HTTPリクエスト
     * @param resp HTTPレスポンス
     * @exception ServletException 
     * @exception IOException 
     */
    protected void doPost(
        HttpServletRequest req,
        HttpServletResponse resp
    ) throws ServletException, IOException{
        process(req, resp);
    }
    
    /**
     * GETリクエスト処理を行う。<p>
     *
     * @param req HTTPリクエスト
     * @param resp HTTPレスポンス
     * @exception ServletException 
     * @exception IOException 
     */
    protected void doGet(
        HttpServletRequest req,
        HttpServletResponse resp
    ) throws ServletException, IOException{
        process(req, resp);
    }
    
    /**
     * リクエスト処理を行う。<p>
     * 管理コンソール処理を行う。
     *
     * @param req HTTPリクエスト
     * @param resp HTTPレスポンス
     * @exception ServletException 
     * @exception IOException 
     */
    protected void process(
        HttpServletRequest req,
        HttpServletResponse resp
    ) throws ServletException, IOException{
        
        if(!isConsoleEnabled()){
            resp.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
            return;
        }
        req.setCharacterEncoding("UTF-8");
        
        final String action = req.getParameter("action");
        if(action == null){
            processIndexResponse(req, resp);
        }else if(action.equals("manager")){
            processManagerResponse(req, resp);
        }else if(action.equals("service")){
            processServiceResponse(req, resp);
        }else if(action.equals("set")){
            if(!isAttributeSetEnabled()){
                resp.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
                return;
            }
            processSetAttributeResponse(req, resp);
        }else if(action.equals("call")){
            if(!isMethodCallEnabled()){
                resp.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
                return;
            }
            processCallAttributeResponse(req, resp);
        }else{
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }
    
    /**
     * 管理コンソールのトップ画面リクエスト処理を行う。<p>
     *
     * @param req HTTPリクエスト
     * @param resp HTTPレスポンス
     * @exception ServletException 
     * @exception IOException 
     */
    protected void processIndexResponse(
        HttpServletRequest req,
        HttpServletResponse resp
    ) throws ServletException, IOException{
        resp.setContentType("text/html;charset=UTF-8");
        final StringBuilder buf = new StringBuilder();
        buf.append("<html>");
        buf.append("<head><title>Nimbus ServiceManagerFactory</title></head>");
        buf.append("<body>");
        
        buf.append("<b>Service definition paths</b><br>");
        buf.append("<ul>");
        final Collection<ServiceLoader> loaderSet = ServiceManagerFactory.getLoaders();
        final String[] serviceURLs = new String[loaderSet.size()];
        int count = 0;
        for(ServiceLoader loader : loaderSet){
            serviceURLs[count++] = loader.getServiceURL().toString();
        }
        Arrays.sort(serviceURLs);
        for(int i = 0; i < serviceURLs.length; i++){
            String fileName = serviceURLs[i];
            final int index = fileName.lastIndexOf('/');
            if(index != -1){
                fileName = fileName.substring(index + 1);
            }
            buf.append("<li>").append("<a href=\"").append(serviceURLs[i])
                .append("\">").append(fileName)
                .append("</a>").append("</li>");
        }
        buf.append("</ul>");
        buf.append("<p>");
        
        buf.append("<b>Service managers</b><br>");
        buf.append("<ul>");
        final ServiceManager[] managers = ServiceManagerFactory.findManagers();
        final String[] managerNames = new String[managers.length];
        for(int i = 0; i < managers.length; i++){
            managerNames[i] = managers[i].getServiceName();
        }
        Arrays.sort(managerNames);
        StringBuilder url = new StringBuilder();
        for(int i = 0; i < managerNames.length; i++){
            url.setLength(0);
            url.append(getCurrentPath(req))
               .append("?action=manager&name=")
               .append(managerNames[i]);
            buf.append("<li>");
            buf.append("<a href=\"")
               .append(resp.encodeURL(url.toString()))
               .append("\">");
            buf.append(managerNames[i]).append("</a>");
            buf.append("</li>");
        }
        buf.append("</ul>");
        
        buf.append("</body>");
        buf.append("</html>");
        resp.getWriter().println(buf.toString());
    }
    
    private String getCurrentPath(HttpServletRequest req){
        String path = req.getServletPath();
        if(path.endsWith("/")){
            return "." + (req.getPathInfo() == null ? "" : req.getPathInfo());
        }else{
            return "." + path;
        }
    }
    
    /**
     * 管理コンソールのServiceManager画面リクエスト処理を行う。<p>
     *
     * @param req HTTPリクエスト
     * @param resp HTTPレスポンス
     * @exception ServletException 
     * @exception IOException 
     */
    protected void processManagerResponse(
        HttpServletRequest req,
        HttpServletResponse resp
    ) throws ServletException, IOException{
        final String managerName = req.getParameter("name");
        final ServiceManager manager = ServiceManagerFactory.findManager(managerName);
        resp.setContentType("text/html;charset=UTF-8");
        final StringBuilder buf = new StringBuilder();
        buf.append("<html>");
        buf.append("<head><title>Nimbus ServiceManager ")
            .append(managerName).append("</title></head>");
        buf.append("<body>");
        
        buf.append("<a href=\"").append(getCurrentPath(req))
            .append("\">ServiceManagerFactory</a>");
        buf.append("<hr>");
        
        buf.append("<b>ServiceManager name : </b>").append(managerName);
        
        buf.append("<b>Services</b><br>");
        buf.append("<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\" width=\"90%\">");
        buf.append("<tr bgcolor=\"#cccccc\"><th>name</th><th>class</th></tr>");
        final ServiceName[] serviceNames = new ServiceName[manager.serviceNameSet().size()];
        int count = 0;
        for(String serviceNameStr : manager.serviceNameSet()){
            serviceNames[count++] = new ServiceName(managerName, serviceNameStr);
        }
        Arrays.sort(serviceNames);
        final StringBuilder url = new StringBuilder();
        for(int i = 0; i < serviceNames.length; i++){
            final ServiceName serviceName = serviceNames[i];
            url.setLength(0);
            url.append(getCurrentPath(req))
               .append("?action=service&name=")
               .append(URLEncoder.encode(serviceName.toString(), "UTF-8"));
            buf.append("<tr>");
            buf.append("<td><a href=\"")
               .append(resp.encodeURL(url.toString()))
               .append("\">");
            buf.append(serviceName.getServiceName()).append("</a></td><td>&nbsp;")
                .append(getTargetObject(ServiceManagerFactory.getService(serviceName)).getClass().getName())
                .append("</td>");
            buf.append("</tr>");
        }
        buf.append("</table>");
        
        buf.append("<hr>");
        buf.append("<a href=\"").append(getCurrentPath(req))
            .append("\">ServiceManagerFactory</a>");
        
        buf.append("</body>");
        buf.append("</html>");
        resp.getWriter().println(buf.toString());
    }
    
    private Object getTargetObject(Service service){
        Object targetObj = service;
        if(service instanceof GenericsServiceProxy){
            targetObj = ServiceManagerFactory.getServiceObject(
                service.getServiceManagerName(),
                service.getServiceName()
            );
        }
        return targetObj;
    }
    
    /**
     * 管理コンソールのService画面リクエスト処理を行う。<p>
     *
     * @param req HTTPリクエスト
     * @param resp HTTPレスポンス
     * @exception ServletException 
     * @exception IOException 
     */
    protected void processServiceResponse(
        HttpServletRequest req,
        HttpServletResponse resp
    ) throws ServletException, IOException{
        final String serviceNameStr = req.getParameter("name");
        final ServiceNameEditor editor = new ServiceNameEditor();
        editor.setAsText(serviceNameStr);
        final ServiceName serviceName = (ServiceName)editor.getValue();
        final Service service = ServiceManagerFactory.getService(serviceName);
        
        final Class<?> targetClass = getTargetObject(service).getClass();
        
        resp.setContentType("text/html;charset=UTF-8");
        final StringBuilder buf = new StringBuilder();
        buf.append("<html>");
        buf.append("<head><title>Nimbus Service ")
            .append(serviceNameStr).append("</title></head>");
        buf.append("<body>");
        
        buf.append("<a href=\"").append(getCurrentPath(req))
            .append("\">ServiceManagerFactory</a>");
        buf.append("/");
        final StringBuilder url = new StringBuilder();
        url.append(getCurrentPath(req))
            .append("?action=manager&name=")
            .append(serviceName.getServiceManagerName());
        buf.append("<a href=\"").append(resp.encodeURL(url.toString()))
            .append("\">ServiceManager</a>");
        buf.append("<hr>");
        
        buf.append("<b>Service name : </b>").append(serviceNameStr);
        buf.append("<p>");
        
        buf.append("<b>Service class : </b>").append(targetClass);
        buf.append("<p>");
        
        final Method[] methods = targetClass.getMethods();
        final Map<String, AttributeMethod> attributeMap = getAttributes(methods);
        final String[] attributeNames = attributeMap.keySet()
            .toArray(new String[attributeMap.size()]);
        Arrays.sort(attributeNames);
        buf.append("<b>Attributes</b><br>");
        buf.append("<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\" width=\"90%\">");
        buf.append("<tr bgcolor=\"#cccccc\"><th>name</th><th>r/w</th><th>type</th><th>value</th><th>apply</th></tr>");
        for(int i = 0; i < attributeNames.length; i++){
            final String attributeName = attributeNames[i];
            final AttributeMethod attrMethod
                 = (AttributeMethod)attributeMap.get(attributeName);
            final String attrType = getAttributeType(attrMethod);
            buf.append("<form name=\"").append(attributeName)
                .append("\" action=\"").append(getCurrentPath(req)).append("\" method=\"POST\">");
            buf.append("<input type=\"hidden\" name=\"action\" value=\"set\">");
            buf.append("<input type=\"hidden\" name=\"name\" value=\"")
                .append(serviceNameStr).append("\">");
            if(attrType.equals(ATTRIBUTE_READ_AND_WRITE)
                || attrType.equals(ATTRIBUTE_WRITE_ONLY)){
                final MethodSignature signature = new MethodSignature(
                    attrMethod.getSetter()
                );
                buf.append("<input type=\"hidden\" name=\"method\" value=\"")
                    .append(signature).append("\">");
            }
            
            buf.append("<tr>");
            buf.append("<td>").append(attributeName).append("</td>");
            buf.append("<td>").append(attrType).append("</td>");
            buf.append("<td>").append(attrMethod.getType().getName()).append("</td>");
            buf.append("<td>");
            if(attrType.equals(ATTRIBUTE_READ_AND_WRITE)){
                final String attrValue = getAttributeValue(service, attrMethod);
                buf.append("<textarea name=\"attributeValue\" cols=\"40\" rows=\"2\">")
                    .append(attrValue).append("</textarea>");
            }else if(attrType.equals(ATTRIBUTE_WRITE_ONLY)){
                buf.append("<textarea name=\"attributeValue\" cols=\"40\" rows=\"2\">")
                    .append("</textarea>");
            }else{
                final String attrValue = getAttributeValue(service, attrMethod);
                buf.append("<textarea name=\"attributeValue\" readonly cols=\"40\" rows=\"2\">")
                    .append(attrValue).append("</textarea>");
            }
            buf.append("</td>");
            buf.append("<td>");
            if(attrType.equals(ATTRIBUTE_READ_AND_WRITE)
                || attrType.equals(ATTRIBUTE_WRITE_ONLY)){
                buf.append("<input type=\"submit\" value=\"apply\">");
            }else{
                buf.append("　");
            }
            buf.append("</td>");
            buf.append("</tr>");
            buf.append("</form>");
        }
        buf.append("</table>");
        buf.append("<p>");
        
        buf.append("<b>Methods</b><br>");
        buf.append("<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\" width=\"90%\">");
        buf.append("<tr bgcolor=\"#cccccc\"><th rowspan=\"2\">method</th><th colspan=\"2\">arguments</th><th rowspan=\"2\">call</th></tr>");
        buf.append("<tr bgcolor=\"#cccccc\"><th>value</th><th>type</th></tr>");
        final Map<MethodSignature, Method> methodMap = new HashMap<MethodSignature, Method>();
        for(int i = 0; i < methods.length; i++){
            if(isIgnoreMethod(methods[i]) || isAttributeMethod(methods[i])){
                continue;
            }
            methodMap.put(new MethodSignature(methods[i]), methods[i]);
        }
        final MethodSignature[] sigs = (MethodSignature[])methodMap.keySet()
            .toArray(new MethodSignature[methodMap.size()]);
        Arrays.sort(sigs);
        for(int i = 0; i < sigs.length; i++){
            buf.append("<form name=\"").append(sigs[i])
                .append("\" action=\"").append(getCurrentPath(req)).append("\" method=\"POST\">");
            buf.append("<input type=\"hidden\" name=\"action\" value=\"call\">");
            buf.append("<input type=\"hidden\" name=\"name\" value=\"")
                .append(serviceNameStr).append("\">");
            buf.append("<input type=\"hidden\" name=\"method\" value=\"")
                .append(sigs[i]).append("\">");
            
            buf.append("<tr>");
            buf.append("<td>").append(sigs[i]).append("</td>");
            buf.append("<td>");
            final Class<?>[] paramTypes
                 = ((Method)methodMap.get(sigs[i])).getParameterTypes();
            if(paramTypes.length == 0){
                buf.append("　");
            }else{
                for(int j = 0, max = paramTypes.length; j < max; j++){
                    buf.append("<textarea name=\"args\" cols=\"40\" rows=\"2\"></textarea>");
                    if(j != max - 1){
                        buf.append("<br>");
                    }
                }
            }
            buf.append("</td>");
            buf.append("<td>");
            if(paramTypes.length == 0){
                buf.append("　");
            }else{
                for(int j = 0, max = paramTypes.length; j < max; j++){
                    buf.append("<input type=\"text\" name=\"argTypes\">");
                    if(j != max - 1){
                        buf.append("<br>");
                    }
                }
            }
            buf.append("</td>");
            buf.append("<td>").append("<input type=\"submit\" value=\"call\">")
                .append("</td>");
            buf.append("</tr>");
            buf.append("</form>");
        }
        buf.append("</table>");
        
        buf.append("<hr>");
        buf.append("<a href=\"").append(getCurrentPath(req))
            .append("\">ServiceManagerFactory</a>");
        buf.append("/");
        buf.append("<a href=\"").append(resp.encodeURL(url.toString()))
            .append("\">ServiceManager</a>");
        
        buf.append("</body>");
        buf.append("</html>");
        resp.getWriter().println(buf.toString());
    }
    
    /**
     * 管理コンソールのサービス属性設定リクエスト処理を行う。<p>
     *
     * @param req HTTPリクエスト
     * @param resp HTTPレスポンス
     * @exception ServletException 
     * @exception IOException 
     */
    protected void processSetAttributeResponse(
        HttpServletRequest req,
        HttpServletResponse resp
    ) throws ServletException, IOException{
        final String serviceNameStr = req.getParameter("name");
        final ServiceNameEditor editor = new ServiceNameEditor();
        editor.setAsText(serviceNameStr);
        final ServiceName serviceName = (ServiceName)editor.getValue();
        final Service service = ServiceManagerFactory.getService(serviceName);
        final String method = req.getParameter("method");
        final String attributeValueStr = req.getParameter("attributeValue");
        final String result = setAttributeValue(
            service,
            method,
            attributeValueStr
        );
        
        resp.setContentType("text/html;charset=UTF-8");
        final StringBuilder buf = new StringBuilder();
        buf.append("<html>");
        buf.append("<head><title>Nimbus Set Attribute</title></head>");
        buf.append("<body>");
        
        buf.append("<a href=\"").append(getCurrentPath(req))
            .append("\">ServiceManagerFactory</a>");
        buf.append("/");
        final StringBuilder url = new StringBuilder();
        url.append(getCurrentPath(req))
            .append("?action=manager&name=")
            .append(serviceName.getServiceManagerName());
        buf.append("<a href=\"").append(resp.encodeURL(url.toString()))
            .append("\">ServiceManager</a>");
        buf.append("/");
        url.setLength(0);
        url.append(getCurrentPath(req))
            .append("?action=service&name=")
            .append(URLEncoder.encode(serviceNameStr, "UTF-8"));
        buf.append("<a href=\"").append(resp.encodeURL(url.toString()))
            .append("\">Service</a>");
        buf.append("<hr>");
        
        buf.append("<pre>").append(result).append("</pre>");
        
        buf.append("<hr>");
        buf.append("<a href=\"").append(getCurrentPath(req))
            .append("\">ServiceManagerFactory</a>");
        buf.append("/");
        url.setLength(0);
        url.append(getCurrentPath(req))
            .append("?action=manager&name=")
            .append(serviceName.getServiceManagerName());
        buf.append("<a href=\"").append(resp.encodeURL(url.toString()))
            .append("\">ServiceManager</a>");
        buf.append("/");
        url.setLength(0);
        url.append(getCurrentPath(req))
            .append("?action=service&name=")
            .append(URLEncoder.encode(serviceNameStr, "UTF-8"));
        buf.append("<a href=\"").append(resp.encodeURL(url.toString()))
            .append("\">Service</a>");
        
        buf.append("</body>");
        buf.append("</html>");
        resp.getWriter().println(buf.toString());
    }
    
    /**
     * 管理コンソールのサービスメソッド呼び出しリクエスト処理を行う。<p>
     *
     * @param req HTTPリクエスト
     * @param resp HTTPレスポンス
     * @exception ServletException 
     * @exception IOException 
     */
    protected void processCallAttributeResponse(
        HttpServletRequest req,
        HttpServletResponse resp
    ) throws ServletException, IOException{
        final String serviceNameStr = req.getParameter("name");
        final ServiceNameEditor editor = new ServiceNameEditor();
        editor.setAsText(serviceNameStr);
        final ServiceName serviceName = (ServiceName)editor.getValue();
        final Service service = ServiceManagerFactory.getService(serviceName);
        final String method = req.getParameter("method");
        final String[] args = req.getParameterValues("args");
        final String[] argTypes = req.getParameterValues("argTypes");
        final String result = callMethod(
            service,
            method,
            args,
            argTypes
        );
        
        resp.setContentType("text/html;charset=UTF-8");
        final StringBuilder buf = new StringBuilder();
        buf.append("<html>");
        buf.append("<head><title>Nimbus Call Method</title></head>");
        buf.append("<body>");
        
        buf.append("<a href=\"").append(getCurrentPath(req))
            .append("\">ServiceManagerFactory</a>");
        buf.append("/");
        final StringBuilder url = new StringBuilder();
        url.append(getCurrentPath(req))
            .append("?action=manager&name=")
            .append(serviceName.getServiceManagerName());
        buf.append("<a href=\"").append(resp.encodeURL(url.toString()))
            .append("\">ServiceManager</a>");
        buf.append("/");
        url.setLength(0);
        url.append(getCurrentPath(req))
            .append("?action=service&name=")
            .append(URLEncoder.encode(serviceNameStr, "UTF-8"));
        buf.append("<a href=\"").append(resp.encodeURL(url.toString()))
            .append("\">Service</a>");
        buf.append("<hr>");
        
        buf.append("<pre>").append(result).append("</pre>");
        
        buf.append("<hr>");
        buf.append("<a href=\"").append(getCurrentPath(req))
            .append("\">ServiceManagerFactory</a>");
        buf.append("/");
        url.setLength(0);
        url.append(getCurrentPath(req))
            .append("?action=manager&name=")
            .append(serviceName.getServiceManagerName());
        buf.append("<a href=\"").append(resp.encodeURL(url.toString()))
            .append("\">ServiceManager</a>");
        buf.append("/");
        url.setLength(0);
        url.append(getCurrentPath(req))
            .append("?action=service&name=")
            .append(URLEncoder.encode(serviceNameStr, "UTF-8"));
        buf.append("<a href=\"").append(resp.encodeURL(url.toString()))
            .append("\">Service</a>");
        
        buf.append("</body>");
        buf.append("</html>");
        resp.getWriter().println(buf.toString());
    }
    
    private String callMethod(
        Service service,
        String methodStr,
        String[] argsStr,
        String[] argTypesStr
    ){
        final Object targetObj = getTargetObject(service);
        final StringWriter result = new StringWriter();
        final PrintWriter writer = new PrintWriter(result);
        try{
            final ClassLoader loader = NimbusClassLoader.getInstance();
            final MethodSignature signature = new MethodSignature(methodStr);
            final Method method = signature.getMethod(targetObj);
            final Class<?>[] paramTypes = method.getParameterTypes();
            Object[] params = new Object[paramTypes.length];
            for(int i = 0; i < paramTypes.length; i++){
                Class<?> editType = paramTypes[i];
                if(argTypesStr[i] != null && argTypesStr[i].length() != 0){
                    try{
                        editType = Class.forName(argTypesStr[i], true, loader);
                    }catch(ClassNotFoundException e){
                    }
                }
                if(argsStr[i] == null || argsStr[i].equals("null")){
                    params[i] = null;
                }else{
                    final PropertyEditor editor = findEditor(service, editType);
                    if(editor == null){
                        if(paramTypes[i].equals(Object.class)){
                            params[i] = argsStr[i];
                        }else{
                            writer.println("Failed!!");
                            writer.print("PropertyEditor ");
                            writer.print(paramTypes[i]);
                            writer.println(" not found.");
                            return result.toString();
                        }
                    }else if(argsStr.length > i){
                        editor.setAsText(argsStr[i]);
                        params[i] = editor.getValue();
                    }else{
                        params[i] = null;
                    }
                }
            }
            final Object ret = method.invoke(targetObj, params);
            writer.println("Success!!");
            final Class<?> retType = method.getReturnType();
            if(!retType.equals(Void.TYPE)){
                final PropertyEditor editor
                     = findEditor(service, retType);
                if(editor == null){
                    writer.println(ret);
                }else{
                    editor.setValue(ret);
                    writer.println(editor.getValue());
                }
            }
        }catch(Exception e){
            writer.println("Failed!!");
            e.printStackTrace(writer);
            return result.toString();
        }
        return result.toString();
    }
    
    private String setAttributeValue(
        Service service,
        String methodStr,
        String value
    ){
        final Object targetObj = getTargetObject(service);
        try{
            final MethodSignature signature = new MethodSignature(methodStr);
            final Method method = signature.getMethod(targetObj);
            final Class<?>[] paramTypes = method.getParameterTypes();
            final PropertyEditor editor
                 = findEditor(service, paramTypes[0]);
            Object[] params = null;
            if(editor == null){
                return "Failed!! PropertyEditor " + paramTypes[0] + " not found.";
            }else if(value == null || value.equals("null")){
                params = new Object[]{null};
            }else{
                editor.setAsText(value);
                params = new Object[]{editor.getValue()};
            }
            method.invoke(targetObj, params);
        }catch(Exception e){
            final StringWriter sw = new StringWriter();
            final PrintWriter writer = new PrintWriter(sw);
            e.printStackTrace(writer);
            return sw.toString();
        }
        return "Success!!";
    }
    
    private PropertyEditor findEditor(Service service, Class<?> type){
        PropertyEditor editor = null;
        if(service instanceof ServiceManager){
            final Set<ManagerMetaData> managerDatas = ((ServiceManager)service)
                .getManagerMetaDataSet();
            for(ManagerMetaData data : managerDatas){
                final ServiceLoader loader = data.getServiceLoader();
                editor = loader.findEditor(type);
                if(editor != null){
                    break;
                }
            }
        }else{
            final ServiceMetaData metaData = ServiceManagerFactory.getServiceMetaData(
                service.getServiceManagerName(),
                service.getServiceName()
            );
            if(metaData == null){
                editor = NimbusPropertyEditorManager.findEditor(type);
            }else{
                final ServiceLoader loader = metaData.getServiceLoader();
                editor = loader.findEditor(type);
            }
        }
        return editor;
    }
    
    private String getAttributeValue(Service service, AttributeMethod method){
        final Object targetObj = getTargetObject(service);
        String result = null;
        try{
            final Object val = method.getGetter().invoke(targetObj);
            if(val == null){
                return "null";
            }
            final PropertyEditor editor
                 = findEditor(service, method.getGetter().getReturnType());
            if(editor == null){
                return val.toString();
            }
            editor.setValue(val);
            result = editor.getAsText();
            if(result == null){
                return "null";
            }
        }catch(IllegalAccessException e){
            e.printStackTrace();
            return "Can not get!!";
        }catch(InvocationTargetException e){
            e.printStackTrace();
            return "Can not get!!";
        }catch(ServiceNotFoundException e){
            e.printStackTrace();
            return "Can not get!!";
        }
        return result;
    }
    
    private Map<String, AttributeMethod> getAttributes(Method[] methods){
        final Map<String, AttributeMethod> result = new HashMap<String, AttributeMethod>();
        for(int i = 0; i < methods.length; i++){
            if(isIgnoreMethod(methods[i])){
                continue;
            }
            final String methodName = methods[i].getName();
            final Class<?> retType = methods[i].getReturnType();
            final Class<?>[] paramTypes = methods[i].getParameterTypes();
            if(!isAttributeMethod(methods[i])){
                continue;
            }
            boolean isAttributeSet = false;
            boolean isAttributeGet = false;
            isAttributeSet = isAttributeSetMethod(
                methodName,
                retType,
                paramTypes
            );
            if(!isAttributeSet){
                isAttributeGet = isAttributeGetMethod(
                    methodName,
                    retType,
                    paramTypes
                );
                if(!isAttributeGet){
                    isAttributeGet = isAttributeIsMethod(
                        methodName,
                        retType,
                        paramTypes
                    );
                }
            }
            if(!isAttributeSet && !isAttributeGet){
                continue;
            }
            final String attrName = getAttributeName(methodName);
            AttributeMethod attrMethod = null;
            if(result.containsKey(attrName)){
                attrMethod = (AttributeMethod)result.get(attrName);
            }else{
                attrMethod = new AttributeMethod();
                result.put(attrName, attrMethod);
            }
            if(isAttributeSet){
                attrMethod.setSetter(methods[i]);
            }else{
                attrMethod.setGetter(methods[i]);
            }
        }
        return result;
    }
    
    private boolean isAttributeMethod(Method method){
        final String methodName = method.getName();
        final Class<?> retType = method.getReturnType();
        final Class<?>[] paramTypes = method.getParameterTypes();
        return isAttributeGetMethod(methodName, retType, paramTypes)
            || isAttributeIsMethod(methodName, retType, paramTypes)
            || isAttributeSetMethod(methodName, retType, paramTypes);
    }
    
    private boolean isAttributeGetMethod(
        String methodName,
        Class<?> retType,
        Class<?>[] paramTypes
    ){
        return methodName.startsWith("get")
             && methodName.length() > 3
             && !retType.equals(Void.TYPE)
             && paramTypes.length == 0;
    }
    
    private boolean isAttributeIsMethod(
        String methodName,
        Class<?> retType,
        Class<?>[] paramTypes
    ){
        return methodName.startsWith("is")
             && methodName.length() > 2
             && retType.equals(Boolean.TYPE)
             && paramTypes.length == 0;
    }
    
    private boolean isAttributeSetMethod(
        String methodName,
        Class<?> retType,
        Class<?>[] paramTypes
    ){
        return methodName.startsWith("set")
             && methodName.length() > 3
             && retType.equals(Void.TYPE)
             && paramTypes.length == 1;
    }
    
    private String getAttributeName(String methodName){
        final int length = methodName.length();
        if(methodName.startsWith("get") && length > 3){
            return methodName.substring(3);
        }else if(methodName.startsWith("is") && length > 2){
            return methodName.substring(2);
        }else if(methodName.startsWith("set") && length > 3){
            return methodName.substring(3);
        }
        return null;
    }
    
    private String getAttributeType(AttributeMethod method){
        final boolean hasSetter = method.getSetter() != null;
        final boolean hasGetter = method.getGetter() != null;
        if(hasSetter && hasGetter){
            return ATTRIBUTE_READ_AND_WRITE;
        }else if(hasSetter){
            return ATTRIBUTE_WRITE_ONLY;
        }else{
            return ATTRIBUTE_READ_ONLY;
        }
    }
    
    private boolean isIgnoreMethod(Method method){
        if(ignoreMethodMap == null){
            return false;
        }
        final List<Method> ignoreMethods = ignoreMethodMap.getValueList(
            method.getDeclaringClass()
        );
        if(ignoreMethods == null || ignoreMethods.size() == 0){
            return false;
        }
        for(int i = 0, imax = ignoreMethods.size(); i < imax; i++){
            final Method ignoreMethod = (Method)ignoreMethods.get(i);
            if(ignoreMethod.equals(method)){
                return true;
            }else if(ignoreMethod.getName().equals(method.getName())
                && ignoreMethod.getParameterTypes().length
                        == method.getParameterTypes().length
                && ignoreMethod.getReturnType().equals(method.getReturnType())
            ){
                Class<?>[] ignoreParamTypes = ignoreMethod.getParameterTypes();
                Class<?>[] paramTypes = method.getParameterTypes();
                if(ignoreParamTypes.length == 0){
                    return true;
                }
                boolean isMatch = true;
                for(int j = 0; j < ignoreParamTypes.length; j++){
                    if(!ignoreParamTypes[j].equals(paramTypes[j])){
                        isMatch = false;
                        break;
                    }
                }
                if(isMatch){
                    return true;
                }
            }
        }
        return false;
    }
    
    /**
     * サーブレットの破棄処理を行う。<p>
     * サービス定義のアンロードを行う。<br>
     */
    public void destroy(){
        final String[] servicePaths = getServicePaths();
        
        if(servicePaths != null && servicePaths.length != 0){
            for(int i = servicePaths.length; --i >= 0;){
                ServiceManagerFactory.unloadManager(servicePaths[i]);
            }
        }
    }
    
    private class AttributeMethod{
        private Method getterMethod;
        private Method setterMethod;
        private Class<?> attributeType;
        public void setGetter(Method getter){
            getterMethod = getter;
            if(getter != null && attributeType == null){
                attributeType = getter.getReturnType();
            }
        }
        public Method getGetter(){
            return getterMethod;
        }
        public void setSetter(Method setter){
            setterMethod = setter;
            if(setter != null && attributeType == null){
                attributeType = setter.getParameterTypes()[0];
            }
        }
        public Method getSetter(){
            return setterMethod;
        }
        public Class<?> getType(){
            return attributeType;
        }
    }
    
    private class MethodSignature implements Comparable<MethodSignature>{
        private String methodName;
        private Class<?>[] paramTypes;
        
        public MethodSignature(Method method){
            methodName = method.getName();
            paramTypes = method.getParameterTypes();
        }
        
        public MethodSignature(String method)
         throws IllegalArgumentException, ClassNotFoundException{
            String tmp = method;
            int index = tmp.indexOf('(');
            if(index == -1 || index == 0 || index == tmp.length() - 1){
                throw new IllegalArgumentException("Invalid method : " + method);
            }
            methodName = tmp.substring(0, index);
            tmp = tmp.substring(index + 1);
            index = tmp.indexOf(')');
            if(index == -1 || index != tmp.length() - 1){
                throw new IllegalArgumentException("Invalid method : " + method);
            }
            if(index == 0){
                paramTypes = null;
            }else{
                tmp = tmp.substring(0, index);
                final StringTokenizer tokens = new StringTokenizer(tmp, ",");
                final List<Class<?>> paramTypeList = new ArrayList<Class<?>>();
                final ClassLoader loader = NimbusClassLoader.getInstance();
                while(tokens.hasMoreTokens()){
                    final String paramTypeStr = tokens.nextToken().trim();
                    if(paramTypeStr.length() == 0){
                        throw new IllegalArgumentException("Invalid method : " + method);
                    }
                    Class<?> paramType = null;
                    if(paramTypeStr.equals("boolean")){
                        paramType = Boolean.TYPE;
                    }else if(paramTypeStr.equals("byte")){
                        paramType = Byte.TYPE;
                    }else if(paramTypeStr.equals("char")){
                        paramType = Character.TYPE;
                    }else if(paramTypeStr.equals("short")){
                        paramType = Short.TYPE;
                    }else if(paramTypeStr.equals("int")){
                        paramType = Integer.TYPE;
                    }else if(paramTypeStr.equals("long")){
                        paramType = Long.TYPE;
                    }else if(paramTypeStr.equals("float")){
                        paramType = Float.TYPE;
                    }else if(paramTypeStr.equals("double")){
                        paramType = Double.TYPE;
                    }else{
                        paramType = Class.forName(paramTypeStr, true, loader);
                    }
                    paramTypeList.add(paramType);
                }
                paramTypes = (Class[])paramTypeList.toArray(new Class[paramTypeList.size()]);
            }
        }
        
        public Method getMethod(Object obj) throws NoSuchMethodException{
            return obj.getClass().getMethod(methodName, paramTypes);
        }
        
        public boolean equals(Object o){
            if(o == null){
                return false;
            }
            if(o == this){
                return true;
            }
            if(o instanceof MethodSignature){
                final MethodSignature comp = (MethodSignature)o;
                if(!methodName.equals(comp.methodName)){
                    return false;
                }
                if(paramTypes == comp.paramTypes){
                    return true;
                }
                if((paramTypes == null && comp.paramTypes != null)
                    || (paramTypes != null && comp.paramTypes == null)
                    || (paramTypes.length != comp.paramTypes.length)
                ){
                    return false;
                }
                for(int i = 0; i < paramTypes.length; i++){
                    if(!paramTypes[i].equals(comp.paramTypes[i])){
                        return false;
                    }
                }
                return true;
            }else{
                return false;
            }
        }
        
        public int hashCode(){
            int hashCode = methodName.hashCode();
            if(paramTypes != null){
                for(int i = 0; i < paramTypes.length; i++){
                    hashCode += paramTypes[i].hashCode();
                }
            }
            return hashCode;
        }
        
        public String toString(){
            final StringBuilder buf = new StringBuilder(methodName);
            buf.append('(');
            if(paramTypes != null){
                for(int i = 0, max = paramTypes.length; i < max; i++){
                    buf.append(paramTypes[i].getName());
                    if(i != max - 1){
                        buf.append(',');
                    }
                }
            }
            buf.append(')');
            return buf.toString();
        }
        
        public int compareTo(MethodSignature sig){
            if(methodName.equals(sig.methodName)){
                if(paramTypes == null){
                    if(sig.paramTypes == null){
                        return 0;
                    }else{
                        return -1;
                    }
                }else{
                    if(sig.paramTypes == null){
                        return 1;
                    }else if(paramTypes.length == sig.paramTypes.length){
                        for(int i = 0; i < paramTypes.length; i++){
                            final int ret = paramTypes[i].getName()
                                .compareTo(sig.paramTypes[i].getName());
                            if(ret != 0){
                                return ret;
                            }
                        }
                        return 0;
                    }else{
                        return paramTypes.length > sig.paramTypes.length ? 1 : -1;
                    }
                }
            }else{
                return methodName.compareTo(sig.methodName);
            }
        }
    }
}
