Initial Commit
[packages] / xemacs-packages / jde / java / src / jde / debugger / spec / MethodBreakpointSpec.java
1 package jde.debugger.spec;
2
3 import com.sun.jdi.*;
4 import com.sun.jdi.request.*;
5
6 import java.util.*;
7 import jde.debugger.JDEException;
8
9 /**
10  * MethodBreakpointSpec.java
11  * <p>
12  * Funnily, it seems this class actually supports setting breakpoints in
13  * a particular method of an arbitrary filename! since it's very weird, it's
14  * not supported. Let us know if you require such a feature...
15  * <p>
16  * Created: Thu Jul 15 15:52:45 1999
17  *
18  * @author Amit Kumar
19  * @since 0.1
20  * @version $Revision: 1.3 $
21  */
22
23 public class MethodBreakpointSpec extends BreakpointSpec {
24
25   String methodName;
26   List methodArgs;
27     
28   public MethodBreakpointSpec(ReferenceTypeSpec refSpec,
29                               String methodName, List methodArgs) {
30     super(refSpec);
31     this.methodName = methodName;
32     this.methodArgs = methodArgs;
33   }
34
35   boolean resolve(ReferenceType refType) throws JDEException {
36     if (!isValidMethodName(methodName)) {
37       throw new JDEException("'"+methodName+"' is not a valid method name.");
38     }
39     if (!(refType instanceof ClassType)) {
40       throw new JDEException("'"+refType+"' is not a Class"); 
41     }
42     Location location = getLocation((ClassType)refType);
43     if (location == null) {
44       throw new JDEException("Can't set breakpoint on an abstract/native method");
45     }
46     BreakpointRequest br = refType.virtualMachine().eventRequestManager().createBreakpointRequest(location);
47
48     setRequest(br);
49     return true;
50   }
51
52   private Location getLocation(ClassType clazz) throws JDEException {
53     Method method = findMatchingMethod(clazz);
54     Location location = method.location();
55     return location;
56   }
57     
58   public String getMethodName() {
59     return methodName;
60   }
61
62   public List getMethodArgs() {
63     return methodArgs;
64   }
65
66   public String toString() {
67     StringBuffer buffer = new StringBuffer("break in_method ");
68     if (refSpec instanceof SourceNameReferenceTypeSpec) {
69       buffer.append(((SourceNameReferenceTypeSpec)refSpec).getSourceName());
70     } else if (refSpec instanceof PatternReferenceTypeSpec) {
71       buffer.append(((PatternReferenceTypeSpec)refSpec).getClassPattern());
72     }
73     buffer.append(" "+methodName+" ");
74     if (methodArgs != null) {
75       Iterator iter = methodArgs.iterator();
76       boolean first = true;
77       buffer.append('(');
78       while (iter.hasNext()) {
79         if (!first) {
80           buffer.append(',');
81         }
82         buffer.append((String)iter.next());
83         first = false;
84       }
85       buffer.append(")");
86     }
87     return buffer.toString();
88   }
89
90   private boolean isValidMethodName(String s) {
91     return isJavaIdentifier(s) || 
92       s.equals("<init>") ||
93       s.equals("<clinit>");
94   }
95
96   /* 
97    * Compare a method's argument types with a Vector of type names.
98    * Return true if each argument type has a name identical to the 
99    * corresponding string in the vector and if the number of 
100    * arguments in the method matches the number of names passed
101    */
102   private boolean compareArgTypes(Method method, List nameList) {
103     List argTypeNames = method.argumentTypeNames();
104
105
106     // If argument counts differ, we can stop here
107     if (argTypeNames.size() != nameList.size()) {
108       return false;
109     }
110
111     // Compare each argument type's name
112     for (int i=0; i<argTypeNames.size(); ++i) {
113       String comp1 = (String)argTypeNames.get(i);
114       String comp2 = (String)nameList.get(i);
115       if (! comp1.equals(comp2)) {
116         return false;
117       }
118     }
119     return true;
120   }
121
122   /**
123    * Remove unneeded spaces and expand class names to fully 
124    * qualified names, if necessary and possible.
125    */
126   private String normalizeArgTypeName(String name)
127     throws JDEException {
128     /* 
129      * Separate the type name from any array modifiers, 
130      * stripping whitespace after the name ends
131      */
132     int i = 0;
133     StringBuffer typePart = new StringBuffer();
134     StringBuffer arrayPart = new StringBuffer();
135     name = name.trim();
136     while (i < name.length()) {
137       char c = name.charAt(i);
138       if (Character.isWhitespace(c) || c == '[') {
139         break;      // name is complete
140       }
141       typePart.append(c);
142       i++;
143     }
144     while (i < name.length()) {
145       char c = name.charAt(i);
146       if ( (c == '[') || (c == ']') ) {
147         arrayPart.append(c);
148       } else if (!Character.isWhitespace(c)) {
149         throw new JDEException("At least one of the arguments of method '"+methodName+"' is invalid.");
150       }
151       i++;
152     }
153     name = typePart.toString();
154
155     /*
156      * When there's no sign of a package name already, 
157      * try to expand the 
158      * the name to a fully qualified class name
159      */
160     if ((name.indexOf('.') == -1) || name.startsWith("*.")) {
161       try {
162         List refs = new LinkedList(); // XXX to get it to compile
163         //                List refs = proc.findClassesMatchingPattern(name);
164         // if more than one class match, take the first, but
165         // inform anyways.
166         if (refs.size() > 0) {
167           name = ((ReferenceType)(refs.get(0))).name();
168           // warn
169           if (refs.size() > 1) {
170             //                  jde.signal(procID, WARNING, "(Method Breakpoint Warning) More than one classes matched resolving an argument for method '"+methodName+"'. Defaulting to the first match.");
171           }
172         }
173       } catch (IllegalArgumentException e) {
174         // We'll try the name as is 
175       }
176     }
177     name += arrayPart.toString();
178     return name;
179   }
180
181   /* 
182    * Attempt an unambiguous match of the method name and 
183    * argument specification to a method. If no arguments 
184    * are specified, the method must not be overloaded.
185    * Otherwise, the argument types much match exactly 
186    */
187   private Method findMatchingMethod(ClassType clazz) 
188     throws JDEException {
189
190     // Normalize the argument string once before looping below.
191     List argTypeNames = null;
192     if (methodArgs != null) {
193       argTypeNames = new ArrayList(methodArgs.size());
194       Iterator iter = methodArgs.iterator();
195       while (iter.hasNext()) {
196         String name = (String)iter.next();
197         name = normalizeArgTypeName(name);
198         argTypeNames.add(name);
199       }
200     }
201
202     // Check each method in the class for matches
203     Iterator iter = clazz.methods().iterator();
204     Method firstMatch = null;  // first method with matching name
205     Method exactMatch = null;  // (only) method with same name & sig
206     int matchCount = 0;        // > 1 implies overload
207     while (iter.hasNext()) {
208       Method candidate = (Method)iter.next();
209
210       if (candidate.name().equals(getMethodName())) {
211         matchCount++;
212
213         // Remember the first match in case it is the only one
214         if (matchCount == 1) {
215           firstMatch = candidate;
216         }
217
218         // If argument types were specified, check against candidate
219         if ((argTypeNames != null) 
220             && compareArgTypes(candidate, argTypeNames) == true) {
221           exactMatch = candidate;
222           break;
223         }
224       }
225     }
226
227     // Determine method for breakpoint
228     Method method = null;
229     if (exactMatch != null) {
230       // Name and signature match
231       method = exactMatch;
232     } else if ((argTypeNames == null) && (matchCount > 0)) {
233       // At least one name matched and no arg types were specified
234       if (matchCount == 1) {
235         method = firstMatch;       // Only one match; safe to use it
236       } else {
237         throw new JDEException("Ambiguous method '"+methodName+"'. Specify arguments.");
238       }
239     } else {
240       throw new JDEException("No method named '"+methodName+"' in class.");
241     }
242     return method;
243   }
244     
245 } // MethodBreakpointSpec
246
247 /*
248  * $Log: MethodBreakpointSpec.java,v $
249  * Revision 1.3  2003/01/15 06:06:15  paulk
250  * Petter Mahlen's changes.
251  *
252  */
253
254 // End of MethodBreakpointSpec.java