001: ///////////////////////////////
002: // Makumba, Makumba tag library
003: // Copyright (C) 2000-2003 http://www.makumba.org
004: //
005: // This library is free software; you can redistribute it and/or
006: // modify it under the terms of the GNU Lesser General Public
007: // License as published by the Free Software Foundation; either
008: // version 2.1 of the License, or (at your option) any later version.
009: //
010: // This library is distributed in the hope that it will be useful,
011: // but WITHOUT ANY WARRANTY; without even the implied warranty of
012: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: // Lesser General Public License for more details.
014: //
015: // You should have received a copy of the GNU Lesser General Public
016: // License along with this library; if not, write to the Free Software
017: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: //
019: // -------------
020: // $Id: SourceSyntaxPoints.java 1964 2007-10-29 23:40:16Z cristian_bogdan $
021: // $Name$
022: /////////////////////////////////////
023:
024: package org.makumba.analyser.engine;
025:
026: import java.io.BufferedReader;
027: import java.io.File;
028: import java.io.FileNotFoundException;
029: import java.io.FileReader;
030: import java.io.IOException;
031: import java.util.ArrayList;
032: import java.util.Collections;
033: import java.util.Iterator;
034: import java.util.TreeSet;
035: import java.util.regex.Matcher;
036: import java.util.regex.Pattern;
037:
038: import org.makumba.ProgrammerError;
039:
040: /**
041: * The collection of syntax points in a source file gathered from a source analysis.
042: *
043: * @author Cristian Bogdan
044: * @version $Id: SourceSyntaxPoints.java 1964 2007-10-29 23:40:16Z cristian_bogdan $
045: */
046: public class SourceSyntaxPoints {
047: static interface PreprocessorClient {
048: public void treatInclude(int position, String includeDirective,
049: SourceSyntaxPoints host);
050:
051: public Pattern[] getCommentPatterns();
052:
053: public String[] getCommentPatternNames();
054:
055: public Pattern[] getLiteralPatterns();
056:
057: public String[] getLiteralPatternNames();
058:
059: public Pattern getIncludePattern();
060:
061: public String getIncludePatternName();
062: }
063:
064: /** The path of the analyzed file */
065: File file;
066:
067: public String toString() {
068: return file.toString() + " " + offset;
069: }
070:
071: PreprocessorClient client;
072:
073: /** The timestamp of the analyzed file. If it is found newer on disk, the cached object is discarded. */
074: long lastChanged;
075:
076: /** The syntax points, sorted */
077: TreeSet syntaxPoints = new TreeSet();
078:
079: /** The line beginnings, added in occuring order */
080: ArrayList lineBeginnings = new ArrayList();
081:
082: /** The file beginnings, added in occuring order. When file F includes file I, I begins, then F begins again */
083: ArrayList fileBeginningIndexes = new ArrayList();
084:
085: ArrayList fileBeginnings = new ArrayList();
086:
087: /** The original text */
088: String originalText;
089:
090: /** The content, where comments are replaced by whitespace and include directives are replaced by included text */
091: String content;
092:
093: /** offset in the including file */
094: int offset;
095:
096: /** the parent, in which we are included */
097: SourceSyntaxPoints parent;
098:
099: /**
100: * The constructor inserts syntax points (begin and end) for every line in a text, and does preprocessing
101: * (uncomments text, includes other text). Most syntax colourers need to do specific operations at every line.
102: *
103: * @param f
104: * the parsed file
105: * @param cl
106: * the preprocessor
107: */
108: public SourceSyntaxPoints(File f, PreprocessorClient cl) {
109: this (f, cl, null, null, 0);
110: }
111:
112: /**
113: * The constructor inserts syntax points (begin and end) for every line in a text, and does preprocessing
114: * (uncomments text, includes other text). Most syntax colourers need to do specific operations at every line.
115: *
116: * @param f
117: * the parsed file
118: * @param cl
119: * the preprocessor
120: * @param parent
121: * the parent in which we are included
122: * @param includeDirective
123: * the include directive
124: * @param offset
125: * the offset at which the inclusion takes place
126: */
127:
128: public SourceSyntaxPoints(File f, PreprocessorClient cl,
129: SourceSyntaxPoints parent, String includeDirective,
130: int offset) {
131: this .offset = offset;
132: this .parent = parent;
133: file = f;
134: client = cl;
135:
136: lastChanged = file.lastModified();
137:
138: content = originalText = readFile(includeDirective);
139:
140: fileBeginningIndexes.add(new Integer(0));
141: fileBeginnings.add(this );
142:
143: findLineBreaks();
144:
145: // ignore literals from the text
146: if (client.getLiteralPatterns() != null) {
147: for (int i = 0; i < client.getLiteralPatterns().length; i++) {
148: treatLiterals(i);
149: }
150: }
151: // remove comments from the text
152: if (client.getCommentPatterns() != null) {
153: for (int i = 0; i < client.getCommentPatterns().length; i++) {
154: unComment(i);
155: }
156: }
157:
158: if (client.getIncludePattern() != null)
159: include();
160: }
161:
162: /**
163: * Finds the line breaks in the string
164: */
165: void findLineBreaks() {
166: int start = 0;
167: int line = 1;
168:
169: int max = originalText.length();
170: for (int i = 0; i < max; i++) {
171: // if found "\r\n" then treat together as one line break.
172: if (originalText.charAt(i) == '\r') {
173: if (i + 1 < max && originalText.charAt(i + 1) == '\n')
174: i++;
175: } else if (originalText.charAt(i) != '\n')
176: continue;
177:
178: // found a linebreak
179: addSyntaxPointsLine(start, i, "TextLine", new Integer(line));
180: start = i + 1;
181: line++;
182: }
183:
184: // treat the last line (not ending with '\n')
185: if (start < max)
186: addSyntaxPointsLine(start, max, "TextLine", new Integer(
187: line));
188: }
189:
190: /**
191: * Gets the text of the line n
192: *
193: * @param n
194: * the line number
195: * @return A String containing the text at the indicated line
196: */
197: public String getLineText(int n) {
198: SyntaxPoint line = (SyntaxPoint) lineBeginnings.get(n - 1);
199: if (n == lineBeginnings.size())
200: return originalText.substring(line.getOriginalPosition());
201:
202: SyntaxPoint nextline = (SyntaxPoint) lineBeginnings.get(n);
203:
204: return originalText.substring(line.getOriginalPosition(),
205: nextline.getOriginalPosition() - 1);
206: }
207:
208: /**
209: * Includes a file into the current content
210: */
211: void include() {
212: while (true) {
213: Matcher m = client.getIncludePattern().matcher(content);
214: if (!m.find())
215: return;
216: client.treatInclude(m.start(), content.substring(m.start(),
217: m.end()), this );
218: }
219: }
220:
221: /**
222: * Includes the given file, at the given position, included by the given directive
223: *
224: * @param f
225: * the file to be included
226: * @param position
227: * the position of the included file
228: * @param includeDirective
229: * the directive calling for the inclusion
230: */
231: public void include(File f, int position, String includeDirective) {
232: SourceSyntaxPoints sf = new SourceSyntaxPoints(f, client, this ,
233: includeDirective, position + offset);
234:
235: // FIXME: add a syntax point for the include
236: // record the next position in this file for @include, also the text
237:
238: int delta = sf.getContent().length()
239: - includeDirective.length();
240:
241: StringBuffer sb = new StringBuffer();
242: sb.append(content.substring(0, position)).
243: // add the content of the file
244: append(sf.getContent()).
245: // but remove the include directive
246: append(
247: content.substring(position
248: + includeDirective.length()));
249:
250: content = sb.toString();
251:
252: // we move the position of all SyntaxPoints that occur after the include
253: for (Iterator i = syntaxPoints.iterator(); i.hasNext();) {
254: SyntaxPoint sp = (SyntaxPoint) i.next();
255: if (sp.position > position + offset)
256: sp.moveByInclude(delta);
257: }
258:
259: // we add a fileBeginning
260: int n = fileBeginningIndexes.size() - 1;
261: // replace the one at the end, for some reason
262: if (((Integer) fileBeginningIndexes.get(n)).intValue() == position)
263: fileBeginnings.set(n, sf);
264: else {
265: // add one at the end
266: fileBeginningIndexes.add(new Integer(position));
267: fileBeginnings.add(sf);
268: }
269: fileBeginningIndexes.add(new Integer(position + delta));
270: fileBeginnings.add(this );
271: }
272:
273: /**
274: * Treats comments, to be specific creates a syntax point for them and then replaces their content.
275: *
276: * @param patternIndex
277: * the index at which the comment is stored
278: */
279: void unComment(int patternIndex) {
280: unComment(client.getCommentPatterns()[patternIndex], client
281: .getCommentPatternNames()[patternIndex]);
282: }
283:
284: /**
285: * Replaces comments or literals from a text by blanks, and stores syntax points. The comment or literal is defined
286: * by the given pattern and pattern name. As a result, the text with comments is replaced by blanks, of equal length
287: * as the input.
288: *
289: * @param pattern
290: * the pattern to match the literal or comment
291: * @param patternName
292: * the name of the pattern.
293: */
294: private void unComment(Pattern pattern, String patternName) {
295: Matcher m = pattern.matcher(content);
296: int endOfLast = 0;
297: StringBuffer uncommentedContent = new StringBuffer();
298: while (m.find()) {
299: uncommentedContent.append(content.substring(endOfLast, m
300: .start()));
301: for (int i = m.start(); i < m.end(); i++)
302: uncommentedContent.append(' ');
303: endOfLast = m.end();
304: java.util.logging.Logger.getLogger(
305: "org.makumba." + "syntaxpoint.comment").fine(
306: "UNCOMMENT " + patternName + " : " + m.group());
307: addSyntaxPoints(m.start() + offset, m.end() + offset,
308: patternName, null);
309: }
310: uncommentedContent.append(content.substring(endOfLast));
311: content = uncommentedContent.toString();
312: }
313:
314: /**
315: * Treat literals, to be specific creates a syntax point for them and then replaces their content.
316: *
317: * @param patternIndex
318: * the index at which the literal is stored
319: */
320: void treatLiterals(int patternIndex) {
321: unComment(client.getLiteralPatterns()[patternIndex], client
322: .getLiteralPatternNames()[patternIndex]);
323: }
324:
325: /**
326: * Creates a beginning and end syntaxPoint for a syntax entity, and adds these to the collection of points.
327: *
328: * @param start
329: * the starting position
330: * @param end
331: * the end position
332: * @param type
333: * String stating the type of syntax point
334: * @param extra
335: * any extra info (for example the object created at the syntax point
336: * @see #addSyntaxPointsCommon(int start, int end, String type, Object extra)
337: */
338: public SyntaxPoint.End addSyntaxPoints(int start, int end,
339: String type, Object extra) {
340: SourceSyntaxPoints ssp = findSourceFile(start);
341: if (ssp == this )
342: return addSyntaxPoints1(start, end, type, extra);
343: else
344: return ssp.addSyntaxPoints(start, end, type, extra);
345: }
346:
347: SyntaxPoint.End addSyntaxPoints1(int start, int end, String type,
348: Object extra) {
349: SyntaxPoint.End e = addSyntaxPointsCommon(start, end, type,
350: extra);
351: setLineAndColumn(e);
352: setLineAndColumn((SyntaxPoint) e.getOtherInfo());
353: /*
354: * useful debug: if(e.getType().indexOf("Attribute")==-1){ SyntaxPoint b= (SyntaxPoint)e.getOtherInfo();
355: * System.out.println(file.getName()+":"+b.getLine()+":"+b.getColumn()+":"+e.getLine()+":"+e.getColumn()+" "+b+"
356: * "+e); }
357: */
358: return e;
359: }
360:
361: /**
362: * Fills in the Line and Column for the given SyntaxPoint, based on the collection of lineBeginnings syntaxPoints.
363: *
364: * @param point
365: * the syntax point to be filled in
366: */
367: void setLineAndColumn(SyntaxPoint point) {
368: SyntaxPoint lineBegin = (SyntaxPoint) lineBeginnings.get((-1)
369: * Collections.binarySearch(lineBeginnings, point) - 2);
370: point.line = lineBegin.line;
371: point.column = point.position - lineBegin.position + 1;
372: point.sourceFile = this ;
373: }
374:
375: /**
376: * Finds the source file that contains the given syntax point
377: *
378: * @param position
379: * position of the syntax point
380: */
381: SourceSyntaxPoints findSourceFile(int position) {
382: int index = Collections.binarySearch(fileBeginningIndexes,
383: new Integer(position - offset));
384: if (index < 0)
385: index = -index - 2;
386: return (SourceSyntaxPoints) fileBeginnings.get(index);
387: }
388:
389: /**
390: * Creates begin- and end- syntaxpoints (but without setting the line and column fields) at given location and with
391: * given info, and adds them to the collection.
392: *
393: * @param start
394: * the starting position
395: * @param end
396: * the end position
397: * @param type
398: * String stating the type of syntax point
399: * @param extra
400: * any extra info (for example the object created at the syntax point
401: *
402: * @return the created <tt>SyntaxPoint.End</tt>
403: * @see #addSyntaxPoints(int, int, String, Object)
404: */
405: SyntaxPoint.End addSyntaxPointsCommon(int start, int end,
406: String type, Object extra) {
407: // Java Note: defining these final variables, because "An inner class defined inside a method
408: // can still access all of the member variables of the outer class, but it can only
409: // access final variables of the method."
410: final String type1 = type;
411: final Object extra1 = extra;
412:
413: SyntaxPoint point = new SyntaxPoint(start) {
414: public String getType() {
415: return type1;
416: }
417:
418: public Object getOtherInfo() {
419: return extra1;
420: }
421: };
422:
423: syntaxPoints.add(point);
424:
425: SyntaxPoint.End theEnd = (SyntaxPoint.End) SyntaxPoint.makeEnd(
426: point, end);
427: syntaxPoints.add(theEnd);
428:
429: return theEnd;
430: }
431:
432: /**
433: * Creates begin- and end- syntaxpoints for a full line in text.
434: *
435: * @param start
436: * the starting position
437: * @param end
438: * the end position
439: * @param type
440: * String stating the type of syntax point
441: * @param extra
442: * any extra info (for example the object created at the syntax point
443: */
444: void addSyntaxPointsLine(int start, int end, String type,
445: Object extra) {
446: SyntaxPoint.End e = addSyntaxPointsCommon(start, end, type,
447: extra);
448: e.moveByInclude(offset);
449: SyntaxPoint lineBegin = (SyntaxPoint) e.getOtherInfo();
450: lineBegin.moveByInclude(offset);
451: lineBegin.line = e.line = ((Integer) lineBegin.getOtherInfo())
452: .intValue();
453: lineBegin.column = 1;
454: e.column = end - start + 1;
455: e.sourceFile = lineBegin.sourceFile = this ;
456: lineBeginnings.add(lineBegin);
457: }
458:
459: /**
460: * Checks if the file changed on the disk since it was last analysed.
461: *
462: * @return <code>false</code> if unchanged, <code>true</code> otherwise
463: */
464: boolean unchanged() {
465: if (file.lastModified() != lastChanged)
466: return false;
467: for (Iterator i = fileBeginnings.iterator(); i.hasNext();) {
468: SourceSyntaxPoints ss = (SourceSyntaxPoints) i.next();
469: if (ss != this && !ss.unchanged())
470: return false;
471: }
472: return true;
473: }
474:
475: /**
476: * Reads the content of the JSP file into a string.
477: *
478: * @param includeDirective
479: * the directive by which this file has been included
480: * @return A String containing a JSP file
481: */
482: String readFile(String includeDirective) {
483: StringBuffer sb = new StringBuffer();
484: try {
485: BufferedReader rd = new BufferedReader(new FileReader(file));
486: char[] buffer = new char[2048];
487: int n;
488: while ((n = rd.read(buffer)) != -1)
489: sb.append(buffer, 0, n);
490: } catch (FileNotFoundException e) {
491: String msg = "File '" + file.getName()
492: + "' not found.\n\t(" + e.getMessage() + ")";
493: if (includeDirective != null) {
494: msg = "Error in include directive:\n\n"
495: + includeDirective + "\n\n" + msg;
496: } else {
497: msg = "Error in reading a file: " + msg;
498: }
499: throw new ProgrammerError(msg);
500: } catch (IOException e) {
501: e.printStackTrace();
502: }
503: return sb.toString();
504: }
505:
506: String getContent() {
507: return content;
508: }
509:
510: /**
511: * Returns the syntaxPoints.
512: *
513: * @return An array of SyntaxPoints
514: */
515: public SyntaxPoint[] getSyntaxPoints() {
516: ArrayList list = new ArrayList(syntaxPoints);
517: Collections.sort(list);
518: SyntaxPoint[] result = (SyntaxPoint[]) list
519: .toArray(new SyntaxPoint[syntaxPoints.size()]);
520: // the following is needed to pass by a bug occuring to sorting (TextLine begin&end on the same line&column are
521: // switched)
522: // preferably, this would be done on creation of the Treeset, but i failed to fix the problem there
523: for (int i = 0; i + 1 < result.length; i++) {
524: if (result[i].getType().equals("TextLine")
525: && result[i + 1].getType().equals("TextLine")
526: && result[i].getLine() == result[i + 1].getLine()
527: && result[i].getColumn() == result[i + 1]
528: .getColumn() && !result[i].isBegin()
529: && result[i + 1].isBegin()) {
530: SyntaxPoint temp = result[i];
531: result[i] = result[i + 1];
532: result[i + 1] = temp;
533: }
534: }
535: return result;
536: }
537:
538: }// end class
|