001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.jasper.compiler;
018:
019: import java.io.File;
020: import java.io.FileInputStream;
021: import java.io.FileNotFoundException;
022: import java.io.FileOutputStream;
023: import java.io.IOException;
024: import java.io.OutputStreamWriter;
025: import java.io.PrintWriter;
026: import java.io.UnsupportedEncodingException;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.Map;
030:
031: import org.apache.jasper.JasperException;
032: import org.apache.jasper.JspCompilationContext;
033:
034: /**
035: * Contains static utilities for generating SMAP data based on the
036: * current version of Jasper.
037: *
038: * @author Jayson Falkner
039: * @author Shawn Bayern
040: * @author Robert Field (inner SDEInstaller class)
041: * @author Mark Roth
042: * @author Kin-man Chung
043: */
044: public class SmapUtil {
045:
046: private static final boolean verbose = false;
047:
048: //*********************************************************************
049: // Constants
050:
051: public static final String SMAP_ENCODING = "UTF-8";
052:
053: //*********************************************************************
054: // Public entry points
055:
056: /**
057: * Generates an appropriate SMAP representing the current compilation
058: * context. (JSR-045.)
059: *
060: * @param ctxt Current compilation context
061: * @param pageNodes The current JSP page
062: * @return a SMAP for the page
063: */
064: public static String[] generateSmap(JspCompilationContext ctxt,
065: Node.Nodes pageNodes) throws IOException {
066:
067: // Scan the nodes for presence of Jasper generated inner classes
068: PreScanVisitor psVisitor = new PreScanVisitor();
069: try {
070: pageNodes.visit(psVisitor);
071: } catch (JasperException ex) {
072: }
073: HashMap map = psVisitor.getMap();
074:
075: // set up our SMAP generator
076: SmapGenerator g = new SmapGenerator();
077:
078: /** Disable reading of input SMAP because:
079: 1. There is a bug here: getRealPath() is null if .jsp is in a jar
080: Bugzilla 14660.
081: 2. Mappings from other sources into .jsp files are not supported.
082: TODO: fix 1. if 2. is not true.
083: // determine if we have an input SMAP
084: String smapPath = inputSmapPath(ctxt.getRealPath(ctxt.getJspFile()));
085: File inputSmap = new File(smapPath);
086: if (inputSmap.exists()) {
087: byte[] embeddedSmap = null;
088: byte[] subSmap = SDEInstaller.readWhole(inputSmap);
089: String subSmapString = new String(subSmap, SMAP_ENCODING);
090: g.addSmap(subSmapString, "JSP");
091: }
092: **/
093:
094: // now, assemble info about our own stratum (JSP) using JspLineMap
095: SmapStratum s = new SmapStratum("JSP");
096:
097: g.setOutputFileName(unqualify(ctxt.getServletJavaFileName()));
098:
099: // Map out Node.Nodes
100: evaluateNodes(pageNodes, s, map, ctxt.getOptions()
101: .getMappedFile());
102: s.optimizeLineSection();
103: g.addStratum(s, true);
104:
105: if (ctxt.getOptions().isSmapDumped()) {
106: File outSmap = new File(ctxt.getClassFileName() + ".smap");
107: PrintWriter so = new PrintWriter(new OutputStreamWriter(
108: new FileOutputStream(outSmap), 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
128: .setOutputFileName(unqualify(ctxt
129: .getServletJavaFileName()));
130: g.addStratum(s, true);
131:
132: String innerClassFileName = classFileName.substring(0,
133: classFileName.indexOf(".class"))
134: + '$' + innerClass + ".class";
135: if (ctxt.getOptions().isSmapDumped()) {
136: File outSmap = new File(innerClassFileName + ".smap");
137: PrintWriter so = new PrintWriter(
138: new OutputStreamWriter(new FileOutputStream(
139: outSmap), 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) throws IOException {
152: if (smap == null) {
153: return;
154: }
155:
156: for (int i = 0; i < smap.length; i += 2) {
157: File outServlet = new File(smap[i]);
158: SDEInstaller.install(outServlet, smap[i + 1].getBytes());
159: }
160: }
161:
162: //*********************************************************************
163: // Private utilities
164:
165: /**
166: * Returns an unqualified version of the given file path.
167: */
168: private static String unqualify(String path) {
169: path = path.replace('\\', '/');
170: return path.substring(path.lastIndexOf('/') + 1);
171: }
172:
173: /**
174: * Returns a file path corresponding to a potential SMAP input
175: * for the given compilation input (JSP file).
176: */
177: private static String inputSmapPath(String path) {
178: return path.substring(0, path.lastIndexOf('.') + 1) + "smap";
179: }
180:
181: //*********************************************************************
182: // Installation logic (from Robert Field, JSR-045 spec lead)
183: private static class SDEInstaller {
184:
185: static final String nameSDE = "SourceDebugExtension";
186:
187: byte[] orig;
188: byte[] sdeAttr;
189: byte[] gen;
190:
191: int origPos = 0;
192: int genPos = 0;
193:
194: int sdeIndex;
195:
196: public static void main(String[] args) throws IOException {
197: if (args.length == 2) {
198: install(new File(args[0]), new File(args[1]));
199: } else if (args.length == 3) {
200: install(new File(args[0]), new File(args[1]), new File(
201: args[2]));
202: } else {
203: System.err
204: .println("Usage: <command> <input class file> "
205: + "<attribute file> <output class file name>\n"
206: + "<command> <input/output class file> <attribute file>");
207: }
208: }
209:
210: static void install(File inClassFile, File attrFile,
211: File outClassFile) throws IOException {
212: new SDEInstaller(inClassFile, attrFile, outClassFile);
213: }
214:
215: static void install(File inOutClassFile, File attrFile)
216: throws IOException {
217: File tmpFile = new File(inOutClassFile.getPath() + "tmp");
218: new SDEInstaller(inOutClassFile, attrFile, tmpFile);
219: if (!inOutClassFile.delete()) {
220: throw new IOException("inOutClassFile.delete() failed");
221: }
222: if (!tmpFile.renameTo(inOutClassFile)) {
223: throw new IOException(
224: "tmpFile.renameTo(inOutClassFile) failed");
225: }
226: }
227:
228: static void install(File classFile, byte[] smap)
229: throws IOException {
230: File tmpFile = new File(classFile.getPath() + "tmp");
231: new SDEInstaller(classFile, smap, tmpFile);
232: if (!classFile.delete()) {
233: throw new IOException("classFile.delete() failed");
234: }
235: if (!tmpFile.renameTo(classFile)) {
236: throw new IOException(
237: "tmpFile.renameTo(classFile) failed");
238: }
239: }
240:
241: SDEInstaller(File inClassFile, byte[] sdeAttr, File outClassFile)
242: throws IOException {
243: if (!inClassFile.exists()) {
244: throw new FileNotFoundException("no such file: "
245: + inClassFile);
246: }
247:
248: this .sdeAttr = sdeAttr;
249: // get the bytes
250: orig = readWhole(inClassFile);
251: gen = new byte[orig.length + sdeAttr.length + 100];
252:
253: // do it
254: addSDE();
255:
256: // write result
257: FileOutputStream outStream = new FileOutputStream(
258: 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 (verbose) {
285: System.out.println("constant pool count: "
286: + constantPoolCount);
287: }
288: writeU2(constantPoolCount);
289:
290: // copy old constant pool return index of SDE symbol, if found
291: sdeIndex = copyConstantPool(constantPoolCount);
292: if (sdeIndex < 0) {
293: // if "SourceDebugExtension" symbol not there add it
294: writeUtf8ForSDE();
295:
296: // increment the countantPoolCount
297: sdeIndex = constantPoolCount;
298: ++constantPoolCount;
299: randomAccessWriteU2(constantPoolCountPos,
300: constantPoolCount);
301:
302: if (verbose) {
303: System.out
304: .println("SourceDebugExtension not found, installed at: "
305: + sdeIndex);
306: }
307: } else {
308: if (verbose) {
309: System.out
310: .println("SourceDebugExtension found at: "
311: + sdeIndex);
312: }
313: }
314: copy(2 + 2 + 2); // access, this, super
315: int interfaceCount = readU2();
316: writeU2(interfaceCount);
317: if (verbose) {
318: System.out.println("interfaceCount: " + interfaceCount);
319: }
320: copy(interfaceCount * 2);
321: copyMembers(); // fields
322: copyMembers(); // methods
323: int attrCountPos = genPos;
324: int attrCount = readU2();
325: writeU2(attrCount);
326: if (verbose) {
327: System.out.println("class attrCount: " + attrCount);
328: }
329: // copy the class attributes, return true if SDE attr found (not copied)
330: if (!copyAttrs(attrCount)) {
331: // we will be adding SDE and it isn't already counted
332: ++attrCount;
333: randomAccessWriteU2(attrCountPos, attrCount);
334: if (verbose) {
335: System.out.println("class attrCount incremented");
336: }
337: }
338: writeAttrForSDE(sdeIndex);
339: }
340:
341: void copyMembers() {
342: int count = readU2();
343: writeU2(count);
344: if (verbose) {
345: System.out.println("members count: " + count);
346: }
347: for (int i = 0; i < count; ++i) {
348: copy(6); // access, name, descriptor
349: int attrCount = readU2();
350: writeU2(attrCount);
351: if (verbose) {
352: System.out.println("member attr count: "
353: + attrCount);
354: }
355: copyAttrs(attrCount);
356: }
357: }
358:
359: boolean copyAttrs(int attrCount) {
360: boolean sdeFound = false;
361: for (int i = 0; i < attrCount; ++i) {
362: int nameIndex = readU2();
363: // don't write old SDE
364: if (nameIndex == sdeIndex) {
365: sdeFound = true;
366: if (verbose) {
367: System.out.println("SDE attr found");
368: }
369: } else {
370: writeU2(nameIndex); // name
371: int len = readU4();
372: writeU4(len);
373: copy(len);
374: if (verbose) {
375: System.out.println("attr len: " + len);
376: }
377: }
378: }
379: return sdeFound;
380: }
381:
382: void writeAttrForSDE(int index) {
383: writeU2(index);
384: writeU4(sdeAttr.length);
385: for (int i = 0; i < sdeAttr.length; ++i) {
386: writeU1(sdeAttr[i]);
387: }
388: }
389:
390: void randomAccessWriteU2(int pos, int val) {
391: int savePos = genPos;
392: genPos = pos;
393: writeU2(val);
394: genPos = savePos;
395: }
396:
397: int readU1() {
398: return ((int) orig[origPos++]) & 0xFF;
399: }
400:
401: int readU2() {
402: int res = readU1();
403: return (res << 8) + readU1();
404: }
405:
406: int readU4() {
407: int res = readU2();
408: return (res << 16) + readU2();
409: }
410:
411: void writeU1(int val) {
412: gen[genPos++] = (byte) val;
413: }
414:
415: void writeU2(int val) {
416: writeU1(val >> 8);
417: writeU1(val & 0xFF);
418: }
419:
420: void writeU4(int val) {
421: writeU2(val >> 16);
422: writeU2(val & 0xFFFF);
423: }
424:
425: void copy(int count) {
426: for (int i = 0; i < count; ++i) {
427: gen[genPos++] = orig[origPos++];
428: }
429: }
430:
431: byte[] readBytes(int count) {
432: byte[] bytes = new byte[count];
433: for (int i = 0; i < count; ++i) {
434: bytes[i] = orig[origPos++];
435: }
436: return bytes;
437: }
438:
439: void writeBytes(byte[] bytes) {
440: for (int i = 0; i < bytes.length; ++i) {
441: gen[genPos++] = bytes[i];
442: }
443: }
444:
445: int copyConstantPool(int constantPoolCount)
446: throws UnsupportedEncodingException, IOException {
447: int sdeIndex = -1;
448: // copy const pool index zero not in class file
449: for (int i = 1; i < constantPoolCount; ++i) {
450: int tag = readU1();
451: writeU1(tag);
452: switch (tag) {
453: case 7: // Class
454: case 8: // String
455: if (verbose) {
456: System.out.println(i + " copying 2 bytes");
457: }
458: copy(2);
459: break;
460: case 9: // Field
461: case 10: // Method
462: case 11: // InterfaceMethod
463: case 3: // Integer
464: case 4: // Float
465: case 12: // NameAndType
466: if (verbose) {
467: System.out.println(i + " copying 4 bytes");
468: }
469: copy(4);
470: break;
471: case 5: // Long
472: case 6: // Double
473: if (verbose) {
474: System.out.println(i + " copying 8 bytes");
475: }
476: copy(8);
477: i++;
478: break;
479: case 1: // Utf8
480: int len = readU2();
481: writeU2(len);
482: byte[] utf8 = readBytes(len);
483: String str = new String(utf8, "UTF-8");
484: if (verbose) {
485: System.out.println(i + " read class attr -- '"
486: + str + "'");
487: }
488: if (str.equals(nameSDE)) {
489: sdeIndex = i;
490: }
491: writeBytes(utf8);
492: break;
493: default:
494: throw new IOException("unexpected tag: " + tag);
495: }
496: }
497: return sdeIndex;
498: }
499:
500: void writeUtf8ForSDE() {
501: int len = nameSDE.length();
502: writeU1(1); // Utf8 tag
503: writeU2(len);
504: for (int i = 0; i < len; ++i) {
505: writeU1(nameSDE.charAt(i));
506: }
507: }
508: }
509:
510: public static void evaluateNodes(Node.Nodes nodes, SmapStratum s,
511: HashMap innerClassMap, boolean breakAtLF) {
512: try {
513: nodes
514: .visit(new SmapGenVisitor(s, breakAtLF,
515: innerClassMap));
516: } catch (JasperException ex) {
517: }
518: }
519:
520: static class SmapGenVisitor extends Node.Visitor {
521:
522: private SmapStratum smap;
523: private boolean breakAtLF;
524: private HashMap innerClassMap;
525:
526: SmapGenVisitor(SmapStratum s, boolean breakAtLF, HashMap map) {
527: this .smap = s;
528: this .breakAtLF = breakAtLF;
529: this .innerClassMap = map;
530: }
531:
532: public void visitBody(Node n) throws JasperException {
533: SmapStratum smapSave = smap;
534: String innerClass = n.getInnerClassName();
535: if (innerClass != null) {
536: this .smap = (SmapStratum) innerClassMap.get(innerClass);
537: }
538: super .visitBody(n);
539: smap = smapSave;
540: }
541:
542: public void visit(Node.Declaration n) throws JasperException {
543: doSmapText(n);
544: }
545:
546: public void visit(Node.Expression n) throws JasperException {
547: doSmapText(n);
548: }
549:
550: public void visit(Node.Scriptlet n) throws JasperException {
551: doSmapText(n);
552: }
553:
554: public void visit(Node.IncludeAction n) throws JasperException {
555: doSmap(n);
556: visitBody(n);
557: }
558:
559: public void visit(Node.ForwardAction n) throws JasperException {
560: doSmap(n);
561: visitBody(n);
562: }
563:
564: public void visit(Node.GetProperty n) throws JasperException {
565: doSmap(n);
566: visitBody(n);
567: }
568:
569: public void visit(Node.SetProperty n) throws JasperException {
570: doSmap(n);
571: visitBody(n);
572: }
573:
574: public void visit(Node.UseBean n) throws JasperException {
575: doSmap(n);
576: visitBody(n);
577: }
578:
579: public void visit(Node.PlugIn n) throws JasperException {
580: doSmap(n);
581: visitBody(n);
582: }
583:
584: public void visit(Node.CustomTag n) throws JasperException {
585: doSmap(n);
586: visitBody(n);
587: }
588:
589: public void visit(Node.UninterpretedTag n)
590: throws JasperException {
591: doSmap(n);
592: visitBody(n);
593: }
594:
595: public void visit(Node.JspElement n) throws JasperException {
596: doSmap(n);
597: visitBody(n);
598: }
599:
600: public void visit(Node.JspText n) throws JasperException {
601: doSmap(n);
602: visitBody(n);
603: }
604:
605: public void visit(Node.NamedAttribute n) throws JasperException {
606: visitBody(n);
607: }
608:
609: public void visit(Node.JspBody n) throws JasperException {
610: doSmap(n);
611: visitBody(n);
612: }
613:
614: public void visit(Node.InvokeAction n) throws JasperException {
615: doSmap(n);
616: visitBody(n);
617: }
618:
619: public void visit(Node.DoBodyAction n) throws JasperException {
620: doSmap(n);
621: visitBody(n);
622: }
623:
624: public void visit(Node.ELExpression n) throws JasperException {
625: doSmap(n);
626: }
627:
628: public void visit(Node.TemplateText n) throws JasperException {
629: Mark mark = n.getStart();
630: if (mark == null) {
631: return;
632: }
633:
634: //Add the file information
635: String fileName = mark.getFile();
636: smap.addFile(unqualify(fileName), fileName);
637:
638: //Add a LineInfo that corresponds to the beginning of this node
639: int iInputStartLine = mark.getLineNumber();
640: int iOutputStartLine = n.getBeginJavaLine();
641: int iOutputLineIncrement = breakAtLF ? 1 : 0;
642: smap.addLineData(iInputStartLine, fileName, 1,
643: iOutputStartLine, iOutputLineIncrement);
644:
645: // Output additional mappings in the text
646: java.util.ArrayList extraSmap = n.getExtraSmap();
647:
648: if (extraSmap != null) {
649: for (int i = 0; i < extraSmap.size(); i++) {
650: iOutputStartLine += iOutputLineIncrement;
651: smap.addLineData(iInputStartLine
652: + ((Integer) extraSmap.get(i)).intValue(),
653: fileName, 1, iOutputStartLine,
654: iOutputLineIncrement);
655: }
656: }
657: }
658:
659: private void doSmap(Node n, int inLineCount, int outIncrement,
660: int skippedLines) {
661: Mark mark = n.getStart();
662: if (mark == null) {
663: return;
664: }
665:
666: String unqualifiedName = unqualify(mark.getFile());
667: smap.addFile(unqualifiedName, mark.getFile());
668: smap.addLineData(mark.getLineNumber() + skippedLines, mark
669: .getFile(), inLineCount - skippedLines, n
670: .getBeginJavaLine()
671: + skippedLines, outIncrement);
672: }
673:
674: private void doSmap(Node n) {
675: doSmap(n, 1, n.getEndJavaLine() - n.getBeginJavaLine(), 0);
676: }
677:
678: private void doSmapText(Node n) {
679: String text = n.getText();
680: int index = 0;
681: int next = 0;
682: int lineCount = 1;
683: int skippedLines = 0;
684: boolean slashStarSeen = false;
685: boolean beginning = true;
686:
687: // Count lines inside text, but skipping comment lines at the
688: // beginning of the text.
689: while ((next = text.indexOf('\n', index)) > -1) {
690: if (beginning) {
691: String line = text.substring(index, next).trim();
692: if (!slashStarSeen && line.startsWith("/*")) {
693: slashStarSeen = true;
694: }
695: if (slashStarSeen) {
696: skippedLines++;
697: int endIndex = line.indexOf("*/");
698: if (endIndex >= 0) {
699: // End of /* */ comment
700: slashStarSeen = false;
701: if (endIndex < line.length() - 2) {
702: // Some executable code after comment
703: skippedLines--;
704: beginning = false;
705: }
706: }
707: } else if (line.length() == 0
708: || line.startsWith("//")) {
709: skippedLines++;
710: } else {
711: beginning = false;
712: }
713: }
714: lineCount++;
715: index = next + 1;
716: }
717:
718: doSmap(n, lineCount, 1, skippedLines);
719: }
720: }
721:
722: private static class PreScanVisitor extends Node.Visitor {
723:
724: HashMap map = new HashMap();
725:
726: public void doVisit(Node n) {
727: String inner = n.getInnerClassName();
728: if (inner != null && !map.containsKey(inner)) {
729: map.put(inner, new SmapStratum("JSP"));
730: }
731: }
732:
733: HashMap getMap() {
734: return map;
735: }
736: }
737: }
|