blob: 2fd7fff9c93528866a53b0575c0e8212016cf103 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.formatter.comment;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
/**
* Multi-line comment line in a comment region.
*
* @since 3.0
*/
public class MultiCommentLine extends CommentLine implements ICommentAttributes, IHtmlTagDelimiters, IJavaDocTagConstants {
/** Line prefix of multi-line comment content lines */
public static final String MULTI_COMMENT_CONTENT_PREFIX= " * "; //$NON-NLS-1$
/** Line prefix of multi-line comment end lines */
public static final String MULTI_COMMENT_END_PREFIX= " */"; //$NON-NLS-1$
/** Line prefix of multi-line comment content lines */
public static final String MULTI_COMMENT_START_PREFIX= "/* "; //$NON-NLS-1$
/** The indentation reference of this line */
private String fReferenceIndentation= ""; //$NON-NLS-1$
/** The javadoc tag lookup. */
private static final Set fgTagLookup;
static {
fgTagLookup= new HashSet();
for (int i= 0; i < JAVADOC_BREAK_TAGS.length; i++) {
fgTagLookup.add(JAVADOC_BREAK_TAGS[i]);
}
for (int i= 0; i < JAVADOC_SINGLE_BREAK_TAG.length; i++) {
fgTagLookup.add(JAVADOC_SINGLE_BREAK_TAG[i]);
}
for (int i= 0; i < JAVADOC_CODE_TAGS.length; i++) {
fgTagLookup.add(JAVADOC_CODE_TAGS[i]);
}
for (int i= 0; i < JAVADOC_IMMUTABLE_TAGS.length; i++) {
fgTagLookup.add(JAVADOC_IMMUTABLE_TAGS[i]);
}
for (int i= 0; i < JAVADOC_NEWLINE_TAGS.length; i++) {
fgTagLookup.add(JAVADOC_NEWLINE_TAGS[i]);
}
for (int i= 0; i < JAVADOC_SEPARATOR_TAGS.length; i++) {
fgTagLookup.add(JAVADOC_SEPARATOR_TAGS[i]);
}
}
/**
* Creates a new multi-line comment line.
*
* @param region comment region to create the line for
*/
protected MultiCommentLine(final CommentRegion region) {
super(region);
}
/*
* @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#adapt(org.eclipse.jdt.internal.corext.text.comment.CommentLine)
*/
protected void adapt(final CommentLine previous) {
if (!hasAttribute(COMMENT_ROOT) && !hasAttribute(COMMENT_PARAMETER) && !previous.hasAttribute(COMMENT_BLANKLINE))
fReferenceIndentation= previous.getIndentationReference();
}
/*
* @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#append(org.eclipse.jdt.internal.corext.text.comment.CommentRange)
*/
protected void append(final CommentRange range) {
final MultiCommentRegion parent= (MultiCommentRegion)getParent();
if (range.hasAttribute(COMMENT_PARAMETER))
setAttribute(COMMENT_PARAMETER);
else if (range.hasAttribute(COMMENT_ROOT))
setAttribute(COMMENT_ROOT);
else if (range.hasAttribute(COMMENT_BLANKLINE))
setAttribute(COMMENT_BLANKLINE);
final int ranges= getSize();
if (ranges == 1) {
if (parent.isIndentRoots()) {
final CommentRange first= getFirst();
final String common= parent.getText(first.getOffset(), first.getLength()) + CommentRegion.COMMENT_RANGE_DELIMITER;
if (hasAttribute(COMMENT_ROOT))
fReferenceIndentation= common;
else if (hasAttribute(COMMENT_PARAMETER)) {
if (parent.isIndentDescriptions())
fReferenceIndentation= "\t" + common; //$NON-NLS-1$
else
fReferenceIndentation= common;
}
}
}
super.append(range);
}
/*
* @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#getContentLinePrefix()
*/
protected String getContentPrefix() {
return MULTI_COMMENT_CONTENT_PREFIX;
}
/*
* @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#getEndLinePrefix()
*/
protected String getEndingPrefix() {
return MULTI_COMMENT_END_PREFIX;
}
/**
* Returns the reference indentation to use for this line.
*
* @return the reference indentation for this line
*/
protected final String getIndentationReference() {
return fReferenceIndentation;
}
/*
* @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#getStartLinePrefix()
*/
protected String getStartingPrefix() {
return MULTI_COMMENT_START_PREFIX;
}
/*
* @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#scanLine(int)
*/
protected void scanLine(final int line) {
final CommentRegion parent= getParent();
final String start= getStartingPrefix().trim();
final String end= getEndingPrefix().trim();
final String content= getContentPrefix().trim();
final int lines= parent.getSize();
final CommentRange range= getFirst();
int offset= 0;
int postfix= 0;
String text= parent.getText(range.getOffset(), range.getLength());
if (line == 0) {
offset= text.indexOf(start);
if (offset >= 0 && text.substring(0, offset).trim().length() != 0)
offset= -1;
if (offset >= 0) {
offset += start.length();
range.trimBegin(offset);
postfix= text.lastIndexOf(end);
if (postfix >= 0 && text.substring(postfix + end.length()).trim().length() != 0)
postfix= -1;
if (postfix >= offset)
// comment ends on same line
range.setLength(postfix - offset);
else {
postfix= text.lastIndexOf(content);
if (postfix >= 0 && text.substring(postfix + content.length()).trim().length() != 0)
postfix= -1;
if (postfix >= offset) {
range.setLength(postfix - offset);
parent.setBorder(BORDER_UPPER);
if (postfix > offset) {
text= parent.getText(range.getOffset(), range.getLength());
final IRegion region= trimLine(text, content);
range.move(region.getOffset());
range.setLength(region.getLength());
}
}
}
}
} else if (line == lines - 1) {
offset= text.indexOf(content);
if (offset >= 0 && text.substring(0, offset).trim().length() != 0)
offset= -1;
postfix= text.lastIndexOf(end);
if (postfix >= 0 && text.substring(postfix + end.length()).trim().length() != 0)
postfix= -1;
if (offset >= 0 && offset == postfix)
// no content on line, only the comment postfix
range.setLength(0);
else {
if (offset >= 0)
// omit the content prefix
range.trimBegin(offset + content.length());
if (postfix >= 0)
// omit the comment postfix
range.trimEnd(-end.length());
text= parent.getText(range.getOffset(), range.getLength());
final IRegion region= trimLine(text, content);
if (region.getOffset() != 0 || region.getLength() != text.length()) {
range.move(region.getOffset());
range.setLength(region.getLength());
parent.setBorder(BORDER_UPPER);
parent.setBorder(BORDER_LOWER);
}
}
} else {
offset= text.indexOf(content);
if (offset >= 0 && text.substring(0, offset).trim().length() != 0)
offset= -1;
if (offset >= 0) {
offset += content.length();
range.trimBegin(offset);
}
}
}
/*
* @see org.eclipse.jdt.internal.corext.text.comment.CommentLine#tokenizeLine(int)
*/
protected void tokenizeLine(int line) {
int offset= 0;
int index= offset;
final CommentRegion parent= getParent();
final CommentRange range= getFirst();
final int begin= range.getOffset();
final String content= parent.getText(begin, range.getLength());
final int length= content.length();
while (offset < length && Character.isWhitespace(content.charAt(offset)))
offset++;
CommentRange result= null;
if (offset >= length && !parent.isClearLines() && (line > 0 && line < parent.getSize() - 1)) {
result= new CommentRange(begin, 0);
result.setAttribute(COMMENT_BLANKLINE);
result.setAttribute(COMMENT_FIRST_TOKEN);
parent.append(result);
}
int attribute= COMMENT_FIRST_TOKEN | COMMENT_STARTS_WITH_RANGE_DELIMITER;
while (offset < length) {
while (offset < length && Character.isWhitespace(content.charAt(offset))) {
offset++;
attribute |= COMMENT_STARTS_WITH_RANGE_DELIMITER;
}
index= offset;
if (index < length) {
if (content.charAt(index) == HTML_TAG_PREFIX) {
// in order to avoid recognizing any < in a comment, even those which are part of e.g.
// java source code, we validate the tag content to be one of the recognized
// tags (structural, breaks, pre, code).
int tag= ++index;
while (index < length && content.charAt(index) != HTML_TAG_POSTFIX && content.charAt(index) != HTML_TAG_PREFIX)
index++;
if (index < length && content.charAt(index) == HTML_TAG_POSTFIX && isValidTag(content.substring(tag, index))) {
index++;
attribute |= COMMENT_HTML; // only set html attribute if postfix found
} else {
// no tag - do the usual thing from the original offset
index= tag;
while (index < length && !Character.isWhitespace(content.charAt(index)) && content.charAt(index) != HTML_TAG_PREFIX && !content.startsWith(LINK_TAG_PREFIX, index))
index++;
}
} else if (content.startsWith(LINK_TAG_PREFIX, index)) {
while (index < length && content.charAt(index) != LINK_TAG_POSTFIX)
index++;
if (index < length && content.charAt(index) == LINK_TAG_POSTFIX)
index++;
attribute |= COMMENT_OPEN | COMMENT_CLOSE;
} else {
while (index < length && !Character.isWhitespace(content.charAt(index)) && content.charAt(index) != HTML_TAG_PREFIX && !content.startsWith(LINK_TAG_PREFIX, index))
index++;
}
}
if (index - offset > 0) {
result= new CommentRange(begin + offset, index - offset);
result.setAttribute(attribute);
parent.append(result);
offset= index;
}
attribute= 0;
}
}
/**
* Checks whether <code>tag</code> is a valid tag content (text inside
* the angular brackets &lt;, &gt;).
* <p>
* The algorithm is to see if the tag trimmed of whitespace and an
* optional slash starts with one of our recognized tags.
*
* @param tag the tag to check
* @return <code>true</code> if <code>tag</code> is a valid tag
* content
*/
private boolean isValidTag(String tag) {
// strip the slash
if (tag.startsWith("/")) //$NON-NLS-1$
tag= tag.substring(1, tag.length());
// strip ws
tag= tag.trim();
// extract first token
int i= 0;
while (i < tag.length() && !Character.isWhitespace(tag.charAt(i)))
i++;
tag= tag.substring(0, i);
// see if it's a tag
return isTagName(tag.toLowerCase());
}
/**
* Checks whether <code>tag</code> is one of the configured tags.
*
* @param tag the tag to check
* @return <code>true</code> if <code>tag</code> is a configured tag
* name
*/
private boolean isTagName(String tag) {
return fgTagLookup.contains(tag);
}
/**
* Removes all leading and trailing occurrences from <code>line</code>.
*
* @param line the string to remove the occurrences of
* <code>trimmable</code>
* @param trimmable the string to remove from <code>line</code>
* @return the region of the trimmed substring within <code>line</code>
*/
protected final IRegion trimLine(final String line, final String trimmable) {
final int trim= trimmable.length();
int offset= 0;
int length= line.length() - trim;
while (line.startsWith(trimmable, offset))
offset += trim;
while (line.startsWith(trimmable, length))
length -= trim;
return new Region(offset, length + trim);
}
}