blob: c79f17e36082d86c70258e203ad142a342cff90d [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1998-2003 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package com.sun.tools.example.debug.tty;
27
28import com.sun.jdi.*;
29import com.sun.jdi.request.*;
30
31import java.util.ArrayList;
32import java.util.List;
33import java.util.Iterator;
34
35class BreakpointSpec extends EventRequestSpec {
36 String methodId;
37 List methodArgs;
38 int lineNumber;
39
40 BreakpointSpec(ReferenceTypeSpec refSpec, int lineNumber) {
41 super(refSpec);
42 this.methodId = null;
43 this.methodArgs = null;
44 this.lineNumber = lineNumber;
45 }
46
47 BreakpointSpec(ReferenceTypeSpec refSpec, String methodId,
48 List methodArgs) throws MalformedMemberNameException {
49 super(refSpec);
50 this.methodId = methodId;
51 this.methodArgs = methodArgs;
52 this.lineNumber = 0;
53 if (!isValidMethodName(methodId)) {
54 throw new MalformedMemberNameException(methodId);
55 }
56 }
57
58 /**
59 * The 'refType' is known to match, return the EventRequest.
60 */
61 EventRequest resolveEventRequest(ReferenceType refType)
62 throws AmbiguousMethodException,
63 AbsentInformationException,
64 InvalidTypeException,
65 NoSuchMethodException,
66 LineNotFoundException {
67 Location location = location(refType);
68 if (location == null) {
69 throw new InvalidTypeException();
70 }
71 EventRequestManager em = refType.virtualMachine().eventRequestManager();
72 EventRequest bp = em.createBreakpointRequest(location);
73 bp.setSuspendPolicy(suspendPolicy);
74 bp.enable();
75 return bp;
76 }
77
78 String methodName() {
79 return methodId;
80 }
81
82 int lineNumber() {
83 return lineNumber;
84 }
85
86 List methodArgs() {
87 return methodArgs;
88 }
89
90 boolean isMethodBreakpoint() {
91 return (methodId != null);
92 }
93
94 public int hashCode() {
95 return refSpec.hashCode() + lineNumber +
96 ((methodId != null) ? methodId.hashCode() : 0) +
97 ((methodArgs != null) ? methodArgs.hashCode() : 0);
98 }
99
100 public boolean equals(Object obj) {
101 if (obj instanceof BreakpointSpec) {
102 BreakpointSpec breakpoint = (BreakpointSpec)obj;
103
104 return ((methodId != null) ?
105 methodId.equals(breakpoint.methodId)
106 : methodId == breakpoint.methodId) &&
107 ((methodArgs != null) ?
108 methodArgs.equals(breakpoint.methodArgs)
109 : methodArgs == breakpoint.methodArgs) &&
110 refSpec.equals(breakpoint.refSpec) &&
111 (lineNumber == breakpoint.lineNumber);
112 } else {
113 return false;
114 }
115 }
116
117 String errorMessageFor(Exception e) {
118 if (e instanceof AmbiguousMethodException) {
119 return (MessageOutput.format("Method is overloaded; specify arguments",
120 methodName()));
121 /*
122 * TO DO: list the methods here
123 */
124 } else if (e instanceof NoSuchMethodException) {
125 return (MessageOutput.format("No method in",
126 new Object [] {methodName(),
127 refSpec.toString()}));
128 } else if (e instanceof AbsentInformationException) {
129 return (MessageOutput.format("No linenumber information for",
130 refSpec.toString()));
131 } else if (e instanceof LineNotFoundException) {
132 return (MessageOutput.format("No code at line",
133 new Object [] {new Long (lineNumber()),
134 refSpec.toString()}));
135 } else if (e instanceof InvalidTypeException) {
136 return (MessageOutput.format("Breakpoints can be located only in classes.",
137 refSpec.toString()));
138 } else {
139 return super.errorMessageFor( e);
140 }
141 }
142
143 public String toString() {
144 StringBuffer buffer = new StringBuffer(refSpec.toString());
145 if (isMethodBreakpoint()) {
146 buffer.append('.');
147 buffer.append(methodId);
148 if (methodArgs != null) {
149 Iterator iter = methodArgs.iterator();
150 boolean first = true;
151 buffer.append('(');
152 while (iter.hasNext()) {
153 if (!first) {
154 buffer.append(',');
155 }
156 buffer.append((String)iter.next());
157 first = false;
158 }
159 buffer.append(")");
160 }
161 } else {
162 buffer.append(':');
163 buffer.append(lineNumber);
164 }
165 return MessageOutput.format("breakpoint", buffer.toString());
166 }
167
168 private Location location(ReferenceType refType) throws
169 AmbiguousMethodException,
170 AbsentInformationException,
171 NoSuchMethodException,
172 LineNotFoundException {
173 Location location = null;
174 if (isMethodBreakpoint()) {
175 Method method = findMatchingMethod(refType);
176 location = method.location();
177 } else {
178 // let AbsentInformationException be thrown
179 List locs = refType.locationsOfLine(lineNumber());
180 if (locs.size() == 0) {
181 throw new LineNotFoundException();
182 }
183 // TO DO: handle multiple locations
184 location = (Location)locs.get(0);
185 if (location.method() == null) {
186 throw new LineNotFoundException();
187 }
188 }
189 return location;
190 }
191
192 private boolean isValidMethodName(String s) {
193 return isJavaIdentifier(s) ||
194 s.equals("<init>") ||
195 s.equals("<clinit>");
196 }
197
198 /*
199 * Compare a method's argument types with a Vector of type names.
200 * Return true if each argument type has a name identical to the
201 * corresponding string in the vector (allowing for varars)
202 * and if the number of arguments in the method matches the
203 * number of names passed
204 */
205 private boolean compareArgTypes(Method method, List nameList) {
206 List argTypeNames = method.argumentTypeNames();
207
208 // If argument counts differ, we can stop here
209 if (argTypeNames.size() != nameList.size()) {
210 return false;
211 }
212
213 // Compare each argument type's name
214 int nTypes = argTypeNames.size();
215 for (int i = 0; i < nTypes; ++i) {
216 String comp1 = (String)argTypeNames.get(i);
217 String comp2 = (String)nameList.get(i);
218 if (! comp1.equals(comp2)) {
219 /*
220 * We have to handle varargs. EG, the
221 * method's last arg type is xxx[]
222 * while the nameList contains xxx...
223 * Note that the nameList can also contain
224 * xxx[] in which case we don't get here.
225 */
226 if (i != nTypes - 1 ||
227 !method.isVarArgs() ||
228 !comp2.endsWith("...")) {
229 return false;
230 }
231 /*
232 * The last types differ, it is a varargs
233 * method and the nameList item is varargs.
234 * We just have to compare the type names, eg,
235 * make sure we don't have xxx[] for the method
236 * arg type and yyy... for the nameList item.
237 */
238 int comp1Length = comp1.length();
239 if (comp1Length + 1 != comp2.length()) {
240 // The type names are different lengths
241 return false;
242 }
243 // We know the two type names are the same length
244 if (!comp1.regionMatches(0, comp2, 0, comp1Length - 2)) {
245 return false;
246 }
247 // We do have xxx[] and xxx... as the last param type
248 return true;
249 }
250 }
251
252 return true;
253 }
254
255
256 /*
257 * Remove unneeded spaces and expand class names to fully
258 * qualified names, if necessary and possible.
259 */
260 private String normalizeArgTypeName(String name) {
261 /*
262 * Separate the type name from any array modifiers,
263 * stripping whitespace after the name ends
264 */
265 int i = 0;
266 StringBuffer typePart = new StringBuffer();
267 StringBuffer arrayPart = new StringBuffer();
268 name = name.trim();
269 int nameLength = name.length();
270 /*
271 * For varargs, there can be spaces before the ... but not
272 * within the ... So, we will just ignore the ...
273 * while stripping blanks.
274 */
275 boolean isVarArgs = name.endsWith("...");
276 if (isVarArgs) {
277 nameLength -= 3;
278 }
279 while (i < nameLength) {
280 char c = name.charAt(i);
281 if (Character.isWhitespace(c) || c == '[') {
282 break; // name is complete
283 }
284 typePart.append(c);
285 i++;
286 }
287 while (i < nameLength) {
288 char c = name.charAt(i);
289 if ( (c == '[') || (c == ']')) {
290 arrayPart.append(c);
291 } else if (!Character.isWhitespace(c)) {
292 throw new IllegalArgumentException
293 (MessageOutput.format("Invalid argument type name"));
294 }
295 i++;
296 }
297 name = typePart.toString();
298
299 /*
300 * When there's no sign of a package name already, try to expand the
301 * the name to a fully qualified class name
302 */
303 if ((name.indexOf('.') == -1) || name.startsWith("*.")) {
304 try {
305 ReferenceType argClass = Env.getReferenceTypeFromToken(name);
306 if (argClass != null) {
307 name = argClass.name();
308 }
309 } catch (IllegalArgumentException e) {
310 // We'll try the name as is
311 }
312 }
313 name += arrayPart.toString();
314 if (isVarArgs) {
315 name += "...";
316 }
317 return name;
318 }
319
320 /*
321 * Attempt an unambiguous match of the method name and
322 * argument specification to a method. If no arguments
323 * are specified, the method must not be overloaded.
324 * Otherwise, the argument types much match exactly
325 */
326 private Method findMatchingMethod(ReferenceType refType)
327 throws AmbiguousMethodException,
328 NoSuchMethodException {
329
330 // Normalize the argument string once before looping below.
331 List<String> argTypeNames = null;
332 if (methodArgs() != null) {
333 argTypeNames = new ArrayList<String>(methodArgs().size());
334 Iterator iter = methodArgs().iterator();
335 while (iter.hasNext()) {
336 String name = (String)iter.next();
337 name = normalizeArgTypeName(name);
338 argTypeNames.add(name);
339 }
340 }
341
342 // Check each method in the class for matches
343 Iterator iter = refType.methods().iterator();
344 Method firstMatch = null; // first method with matching name
345 Method exactMatch = null; // (only) method with same name & sig
346 int matchCount = 0; // > 1 implies overload
347 while (iter.hasNext()) {
348 Method candidate = (Method)iter.next();
349
350 if (candidate.name().equals(methodName())) {
351 matchCount++;
352
353 // Remember the first match in case it is the only one
354 if (matchCount == 1) {
355 firstMatch = candidate;
356 }
357
358 // If argument types were specified, check against candidate
359 if ((argTypeNames != null)
360 && compareArgTypes(candidate, argTypeNames) == true) {
361 exactMatch = candidate;
362 break;
363 }
364 }
365 }
366
367 // Determine method for breakpoint
368 Method method = null;
369 if (exactMatch != null) {
370 // Name and signature match
371 method = exactMatch;
372 } else if ((argTypeNames == null) && (matchCount > 0)) {
373 // At least one name matched and no arg types were specified
374 if (matchCount == 1) {
375 method = firstMatch; // Only one match; safe to use it
376 } else {
377 throw new AmbiguousMethodException();
378 }
379 } else {
380 throw new NoSuchMethodException(methodName());
381 }
382 return method;
383 }
384}