View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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          // Scan the nodes for presence of Jasper generated inner classes
66          PreScanVisitor psVisitor = new PreScanVisitor();
67          try {
68              pageNodes.visit(psVisitor);
69          } catch (JasperException ex) {
70          }
71          HashMap map = psVisitor.getMap();
72  
73          // set up our SMAP generator
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          // now, assemble info about our own stratum (JSP) using JspLineMap
93          SmapStratum s = new SmapStratum("JSP");
94  
95          g.setOutputFileName(unqualify(ctxt.getServletJavaFileName()));
96  
97          // Map out Node.Nodes
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                                 // Some executable code after comment
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 }