KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > corext > template > java > JavaFormatter


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 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 package org.eclipse.jdt.internal.corext.template.java;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.HashMap JavaDoc;
15 import java.util.Iterator JavaDoc;
16 import java.util.List JavaDoc;
17 import java.util.Map JavaDoc;
18
19 import org.eclipse.text.edits.MalformedTreeException;
20 import org.eclipse.text.edits.MultiTextEdit;
21 import org.eclipse.text.edits.RangeMarker;
22 import org.eclipse.text.edits.ReplaceEdit;
23 import org.eclipse.text.edits.TextEdit;
24
25 import org.eclipse.core.runtime.Assert;
26
27 import org.eclipse.jface.text.BadLocationException;
28 import org.eclipse.jface.text.BadPositionCategoryException;
29 import org.eclipse.jface.text.Document;
30 import org.eclipse.jface.text.IDocument;
31 import org.eclipse.jface.text.IRegion;
32 import org.eclipse.jface.text.Position;
33 import org.eclipse.jface.text.TypedPosition;
34 import org.eclipse.jface.text.rules.FastPartitioner;
35 import org.eclipse.jface.text.source.LineRange;
36 import org.eclipse.jface.text.templates.DocumentTemplateContext;
37 import org.eclipse.jface.text.templates.TemplateBuffer;
38 import org.eclipse.jface.text.templates.TemplateContext;
39 import org.eclipse.jface.text.templates.TemplateVariable;
40
41 import org.eclipse.jdt.core.IJavaProject;
42 import org.eclipse.jdt.core.JavaCore;
43 import org.eclipse.jdt.core.formatter.CodeFormatter;
44
45 import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
46
47 import org.eclipse.jdt.ui.text.IJavaPartitions;
48
49 import org.eclipse.jdt.internal.ui.javaeditor.IndentUtil;
50 import org.eclipse.jdt.internal.ui.text.FastJavaPartitionScanner;
51
52 /**
53  * A template editor using the Java formatter to format a template buffer.
54  */

55 public class JavaFormatter {
56
57     private static final String JavaDoc COMMENT_START= "/*-"; //$NON-NLS-1$
58
private static final String JavaDoc COMMENT_END= "*/"; //$NON-NLS-1$
59

60     /** The line delimiter to use if code formatter is not used. */
61     private final String JavaDoc fLineDelimiter;
62     /** The initial indent level */
63     private final int fInitialIndentLevel;
64
65     /** The java partitioner */
66     private boolean fUseCodeFormatter;
67     private final IJavaProject fProject;
68     
69     /**
70      * Wraps a {@link TemplateBuffer} and tracks the variable offsets while changes to the buffer
71      * occur. Whitespace variables are also tracked.
72      */

73     private static final class VariableTracker {
74         private static final String JavaDoc CATEGORY= "__template_variables"; //$NON-NLS-1$
75
private Document fDocument;
76         private final TemplateBuffer fBuffer;
77         private List JavaDoc fPositions;
78         
79         /**
80          * Creates a new tracker.
81          *
82          * @param buffer the buffer to track
83          * @throws MalformedTreeException
84          * @throws BadLocationException
85          */

86         public VariableTracker(TemplateBuffer buffer) throws MalformedTreeException, BadLocationException {
87             Assert.isLegal(buffer != null);
88             fBuffer= buffer;
89             fDocument= new Document(fBuffer.getString());
90             installJavaStuff(fDocument);
91             fDocument.addPositionCategory(CATEGORY);
92             fDocument.addPositionUpdater(new ExclusivePositionUpdater(CATEGORY));
93             fPositions= createRangeMarkers(fBuffer.getVariables(), fDocument);
94         }
95         
96         /**
97          * Installs a java partitioner with <code>document</code>.
98          *
99          * @param document the document
100          */

101         private static void installJavaStuff(Document document) {
102             String JavaDoc[] types= new String JavaDoc[] {
103                                           IJavaPartitions.JAVA_DOC,
104                                           IJavaPartitions.JAVA_MULTI_LINE_COMMENT,
105                                           IJavaPartitions.JAVA_SINGLE_LINE_COMMENT,
106                                           IJavaPartitions.JAVA_STRING,
107                                           IJavaPartitions.JAVA_CHARACTER,
108                                           IDocument.DEFAULT_CONTENT_TYPE
109             };
110             FastPartitioner partitioner= new FastPartitioner(new FastJavaPartitionScanner(), types);
111             partitioner.connect(document);
112             document.setDocumentPartitioner(IJavaPartitions.JAVA_PARTITIONING, partitioner);
113         }
114         
115         /**
116          * Returns the document with the buffer contents. Whitespace variables are decorated with
117          * comments.
118          *
119          * @return the buffer document
120          */

121         public IDocument getDocument() {
122             checkState();
123             return fDocument;
124         }
125         
126         private void checkState() {
127             if (fDocument == null)
128                 throw new IllegalStateException JavaDoc();
129         }
130
131         /**
132          * Restores any decorated regions and updates the buffer's variable offsets.
133          *
134          * @return the buffer.
135          * @throws MalformedTreeException
136          * @throws BadLocationException
137          */

138         public TemplateBuffer updateBuffer() throws MalformedTreeException, BadLocationException {
139             checkState();
140             TemplateVariable[] variables= fBuffer.getVariables();
141             try {
142                 removeRangeMarkers(fPositions, fDocument, variables);
143             } catch (BadPositionCategoryException x) {
144                 Assert.isTrue(false);
145             }
146             fBuffer.setContent(fDocument.get(), variables);
147             fDocument= null;
148             
149             return fBuffer;
150         }
151         
152         private List JavaDoc createRangeMarkers(TemplateVariable[] variables, IDocument document) throws MalformedTreeException, BadLocationException {
153             Map JavaDoc markerToOriginal= new HashMap JavaDoc();
154             
155             MultiTextEdit root= new MultiTextEdit(0, document.getLength());
156             List JavaDoc edits= new ArrayList JavaDoc();
157             boolean hasModifications= false;
158             for (int i= 0; i != variables.length; i++) {
159                 final TemplateVariable variable= variables[i];
160                 int[] offsets= variable.getOffsets();
161                 
162                 String JavaDoc value= variable.getDefaultValue();
163                 if (isWhitespaceVariable(value)) {
164                     // replace whitespace positions with unformattable comments
165
String JavaDoc placeholder= COMMENT_START + value + COMMENT_END;
166                     for (int j= 0; j != offsets.length; j++) {
167                         ReplaceEdit replace= new ReplaceEdit(offsets[j], value.length(), placeholder);
168                         root.addChild(replace);
169                         hasModifications= true;
170                         markerToOriginal.put(replace, value);
171                         edits.add(replace);
172                     }
173                 } else {
174                     for (int j= 0; j != offsets.length; j++) {
175                         RangeMarker marker= new RangeMarker(offsets[j], value.length());
176                         root.addChild(marker);
177                         edits.add(marker);
178                     }
179                 }
180             }
181             
182             if (hasModifications) {
183                 // update the document and convert the replaces to markers
184
root.apply(document, TextEdit.UPDATE_REGIONS);
185             }
186             
187             List JavaDoc positions= new ArrayList JavaDoc();
188             for (Iterator JavaDoc it= edits.iterator(); it.hasNext();) {
189                 TextEdit edit= (TextEdit) it.next();
190                 try {
191                     // abuse TypedPosition to piggy back the original contents of the position
192
final TypedPosition pos= new TypedPosition(edit.getOffset(), edit.getLength(), (String JavaDoc) markerToOriginal.get(edit));
193                     document.addPosition(CATEGORY, pos);
194                     positions.add(pos);
195                 } catch (BadPositionCategoryException x) {
196                     Assert.isTrue(false);
197                 }
198             }
199             
200             return positions;
201         }
202         
203         private boolean isWhitespaceVariable(String JavaDoc value) {
204             int length= value.length();
205             return length == 0 || Character.isWhitespace(value.charAt(0)) || Character.isWhitespace(value.charAt(length - 1));
206         }
207         
208         private void removeRangeMarkers(List JavaDoc positions, IDocument document, TemplateVariable[] variables) throws MalformedTreeException, BadLocationException, BadPositionCategoryException {
209             
210             // revert previous changes
211
for (Iterator JavaDoc it= positions.iterator(); it.hasNext();) {
212                 TypedPosition position= (TypedPosition) it.next();
213                 // remove and re-add in order to not confuse ExclusivePositionUpdater
214
document.removePosition(CATEGORY, position);
215                 final String JavaDoc original= position.getType();
216                 if (original != null) {
217                     document.replace(position.getOffset(), position.getLength(), original);
218                     position.setLength(original.length());
219                 }
220                 document.addPosition(position);
221             }
222             
223             Iterator JavaDoc it= positions.iterator();
224             for (int i= 0; i != variables.length; i++) {
225                 TemplateVariable variable= variables[i];
226
227                 int[] offsets= new int[variable.getOffsets().length];
228                 for (int j= 0; j != offsets.length; j++)
229                     offsets[j]= ((Position) it.next()).getOffset();
230
231                 variable.setOffsets(offsets);
232             }
233
234         }
235     }
236
237     /**
238      * Creates a JavaFormatter with the target line delimiter.
239      *
240      * @param lineDelimiter the line delimiter to use
241      * @param initialIndentLevel the initial indentation level
242      * @param useCodeFormatter <code>true</code> if the core code formatter should be used
243      * @param project the java project from which to get the preferences, or <code>null</code> for workbench settings
244      */

245     public JavaFormatter(String JavaDoc lineDelimiter, int initialIndentLevel, boolean useCodeFormatter, IJavaProject project) {
246         fLineDelimiter= lineDelimiter;
247         fUseCodeFormatter= useCodeFormatter;
248         fInitialIndentLevel= initialIndentLevel;
249         fProject= project;
250     }
251
252     /**
253      * Formats the template buffer.
254      * @param buffer
255      * @param context
256      * @throws BadLocationException
257      */

258     public void format(TemplateBuffer buffer, TemplateContext context) throws BadLocationException {
259         try {
260             VariableTracker tracker= new VariableTracker(buffer);
261             IDocument document= tracker.getDocument();
262             
263             internalFormat(document, context);
264             convertLineDelimiters(document);
265             if (!isReplacedAreaEmpty(context))
266                 trimStart(document);
267             
268             tracker.updateBuffer();
269         } catch (MalformedTreeException e) {
270             throw new BadLocationException();
271         }
272     }
273
274     /**
275      * @param document
276      * @param context
277      * @throws BadLocationException
278      */

279     private void internalFormat(IDocument document, TemplateContext context) throws BadLocationException {
280         if (fUseCodeFormatter) {
281             // try to format and fall back to indenting
282
try {
283                 format(document, (CompilationUnitContext) context);
284                 return;
285             } catch (BadLocationException e) {
286                 // ignore and indent
287
} catch (MalformedTreeException e) {
288                 // ignore and indent
289
}
290         }
291         indent(document);
292     }
293
294     private void convertLineDelimiters(IDocument document) throws BadLocationException {
295         int lines= document.getNumberOfLines();
296         for (int line= 0; line < lines; line++) {
297             IRegion region= document.getLineInformation(line);
298             String JavaDoc lineDelimiter= document.getLineDelimiter(line);
299             if (lineDelimiter != null)
300                 document.replace(region.getOffset() + region.getLength(), lineDelimiter.length(), fLineDelimiter);
301         }
302     }
303
304     private void trimStart(IDocument document) throws BadLocationException {
305         int i= 0;
306         while ((i != document.getLength()) && Character.isWhitespace(document.getChar(i)))
307             i++;
308         
309         document.replace(0, i, ""); //$NON-NLS-1$
310
}
311
312     private boolean isReplacedAreaEmpty(TemplateContext context) {
313         // don't trim the buffer if the replacement area is empty
314
// case: surrounding empty lines with block
315
if (context instanceof DocumentTemplateContext) {
316             DocumentTemplateContext dtc= (DocumentTemplateContext) context;
317             if (dtc.getStart() == dtc.getCompletionOffset())
318                 try {
319                     if (dtc.getDocument().get(dtc.getStart(), dtc.getEnd() - dtc.getStart()).trim().length() == 0)
320                         return true;
321                 } catch (BadLocationException x) {
322                     // ignore - this may happen when the document was modified after the initial invocation, and the
323
// context does not track the changes properly - don't trim in that case
324
return true;
325                 }
326         }
327         return false;
328     }
329
330     private void format(IDocument doc, CompilationUnitContext context) throws BadLocationException {
331         Map JavaDoc options;
332         IJavaProject project= context.getJavaProject();
333         if (project != null)
334             options= project.getOptions(true);
335         else
336             options= JavaCore.getOptions();
337
338         String JavaDoc contents= doc.get();
339         int[] kinds= { CodeFormatter.K_EXPRESSION, CodeFormatter.K_STATEMENTS, CodeFormatter.K_UNKNOWN};
340         TextEdit edit= null;
341         for (int i= 0; i < kinds.length && edit == null; i++) {
342             edit= CodeFormatterUtil.format2(kinds[i], contents, fInitialIndentLevel, fLineDelimiter, options);
343         }
344
345         if (edit == null)
346             throw new BadLocationException(); // fall back to indenting
347

348         edit.apply(doc, TextEdit.UPDATE_REGIONS);
349     }
350
351     private void indent(IDocument document) throws BadLocationException, MalformedTreeException {
352         // first line
353
int offset= document.getLineOffset(0);
354         document.replace(offset, 0, CodeFormatterUtil.createIndentString(fInitialIndentLevel, fProject));
355         
356         // following lines
357
int lineCount= document.getNumberOfLines();
358         IndentUtil.indentLines(document, new LineRange(1, lineCount - 1), fProject, null);
359     }
360 }
361
Popular Tags