001: /*
002: * Copyright 1998-2003 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.tools.example.debug.tty;
027:
028: import com.sun.jdi.*;
029: import com.sun.jdi.request.*;
030:
031: import java.util.ArrayList;
032: import java.util.List;
033: import java.util.Iterator;
034:
035: class BreakpointSpec extends EventRequestSpec {
036: String methodId;
037: List methodArgs;
038: int lineNumber;
039:
040: BreakpointSpec(ReferenceTypeSpec refSpec, int lineNumber) {
041: super (refSpec);
042: this .methodId = null;
043: this .methodArgs = null;
044: this .lineNumber = lineNumber;
045: }
046:
047: BreakpointSpec(ReferenceTypeSpec refSpec, String methodId,
048: List methodArgs) throws MalformedMemberNameException {
049: super (refSpec);
050: this .methodId = methodId;
051: this .methodArgs = methodArgs;
052: this .lineNumber = 0;
053: if (!isValidMethodName(methodId)) {
054: throw new MalformedMemberNameException(methodId);
055: }
056: }
057:
058: /**
059: * The 'refType' is known to match, return the EventRequest.
060: */
061: EventRequest resolveEventRequest(ReferenceType refType)
062: throws AmbiguousMethodException,
063: AbsentInformationException, InvalidTypeException,
064: NoSuchMethodException, LineNotFoundException {
065: Location location = location(refType);
066: if (location == null) {
067: throw new InvalidTypeException();
068: }
069: EventRequestManager em = refType.virtualMachine()
070: .eventRequestManager();
071: EventRequest bp = em.createBreakpointRequest(location);
072: bp.setSuspendPolicy(suspendPolicy);
073: bp.enable();
074: return bp;
075: }
076:
077: String methodName() {
078: return methodId;
079: }
080:
081: int lineNumber() {
082: return lineNumber;
083: }
084:
085: List methodArgs() {
086: return methodArgs;
087: }
088:
089: boolean isMethodBreakpoint() {
090: return (methodId != null);
091: }
092:
093: public int hashCode() {
094: return refSpec.hashCode() + lineNumber
095: + ((methodId != null) ? methodId.hashCode() : 0)
096: + ((methodArgs != null) ? methodArgs.hashCode() : 0);
097: }
098:
099: public boolean equals(Object obj) {
100: if (obj instanceof BreakpointSpec) {
101: BreakpointSpec breakpoint = (BreakpointSpec) obj;
102:
103: return ((methodId != null) ? methodId
104: .equals(breakpoint.methodId)
105: : methodId == breakpoint.methodId)
106: && ((methodArgs != null) ? methodArgs
107: .equals(breakpoint.methodArgs)
108: : methodArgs == breakpoint.methodArgs)
109: && refSpec.equals(breakpoint.refSpec)
110: && (lineNumber == breakpoint.lineNumber);
111: } else {
112: return false;
113: }
114: }
115:
116: String errorMessageFor(Exception e) {
117: if (e instanceof AmbiguousMethodException) {
118: return (MessageOutput.format(
119: "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", new Object[] {
126: methodName(), refSpec.toString() }));
127: } else if (e instanceof AbsentInformationException) {
128: return (MessageOutput
129: .format("No linenumber information for", refSpec
130: .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(
137: "Breakpoints can be located only in classes.",
138: refSpec.toString()));
139: } else {
140: return super .errorMessageFor(e);
141: }
142: }
143:
144: public String toString() {
145: StringBuffer buffer = new StringBuffer(refSpec.toString());
146: if (isMethodBreakpoint()) {
147: buffer.append('.');
148: buffer.append(methodId);
149: if (methodArgs != null) {
150: Iterator iter = methodArgs.iterator();
151: boolean first = true;
152: buffer.append('(');
153: while (iter.hasNext()) {
154: if (!first) {
155: buffer.append(',');
156: }
157: buffer.append((String) iter.next());
158: first = false;
159: }
160: buffer.append(")");
161: }
162: } else {
163: buffer.append(':');
164: buffer.append(lineNumber);
165: }
166: return MessageOutput.format("breakpoint", buffer.toString());
167: }
168:
169: private Location location(ReferenceType refType)
170: throws AmbiguousMethodException,
171: AbsentInformationException, 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) || s.equals("<init>")
194: || s.equals("<clinit>");
195: }
196:
197: /*
198: * Compare a method's argument types with a Vector of type names.
199: * Return true if each argument type has a name identical to the
200: * corresponding string in the vector (allowing for varars)
201: * and if the number of arguments in the method matches the
202: * number of names passed
203: */
204: private boolean compareArgTypes(Method method, List nameList) {
205: List argTypeNames = method.argumentTypeNames();
206:
207: // If argument counts differ, we can stop here
208: if (argTypeNames.size() != nameList.size()) {
209: return false;
210: }
211:
212: // Compare each argument type's name
213: int nTypes = argTypeNames.size();
214: for (int i = 0; i < nTypes; ++i) {
215: String comp1 = (String) argTypeNames.get(i);
216: String comp2 = (String) nameList.get(i);
217: if (!comp1.equals(comp2)) {
218: /*
219: * We have to handle varargs. EG, the
220: * method's last arg type is xxx[]
221: * while the nameList contains xxx...
222: * Note that the nameList can also contain
223: * xxx[] in which case we don't get here.
224: */
225: if (i != nTypes - 1 || !method.isVarArgs()
226: || !comp2.endsWith("...")) {
227: return false;
228: }
229: /*
230: * The last types differ, it is a varargs
231: * method and the nameList item is varargs.
232: * We just have to compare the type names, eg,
233: * make sure we don't have xxx[] for the method
234: * arg type and yyy... for the nameList item.
235: */
236: int comp1Length = comp1.length();
237: if (comp1Length + 1 != comp2.length()) {
238: // The type names are different lengths
239: return false;
240: }
241: // We know the two type names are the same length
242: if (!comp1.regionMatches(0, comp2, 0, comp1Length - 2)) {
243: return false;
244: }
245: // We do have xxx[] and xxx... as the last param type
246: return true;
247: }
248: }
249:
250: return true;
251: }
252:
253: /*
254: * Remove unneeded spaces and expand class names to fully
255: * qualified names, if necessary and possible.
256: */
257: private String normalizeArgTypeName(String name) {
258: /*
259: * Separate the type name from any array modifiers,
260: * stripping whitespace after the name ends
261: */
262: int i = 0;
263: StringBuffer typePart = new StringBuffer();
264: StringBuffer arrayPart = new StringBuffer();
265: name = name.trim();
266: int nameLength = name.length();
267: /*
268: * For varargs, there can be spaces before the ... but not
269: * within the ... So, we will just ignore the ...
270: * while stripping blanks.
271: */
272: boolean isVarArgs = name.endsWith("...");
273: if (isVarArgs) {
274: nameLength -= 3;
275: }
276: while (i < nameLength) {
277: char c = name.charAt(i);
278: if (Character.isWhitespace(c) || c == '[') {
279: break; // name is complete
280: }
281: typePart.append(c);
282: i++;
283: }
284: while (i < nameLength) {
285: char c = name.charAt(i);
286: if ((c == '[') || (c == ']')) {
287: arrayPart.append(c);
288: } else if (!Character.isWhitespace(c)) {
289: throw new IllegalArgumentException(MessageOutput
290: .format("Invalid argument type name"));
291: }
292: i++;
293: }
294: name = typePart.toString();
295:
296: /*
297: * When there's no sign of a package name already, try to expand the
298: * the name to a fully qualified class name
299: */
300: if ((name.indexOf('.') == -1) || name.startsWith("*.")) {
301: try {
302: ReferenceType argClass = Env
303: .getReferenceTypeFromToken(name);
304: if (argClass != null) {
305: name = argClass.name();
306: }
307: } catch (IllegalArgumentException e) {
308: // We'll try the name as is
309: }
310: }
311: name += arrayPart.toString();
312: if (isVarArgs) {
313: name += "...";
314: }
315: return name;
316: }
317:
318: /*
319: * Attempt an unambiguous match of the method name and
320: * argument specification to a method. If no arguments
321: * are specified, the method must not be overloaded.
322: * Otherwise, the argument types much match exactly
323: */
324: private Method findMatchingMethod(ReferenceType refType)
325: throws AmbiguousMethodException, NoSuchMethodException {
326:
327: // Normalize the argument string once before looping below.
328: List<String> argTypeNames = null;
329: if (methodArgs() != null) {
330: argTypeNames = new ArrayList<String>(methodArgs().size());
331: Iterator iter = methodArgs().iterator();
332: while (iter.hasNext()) {
333: String name = (String) iter.next();
334: name = normalizeArgTypeName(name);
335: argTypeNames.add(name);
336: }
337: }
338:
339: // Check each method in the class for matches
340: Iterator iter = refType.methods().iterator();
341: Method firstMatch = null; // first method with matching name
342: Method exactMatch = null; // (only) method with same name & sig
343: int matchCount = 0; // > 1 implies overload
344: while (iter.hasNext()) {
345: Method candidate = (Method) iter.next();
346:
347: if (candidate.name().equals(methodName())) {
348: matchCount++;
349:
350: // Remember the first match in case it is the only one
351: if (matchCount == 1) {
352: firstMatch = candidate;
353: }
354:
355: // If argument types were specified, check against candidate
356: if ((argTypeNames != null)
357: && compareArgTypes(candidate, argTypeNames) == true) {
358: exactMatch = candidate;
359: break;
360: }
361: }
362: }
363:
364: // Determine method for breakpoint
365: Method method = null;
366: if (exactMatch != null) {
367: // Name and signature match
368: method = exactMatch;
369: } else if ((argTypeNames == null) && (matchCount > 0)) {
370: // At least one name matched and no arg types were specified
371: if (matchCount == 1) {
372: method = firstMatch; // Only one match; safe to use it
373: } else {
374: throw new AmbiguousMethodException();
375: }
376: } else {
377: throw new NoSuchMethodException(methodName());
378: }
379: return method;
380: }
381: }
|