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.jasper.compiler;
19
20 import java.util.ArrayList;
21 import java.util.List;
22
23 /**
24  * Represents the line and file mappings associated with a JSR-045
25  * "stratum".
26  *
27  * @author Jayson Falkner
28  * @author Shawn Bayern
29  */

30 public class SmapStratum {
31
32     //*********************************************************************
33     // Class for storing LineInfo data
34
35     /**
36      * Represents a single LineSection in an SMAP, associated with
37      * a particular stratum.
38      */

39     static class LineInfo {
40         private int inputStartLine = -1;
41         private int outputStartLine = -1;
42         private int lineFileID = 0;
43         private int inputLineCount = 1;
44         private int outputLineIncrement = 1;
45         private boolean lineFileIDSet = false;
46
47         public void setInputStartLine(int inputStartLine) {
48             if (inputStartLine < 0)
49                 throw new IllegalArgumentException(Localizer.getMessage(
50                         "jsp.error.negativeParameter", Integer.valueOf(inputStartLine)));
51             this.inputStartLine = inputStartLine;
52         }
53
54         public void setOutputStartLine(int outputStartLine) {
55             if (outputStartLine < 0)
56                 throw new IllegalArgumentException(Localizer.getMessage(
57                         "jsp.error.negativeParameter", Integer.valueOf(outputStartLine)));
58             this.outputStartLine = outputStartLine;
59         }
60
61         /**
62          * Sets lineFileID.  Should be called only when different from
63          * that of prior LineInfo object (in any given context) or 0
64          * if the current LineInfo has no (logical) predecessor.
65          * <code>LineInfo</code> will print this file number no matter what.
66          *
67          * @param lineFileID The new line file ID
68          */

69         public void setLineFileID(int lineFileID) {
70             if (lineFileID < 0)
71                 throw new IllegalArgumentException(Localizer.getMessage(
72                         "jsp.error.negativeParameter", Integer.valueOf(lineFileID)));
73             this.lineFileID = lineFileID;
74             this.lineFileIDSet = true;
75         }
76
77         public void setInputLineCount(int inputLineCount) {
78             if (inputLineCount < 0)
79                 throw new IllegalArgumentException(Localizer.getMessage(
80                         "jsp.error.negativeParameter", Integer.valueOf(inputLineCount)));
81             this.inputLineCount = inputLineCount;
82         }
83
84         public void setOutputLineIncrement(int outputLineIncrement) {
85             if (outputLineIncrement < 0)
86                 throw new IllegalArgumentException(Localizer.getMessage(
87                         "jsp.error.negativeParameter", Integer.valueOf(outputLineIncrement)));
88             this.outputLineIncrement = outputLineIncrement;
89         }
90
91         public int getMaxOutputLineNumber() {
92             return outputStartLine + inputLineCount * outputLineIncrement;
93         }
94
95         /**
96          * @return the current LineInfo as a String, print all values only when
97          *         appropriate (but LineInfoID if and only if it's been
98          *         specified, as its necessity is sensitive to context).
99          */

100         public String getString() {
101             if (inputStartLine == -1 || outputStartLine == -1)
102                 throw new IllegalStateException();
103             StringBuilder out = new StringBuilder();
104             out.append(inputStartLine);
105             if (lineFileIDSet)
106                 out.append("#" + lineFileID);
107             if (inputLineCount != 1)
108                 out.append("," + inputLineCount);
109             out.append(":" + outputStartLine);
110             if (outputLineIncrement != 1)
111                 out.append("," + outputLineIncrement);
112             out.append('\n');
113             return out.toString();
114         }
115
116         @Override
117         public String toString() {
118             return getString();
119         }
120     }
121
122     //*********************************************************************
123     // Private state
124
125     private final List<String> fileNameList = new ArrayList<>();
126     private final List<String> filePathList = new ArrayList<>();
127     private final List<LineInfo> lineData = new ArrayList<>();
128     private int lastFileID;
129     // .java file
130     private String outputFileName;
131     // .class file
132     private String classFileName;
133
134     //*********************************************************************
135     // Methods to add mapping information
136
137     /**
138      * Adds record of a new file, by filename.
139      *
140      * @param filename the filename to add, unqualified by path.
141      */

142     public void addFile(String filename) {
143         addFile(filename, filename);
144     }
145
146     /**
147      * Adds record of a new file, by filename and path.  The path
148      * may be relative to a source compilation path.
149      *
150      * @param filename the filename to add, unqualified by path
151      * @param filePath the path for the filename, potentially relative
152      *                 to a source compilation path
153      */

154     public void addFile(String filename, String filePath) {
155         int pathIndex = filePathList.indexOf(filePath);
156         if (pathIndex == -1) {
157             fileNameList.add(filename);
158             filePathList.add(filePath);
159         }
160     }
161
162     /**
163      * Combines consecutive LineInfos wherever possible
164      */

165     public void optimizeLineSection() {
166
167 /* Some debugging code
168         for (int i = 0; i < lineData.size(); i++) {
169             LineInfo li = (LineInfo)lineData.get(i);
170             System.out.print(li.toString());
171         }
172 */

173         //Incorporate each LineInfo into the previous LineInfo's
174         //outputLineIncrement, if possible
175         int i = 0;
176         while (i < lineData.size() - 1) {
177             LineInfo li = lineData.get(i);
178             LineInfo liNext = lineData.get(i + 1);
179             if (!liNext.lineFileIDSet
180                 && liNext.inputStartLine == li.inputStartLine
181                 && liNext.inputLineCount == 1
182                 && li.inputLineCount == 1
183                 && liNext.outputStartLine
184                     == li.outputStartLine
185                         + li.inputLineCount * li.outputLineIncrement) {
186                 li.setOutputLineIncrement(
187                     liNext.outputStartLine
188                         - li.outputStartLine
189                         + liNext.outputLineIncrement);
190                 lineData.remove(i + 1);
191             } else {
192                 i++;
193             }
194         }
195
196         //Incorporate each LineInfo into the previous LineInfo's
197         //inputLineCount, if possible
198         i = 0;
199         while (i < lineData.size() - 1) {
200             LineInfo li = lineData.get(i);
201             LineInfo liNext = lineData.get(i + 1);
202             if (!liNext.lineFileIDSet
203                 && liNext.inputStartLine == li.inputStartLine + li.inputLineCount
204                 && liNext.outputLineIncrement == li.outputLineIncrement
205                 && liNext.outputStartLine
206                     == li.outputStartLine
207                         + li.inputLineCount * li.outputLineIncrement) {
208                 li.setInputLineCount(li.inputLineCount + liNext.inputLineCount);
209                 lineData.remove(i + 1);
210             } else {
211                 i++;
212             }
213         }
214     }
215
216     /**
217      * Adds complete information about a simple line mapping.  Specify
218      * all the fields in this method; the back-end machinery takes care
219      * of printing only those that are necessary in the final SMAP.
220      * (My view is that fields are optional primarily for spatial efficiency,
221      * not for programmer convenience.  Could always add utility methods
222      * later.)
223      *
224      * @param inputStartLine starting line in the source file
225      *        (SMAP <code>InputStartLine</code>)
226      * @param inputFileName the filepath (or name) from which the input comes
227      *        (yields SMAP <code>LineFileID</code>)  Use unqualified names
228      *        carefully, and only when they uniquely identify a file.
229      * @param inputLineCount the number of lines in the input to map
230      *        (SMAP <code>LineFileCount</code>)
231      * @param outputStartLine starting line in the output file
232      *        (SMAP <code>OutputStartLine</code>)
233      * @param outputLineIncrement number of output lines to map to each
234      *        input line (SMAP <code>OutputLineIncrement</code>).  <i>Given the
235      *        fact that the name starts with "output", I continuously have
236      *        the subconscious urge to call this field
237      *        <code>OutputLineExcrement</code>.</i>
238      */

239     public void addLineData(
240         int inputStartLine,
241         String inputFileName,
242         int inputLineCount,
243         int outputStartLine,
244         int outputLineIncrement) {
245         // check the input - what are you doing here??
246         int fileIndex = filePathList.indexOf(inputFileName);
247         if (fileIndex == -1) // still
248             throw new IllegalArgumentException(
249                 "inputFileName: " + inputFileName);
250
251         //Jasper incorrectly SMAPs certain Nodes, giving them an
252         //outputStartLine of 0.  This can cause a fatal error in
253         //optimizeLineSection, making it impossible for Jasper to
254         //compile the JSP.  Until we can fix the underlying
255         //SMAPping problem, we simply ignore the flawed SMAP entries.
256         if (outputStartLine == 0)
257             return;
258
259         // build the LineInfo
260         LineInfo li = new LineInfo();
261         li.setInputStartLine(inputStartLine);
262         li.setInputLineCount(inputLineCount);
263         li.setOutputStartLine(outputStartLine);
264         li.setOutputLineIncrement(outputLineIncrement);
265         if (fileIndex != lastFileID)
266             li.setLineFileID(fileIndex);
267         lastFileID = fileIndex;
268
269         // save it
270         lineData.add(li);
271     }
272
273
274     public void addLineInfo(LineInfo li) {
275         lineData.add(li);
276     }
277
278
279     public void setOutputFileName(String outputFileName) {
280         this.outputFileName = outputFileName;
281     }
282
283
284     public void setClassFileName(String classFileName) {
285         this.classFileName = classFileName;
286     }
287
288
289     public String getClassFileName() {
290         return classFileName;
291     }
292
293
294     //*********************************************************************
295     // Methods to retrieve information
296
297     @Override
298     public String toString() {
299         return getSmapStringInternal();
300     }
301
302
303     public String getSmapString() {
304
305         if (outputFileName == null) {
306             throw new IllegalStateException();
307         }
308
309         return getSmapStringInternal();
310     }
311
312
313     private String getSmapStringInternal() {
314         StringBuilder out = new StringBuilder();
315
316         // start the SMAP
317         out.append("SMAP\n");
318         out.append(outputFileName + '\n');
319         out.append("JSP\n");
320
321         // print StratumSection
322         out.append("*S JSP\n");
323
324         // print FileSection
325         out.append("*F\n");
326         int bound = fileNameList.size();
327         for (int i = 0; i < bound; i++) {
328             if (filePathList.get(i) != null) {
329                 out.append("+ " + i + " " + fileNameList.get(i) + "\n");
330                 // Source paths must be relative, not absolute, so we
331                 // remove the leading "/"if one exists.
332                 String filePath = filePathList.get(i);
333                 if (filePath.startsWith("/")) {
334                     filePath = filePath.substring(1);
335                 }
336                 out.append(filePath + "\n");
337             } else {
338                 out.append(i + " " + fileNameList.get(i) + "\n");
339             }
340         }
341
342         // print LineSection
343         out.append("*L\n");
344         bound = lineData.size();
345         for (int i = 0; i < bound; i++) {
346             LineInfo li = lineData.get(i);
347             out.append(li.getString());
348         }
349
350         // end the SMAP
351         out.append("*E\n");
352
353         return out.toString();
354     }
355
356
357     public SmapInput getInputLineNumber(int outputLineNumber) {
358         // For a given Java line number, provide the associated line number
359         // in the JSP/tag source
360         int inputLineNumber = -1;
361         int fileId = 0;
362
363         for (LineInfo lineInfo : lineData) {
364             if (lineInfo.lineFileIDSet) {
365                 fileId = lineInfo.lineFileID;
366             }
367             if (lineInfo.outputStartLine > outputLineNumber) {
368                 // Didn't find match
369                 break;
370             }
371
372             if (lineInfo.getMaxOutputLineNumber() < outputLineNumber) {
373                 // Too early
374                 continue;
375             }
376
377             // This is the match
378             int inputOffset =
379                     (outputLineNumber - lineInfo.outputStartLine) / lineInfo.outputLineIncrement;
380
381             inputLineNumber = lineInfo.inputStartLine + inputOffset;
382         }
383
384         return new SmapInput(filePathList.get(fileId), inputLineNumber);
385     }
386 }
387