001: /*
002: * Copyright 2004-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.tools.javac.parser;
027:
028: import java.io.*;
029: import java.nio.*;
030:
031: import com.sun.tools.javac.util.*;
032: import static com.sun.tools.javac.util.LayoutCharacters.*;
033:
034: /** An extension to the base lexical analyzer that captures
035: * and processes the contents of doc comments. It does so by
036: * translating Unicode escape sequences and by stripping the
037: * leading whitespace and starts from each line of the comment.
038: *
039: * <p><b>This is NOT part of any API supported by Sun Microsystems. If
040: * you write code that depends on this, you do so at your own risk.
041: * This code and its internal interfaces are subject to change or
042: * deletion without notice.</b>
043: */
044: public class DocCommentScanner extends Scanner {
045:
046: /** A factory for creating scanners. */
047: public static class Factory extends Scanner.Factory {
048:
049: public static void preRegister(final Context context) {
050: context.put(scannerFactoryKey,
051: new Context.Factory<Scanner.Factory>() {
052: public Factory make() {
053: return new Factory(context);
054: }
055: });
056: }
057:
058: /** Create a new scanner factory. */
059: protected Factory(Context context) {
060: super (context);
061: }
062:
063: @Override
064: public Scanner newScanner(CharSequence input) {
065: if (input instanceof CharBuffer) {
066: return new DocCommentScanner(this , (CharBuffer) input);
067: } else {
068: char[] array = input.toString().toCharArray();
069: return newScanner(array, array.length);
070: }
071: }
072:
073: @Override
074: public Scanner newScanner(char[] input, int inputLength) {
075: return new DocCommentScanner(this , input, inputLength);
076: }
077: }
078:
079: /** Create a scanner from the input buffer. buffer must implement
080: * array() and compact(), and remaining() must be less than limit().
081: */
082: protected DocCommentScanner(Factory fac, CharBuffer buffer) {
083: super (fac, buffer);
084: }
085:
086: /** Create a scanner from the input array. The array must have at
087: * least a single character of extra space.
088: */
089: protected DocCommentScanner(Factory fac, char[] input,
090: int inputLength) {
091: super (fac, input, inputLength);
092: }
093:
094: /** Starting position of the comment in original source
095: */
096: private int pos;
097:
098: /** The comment input buffer, index of next chacter to be read,
099: * index of one past last character in buffer.
100: */
101: private char[] buf;
102: private int bp;
103: private int buflen;
104:
105: /** The current character.
106: */
107: private char ch;
108:
109: /** The column number position of the current character.
110: */
111: private int col;
112:
113: /** The buffer index of the last converted Unicode character
114: */
115: private int unicodeConversionBp = 0;
116:
117: /**
118: * Buffer for doc comment.
119: */
120: private char[] docCommentBuffer = new char[1024];
121:
122: /**
123: * Number of characters in doc comment buffer.
124: */
125: private int docCommentCount;
126:
127: /**
128: * Translated and stripped contents of doc comment
129: */
130: private String docComment = null;
131:
132: /** Unconditionally expand the comment buffer.
133: */
134: private void expandCommentBuffer() {
135: char[] newBuffer = new char[docCommentBuffer.length * 2];
136: System.arraycopy(docCommentBuffer, 0, newBuffer, 0,
137: docCommentBuffer.length);
138: docCommentBuffer = newBuffer;
139: }
140:
141: /** Convert an ASCII digit from its base (8, 10, or 16)
142: * to its value.
143: */
144: private int digit(int base) {
145: char c = ch;
146: int result = Character.digit(c, base);
147: if (result >= 0 && c > 0x7f) {
148: ch = "0123456789abcdef".charAt(result);
149: }
150: return result;
151: }
152:
153: /** Convert Unicode escape; bp points to initial '\' character
154: * (Spec 3.3).
155: */
156: private void convertUnicode() {
157: if (ch == '\\' && unicodeConversionBp != bp) {
158: bp++;
159: ch = buf[bp];
160: col++;
161: if (ch == 'u') {
162: do {
163: bp++;
164: ch = buf[bp];
165: col++;
166: } while (ch == 'u');
167: int limit = bp + 3;
168: if (limit < buflen) {
169: int d = digit(16);
170: int code = d;
171: while (bp < limit && d >= 0) {
172: bp++;
173: ch = buf[bp];
174: col++;
175: d = digit(16);
176: code = (code << 4) + d;
177: }
178: if (d >= 0) {
179: ch = (char) code;
180: unicodeConversionBp = bp;
181: return;
182: }
183: }
184: // "illegal.Unicode.esc", reported by base scanner
185: } else {
186: bp--;
187: ch = '\\';
188: col--;
189: }
190: }
191: }
192:
193: /** Read next character.
194: */
195: private void scanChar() {
196: bp++;
197: ch = buf[bp];
198: switch (ch) {
199: case '\r': // return
200: col = 0;
201: break;
202: case '\n': // newline
203: if (bp == 0 || buf[bp - 1] != '\r') {
204: col = 0;
205: }
206: break;
207: case '\t': // tab
208: col = (col / TabInc * TabInc) + TabInc;
209: break;
210: case '\\': // possible Unicode
211: col++;
212: convertUnicode();
213: break;
214: default:
215: col++;
216: break;
217: }
218: }
219:
220: /**
221: * Read next character in doc comment, skipping over double '\' characters.
222: * If a double '\' is skipped, put in the buffer and update buffer count.
223: */
224: private void scanDocCommentChar() {
225: scanChar();
226: if (ch == '\\') {
227: if (buf[bp + 1] == '\\' && unicodeConversionBp != bp) {
228: if (docCommentCount == docCommentBuffer.length)
229: expandCommentBuffer();
230: docCommentBuffer[docCommentCount++] = ch;
231: bp++;
232: col++;
233: } else {
234: convertUnicode();
235: }
236: }
237: }
238:
239: /* Reset doc comment before reading each new token
240: */
241: public void nextToken() {
242: docComment = null;
243: super .nextToken();
244: }
245:
246: /**
247: * Returns the documentation string of the current token.
248: */
249: public String docComment() {
250: return docComment;
251: }
252:
253: /**
254: * Process a doc comment and make the string content available.
255: * Strips leading whitespace and stars.
256: */
257: @SuppressWarnings("fallthrough")
258: protected void processComment(CommentStyle style) {
259: if (style != CommentStyle.JAVADOC) {
260: return;
261: }
262:
263: pos = pos();
264: buf = getRawCharacters(pos, endPos());
265: buflen = buf.length;
266: bp = 0;
267: col = 0;
268:
269: docCommentCount = 0;
270:
271: boolean firstLine = true;
272:
273: // Skip over first slash
274: scanDocCommentChar();
275: // Skip over first star
276: scanDocCommentChar();
277:
278: // consume any number of stars
279: while (bp < buflen && ch == '*') {
280: scanDocCommentChar();
281: }
282: // is the comment in the form /**/, /***/, /****/, etc. ?
283: if (bp < buflen && ch == '/') {
284: docComment = "";
285: return;
286: }
287:
288: // skip a newline on the first line of the comment.
289: if (bp < buflen) {
290: if (ch == LF) {
291: scanDocCommentChar();
292: firstLine = false;
293: } else if (ch == CR) {
294: scanDocCommentChar();
295: if (ch == LF) {
296: scanDocCommentChar();
297: firstLine = false;
298: }
299: }
300: }
301:
302: outerLoop:
303:
304: // The outerLoop processes the doc comment, looping once
305: // for each line. For each line, it first strips off
306: // whitespace, then it consumes any stars, then it
307: // puts the rest of the line into our buffer.
308: while (bp < buflen) {
309:
310: // The wsLoop consumes whitespace from the beginning
311: // of each line.
312: wsLoop:
313:
314: while (bp < buflen) {
315: switch (ch) {
316: case ' ':
317: scanDocCommentChar();
318: break;
319: case '\t':
320: col = ((col - 1) / TabInc * TabInc) + TabInc;
321: scanDocCommentChar();
322: break;
323: case FF:
324: col = 0;
325: scanDocCommentChar();
326: break;
327: // Treat newline at beginning of line (blank line, no star)
328: // as comment text. Old Javadoc compatibility requires this.
329: /*---------------------------------*
330: case CR: // (Spec 3.4)
331: scanDocCommentChar();
332: if (ch == LF) {
333: col = 0;
334: scanDocCommentChar();
335: }
336: break;
337: case LF: // (Spec 3.4)
338: scanDocCommentChar();
339: break;
340: *---------------------------------*/
341: default:
342: // we've seen something that isn't whitespace;
343: // jump out.
344: break wsLoop;
345: }
346: }
347:
348: // Are there stars here? If so, consume them all
349: // and check for the end of comment.
350: if (ch == '*') {
351: // skip all of the stars
352: do {
353: scanDocCommentChar();
354: } while (ch == '*');
355:
356: // check for the closing slash.
357: if (ch == '/') {
358: // We're done with the doc comment
359: // scanChar() and breakout.
360: break outerLoop;
361: }
362: } else if (!firstLine) {
363: //The current line does not begin with a '*' so we will indent it.
364: for (int i = 1; i < col; i++) {
365: if (docCommentCount == docCommentBuffer.length)
366: expandCommentBuffer();
367: docCommentBuffer[docCommentCount++] = ' ';
368: }
369: }
370:
371: // The textLoop processes the rest of the characters
372: // on the line, adding them to our buffer.
373: textLoop: while (bp < buflen) {
374: switch (ch) {
375: case '*':
376: // Is this just a star? Or is this the
377: // end of a comment?
378: scanDocCommentChar();
379: if (ch == '/') {
380: // This is the end of the comment,
381: // set ch and return our buffer.
382: break outerLoop;
383: }
384: // This is just an ordinary star. Add it to
385: // the buffer.
386: if (docCommentCount == docCommentBuffer.length)
387: expandCommentBuffer();
388: docCommentBuffer[docCommentCount++] = '*';
389: break;
390: case ' ':
391: case '\t':
392: if (docCommentCount == docCommentBuffer.length)
393: expandCommentBuffer();
394: docCommentBuffer[docCommentCount++] = ch;
395: scanDocCommentChar();
396: break;
397: case FF:
398: scanDocCommentChar();
399: break textLoop; // treat as end of line
400: case CR: // (Spec 3.4)
401: scanDocCommentChar();
402: if (ch != LF) {
403: // Canonicalize CR-only line terminator to LF
404: if (docCommentCount == docCommentBuffer.length)
405: expandCommentBuffer();
406: docCommentBuffer[docCommentCount++] = (char) LF;
407: break textLoop;
408: }
409: /* fall through to LF case */
410: case LF: // (Spec 3.4)
411: // We've seen a newline. Add it to our
412: // buffer and break out of this loop,
413: // starting fresh on a new line.
414: if (docCommentCount == docCommentBuffer.length)
415: expandCommentBuffer();
416: docCommentBuffer[docCommentCount++] = ch;
417: scanDocCommentChar();
418: break textLoop;
419: default:
420: // Add the character to our buffer.
421: if (docCommentCount == docCommentBuffer.length)
422: expandCommentBuffer();
423: docCommentBuffer[docCommentCount++] = ch;
424: scanDocCommentChar();
425: }
426: } // end textLoop
427: firstLine = false;
428: } // end outerLoop
429:
430: if (docCommentCount > 0) {
431: int i = docCommentCount - 1;
432: trailLoop: while (i > -1) {
433: switch (docCommentBuffer[i]) {
434: case '*':
435: i--;
436: break;
437: default:
438: break trailLoop;
439: }
440: }
441: docCommentCount = i + 1;
442:
443: // Store the text of the doc comment
444: docComment = new String(docCommentBuffer, 0,
445: docCommentCount);
446: } else {
447: docComment = "";
448: }
449: }
450:
451: /** Build a map for translating between line numbers and
452: * positions in the input.
453: *
454: * @return a LineMap */
455: public Position.LineMap getLineMap() {
456: char[] buf = getRawCharacters();
457: return Position.makeLineMap(buf, buf.length, true);
458: }
459: }
|