/******************************************************************************* * 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.compiler.parser.Scanner; import org.eclipse.jdt.internal.formatter.comment.IJavaDocTagConstants; /** * Represents a block in a {@link FormatJavadoc} which might be a * <b>description</b> or a <b>tag</b> (see{@link #isDescription()}). * </p><p> * The block might have a tag, a reference and nodes (see * {@link FormatJavadocNode}. Each of these elements might be present or not, * but at least one of them is. * </p> */ public class FormatJavadocBlock extends FormatJavadocNode implements IJavaDocTagConstants { // flags final static int INLINED = 0x0001; final static int FIRST = 0x0002; final static int ON_HEADER_LINE = 0x0004; final static int TEXT_ON_TAG_LINE = 0x0008; final static int ONE_LINE_TAG = 0x0010; final static int PARAM_TAG = 0x0020; final static int IN_PARAM_TAG = 0x0040; final static int IN_DESCRIPTION = 0x0080; final static int IMMUTABLE = 0x0100; // constants final static int MAX_TAG_HIERARCHY = 10; private int tagValue = NO_TAG_VALUE; int tagEnd; FormatJavadocReference reference; FormatJavadocNode[] nodes; int nodesPtr = -1; int flags = 0; public FormatJavadocBlock(int start, int end, int line, int value) { super(start, end, line); this.tagValue = value; this.tagEnd = end; switch (value) { case TAG_PARAM_VALUE: // TODO why are following tags considered like @param by the formatter? case TAG_SERIAL_FIELD_VALUE: case TAG_THROWS_VALUE: case TAG_EXCEPTION_VALUE: this.flags |= PARAM_TAG; break; case TAG_CODE_VALUE: case TAG_LITERAL_VALUE: this.flags |= IMMUTABLE; break; } } private void addNode(FormatJavadocNode node) { // Initialize or resize array if necessary if (++this.nodesPtr == 0) { this.nodes = new FormatJavadocNode[DEFAULT_ARRAY_SIZE]; } else if (this.nodesPtr >= this.nodes.length) { System.arraycopy( this.nodes, 0, this.nodes = new FormatJavadocNode[this.nodes.length+INCREMENT_ARRAY_SIZE], 0, this.nodesPtr); } // Store the node this.nodes[this.nodesPtr] = node; this.sourceEnd = node.sourceEnd; } void addBlock(FormatJavadocBlock block, int htmlLevel) { if (this.nodes != null) { FormatJavadocText[] textHierarchy = getTextHierarchy(block, htmlLevel); if (textHierarchy != null) { FormatJavadocText lastText = textHierarchy[htmlLevel]; if (lastText != null) { lastText.appendNode(block); for (int i=htmlLevel-1; i>=0; i--) { textHierarchy[i].sourceEnd = block.sourceEnd; } this.sourceEnd = block.sourceEnd; if (isParamTag()) { block.flags |= IN_PARAM_TAG; } else if (isDescription()) { block.flags |= IN_DESCRIPTION; } block.flags |= INLINED; return; } } } addNode(block); if (isParamTag()) { block.flags |= IN_PARAM_TAG; } else if (isDescription()) { block.flags |= IN_DESCRIPTION; } block.flags |= INLINED; } void addText(FormatJavadocText text) { if (this.nodes != null) { FormatJavadocText[] textHierarchy = getTextHierarchy(text, text.depth); if (textHierarchy != null) { FormatJavadocText lastText = textHierarchy[text.depth]; if (lastText != null) { lastText.appendText(text); for (int i=text.depth-1; i>=0; i--) { textHierarchy[i].sourceEnd = text.sourceEnd; } this.sourceEnd = text.sourceEnd; return; } if (text.depth > 0) { FormatJavadocText parentText = textHierarchy[text.depth-1]; if (parentText != null) { parentText.appendText(text); for (int i=text.depth-2; i>=0; i--) { textHierarchy[i].sourceEnd = text.sourceEnd; } this.sourceEnd = text.sourceEnd; return; } } } } if (text.isHtmlTag()) { switch (text.getHtmlTagID()) { case JAVADOC_CODE_TAGS_ID: text.linesBefore = this.nodesPtr == -1 ? 0 : 2; break; case JAVADOC_SEPARATOR_TAGS_ID: text.linesBefore = 1; break; // case JAVADOC_BREAK_TAGS_ID: // if (this.nodesPtr >= 0) text.linesBefore = 1; } } addNode(text); if (isImmutable()) { text.immutable = true; } } void clean() { int length = this.nodes == null ? 0 : this.nodes.length; if (this.nodesPtr != (length-1)) { System.arraycopy(this.nodes, 0, this.nodes = new FormatJavadocNode[this.nodesPtr+1], 0, this.nodesPtr+1); } for (int i=0; i<=this.nodesPtr; i++) { this.nodes[i].clean(); } } FormatJavadocNode getLastNode() { if (this.nodes != null) { return this.nodes[this.nodesPtr]; } return null; } /* * Return the text hierarchy for the given node */ FormatJavadocText[] getTextHierarchy(FormatJavadocNode node, int htmlDepth) { if (this.nodes == null) return null; FormatJavadocText[] textHierarchy = null; int ptr = 0; FormatJavadocText text = node.isText() ? (FormatJavadocText) node : null; FormatJavadocNode lastNode = this.nodes[this.nodesPtr]; while (lastNode.isText()) { FormatJavadocText lastText = (FormatJavadocText) lastNode; int lastTagCategory = lastText.getHtmlTagID(); boolean lastSingleTag = lastTagCategory <= JAVADOC_SINGLE_TAGS_ID; boolean lastTextCanHaveChildren = lastText.isHtmlTag() && !lastText.isClosingHtmlTag() && !lastSingleTag; if (lastText.depth == htmlDepth || // found same html tag level => use it lastText.htmlNodesPtr == -1) { // no more sub-levels => add one // Text breakage if (lastText.isHtmlTag()) { // Set some lines before if previous was specific html tag // The added text is concerned if the parent has no child yet or is top level and closing html tag boolean setLinesBefore = lastText.separatorsPtr == -1 || (ptr == 0 && lastText.isClosingHtmlTag()); if (!setLinesBefore && ptr > 0 && lastText.isClosingHtmlTag()) { // for non-top level closing html tag, text is concerned only if no new text has been written after FormatJavadocText parentText = textHierarchy[ptr-1]; int textStart = (int) parentText.separators[parentText.separatorsPtr]; if (textStart < lastText.sourceStart) { setLinesBefore = true; } } if (setLinesBefore) { switch (lastText.getHtmlTagID()) { case JAVADOC_CODE_TAGS_ID: if (node.linesBefore < 2) { node.linesBefore = 2; } break; case JAVADOC_SEPARATOR_TAGS_ID: case JAVADOC_SINGLE_BREAK_TAG_ID: if (node.linesBefore < 1) { node.linesBefore = 1; } } } // If adding an html tag on same html tag, then close previous one and leave if (text != null && text.isHtmlTag() && !text.isClosingHtmlTag() && text.getHtmlTagIndex() == lastText.getHtmlTagIndex() && !lastText.isClosingHtmlTag()) { lastText.closeTag(); return textHierarchy; } } // If we have a text after another text, keep the same level to append if (lastTextCanHaveChildren || (htmlDepth == 0 && !lastText.isHtmlTag() && text != null && !text.isHtmlTag())) { if (textHierarchy == null) textHierarchy = new FormatJavadocText[htmlDepth+1]; textHierarchy[ptr] = lastText; return textHierarchy; } // Last text cannot have children, so return the built hierarchy return textHierarchy; } if (textHierarchy == null) textHierarchy = new FormatJavadocText[htmlDepth+1]; textHierarchy[ptr++] = lastText; lastNode = lastText.htmlNodes[lastText.htmlNodesPtr]; } return textHierarchy; } /** * Returns whether the text is on the same line of the tag or not. * * @return <code>true</code> if the text is on the same line than the tag, * <code>false</code> otherwise. */ public boolean hasTextOnTagLine() { return (this.flags & TEXT_ON_TAG_LINE) != 0; } /** * Returns whether the block is the javadoc comment description or not. * The description begins after the starting delimiter and continues until the tag * section. * * @return <code>true</code> if the block is the javadoc description, * <code>false</code> otherwise. */ public boolean isDescription() { return this.tagValue == NO_TAG_VALUE; } /** * Returns whether the block is the first block of the javadoc comment or not * (independently of the fact it's a description or not). * * @return <code>true</code> if the block is the first of the javadoc * comment, <code>false</code> otherwise. */ public boolean isFirst() { return (this.flags & FIRST) != 0; } /** * Returns whether the first block starts on the same line than the javadoc * starting delimiter or not. * * @return <code>true</code> if the the first block starts on the same line * than the javadoc starting delimiter, <code>false</code> otherwise. */ public boolean isHeaderLine() { return (this.flags & ON_HEADER_LINE) != 0; } /** * Returns whether the block is immutable or not. * <p> * Currently, only {@code} and {@literal} inline tags block are considered as * immutable. * </p> * @return <code>true</code> if the block is immutable, * <code>false</code> otherwise. */ public boolean isImmutable() { return (this.flags & IMMUTABLE) == IMMUTABLE; } /** * Returns whether the block is a description or inlined in a description. * @see #isParamTag() * * @return <code>true</code> if the block is a description or inlined in a * description, <code>false</code> otherwise. */ public boolean isInDescription() { return this.tagValue == NO_TAG_VALUE || (this.flags & IN_DESCRIPTION) == IN_DESCRIPTION; } /** * Returns whether the text is on the same line of the tag. * * @return <code>true</code> if the text is on the same line than the tag, * <code>false</code> otherwise. */ public boolean isInlined() { return (this.flags & INLINED) != 0; } /** * Returns whether the block is a param tag or inlined in a param tag. * @see #isParamTag() * * @return <code>true</code> if the block is a param tag or inlined in a param * tag, <code>false</code> otherwise. */ public boolean isInParamTag() { return (this.flags & (PARAM_TAG | IN_PARAM_TAG)) != 0; } /** * Returns whether the the tag is on one line only. * * @return <code>true</code> if the tag is on one line only, * <code>false</code> otherwise. */ public boolean isOneLineTag() { return (this.flags & ONE_LINE_TAG) != 0; } /** * Returns whether the block is a param tag or not. Note that this also includes * @serialField, @throws and @exception tags. * * @return <code>true</code> if the block is a param tag, * <code>false</code> otherwise. */ public boolean isParamTag() { return (this.flags & PARAM_TAG) == PARAM_TAG; } void setHeaderLine(int javadocLineStart) { if (javadocLineStart == this.lineStart) { this.flags |= ON_HEADER_LINE; } for (int i=0; i<this.nodesPtr; i++) { this.nodes[i].setHeaderLine(javadocLineStart); } } protected void toString(StringBuffer buffer) { boolean inlined = (this.flags & INLINED) != 0; if (inlined) buffer.append(" {"); //$NON-NLS-1$ buffer.append('@'); if (this.tagValue == TAG_OTHERS_VALUE) { buffer.append("others_tag"); //$NON-NLS-1$ } else { buffer.append(TAG_NAMES[this.tagValue]); } super.toString(buffer); if (this.reference == null) { buffer.append('\n'); } else { buffer.append(" ("); //$NON-NLS-1$ this.reference.toString(buffer); buffer.append(")\n"); //$NON-NLS-1$ } StringBuffer flagsBuffer = new StringBuffer(); if (isDescription()) { if (flagsBuffer.length() > 0) flagsBuffer.append(','); flagsBuffer.append("description"); //$NON-NLS-1$ } if (isFirst()) { if (flagsBuffer.length() > 0) flagsBuffer.append(','); flagsBuffer.append("first"); //$NON-NLS-1$ } if (isHeaderLine()) { if (flagsBuffer.length() > 0) flagsBuffer.append(','); flagsBuffer.append("header line"); //$NON-NLS-1$ } if (isImmutable()) { if (flagsBuffer.length() > 0) flagsBuffer.append(','); flagsBuffer.append("immutable"); //$NON-NLS-1$ } if (isInDescription()) { if (flagsBuffer.length() > 0) flagsBuffer.append(','); flagsBuffer.append("in description"); //$NON-NLS-1$ } if (isInlined()) { if (flagsBuffer.length() > 0) flagsBuffer.append(','); flagsBuffer.append("inlined"); //$NON-NLS-1$ } if (isInParamTag()) { if (flagsBuffer.length() > 0) flagsBuffer.append(','); flagsBuffer.append("in param tag"); //$NON-NLS-1$ } if (isOneLineTag()) { if (flagsBuffer.length() > 0) flagsBuffer.append(','); flagsBuffer.append("one line tag"); //$NON-NLS-1$ } if (isParamTag()) { if (flagsBuffer.length() > 0) flagsBuffer.append(','); flagsBuffer.append("param tag"); //$NON-NLS-1$ } if (flagsBuffer.length() > 0) { if (inlined) buffer.append('\t'); buffer.append(" flags: "); //$NON-NLS-1$ buffer.append(flagsBuffer); buffer.append('\n'); } if (this.nodesPtr > -1) { for (int i = 0; i <= this.nodesPtr; i++) { if (inlined) buffer.append('\t'); this.nodes[i].toString(buffer); } } } public String toStringDebug(char[] source) { StringBuffer buffer = new StringBuffer(); toStringDebug(buffer, source); return buffer.toString(); } public void toStringDebug(StringBuffer buffer, char[] source) { if (this.tagValue > 0) { buffer.append(source, this.sourceStart, this.tagEnd-this.sourceStart+1); buffer.append(' '); } if (this.reference != null) { this.reference.toStringDebug(buffer, source); } for (int i=0; i<=this.nodesPtr; i++) { this.nodes[i].toStringDebug(buffer, source); } } void update(Scanner scanner) { int blockEnd = scanner.getLineNumber(this.sourceEnd); if (blockEnd == this.lineStart) { this.flags |= FormatJavadocBlock.ONE_LINE_TAG; } for (int i=0; i<=this.nodesPtr; i++) { if (!this.nodes[i].isText()) { ((FormatJavadocBlock)this.nodes[i]).update(scanner); } } } }