001    package serp.bytecode;
002    
003    import java.io.*;
004    import java.lang.reflect.*;
005    
006    import serp.bytecode.lowlevel.*;
007    import serp.bytecode.visitor.*;
008    import serp.util.*;
009    
010    /**
011     * An instruction that invokes a method.
012     *
013     * @author Abe White
014     */
015    public 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    }