001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.formatter.comment;
011:
012: import java.util.HashSet;
013: import java.util.Set;
014:
015: import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
016: import org.eclipse.jface.text.IRegion;
017: import org.eclipse.jface.text.Region;
018:
019: /**
020: * Multi-line comment line in a comment region.
021: *
022: * @since 3.0
023: */
024: public class MultiCommentLine extends CommentLine implements
025: ICommentAttributes, IHtmlTagDelimiters, IJavaDocTagConstants {
026:
027: /** Line prefix of multi-line comment content lines */
028: public static final String MULTI_COMMENT_CONTENT_PREFIX = " * "; //$NON-NLS-1$
029:
030: /** Line prefix of multi-line comment end lines */
031: public static final String MULTI_COMMENT_END_PREFIX = " */"; //$NON-NLS-1$
032:
033: /** Line prefix of multi-line comment content lines */
034: public static final String MULTI_COMMENT_START_PREFIX = "/* "; //$NON-NLS-1$
035:
036: /** The indentation reference of this line */
037: private String fReferenceIndentation = ""; //$NON-NLS-1$
038:
039: /** The javadoc tag lookup. */
040: private static final Set fgTagLookup;
041:
042: static {
043: fgTagLookup = new HashSet();
044: for (int i = 0; i < JAVADOC_BREAK_TAGS.length; i++) {
045: fgTagLookup.add(new String(JAVADOC_BREAK_TAGS[i]));
046: }
047: for (int i = 0; i < JAVADOC_SINGLE_BREAK_TAG.length; i++) {
048: fgTagLookup.add(new String(JAVADOC_SINGLE_BREAK_TAG[i]));
049: }
050: for (int i = 0; i < JAVADOC_CODE_TAGS.length; i++) {
051: fgTagLookup.add(new String(JAVADOC_CODE_TAGS[i]));
052: }
053: for (int i = 0; i < JAVADOC_IMMUTABLE_TAGS.length; i++) {
054: fgTagLookup.add(new String(JAVADOC_IMMUTABLE_TAGS[i]));
055: }
056: for (int i = 0; i < JAVADOC_NEWLINE_TAGS.length; i++) {
057: fgTagLookup.add(new String(JAVADOC_NEWLINE_TAGS[i]));
058: }
059: for (int i = 0; i < JAVADOC_SEPARATOR_TAGS.length; i++) {
060: fgTagLookup.add(new String(JAVADOC_SEPARATOR_TAGS[i]));
061: }
062: }
063:
064: /**
065: * Creates a new multi-line comment line.
066: *
067: * @param region comment region to create the line for
068: */
069: protected MultiCommentLine(final CommentRegion region) {
070: super (region);
071: }
072:
073: /*
074: * @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#adapt(org.eclipse.jdt.internal.corext.text.comment.CommentLine)
075: */
076: protected void adapt(final CommentLine previous) {
077:
078: if (!hasAttribute(COMMENT_ROOT)
079: && !hasAttribute(COMMENT_PARAMETER)
080: && !previous.hasAttribute(COMMENT_BLANKLINE))
081: fReferenceIndentation = previous.getIndentationReference();
082: }
083:
084: /*
085: * @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#append(org.eclipse.jdt.internal.corext.text.comment.CommentRange)
086: */
087: protected void append(final CommentRange range) {
088:
089: final MultiCommentRegion parent = (MultiCommentRegion) getParent();
090:
091: if (range.hasAttribute(COMMENT_PARAMETER))
092: setAttribute(COMMENT_PARAMETER);
093: else if (range.hasAttribute(COMMENT_ROOT))
094: setAttribute(COMMENT_ROOT);
095: else if (range.hasAttribute(COMMENT_BLANKLINE))
096: setAttribute(COMMENT_BLANKLINE);
097:
098: final int ranges = getSize();
099: if (ranges == 1) {
100:
101: if (parent.isIndentRoots()) {
102:
103: final CommentRange first = getFirst();
104: final String common = parent.getText(first.getOffset(),
105: first.getLength())
106: + CommentRegion.COMMENT_RANGE_DELIMITER;
107:
108: if (hasAttribute(COMMENT_ROOT))
109: fReferenceIndentation = common;
110: else if (hasAttribute(COMMENT_PARAMETER)) {
111: if (parent.isIndentDescriptions())
112: fReferenceIndentation = "\t" + common; //$NON-NLS-1$
113: else
114: fReferenceIndentation = common;
115: }
116: }
117: }
118: super .append(range);
119: }
120:
121: /*
122: * @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#getContentLinePrefix()
123: */
124: protected String getContentPrefix() {
125: return MULTI_COMMENT_CONTENT_PREFIX;
126: }
127:
128: /*
129: * @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#getEndLinePrefix()
130: */
131: protected String getEndingPrefix() {
132: return MULTI_COMMENT_END_PREFIX;
133: }
134:
135: /**
136: * Returns the reference indentation to use for this line.
137: *
138: * @return the reference indentation for this line
139: */
140: protected final String getIndentationReference() {
141: return fReferenceIndentation;
142: }
143:
144: /*
145: * @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#getStartLinePrefix()
146: */
147: protected String getStartingPrefix() {
148: return MULTI_COMMENT_START_PREFIX;
149: }
150:
151: /*
152: * @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#scanLine(int)
153: */
154: protected void scanLine(final int line) {
155:
156: final CommentRegion parent = getParent();
157: final String start = getStartingPrefix().trim();
158: final String end = getEndingPrefix().trim();
159: final String content = getContentPrefix().trim();
160:
161: final int lines = parent.getSize();
162: final CommentRange range = getFirst();
163:
164: int offset = 0;
165: int postfix = 0;
166:
167: String text = parent.getText(range.getOffset(), range
168: .getLength());
169: if (line == 0) {
170:
171: offset = text.indexOf(start);
172: if (offset >= 0
173: && text.substring(0, offset).trim().length() != 0)
174: offset = -1;
175:
176: if (offset >= 0) {
177:
178: offset += start.length();
179: range.trimBegin(offset);
180:
181: postfix = text.lastIndexOf(end);
182: if (postfix >= 0
183: && text.substring(postfix + end.length())
184: .trim().length() != 0)
185: postfix = -1;
186:
187: if (postfix >= offset)
188: // comment ends on same line
189: range.setLength(postfix - offset);
190: else {
191: postfix = text.lastIndexOf(content);
192: if (postfix >= 0
193: && text.substring(
194: postfix + content.length()).trim()
195: .length() != 0)
196: postfix = -1;
197:
198: if (postfix >= offset) {
199:
200: range.setLength(postfix - offset);
201: parent.setBorder(BORDER_UPPER);
202:
203: if (postfix > offset) {
204:
205: text = parent.getText(range.getOffset(),
206: range.getLength());
207: final IRegion region = trimLine(text,
208: content);
209:
210: range.move(region.getOffset());
211: range.setLength(region.getLength());
212: }
213: }
214: }
215: }
216: } else if (line == lines - 1) {
217:
218: offset = text.indexOf(content);
219: if (offset >= 0
220: && text.substring(0, offset).trim().length() != 0)
221: offset = -1;
222: postfix = text.lastIndexOf(end);
223: if (postfix >= 0
224: && text.substring(postfix + end.length()).trim()
225: .length() != 0)
226: postfix = -1;
227:
228: if (offset >= 0 && offset == postfix)
229: // no content on line, only the comment postfix
230: range.setLength(0);
231: else {
232: if (offset >= 0)
233: // omit the content prefix
234: range.trimBegin(offset + content.length());
235:
236: if (postfix >= 0)
237: // omit the comment postfix
238: range.trimEnd(-end.length());
239:
240: text = parent.getText(range.getOffset(), range
241: .getLength());
242: final IRegion region = trimLine(text, content);
243: if (region.getOffset() != 0
244: || region.getLength() != text.length()) {
245:
246: range.move(region.getOffset());
247: range.setLength(region.getLength());
248:
249: parent.setBorder(BORDER_UPPER);
250: parent.setBorder(BORDER_LOWER);
251: }
252: }
253: } else {
254:
255: offset = text.indexOf(content);
256: if (offset >= 0
257: && text.substring(0, offset).trim().length() != 0)
258: offset = -1;
259:
260: if (offset >= 0) {
261:
262: offset += content.length();
263: range.trimBegin(offset);
264: }
265: }
266: }
267:
268: /*
269: * @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#tokenizeLine(int)
270: */
271: protected void tokenizeLine(int line) {
272:
273: int offset = 0;
274: int index = offset;
275:
276: final CommentRegion parent = getParent();
277: final CommentRange range = getFirst();
278: final int begin = range.getOffset();
279:
280: final String content = parent.getText(begin, range.getLength());
281: final int length = content.length();
282:
283: while (offset < length
284: && ScannerHelper.isWhitespace(content.charAt(offset)))
285: offset++;
286:
287: CommentRange result = null;
288: if (offset >= length && !parent.isClearLines()
289: && (line > 0 && line < parent.getSize() - 1)) {
290:
291: result = new CommentRange(begin, 0);
292: result.setAttribute(COMMENT_BLANKLINE);
293: result.setAttribute(COMMENT_FIRST_TOKEN);
294:
295: parent.append(result);
296: }
297:
298: int attribute = COMMENT_FIRST_TOKEN
299: | COMMENT_STARTS_WITH_RANGE_DELIMITER;
300: while (offset < length) {
301:
302: while (offset < length
303: && ScannerHelper.isWhitespace(content
304: .charAt(offset))) {
305: offset++;
306: attribute |= COMMENT_STARTS_WITH_RANGE_DELIMITER;
307: }
308:
309: index = offset;
310:
311: if (index < length) {
312:
313: if (content.charAt(index) == HTML_TAG_PREFIX) {
314:
315: // in order to avoid recognizing any < in a comment, even those which are part of e.g.
316: // java source code, we validate the tag content to be one of the recognized
317: // tags (structural, breaks, pre, code).
318: int tag = ++index;
319: while (index < length
320: && content.charAt(index) != HTML_TAG_POSTFIX
321: && content.charAt(index) != HTML_TAG_PREFIX)
322: index++;
323:
324: if (index < length
325: && content.charAt(index) == HTML_TAG_POSTFIX
326: && isValidTag(content.substring(tag, index))) {
327: index++;
328: attribute |= COMMENT_HTML; // only set html attribute if postfix found
329: } else {
330: // no tag - do the usual thing from the original offset
331: index = tag;
332: while (index < length
333: && !ScannerHelper.isWhitespace(content
334: .charAt(index))
335: && content.charAt(index) != HTML_TAG_PREFIX
336: && !content.startsWith(
337: LINK_TAG_PREFIX_STRING, index))
338: index++;
339: }
340:
341: } else if (content.startsWith(LINK_TAG_PREFIX_STRING,
342: index)) {
343:
344: while (index < length
345: && content.charAt(index) != LINK_TAG_POSTFIX)
346: index++;
347:
348: if (index < length
349: && content.charAt(index) == LINK_TAG_POSTFIX)
350: index++;
351:
352: attribute |= COMMENT_OPEN | COMMENT_CLOSE;
353:
354: } else {
355:
356: while (index < length
357: && !ScannerHelper.isWhitespace(content
358: .charAt(index))
359: && content.charAt(index) != HTML_TAG_PREFIX
360: && !content.startsWith(
361: LINK_TAG_PREFIX_STRING, index))
362: index++;
363: }
364: }
365:
366: if (index - offset > 0) {
367:
368: result = new CommentRange(begin + offset, index
369: - offset);
370: result.setAttribute(attribute);
371:
372: parent.append(result);
373: offset = index;
374: }
375:
376: attribute = 0;
377: }
378: }
379:
380: /**
381: * Checks whether <code>tag</code> is a valid tag content (text inside
382: * the angular brackets <, >).
383: * <p>
384: * The algorithm is to see if the tag trimmed of whitespace and an
385: * optional slash starts with one of our recognized tags.
386: *
387: * @param tag the tag to check
388: * @return <code>true</code> if <code>tag</code> is a valid tag
389: * content
390: */
391: private boolean isValidTag(String tag) {
392: // strip the slash
393: if (tag.startsWith("/")) //$NON-NLS-1$
394: tag = tag.substring(1, tag.length());
395:
396: // strip ws
397: tag = tag.trim();
398:
399: // extract first token
400: int i = 0;
401: while (i < tag.length()
402: && !ScannerHelper.isWhitespace(tag.charAt(i)))
403: i++;
404: tag = tag.substring(0, i);
405:
406: // see if it's a tag
407: return isTagName(tag.toLowerCase());
408: }
409:
410: /**
411: * Checks whether <code>tag</code> is one of the configured tags.
412: *
413: * @param tag the tag to check
414: * @return <code>true</code> if <code>tag</code> is a configured tag
415: * name
416: */
417: private boolean isTagName(String tag) {
418: return fgTagLookup.contains(tag);
419: }
420:
421: /**
422: * Removes all leading and trailing occurrences from <code>line</code>.
423: *
424: * @param line the string to remove the occurrences of
425: * <code>trimmable</code>
426: * @param trimmable the string to remove from <code>line</code>
427: * @return the region of the trimmed substring within <code>line</code>
428: */
429: protected final IRegion trimLine(final String line,
430: final String trimmable) {
431:
432: final int trim = trimmable.length();
433:
434: int offset = 0;
435: int length = line.length() - trim;
436:
437: while (line.startsWith(trimmable, offset))
438: offset += trim;
439:
440: while (line.startsWith(trimmable, length))
441: length -= trim;
442:
443: return new Region(offset, length + trim);
444: }
445: }
|