1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.struts2.jasper.compiler;
19
20 import com.opensymphony.xwork2.util.logging.Logger;
21 import com.opensymphony.xwork2.util.logging.LoggerFactory;
22 import org.apache.struts2.jasper.JasperException;
23 import org.apache.struts2.jasper.JspCompilationContext;
24
25 import java.io.*;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.Map;
29
30 /***
31 * Contains static utilities for generating SMAP data based on the
32 * current version of Jasper.
33 *
34 * @author Jayson Falkner
35 * @author Shawn Bayern
36 * @author Robert Field (inner SDEInstaller class)
37 * @author Mark Roth
38 * @author Kin-man Chung
39 */
40 public class SmapUtil {
41
42 private Logger log = LoggerFactory.getLogger(SmapUtil.class);
43
44 //**********************************************************************
45 // Constants
46
47 public static final String SMAP_ENCODING = "UTF-8";
48
49 //*********************************************************************
50 // Public entry points
51
52 /**
53 * Generates an appropriate SMAP representing the current compilation
54 * context. (JSR-045.)
55 *
56 * @param ctxt Current compilation context
57 * @param pageNodes The current JSP page
58 * @return a SMAP for the page
59 */
60 public static String[] generateSmap(
61 JspCompilationContext ctxt,
62 Node.Nodes pageNodes)
63 throws IOException {
64
65
66 PreScanVisitor psVisitor = new PreScanVisitor();
67 try {
68 pageNodes.visit(psVisitor);
69 } catch (JasperException ex) {
70 }
71 HashMap map = psVisitor.getMap();
72
73
74 SmapGenerator g = new SmapGenerator();
75
76 /*** Disable reading of input SMAP because:
77 1. There is a bug here: getRealPath() is null if .jsp is in a jar
78 Bugzilla 14660.
79 2. Mappings from other sources into .jsp files are not supported.
80 TODO: fix 1. if 2. is not true.
81 // determine if we have an input SMAP
82 String smapPath = inputSmapPath(ctxt.getRealPath(ctxt.getJspFile()));
83 File inputSmap = new File(smapPath);
84 if (inputSmap.exists()) {
85 byte[] embeddedSmap = null;
86 byte[] subSmap = SDEInstaller.readWhole(inputSmap);
87 String subSmapString = new String(subSmap, SMAP_ENCODING);
88 g.addSmap(subSmapString, "JSP");
89 }
90 **/
91
92
93 SmapStratum s = new SmapStratum("JSP");
94
95 g.setOutputFileName(unqualify(ctxt.getServletJavaFileName()));
96
97
98 evaluateNodes(pageNodes, s, map, ctxt.getOptions().getMappedFile());
99 s.optimizeLineSection();
100 g.addStratum(s, true);
101
102 if (ctxt.getOptions().isSmapDumped()) {
103 File outSmap = new File(ctxt.getClassFileName() + ".smap");
104 PrintWriter so =
105 new PrintWriter(
106 new OutputStreamWriter(
107 new FileOutputStream(outSmap),
108 SMAP_ENCODING));
109 so.print(g.getString());
110 so.close();
111 }
112
113 String classFileName = ctxt.getClassFileName();
114 int innerClassCount = map.size();
115 String[] smapInfo = new String[2 + innerClassCount * 2];
116 smapInfo[0] = classFileName;
117 smapInfo[1] = g.getString();
118
119 int count = 2;
120 Iterator iter = map.entrySet().iterator();
121 while (iter.hasNext()) {
122 Map.Entry entry = (Map.Entry) iter.next();
123 String innerClass = (String) entry.getKey();
124 s = (SmapStratum) entry.getValue();
125 s.optimizeLineSection();
126 g = new SmapGenerator();
127 g.setOutputFileName(unqualify(ctxt.getServletJavaFileName()));
128 g.addStratum(s, true);
129
130 String innerClassFileName =
131 classFileName.substring(0, classFileName.indexOf(".class")) +
132 '$' + innerClass + ".class";
133 if (ctxt.getOptions().isSmapDumped()) {
134 File outSmap = new File(innerClassFileName + ".smap");
135 PrintWriter so =
136 new PrintWriter(
137 new OutputStreamWriter(
138 new FileOutputStream(outSmap),
139 SMAP_ENCODING));
140 so.print(g.getString());
141 so.close();
142 }
143 smapInfo[count] = innerClassFileName;
144 smapInfo[count + 1] = g.getString();
145 count += 2;
146 }
147
148 return smapInfo;
149 }
150
151 public static void installSmap(String[] smap)
152 throws IOException {
153 if (smap == null) {
154 return;
155 }
156
157 for (int i = 0; i < smap.length; i += 2) {
158 File outServlet = new File(smap[i]);
159 SDEInstaller.install(outServlet, smap[i + 1].getBytes());
160 }
161 }
162
163 //**********************************************************************
164 // Private utilities
165
166 /**
167 * Returns an unqualified version of the given file path.
168 */
169 private static String unqualify(String path) {
170 path = path.replace('//', '/');
171 return path.substring(path.lastIndexOf('/') + 1);
172 }
173
174 /***
175 * Returns a file path corresponding to a potential SMAP input
176 * for the given compilation input (JSP file).
177 */
178 private static String inputSmapPath(String path) {
179 return path.substring(0, path.lastIndexOf('.') + 1) + "smap";
180 }
181
182 //**********************************************************************
183 // Installation logic (from Robert Field, JSR-045 spec lead)
184 private static class SDEInstaller {
185
186 private Logger log = LoggerFactory.getLogger(SDEInstaller.class);
187
188 static final String nameSDE = "SourceDebugExtension";
189
190 byte[] orig;
191 byte[] sdeAttr;
192 byte[] gen;
193
194 int origPos = 0;
195 int genPos = 0;
196
197 int sdeIndex;
198
199 public static void main(String[] args) throws IOException {
200 if (args.length == 2) {
201 install(new File(args[0]), new File(args[1]));
202 } else if (args.length == 3) {
203 install(
204 new File(args[0]),
205 new File(args[1]),
206 new File(args[2]));
207 } else {
208 System.err.println(
209 "Usage: <command> <input class file> "
210 + "<attribute file> <output class file name>\n"
211 + "<command> <input/output class file> <attribute file>");
212 }
213 }
214
215 static void install(File inClassFile, File attrFile, File outClassFile)
216 throws IOException {
217 new SDEInstaller(inClassFile, attrFile, outClassFile);
218 }
219
220 static void install(File inOutClassFile, File attrFile)
221 throws IOException {
222 File tmpFile = new File(inOutClassFile.getPath() + "tmp");
223 new SDEInstaller(inOutClassFile, attrFile, tmpFile);
224 if (!inOutClassFile.delete()) {
225 throw new IOException("inOutClassFile.delete() failed");
226 }
227 if (!tmpFile.renameTo(inOutClassFile)) {
228 throw new IOException("tmpFile.renameTo(inOutClassFile) failed");
229 }
230 }
231
232 static void install(File classFile, byte[] smap) throws IOException {
233 File tmpFile = new File(classFile.getPath() + "tmp");
234 new SDEInstaller(classFile, smap, tmpFile);
235 if (!classFile.delete()) {
236 throw new IOException("classFile.delete() failed");
237 }
238 if (!tmpFile.renameTo(classFile)) {
239 throw new IOException("tmpFile.renameTo(classFile) failed");
240 }
241 }
242
243 SDEInstaller(File inClassFile, byte[] sdeAttr, File outClassFile)
244 throws IOException {
245 if (!inClassFile.exists()) {
246 throw new FileNotFoundException("no such file: " + inClassFile);
247 }
248
249 this.sdeAttr = sdeAttr;
250 // get the bytes
251 orig = readWhole(inClassFile);
252 gen = new byte[orig.length + sdeAttr.length + 100];
253
254 // do it
255 addSDE();
256
257 // write result
258 FileOutputStream outStream = new FileOutputStream(outClassFile);
259 outStream.write(gen, 0, genPos);
260 outStream.close();
261 }
262
263 SDEInstaller(File inClassFile, File attrFile, File outClassFile)
264 throws IOException {
265 this(inClassFile, readWhole(attrFile), outClassFile);
266 }
267
268 static byte[] readWhole(File input) throws IOException {
269 FileInputStream inStream = new FileInputStream(input);
270 int len = (int) input.length();
271 byte[] bytes = new byte[len];
272 if (inStream.read(bytes, 0, len) != len) {
273 throw new IOException("expected size: " + len);
274 }
275 inStream.close();
276 return bytes;
277 }
278
279 void addSDE() throws UnsupportedEncodingException, IOException {
280 int i;
281 copy(4 + 2 + 2); // magic min/maj version
282 int constantPoolCountPos = genPos;
283 int constantPoolCount = readU2();
284 if (log.isDebugEnabled())
285 log.debug("constant pool count: " + constantPoolCount);
286 writeU2(constantPoolCount);
287
288 // copy old constant pool return index of SDE symbol, if found
289 sdeIndex = copyConstantPool(constantPoolCount);
290 if (sdeIndex < 0) {
291 // if "SourceDebugExtension" symbol not there add it
292 writeUtf8ForSDE();
293
294 // increment the countantPoolCount
295 sdeIndex = constantPoolCount;
296 ++constantPoolCount;
297 randomAccessWriteU2(constantPoolCountPos, constantPoolCount);
298
299 if (log.isDebugEnabled())
300 log.debug("SourceDebugExtension not found, installed at: " + sdeIndex);
301 } else {
302 if (log.isDebugEnabled())
303 log.debug("SourceDebugExtension found at: " + sdeIndex);
304 }
305 copy(2 + 2 + 2); // access, this, super
306 int interfaceCount = readU2();
307 writeU2(interfaceCount);
308 if (log.isDebugEnabled())
309 log.debug("interfaceCount: " + interfaceCount);
310 copy(interfaceCount * 2);
311 copyMembers(); // fields
312 copyMembers(); // methods
313 int attrCountPos = genPos;
314 int attrCount = readU2();
315 writeU2(attrCount);
316 if (log.isDebugEnabled())
317 log.debug("class attrCount: " + attrCount);
318 // copy the class attributes, return true if SDE attr found (not copied)
319 if (!copyAttrs(attrCount)) {
320 // we will be adding SDE and it isn't already counted
321 ++attrCount;
322 randomAccessWriteU2(attrCountPos, attrCount);
323 if (log.isDebugEnabled())
324 log.debug("class attrCount incremented");
325 }
326 writeAttrForSDE(sdeIndex);
327 }
328
329 void copyMembers() {
330 int count = readU2();
331 writeU2(count);
332 if (log.isDebugEnabled())
333 log.debug("members count: " + count);
334 for (int i = 0; i < count; ++i) {
335 copy(6); // access, name, descriptor
336 int attrCount = readU2();
337 writeU2(attrCount);
338 if (log.isDebugEnabled())
339 log.debug("member attr count: " + attrCount);
340 copyAttrs(attrCount);
341 }
342 }
343
344 boolean copyAttrs(int attrCount) {
345 boolean sdeFound = false;
346 for (int i = 0; i < attrCount; ++i) {
347 int nameIndex = readU2();
348 // don't write old SDE
349 if (nameIndex == sdeIndex) {
350 sdeFound = true;
351 if (log.isDebugEnabled())
352 log.debug("SDE attr found");
353 } else {
354 writeU2(nameIndex); // name
355 int len = readU4();
356 writeU4(len);
357 copy(len);
358 if (log.isDebugEnabled())
359 log.debug("attr len: " + len);
360 }
361 }
362 return sdeFound;
363 }
364
365 void writeAttrForSDE(int index) {
366 writeU2(index);
367 writeU4(sdeAttr.length);
368 for (int i = 0; i < sdeAttr.length; ++i) {
369 writeU1(sdeAttr[i]);
370 }
371 }
372
373 void randomAccessWriteU2(int pos, int val) {
374 int savePos = genPos;
375 genPos = pos;
376 writeU2(val);
377 genPos = savePos;
378 }
379
380 int readU1() {
381 return ((int) orig[origPos++]) & 0xFF;
382 }
383
384 int readU2() {
385 int res = readU1();
386 return (res << 8) + readU1();
387 }
388
389 int readU4() {
390 int res = readU2();
391 return (res << 16) + readU2();
392 }
393
394 void writeU1(int val) {
395 gen[genPos++] = (byte) val;
396 }
397
398 void writeU2(int val) {
399 writeU1(val >> 8);
400 writeU1(val & 0xFF);
401 }
402
403 void writeU4(int val) {
404 writeU2(val >> 16);
405 writeU2(val & 0xFFFF);
406 }
407
408 void copy(int count) {
409 for (int i = 0; i < count; ++i) {
410 gen[genPos++] = orig[origPos++];
411 }
412 }
413
414 byte[] readBytes(int count) {
415 byte[] bytes = new byte[count];
416 for (int i = 0; i < count; ++i) {
417 bytes[i] = orig[origPos++];
418 }
419 return bytes;
420 }
421
422 void writeBytes(byte[] bytes) {
423 for (int i = 0; i < bytes.length; ++i) {
424 gen[genPos++] = bytes[i];
425 }
426 }
427
428 int copyConstantPool(int constantPoolCount)
429 throws UnsupportedEncodingException, IOException {
430 int sdeIndex = -1;
431 // copy const pool index zero not in class file
432 for (int i = 1; i < constantPoolCount; ++i) {
433 int tag = readU1();
434 writeU1(tag);
435 switch (tag) {
436 case 7: // Class
437 case 8: // String
438 if (log.isDebugEnabled())
439 log.debug(i + " copying 2 bytes");
440 copy(2);
441 break;
442 case 9: // Field
443 case 10: // Method
444 case 11: // InterfaceMethod
445 case 3: // Integer
446 case 4: // Float
447 case 12: // NameAndType
448 if (log.isDebugEnabled())
449 log.debug(i + " copying 4 bytes");
450 copy(4);
451 break;
452 case 5: // Long
453 case 6: // Double
454 if (log.isDebugEnabled())
455 log.debug(i + " copying 8 bytes");
456 copy(8);
457 i++;
458 break;
459 case 1: // Utf8
460 int len = readU2();
461 writeU2(len);
462 byte[] utf8 = readBytes(len);
463 String str = new String(utf8, "UTF-8");
464 if (log.isDebugEnabled())
465 log.debug(i + " read class attr -- '" + str + "'");
466 if (str.equals(nameSDE)) {
467 sdeIndex = i;
468 }
469 writeBytes(utf8);
470 break;
471 default:
472 throw new IOException("unexpected tag: " + tag);
473 }
474 }
475 return sdeIndex;
476 }
477
478 void writeUtf8ForSDE() {
479 int len = nameSDE.length();
480 writeU1(1); // Utf8 tag
481 writeU2(len);
482 for (int i = 0; i < len; ++i) {
483 writeU1(nameSDE.charAt(i));
484 }
485 }
486 }
487
488 public static void evaluateNodes(
489 Node.Nodes nodes,
490 SmapStratum s,
491 HashMap innerClassMap,
492 boolean breakAtLF) {
493 try {
494 nodes.visit(new SmapGenVisitor(s, breakAtLF, innerClassMap));
495 } catch (JasperException ex) {
496 }
497 }
498
499 static class SmapGenVisitor extends Node.Visitor {
500
501 private SmapStratum smap;
502 private boolean breakAtLF;
503 private HashMap innerClassMap;
504
505 SmapGenVisitor(SmapStratum s, boolean breakAtLF, HashMap map) {
506 this.smap = s;
507 this.breakAtLF = breakAtLF;
508 this.innerClassMap = map;
509 }
510
511 public void visitBody(Node n) throws JasperException {
512 SmapStratum smapSave = smap;
513 String innerClass = n.getInnerClassName();
514 if (innerClass != null) {
515 this.smap = (SmapStratum) innerClassMap.get(innerClass);
516 }
517 super.visitBody(n);
518 smap = smapSave;
519 }
520
521 public void visit(Node.Declaration n) throws JasperException {
522 doSmapText(n);
523 }
524
525 public void visit(Node.Expression n) throws JasperException {
526 doSmapText(n);
527 }
528
529 public void visit(Node.Scriptlet n) throws JasperException {
530 doSmapText(n);
531 }
532
533 public void visit(Node.IncludeAction n) throws JasperException {
534 doSmap(n);
535 visitBody(n);
536 }
537
538 public void visit(Node.ForwardAction n) throws JasperException {
539 doSmap(n);
540 visitBody(n);
541 }
542
543 public void visit(Node.GetProperty n) throws JasperException {
544 doSmap(n);
545 visitBody(n);
546 }
547
548 public void visit(Node.SetProperty n) throws JasperException {
549 doSmap(n);
550 visitBody(n);
551 }
552
553 public void visit(Node.UseBean n) throws JasperException {
554 doSmap(n);
555 visitBody(n);
556 }
557
558 public void visit(Node.PlugIn n) throws JasperException {
559 doSmap(n);
560 visitBody(n);
561 }
562
563 public void visit(Node.CustomTag n) throws JasperException {
564 doSmap(n);
565 visitBody(n);
566 }
567
568 public void visit(Node.UninterpretedTag n) throws JasperException {
569 doSmap(n);
570 visitBody(n);
571 }
572
573 public void visit(Node.JspElement n) throws JasperException {
574 doSmap(n);
575 visitBody(n);
576 }
577
578 public void visit(Node.JspText n) throws JasperException {
579 doSmap(n);
580 visitBody(n);
581 }
582
583 public void visit(Node.NamedAttribute n) throws JasperException {
584 visitBody(n);
585 }
586
587 public void visit(Node.JspBody n) throws JasperException {
588 doSmap(n);
589 visitBody(n);
590 }
591
592 public void visit(Node.InvokeAction n) throws JasperException {
593 doSmap(n);
594 visitBody(n);
595 }
596
597 public void visit(Node.DoBodyAction n) throws JasperException {
598 doSmap(n);
599 visitBody(n);
600 }
601
602 public void visit(Node.ELExpression n) throws JasperException {
603 doSmap(n);
604 }
605
606 public void visit(Node.TemplateText n) throws JasperException {
607 Mark mark = n.getStart();
608 if (mark == null) {
609 return;
610 }
611
612 //Add the file information
613 String fileName = mark.getFile();
614 smap.addFile(unqualify(fileName), fileName);
615
616 //Add a LineInfo that corresponds to the beginning of this node
617 int iInputStartLine = mark.getLineNumber();
618 int iOutputStartLine = n.getBeginJavaLine();
619 int iOutputLineIncrement = breakAtLF ? 1 : 0;
620 smap.addLineData(iInputStartLine, fileName, 1, iOutputStartLine,
621 iOutputLineIncrement);
622
623 // Output additional mappings in the text
624 java.util.ArrayList extraSmap = n.getExtraSmap();
625
626 if (extraSmap != null) {
627 for (int i = 0; i < extraSmap.size(); i++) {
628 iOutputStartLine += iOutputLineIncrement;
629 smap.addLineData(
630 iInputStartLine + ((Integer) extraSmap.get(i)).intValue(),
631 fileName,
632 1,
633 iOutputStartLine,
634 iOutputLineIncrement);
635 }
636 }
637 }
638
639 private void doSmap(
640 Node n,
641 int inLineCount,
642 int outIncrement,
643 int skippedLines) {
644 Mark mark = n.getStart();
645 if (mark == null) {
646 return;
647 }
648
649 String unqualifiedName = unqualify(mark.getFile());
650 smap.addFile(unqualifiedName, mark.getFile());
651 smap.addLineData(
652 mark.getLineNumber() + skippedLines,
653 mark.getFile(),
654 inLineCount - skippedLines,
655 n.getBeginJavaLine() + skippedLines,
656 outIncrement);
657 }
658
659 private void doSmap(Node n) {
660 doSmap(n, 1, n.getEndJavaLine() - n.getBeginJavaLine(), 0);
661 }
662
663 private void doSmapText(Node n) {
664 String text = n.getText();
665 int index = 0;
666 int next = 0;
667 int lineCount = 1;
668 int skippedLines = 0;
669 boolean slashStarSeen = false;
670 boolean beginning = true;
671
672 // Count lines inside text, but skipping comment lines at the
673 // beginning of the text.
674 while ((next = text.indexOf('\n', index)) > -1) {
675 if (beginning) {
676 String line = text.substring(index, next).trim();
677 if (!slashStarSeen && line.startsWith("/*")) {
678 slashStarSeen = true;
679 }
680 if (slashStarSeen) {
681 skippedLines++;
682 int endIndex = line.indexOf("*/");
683 if (endIndex >= 0) {
684 // End of /* */ comment
685 slashStarSeen = false;
686 if (endIndex < line.length() - 2) {
687
688 skippedLines--;
689 beginning = false;
690 }
691 }
692 } else if (line.length() == 0 || line.startsWith("//")) {
693 skippedLines++;
694 } else {
695 beginning = false;
696 }
697 }
698 lineCount++;
699 index = next + 1;
700 }
701
702 doSmap(n, lineCount, 1, skippedLines);
703 }
704 }
705
706 private static class PreScanVisitor extends Node.Visitor {
707
708 HashMap map = new HashMap();
709
710 public void doVisit(Node n) {
711 String inner = n.getInnerClassName();
712 if (inner != null && !map.containsKey(inner)) {
713 map.put(inner, new SmapStratum("JSP"));
714 }
715 }
716
717 HashMap getMap() {
718 return map;
719 }
720 }
721
722 }