001package serp.bytecode;
002
003import java.io.*;
004import java.lang.reflect.*;
005
006import serp.bytecode.lowlevel.*;
007import serp.bytecode.visitor.*;
008import serp.util.*;
009
010/**
011 * An instruction that invokes a method.
012 *
013 * @author Abe White
014 */
015public class MethodInstruction extends Instruction {
016    private int _index = 0;
017
018    MethodInstruction(Code owner, int opcode) {
019        super(owner, opcode);
020    }
021
022    int getLength() {
023        if (getOpcode() == Constants.INVOKEINTERFACE)
024            return super.getLength() + 4;
025        return super.getLength() + 2;
026    }
027
028    public int getLogicalStackChange() {
029        String ret = getMethodReturnName();
030        if (ret == null)
031            return 0;
032
033        // subtract a stack pos for the this ptr
034        int stack = 0;
035        if (getOpcode() != Constants.INVOKESTATIC)
036            stack--;
037
038        // and for each arg
039        String[] params = getMethodParamNames();
040        for (int i = 0; i < params.length; i++)
041            stack--;
042
043        // add for the return value, if any
044        if (!void.class.getName().equals(ret))
045            stack++;
046        return stack;
047    }
048
049    public int getStackChange() {
050        String ret = getMethodReturnName();
051        if (ret == null)
052            return 0;
053
054        // subtract a stack pos for the this ptr
055        int stack = 0;
056        if (getOpcode() != Constants.INVOKESTATIC)
057            stack--;
058
059        // and for each arg (2 for longs, doubles)
060        String[] params = getMethodParamNames();
061        for (int i = 0; i < params.length; i++, stack--)
062            if (long.class.getName().equals(params[i]) 
063                || double.class.getName().equals(params[i]))
064                stack--;
065
066        // add for the return value, if any
067        if (!void.class.getName().equals(ret))
068            stack++;
069        if (long.class.getName().equals(ret) 
070            || double.class.getName().equals(ret))
071            stack++;
072        return stack;
073    }
074
075    /////////////////////
076    // Method operations
077    /////////////////////
078
079    /**
080     * Return the index in the class {@link ConstantPool} of the
081     * {@link ComplexEntry} describing the method to operate on.
082     */
083    public int getMethodIndex() {
084        return _index;
085    }
086
087    /**
088     * Set the index in the class {@link ConstantPool} of the
089     * {@link ComplexEntry} describing the method to operate on.
090     *
091     * @return this instruction, for method chaining
092     */
093    public MethodInstruction setMethodIndex(int index) {
094        _index = index;
095        return this;
096    }
097
098    /**
099     * Return the method this instruction operates on, or null if not set.
100     */
101    public BCMethod getMethod() {
102        String dec = getMethodDeclarerName();
103        if (dec == null)
104            return null;
105
106        BCClass bc = getProject().loadClass(dec, getClassLoader());
107        BCMethod[] meths = bc.getMethods(getMethodName(),getMethodParamNames());
108        if (meths.length == 0)
109            return null;
110        return meths[0];
111    }
112
113    /**
114     * Set the method this instruction operates on.
115     *
116     * @return this instruction, for method chaining
117     */
118    public MethodInstruction setMethod(BCMethod method) {
119        if (method == null)
120            return setMethodIndex(0);
121        return setMethod(method.getDeclarer().getName(), method.getName(),
122            method.getReturnName(), method.getParamNames(), false);
123    }
124
125    /**
126     * Set the method this instruction operates on.
127     *
128     * @return this instruction, for method chaining
129     */
130    public MethodInstruction setMethod(Method method) {
131        if (method == null)
132            return setMethodIndex(0);
133        return setMethod(method.getDeclaringClass(), method.getName(),
134            method.getReturnType(), method.getParameterTypes());
135    }
136
137    /**
138     * Set the method this instruction operates on.
139     *
140     * @return this instruction, for method chaining
141     */
142    public MethodInstruction setMethod(Constructor method) {
143        if (method == null)
144            return setMethodIndex(0);
145        setOpcode(Constants.INVOKESPECIAL);
146        return setMethod(method.getDeclaringClass(), "<init>", void.class,
147            method.getParameterTypes());
148    }
149
150    /**
151     * Set the method this instruction operates on.
152     *
153     * @param dec the full class name of the method's declaring class
154     * @param name the method name
155     * @param returnType the full class name of the method return type
156     * @param param the full class names of the method param types
157     * @return this instruction, for method chaining
158     */
159    public MethodInstruction setMethod(String dec, String name,
160        String returnType, String[] params) {
161        return setMethod(dec, name, returnType, params, true);
162    }
163
164    /**
165     * Set the method this instruction operates on.
166     *
167     * @param dec the full class name of the method's declaring class
168     * @param name the method name
169     * @param returnType the full class name of the method return type
170     * @param param the full class names of the method param types
171     * @param copy whether to copy the the parameter array
172     * @return this instruction, for method chaining
173     */
174    private MethodInstruction setMethod(String dec, String name,
175        String returnType, String[] params, boolean copy) {
176        if (name == null && returnType == null && dec == null 
177            && (params == null || params.length == 0))
178            return setMethodIndex(0);
179
180        if (dec == null)
181            dec = "";
182        if (name == null)
183            name = "";
184        if (returnType == null)
185            returnType = "";
186        if (params == null)
187            params = new String[0];
188        else if (copy) {
189            String[] pcopy = new String[params.length];
190            System.arraycopy(params, 0, pcopy, 0, params.length);
191            params = pcopy;
192        }
193
194        NameCache cache = getProject().getNameCache();
195        returnType = cache.getInternalForm(returnType, true);
196        dec = cache.getInternalForm(dec, false);
197        for (int i = 0; i < params.length; i++)
198            params[i] = cache.getInternalForm(params[i], true);
199
200        String desc = cache.getDescriptor(returnType, params);
201        if (getOpcode() == Constants.INVOKEINTERFACE)
202            return setMethodIndex(getPool().findInterfaceMethodEntry(dec, name,
203                desc, true));
204        return setMethodIndex(getPool().findMethodEntry(dec, name, desc, true));
205    }
206
207    /**
208     * Set the method this instruction operates on, for methods that are
209     * declared by the current class.
210     *
211     * @param name the method name
212     * @param returnType the full class name of the method return type
213     * @param param the full class names of the method param types
214     * @return this instruction, for method chaining
215     */
216    public MethodInstruction setMethod(String name, String returnType,
217        String[] params) {
218        BCClass owner = getCode().getMethod().getDeclarer();
219        return setMethod(owner.getName(), name, returnType, params);
220    }
221
222    /**
223     * Set the method this instruction operates on.
224     *
225     * @param dec the method's declaring class
226     * @param name the method name
227     * @param returnType the class of the method return type
228     * @param param the class of the method param types
229     * @return this instruction, for method chaining
230     */
231    public MethodInstruction setMethod(Class dec, String name,
232        Class returnType, Class[] params) {
233        String decName = (dec == null) ? null : dec.getName();
234        String returnName = (returnType == null) ? null : returnType.getName();
235        String[] paramNames = null;
236        if (params != null) {
237            paramNames = new String[params.length];
238            for (int i = 0; i < params.length; i++)
239                paramNames[i] = params[i].getName();
240        }
241        return setMethod(decName, name, returnName, paramNames, false);
242    }
243
244    /**
245     * Set the method this instruction operates on, for methods that are
246     * declared by the current class.
247     *
248     * @param name the method name
249     * @param returnType the class of the method return type
250     * @param param the class of the method param types
251     * @return this instruction, for method chaining
252     */
253    public MethodInstruction setMethod(String name, Class returnType,
254        Class[] params) {
255        BCClass owner = getCode().getMethod().getDeclarer();
256        String returnName = (returnType == null) ? null : returnType.getName();
257        String[] paramNames = null;
258        if (params != null) {
259            paramNames = new String[params.length];
260            for (int i = 0; i < params.length; i++)
261                paramNames[i] = params[i].getName();
262        }
263        return setMethod(owner.getName(), name, returnName, paramNames, false);
264    }
265
266    /**
267     * Set the method this instruction operates on.
268     *
269     * @param dec the method's declaring class
270     * @param name the method name
271     * @param returnType the class of the method return type
272     * @param param the class of the method param types
273     * @return this instruction, for method chaining
274     */
275    public MethodInstruction setMethod(BCClass dec, String name,
276        BCClass returnType, BCClass[] params) {
277        String decName = (dec == null) ? null : dec.getName();
278        String returnName = (returnType == null) ? null : returnType.getName();
279        String[] paramNames = null;
280        if (params != null) {
281            paramNames = new String[params.length];
282            for (int i = 0; i < params.length; i++)
283                paramNames[i] = params[i].getName();
284        }
285        return setMethod(decName, name, returnName, paramNames, false);
286    }
287
288    /**
289     * Set the method this instruction operates on, for methods that are
290     * declared by the current class.
291     *
292     * @param name the method name
293     * @param returnType the class of the method return type
294     * @param param the class of the method param types
295     * @return this instruction, for method chaining
296     */
297    public MethodInstruction setMethod(String name, BCClass returnType,
298        BCClass[] params) {
299        BCClass owner = getCode().getMethod().getDeclarer();
300        String returnName = (returnType == null) ? null : returnType.getName();
301        String[] paramNames = null;
302        if (params != null) {
303            paramNames = new String[params.length];
304            for (int i = 0; i < params.length; i++)
305                paramNames[i] = params[i].getName();
306        }
307        return setMethod(owner.getName(), name, returnName, paramNames, false);
308    }
309
310    /////////////////////////////////////////
311    // Name, Return, Param, Owner operations
312    /////////////////////////////////////////
313
314    /**
315     * Return the name of the method this instruction operates on, or null
316     * if not set.
317     */
318    public String getMethodName() {
319        if (_index == 0)
320            return null;
321
322        ComplexEntry entry = (ComplexEntry) getPool().getEntry(_index);
323        String name = entry.getNameAndTypeEntry().getNameEntry().getValue();
324        if (name.length() == 0)
325            return null;
326        return name;
327    }
328
329    /**
330     * Set the name of the method this instruction operates on.
331     *
332     * @return this instruction, for method chaining
333     */
334    public MethodInstruction setMethodName(String name) {
335        return setMethod(getMethodDeclarerName(), name, getMethodReturnName(),
336            getMethodParamNames());
337    }
338
339    /**
340     * Return the return type of the method this instruction operates on,
341     * or null if not set.
342     */
343    public String getMethodReturnName() {
344        if (_index == 0)
345            return null;
346
347        ComplexEntry entry = (ComplexEntry) getPool().getEntry(_index);
348        String desc = entry.getNameAndTypeEntry().getDescriptorEntry().
349            getValue();
350        NameCache cache = getProject().getNameCache();
351        String name = cache.getExternalForm(cache.getDescriptorReturnName(desc),
352            false);
353        if (name.length() == 0)
354            return null;
355        return name;
356    }
357
358    /**
359     * Return the return type of the method this instruction operates on,
360     * or null if not set.
361     */
362    public Class getMethodReturnType() {
363        String type = getMethodReturnName();
364        if (type == null)
365            return null;
366        return Strings.toClass(type, getClassLoader());
367    }
368
369    /**
370     * Return the return type of the method this instruction operates on,
371     * or null if not set.
372     */
373    public BCClass getMethodReturnBC() {
374        String type = getMethodReturnName();
375        if (type == null)
376            return null;
377        return getProject().loadClass(type, getClassLoader());
378    }
379
380    /**
381     * Set the return type of the method this instruction operates on.
382     *
383     * @return this instruction, for method chaining
384     */
385    public MethodInstruction setMethodReturn(String type) {
386        return setMethod(getMethodDeclarerName(), getMethodName(), type,
387            getMethodParamNames());
388    }
389
390    /**
391     * Set the return type of the method this instruction operates on.
392     *
393     * @return this instruction, for method chaining
394     */
395    public MethodInstruction setMethodReturn(Class type) {
396        String name = null;
397        if (type != null)
398            name = type.getName();
399        return setMethodReturn(name);
400    }
401
402    /**
403     * Set the return type of the method this instruction operates on.
404     *
405     * @return this instruction, for method chaining
406     */
407    public MethodInstruction setMethodReturn(BCClass type) {
408        String name = null;
409        if (type != null)
410            name = type.getName();
411        return setMethodReturn(name);
412    }
413
414    /**
415     * Return the param types of the method this instruction operates on,
416     * or empty array if none.
417     */
418    public String[] getMethodParamNames() {
419        if (_index == 0)
420            return new String[0];
421
422        ComplexEntry entry = (ComplexEntry) getPool().getEntry(_index);
423        String desc = entry.getNameAndTypeEntry().getDescriptorEntry().
424            getValue();
425        NameCache cache = getProject().getNameCache();
426        String[] names = cache.getDescriptorParamNames(desc);
427        for (int i = 0; i < names.length; i++)
428            names[i] = cache.getExternalForm(names[i], false);
429        return names;
430    }
431
432    /**
433     * Return the param types of the method this instruction operates on,
434     * or empty array if none.
435     */
436    public Class[] getMethodParamTypes() {
437        String[] paramNames = getMethodParamNames();
438        Class[] params = new Class[paramNames.length];
439        for (int i = 0; i < paramNames.length; i++)
440            params[i] = Strings.toClass(paramNames[i], getClassLoader());
441        return params;
442    }
443
444    /**
445     * Return the param types of the method this instruction operates on,
446     * or empty array if none.
447     */
448    public BCClass[] getMethodParamBCs() {
449        String[] paramNames = getMethodParamNames();
450        BCClass[] params = new BCClass[paramNames.length];
451        for (int i = 0; i < paramNames.length; i++)
452            params[i] = getProject().loadClass(paramNames[i], getClassLoader());
453        return params;
454    }
455
456    /**
457     * Set the param types of the method this instruction operates on.
458     *
459     * @return this instruction, for method chaining
460     */
461    public MethodInstruction setMethodParams(String[] types) {
462        return setMethod(getMethodDeclarerName(), getMethodName(),
463            getMethodReturnName(), types);
464    }
465
466    /**
467     * Set the param types of the method this instruction operates on.
468     *
469     * @return this instruction, for method chaining
470     */
471    public void setMethodParams(Class[] types) {
472        if (types == null)
473            setMethodParams((String[]) null);
474        else {
475            String[] names = new String[types.length];
476            for (int i = 0; i < types.length; i++)
477                names[i] = types[i].getName();
478            setMethodParams(names);
479        }
480    }
481
482    /**
483     * Set the param types of the method this instruction operates on.
484     *
485     * @return this instruction, for method chaining
486     */
487    public void setMethodParams(BCClass[] types) {
488        if (types == null)
489            setMethodParams((String[]) null);
490        else {
491            String[] names = new String[types.length];
492            for (int i = 0; i < types.length; i++)
493                names[i] = types[i].getName();
494            setMethodParams(names);
495        }
496    }
497
498    /**
499     * Return the declaring type of the method this instruction operates on,
500     * or null if not set.
501     */
502    public String getMethodDeclarerName() {
503        if (_index == 0)
504            return null;
505
506        ComplexEntry entry = (ComplexEntry) getPool().getEntry(_index);
507        String name = getProject().getNameCache().getExternalForm
508            (entry.getClassEntry().getNameEntry().getValue(), false);
509        if (name.length() == 0)
510            return null;
511        return name;
512    }
513
514    /**
515     * Return the declaring type of the method this instruction operates on,
516     * or null if not set.
517     */
518    public Class getMethodDeclarerType() {
519        String type = getMethodDeclarerName();
520        if (type == null)
521            return null;
522        return Strings.toClass(type, getClassLoader());
523    }
524
525    /**
526     * Return the declaring type of the method this instruction operates on,
527     * or null if not set.
528     */
529    public BCClass getMethodDeclarerBC() {
530        String type = getMethodDeclarerName();
531        if (type == null)
532            return null;
533        return getProject().loadClass(type, getClassLoader());
534    }
535
536    /**
537     * Set the declaring type of the method this instruction operates on.
538     *
539     * @return this instruction, for method chaining
540     */
541    public MethodInstruction setMethodDeclarer(String type) {
542        return setMethod(type, getMethodName(), getMethodReturnName(),
543            getMethodParamNames());
544    }
545
546    /**
547     * Set the declaring type of the method this instruction operates on.
548     *
549     * @return this instruction, for method chaining
550     */
551    public MethodInstruction setMethodDeclarer(Class type) {
552        String name = null;
553        if (type != null)
554            name = type.getName();
555        return setMethodDeclarer(name);
556    }
557
558    /**
559     * Set the declaring type of the method this instruction operates on.
560     *
561     * @return this instruction, for method chaining
562     */
563    public MethodInstruction setMethodDeclarer(BCClass type) {
564        String name = null;
565        if (type != null)
566            name = type.getName();
567        return setMethodDeclarer(name);
568    }
569
570    /**
571     * MethodInstructions are equal if the method they reference is the same,
572     * or if the method of either is unset.
573     */
574    public boolean equalsInstruction(Instruction other) {
575        if (other == this)
576            return true;
577        if (!(other instanceof MethodInstruction))
578            return false;
579        if (!super.equalsInstruction(other))
580            return false;
581
582        MethodInstruction ins = (MethodInstruction) other;
583        String s1 = getMethodName();
584        String s2 = ins.getMethodName();
585        if (!(s1 == null || s2 == null || s1.equals(s2)))
586            return false;
587
588        s1 = getMethodReturnName();
589        s2 = ins.getMethodReturnName();
590        if (!(s1 == null || s2 == null || s1.equals(s2)))
591            return false;
592
593        s1 = getMethodDeclarerName();
594        s2 = ins.getMethodDeclarerName();
595        if (!(s1 == null || s2 == null || s1.equals(s2)))
596            return false;
597
598        String[] p1 = getMethodParamNames();
599        String[] p2 = ins.getMethodParamNames();
600        if (!(p1.length == 0 || p2.length == 0 || p1.length == p2.length))
601            return false;
602
603        for (int i = 0; i < p1.length; i++)
604            if (!(p1[i] == null || p2[i] == null || p1[i].equals(p2[i])))
605                return false;
606        return true;
607    }
608
609    public void acceptVisit(BCVisitor visit) {
610        visit.enterMethodInstruction(this);
611        visit.exitMethodInstruction(this);
612    }
613
614    void read(Instruction orig) {
615        super.read(orig);
616        MethodInstruction ins = (MethodInstruction) orig;
617        setMethod(ins.getMethodDeclarerName(), ins.getMethodName(),
618            ins.getMethodReturnName(), ins.getMethodParamNames());
619    }
620
621    void read(DataInput in) throws IOException {
622        super.read(in);
623        _index = in.readUnsignedShort();
624        if (getOpcode() == Constants.INVOKEINTERFACE) {
625            in.readByte();
626            in.readByte();
627        }
628    }
629
630    void write(DataOutput out) throws IOException {
631        super.write(out);
632        out.writeShort(_index);
633        if (getOpcode() == Constants.INVOKEINTERFACE) {
634            String[] args = getMethodParamNames();
635            int count = 1;
636            for (int i = 0; i < args.length; i++, count++)
637                if (long.class.getName().equals(args[i]) 
638                    || double.class.getName().equals(args[i]))
639                    count++;
640
641            out.writeByte(count);
642            out.writeByte(0);
643        }
644    }
645}