1/*
2 * Javassist, a Java-bytecode translator toolkit.
3 * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License.  Alternatively, the contents of this file may be used under
8 * the terms of the GNU Lesser General Public License Version 2.1 or later.
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 */
15
16package javassist.compiler;
17
18import java.util.List;
19import java.util.Iterator;
20import javassist.*;
21import javassist.bytecode.*;
22import javassist.compiler.ast.*;
23
24/* Code generator methods depending on javassist.* classes.
25 */
26public class MemberResolver implements TokenId {
27    private ClassPool classPool;
28
29    public MemberResolver(ClassPool cp) {
30        classPool = cp;
31    }
32
33    public ClassPool getClassPool() { return classPool; }
34
35    private static void fatal() throws CompileError {
36        throw new CompileError("fatal");
37    }
38
39    /**
40     * @param jvmClassName      a class name.  Not a package name.
41     */
42    public void recordPackage(String jvmClassName) {
43        String classname = jvmToJavaName(jvmClassName);
44        for (;;) {
45            int i = classname.lastIndexOf('.');
46            if (i > 0) {
47                classname = classname.substring(0, i);
48                classPool.recordInvalidClassName(classname);
49            }
50            else
51                break;
52        }
53    }
54
55    public static class Method {
56        public CtClass declaring;
57        public MethodInfo info;
58        public int notmatch;
59
60        public Method(CtClass c, MethodInfo i, int n) {
61            declaring = c;
62            info = i;
63            notmatch = n;
64        }
65
66        /**
67         * Returns true if the invoked method is static.
68         */
69        public boolean isStatic() {
70            int acc = info.getAccessFlags();
71            return (acc & AccessFlag.STATIC) != 0;
72        }
73    }
74
75    public Method lookupMethod(CtClass clazz, CtClass currentClass, MethodInfo current,
76                                String methodName,
77                                int[] argTypes, int[] argDims,
78                                String[] argClassNames)
79        throws CompileError
80    {
81        Method maybe = null;
82        // to enable the creation of a recursively called method
83        if (current != null && clazz == currentClass)
84            if (current.getName().equals(methodName)) {
85                int res = compareSignature(current.getDescriptor(),
86                                           argTypes, argDims, argClassNames);
87                if (res != NO) {
88                    Method r = new Method(clazz, current, res);
89                    if (res == YES)
90                        return r;
91                    else
92                        maybe = r;
93                }
94            }
95
96        Method m = lookupMethod(clazz, methodName, argTypes, argDims,
97                                argClassNames, maybe != null);
98        if (m != null)
99            return m;
100        else
101            return maybe;
102    }
103
104    private Method lookupMethod(CtClass clazz, String methodName,
105                               int[] argTypes, int[] argDims,
106                               String[] argClassNames, boolean onlyExact)
107        throws CompileError
108    {
109        Method maybe = null;
110        ClassFile cf = clazz.getClassFile2();
111        // If the class is an array type, the class file is null.
112        // If so, search the super class java.lang.Object for clone() etc.
113        if (cf != null) {
114            List list = cf.getMethods();
115            int n = list.size();
116            for (int i = 0; i < n; ++i) {
117                MethodInfo minfo = (MethodInfo)list.get(i);
118                if (minfo.getName().equals(methodName)) {
119                    int res = compareSignature(minfo.getDescriptor(),
120                                           argTypes, argDims, argClassNames);
121                    if (res != NO) {
122                        Method r = new Method(clazz, minfo, res);
123                        if (res == YES)
124                            return r;
125                        else if (maybe == null || maybe.notmatch > res)
126                            maybe = r;
127                    }
128                }
129            }
130        }
131
132        if (onlyExact)
133            maybe = null;
134        else
135            onlyExact = maybe != null;
136
137        int mod = clazz.getModifiers();
138        boolean isIntf = Modifier.isInterface(mod);
139        try {
140            // skip searching java.lang.Object if clazz is an interface type.
141            if (!isIntf) {
142                CtClass pclazz = clazz.getSuperclass();
143                if (pclazz != null) {
144                    Method r = lookupMethod(pclazz, methodName, argTypes,
145                                            argDims, argClassNames, onlyExact);
146                    if (r != null)
147                        return r;
148                }
149            }
150        }
151        catch (NotFoundException e) {}
152
153        if (isIntf || Modifier.isAbstract(mod))
154            try {
155                CtClass[] ifs = clazz.getInterfaces();
156                int size = ifs.length;
157                for (int i = 0; i < size; ++i) {
158                    Method r = lookupMethod(ifs[i], methodName,
159                                            argTypes, argDims, argClassNames,
160                                            onlyExact);
161                    if (r != null)
162                        return r;
163                }
164
165                if (isIntf) {
166                    // finally search java.lang.Object.
167                    CtClass pclazz = clazz.getSuperclass();
168                    if (pclazz != null) {
169                        Method r = lookupMethod(pclazz, methodName, argTypes,
170                                                argDims, argClassNames, onlyExact);
171                        if (r != null)
172                            return r;
173                    }
174                }
175            }
176            catch (NotFoundException e) {}
177
178        return maybe;
179    }
180
181    private static final int YES = 0;
182    private static final int NO = -1;
183
184    /*
185     * Returns YES if actual parameter types matches the given signature.
186     *
187     * argTypes, argDims, and argClassNames represent actual parameters.
188     *
189     * This method does not correctly implement the Java method dispatch
190     * algorithm.
191     *
192     * If some of the parameter types exactly match but others are subtypes of
193     * the corresponding type in the signature, this method returns the number
194     * of parameter types that do not exactly match.
195     */
196    private int compareSignature(String desc, int[] argTypes,
197                                 int[] argDims, String[] argClassNames)
198        throws CompileError
199    {
200        int result = YES;
201        int i = 1;
202        int nArgs = argTypes.length;
203        if (nArgs != Descriptor.numOfParameters(desc))
204            return NO;
205
206        int len = desc.length();
207        for (int n = 0; i < len; ++n) {
208            char c = desc.charAt(i++);
209            if (c == ')')
210                return (n == nArgs ? result : NO);
211            else if (n >= nArgs)
212                return NO;
213
214            int dim = 0;
215            while (c == '[') {
216                ++dim;
217                c = desc.charAt(i++);
218            }
219
220            if (argTypes[n] == NULL) {
221                if (dim == 0 && c != 'L')
222                    return NO;
223
224                if (c == 'L')
225                    i = desc.indexOf(';', i) + 1;
226            }
227            else if (argDims[n] != dim) {
228                if (!(dim == 0 && c == 'L'
229                      && desc.startsWith("java/lang/Object;", i)))
230                    return NO;
231
232                // if the thread reaches here, c must be 'L'.
233                i = desc.indexOf(';', i) + 1;
234                result++;
235                if (i <= 0)
236                    return NO;  // invalid descriptor?
237            }
238            else if (c == 'L') {        // not compare
239                int j = desc.indexOf(';', i);
240                if (j < 0 || argTypes[n] != CLASS)
241                    return NO;
242
243                String cname = desc.substring(i, j);
244                if (!cname.equals(argClassNames[n])) {
245                    CtClass clazz = lookupClassByJvmName(argClassNames[n]);
246                    try {
247                        if (clazz.subtypeOf(lookupClassByJvmName(cname)))
248                            result++;
249                        else
250                            return NO;
251                    }
252                    catch (NotFoundException e) {
253                        result++; // should be NO?
254                    }
255                }
256
257                i = j + 1;
258            }
259            else {
260                int t = descToType(c);
261                int at = argTypes[n];
262                if (t != at)
263                    if (t == INT
264                        && (at == SHORT || at == BYTE || at == CHAR))
265                        result++;
266                    else
267                        return NO;
268            }
269        }
270
271        return NO;
272    }
273
274    /**
275     * Only used by fieldAccess() in MemberCodeGen and TypeChecker.
276     *
277     * @param jvmClassName  a JVM class name.  e.g. java/lang/String
278     */
279    public CtField lookupFieldByJvmName2(String jvmClassName, Symbol fieldSym,
280                                         ASTree expr) throws NoFieldException
281    {
282        String field = fieldSym.get();
283        CtClass cc = null;
284        try {
285            cc = lookupClass(jvmToJavaName(jvmClassName), true);
286        }
287        catch (CompileError e) {
288            // EXPR might be part of a qualified class name.
289            throw new NoFieldException(jvmClassName + "/" + field, expr);
290        }
291
292        try {
293            return cc.getField(field);
294        }
295        catch (NotFoundException e) {
296            // maybe an inner class.
297            jvmClassName = javaToJvmName(cc.getName());
298            throw new NoFieldException(jvmClassName + "$" + field, expr);
299        }
300    }
301
302    /**
303     * @param jvmClassName  a JVM class name.  e.g. java/lang/String
304     */
305    public CtField lookupFieldByJvmName(String jvmClassName, Symbol fieldName)
306        throws CompileError
307    {
308        return lookupField(jvmToJavaName(jvmClassName), fieldName);
309    }
310
311    /**
312     * @param name      a qualified class name. e.g. java.lang.String
313     */
314    public CtField lookupField(String className, Symbol fieldName)
315        throws CompileError
316    {
317        CtClass cc = lookupClass(className, false);
318        try {
319            return cc.getField(fieldName.get());
320        }
321        catch (NotFoundException e) {}
322        throw new CompileError("no such field: " + fieldName.get());
323    }
324
325    public CtClass lookupClassByName(ASTList name) throws CompileError {
326        return lookupClass(Declarator.astToClassName(name, '.'), false);
327    }
328
329    public CtClass lookupClassByJvmName(String jvmName) throws CompileError {
330        return lookupClass(jvmToJavaName(jvmName), false);
331    }
332
333    public CtClass lookupClass(Declarator decl) throws CompileError {
334        return lookupClass(decl.getType(), decl.getArrayDim(),
335                           decl.getClassName());
336    }
337
338    /**
339     * @parma classname         jvm class name.
340     */
341    public CtClass lookupClass(int type, int dim, String classname)
342        throws CompileError
343    {
344        String cname = "";
345        CtClass clazz;
346        if (type == CLASS) {
347            clazz = lookupClassByJvmName(classname);
348            if (dim > 0)
349                cname = clazz.getName();
350            else
351                return clazz;
352        }
353        else
354            cname = getTypeName(type);
355
356        while (dim-- > 0)
357            cname += "[]";
358
359        return lookupClass(cname, false);
360    }
361
362    /*
363     * type cannot be CLASS
364     */
365    static String getTypeName(int type) throws CompileError {
366        String cname = "";
367        switch (type) {
368        case BOOLEAN :
369            cname = "boolean";
370            break;
371        case CHAR :
372            cname = "char";
373            break;
374        case BYTE :
375            cname = "byte";
376            break;
377        case SHORT :
378            cname = "short";
379            break;
380        case INT :
381            cname = "int";
382            break;
383        case LONG :
384            cname = "long";
385            break;
386        case FLOAT :
387            cname = "float";
388            break;
389        case DOUBLE :
390            cname = "double";
391            break;
392        case VOID :
393            cname = "void";
394            break;
395        default :
396            fatal();
397        }
398
399        return cname;
400    }
401
402    /**
403     * @param name      a qualified class name. e.g. java.lang.String
404     */
405    public CtClass lookupClass(String name, boolean notCheckInner)
406        throws CompileError
407    {
408        try {
409            return lookupClass0(name, notCheckInner);
410        }
411        catch (NotFoundException e) {
412            return searchImports(name);
413        }
414    }
415
416    private CtClass searchImports(String orgName)
417        throws CompileError
418    {
419        if (orgName.indexOf('.') < 0) {
420            Iterator it = classPool.getImportedPackages();
421            while (it.hasNext()) {
422                String pac = (String)it.next();
423                String fqName = pac + '.' + orgName;
424                try {
425                    CtClass cc = classPool.get(fqName);
426                    // if the class is found,
427                    classPool.recordInvalidClassName(orgName);
428                    return cc;
429                }
430                catch (NotFoundException e) {
431                    classPool.recordInvalidClassName(fqName);
432                    try {
433                        if (pac.endsWith("." + orgName)) {
434                            CtClass cc = classPool.get(pac);
435                            // if the class is found,
436                            classPool.recordInvalidClassName(orgName);
437                            return cc;
438                        }
439                    }
440                    catch (NotFoundException e2) {
441                        classPool.recordInvalidClassName(pac);
442                    }
443                }
444            }
445        }
446
447        throw new CompileError("no such class: " + orgName);
448    }
449
450    private CtClass lookupClass0(String classname, boolean notCheckInner)
451        throws NotFoundException
452    {
453        CtClass cc = null;
454        do {
455            try {
456                cc = classPool.get(classname);
457            }
458            catch (NotFoundException e) {
459                int i = classname.lastIndexOf('.');
460                if (notCheckInner || i < 0)
461                    throw e;
462                else {
463                    StringBuffer sbuf = new StringBuffer(classname);
464                    sbuf.setCharAt(i, '$');
465                    classname = sbuf.toString();
466                }
467            }
468        } while (cc == null);
469        return cc;
470    }
471
472    /* Converts a class name into a JVM-internal representation.
473     *
474     * It may also expand a simple class name to java.lang.*.
475     * For example, this converts Object into java/lang/Object.
476     */
477    public String resolveClassName(ASTList name) throws CompileError {
478        if (name == null)
479            return null;
480        else
481            return javaToJvmName(lookupClassByName(name).getName());
482    }
483
484    /* Expands a simple class name to java.lang.*.
485     * For example, this converts Object into java/lang/Object.
486     */
487    public String resolveJvmClassName(String jvmName) throws CompileError {
488        if (jvmName == null)
489            return null;
490        else
491            return javaToJvmName(lookupClassByJvmName(jvmName).getName());
492    }
493
494    public static CtClass getSuperclass(CtClass c) throws CompileError {
495        try {
496            CtClass sc = c.getSuperclass();
497            if (sc != null)
498                return sc;
499        }
500        catch (NotFoundException e) {}
501        throw new CompileError("cannot find the super class of "
502                               + c.getName());
503    }
504
505    public static String javaToJvmName(String classname) {
506        return classname.replace('.', '/');
507    }
508
509    public static String jvmToJavaName(String classname) {
510        return classname.replace('/', '.');
511    }
512
513    public static int descToType(char c) throws CompileError {
514        switch (c) {
515        case 'Z' :
516            return BOOLEAN;
517        case 'C' :
518            return CHAR;
519        case 'B' :
520            return  BYTE;
521        case 'S' :
522            return SHORT;
523        case 'I' :
524            return INT;
525        case 'J' :
526            return LONG;
527        case 'F' :
528            return FLOAT;
529        case 'D' :
530            return DOUBLE;
531        case 'V' :
532            return VOID;
533        case 'L' :
534        case '[' :
535            return CLASS;
536        default :
537            fatal();
538            return VOID;    // never reach here
539        }
540    }
541
542    public static int getModifiers(ASTList mods) {
543        int m = 0;
544        while (mods != null) {
545            Keyword k = (Keyword)mods.head();
546            mods = mods.tail();
547            switch (k.get()) {
548            case STATIC :
549                m |= Modifier.STATIC;
550                break;
551            case FINAL :
552                m |= Modifier.FINAL;
553                break;
554            case SYNCHRONIZED :
555                m |= Modifier.SYNCHRONIZED;
556                break;
557            case ABSTRACT :
558                m |= Modifier.ABSTRACT;
559                break;
560            case PUBLIC :
561                m |= Modifier.PUBLIC;
562                break;
563            case PROTECTED :
564                m |= Modifier.PROTECTED;
565                break;
566            case PRIVATE :
567                m |= Modifier.PRIVATE;
568                break;
569            case VOLATILE :
570                m |= Modifier.VOLATILE;
571                break;
572            case TRANSIENT :
573                m |= Modifier.TRANSIENT;
574                break;
575            case STRICT :
576                m |= Modifier.STRICT;
577                break;
578            }
579        }
580
581        return m;
582    }
583}
584