/******************************************************************************* * Copyright (c) 2000, 2010 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; import org.eclipse.jdt.internal.formatter.comment.IJavaDocTagConstants; /** * Represents text inside a javadoc comment block. * <p> * Text may be simple as <code>Line inside a javadoc comment block</code> or may be a html tag. Note * that to minimize memory footprint, only text positions are stored. * </p> * <p> * Simple text may have one or several lines. When it has several lines, the positions of the line * breaks are also stored in the {@link #separators} array. * </p> * <p> * When text has html tags, then they are stored in {@link #htmlNodes} array in a recursive way. * </p> */ public class FormatJavadocText extends FormatJavadocNode implements IJavaDocTagConstants { long[] separators; int separatorsPtr= -1; private int htmlTagIndex= -1; boolean immutable= false; FormatJavadocNode[] htmlNodes; int[] htmlIndexes; int htmlNodesPtr= -1; int depth= 0; public FormatJavadocText(int start, int end, int line, int htmlIndex, int htmlDepth) { super(start, end, line); this.htmlTagIndex= htmlIndex; this.depth= htmlDepth; } /* * Append a text to current one. * If the given text is not an html tag or is a closing tag, then just append to * the current text recording the separators. Otherwise, create a new html tag * child node. */ void appendText(FormatJavadocText text) { text.immutable= this.immutable; if (this.depth == text.depth) { addSeparator(text); this.sourceEnd= text.sourceEnd; if (text.isClosingHtmlTag()) { // close the tag this.htmlTagIndex= text.htmlTagIndex; } } else { appendNode(text); } if (text.isHtmlTag()) { switch (text.htmlTagIndex & JAVADOC_TAGS_ID_MASK) { case JAVADOC_CODE_TAGS_ID: text.linesBefore= this.htmlNodesPtr == -1 ? 0 : 2; break; case JAVADOC_SEPARATOR_TAGS_ID: text.linesBefore= 1; break; case JAVADOC_SINGLE_BREAK_TAG_ID: if (!text.isClosingHtmlTag()) text.linesBefore= 1; break; case JAVADOC_BREAK_TAGS_ID: if (!text.isClosingHtmlTag()) text.linesBefore= 1; } } } void appendNode(FormatJavadocNode node) { if (++this.htmlNodesPtr == 0) { // lazy initialization this.htmlNodes= new FormatJavadocNode[DEFAULT_ARRAY_SIZE]; } else { if (this.htmlNodesPtr == this.htmlNodes.length) { int size= this.htmlNodesPtr + DEFAULT_ARRAY_SIZE; System.arraycopy(this.htmlNodes, 0, (this.htmlNodes= new FormatJavadocNode[size]), 0, this.htmlNodesPtr); } } addSeparator(node); this.htmlNodes[this.htmlNodesPtr]= node; this.sourceEnd= node.sourceEnd; } private void addSeparator(FormatJavadocNode node) { // Just append the text if (++this.separatorsPtr == 0) { // lazy initialization this.separators= new long[DEFAULT_ARRAY_SIZE]; this.htmlIndexes= new int[DEFAULT_ARRAY_SIZE]; } else { // resize if needed if (this.separatorsPtr == this.separators.length) { int size= this.separatorsPtr + DEFAULT_ARRAY_SIZE; System.arraycopy(this.separators, 0, (this.separators= new long[size]), 0, this.separatorsPtr); System.arraycopy(this.htmlIndexes, 0, (this.htmlIndexes= new int[size]), 0, this.separatorsPtr); } } this.separators[this.separatorsPtr]= (((long)this.sourceEnd) << 32) + node.sourceStart; this.htmlIndexes[this.separatorsPtr]= node.isText() ? ((FormatJavadocText)node).htmlTagIndex : -1; } void clean() { int length= this.separators == null ? 0 : this.separators.length; if (this.separatorsPtr != (length - 1)) { System.arraycopy(this.separators, 0, this.separators= new long[this.separatorsPtr + 1], 0, this.separatorsPtr + 1); System.arraycopy(this.htmlIndexes, 0, this.htmlIndexes= new int[this.separatorsPtr + 1], 0, this.separatorsPtr + 1); } length= this.htmlNodes == null ? 0 : this.htmlNodes.length; if (this.htmlNodesPtr != (length - 1)) { System.arraycopy(this.htmlNodes, 0, this.htmlNodes= new FormatJavadocNode[this.htmlNodesPtr + 1], 0, this.htmlNodesPtr + 1); for (int i= 0; i <= this.htmlNodesPtr; i++) { this.htmlNodes[i].clean(); } } } void closeTag() { this.htmlTagIndex|= JAVADOC_CLOSED_TAG; } int getHtmlTagIndex() { return this.htmlTagIndex & JAVADOC_TAGS_INDEX_MASK; } int getHtmlTagID() { return this.htmlTagIndex & JAVADOC_TAGS_ID_MASK; } FormatJavadocNode getLastNode() { if (this.htmlNodes != null) { return this.htmlNodes[this.htmlNodesPtr]; } return null; } /** * Returns whether the text is a closing html tag or not. * * @return <code>true</code> if the node is an html tag and has '/' before its name (e.g. * </bla>), <code>false</code> otherwise. */ public boolean isClosingHtmlTag() { return this.htmlTagIndex != -1 && (this.htmlTagIndex & JAVADOC_CLOSED_TAG) != 0; } /** * Returns whether the text is a html tag or not. * * @return <code>true</code> if the node is a html tag, <code>false</code> otherwise. */ public boolean isHtmlTag() { return this.htmlTagIndex != -1; } /** * Returns whether the node is an immutable html tag or not. * <p> * The text in an immutable tags is <b>never</b> formatted. * </p> * * @return <code>true</code> if the node is an immutable tag, <code>false</code> otherwise. */ public boolean isImmutableHtmlTag() { return this.htmlTagIndex != -1 && (this.htmlTagIndex & JAVADOC_TAGS_ID_MASK) == JAVADOC_IMMUTABLE_TAGS_ID; } /** * Returns whether the text is immutable or not. * <p> * A text in considered as immutable when it belongs to an immutable block or when it's an * immutable html tag. * </p> * * @return <code>true</code> if the node is an immutable tag, <code>false</code> otherwise. */ public boolean isImmutable() { return this.immutable || (this.htmlTagIndex != -1 && (this.htmlTagIndex & JAVADOC_TAGS_ID_MASK) == JAVADOC_IMMUTABLE_TAGS_ID); } /** * Returns whether the text at the given separator index position is after a separator tag or * not. * * @return <code>true</code> if the text is after a separator tag, <code>false</code> otherwise * or if the given index is out the range of the text separators. */ public boolean isTextAfterHtmlSeparatorTag(int separatorIndex) { int ptr= separatorIndex; if (ptr > this.separatorsPtr) return false; int tagIndex= this.htmlIndexes[ptr] & JAVADOC_TAGS_ID_MASK; return tagIndex != -1 && tagIndex == JAVADOC_SEPARATOR_TAGS_ID; } /* (non-Javadoc) * @see org.eclipse.jdt.internal.formatter.FormatJavadocNode#isText() */ public boolean isText() { return true; } /* (non-Javadoc) * @see org.eclipse.jdt.internal.formatter.FormatJavadocNode#setHeaderLine(int) */ void setHeaderLine(int javadocLineStart) { for (int i= 0; i < this.htmlNodesPtr; i++) { FormatJavadocNode node= this.htmlNodes[i]; if (!node.isText()) { ((FormatJavadocBlock)node).setHeaderLine(javadocLineStart); } } } protected void toString(StringBuffer buffer) { StringBuffer indentation= new StringBuffer(); for (int t= 0; t <= this.depth; t++) indentation.append('\t'); buffer.append(indentation); if (isImmutable()) { buffer.append("immutable "); //$NON-NLS-1$ } buffer.append("text"); //$NON-NLS-1$ super.toString(buffer); buffer.append(" ("); //$NON-NLS-1$ buffer.append(this.separatorsPtr + 1).append(" sections, "); //$NON-NLS-1$ buffer.append(this.htmlNodesPtr + 1).append(" html tags, "); //$NON-NLS-1$ buffer.append(this.depth).append(" depth, "); //$NON-NLS-1$ buffer.append(this.linesBefore).append(" before, "); //$NON-NLS-1$ String tagID= "no"; //$NON-NLS-1$ switch (getHtmlTagID()) { case JAVADOC_TAGS_ID_MASK: tagID= "mask"; //$NON-NLS-1$ break; case JAVADOC_SINGLE_BREAK_TAG_ID: tagID= "single break"; //$NON-NLS-1$ break; case JAVADOC_CODE_TAGS_ID: tagID= "code"; //$NON-NLS-1$ break; case JAVADOC_BREAK_TAGS_ID: tagID= "break"; //$NON-NLS-1$ break; case JAVADOC_IMMUTABLE_TAGS_ID: tagID= "immutable"; //$NON-NLS-1$ break; case JAVADOC_SEPARATOR_TAGS_ID: tagID= "separator"; //$NON-NLS-1$ break; } buffer.append(tagID).append(" tag id)"); //$NON-NLS-1$ buffer.append('\n'); } public void toStringDebug(StringBuffer buffer, char[] source) { if (buffer.length() > 0) { for (int l= 0; l < this.linesBefore; l++) { buffer.append('\n'); for (int t= 0; t < this.depth; t++) buffer.append('\t'); } } if (this.separatorsPtr == -1) { super.toStringDebug(buffer, source); return; } int ptr= 0; int nextStart= this.sourceStart; int idx= 0; while (idx <= this.separatorsPtr || (this.htmlNodesPtr != -1 && ptr <= this.htmlNodesPtr)) { if (idx > this.separatorsPtr) { // last node FormatJavadocNode node= this.htmlNodes[ptr++]; node.toStringDebug(buffer, source); return; } int end= (int)(this.separators[idx] >>> 32); if (this.htmlNodesPtr >= 0 && ptr <= this.htmlNodesPtr && end > this.htmlNodes[ptr].sourceStart) { FormatJavadocNode node= this.htmlNodes[ptr++]; node.toStringDebug(buffer, source); } else { if (idx > 1 && source[nextStart] != '<') { buffer.append('\n'); for (int t= 0; t < this.depth; t++) buffer.append('\t'); } buffer.append(source, nextStart, end - nextStart + 1); } nextStart= (int)this.separators[idx++]; } if (source[nextStart] == '<') { switch (getHtmlTagID()) { case JAVADOC_CODE_TAGS_ID: buffer.append('\n'); for (int t= 0; t < this.depth; t++) buffer.append('\t'); break; } } buffer.append(source, nextStart, this.sourceEnd - nextStart + 1); buffer.append(' '); } }