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