/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * 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.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * 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.
 */

package javassist.compiler;

import java.util.ArrayList;
import java.util.List;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.Bytecode;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.Descriptor;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import javassist.compiler.ast.ASTList;
import javassist.compiler.ast.ASTree;
import javassist.compiler.ast.ArrayInit;
import javassist.compiler.ast.CallExpr;
import javassist.compiler.ast.Declarator;
import javassist.compiler.ast.Expr;
import javassist.compiler.ast.Keyword;
import javassist.compiler.ast.Member;
import javassist.compiler.ast.MethodDecl;
import javassist.compiler.ast.NewExpr;
import javassist.compiler.ast.Pair;
import javassist.compiler.ast.Stmnt;
import javassist.compiler.ast.Symbol;

/* Code generator methods depending on javassist.* classes.
 */
public class MemberCodeGen extends CodeGen {
    protected MemberResolver resolver;
    protected CtClass   thisClass;
    protected MethodInfo thisMethod;

    protected boolean resultStatic;

    public MemberCodeGen(Bytecode b, CtClass cc, ClassPool cp) {
        super(b);
        resolver = new MemberResolver(cp);
        thisClass = cc;
        thisMethod = null;
    }

    /**
     * Returns the major version of the class file
     * targeted by this compilation.
     */
    public int getMajorVersion() {
        ClassFile cf = thisClass.getClassFile2();
        if (cf == null)
            return ClassFile.MAJOR_VERSION;     // JDK 1.3
        return cf.getMajorVersion();
    }

    /**
     * Records the currently compiled method.
     */
    public void setThisMethod(CtMethod m) {
        thisMethod = m.getMethodInfo2();
        if (typeChecker != null)
            typeChecker.setThisMethod(thisMethod);
    }

    public CtClass getThisClass() { return thisClass; }

    /**
     * Returns the JVM-internal representation of this class name.
     */
    @Override
    protected String getThisName() {
        return MemberResolver.javaToJvmName(thisClass.getName());
    }

    /**
     * Returns the JVM-internal representation of this super class name.
     */
    @Override
    protected String getSuperName() throws CompileError {
        return MemberResolver.javaToJvmName(
                        MemberResolver.getSuperclass(thisClass).getName());
    }

    @Override
    protected void insertDefaultSuperCall() throws CompileError {
        bytecode.addAload(0);
        bytecode.addInvokespecial(MemberResolver.getSuperclass(thisClass),
                                  "<init>", "()V");
    }

    static class JsrHook extends ReturnHook {
        List<int[]> jsrList;
        CodeGen cgen;
        int var;

        JsrHook(CodeGen gen) {
            super(gen);
            jsrList = new ArrayList<int[]>();
            cgen = gen;
            var = -1;
        }

        private int getVar(int size) {
            if (var < 0) {
                var = cgen.getMaxLocals();
                cgen.incMaxLocals(size);
            }

            return var;
        }

        private void jsrJmp(Bytecode b) {
            b.addOpcode(Opcode.GOTO);
            jsrList.add(new int[] {b.currentPc(), var});
            b.addIndex(0);
        }

        @Override
        protected boolean doit(Bytecode b, int opcode) {
            switch (opcode) {
            case Opcode.RETURN :
                jsrJmp(b);
                break;
            case ARETURN :
                b.addAstore(getVar(1));
                jsrJmp(b);
                b.addAload(var);
                break;
            case IRETURN :
                b.addIstore(getVar(1));
                jsrJmp(b);
                b.addIload(var);
                break;
            case LRETURN :
                b.addLstore(getVar(2));
                jsrJmp(b);
                b.addLload(var);
                break;
            case DRETURN :
                b.addDstore(getVar(2));
                jsrJmp(b);
                b.addDload(var);
                break;
            case FRETURN :
                b.addFstore(getVar(1));
                jsrJmp(b);
                b.addFload(var);
                break;
            default :
                throw new RuntimeException("fatal");
            }

            return false;
        }
    }

    static class JsrHook2 extends ReturnHook {
        int var;
        int target;

        JsrHook2(CodeGen gen, int[] retTarget) {
            super(gen);
            target = retTarget[0];
            var = retTarget[1];
        }

        @Override
        protected boolean doit(Bytecode b, int opcode) {
            switch (opcode) {
            case Opcode.RETURN :
                break;
            case ARETURN :
                b.addAstore(var);
                break;
            case IRETURN :
                b.addIstore(var);
                break;
            case LRETURN :
                b.addLstore(var);
                break;
            case DRETURN :
                b.addDstore(var);
                break;
            case FRETURN :
                b.addFstore(var);
                break;
            default :
                throw new RuntimeException("fatal");
            }

            b.addOpcode(Opcode.GOTO);
            b.addIndex(target - b.currentPc() + 3);
            return true;
        }
    }

    @Override
    protected void atTryStmnt(Stmnt st) throws CompileError {
        Bytecode bc = bytecode;
        Stmnt body = (Stmnt)st.getLeft();
        if (body == null)
            return;

        ASTList catchList = (ASTList)st.getRight().getLeft();
        Stmnt finallyBlock = (Stmnt)st.getRight().getRight().getLeft();
        List<Integer> gotoList = new ArrayList<Integer>();

        JsrHook jsrHook = null;
        if (finallyBlock != null)
            jsrHook = new JsrHook(this);

        int start = bc.currentPc();
        body.accept(this);
        int end = bc.currentPc();
        if (start == end)
            throw new CompileError("empty try block");

        boolean tryNotReturn = !hasReturned;
        if (tryNotReturn) {
            bc.addOpcode(Opcode.GOTO);
            gotoList.add(bc.currentPc());
            bc.addIndex(0);   // correct later
        }

        int var = getMaxLocals();
        incMaxLocals(1);
        while (catchList != null) {
            // catch clause
            Pair p = (Pair)catchList.head();
            catchList = catchList.tail();
            Declarator decl = (Declarator)p.getLeft();
            Stmnt block = (Stmnt)p.getRight();

            decl.setLocalVar(var);

            CtClass type = resolver.lookupClassByJvmName(decl.getClassName());
            decl.setClassName(MemberResolver.javaToJvmName(type.getName()));
            bc.addExceptionHandler(start, end, bc.currentPc(), type);
            bc.growStack(1);
            bc.addAstore(var);
            hasReturned = false;
            if (block != null)
                block.accept(this);

            if (!hasReturned) {
                bc.addOpcode(Opcode.GOTO);
                gotoList.add(bc.currentPc());
                bc.addIndex(0);   // correct later
                tryNotReturn = true;
            }
        }

        if (finallyBlock != null) {
            jsrHook.remove(this);
            // catch (any) clause
            int pcAnyCatch = bc.currentPc();
            bc.addExceptionHandler(start, pcAnyCatch, pcAnyCatch, 0);
            bc.growStack(1);
            bc.addAstore(var);
            hasReturned = false;
            finallyBlock.accept(this);
            if (!hasReturned) {
                bc.addAload(var);
                bc.addOpcode(ATHROW);
            }

            addFinally(jsrHook.jsrList, finallyBlock);
        }

        int pcEnd = bc.currentPc();
        patchGoto(gotoList, pcEnd);
        hasReturned = !tryNotReturn;
        if (finallyBlock != null) {
            if (tryNotReturn)
                finallyBlock.accept(this);
        }
    }

    /**
     * Adds a finally clause for earch return statement.
     */
    private void addFinally(List<int[]> returnList, Stmnt finallyBlock)
        throws CompileError
    {
        Bytecode bc = bytecode;
        for (final int[] ret:returnList) {
            int pc = ret[0];
            bc.write16bit(pc, bc.currentPc() - pc + 1);
            ReturnHook hook = new JsrHook2(this, ret);
            finallyBlock.accept(this);
            hook.remove(this);
            if (!hasReturned) {
                bc.addOpcode(Opcode.GOTO);
                bc.addIndex(pc + 3 - bc.currentPc());
            }
        }
    }

    @Override
    public void atNewExpr(NewExpr expr) throws CompileError {
        if (expr.isArray())
            atNewArrayExpr(expr);
        else {
            CtClass clazz = resolver.lookupClassByName(expr.getClassName());
            String cname = clazz.getName();
            ASTList args = expr.getArguments();
            bytecode.addNew(cname);
            bytecode.addOpcode(DUP);

            atMethodCallCore(clazz, MethodInfo.nameInit, args,
                             false, true, -1, null);

            exprType = CLASS;
            arrayDim = 0;
            className = MemberResolver.javaToJvmName(cname);
        }
    }

    public void atNewArrayExpr(NewExpr expr) throws CompileError {
        int type = expr.getArrayType();
        ASTList size = expr.getArraySize();
        ASTList classname = expr.getClassName();
        ArrayInit init = expr.getInitializer();
        if (size.length() > 1) {
            if (init != null)
                throw new CompileError(
                        "sorry, multi-dimensional array initializer " +
                        "for new is not supported");

            atMultiNewArray(type, classname, size);
            return;
        }

        ASTree sizeExpr = size.head();
        atNewArrayExpr2(type, sizeExpr, Declarator.astToClassName(classname, '/'), init);
    }

    private void atNewArrayExpr2(int type, ASTree sizeExpr,
                        String jvmClassname, ArrayInit init) throws CompileError {
        if (init == null)
            if (sizeExpr == null)
                throw new CompileError("no array size");
            else
                sizeExpr.accept(this);
        else
            if (sizeExpr == null) {
                int s = init.length();
                bytecode.addIconst(s);
            }
            else
                throw new CompileError("unnecessary array size specified for new");

        String elementClass;
        if (type == CLASS) {
            elementClass = resolveClassName(jvmClassname);
            bytecode.addAnewarray(MemberResolver.jvmToJavaName(elementClass));
        }
        else {
            elementClass = null;
            int atype = 0;
            switch (type) {
            case BOOLEAN :
                atype = T_BOOLEAN;
                break;
            case CHAR :
                atype = T_CHAR;
                break;
            case FLOAT :
                atype = T_FLOAT;
                break;
            case DOUBLE :
                atype = T_DOUBLE;
                break;
            case BYTE :
                atype = T_BYTE;
                break;
            case SHORT :
                atype = T_SHORT;
                break;
            case INT :
                atype = T_INT;
                break;
            case LONG :
                atype = T_LONG;
                break;
            default :
                badNewExpr();
                break;
            }

            bytecode.addOpcode(NEWARRAY);
            bytecode.add(atype);
        }

        if (init != null) {
            int s = init.length();
            ASTList list = init;
            for (int i = 0; i < s; i++) {
                bytecode.addOpcode(DUP);
                bytecode.addIconst(i);
                list.head().accept(this);
                if (!isRefType(type))
                    atNumCastExpr(exprType, type);

                bytecode.addOpcode(getArrayWriteOp(type, 0));
                list = list.tail();
            }
        }

        exprType = type;
        arrayDim = 1;
        className = elementClass;
    }

    private static void badNewExpr() throws CompileError {
        throw new CompileError("bad new expression");
    }

    @Override
    protected void atArrayVariableAssign(ArrayInit init, int varType,
                                         int varArray, String varClass) throws CompileError {
        atNewArrayExpr2(varType, null, varClass, init);
    }

    @Override
    public void atArrayInit(ArrayInit init) throws CompileError {
        throw new CompileError("array initializer is not supported");
    }

    protected void atMultiNewArray(int type, ASTList classname, ASTList size)
        throws CompileError
    {
        int count, dim;
        dim = size.length();
        for (count = 0; size != null; size = size.tail()) {
            ASTree s = size.head();
            if (s == null)
                break;          // int[][][] a = new int[3][4][];

            ++count;
            s.accept(this);
            if (exprType != INT)
                throw new CompileError("bad type for array size");
        }

        String desc;
        exprType = type;
        arrayDim = dim;
        if (type == CLASS) {
            className = resolveClassName(classname);
            desc = toJvmArrayName(className, dim);
        }
        else
            desc = toJvmTypeName(type, dim);

        bytecode.addMultiNewarray(desc, count);
    }

    @Override
    public void atCallExpr(CallExpr expr) throws CompileError {
        String mname = null;
        CtClass targetClass = null;
        ASTree method = expr.oprand1();
        ASTList args = (ASTList)expr.oprand2();
        boolean isStatic = false;
        boolean isSpecial = false;
        int aload0pos = -1;

        MemberResolver.Method cached = expr.getMethod();
        if (method instanceof Member) {
            mname = ((Member)method).get();
            targetClass = thisClass;
            if (inStaticMethod || (cached != null && cached.isStatic()))
                isStatic = true;            // should be static
            else {
                aload0pos = bytecode.currentPc();
                bytecode.addAload(0);       // this
            }
        }
        else if (method instanceof Keyword) {   // constructor
            isSpecial = true;
            mname = MethodInfo.nameInit;        // <init>
            targetClass = thisClass;
            if (inStaticMethod)
                throw new CompileError("a constructor cannot be static");
            bytecode.addAload(0);   // this

            if (((Keyword)method).get() == SUPER)
                targetClass = MemberResolver.getSuperclass(targetClass);
        }
        else if (method instanceof Expr) {
            Expr e = (Expr)method;
            mname = ((Symbol)e.oprand2()).get();
            int op = e.getOperator();
            if (op == MEMBER) {                 // static method
                targetClass
                    = resolver.lookupClass(((Symbol)e.oprand1()).get(), false);
                isStatic = true;
            }
            else if (op == '.') {
                ASTree target = e.oprand1();
                String classFollowedByDotSuper = TypeChecker.isDotSuper(target);
                if (classFollowedByDotSuper != null) {
                    isSpecial = true;
                    targetClass = MemberResolver.getSuperInterface(thisClass,
                                                        classFollowedByDotSuper);
                    if (inStaticMethod || (cached != null && cached.isStatic()))
                        isStatic = true;            // should be static
                    else {
                        aload0pos = bytecode.currentPc();
                        bytecode.addAload(0);       // this
                    }
                }
                else {
                    if (target instanceof Keyword)
                        if (((Keyword)target).get() == SUPER)
                            isSpecial = true;

                    try {
                        target.accept(this);
                    }
                    catch (NoFieldException nfe) {
                        if (nfe.getExpr() != target)
                            throw nfe;

                        // it should be a static method.
                        exprType = CLASS;
                        arrayDim = 0;
                        className = nfe.getField(); // JVM-internal
                        isStatic = true;
                    }

                    if (arrayDim > 0)
                        targetClass = resolver.lookupClass(javaLangObject, true);
                    else if (exprType == CLASS /* && arrayDim == 0 */)
                        targetClass = resolver.lookupClassByJvmName(className);
                    else
                        badMethod();
                }
            }
            else
                badMethod();
        }
        else
            fatal();

        atMethodCallCore(targetClass, mname, args, isStatic, isSpecial,
                         aload0pos, cached);
    }

    private static void badMethod() throws CompileError {
        throw new CompileError("bad method");
    }

    /*
     * atMethodCallCore() is also called by doit() in NewExpr.ProceedForNew
     *
     * @param targetClass       the class at which method lookup starts.
     * @param found         not null if the method look has been already done.
     */
    public void atMethodCallCore(CtClass targetClass, String mname,
                        ASTList args, boolean isStatic, boolean isSpecial,
                        int aload0pos, MemberResolver.Method found)
        throws CompileError
    {
        int nargs = getMethodArgsLength(args);
        int[] types = new int[nargs];
        int[] dims = new int[nargs];
        String[] cnames = new String[nargs];

        if (!isStatic && found != null && found.isStatic()) {
            bytecode.addOpcode(POP);
            isStatic = true;
        }

        @SuppressWarnings("unused")
        int stack = bytecode.getStackDepth();

        // generate code for evaluating arguments.
        atMethodArgs(args, types, dims, cnames);

        if (found == null)
            found = resolver.lookupMethod(targetClass, thisClass, thisMethod,
                                          mname, types, dims, cnames);

        if (found == null) {
            String msg;
            if (mname.equals(MethodInfo.nameInit))
                msg = "constructor not found";
            else
                msg = "Method " + mname + " not found in "
                    + targetClass.getName();

            throw new CompileError(msg);
        }

        atMethodCallCore2(targetClass, mname, isStatic, isSpecial,
                          aload0pos, found);
    }

    private boolean isFromSameDeclaringClass(CtClass outer, CtClass inner) {
        try {
            while (outer != null) {
                if (isEnclosing(outer, inner))
                    return true;
                outer = outer.getDeclaringClass();
            }
        }
        catch (NotFoundException e) {}
        return false;
    }

    private void atMethodCallCore2(CtClass targetClass, String mname,
                                   boolean isStatic, boolean isSpecial,
                                   int aload0pos,
                                   MemberResolver.Method found)
        throws CompileError
    {
        CtClass declClass = found.declaring;
        MethodInfo minfo = found.info;
        String desc = minfo.getDescriptor();
        int acc = minfo.getAccessFlags();

        if (mname.equals(MethodInfo.nameInit)) {
            isSpecial = true;
            if (declClass != targetClass)
                throw new CompileError("no such constructor: " + targetClass.getName());

            if (declClass != thisClass && AccessFlag.isPrivate(acc)) {
                if (declClass.getClassFile().getMajorVersion() < ClassFile.JAVA_11
                        || !isFromSameDeclaringClass(declClass, thisClass)) {
                    desc = getAccessibleConstructor(desc, declClass, minfo);
                    bytecode.addOpcode(Opcode.ACONST_NULL); // the last parameter
                }
            }
        }
        else if (AccessFlag.isPrivate(acc))
            if (declClass == thisClass)
                isSpecial = true;
            else {
                isSpecial = false;
                isStatic = true;
                String origDesc = desc;
                if ((acc & AccessFlag.STATIC) == 0)
                    desc = Descriptor.insertParameter(declClass.getName(),
                                                      origDesc);

                acc = AccessFlag.setPackage(acc) | AccessFlag.STATIC;
                mname = getAccessiblePrivate(mname, origDesc, desc,
                                             minfo, declClass);
            }

        boolean popTarget = false;
        if ((acc & AccessFlag.STATIC) != 0) {
            if (!isStatic) {
                /* this method is static but the target object is
                   on stack.  It must be popped out.  If aload0pos >= 0,
                   then the target object was pushed by aload_0.  It is
                   overwritten by NOP.
                */
                isStatic = true;
                if (aload0pos >= 0)
                    bytecode.write(aload0pos, NOP);
                else
                    popTarget = true;
            }

            bytecode.addInvokestatic(declClass, mname, desc);
        }
        else if (isSpecial)    // if (isSpecial && notStatic(acc))
            bytecode.addInvokespecial(targetClass, mname, desc);
        else {
            if (!Modifier.isPublic(declClass.getModifiers())
                || declClass.isInterface() != targetClass.isInterface())
                declClass = targetClass;

            if (declClass.isInterface()) {
                int nargs = Descriptor.paramSize(desc) + 1;
                bytecode.addInvokeinterface(declClass, mname, desc, nargs);
            }
            else
                if (isStatic)
                    throw new CompileError(mname + " is not static");
                else
                    bytecode.addInvokevirtual(declClass, mname, desc);
        }

        setReturnType(desc, isStatic, popTarget);
    }

    /*
     * Finds (or adds if necessary) a hidden accessor if the method
     * is in an enclosing class.
     *
     * @param desc          the descriptor of the method.
     * @param declClass     the class declaring the method.
     */
    protected String getAccessiblePrivate(String methodName, String desc,
                                          String newDesc, MethodInfo minfo,
                                          CtClass declClass)
        throws CompileError
    {
        if (isEnclosing(declClass, thisClass)) {
            AccessorMaker maker = declClass.getAccessorMaker();
            if (maker != null)
                return maker.getMethodAccessor(methodName, desc, newDesc,
                                               minfo);
        }

        throw new CompileError("Method " + methodName
                               + " is private");
    }

    /*
     * Finds (or adds if necessary) a hidden constructor if the given
     * constructor is in an enclosing class.
     *
     * @param desc          the descriptor of the constructor.
     * @param declClass     the class declaring the constructor.
     * @param minfo         the method info of the constructor.
     * @return the descriptor of the hidden constructor.
     */
    protected String getAccessibleConstructor(String desc, CtClass declClass,
                                              MethodInfo minfo)
        throws CompileError
    {
        if (isEnclosing(declClass, thisClass)) {
            AccessorMaker maker = declClass.getAccessorMaker();
            if (maker != null)
                return maker.getConstructor(declClass, desc, minfo);
        }

        throw new CompileError("the called constructor is private in "
                               + declClass.getName());
    }

    private boolean isEnclosing(CtClass outer, CtClass inner) {
        try {
            while (inner != null) {
                inner = inner.getDeclaringClass();
                if (inner == outer)
                    return true;
            }
        }
        catch (NotFoundException e) {}
        return false;   
    }

    public int getMethodArgsLength(ASTList args) {
        return ASTList.length(args);
    }

    public void atMethodArgs(ASTList args, int[] types, int[] dims,
                             String[] cnames) throws CompileError {
        int i = 0;
        while (args != null) {
            ASTree a = args.head();
            a.accept(this);
            types[i] = exprType;
            dims[i] = arrayDim;
            cnames[i] = className;
            ++i;
            args = args.tail();
        }
    }

    void setReturnType(String desc, boolean isStatic, boolean popTarget)
        throws CompileError
    {
        int i = desc.indexOf(')');
        if (i < 0)
            badMethod();

        char c = desc.charAt(++i);
        int dim = 0;
        while (c == '[') {
            ++dim;
            c = desc.charAt(++i);
        }

        arrayDim = dim;
        if (c == 'L') {
            int j = desc.indexOf(';', i + 1);
            if (j < 0)
                badMethod();

            exprType = CLASS;
            className = desc.substring(i + 1, j);
        }
        else {
            exprType = MemberResolver.descToType(c);
            className = null;
        }

        int etype = exprType;
        if (isStatic) {
            if (popTarget) {
                if (is2word(etype, dim)) {
                    bytecode.addOpcode(DUP2_X1);
                    bytecode.addOpcode(POP2);
                    bytecode.addOpcode(POP);
                }
                else if (etype == VOID)
                    bytecode.addOpcode(POP);
                else {
                    bytecode.addOpcode(SWAP);
                    bytecode.addOpcode(POP);
                }
            }
        }
    }

    @Override
    protected void atFieldAssign(Expr expr, int op, ASTree left,
                        ASTree right, boolean doDup) throws CompileError
    {
        CtField f = fieldAccess(left, false);
        boolean is_static = resultStatic;
        if (op != '=' && !is_static)
            bytecode.addOpcode(DUP);

        int fi;
        if (op == '=') {
            FieldInfo finfo = f.getFieldInfo2();
            setFieldType(finfo);
            AccessorMaker maker = isAccessibleField(f, finfo);            
            if (maker == null)
                fi = addFieldrefInfo(f, finfo);
            else
                fi = 0;
        }
        else
            fi = atFieldRead(f, is_static);

        int fType = exprType;
        int fDim = arrayDim;
        String cname = className;

        atAssignCore(expr, op, right, fType, fDim, cname);

        boolean is2w = is2word(fType, fDim);
        if (doDup) {
            int dup_code;
            if (is_static)
                dup_code = (is2w ? DUP2 : DUP);
            else
                dup_code = (is2w ? DUP2_X1 : DUP_X1);

            bytecode.addOpcode(dup_code);
        }

        atFieldAssignCore(f, is_static, fi, is2w);

        exprType = fType;
        arrayDim = fDim;
        className = cname;
    }

    /* If fi == 0, the field must be a private field in an enclosing class.
     */
    private void atFieldAssignCore(CtField f, boolean is_static, int fi,
                                   boolean is2byte) throws CompileError {
        if (fi != 0) {
            if (is_static) {
               bytecode.add(PUTSTATIC);
               bytecode.growStack(is2byte ? -2 : -1);
            }
            else {
                bytecode.add(PUTFIELD);
                bytecode.growStack(is2byte ? -3 : -2);
            }

            bytecode.addIndex(fi);
        }
        else {
            CtClass declClass = f.getDeclaringClass();
            AccessorMaker maker = declClass.getAccessorMaker();
            // make should be non null.
            FieldInfo finfo = f.getFieldInfo2();
            MethodInfo minfo = maker.getFieldSetter(finfo, is_static);
            bytecode.addInvokestatic(declClass, minfo.getName(),
                                     minfo.getDescriptor());
        }
    }

    /* overwritten in JvstCodeGen.
     */
    @Override
    public void atMember(Member mem) throws CompileError {
        atFieldRead(mem);
    }

    @Override
    protected void atFieldRead(ASTree expr) throws CompileError
    {
        CtField f = fieldAccess(expr, true);
        if (f == null) {
            atArrayLength(expr);
            return;
        }

        boolean is_static = resultStatic;
        ASTree cexpr = TypeChecker.getConstantFieldValue(f);
        if (cexpr == null)
            atFieldRead(f, is_static);
        else {
            cexpr.accept(this);
            setFieldType(f.getFieldInfo2());
        }
    }

    private void atArrayLength(ASTree expr) throws CompileError {
        if (arrayDim == 0)
            throw new CompileError(".length applied to a non array");

        bytecode.addOpcode(ARRAYLENGTH);
        exprType = INT;
        arrayDim = 0;
    }

    /**
     * Generates bytecode for reading a field value.
     * It returns a fieldref_info index or zero if the field is a private
     * one declared in an enclosing class. 
     */
    private int atFieldRead(CtField f, boolean isStatic) throws CompileError {
        FieldInfo finfo = f.getFieldInfo2();
        boolean is2byte = setFieldType(finfo);
        AccessorMaker maker = isAccessibleField(f, finfo);
        if (maker != null) {
            MethodInfo minfo = maker.getFieldGetter(finfo, isStatic);
            bytecode.addInvokestatic(f.getDeclaringClass(), minfo.getName(),
                                     minfo.getDescriptor());
            return 0;
        }
        int fi = addFieldrefInfo(f, finfo);
        if (isStatic) {
            bytecode.add(GETSTATIC);
            bytecode.growStack(is2byte ? 2 : 1);
        }
        else {
            bytecode.add(GETFIELD);
            bytecode.growStack(is2byte ? 1 : 0);
        }

        bytecode.addIndex(fi);
        return fi;
    }

    /**
     * Returns null if the field is accessible.  Otherwise, it throws
     * an exception or it returns AccessorMaker if the field is a private
     * one declared in an enclosing class.
     */
    private AccessorMaker isAccessibleField(CtField f, FieldInfo finfo)
        throws CompileError
    {
        if (AccessFlag.isPrivate(finfo.getAccessFlags())
                && f.getDeclaringClass() != thisClass) {
            CtClass declClass = f.getDeclaringClass();
            if (isEnclosing(declClass, thisClass)) {
                AccessorMaker maker = declClass.getAccessorMaker();
                if (maker != null)
                    return maker;
            }
            throw new CompileError("Field " + f.getName() + " in "
                                   + declClass.getName() + " is private.");
        }

        return null;    // accessible field
    }

    /**
     * Sets exprType, arrayDim, and className.
     *
     * @return true if the field type is long or double. 
     */
    private boolean setFieldType(FieldInfo finfo) throws CompileError {
        String type = finfo.getDescriptor();

        int i = 0;
        int dim = 0;
        char c = type.charAt(i);
        while (c == '[') {
            ++dim;
            c = type.charAt(++i);
        }

        arrayDim = dim;
        exprType = MemberResolver.descToType(c);

        if (c == 'L')
            className = type.substring(i + 1, type.indexOf(';', i + 1));
        else
            className = null;

        boolean is2byte = dim == 0 && (c == 'J' || c == 'D');
        return is2byte;
    }

    private int addFieldrefInfo(CtField f, FieldInfo finfo) {
        ConstPool cp = bytecode.getConstPool();
        String cname = f.getDeclaringClass().getName();
        int ci = cp.addClassInfo(cname);
        String name = finfo.getName();
        String type = finfo.getDescriptor();
        return cp.addFieldrefInfo(ci, name, type);
    }

    @Override
    protected void atClassObject2(String cname) throws CompileError {
        if (getMajorVersion() < ClassFile.JAVA_5)
            super.atClassObject2(cname);
        else
            bytecode.addLdc(bytecode.getConstPool().addClassInfo(cname));
    }

    @Override
    protected void atFieldPlusPlus(int token, boolean isPost,
                                   ASTree oprand, Expr expr, boolean doDup)
        throws CompileError
    {
        CtField f = fieldAccess(oprand, false);
        boolean is_static = resultStatic;
        if (!is_static)
            bytecode.addOpcode(DUP);

        int fi = atFieldRead(f, is_static);
        int t = exprType;
        boolean is2w = is2word(t, arrayDim);

        int dup_code;
        if (is_static)
            dup_code = (is2w ? DUP2 : DUP);
        else
            dup_code = (is2w ? DUP2_X1 : DUP_X1);

        atPlusPlusCore(dup_code, doDup, token, isPost, expr);
        atFieldAssignCore(f, is_static, fi, is2w);
    }

    /* This method also returns a value in resultStatic.
     *
     * @param acceptLength      true if array length is acceptable
     */
    protected CtField fieldAccess(ASTree expr, boolean acceptLength)
            throws CompileError
    {
        if (expr instanceof Member) {
            String name = ((Member)expr).get();
            CtField f = null;
            try {
                f = thisClass.getField(name);
            }
            catch (NotFoundException e) {
                // EXPR might be part of a static member access?
                throw new NoFieldException(name, expr);
            }

            boolean is_static = Modifier.isStatic(f.getModifiers());
            if (!is_static)
                if (inStaticMethod)
                    throw new CompileError(
                                "not available in a static method: " + name);
                else
                    bytecode.addAload(0);       // this

            resultStatic = is_static;
            return f;
        }
        else if (expr instanceof Expr) {
            Expr e = (Expr)expr;
            int op = e.getOperator();
            if (op == MEMBER) {
                /* static member by # (extension by Javassist)
                 * For example, if int.class is parsed, the resulting tree
                 * is (# "java.lang.Integer" "TYPE"). 
                 */
                CtField f = resolver.lookupField(((Symbol)e.oprand1()).get(),
                                         (Symbol)e.oprand2());
                resultStatic = true;
                return f;
            }
            else if (op == '.') {
                CtField f = null;
                try {
                    e.oprand1().accept(this);
                    /* Don't call lookupFieldByJvmName2().
                     * The left operand of . is not a class name but
                     * a normal expression.
                     */
                    if (exprType == CLASS && arrayDim == 0)
                        f = resolver.lookupFieldByJvmName(className,
                                                    (Symbol)e.oprand2());
                    else if (acceptLength && arrayDim > 0
                             && ((Symbol)e.oprand2()).get().equals("length"))
                        return null;    // expr is an array length.
                    else
                        badLvalue();

                    boolean is_static = Modifier.isStatic(f.getModifiers());
                    if (is_static)
                        bytecode.addOpcode(POP);

                    resultStatic = is_static;
                    return f;
                }
                catch (NoFieldException nfe) {
                    if (nfe.getExpr() != e.oprand1())
                        throw nfe;

                    /* EXPR should be a static field.
                     * If EXPR might be part of a qualified class name,
                     * lookupFieldByJvmName2() throws NoFieldException.
                     */
                    Symbol fname = (Symbol)e.oprand2();
                    String cname = nfe.getField();
                    f = resolver.lookupFieldByJvmName2(cname, fname, expr);
                    resultStatic = true;
                    return f;
                }
            }
            else
                badLvalue();
        }
        else
            badLvalue();

        resultStatic = false;
        return null;    // never reach
    }

    private static void badLvalue() throws CompileError {
        throw new CompileError("bad l-value");
    }

    public CtClass[] makeParamList(MethodDecl md) throws CompileError {
        CtClass[] params;
        ASTList plist = md.getParams();
        if (plist == null)
            params = new CtClass[0];
        else {
            int i = 0;
            params = new CtClass[plist.length()];
            while (plist != null) {
                params[i++] = resolver.lookupClass((Declarator)plist.head());
                plist = plist.tail();
            }
        }

        return params;
    }

    public CtClass[] makeThrowsList(MethodDecl md) throws CompileError {
        CtClass[] clist;
        ASTList list = md.getThrows();
        if (list == null)
            return null;
        int i = 0;
        clist = new CtClass[list.length()];
        while (list != null) {
            clist[i++] = resolver.lookupClassByName((ASTList)list.head());
            list = list.tail();
        }

        return clist;
    }

    /* Converts a class name into a JVM-internal representation.
     *
     * It may also expand a simple class name to java.lang.*.
     * For example, this converts Object into java/lang/Object.
     */
    @Override
    protected String resolveClassName(ASTList name) throws CompileError {
        return resolver.resolveClassName(name);
    }

    /* Expands a simple class name to java.lang.*.
     * For example, this converts Object into java/lang/Object.
     */
    @Override
    protected String resolveClassName(String jvmName) throws CompileError {
        return resolver.resolveJvmClassName(jvmName);
    }
}
