/*
 * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.jmx;

import javax.management.Attribute;
import javax.management.MBeanServer;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Proxy hander for mbeans.
 */
public class JmxInvocationHandler implements InvocationHandler {
  private MBeanServer _server;
  private ClassLoader _loader;
  private ObjectName _name;

  /**
   * Creates the invocation handler.
   */
  public JmxInvocationHandler(MBeanServer mbeanServer,
			      ClassLoader loader,
			      ObjectName objectName)
  {
    _server = mbeanServer;
    _loader = loader;
    _name = objectName;
  }

  /**
   * Creates a new proxy instance.
   */
  public static Object newProxyInstance(MBeanServer server,
					ClassLoader loader,
					ObjectName objectName,
					Class interfaceClass,
					boolean notificationBroadcaster)
  {
    Class []interfaces;

    if (notificationBroadcaster)
      interfaces = new Class[] { interfaceClass, NotificationEmitter.class };
    else
      interfaces = new Class[] { interfaceClass };

    JmxInvocationHandler handler;
    
    handler = new JmxInvocationHandler(server, loader, objectName);
    
    return Proxy.newProxyInstance(interfaceClass.getClassLoader(),
				  interfaces,
				  handler);
  }

  /**
   * Handles the object invocation.
   *
   * @param proxy the proxy object to invoke
   * @param method the method to call
   * @param args the arguments to the proxy object
   */
  public Object invoke(Object proxy, Method method, Object []args)
    throws Throwable
  {
    String methodName = method.getName();
    Class []params = method.getParameterTypes();

    // equals and hashCode are special cased
    if (methodName.equals("equals")
	&& params.length == 1 && params[0].equals(Object.class)) {
      Object value = args[0];
      if (value == null || ! Proxy.isProxyClass(value.getClass()))
        return Boolean.FALSE;

      JmxInvocationHandler handler;

      handler = (JmxInvocationHandler) Proxy.getInvocationHandler(value);

      return new Boolean(_name.equals(handler._name));
    }
    else if (methodName.equals("hashCode") && params.length == 0)
      return new Integer(_name.hashCode());

    int len = methodName.length();
    String attrName;

    Class returnType = method.getReturnType();
    
    if (params.length == 0 && methodName.startsWith("get") && len > 3) {
      attrName = methodName.substring(3);

      return marshall(_server.getAttribute(_name, attrName), returnType);
    }
    else if (params.length == 1 && returnType.equals(void.class) &&
	     methodName.startsWith("set") && len > 3) {
      attrName = methodName.substring(3);

      Attribute attr = new Attribute(attrName, args[0]);

      _server.setAttribute(_name, attr);

      return null;
    }
    else if (methodName.equals("addNotificationListener")) {
      if (args.length != 3) {
      }
      else if (args[0] instanceof NotificationListener) {
	_server.addNotificationListener(_name,
					(NotificationListener) args[0],
					(NotificationFilter) args[1],
					args[2]);
	return null;
      }
      else if (args[0] instanceof ObjectName) {
	_server.addNotificationListener(_name,
					(ObjectName) args[0],
					(NotificationFilter) args[1],
					args[2]);
	return null;
      }
    }
    else if (methodName.equals("removeNotificationListener")) {
      if (args.length == 3) {
	if (args[0] instanceof NotificationListener) {
	  _server.removeNotificationListener(_name,
					     (NotificationListener) args[0],
					     (NotificationFilter) args[1],
					     args[2]);
	  return null;
	}
	else if (args[0] instanceof ObjectName) {
	  _server.removeNotificationListener(_name,
					     (ObjectName) args[0],
					     (NotificationFilter) args[1],
					     args[2]);
	  return null;
	}
      }
      else if (args.length == 1) {
	if (args[0] instanceof NotificationListener) {
	  _server.removeNotificationListener(_name,
					     (NotificationListener) args[0]);
	  return null;
	}
	else if (args[0] instanceof ObjectName) {
	  _server.removeNotificationListener(_name, (ObjectName) args[0]);

	  return null;
	}
      }
    }

    String []sig = new String[params.length];

    for (int i = 0; i < sig.length; i++)
      sig[i] = params[i].getName();

    Object value = _server.invoke(_name, methodName, args, sig);

    return marshall(value, returnType);
  }

  private Object marshall(Object value, Class retType)
  {
    if (retType == null || value == null ||
	retType.isAssignableFrom(value.getClass()))
      return value;
	
    if (value instanceof ObjectName) {
      ObjectName name = (ObjectName) value;

      Object proxy = Jmx.find(name, _loader, _server);

      if (retType.isInstance(proxy))
	return proxy;
      else
	return value;
    }
    else if (value instanceof ObjectName[] && retType.isArray()) {
      Class type = retType.getComponentType();
      ObjectName []names = (ObjectName []) value;
      
      Object proxies = Array.newInstance(type, names.length);

      for (int i = 0; i < names.length; i++) {
	Object proxy = Jmx.find(names[i], _loader, _server);

	if (proxy == null) {
	  Array.set(proxies, i, null);
	  continue;
	}
	
	if (! type.isInstance(proxy))
	  return value;

	Array.set(proxies, i, Jmx.find(names[i], _loader, _server));
      }
					   
      return proxies;
    }

    return value;
  }
}
