| /******************************************************************************* |
| * 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.io.IOException; |
| import java.io.StringReader; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| |
| import org.eclipse.text.edits.TextEdit; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.DefaultLineTracker; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.ILineTracker; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.TextUtilities; |
| |
| import org.eclipse.jdt.core.formatter.CodeFormatter; |
| import org.eclipse.jdt.internal.formatter.CodeFormatterVisitor; |
| |
| |
| /** |
| * Javadoc region in a source code document. |
| * |
| * @since 3.0 |
| */ |
| public class JavaDocRegion extends MultiCommentRegion implements IJavaDocTagConstants { |
| |
| /** The positions of code ranges */ |
| private final ArrayList fCodePositions= new ArrayList(); |
| |
| /** Should HTML tags be formatted? */ |
| private final boolean fFormatHtml; |
| |
| /** Should source code regions be formatted? */ |
| private final boolean fFormatSource; |
| |
| /** |
| * Creates a new Javadoc region. |
| * |
| * @param document the document which contains the comment region |
| * @param position the position of this comment region in the document |
| * @param formatter the given formatter |
| */ |
| public JavaDocRegion(final IDocument document, final Position position, final CodeFormatterVisitor formatter) { |
| super(document, position, formatter); |
| |
| fFormatSource= this.preferences.comment_format_source; |
| fFormatHtml= this.preferences.comment_format_html; |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.corext.text.comment.CommentRegion#canFormat(org.eclipse.jdt.internal.corext.text.comment.CommentRange, org.eclipse.jdt.internal.corext.text.comment.CommentRange) |
| */ |
| protected boolean canFormat(final CommentRange previous, final CommentRange next) { |
| |
| if (previous != null) { |
| |
| final boolean isCurrentCode= next.hasAttribute(COMMENT_CODE); |
| final boolean isLastCode= previous.hasAttribute(COMMENT_CODE); |
| |
| final int base= getOffset(); |
| |
| if (!isLastCode && isCurrentCode) |
| fCodePositions.add(new Position(base + previous.getOffset())); |
| else if (isLastCode && !isCurrentCode) |
| fCodePositions.add(new Position(base + next.getOffset() + next.getLength())); |
| |
| if (previous.hasAttribute(COMMENT_IMMUTABLE) && next.hasAttribute(COMMENT_IMMUTABLE)) |
| return false; |
| |
| return true; |
| } |
| return false; |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.corext.text.comment.CommentRegion#formatRegion(java.lang.String, int) |
| */ |
| protected final void formatRegion(final String indentation, final int width) { |
| |
| super.formatRegion(indentation, width); |
| |
| if (fFormatSource) { |
| |
| try { |
| |
| if (fCodePositions.size() > 0) { |
| |
| int begin= 0; |
| int end= 0; |
| |
| Position position= null; |
| |
| final IDocument document= getDocument(); |
| |
| for (int index= fCodePositions.size() - 1; index >= 0;) { |
| |
| position= (Position)fCodePositions.get(index--); |
| begin= position.getOffset(); |
| |
| if (index >= 0) { |
| position= (Position)fCodePositions.get(index--); |
| end= position.getOffset(); |
| } else { |
| /* |
| * Handle missing closing tag |
| * see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=57011 |
| */ |
| position= null; |
| end= getOffset() + getLength() - MultiCommentLine.MULTI_COMMENT_END_PREFIX.trim().length(); |
| while (end > begin && Character.isWhitespace(document.getChar(end - 1))) |
| end--; |
| } |
| |
| String snippet= document.get(begin, end - begin); |
| snippet= preprocessCodeSnippet(snippet); |
| snippet= formatCodeSnippet(snippet); |
| snippet= postprocessCodeSnippet(snippet, indentation); |
| |
| logEdit(snippet, begin - getOffset(), end - begin); |
| } |
| } |
| } catch (BadLocationException e) { |
| // Can not happen |
| CommentFormatterUtil.log(e); |
| } |
| } |
| } |
| |
| /** |
| * Preprocess a given code snippet. |
| * |
| * @param snippet the code snippet |
| * @return the preprocessed code snippet |
| */ |
| private String preprocessCodeSnippet(String snippet) { |
| // strip content prefix |
| StringBuffer buffer= new StringBuffer(); |
| ILineTracker tracker= new DefaultLineTracker(); |
| String contentPrefix= MultiCommentLine.MULTI_COMMENT_CONTENT_PREFIX.trim(); |
| |
| buffer.setLength(0); |
| buffer.append(snippet); |
| tracker.set(snippet); |
| for (int line= tracker.getNumberOfLines() - 1; line > 0; line--) { |
| int lineOffset; |
| try { |
| lineOffset= tracker.getLineOffset(line); |
| } catch (BadLocationException e) { |
| // Can not happen |
| CommentFormatterUtil.log(e); |
| return snippet; |
| } |
| int prefixOffset= buffer.indexOf(contentPrefix, lineOffset); |
| if (prefixOffset >= 0 && buffer.substring(lineOffset, prefixOffset).trim().length() == 0) |
| buffer.delete(lineOffset, prefixOffset + contentPrefix.length()); |
| } |
| |
| return convertHtml2Java(buffer.toString()); |
| } |
| |
| /** |
| * Format the given code snippet |
| * |
| * @param snippet the code snippet |
| * @return the formatted code snippet |
| */ |
| private String formatCodeSnippet(String snippet) { |
| String lineDelimiter= TextUtilities.getDefaultLineDelimiter(getDocument()); |
| TextEdit edit= CommentFormatterUtil.format2(CodeFormatter.K_UNKNOWN, snippet, 0, lineDelimiter, this.preferences.getMap()); |
| if (edit != null) |
| snippet= CommentFormatterUtil.evaluateFormatterEdit(snippet, edit, null); |
| return snippet; |
| } |
| |
| /** |
| * Postprocesses the given code snippet with the given indentation. |
| * |
| * @param snippet the code snippet |
| * @param indentation the indentation |
| * @return the postprocessed code snippet |
| */ |
| private String postprocessCodeSnippet(String snippet, String indentation) { |
| // patch content prefix |
| StringBuffer buffer= new StringBuffer(); |
| ILineTracker tracker= new DefaultLineTracker(); |
| String patch= indentation + MultiCommentLine.MULTI_COMMENT_CONTENT_PREFIX; |
| |
| buffer.setLength(0); |
| buffer.append(getDelimiter()); |
| buffer.append(convertJava2Html(snippet)); |
| buffer.append(getDelimiter()); |
| tracker.set(buffer.toString()); |
| |
| for (int line= tracker.getNumberOfLines() - 1; line > 0; line--) |
| try { |
| buffer.insert(tracker.getLineOffset(line), patch); |
| } catch (BadLocationException e) { |
| // Can not happen |
| CommentFormatterUtil.log(e); |
| return snippet; |
| } |
| |
| return buffer.toString(); |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.corext.text.comment.MultiCommentRegion#markHtmlRanges() |
| */ |
| protected final void markHtmlRanges() { |
| |
| markTagRanges(JAVADOC_IMMUTABLE_TAGS, COMMENT_IMMUTABLE, true); |
| |
| if (fFormatSource) |
| markTagRanges(JAVADOC_CODE_TAGS, COMMENT_CODE, false); |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.corext.text.comment.MultiCommentRegion#markHtmlTag(org.eclipse.jdt.internal.corext.text.comment.CommentRange, java.lang.String) |
| */ |
| protected final void markHtmlTag(final CommentRange range, final String token) { |
| |
| if (range.hasAttribute(COMMENT_HTML)) { |
| |
| range.markHtmlTag(JAVADOC_IMMUTABLE_TAGS, token, COMMENT_IMMUTABLE, true, true); |
| if (fFormatHtml) { |
| |
| range.markHtmlTag(JAVADOC_SEPARATOR_TAGS, token, COMMENT_SEPARATOR, true, true); |
| range.markHtmlTag(JAVADOC_BREAK_TAGS, token, COMMENT_BREAK, false, true); |
| range.markHtmlTag(JAVADOC_SINGLE_BREAK_TAG, token, COMMENT_BREAK, true, false); |
| range.markHtmlTag(JAVADOC_NEWLINE_TAGS, token, COMMENT_NEWLINE, true, false); |
| |
| } else |
| range.markHtmlTag(JAVADOC_CODE_TAGS, token, COMMENT_SEPARATOR, true, true); |
| } |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.corext.text.comment.MultiCommentRegion#markJavadocTag(org.eclipse.jdt.internal.corext.text.comment.CommentRange, java.lang.String) |
| */ |
| protected final void markJavadocTag(final CommentRange range, final String token) { |
| |
| range.markPrefixTag(JAVADOC_PARAM_TAGS, COMMENT_TAG_PREFIX, token, COMMENT_PARAMETER); |
| |
| if (token.charAt(0) == JAVADOC_TAG_PREFIX && !range.hasAttribute(COMMENT_PARAMETER)) |
| range.setAttribute(COMMENT_ROOT); |
| } |
| |
| /** |
| * Marks the comment region with the HTML range tag. |
| * |
| * @param tags the HTML tag which confines the HTML range |
| * @param attribute the attribute to set if the comment range is in the |
| * HTML range |
| * @param html <code>true</code> iff the HTML tags in this HTML range |
| * should be marked too, <code>false</code> otherwise |
| */ |
| protected final void markTagRanges(final String[] tags, final int attribute, final boolean html) { |
| |
| int level= 0; |
| int count= 0; |
| String token= null; |
| CommentRange current= null; |
| |
| for (int index= 0; index < tags.length; index++) { |
| |
| level= 0; |
| for (final Iterator iterator= getRanges().iterator(); iterator.hasNext();) { |
| |
| current= (CommentRange)iterator.next(); |
| count= current.getLength(); |
| |
| if (count > 0 || level > 0) { // PR44035: when inside a tag, mark blank lines as well to get proper snippet formatting |
| |
| token= getText(current.getOffset(), current.getLength()); |
| level= current.markTagRange(token, tags[index], level, attribute, html); |
| } |
| } |
| } |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.corext.text.comment.CommentRegion#canAppend(org.eclipse.jdt.internal.corext.text.comment.CommentLine, org.eclipse.jdt.internal.corext.text.comment.CommentRange, org.eclipse.jdt.internal.corext.text.comment.CommentRange, int, int) |
| */ |
| protected boolean canAppend(CommentLine line, CommentRange previous, CommentRange next, int index, int count) { |
| // don't append code sections |
| if (next.hasAttribute(COMMENT_CODE | COMMENT_FIRST_TOKEN) && line.getSize() != 0) |
| return false; |
| return super.canAppend(line, previous, next, index, count); |
| } |
| |
| /** |
| * Converts <code>formatted</code> into valid html code suitable to be |
| * put inside <pre></pre> tags by replacing any html symbols |
| * by the relevant entities. |
| * |
| * @param formatted the formatted java code |
| * @return html version of the formatted code |
| */ |
| private String convertJava2Html(String formatted) { |
| Java2HTMLEntityReader reader= new Java2HTMLEntityReader(new StringReader(formatted)); |
| char[] buf= new char[256]; |
| StringBuffer buffer= new StringBuffer(); |
| int l; |
| try { |
| do { |
| l= reader.read(buf); |
| if (l != -1) |
| buffer.append(buf, 0, l); |
| } while (l > 0); |
| return buffer.toString(); |
| } catch (IOException e) { |
| return formatted; |
| } |
| } |
| |
| /** |
| * Converts <code>html</code> into java code suitable for formatting |
| * by replacing any html entities by their plain text representation. |
| * |
| * @param html html code, may contain html entities |
| * @return plain textified version of <code>html</code> |
| */ |
| private String convertHtml2Java(String html) { |
| HTMLEntity2JavaReader reader= new HTMLEntity2JavaReader(new StringReader(html)); |
| char[] buf= new char[html.length()]; // html2text never gets longer, only shorter! |
| |
| try { |
| int read= reader.read(buf); |
| return new String(buf, 0, read); |
| } catch (IOException e) { |
| return html; |
| } |
| } |
| |
| /* |
| * @see org.eclipse.jdt.internal.corext.text.comment.CommentRegion#createLine() |
| * @since 3.1 |
| */ |
| protected CommentLine createLine() { |
| return new JavaDocLine(this); |
| } |
| } |