/******************************************************************************* * 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() && text != null) { // 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 (text.linesBefore < 2) { text.linesBefore= 2; } break; case JAVADOC_SEPARATOR_TAGS_ID: case JAVADOC_SINGLE_BREAK_TAG_ID: if (text.linesBefore < 1) { text.linesBefore= 1; } } } // If adding an html tag on same html tag, then close previous one and leave if (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); } } } }