package org.seasar.framework.aop.javassist;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
import javassist.NotFoundException;

import org.seasar.framework.util.ClassUtil;

/**
 * @author koichik
 */
public class AbstractGenerator {
    //instance fields
    protected final ClassPool classPool;

    public static String fromObject(final Class type, final String expr) {
        if (type.equals(void.class) || type.equals(Object.class)) {
            return expr;
        }
        if (type.equals(boolean.class) || type.equals(char.class)) {
            final Class wrapper = ClassUtil.getWrapperClass(type);
            return "((" + wrapper.getName() + ") " + expr + ")." + type.getName() + "Value()";
        }
        if (type.isPrimitive()) {
            return "((java.lang.Number) " + expr + ")." + type.getName() + "Value()";
        }
        return "(" + ClassUtil.getSimpleClassName(type) + ") " + expr;
    }

    public static String toObject(final Class type, final String expr) {
        if (type.isPrimitive()) {
            final Class wrapper = ClassUtil.getWrapperClass(type);
            return "new " + wrapper.getName() + "(" + expr + ")";
        }
        return expr;
    }

    protected AbstractGenerator(final ClassPool classPool) {
        this.classPool = classPool;
    }

    protected CtClass toCtClass(final Class clazz) {
        return toCtClass(ClassUtil.getSimpleClassName(clazz));
    }

    protected CtClass toCtClass(final String className) {
        try {
            return classPool.get(className);
        }
        catch (final NotFoundException e) {
            throw new NotFoundRuntimeException(e);
        }
    }

    protected CtClass[] toCtClassArray(final String[] classNames) {
        final CtClass[] result = new CtClass[classNames.length];
        for (int i = 0; i < result.length; ++i) {
            result[i] = toCtClass(classNames[i]);
        }
        return result;
    }

    protected CtClass[] toCtClassArray(final Class[] classes) {
        final CtClass[] result = new CtClass[classes.length];
        for (int i = 0; i < result.length; ++i) {
            result[i] = toCtClass(classes[i]);
        }
        return result;
    }

    protected CtClass createCtClass(final String name) {
        return createCtClass(name, Object.class);
    }

    protected CtClass createCtClass(final String name, final Class superClass) {
        return createCtClass(name, toCtClass(superClass));
    }

    protected CtClass createCtClass(final String name, final CtClass superClass) {
        return classPool.makeClass(name, superClass);
    }

    protected CtClass getAndRenameCtClass(final Class orgClass, final String newName) {
        return getAndRenameCtClass(ClassUtil.getSimpleClassName(orgClass), newName);
    }

    protected CtClass getAndRenameCtClass(final String orgName, final String newName) {
        try {
            return classPool.getAndRename(orgName, newName);
        }
        catch (final NotFoundException e) {
            throw new NotFoundRuntimeException(e);
        }
    }

    protected static Class toClass(final ClassLoader classLoader, final CtClass clazz) {
        try {
            return clazz.toClass(classLoader);
        }
        catch (final CannotCompileException e) {
            throw new CannotCompileRuntimeException(e);
        }
    }

    protected void setInterface(final CtClass clazz, final Class interfaceType) {
        clazz.setInterfaces(new CtClass[] { toCtClass(interfaceType) });
    }

    protected void setInterfaces(final CtClass clazz, final Class[] interfaces) {
        clazz.setInterfaces(toCtClassArray(interfaces));
    }

    protected CtConstructor createDefaultConstructor(final Class clazz) {
        return createDefaultConstructor(toCtClass(clazz));
    }

    protected CtConstructor createDefaultConstructor(final CtClass clazz) {
        try {
            final CtConstructor ctConstructor = CtNewConstructor.defaultConstructor(clazz);
            clazz.addConstructor(ctConstructor);
            return ctConstructor;
        }
        catch (final CannotCompileException e) {
            throw new CannotCompileRuntimeException(e);
        }
    }

    protected CtConstructor createConstructor(final CtClass clazz, final Constructor constructor) {
        return createConstructor(clazz, toCtClassArray(constructor.getParameterTypes()),
                toCtClassArray(constructor.getExceptionTypes()));
    }

    protected CtConstructor createConstructor(final CtClass clazz, final CtClass[] parameterTypes,
            final CtClass[] exceptionTypes) {
        try {
            final CtConstructor ctConstructor = CtNewConstructor.make(parameterTypes,
                    exceptionTypes, clazz);
            clazz.addConstructor(ctConstructor);
            return ctConstructor;
        }
        catch (final CannotCompileException e) {
            throw new CannotCompileRuntimeException(e);
        }
    }

    protected CtMethod getDeclaredMethod(final CtClass clazz, final String name,
            final CtClass[] argTypes) {
        try {
            return clazz.getDeclaredMethod(name, argTypes);
        }
        catch (final NotFoundException e) {
            throw new NotFoundRuntimeException(e);
        }
    }

    protected CtMethod createMethod(final CtClass clazz, final String src) {
        try {
            final CtMethod ctMethod = CtNewMethod.make(src, clazz);
            clazz.addMethod(ctMethod);
            return ctMethod;
        }
        catch (final CannotCompileException e) {
            throw new CannotCompileRuntimeException(e);
        }
    }

    protected CtMethod createMethod(final CtClass clazz, final Method method, final String body) {
        return createMethod(clazz, method.getModifiers(), method.getReturnType(), method.getName(),
                method.getParameterTypes(), method.getExceptionTypes(), body);
    }

    protected CtMethod createMethod(final CtClass clazz, final int modifier,
            final Class returnType, final String methodName, final Class[] parameterTypes,
            final Class[] exceptionTypes, final String body) {
        try {
            final CtMethod ctMethod = CtNewMethod.make(modifier
                    & ~(Modifier.ABSTRACT | Modifier.NATIVE), toCtClass(returnType), methodName,
                    toCtClassArray(parameterTypes), toCtClassArray(exceptionTypes), body, clazz);
            clazz.addMethod(ctMethod);
            ctMethod.setBody(body);
            return ctMethod;
        }
        catch (final CannotCompileException e) {
            throw new CannotCompileRuntimeException(e);
        }
    }

    protected void setMethodBody(final CtMethod method, final String src) {
        try {
            method.setBody(src);
        }
        catch (final CannotCompileException e) {
            throw new CannotCompileRuntimeException(e);
        }
    }
}