]> git.uio.no Git - ifi-stolz-refaktor.git/blame - case-study/jdt-before/core extension/org/eclipse/jdt/internal/corext/template/java/JavaFormatter.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-before / core extension / org / eclipse / jdt / internal / corext / template / java / JavaFormatter.java
CommitLineData
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 *******************************************************************************/
11package org.eclipse.jdt.internal.corext.template.java;
12
13import java.util.ArrayList;
14import java.util.HashMap;
15import java.util.Iterator;
16import java.util.List;
17import java.util.Map;
18
19import org.eclipse.core.runtime.Assert;
20
21import org.eclipse.text.edits.MalformedTreeException;
22import org.eclipse.text.edits.MultiTextEdit;
23import org.eclipse.text.edits.RangeMarker;
24import org.eclipse.text.edits.ReplaceEdit;
25import org.eclipse.text.edits.TextEdit;
26
27import org.eclipse.jface.text.BadLocationException;
28import org.eclipse.jface.text.BadPositionCategoryException;
29import org.eclipse.jface.text.Document;
30import org.eclipse.jface.text.IDocument;
31import org.eclipse.jface.text.IRegion;
32import org.eclipse.jface.text.TypedPosition;
33import org.eclipse.jface.text.rules.FastPartitioner;
34import org.eclipse.jface.text.source.LineRange;
35import org.eclipse.jface.text.templates.DocumentTemplateContext;
36import org.eclipse.jface.text.templates.TemplateBuffer;
37import org.eclipse.jface.text.templates.TemplateContext;
38import org.eclipse.jface.text.templates.TemplateVariable;
39
40import org.eclipse.jdt.core.IJavaProject;
41import org.eclipse.jdt.core.JavaCore;
42import org.eclipse.jdt.core.formatter.CodeFormatter;
43
44import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
45
46import org.eclipse.jdt.ui.text.IJavaPartitions;
47
48import org.eclipse.jdt.internal.ui.javaeditor.IndentUtil;
49import org.eclipse.jdt.internal.ui.text.FastJavaPartitionScanner;
50
51/**
52 * A template editor using the Java formatter to format a template buffer.
53 */
54public 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}