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