/*
 * 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.core;

import java.util.*;
import java.lang.reflect.*;

import javax.management.*;

import jp.ossc.nimbus.beans.*;

/**
 * 汎用サービスプロキシ。<p>
 *
 * @author M.Takata
 */
public class GenericsServiceProxy extends ServiceBase
 implements DynamicMBean{
    
    private static final long serialVersionUID = -2723473573128599716L;
    
    private static final String MBEAN_SUFFIX = "MBean";
    private static final String MXBEAN_SUFFIX = "MXBean";
    
    protected Set<MBeanConstructorInfo> constructorInfos = new HashSet<MBeanConstructorInfo>();
    
    protected Map<String, MBeanAttributeInfo> attributeInfos = new HashMap<String, MBeanAttributeInfo>();
    
    protected Set<MBeanOperationInfo> operationInfos = new HashSet<MBeanOperationInfo>();
    
    protected MBeanInfo mbeanInfo;
    
    protected Map<String, Property> attributePropCache = new HashMap<String, Property>();
    
    public GenericsServiceProxy(ServiceBaseSupport support) throws Exception{
        super(support);
        initConstructors();
        initBaseAttributes();
        initBaseOperations();
        if(support != null){
            Class<?> mbeanInterface = findMBeanInterface(support.getClass());
            if(mbeanInterface == null
                && support instanceof ServiceProxy){
                mbeanInterface = findMBeanInterface(
                    ((ServiceProxy)support).getTarget().getClass()
                );
            }
            if(mbeanInterface != null){
                initAttributesOf(mbeanInterface, support.getClass());
                initOperationsOf(mbeanInterface, support.getClass());
            }
        }
        initMBeanInfo();
    }
    
    protected void initConstructors() throws Exception{
        for(Constructor<?> constructor : getClass().getConstructors()){
            if(constructor.getParameterTypes().length != 0){
                constructorInfos.add(
                    new MBeanConstructorInfo(null, constructor)
                );
            }
        }
    }
    
    protected void initBaseAttributes() throws Exception{
        initAttributesOf(ServiceBaseMBean.class, getClass());
    }
    
    private boolean isDeclaredMethod(Method method, Class<?> clazz){
        try{
            final Method decMethod = clazz.getMethod(
                method.getName(),
                method.getParameterTypes()
            );
            
            // JRockitとSun JVMの動作の違いを吸収するための実装
            // JRockitでは、インタフェースのClassオブジェクトが
            // Objectクラスのサブクラスという扱いになっている
            if(Object.class.equals(decMethod.getDeclaringClass())){
                return false;
            }
        }catch(NoSuchMethodException e){
            return false;
        }
        return true;
    }
    
    protected void initAttributesOf(
        Class<?> mbeanInterface,
        Class<?> target
   ) throws Exception{
        for(SimpleProperty prop : SimpleProperty.getProperties(target)){
            if(!attributeInfos.containsKey(prop.getPropertyName())){
                
                if((prop.isReadable(target)
                        && isDeclaredMethod(prop.getReadMethod(target),
                                mbeanInterface))
                    || (prop.isWritable(target)
                        && isDeclaredMethod(prop.getWriteMethod(target),
                                mbeanInterface))
                ){
                    String propName = prop.getPropertyName();
                    if(Character.isLowerCase(propName.charAt(0))){
                        if(propName.length() == 1){
                            propName = Character.toString(
                                Character.toUpperCase(propName.charAt(0))
                            );
                        }else{
                            propName = Character.toUpperCase(propName.charAt(0))
                                + propName.substring(1);
                        }
                    }
                    attributeInfos.put(
                        propName,
                        new MBeanAttributeInfo(
                            propName,
                            null,
                            prop.isReadable(target)
                                 ? prop.getReadMethod(target) : null,
                            prop.isWritable(target)
                                 ? prop.getWriteMethod(target) : null
                        )
                    );
                }
            }
        }
    }
    
    protected void initBaseOperations() throws Exception{
        initOperationsOf(ServiceBaseMBean.class, getClass());
    }
    
    protected void initOperationsOf(
        Class<?> mbeanInterface,
        Class<?> target
    ) throws Exception{
        final Method[] methods = mbeanInterface.getMethods();
        
        final SimpleProperty[] props
             = SimpleProperty.getProperties(target);
        for(Method method : methods){
            boolean isOperationMethod = true;
            for(SimpleProperty prop : props){
                if(prop.isReadable(target)){
                    Method readMethod = prop.getReadMethod(target);
                    if(method.getName().equals(readMethod.getName())
                        && readMethod.getParameterTypes().length == 0){
                        isOperationMethod = false;
                        break;
                    }
                }
                if(prop.isWritable(target)){
                    Method writeMethod = prop.getWriteMethod(target);
                    if(method.getName().equals(writeMethod.getName())
                        && writeMethod.getParameterTypes().length == 1
                        && method.getParameterTypes()[0]
                            .equals(writeMethod.getParameterTypes()[0])
                    ){
                        isOperationMethod = false;
                        break;
                    }
                }
            }
            if(isOperationMethod){
                method = target.getMethod(
                    method.getName(),
                    method.getParameterTypes()
                );
                final MBeanOperationInfo oprationInfo
                     = new MBeanOperationInfo("", method);
                if(!operationInfos.contains(oprationInfo)){
                    operationInfos.add(oprationInfo);
                }
            }
        }
    }
    
    protected void initMBeanInfo(){
        mbeanInfo = new MBeanInfo(
            getClass().getName(),
            null,
            (MBeanAttributeInfo[])attributeInfos.values().toArray(
                new MBeanAttributeInfo[attributeInfos.size()]
            ),
            (MBeanConstructorInfo[])constructorInfos.toArray(
                new MBeanConstructorInfo[constructorInfos.size()]
            ),
            (MBeanOperationInfo[])operationInfos.toArray(
                new MBeanOperationInfo[operationInfos.size()]
            ),
            new MBeanNotificationInfo[0]
        );
    }
    
    // DynamicMBeanのJavaDoc
    @Override
    public Object getAttribute(String attribute)
     throws AttributeNotFoundException, MBeanException, ReflectionException{
        final MBeanAttributeInfo attributeInfo
             = (MBeanAttributeInfo)attributeInfos.get(attribute);
        if(attributeInfo != null && attributeInfo.isReadable()){
            Property prop = (Property)attributePropCache.get(attribute);
            if(prop == null){
                prop = PropertyFactory.createProperty(attribute);
                attributePropCache.put(attribute, prop);
            }
            try{
                if(prop.isReadable(this)){
                    return prop.getProperty(this);
                }else{
                    if(support == null){
                        return null;
                    }
                    if(prop.isReadable(support)){
                        return prop.getProperty(support);
                    }else{
                        throw new AttributeNotFoundException(attribute);
                    }
                }
            }catch(NoSuchPropertyException e){
                throw new AttributeNotFoundException(attribute);
            }catch(InvocationTargetException e){
                throw new ReflectionException(e);
            }
        }
        throw new AttributeNotFoundException(attribute);
    }
    
    // DynamicMBeanのJavaDoc
    @Override
    public AttributeList getAttributes(String[] attributes){
        final AttributeList list = new AttributeList();
        for(String attribute : attributes){
            try{
                list.add(
                    new Attribute(attribute, getAttribute(attribute))
                );
            }catch(AttributeNotFoundException e){
            }catch(MBeanException e){
            }catch(ReflectionException e){
            }
        }
        return list;
    }
    
    // DynamicMBeanのJavaDoc
    @Override
    public void setAttribute(Attribute attribute)
     throws AttributeNotFoundException, InvalidAttributeValueException,
            MBeanException, ReflectionException{
        final MBeanAttributeInfo attributeInfo
             = (MBeanAttributeInfo)attributeInfos.get(attribute.getName());
        if(attributeInfo != null && attributeInfo.isWritable()){
            final SimpleProperty prop = (SimpleProperty)PropertyFactory
                .createProperty(attribute.getName());
            try{
                if(prop.isWritable(getClass())){
                    try{
                        prop.setProperty(this, attribute.getValue());
                    }catch(InvocationTargetException e){
                        throw new ReflectionException(e);
                    }
                }else{
                    if(support == null){
                        return;
                    }
                    try{
                        prop.setProperty(support, attribute.getValue());
                    }catch(InvocationTargetException e){
                        throw new ReflectionException(e);
                    }
                }
            }catch(NoSuchPropertyException e){
                throw new AttributeNotFoundException(
                    "name=" + attribute.getName()
                        + ", value=" + attribute.getValue()
                );
            }
        }
    }
    
    // DynamicMBeanのJavaDoc
    @Override
    public AttributeList setAttributes(AttributeList attributes){
        final AttributeList list = new AttributeList();
        for(Object attribute : attributes){
            
            try{
                setAttribute((Attribute)attribute);
                Object val = getAttribute(((Attribute)attribute).getName());
                list.add(new Attribute(((Attribute)attribute).getName(), val));
            }catch(AttributeNotFoundException e){
            }catch(InvalidAttributeValueException e){
            }catch(MBeanException e){
            }catch(ReflectionException e){
            }
        }
        return list;
    }
    
    // DynamicMBeanのJavaDoc
    @Override
    public Object invoke(
        String actionName,
        Object[] params,
        String[] signature
    ) throws MBeanException, ReflectionException{
        
        Class<?>[] paramTypes = null;
        if(signature != null){
            paramTypes = new Class[signature.length];
            try{
                for(int i = 0; i < signature.length; i++){
                    paramTypes[i] = Utility.convertStringToClass(signature[i]);
                }
            }catch(ClassNotFoundException e){
                throw new ReflectionException(e);
            }
        }
        Method method = null;
        
        try{
            method = getClass().getMethod(actionName, paramTypes);
            return method.invoke(this, params);
        }catch(NoSuchMethodException e){
        }catch(IllegalAccessException e){
            throw new ReflectionException(e);
        }catch(InvocationTargetException e){
            throw new ReflectionException(e);
        }
        
        if(support == null){
            return null;
        }
        
        Object ret = null;
        try{
            method = support.getClass().getMethod(actionName, paramTypes);
            ret = method.invoke(support, params);
        }catch(NoSuchMethodException e){
            throw new ReflectionException(e);
        }catch(IllegalAccessException e){
            throw new ReflectionException(e);
        }catch(InvocationTargetException e){
            throw new ReflectionException(e);
        }
        return ret;
    }
    
    // DynamicMBeanのJavaDoc
    @Override
    public MBeanInfo getMBeanInfo(){
        return mbeanInfo;
    }
    
    private static Class<?> findMBeanInterface(Class<?> clazz){
        if(clazz == null){
            return null;
        }
        final String className = clazz.getName();
        final String mbeanInterfaceName = className + MBEAN_SUFFIX;
        final Class<?>[] interfaces = clazz.getInterfaces();
        for(Class<?> itf : interfaces){
            if(itf.equals(DynamicMBean.class)
                || itf.getName().equals(mbeanInterfaceName)
                || itf.getName().endsWith(MXBEAN_SUFFIX)
            ){
                return itf;
            }
        }
        return findMBeanInterface(clazz.getSuperclass());
    }
}