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