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