/*
 * 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 javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.Modifier;
import javassist.NotFoundException;
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.AssignExpr;
import javassist.compiler.ast.BinExpr;
import javassist.compiler.ast.CallExpr;
import javassist.compiler.ast.CastExpr;
import javassist.compiler.ast.CondExpr;
import javassist.compiler.ast.Declarator;
import javassist.compiler.ast.DoubleConst;
import javassist.compiler.ast.Expr;
import javassist.compiler.ast.InstanceOfExpr;
import javassist.compiler.ast.IntConst;
import javassist.compiler.ast.Keyword;
import javassist.compiler.ast.Member;
import javassist.compiler.ast.NewExpr;
import javassist.compiler.ast.StringL;
import javassist.compiler.ast.Symbol;
import javassist.compiler.ast.Variable;
import javassist.compiler.ast.Visitor;

public class TypeChecker extends Visitor implements Opcode, TokenId {
    static final String javaLangObject = "java.lang.Object";
    static final String jvmJavaLangObject = "java/lang/Object";
    static final String jvmJavaLangString = "java/lang/String";
    static final String jvmJavaLangClass = "java/lang/Class";

    /* The following fields are used by atXXX() methods
     * for returning the type of the compiled expression.
     */
    protected int exprType;     // VOID, NULL, CLASS, BOOLEAN, INT, ...
    protected int arrayDim;
    protected String className; // JVM-internal representation

    protected MemberResolver resolver;
    protected CtClass   thisClass;
    protected MethodInfo thisMethod;

    public TypeChecker(CtClass cc, ClassPool cp) {
        resolver = new MemberResolver(cp);
        thisClass = cc;
        thisMethod = null;
    }

    /*
     * Converts an array of tuples of exprType, arrayDim, and className
     * into a String object.
     */
    protected static String argTypesToString(int[] types, int[] dims,
                                             String[] cnames) {
        StringBuilder sbuf = new StringBuilder();
        sbuf.append('(');
        int n = types.length;
        if (n > 0) {
            int i = 0;
            while (true) {
                typeToString(sbuf, types[i], dims[i], cnames[i]);
                if (++i < n)
                    sbuf.append(',');
                else
                    break;
            }
        }

        sbuf.append(')');
        return sbuf.toString();
    }

    /*
     * Converts a tuple of exprType, arrayDim, and className
     * into a String object.
     */
    protected static StringBuilder typeToString(StringBuilder sbuf,
                                        int type, int dim, String cname) {
        String s;
        if (type == CLASS)
            s = MemberResolver.jvmToJavaName(cname);
        else if (type == NULL)
            s = "Object";
        else
            try {
                s = MemberResolver.getTypeName(type);
            }
            catch (CompileError e) {
                s = "?";
            }

        sbuf.append(s);
        while (dim-- > 0)
            sbuf.append("[]");

        return sbuf;
    }

    /**
     * Records the currently compiled method.
     */
    public void setThisMethod(MethodInfo m) {
        thisMethod = m;
    }

    protected static void fatal() throws CompileError {
        throw new CompileError("fatal");
    }

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

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

    /* 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.
     */
    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.
     */
    protected String resolveClassName(String jvmName) throws CompileError {
        return resolver.resolveJvmClassName(jvmName);
    }

    @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();
            atMethodCallCore(clazz, MethodInfo.nameInit, args);
            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();
        ASTree init = expr.getInitializer();
        if (init != null)
            init.accept(this);

        if (size.length() > 1)
            atMultiNewArray(type, classname, size);
        else {
            ASTree sizeExpr = size.head();
            if (sizeExpr != null)
                sizeExpr.accept(this);

            exprType = type;
            arrayDim = 1;
            if (type == CLASS)
                className = resolveClassName(classname);
            else
                className = null;
        }
    }

    @Override
    public void atArrayInit(ArrayInit init) throws CompileError {
        ASTList list = init;
        while (list != null) {
            ASTree h = list.head();
            list = list.tail();
            if (h != null)
                h.accept(this);
        }
    }

    protected void atMultiNewArray(int type, ASTList classname, ASTList size)
        throws CompileError
    {
        @SuppressWarnings("unused")
        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);
        }

        exprType = type;
        arrayDim = dim;
        if (type == CLASS)
            className = resolveClassName(classname);
        else
            className = null;
    }

    @Override
    public void atAssignExpr(AssignExpr expr) throws CompileError {
        // =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, >>>=
        int op = expr.getOperator();
        ASTree left = expr.oprand1();
        ASTree right = expr.oprand2();
        if (left instanceof Variable)
            atVariableAssign(expr, op, (Variable)left,
                             ((Variable)left).getDeclarator(),
                             right);
        else {
            if (left instanceof Expr) {
                Expr e = (Expr)left;
                if (e.getOperator() == ARRAY) {
                    atArrayAssign(expr, op, (Expr)left, right);
                    return;
                }
            }

            atFieldAssign(expr, op, left, right);
        }
    }

    /* op is either =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, or >>>=.
     *
     * expr and var can be null.
     */
    private void atVariableAssign(Expr expr, int op, Variable var,
                                  Declarator d, ASTree right)
        throws CompileError
    {
        int varType = d.getType();
        int varArray = d.getArrayDim();
        String varClass = d.getClassName();

        if (op != '=')
            atVariable(var);

        right.accept(this);
        exprType = varType;
        arrayDim = varArray;
        className = varClass;
    }

    private void atArrayAssign(Expr expr, int op, Expr array,
                        ASTree right) throws CompileError
    {
        atArrayRead(array.oprand1(), array.oprand2());
        int aType = exprType;
        int aDim = arrayDim;
        String cname = className;
        right.accept(this);
        exprType = aType;
        arrayDim = aDim;
        className = cname;
    }

    protected void atFieldAssign(Expr expr, int op, ASTree left, ASTree right)
        throws CompileError
    {
        CtField f = fieldAccess(left);
        atFieldRead(f);
        int fType = exprType;
        int fDim = arrayDim;
        String cname = className;
        right.accept(this);
        exprType = fType;
        arrayDim = fDim;
        className = cname;
    }

    @Override
    public void atCondExpr(CondExpr expr) throws CompileError {
        booleanExpr(expr.condExpr());
        expr.thenExpr().accept(this);
        int type1 = exprType;
        int dim1 = arrayDim;
        @SuppressWarnings("unused")
        String cname1 = className;
        expr.elseExpr().accept(this);

        if (dim1 == 0 && dim1 == arrayDim)
            if (CodeGen.rightIsStrong(type1, exprType))
                expr.setThen(new CastExpr(exprType, 0, expr.thenExpr()));
            else if (CodeGen.rightIsStrong(exprType, type1)) {
                expr.setElse(new CastExpr(type1, 0, expr.elseExpr()));
                exprType = type1;
            }
    }

    /*
     * If atBinExpr() substitutes a new expression for the original
     * binary-operator expression, it changes the operator name to '+'
     * (if the original is not '+') and sets the new expression to the
     * left-hand-side expression and null to the right-hand-side expression. 
     */
    @Override
    public void atBinExpr(BinExpr expr) throws CompileError {
        int token = expr.getOperator();
        int k = CodeGen.lookupBinOp(token);
        if (k >= 0) {
            /* arithmetic operators: +, -, *, /, %, |, ^, &, <<, >>, >>>
             */
            if (token == '+') {
                Expr e = atPlusExpr(expr);
                if (e != null) {
                    /* String concatenation has been translated into
                     * an expression using StringBuffer.
                     */
                    e = CallExpr.makeCall(Expr.make('.', e,
                                            new Member("toString")), null);
                    expr.setOprand1(e);
                    expr.setOprand2(null);    // <---- look at this!
                    className = jvmJavaLangString;
                }
            }
            else {
                ASTree left = expr.oprand1();
                ASTree right = expr.oprand2();
                left.accept(this);
                int type1 = exprType;
                right.accept(this);
                if (!isConstant(expr, token, left, right))
                    computeBinExprType(expr, token, type1);
            }
        }
        else {
            /* equation: &&, ||, ==, !=, <=, >=, <, >
            */
            booleanExpr(expr);
        }
    }

    /* EXPR must be a + expression.
     * atPlusExpr() returns non-null if the given expression is string
     * concatenation.  The returned value is "new StringBuffer().append..".
     */
    private Expr atPlusExpr(BinExpr expr) throws CompileError {
        ASTree left = expr.oprand1();
        ASTree right = expr.oprand2();
        if (right == null) {
            // this expression has been already type-checked.
            // see atBinExpr() above.
            left.accept(this);
            return null;
        }

        if (isPlusExpr(left)) {
            Expr newExpr = atPlusExpr((BinExpr)left);
            if (newExpr != null) {
                right.accept(this);
                exprType = CLASS;
                arrayDim = 0;
                className = "java/lang/StringBuffer";
                return makeAppendCall(newExpr, right);
            }
        }
        else
            left.accept(this);

        int type1 = exprType;
        int dim1 = arrayDim;
        String cname = className;
        right.accept(this);

        if (isConstant(expr, '+', left, right))
            return null;

        if ((type1 == CLASS && dim1 == 0 && jvmJavaLangString.equals(cname))
            || (exprType == CLASS && arrayDim == 0
                && jvmJavaLangString.equals(className))) {
            ASTList sbufClass = ASTList.make(new Symbol("java"),
                            new Symbol("lang"), new Symbol("StringBuffer"));
            ASTree e = new NewExpr(sbufClass, null);
            exprType = CLASS;
            arrayDim = 0;
            className = "java/lang/StringBuffer";
            return makeAppendCall(makeAppendCall(e, left), right);
        }
        computeBinExprType(expr, '+', type1);
        return null;
    }

    private boolean isConstant(BinExpr expr, int op, ASTree left,
                               ASTree right) throws CompileError
    {
        left = stripPlusExpr(left);
        right = stripPlusExpr(right);
        ASTree newExpr = null;
        if (left instanceof StringL && right instanceof StringL && op == '+')
            newExpr = new StringL(((StringL)left).get()
                                  + ((StringL)right).get());
        else if (left instanceof IntConst)
            newExpr = ((IntConst)left).compute(op, right);
        else if (left instanceof DoubleConst)
            newExpr = ((DoubleConst)left).compute(op, right);

        if (newExpr == null)
            return false;       // not a constant expression
        expr.setOperator('+');
        expr.setOprand1(newExpr);
        expr.setOprand2(null);
        newExpr.accept(this);   // for setting exprType, arrayDim, ...
        return true;
    }

    /* CodeGen.atSwitchStmnt() also calls stripPlusExpr().
     */
    static ASTree stripPlusExpr(ASTree expr) {
        if (expr instanceof BinExpr) {
            BinExpr e = (BinExpr)expr;
            if (e.getOperator() == '+' && e.oprand2() == null)
                return e.getLeft();
        }
        else if (expr instanceof Expr) {    // note: BinExpr extends Expr.
            Expr e = (Expr)expr;
            int op = e.getOperator();
            if (op == MEMBER) {
                ASTree cexpr = getConstantFieldValue((Member)e.oprand2());
                if (cexpr != null)
                    return cexpr;
            }
            else if (op == '+' && e.getRight() == null)
                return e.getLeft();
        }
        else if (expr instanceof Member) {
            ASTree cexpr = getConstantFieldValue((Member)expr);
            if (cexpr != null)
                return cexpr;
        }

        return expr;
    }

    /**
     * If MEM is a static final field, this method returns a constant
     * expression representing the value of that field.
     */
    private static ASTree getConstantFieldValue(Member mem) {
        return getConstantFieldValue(mem.getField());
    }

    public static ASTree getConstantFieldValue(CtField f) {
        if (f == null)
            return null;

        Object value = f.getConstantValue();
        if (value == null)
            return null;

        if (value instanceof String)
            return new StringL((String)value);
        else if (value instanceof Double || value instanceof Float) {
            int token = (value instanceof Double)
                        ? DoubleConstant : FloatConstant;
            return new DoubleConst(((Number)value).doubleValue(), token);
        }
        else if (value instanceof Number) {
            int token = (value instanceof Long) ? LongConstant : IntConstant; 
            return new IntConst(((Number)value).longValue(), token);
        }
        else if (value instanceof Boolean)
            return new Keyword(((Boolean)value).booleanValue()
                               ? TokenId.TRUE : TokenId.FALSE);
        else
            return null;
    }

    private static boolean isPlusExpr(ASTree expr) {
        if (expr instanceof BinExpr) {
            BinExpr bexpr = (BinExpr)expr;
            int token = bexpr.getOperator();
            return token == '+';
        }

        return false;
    }

    private static Expr makeAppendCall(ASTree target, ASTree arg) {
        return CallExpr.makeCall(Expr.make('.', target, new Member("append")),
                                 new ASTList(arg));
    }

    private void computeBinExprType(BinExpr expr, int token, int type1)
        throws CompileError
    {
        // arrayDim should be 0.
        int type2 = exprType;
        if (token == LSHIFT || token == RSHIFT || token == ARSHIFT)
            exprType = type1;
        else
            insertCast(expr, type1, type2);

        if (CodeGen.isP_INT(exprType) && exprType != BOOLEAN)
            exprType = INT;         // type1 may be BYTE, ...
    }

    private void booleanExpr(ASTree expr)
        throws CompileError
    {
        int op = CodeGen.getCompOperator(expr);
        if (op == EQ) {         // ==, !=, ...
            BinExpr bexpr = (BinExpr)expr;
            bexpr.oprand1().accept(this);
            int type1 = exprType;
            int dim1 = arrayDim;
            bexpr.oprand2().accept(this);
            if (dim1 == 0 && arrayDim == 0)
                insertCast(bexpr, type1, exprType);
        }
        else if (op == '!')
            ((Expr)expr).oprand1().accept(this);
        else if (op == ANDAND || op == OROR) {
            BinExpr bexpr = (BinExpr)expr;
            bexpr.oprand1().accept(this);
            bexpr.oprand2().accept(this);
        }
        else                // others
            expr.accept(this);

        exprType = BOOLEAN;
        arrayDim = 0;
    }

    private void insertCast(BinExpr expr, int type1, int type2)
        throws CompileError
    {
        if (CodeGen.rightIsStrong(type1, type2))
            expr.setLeft(new CastExpr(type2, 0, expr.oprand1()));
        else
            exprType = type1;
    }

    @Override
    public void atCastExpr(CastExpr expr) throws CompileError {
        String cname = resolveClassName(expr.getClassName());
        expr.getOprand().accept(this);
        exprType = expr.getType();
        arrayDim = expr.getArrayDim();
        className = cname;
    }

    @Override
    public void atInstanceOfExpr(InstanceOfExpr expr) throws CompileError {
        expr.getOprand().accept(this);
        exprType = BOOLEAN;
        arrayDim = 0;
    }

    @Override
    public void atExpr(Expr expr) throws CompileError {
        // array access, member access,
        // (unary) +, (unary) -, ++, --, !, ~

        int token = expr.getOperator();
        ASTree oprand = expr.oprand1();
        if (token == '.') {
            String member = ((Symbol)expr.oprand2()).get();
            if (member.equals("length"))
                try {
                    atArrayLength(expr);
                }
                catch (NoFieldException nfe) {
                    // length might be a class or package name.
                    atFieldRead(expr);
                }
            else if (member.equals("class"))
                atClassObject(expr);  // .class
            else
                atFieldRead(expr);
        }
        else if (token == MEMBER) {     // field read
            String member = ((Symbol)expr.oprand2()).get();
            if (member.equals("class"))
                atClassObject(expr);  // .class
            else
                atFieldRead(expr);
        }
        else if (token == ARRAY)
            atArrayRead(oprand, expr.oprand2());
        else if (token == PLUSPLUS || token == MINUSMINUS)
            atPlusPlus(token, oprand, expr);
        else if (token == '!')
            booleanExpr(expr);
        else if (token == CALL)              // method call
            fatal();
        else {
            oprand.accept(this);
            if (!isConstant(expr, token, oprand))
                if (token == '-' || token == '~')
                    if (CodeGen.isP_INT(exprType))
                        exprType = INT;         // type may be BYTE, ...
        }
    }

    private boolean isConstant(Expr expr, int op, ASTree oprand) {
        oprand = stripPlusExpr(oprand);
        if (oprand instanceof IntConst) {
            IntConst c = (IntConst)oprand;
            long v = c.get();
            if (op == '-')
                v = -v;
            else if (op == '~')
                v = ~v;
            else
                return false;

            c.set(v);
        }
        else if (oprand instanceof DoubleConst) {
            DoubleConst c = (DoubleConst)oprand;
            if (op == '-')
                c.set(-c.get());
            else
                return false;
        }
        else
            return false;

        expr.setOperator('+');
        return true;
    }

    @Override
    public void atCallExpr(CallExpr expr) throws CompileError {
        String mname = null;
        CtClass targetClass = null;
        ASTree method = expr.oprand1();
        ASTList args = (ASTList)expr.oprand2();

        if (method instanceof Member) {
            mname = ((Member)method).get();
            targetClass = thisClass;
        }
        else if (method instanceof Keyword) {   // constructor
            mname = MethodInfo.nameInit;        // <init>
            if (((Keyword)method).get() == SUPER)
                targetClass = MemberResolver.getSuperclass(thisClass);
            else
                targetClass = thisClass;
        }
        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);
            else if (op == '.') {
                ASTree target = e.oprand1();
                String classFollowedByDotSuper = isDotSuper(target);
                if (classFollowedByDotSuper != null)
                    targetClass = MemberResolver.getSuperInterface(thisClass,
                                                        classFollowedByDotSuper);
                else {
                    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
                        e.setOperator(MEMBER);
                        e.setOprand1(new Symbol(MemberResolver.jvmToJavaName(
                                                                className)));
                    }

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

        MemberResolver.Method minfo
                = atMethodCallCore(targetClass, mname, args);
        expr.setMethod(minfo);
    }

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

    /**
     * Returns non-null if target is something like Foo.super
     * for accessing the default method in an interface.
     * Otherwise, null.
     *
     * @return the class name followed by {@code .super} or null.
     */
    static String isDotSuper(ASTree target) {
        if (target instanceof Expr) {
            Expr e = (Expr)target;
            if (e.getOperator() == '.') {
                ASTree right = e.oprand2();
                if (right instanceof Keyword && ((Keyword)right).get() == SUPER)
                    return ((Symbol)e.oprand1()).get();
            }
        }

        return null;
    }

    /**
     * @return  a pair of the class declaring the invoked method
     *          and the MethodInfo of that method.  Never null.
     */
    public MemberResolver.Method atMethodCallCore(CtClass targetClass,
                                                  String mname, ASTList args)
        throws CompileError
    {
        int nargs = getMethodArgsLength(args);
        int[] types = new int[nargs];
        int[] dims = new int[nargs];
        String[] cnames = new String[nargs];
        atMethodArgs(args, types, dims, cnames);

        MemberResolver.Method found
            = resolver.lookupMethod(targetClass, thisClass, thisMethod,
                                    mname, types, dims, cnames);
        if (found == null) {
            String clazz = targetClass.getName();
            String signature = argTypesToString(types, dims, cnames);
            String msg;
            if (mname.equals(MethodInfo.nameInit))
                msg = "cannot find constructor " + clazz + signature;
            else
                msg = mname + signature +  " not found in " + clazz;

            throw new CompileError(msg);
        }

        String desc = found.info.getDescriptor();
        setReturnType(desc);
        return found;
    }

    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) 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;
        }
    }

    private void atFieldRead(ASTree expr) throws CompileError {
        atFieldRead(fieldAccess(expr));
    }

    private void atFieldRead(CtField f) throws CompileError {
        FieldInfo finfo = f.getFieldInfo2();
        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;
    }

    /* if EXPR is to access a static field, fieldAccess() translates EXPR
     * into an expression using '#' (MEMBER).  For example, it translates
     * java.lang.Integer.TYPE into java.lang.Integer#TYPE.  This translation
     * speeds up type resolution by MemberCodeGen.
     */
    protected CtField fieldAccess(ASTree expr) throws CompileError {
        if (expr instanceof Member) {
            Member mem = (Member)expr;
            String name = mem.get();
            try {
                CtField f = thisClass.getField(name);
                if (Modifier.isStatic(f.getModifiers()))
                    mem.setField(f);

                return f;
            }
            catch (NotFoundException e) {
                // EXPR might be part of a static member access?
                throw new NoFieldException(name, expr);
            }
        }
        else if (expr instanceof Expr) {
            Expr e = (Expr)expr;
            int op = e.getOperator();
            if (op == MEMBER) {
                Member mem = (Member)e.oprand2();
                CtField f
                    = resolver.lookupField(((Symbol)e.oprand1()).get(), mem);
                mem.setField(f);
                return f;
            }
            else if (op == '.') {
                try {
                    e.oprand1().accept(this);
                }
                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.
                     */
                    return fieldAccess2(e, nfe.getField());
                }

                CompileError err = null;
                try {
                    if (exprType == CLASS && arrayDim == 0)
                        return resolver.lookupFieldByJvmName(className,
                                                    (Symbol)e.oprand2());
                }
                catch (CompileError ce) {
                    err = ce;
                }

                /* If a filed name is the same name as a package's,
                 * a static member of a class in that package is not
                 * visible.  For example,
                 *
                 * class Foo {
                 *   int javassist;
                 * }
                 *
                 * It is impossible to add the following method:
                 *
                 * String m() { return javassist.CtClass.intType.toString(); }
                 *
                 * because javassist is a field name.  However, this is
                 * often inconvenient, this compiler allows it.  The following
                 * code is for that.
                 */
                ASTree oprnd1 = e.oprand1();
                if (oprnd1 instanceof Symbol)
                    return fieldAccess2(e, ((Symbol)oprnd1).get());

                if (err != null)
                    throw err;
            }
        }

        throw new CompileError("bad field access");
    }

    private CtField fieldAccess2(Expr e, String jvmClassName) throws CompileError {
        Member fname = (Member)e.oprand2();
        CtField f = resolver.lookupFieldByJvmName2(jvmClassName, fname, e);
        e.setOperator(MEMBER);
        e.setOprand1(new Symbol(MemberResolver.jvmToJavaName(jvmClassName)));
        fname.setField(f);
        return f;
    }

    public void atClassObject(Expr expr) throws CompileError {
        exprType = CLASS;
        arrayDim = 0;
        className =jvmJavaLangClass;
    }

    public void atArrayLength(Expr expr) throws CompileError {
        expr.oprand1().accept(this);
        if (arrayDim == 0)
            throw new NoFieldException("length", expr);

        exprType = INT;
        arrayDim = 0;
    }

    public void atArrayRead(ASTree array, ASTree index)
        throws CompileError
    {
        array.accept(this);
        int type = exprType;
        int dim = arrayDim;
        String cname = className;
        index.accept(this);
        exprType = type;
        arrayDim = dim - 1;
        className = cname;
    }

    private void atPlusPlus(int token, ASTree oprand, Expr expr)
        throws CompileError
    {
        boolean isPost = oprand == null;        // ++i or i++?
        if (isPost)
            oprand = expr.oprand2();

        if (oprand instanceof Variable) {
            Declarator d = ((Variable)oprand).getDeclarator();
            exprType = d.getType();
            arrayDim = d.getArrayDim();
        }
        else {
            if (oprand instanceof Expr) {
                Expr e = (Expr)oprand;
                if (e.getOperator() == ARRAY) {
                    atArrayRead(e.oprand1(), e.oprand2());
                    // arrayDim should be 0.
                    int t = exprType;
                    if (t == INT || t == BYTE || t == CHAR || t == SHORT)
                        exprType = INT;

                    return;
                }
            }

            atFieldPlusPlus(oprand);
        }
    }

    protected void atFieldPlusPlus(ASTree oprand) throws CompileError
    {
        CtField f = fieldAccess(oprand);
        atFieldRead(f);
        int t = exprType;
        if (t == INT || t == BYTE || t == CHAR || t == SHORT)
            exprType = INT;
    }

    @Override
    public void atMember(Member mem) throws CompileError {
        atFieldRead(mem);
    }

    @Override
    public void atVariable(Variable v) throws CompileError {
        Declarator d = v.getDeclarator();
        exprType = d.getType();
        arrayDim = d.getArrayDim();
        className = d.getClassName();
    }

    @Override
    public void atKeyword(Keyword k) throws CompileError {
        arrayDim = 0;
        int token = k.get();
        switch (token) {
        case TRUE :
        case FALSE :
            exprType = BOOLEAN;
            break;
        case NULL :
            exprType = NULL;
            break;
        case THIS :
        case SUPER :
            exprType = CLASS;
            if (token == THIS)
                className = getThisName();
            else
                className = getSuperName();
            break;
        default :
            fatal();
        }
    }

    @Override
    public void atStringL(StringL s) throws CompileError {
        exprType = CLASS;
        arrayDim = 0;
        className = jvmJavaLangString;
    }

    @Override
    public void atIntConst(IntConst i) throws CompileError {
        arrayDim = 0;
        int type = i.getType();
        if (type == IntConstant || type == CharConstant)
            exprType = (type == IntConstant ? INT : CHAR);
        else
            exprType = LONG;
    }

    @Override
    public void atDoubleConst(DoubleConst d) throws CompileError {
        arrayDim = 0;
        if (d.getType() == DoubleConstant)
            exprType = DOUBLE;
        else
            exprType = FLOAT;
    }
}
