KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > ui > text > comment > CommentFormattingStrategy


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11
12 package org.eclipse.jdt.internal.ui.text.comment;
13
14 import java.util.LinkedList JavaDoc;
15 import java.util.Map JavaDoc;
16
17 import org.eclipse.text.edits.MalformedTreeException;
18 import org.eclipse.text.edits.TextEdit;
19
20 import org.eclipse.jface.text.BadLocationException;
21 import org.eclipse.jface.text.IDocument;
22 import org.eclipse.jface.text.TextUtilities;
23 import org.eclipse.jface.text.TypedPosition;
24 import org.eclipse.jface.text.formatter.ContextBasedFormattingStrategy;
25 import org.eclipse.jface.text.formatter.FormattingContextProperties;
26 import org.eclipse.jface.text.formatter.IFormattingContext;
27
28 import org.eclipse.jdt.core.JavaCore;
29 import org.eclipse.jdt.core.ToolFactory;
30 import org.eclipse.jdt.core.compiler.IScanner;
31 import org.eclipse.jdt.core.compiler.ITerminalSymbols;
32 import org.eclipse.jdt.core.compiler.InvalidInputException;
33 import org.eclipse.jdt.core.formatter.CodeFormatter;
34 import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
35
36 import org.eclipse.jdt.ui.text.IJavaPartitions;
37
38 import org.eclipse.jdt.internal.ui.JavaPlugin;
39
40 /**
41  * Formatting strategy for general source code comments.
42  *
43  * @since 3.0
44  */

45 public class CommentFormattingStrategy extends ContextBasedFormattingStrategy {
46
47     /** Documents to be formatted by this strategy */
48     private final LinkedList JavaDoc fDocuments= new LinkedList JavaDoc();
49
50     /** Partitions to be formatted by this strategy */
51     private final LinkedList JavaDoc fPartitions= new LinkedList JavaDoc();
52
53     /** Last formatted document's hash-code. */
54     private int fLastDocumentHash;
55
56     /** Last formatted document header's hash-code. */
57     private int fLastHeaderHash;
58
59     /** End of the first class or interface token in the last document. */
60     private int fLastMainTokenEnd= -1;
61
62     /** End of the header in the last document. */
63     private int fLastDocumentsHeaderEnd;
64
65
66     /*
67      * @see org.eclipse.jface.text.formatter.IFormattingStrategyExtension#format()
68      */

69     public void format() {
70         
71         final IDocument document= (IDocument) fDocuments.getFirst();
72
73         TextEdit edit= calculateTextEdit();
74         if (edit == null)
75             return;
76         
77         try {
78             edit.apply(document);
79         } catch (MalformedTreeException x) {
80             JavaPlugin.log(x);
81         } catch (BadLocationException x) {
82             JavaPlugin.log(x);
83         }
84     }
85
86     /**
87      * Calculates the <code>TextEdit</code> used to format the region with the
88      * properties indicated in the formatting context previously supplied by
89      * <code>formatterStarts(IFormattingContext)</code>.
90      *
91      * @see CommentFormattingStrategy#format()
92      * @return A <code>TextEdit</code>, or <code>null</code> if no formating is required
93      * @since 3.3
94      */

95     public TextEdit calculateTextEdit() {
96         super.format();
97
98         final IDocument document= (IDocument) fDocuments.removeFirst();
99         final TypedPosition position= (TypedPosition)fPartitions.removeFirst();
100         if (document == null || position == null)
101             return null;
102
103         Map JavaDoc preferences= getPreferences();
104         final boolean isFormattingHeader= DefaultCodeFormatterConstants.TRUE.equals(preferences.get(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_HEADER));
105         int documentsHeaderEnd= computeHeaderEnd(document, preferences);
106
107         TextEdit edit= null;
108         if (position.offset >= documentsHeaderEnd) {
109             // not a header
110
try {
111                 // compute offset in document of region passed to the formatter
112
int sourceOffset= document.getLineOffset(document.getLineOfOffset(position.getOffset()));
113
114                 // format region
115
int partitionOffset= position.getOffset() - sourceOffset;
116                 int sourceLength= partitionOffset + position.getLength();
117                 String JavaDoc source= document.get(sourceOffset, sourceLength);
118                 CodeFormatter commentFormatter= ToolFactory.createCodeFormatter(preferences, ToolFactory.M_FORMAT_EXISTING);
119                 int indentationLevel= inferIndentationLevel(source.substring(0, partitionOffset), getTabSize(preferences), getIndentSize(preferences));
120                 edit= commentFormatter.format(getKindForPartitionType(position.getType()), source, partitionOffset, position.getLength(), indentationLevel, TextUtilities.getDefaultLineDelimiter(document));
121
122                 // move edit offset to match document
123
if (edit != null)
124                     edit.moveTree(sourceOffset);
125             } catch (BadLocationException x) {
126                 JavaPlugin.log(x);
127             }
128         } else if (isFormattingHeader) {
129             boolean wasJavaDoc= DefaultCodeFormatterConstants.TRUE.equals(preferences.get(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_JAVADOC_COMMENT));
130             if (!wasJavaDoc)
131                 preferences.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_JAVADOC_COMMENT, DefaultCodeFormatterConstants.TRUE);
132             
133             boolean wasBlockComment= DefaultCodeFormatterConstants.TRUE.equals(preferences.get(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_BLOCK_COMMENT));
134             if (!wasBlockComment)
135                 preferences.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_BLOCK_COMMENT, DefaultCodeFormatterConstants.TRUE);
136             
137             boolean wasLineComment= DefaultCodeFormatterConstants.TRUE.equals(preferences.get(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_LINE_COMMENT));
138             if (!wasLineComment)
139                 preferences.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_LINE_COMMENT, DefaultCodeFormatterConstants.TRUE);
140             
141             try {
142                 // compute offset in document of region passed to the formatter
143
int sourceOffset= document.getLineOffset(document.getLineOfOffset(position.getOffset()));
144
145                 // format region
146
int partitionOffset= position.getOffset() - sourceOffset;
147                 int sourceLength= partitionOffset + position.getLength();
148                 String JavaDoc source= document.get(sourceOffset, sourceLength);
149                 CodeFormatter commentFormatter= ToolFactory.createCodeFormatter(preferences);
150                 int indentationLevel= inferIndentationLevel(source.substring(0, partitionOffset), getTabSize(preferences), getIndentSize(preferences));
151                 edit= commentFormatter.format(getKindForPartitionType(position.getType()), source, partitionOffset, position.getLength(), indentationLevel, TextUtilities.getDefaultLineDelimiter(document));
152
153                 // move edit offset to match document
154
if (edit != null)
155                     edit.moveTree(sourceOffset);
156             } catch (BadLocationException x) {
157                 JavaPlugin.log(x);
158             } finally {
159                 if (!wasJavaDoc)
160                     preferences.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_JAVADOC_COMMENT, DefaultCodeFormatterConstants.FALSE);
161                 if (!wasBlockComment)
162                     preferences.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_BLOCK_COMMENT, DefaultCodeFormatterConstants.FALSE);
163                 if (!wasLineComment)
164                     preferences.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_LINE_COMMENT, DefaultCodeFormatterConstants.FALSE);
165             }
166
167         }
168         return edit;
169     }
170
171     /*
172      * @see org.eclipse.jface.text.formatter.IFormattingStrategyExtension#formatterStarts(org.eclipse.jface.text.formatter.IFormattingContext)
173      */

174     public void formatterStarts(IFormattingContext context) {
175         super.formatterStarts(context);
176
177         fPartitions.addLast(context.getProperty(FormattingContextProperties.CONTEXT_PARTITION));
178         fDocuments.addLast(context.getProperty(FormattingContextProperties.CONTEXT_MEDIUM));
179     }
180
181     /*
182      * @see org.eclipse.jface.text.formatter.IFormattingStrategyExtension#formatterStops()
183      */

184     public void formatterStops() {
185         fPartitions.clear();
186         fDocuments.clear();
187
188         super.formatterStops();
189     }
190
191     /**
192      * Map from {@link IJavaPartitions}comment partition types to
193      * {@link CodeFormatter}code snippet kinds.
194      *
195      * @param type the partition type
196      * @return the code snippet kind
197      * @since 3.1
198      */

199     private static int getKindForPartitionType(String JavaDoc type) {
200         if (IJavaPartitions.JAVA_SINGLE_LINE_COMMENT.equals(type))
201                 return CodeFormatter.K_SINGLE_LINE_COMMENT;
202         if (IJavaPartitions.JAVA_MULTI_LINE_COMMENT.equals(type))
203                 return CodeFormatter.K_MULTI_LINE_COMMENT;
204         if (IJavaPartitions.JAVA_DOC.equals(type))
205                 return CodeFormatter.K_JAVA_DOC;
206         return CodeFormatter.K_UNKNOWN;
207     }
208
209     /**
210      * Infer the indentation level based on the given reference indentation
211      * and tab size.
212      *
213      * @param reference the reference indentation
214      * @param tabSize the tab size
215      * @param indentSize the indent size in space equivalents
216      * @return the inferred indentation level
217      * @since 3.1
218      */

219     private int inferIndentationLevel(String JavaDoc reference, int tabSize, int indentSize) {
220         StringBuffer JavaDoc expanded= expandTabs(reference, tabSize);
221
222         int referenceWidth= expanded.length();
223         if (tabSize == 0)
224             return referenceWidth;
225
226         int level= referenceWidth / indentSize;
227         if (referenceWidth % indentSize > 0)
228             level++;
229         return level;
230     }
231
232     /**
233      * Expands the given string's tabs according to the given tab size.
234      *
235      * @param string the string
236      * @param tabSize the tab size
237      * @return the expanded string
238      * @since 3.1
239      */

240     private static StringBuffer JavaDoc expandTabs(String JavaDoc string, int tabSize) {
241         StringBuffer JavaDoc expanded= new StringBuffer JavaDoc();
242         for (int i= 0, n= string.length(), chars= 0; i < n; i++) {
243             char ch= string.charAt(i);
244             if (ch == '\t') {
245                 for (; chars < tabSize; chars++)
246                     expanded.append(' ');
247                 chars= 0;
248             } else {
249                 expanded.append(ch);
250                 chars++;
251                 if (chars >= tabSize)
252                     chars= 0;
253             }
254
255         }
256         return expanded;
257     }
258
259     /**
260      * Returns the visual tab size.
261      *
262      * @param preferences the preferences
263      * @return the visual tab size
264      * @since 3.1
265      */

266     private static int getTabSize(Map JavaDoc preferences) {
267         /*
268          * If the tab-char is SPACE, FORMATTER_INDENTATION_SIZE is not used
269          * by the core formatter.
270          * We piggy back the visual tab length setting in that preference in
271          * that case. See CodeFormatterUtil.
272          */

273         String JavaDoc key;
274         if (JavaCore.SPACE.equals(preferences.get(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
275             key= DefaultCodeFormatterConstants.FORMATTER_INDENTATION_SIZE;
276         else
277             key= DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE;
278
279         if (preferences.containsKey(key))
280             try {
281                 return Integer.parseInt((String JavaDoc) preferences.get(key));
282             } catch (NumberFormatException JavaDoc e) {
283                 // use default
284
}
285         return 4;
286     }
287     
288     /**
289      * Returns the indentation size in space equivalents.
290      *
291      * @param preferences the preferences
292      * @return the indentation size in space equivalents
293      * @since 3.3
294      */

295     private static int getIndentSize(Map JavaDoc preferences) {
296         /*
297          * FORMATTER_INDENTATION_SIZE is only used if FORMATTER_TAB_CHAR is MIXED. Otherwise, the
298          * indentation size is in FORMATTER_TAB_CHAR. See CodeFormatterUtil.
299          */

300         String JavaDoc key;
301         if (DefaultCodeFormatterConstants.MIXED.equals(preferences.get(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
302             key= DefaultCodeFormatterConstants.FORMATTER_INDENTATION_SIZE;
303         else
304             key= DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE;
305         
306         if (preferences.containsKey(key))
307             try {
308                 return Integer.parseInt((String JavaDoc) preferences.get(key));
309             } catch (NumberFormatException JavaDoc e) {
310                 // use default
311
}
312             return 4;
313     }
314
315     /**
316      * Returns the end offset for the document's header.
317      *
318      * @param document the document
319      * @param preferences the given preferences to format
320      * @return the header's end offset
321      */

322     private int computeHeaderEnd(IDocument document, Map JavaDoc preferences) {
323         if (document == null)
324             return -1;
325
326         try {
327             if (fLastMainTokenEnd >= 0 && document.hashCode() == fLastDocumentHash && fLastMainTokenEnd < document.getLength() && document.get(0, fLastMainTokenEnd).hashCode() == fLastHeaderHash)
328                 return fLastDocumentsHeaderEnd;
329         } catch (BadLocationException e) {
330             // should not happen -> recompute
331
}
332
333         IScanner scanner= ToolFactory.createScanner(true, false, false, (String JavaDoc) preferences.get(JavaCore.COMPILER_SOURCE), (String JavaDoc) preferences.get(JavaCore.COMPILER_COMPLIANCE));
334         scanner.setSource(document.get().toCharArray());
335
336         try {
337             int offset= -1;
338             boolean foundComment= false;
339             int terminal= scanner.getNextToken();
340             while (terminal != ITerminalSymbols.TokenNameEOF && !(terminal == ITerminalSymbols.TokenNameclass || terminal == ITerminalSymbols.TokenNameinterface || terminal == ITerminalSymbols.TokenNameenum || (foundComment && (terminal == ITerminalSymbols.TokenNameimport || terminal == ITerminalSymbols.TokenNamepackage)))) {
341
342                 if (terminal == ITerminalSymbols.TokenNameCOMMENT_JAVADOC)
343                     offset= scanner.getCurrentTokenStartPosition();
344
345                 foundComment= terminal == ITerminalSymbols.TokenNameCOMMENT_JAVADOC || terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK;
346
347                 terminal= scanner.getNextToken();
348             }
349
350             int mainTokenEnd= scanner.getCurrentTokenEndPosition();
351             if (terminal != ITerminalSymbols.TokenNameEOF) {
352                 mainTokenEnd++;
353                 if (offset == -1 || (foundComment && (terminal == ITerminalSymbols.TokenNameimport || terminal == ITerminalSymbols.TokenNamepackage)))
354                     offset= scanner.getCurrentTokenStartPosition();
355             } else
356                 offset= -1;
357
358             try {
359                 fLastHeaderHash= document.get(0, mainTokenEnd).hashCode();
360             } catch (BadLocationException e) {
361                 // should not happen -> recompute next time
362
mainTokenEnd= -1;
363             }
364
365             fLastDocumentHash= document.hashCode();
366             fLastMainTokenEnd= mainTokenEnd;
367             fLastDocumentsHeaderEnd= offset;
368             return offset;
369
370         } catch (InvalidInputException ex) {
371             // enable formatting
372
return -1;
373         }
374     }
375 }
376
Popular Tags