001    package edu.rice.cs.cunit.classFile;
002    
003    import edu.rice.cs.cunit.classFile.attributes.AAttributeInfo;
004    import edu.rice.cs.cunit.classFile.attributes.CodeAttributeInfo;
005    import edu.rice.cs.cunit.classFile.attributes.LineNumberTableAttributeInfo;
006    import static edu.rice.cs.cunit.classFile.attributes.LineNumberTableAttributeInfo.LineNumberRecord;
007    import edu.rice.cs.cunit.classFile.attributes.visitors.ADefaultAttributeVisitor;
008    import edu.rice.cs.cunit.classFile.code.InstructionList;
009    import edu.rice.cs.cunit.classFile.constantPool.AUTFPoolInfo;
010    import edu.rice.cs.cunit.classFile.constantPool.ConstantPool;
011    import edu.rice.cs.cunit.classFile.constantPool.visitors.CheckUTFVisitor;
012    
013    import java.io.DataInputStream;
014    import java.io.DataOutputStream;
015    import java.io.IOException;
016    import java.util.ArrayList;
017    import java.util.Collections;
018    import java.util.Comparator;
019    
020    /**
021     * Represents a method in a class file.
022     *
023     * @author Mathias Ricken
024     */
025    
026    public final class MethodInfo {
027        /**
028         * Method access flags.
029         */
030        private short _accessFlags;
031    
032        /**
033         * Name information.
034         */
035        private AUTFPoolInfo _name;
036    
037        /**
038         * Type descriptor information.
039         */
040        private AUTFPoolInfo _descriptor;
041    
042        /**
043         * Attributes.
044         */
045        private ArrayList<AAttributeInfo> _attributes = new ArrayList<AAttributeInfo>();
046    
047        /**
048         * Constructor
049         *
050         * @param accessFlags method access flags
051         * @param name        method name
052         * @param signature   method descriptor
053         * @param attributes  array of attributes
054         */
055        public MethodInfo(short accessFlags,
056                          AUTFPoolInfo name,
057                          AUTFPoolInfo signature,
058                          AAttributeInfo[] attributes) {
059            _accessFlags = accessFlags;
060            _name = name;
061            _descriptor = signature;
062            if (attributes != null) {
063                for(AAttributeInfo attr : attributes) {
064                    _attributes.add(attr);
065                }
066            }
067        }
068    
069        /**
070         * Constructor
071         *
072         * @param di   input stream
073         * @param pool constant pool
074         *
075         * @throws IOException
076         * @throws ClassFormatError
077         */
078        public MethodInfo(DataInputStream di, ConstantPool pool) throws IOException, ClassFormatError {
079            _accessFlags = di.readShort();
080            _name = pool.get(di.readShort()).execute(CheckUTFVisitor.singleton(), null);
081            _descriptor = pool.get(di.readShort()).execute(CheckUTFVisitor.singleton(), null);
082    
083            int count = di.readShort();
084            for(int i = 0; i < count; i++) {
085                _attributes.add(AAttributeInfo.read(di, pool));
086            }
087        }
088    
089        /**
090         * Write this method into the stream.
091         *
092         * @param dos  output stream
093         * @param pool constant pool
094         *
095         * @throws IOException
096         */
097        public void write(DataOutputStream dos, ConstantPool pool) throws IOException {
098            dos.writeShort(_accessFlags);
099            dos.writeShort(pool.indexOf(_name));
100            dos.writeShort(pool.indexOf(_descriptor));
101            dos.writeShort(_attributes.size());
102            for(AAttributeInfo attr : _attributes) {
103                attr.write(dos);
104            }
105        }
106    
107        /**
108         * Return a human-readable version of this method.
109         *
110         * @return string
111         */
112        public String toString() {
113            String s = _descriptor.toString();
114            String paramSig = s.substring(s.indexOf('(') + 1, s.indexOf(')'));
115            String returnSig = s.substring(s.indexOf(')') + 1);
116    
117            // handle constructor correctly
118            StringBuilder parameterList = new StringBuilder();
119            parameterList.append(_name + "(");
120    
121            char initialParameter = 'a';
122            if ((paramSig.length() > 0) && paramSig.charAt(0) != 'V') {
123                while(paramSig.length() > 0) {
124                    StringBuilder varName = new StringBuilder();
125                    varName.append(initialParameter);
126                    initialParameter++;
127                    parameterList.append(ClassFileTools.getTypeString(paramSig, varName.toString()));
128                    paramSig = ClassFileTools.getNextSignature(paramSig);
129                    if (paramSig.length() > 0) {
130                        parameterList.append(", ");
131                    }
132                }
133    
134            }
135            parameterList.append(")");
136    
137            StringBuilder x = new StringBuilder();
138            x.append(ClassFileTools.getAccessString(_accessFlags));
139            x.append(ClassFileTools.getTypeString(returnSig, parameterList.toString()));
140    
141            return x.toString();
142        }
143    
144        /**
145         * Return a human-readable version of this method.
146         *
147         * @param pool constant pool
148         *
149         * @return string
150         */
151        public String toString(final ConstantPool pool) {
152            return toString(pool, true, true);
153        }
154    
155        /**
156         * Return a human-readable version of this method.
157         *
158         * @param pool constant pool
159         * @param lineNumbers print line numbers
160         * @param PCs print PC values
161         * @return string
162         */
163        public String toString(final ConstantPool pool, boolean lineNumbers, boolean PCs) {
164            String s = _descriptor.toString();
165            String paramSig = s.substring(s.indexOf('(') + 1, s.indexOf(')'));
166            String returnSig = s.substring(s.indexOf(')') + 1);
167    
168            // handle constructor correctly
169            StringBuilder parameterList = new StringBuilder();
170            parameterList.append(_name + "(");
171    
172            char initialParameter = 'a';
173            if ((paramSig.length() > 0) && paramSig.charAt(0) != 'V') {
174                while(paramSig.length() > 0) {
175                    StringBuilder varName = new StringBuilder();
176                    varName.append(initialParameter);
177                    initialParameter++;
178                    parameterList.append(ClassFileTools.getTypeString(paramSig, varName.toString()));
179                    paramSig = ClassFileTools.getNextSignature(paramSig);
180                    if (paramSig.length() > 0) {
181                        parameterList.append(", ");
182                    }
183                }
184    
185            }
186            parameterList.append(")");
187    
188            final StringBuilder x = new StringBuilder();
189            x.append(ClassFileTools.getAccessString(_accessFlags));
190            x.append(ClassFileTools.getTypeString(returnSig, parameterList.toString()));
191    
192            for(AAttributeInfo attr : _attributes) {
193                x.append("\n\t" + attr.toString());
194            }
195    
196            if ((_accessFlags & (ClassFile.ACC_NATIVE | ClassFile.ACC_ABSTRACT)) == 0) {
197                CodeAttributeInfo ca = getCodeAttributeInfo();
198                x.append("\n"+(new InstructionList(ca.getCode())).toString(lineNumbers, PCs, pool));
199                
200                ArrayList<LineNumberRecord> lntErrors =
201                    checkLineNumbers();
202                if (lntErrors.size()>0) {
203                    x.append("\t"+LineNumberTableAttributeInfo.getAttributeName()+" Errors!\n");
204                    for(LineNumberRecord lnr : lntErrors) {
205                        x.append("\t\t(PC=" + lnr.startPC + " @ " + lnr.lineNo + ")\n");
206                    }
207                }
208            }
209    
210            return x.toString();
211        }
212        
213        /**
214         * Check the line number table for errors and return a list of erroneous LineNumberRecords.
215         * @return list of errors; empty if none found
216         */
217        public ArrayList<LineNumberRecord> checkLineNumbers() {
218            ArrayList<LineNumberRecord> errors = 
219                new ArrayList<LineNumberRecord>();
220            
221            if ((_accessFlags & (ClassFile.ACC_NATIVE | ClassFile.ACC_ABSTRACT)) == 0) {
222                CodeAttributeInfo ca = getCodeAttributeInfo();
223                LineNumberTableAttributeInfo lnt = null;
224                for (AAttributeInfo attr: ca.getAttributes()) {
225                    lnt = attr.execute(new ADefaultAttributeVisitor<LineNumberTableAttributeInfo, Object>() {
226                        public LineNumberTableAttributeInfo defaultCase(AAttributeInfo host, Object param) {
227                            return null;
228                        }
229                        public LineNumberTableAttributeInfo lineNumberTableCase(LineNumberTableAttributeInfo host, Object param) {
230                            return host;
231                        }
232                    }, null);
233                    if (lnt!=null) { break; }
234                }
235                if (lnt!=null) {
236                    InstructionList il = new InstructionList(ca.getCode());
237                    ArrayList<LineNumberRecord> lns =
238                        new ArrayList<LineNumberRecord>();
239                    for(LineNumberRecord l: lnt.getLineNumbers()) {
240                        lns.add(l);
241                    }
242                    Collections.sort(lns, new Comparator<LineNumberRecord>() {
243                        public int compare(LineNumberRecord o1, LineNumberRecord o2) {
244                            return o1.startPC-o2.startPC;
245                        }
246                        
247                    });
248                    for(LineNumberRecord l: lns) {
249                        boolean found = true;
250                        while(il.getPCFromIndex(il.getIndex())<l.startPC) {
251                            found = il.advanceIndex();
252                            if (!found) { break; }
253                        }
254                        if (!found || (il.getPCFromIndex(il.getIndex())!=l.startPC)) {
255                            errors.add(l);
256                        }
257                    }
258                }
259            }
260            
261            return errors;
262        }
263    
264        /**
265         * Accessor for access flags.
266         *
267         * @return access flags
268         */
269        public short getAccessFlags() {
270            return _accessFlags;
271        }
272    
273        /**
274         * Mutator for access flags.
275         *
276         * @param accessFlags new access flags
277         */
278        public void setAccessFlags(short accessFlags) {
279            _accessFlags = accessFlags;
280        }
281    
282        /**
283         * Accessor for field name.
284         *
285         * @return field name
286         */
287        public AUTFPoolInfo getName() {
288            return _name;
289        }
290    
291        /**
292         * Mutator for field name.
293         *
294         * @param name new field name
295         */
296        public void setName(AUTFPoolInfo name) {
297            _name = name;
298        }
299    
300        /**
301         * Accessor for descriptor.
302         *
303         * @return descriptor
304         */
305        public AUTFPoolInfo getDescriptor() {
306            return _descriptor;
307        }
308    
309        /**
310         * Mutator for descriptor.
311         *
312         * @param descriptor new descriptor
313         */
314        public void setDescriptor(AUTFPoolInfo descriptor) {
315            _descriptor = descriptor;
316        }
317    
318        /**
319         * Accessor for attributes.
320         *
321         * @return attributes
322         */
323        public ArrayList<AAttributeInfo> getAttributes() {
324            return _attributes;
325        }
326    
327    
328        /**
329         * Return the attribute with the specified name.
330         *
331         * @param name attribute name
332         *
333         * @return attribute or null if not found
334         */
335        public AAttributeInfo getAttribute(String name) {
336            if (0 == _attributes.size()) {
337                return null;
338            }
339            for(AAttributeInfo attr : _attributes) {
340                if (0 == name.compareTo(attr.getName().toString())) {
341                    return attr;
342                }
343            }
344            return null;
345        }
346    
347        /**
348         * Return this method's code attribute info.
349         *
350         * @return code attribute info
351         */
352        public CodeAttributeInfo getCodeAttributeInfo() {
353            for(AAttributeInfo attr : _attributes) {
354                CodeAttributeInfo code = attr.execute(new ADefaultAttributeVisitor<CodeAttributeInfo, Object>() {
355                    public CodeAttributeInfo defaultCase(AAttributeInfo host, Object o) {
356                        return null;
357                    }
358    
359                    public CodeAttributeInfo codeCase(CodeAttributeInfo host, Object o) {
360                        return host;
361                    }
362                }, null);
363                if (code != null) {
364                    return code;
365                }
366    
367            }
368            throw new ClassFormatError("Method has no Code attribute");
369        }
370        
371        /**
372         * Return the method's signature, i.e. it's name with parameter types followed in parentheses:
373         * "foo(int,float,java.lang.String)".
374         * @return signature
375         */
376        public String getSignature() {
377           String s = _descriptor.toString();
378           String paramSig = s.substring(s.indexOf('(') + 1, s.indexOf(')'));
379           
380           // handle constructor correctly
381           StringBuilder sb = new StringBuilder(getName().toString());
382           sb.append('(');
383           
384           if ((paramSig.length() > 0) && paramSig.charAt(0) != 'V') {
385               while(paramSig.length() > 0) {
386                   sb.append(ClassFileTools.getTypeString(paramSig, "").trim());
387                   paramSig = ClassFileTools.getNextSignature(paramSig);
388                   if (paramSig.length() > 0) {
389                       sb.append(",");
390                   }
391               }
392           }
393           sb.append(")");
394           return sb.toString();
395        }
396    
397        /**
398         * Indicates whether some other object is "equal to" this one.
399         * @param o the reference object with which to compare.
400         * @return <code>true</code> if this object is the same as the obj argument; <code>false</code> otherwise.
401         */
402        public boolean equals(Object o) {
403            if (this == o) {
404                return true;
405            }
406            if (o == null || getClass() != o.getClass()) {
407                return false;
408            }
409    
410            MethodInfo that = (MethodInfo)o;
411    
412            if (_accessFlags != that._accessFlags) {
413                return false;
414            }
415            if (_attributes != null ? !_attributes.equals(that._attributes) : that._attributes != null) {
416                return false;
417            }
418            if (_descriptor != null ? !_descriptor.equals(that._descriptor) : that._descriptor != null) {
419                return false;
420            }
421            if (_name != null ? !_name.equals(that._name) : that._name != null) {
422                return false;
423            }
424    
425            return true;
426        }
427    
428        /**
429         * Returns a hash code value for the object.
430         * @return a hash code value for this object.
431         */
432        public int hashCode() {
433            int result;
434            result = (int)_accessFlags;
435            result = 31 * result + (_name != null ? _name.hashCode() : 0);
436            result = 31 * result + (_descriptor != null ? _descriptor.hashCode() : 0);
437            result = 31 * result + (_attributes != null ? _attributes.hashCode() : 0);
438            return result;
439        }
440    }