]>
Commit | Line | Data |
---|---|---|
1b2798f6 EK |
1 | /******************************************************************************* |
2 | * Copyright (c) 2000, 2011 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; | |
14 | import java.util.HashMap; | |
15 | import java.util.Iterator; | |
16 | import java.util.List; | |
17 | import java.util.Map; | |
18 | ||
19 | import org.eclipse.core.runtime.Assert; | |
20 | ||
21 | import org.eclipse.text.edits.MalformedTreeException; | |
22 | import org.eclipse.text.edits.MultiTextEdit; | |
23 | import org.eclipse.text.edits.RangeMarker; | |
24 | import org.eclipse.text.edits.ReplaceEdit; | |
25 | import org.eclipse.text.edits.TextEdit; | |
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.TypedPosition; | |
33 | import org.eclipse.jface.text.rules.FastPartitioner; | |
34 | import org.eclipse.jface.text.source.LineRange; | |
35 | import org.eclipse.jface.text.templates.DocumentTemplateContext; | |
36 | import org.eclipse.jface.text.templates.TemplateBuffer; | |
37 | import org.eclipse.jface.text.templates.TemplateContext; | |
38 | import org.eclipse.jface.text.templates.TemplateVariable; | |
39 | ||
40 | import org.eclipse.jdt.core.IJavaProject; | |
41 | import org.eclipse.jdt.core.JavaCore; | |
42 | import org.eclipse.jdt.core.formatter.CodeFormatter; | |
43 | ||
44 | import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil; | |
45 | ||
46 | import org.eclipse.jdt.ui.text.IJavaPartitions; | |
47 | ||
48 | import org.eclipse.jdt.internal.ui.javaeditor.IndentUtil; | |
49 | import org.eclipse.jdt.internal.ui.text.FastJavaPartitionScanner; | |
50 | ||
51 | /** | |
52 | * A template editor using the Java formatter to format a template buffer. | |
53 | */ | |
54 | public class JavaFormatter { | |
55 | ||
56 | private static final String COMMENT_START= "/*-"; //$NON-NLS-1$ | |
57 | private static final String COMMENT_END= "*/"; //$NON-NLS-1$ | |
58 | ||
59 | /** The line delimiter to use if code formatter is not used. */ | |
60 | private final String fLineDelimiter; | |
61 | /** The initial indent level */ | |
62 | private final int fInitialIndentLevel; | |
63 | ||
64 | /** The java partitioner */ | |
65 | private boolean fUseCodeFormatter; | |
66 | private final IJavaProject fProject; | |
67 | ||
68 | /** | |
69 | * Wraps a {@link TemplateBuffer} and tracks the variable offsets while changes to the buffer | |
70 | * occur. Whitespace variables are also tracked. | |
71 | */ | |
72 | private static final class VariableTracker { | |
73 | private static final String CATEGORY= "__template_variables"; //$NON-NLS-1$ | |
74 | private Document fDocument; | |
75 | private final TemplateBuffer fBuffer; | |
76 | private List<TypedPosition> fPositions; | |
77 | ||
78 | /** | |
79 | * Creates a new tracker. | |
80 | * | |
81 | * @param buffer the buffer to track | |
82 | * @throws MalformedTreeException | |
83 | * @throws BadLocationException | |
84 | */ | |
85 | public VariableTracker(TemplateBuffer buffer) throws MalformedTreeException, BadLocationException { | |
86 | Assert.isLegal(buffer != null); | |
87 | fBuffer= buffer; | |
88 | fDocument= new Document(fBuffer.getString()); | |
89 | installJavaStuff(fDocument); | |
90 | fDocument.addPositionCategory(CATEGORY); | |
91 | fDocument.addPositionUpdater(new ExclusivePositionUpdater(CATEGORY)); | |
92 | fPositions= createRangeMarkers(fBuffer.getVariables(), fDocument); | |
93 | } | |
94 | ||
95 | /** | |
96 | * Installs a java partitioner with <code>document</code>. | |
97 | * | |
98 | * @param document the document | |
99 | */ | |
100 | private static void installJavaStuff(Document document) { | |
101 | String[] types= new String[] { | |
102 | IJavaPartitions.JAVA_DOC, | |
103 | IJavaPartitions.JAVA_MULTI_LINE_COMMENT, | |
104 | IJavaPartitions.JAVA_SINGLE_LINE_COMMENT, | |
105 | IJavaPartitions.JAVA_STRING, | |
106 | IJavaPartitions.JAVA_CHARACTER, | |
107 | IDocument.DEFAULT_CONTENT_TYPE | |
108 | }; | |
109 | FastPartitioner partitioner= new FastPartitioner(new FastJavaPartitionScanner(), types); | |
110 | partitioner.connect(document); | |
111 | document.setDocumentPartitioner(IJavaPartitions.JAVA_PARTITIONING, partitioner); | |
112 | } | |
113 | ||
114 | /** | |
115 | * Returns the document with the buffer contents. Whitespace variables are decorated with | |
116 | * comments. | |
117 | * | |
118 | * @return the buffer document | |
119 | */ | |
120 | public IDocument getDocument() { | |
121 | checkState(); | |
122 | return fDocument; | |
123 | } | |
124 | ||
125 | private void checkState() { | |
126 | if (fDocument == null) | |
127 | throw new IllegalStateException(); | |
128 | } | |
129 | ||
130 | /** | |
131 | * Restores any decorated regions and updates the buffer's variable offsets. | |
132 | * | |
133 | * @return the buffer. | |
134 | * @throws MalformedTreeException | |
135 | * @throws BadLocationException | |
136 | */ | |
137 | public TemplateBuffer updateBuffer() throws MalformedTreeException, BadLocationException { | |
138 | checkState(); | |
139 | TemplateVariable[] variables= fBuffer.getVariables(); | |
140 | try { | |
141 | removeRangeMarkers(fPositions, fDocument, variables); | |
142 | } catch (BadPositionCategoryException x) { | |
143 | Assert.isTrue(false); | |
144 | } | |
145 | fBuffer.setContent(fDocument.get(), variables); | |
146 | fDocument= null; | |
147 | ||
148 | return fBuffer; | |
149 | } | |
150 | ||
151 | private List<TypedPosition> createRangeMarkers(TemplateVariable[] variables, IDocument document) throws MalformedTreeException, BadLocationException { | |
152 | Map<ReplaceEdit, String> markerToOriginal= new HashMap<ReplaceEdit, String>(); | |
153 | ||
154 | MultiTextEdit root= new MultiTextEdit(0, document.getLength()); | |
155 | List<TextEdit> edits= new ArrayList<TextEdit>(); | |
156 | boolean hasModifications= false; | |
157 | for (int i= 0; i != variables.length; i++) { | |
158 | final TemplateVariable variable= variables[i]; | |
159 | int[] offsets= variable.getOffsets(); | |
160 | ||
161 | String value= variable.getDefaultValue(); | |
162 | if (isWhitespaceVariable(value)) { | |
163 | // replace whitespace positions with unformattable comments | |
164 | String placeholder= COMMENT_START + value + COMMENT_END; | |
165 | for (int j= 0; j != offsets.length; j++) { | |
166 | ReplaceEdit replace= new ReplaceEdit(offsets[j], value.length(), placeholder); | |
167 | root.addChild(replace); | |
168 | hasModifications= true; | |
169 | markerToOriginal.put(replace, value); | |
170 | edits.add(replace); | |
171 | } | |
172 | } else { | |
173 | for (int j= 0; j != offsets.length; j++) { | |
174 | RangeMarker marker= new RangeMarker(offsets[j], value.length()); | |
175 | root.addChild(marker); | |
176 | edits.add(marker); | |
177 | } | |
178 | } | |
179 | } | |
180 | ||
181 | if (hasModifications) { | |
182 | // update the document and convert the replaces to markers | |
183 | root.apply(document, TextEdit.UPDATE_REGIONS); | |
184 | } | |
185 | ||
186 | List<TypedPosition> positions= new ArrayList<TypedPosition>(); | |
187 | for (Iterator<TextEdit> it= edits.iterator(); it.hasNext();) { | |
188 | TextEdit edit= it.next(); | |
189 | try { | |
190 | // abuse TypedPosition to piggy back the original contents of the position | |
191 | final TypedPosition pos= new TypedPosition(edit.getOffset(), edit.getLength(), markerToOriginal.get(edit)); | |
192 | document.addPosition(CATEGORY, pos); | |
193 | positions.add(pos); | |
194 | } catch (BadPositionCategoryException x) { | |
195 | Assert.isTrue(false); | |
196 | } | |
197 | } | |
198 | ||
199 | return positions; | |
200 | } | |
201 | ||
202 | private boolean isWhitespaceVariable(String value) { | |
203 | int length= value.length(); | |
204 | return length == 0 || Character.isWhitespace(value.charAt(0)) || Character.isWhitespace(value.charAt(length - 1)); | |
205 | } | |
206 | ||
207 | private void removeRangeMarkers(List<TypedPosition> positions, IDocument document, TemplateVariable[] variables) throws MalformedTreeException, BadLocationException, BadPositionCategoryException { | |
208 | ||
209 | // revert previous changes | |
210 | for (Iterator<TypedPosition> it= positions.iterator(); it.hasNext();) { | |
211 | TypedPosition position= it.next(); | |
212 | // remove and re-add in order to not confuse ExclusivePositionUpdater | |
213 | document.removePosition(CATEGORY, position); | |
214 | final String original= position.getType(); | |
215 | if (original != null) { | |
216 | document.replace(position.getOffset(), position.getLength(), original); | |
217 | position.setLength(original.length()); | |
218 | } | |
219 | document.addPosition(position); | |
220 | } | |
221 | ||
222 | Iterator<TypedPosition> it= positions.iterator(); | |
223 | for (int i= 0; i != variables.length; i++) { | |
224 | TemplateVariable variable= variables[i]; | |
225 | ||
226 | int[] offsets= new int[variable.getOffsets().length]; | |
227 | for (int j= 0; j != offsets.length; j++) | |
228 | offsets[j]= it.next().getOffset(); | |
229 | ||
230 | variable.setOffsets(offsets); | |
231 | } | |
232 | ||
233 | } | |
234 | } | |
235 | ||
236 | /** | |
237 | * Creates a JavaFormatter with the target line delimiter. | |
238 | * | |
239 | * @param lineDelimiter the line delimiter to use | |
240 | * @param initialIndentLevel the initial indentation level | |
241 | * @param useCodeFormatter <code>true</code> if the core code formatter should be used | |
242 | * @param project the java project from which to get the preferences, or <code>null</code> for workbench settings | |
243 | */ | |
244 | public JavaFormatter(String lineDelimiter, int initialIndentLevel, boolean useCodeFormatter, IJavaProject project) { | |
245 | fLineDelimiter= lineDelimiter; | |
246 | fUseCodeFormatter= useCodeFormatter; | |
247 | fInitialIndentLevel= initialIndentLevel; | |
248 | fProject= project; | |
249 | } | |
250 | ||
251 | /** | |
252 | * Formats the template buffer. | |
253 | * @param buffer | |
254 | * @param context | |
255 | * @throws BadLocationException | |
256 | */ | |
257 | public void format(TemplateBuffer buffer, TemplateContext context) throws BadLocationException { | |
258 | try { | |
259 | VariableTracker tracker= new VariableTracker(buffer); | |
260 | IDocument document= tracker.getDocument(); | |
261 | ||
262 | internalFormat(document, context); | |
263 | convertLineDelimiters(document); | |
264 | if (!(context instanceof JavaDocContext) && !isReplacedAreaEmpty(context)) | |
265 | trimStart(document); | |
266 | ||
267 | tracker.updateBuffer(); | |
268 | } catch (MalformedTreeException e) { | |
269 | throw new BadLocationException(); | |
270 | } | |
271 | } | |
272 | ||
273 | /** | |
274 | * @param document | |
275 | * @param context | |
276 | * @throws BadLocationException | |
277 | */ | |
278 | private void internalFormat(IDocument document, TemplateContext context) throws BadLocationException { | |
279 | if (fUseCodeFormatter) { | |
280 | // try to format and fall back to indenting | |
281 | try { | |
282 | format(document, (CompilationUnitContext) context); | |
283 | return; | |
284 | } catch (BadLocationException e) { | |
285 | // ignore and indent | |
286 | } catch (MalformedTreeException e) { | |
287 | // ignore and indent | |
288 | } | |
289 | } | |
290 | if (!(context instanceof JavaDocContext)) | |
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 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 | IDocument document= dtc.getDocument(); | |
320 | int lineOffset= document.getLineInformationOfOffset(dtc.getStart()).getOffset(); | |
321 | //only if we are at the beginning of the line | |
322 | if (lineOffset != dtc.getStart()) | |
323 | return false; | |
324 | ||
325 | //Does the selection only contain whitespace characters? | |
326 | if (document.get(dtc.getStart(), dtc.getEnd() - dtc.getStart()).trim().length() == 0) | |
327 | return true; | |
328 | } catch (BadLocationException x) { | |
329 | // ignore - this may happen when the document was modified after the initial invocation, and the | |
330 | // context does not track the changes properly - don't trim in that case | |
331 | return true; | |
332 | } | |
333 | } | |
334 | return false; | |
335 | } | |
336 | ||
337 | private void format(IDocument doc, CompilationUnitContext context) throws BadLocationException { | |
338 | Map<String, String> options; | |
339 | IJavaProject project= context.getJavaProject(); | |
340 | if (project != null) | |
341 | options= project.getOptions(true); | |
342 | else | |
343 | options= JavaCore.getOptions(); | |
344 | ||
345 | String contents= doc.get(); | |
346 | int[] kinds= { CodeFormatter.K_EXPRESSION, CodeFormatter.K_STATEMENTS, CodeFormatter.K_UNKNOWN}; | |
347 | TextEdit edit= null; | |
348 | for (int i= 0; i < kinds.length && edit == null; i++) { | |
349 | edit= CodeFormatterUtil.format2(kinds[i], contents, fInitialIndentLevel, fLineDelimiter, options); | |
350 | } | |
351 | ||
352 | if (edit == null) | |
353 | throw new BadLocationException(); // fall back to indenting | |
354 | ||
355 | edit.apply(doc, TextEdit.UPDATE_REGIONS); | |
356 | } | |
357 | ||
358 | private void indent(IDocument document) throws BadLocationException, MalformedTreeException { | |
359 | // first line | |
360 | int offset= document.getLineOffset(0); | |
361 | document.replace(offset, 0, CodeFormatterUtil.createIndentString(fInitialIndentLevel, fProject)); | |
362 | ||
363 | // following lines | |
364 | int lineCount= document.getNumberOfLines(); | |
365 | IndentUtil.indentLines(document, new LineRange(1, lineCount - 1), fProject, null); | |
366 | } | |
367 | } |