001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.api.debugger.jpda;
043:
044: import java.io.File;
045: import java.io.FileInputStream;
046: import java.io.FileNotFoundException;
047: import java.io.FileOutputStream;
048: import java.io.IOException;
049: import java.io.UnsupportedEncodingException;
050: import java.net.URL;
051: import junit.framework.AssertionFailedError;
052: import org.netbeans.api.debugger.DebuggerManager;
053: import org.netbeans.api.debugger.jpda.event.JPDABreakpointEvent;
054: import org.netbeans.api.debugger.jpda.event.JPDABreakpointListener;
055: import org.netbeans.junit.NbTestCase;
056:
057: /**
058: * Tests JSP line breakpoints at various circumstances.
059: *
060: * The tests use test java application (JspLineBreakpointApp.java) created
061: * from servlet generated by Tomcat's JSP compiler. The java app has all necessary
062: * lines left on the same place as original servlet. In addition SMAP file (JspLineBreakpointApp.txt)
063: * is attached to java app (class file) to support mapping from lines in java source
064: * to lines in JSP, from which the servlet was translated. The JSP pages are placed in
065: * org/netbeans/api/debugger/jpda/testapps/resources directory.
066: *
067: * @author Libor Kotouc
068: */
069: public class JspLineBreakpointTest extends NbTestCase {
070:
071: private static final String SOURCE_NAME = "included.jsp";
072: private static final String SOURCE_PATH_FIRST = "d/" + SOURCE_NAME;
073: private static final String SOURCE_PATH_SECOND = SOURCE_NAME;
074: private static final String CLASS_NAME = "org.netbeans.api.debugger.jpda.testapps.*";
075: private static final int LINE_NUMBER = 2;
076: private static final String STRATUM = "JSP";
077:
078: private final String SRC_ROOT = System.getProperty("test.dir.src");
079:
080: private JPDASupport support;
081: private String testAppCLAZ = null;
082: private String testAppSMAP = null;
083:
084: public JspLineBreakpointTest(String s) {
085: super (s);
086: }
087:
088: protected void setUp() {
089: URL clazURL = getClass().getResource(
090: "testapps/JspLineBreakpointApp.class");
091: assertNotNull(clazURL);
092: testAppCLAZ = clazURL.getPath();
093: URL smapURL = getClass().getResource(
094: "testapps/JspLineBreakpointApp.txt");
095: assertNotNull(smapURL);
096: testAppSMAP = smapURL.getPath();
097: }
098:
099: /**
100: * Tests debugger's ability to make difference between different JSP pages
101: * with the same name while getting the locations during class-loaded event.
102: *
103: * 1. The user creates JSP (index.jsp, include.jsp, d/include.jsp) and
104: * 2. statically includes d/include.jsp (as 1st) and include.jsp (as 2nd) into index.jsp.
105: * 3. Then bp is set in include.jsp (line 2).
106: *
107: * Debugger should stop _only_ in the include.jsp. If debugger stopped in the first JSP
108: * (d/include.jsp), then assertion violation would arise because of source path
109: * equality test.
110: */
111: public void testBreakpointUnambiguity() throws Exception {
112: try {
113: //install SDE extension to class file
114: runSDEInstaller(testAppCLAZ, testAppSMAP);
115:
116: //String URL = getClass().getResource("testapps/resources/included.jsp").toString();
117: String URL = "file:"
118: + SRC_ROOT
119: + "/org/netbeans/api/debugger/jpda/testapps/resources/included.jsp";
120: LineBreakpoint lb = LineBreakpoint.create(URL, LINE_NUMBER);
121: lb.setStratum(STRATUM); // NOI18N
122: lb.setSourceName(SOURCE_NAME);
123: lb.setSourcePath(SOURCE_PATH_SECOND);
124: lb.setPreferredClassName(CLASS_NAME);
125:
126: DebuggerManager dm = DebuggerManager.getDebuggerManager();
127: dm.addBreakpoint(lb);
128:
129: support = JPDASupport
130: .attach("org.netbeans.api.debugger.jpda.testapps.JspLineBreakpointApp");
131: JPDADebugger debugger = support.getDebugger();
132:
133: support.waitState(JPDADebugger.STATE_STOPPED); // breakpoint hit
134: assertNotNull(debugger.getCurrentCallStackFrame());
135: assertEquals("Debugger stopped at wrong file", lb
136: .getSourcePath(), debugger
137: .getCurrentCallStackFrame().getSourcePath(STRATUM));
138:
139: dm.removeBreakpoint(lb);
140: } finally {
141: if (support != null)
142: support.doFinish();
143: }
144: }
145:
146: /**
147: * Tests debugger's ability to stop in one JSP as many times as this JSP
148: * is included in another page.
149: *
150: * 1. The user creates JSP (index.jsp, include.jsp) and
151: * 2. statically includes include.jsp twice into index.jsp.
152: * 3. Then bp is set in include.jsp (line 2).
153: *
154: * Debugger should stop twice in the include.jsp. If debugger didn't stopped
155: * in the include.jsp for the second time, then assertion violation would arise
156: * because of testing debugger's state for STOP state.
157: */
158: public void testBreakpointRepeatability() throws Exception {
159: try {
160: //install SDE extension to class file
161: runSDEInstaller(testAppCLAZ, testAppSMAP);
162:
163: //String URL = getClass().getResource("testapps/resources/included.jsp").toString();
164: String URL = "file:"
165: + SRC_ROOT
166: + "/org/netbeans/api/debugger/jpda/testapps/resources/included.jsp";
167: LineBreakpoint lb = LineBreakpoint.create(URL, LINE_NUMBER);
168: lb.setStratum(STRATUM); // NOI18N
169: lb.setSourceName(SOURCE_NAME);
170: lb.setSourcePath(SOURCE_PATH_SECOND);
171: lb.setPreferredClassName(CLASS_NAME);
172:
173: DebuggerManager dm = DebuggerManager.getDebuggerManager();
174: dm.addBreakpoint(lb);
175:
176: support = JPDASupport
177: .attach("org.netbeans.api.debugger.jpda.testapps.JspLineBreakpointApp");
178: JPDADebugger debugger = support.getDebugger();
179:
180: support.waitState(JPDADebugger.STATE_STOPPED); // first breakpoint hit
181: support.doContinue();
182: support.waitState(JPDADebugger.STATE_STOPPED); // second breakpoint hit
183: assertTrue(
184: "Debugger did not stop at breakpoint for the second time.",
185: debugger.getState() == JPDADebugger.STATE_STOPPED);
186:
187: dm.removeBreakpoint(lb);
188:
189: } finally {
190: if (support != null)
191: support.doFinish();
192: }
193: }
194:
195: private static void runSDEInstaller(String pathToClassFile,
196: String pathToSmapFile) throws IOException {
197: SDEInstaller.main(new String[] { pathToClassFile,
198: pathToSmapFile });
199: }
200:
201: //inner classes
202:
203: private static class SDEInstaller {
204:
205: static final String nameSDE = "SourceDebugExtension";
206:
207: byte[] orig;
208: byte[] sdeAttr;
209: byte[] gen;
210:
211: int origPos = 0;
212: int genPos = 0;
213:
214: int sdeIndex;
215:
216: private boolean isDebugEnabled() {
217: return System.getProperty("sde.SDEInstaller.verbose") != null;
218: }
219:
220: public static void main(String[] args) throws IOException {
221: if (args.length == 2) {
222: install(new File(args[0]), new File(args[1]));
223: } else if (args.length == 3) {
224: install(new File(args[0]), new File(args[1]), new File(
225: args[2]));
226: } else {
227: System.err
228: .println("Usage: <command> <input class file> "
229: + "<attribute file> <output class file name>\n"
230: + "<command> <input/output class file> <attribute file>");
231: }
232: }
233:
234: static void install(File inClassFile, File attrFile,
235: File outClassFile) throws IOException {
236: new SDEInstaller(inClassFile, attrFile, outClassFile);
237: }
238:
239: static void install(File inOutClassFile, File attrFile)
240: throws IOException {
241: File tmpFile = new File(inOutClassFile.getPath() + "tmp");
242: new SDEInstaller(inOutClassFile, attrFile, tmpFile);
243: if (!inOutClassFile.delete()) {
244: throw new IOException("inOutClassFile.delete() failed");
245: }
246: if (!tmpFile.renameTo(inOutClassFile)) {
247: throw new IOException(
248: "tmpFile.renameTo(inOutClassFile) failed");
249: }
250: }
251:
252: static void install(File classFile, byte[] smap)
253: throws IOException {
254: File tmpFile = new File(classFile.getPath() + "tmp");
255: new SDEInstaller(classFile, smap, tmpFile);
256: if (!classFile.delete()) {
257: throw new IOException("classFile.delete() failed");
258: }
259: if (!tmpFile.renameTo(classFile)) {
260: throw new IOException(
261: "tmpFile.renameTo(classFile) failed");
262: }
263: }
264:
265: SDEInstaller(File inClassFile, byte[] sdeAttr, File outClassFile)
266: throws IOException {
267: if (!inClassFile.exists()) {
268: throw new FileNotFoundException("no such file: "
269: + inClassFile);
270: }
271:
272: this .sdeAttr = sdeAttr;
273: // get the bytes
274: orig = readWhole(inClassFile);
275: gen = new byte[orig.length + sdeAttr.length + 100];
276:
277: // do it
278: addSDE();
279:
280: // write result
281: FileOutputStream outStream = new FileOutputStream(
282: outClassFile);
283: outStream.write(gen, 0, genPos);
284: outStream.close();
285: }
286:
287: SDEInstaller(File inClassFile, File attrFile, File outClassFile)
288: throws IOException {
289: this (inClassFile, readWhole(attrFile), outClassFile);
290: }
291:
292: static byte[] readWhole(File input) throws IOException {
293: FileInputStream inStream = new FileInputStream(input);
294: int len = (int) input.length();
295: byte[] bytes = new byte[len];
296: if (inStream.read(bytes, 0, len) != len) {
297: throw new IOException("expected size: " + len);
298: }
299: inStream.close();
300: return bytes;
301: }
302:
303: void addSDE() throws UnsupportedEncodingException, IOException {
304: int i;
305: copy(4 + 2 + 2); // magic min/maj version
306: int constantPoolCountPos = genPos;
307: int constantPoolCount = readU2();
308: if (isDebugEnabled())
309: System.out.println("constant pool count: "
310: + constantPoolCount);
311: writeU2(constantPoolCount);
312:
313: // copy old constant pool return index of SDE symbol, if found
314: sdeIndex = copyConstantPool(constantPoolCount);
315: if (sdeIndex < 0) {
316: // if "SourceDebugExtension" symbol not there add it
317: writeUtf8ForSDE();
318:
319: // increment the countantPoolCount
320: sdeIndex = constantPoolCount;
321: ++constantPoolCount;
322: randomAccessWriteU2(constantPoolCountPos,
323: constantPoolCount);
324:
325: if (isDebugEnabled())
326: System.out
327: .println("SourceDebugExtension not found, installed at: "
328: + sdeIndex);
329: } else {
330: if (isDebugEnabled())
331: System.out
332: .println("SourceDebugExtension found at: "
333: + sdeIndex);
334: }
335: copy(2 + 2 + 2); // access, this, super
336: int interfaceCount = readU2();
337: writeU2(interfaceCount);
338: if (isDebugEnabled())
339: System.out.println("interfaceCount: " + interfaceCount);
340: copy(interfaceCount * 2);
341: copyMembers(); // fields
342: copyMembers(); // methods
343: int attrCountPos = genPos;
344: int attrCount = readU2();
345: writeU2(attrCount);
346: if (isDebugEnabled())
347: System.out.println("class attrCount: " + attrCount);
348: // copy the class attributes, return true if SDE attr found (not copied)
349: if (!copyAttrs(attrCount)) {
350: // we will be adding SDE and it isn't already counted
351: ++attrCount;
352: randomAccessWriteU2(attrCountPos, attrCount);
353: if (isDebugEnabled())
354: System.out.println("class attrCount incremented");
355: }
356: writeAttrForSDE(sdeIndex);
357: }
358:
359: void copyMembers() {
360: int count = readU2();
361: writeU2(count);
362: if (isDebugEnabled())
363: System.out.println("members count: " + count);
364: for (int i = 0; i < count; ++i) {
365: copy(6); // access, name, descriptor
366: int attrCount = readU2();
367: writeU2(attrCount);
368: if (isDebugEnabled())
369: System.out.println("member attr count: "
370: + attrCount);
371: copyAttrs(attrCount);
372: }
373: }
374:
375: boolean copyAttrs(int attrCount) {
376: boolean sdeFound = false;
377: for (int i = 0; i < attrCount; ++i) {
378: int nameIndex = readU2();
379: // don't write old SDE
380: if (nameIndex == sdeIndex) {
381: sdeFound = true;
382: if (isDebugEnabled())
383: System.out.println("SDE attr found");
384: } else {
385: writeU2(nameIndex); // name
386: int len = readU4();
387: writeU4(len);
388: copy(len);
389: if (isDebugEnabled())
390: System.out.println("attr len: " + len);
391: }
392: }
393: return sdeFound;
394: }
395:
396: void writeAttrForSDE(int index) {
397: writeU2(index);
398: writeU4(sdeAttr.length);
399: for (int i = 0; i < sdeAttr.length; ++i) {
400: writeU1(sdeAttr[i]);
401: }
402: }
403:
404: void randomAccessWriteU2(int pos, int val) {
405: int savePos = genPos;
406: genPos = pos;
407: writeU2(val);
408: genPos = savePos;
409: }
410:
411: int readU1() {
412: return ((int) orig[origPos++]) & 0xFF;
413: }
414:
415: int readU2() {
416: int res = readU1();
417: return (res << 8) + readU1();
418: }
419:
420: int readU4() {
421: int res = readU2();
422: return (res << 16) + readU2();
423: }
424:
425: void writeU1(int val) {
426: gen[genPos++] = (byte) val;
427: }
428:
429: void writeU2(int val) {
430: writeU1(val >> 8);
431: writeU1(val & 0xFF);
432: }
433:
434: void writeU4(int val) {
435: writeU2(val >> 16);
436: writeU2(val & 0xFFFF);
437: }
438:
439: void copy(int count) {
440: for (int i = 0; i < count; ++i) {
441: gen[genPos++] = orig[origPos++];
442: }
443: }
444:
445: byte[] readBytes(int count) {
446: byte[] bytes = new byte[count];
447: for (int i = 0; i < count; ++i) {
448: bytes[i] = orig[origPos++];
449: }
450: return bytes;
451: }
452:
453: void writeBytes(byte[] bytes) {
454: for (int i = 0; i < bytes.length; ++i) {
455: gen[genPos++] = bytes[i];
456: }
457: }
458:
459: int copyConstantPool(int constantPoolCount)
460: throws UnsupportedEncodingException, IOException {
461: int sdeIndex = -1;
462: // copy const pool index zero not in class file
463: for (int i = 1; i < constantPoolCount; ++i) {
464: int tag = readU1();
465: writeU1(tag);
466: switch (tag) {
467: case 7: // Class
468: case 8: // String
469: if (isDebugEnabled())
470: System.out.println(i + " copying 2 bytes");
471: copy(2);
472: break;
473: case 9: // Field
474: case 10: // Method
475: case 11: // InterfaceMethod
476: case 3: // Integer
477: case 4: // Float
478: case 12: // NameAndType
479: if (isDebugEnabled())
480: System.out.println(i + " copying 4 bytes");
481: copy(4);
482: break;
483: case 5: // Long
484: case 6: // Double
485: if (isDebugEnabled())
486: System.out.println(i + " copying 8 bytes");
487: copy(8);
488: i++;
489: break;
490: case 1: // Utf8
491: int len = readU2();
492: writeU2(len);
493: byte[] utf8 = readBytes(len);
494: String str = new String(utf8, "UTF-8");
495: if (isDebugEnabled())
496: System.out.println(i + " read class attr -- '"
497: + str + "'");
498: if (str.equals(nameSDE)) {
499: sdeIndex = i;
500: }
501: writeBytes(utf8);
502: break;
503: default:
504: throw new IOException("unexpected tag: " + tag);
505: }
506: }
507: return sdeIndex;
508: }
509:
510: void writeUtf8ForSDE() {
511: int len = nameSDE.length();
512: writeU1(1); // Utf8 tag
513: writeU2(len);
514: for (int i = 0; i < len; ++i) {
515: writeU1(nameSDE.charAt(i));
516: }
517: }
518: }
519:
520: private class TestBreakpointListener implements
521: JPDABreakpointListener {
522:
523: private LineBreakpoint lineBreakpoint;
524: private int conditionResult;
525:
526: private JPDABreakpointEvent event;
527: private AssertionError failure;
528:
529: public TestBreakpointListener(LineBreakpoint lineBreakpoint) {
530: this (lineBreakpoint, JPDABreakpointEvent.CONDITION_NONE);
531: }
532:
533: public TestBreakpointListener(LineBreakpoint lineBreakpoint,
534: int conditionResult) {
535: this .lineBreakpoint = lineBreakpoint;
536: this .conditionResult = conditionResult;
537: }
538:
539: public void breakpointReached(JPDABreakpointEvent event) {
540: try {
541: checkEvent(event);
542: } catch (AssertionError e) {
543: failure = e;
544: } catch (Throwable e) {
545: failure = new AssertionError(e);
546: }
547: }
548:
549: private void checkEvent(JPDABreakpointEvent event) {
550: this .event = event;
551: assertEquals("Breakpoint event: Wrong source breakpoint",
552: lineBreakpoint, event.getSource());
553: assertNotNull("Breakpoint event: Context thread is null",
554: event.getThread());
555:
556: int result = event.getConditionResult();
557: if (result == JPDABreakpointEvent.CONDITION_FAILED
558: && conditionResult != JPDABreakpointEvent.CONDITION_FAILED)
559: failure = new AssertionError(event
560: .getConditionException());
561: else if (result != conditionResult)
562: failure = new AssertionError(
563: "Unexpected breakpoint condition result: "
564: + result);
565: }
566:
567: public void checkResult() {
568: if (event == null) {
569: CallStackFrame f = support.getDebugger()
570: .getCurrentCallStackFrame();
571: int ln = -1;
572: if (f != null) {
573: ln = f.getLineNumber(null);
574: }
575: throw new AssertionError(
576: "Breakpoint was not hit (listener was not notified) "
577: + ln);
578: }
579: if (failure != null)
580: throw failure;
581: }
582: }
583:
584: }
|