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