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