]> git.uio.no Git - ifi-stolz-refaktor.git/blame - case-study/jdt-before/ui/org/eclipse/jdt/internal/ui/text/javadoc/JavaDocAutoIndentStrategy.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-before / ui / org / eclipse / jdt / internal / ui / text / javadoc / JavaDocAutoIndentStrategy.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.ui.text.javadoc;
12
13import org.eclipse.core.runtime.Assert;
14import org.eclipse.core.runtime.CoreException;
15
16import org.eclipse.jface.text.BadLocationException;
17import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
18import org.eclipse.jface.text.DocumentCommand;
19import org.eclipse.jface.text.IDocument;
20import org.eclipse.jface.text.IRegion;
21import org.eclipse.jface.text.ITypedRegion;
22import org.eclipse.jface.text.Region;
23import org.eclipse.jface.text.TextUtilities;
24
25import org.eclipse.ui.IEditorPart;
26import org.eclipse.ui.IWorkbenchPage;
27import org.eclipse.ui.IWorkbenchWindow;
28import org.eclipse.ui.PlatformUI;
29
30import org.eclipse.ui.texteditor.ITextEditorExtension3;
31
32import org.eclipse.jdt.core.ICompilationUnit;
33import org.eclipse.jdt.core.IJavaElement;
34import org.eclipse.jdt.core.IJavaProject;
35import org.eclipse.jdt.core.IMember;
36import org.eclipse.jdt.core.IMethod;
37import org.eclipse.jdt.core.ISourceRange;
38import org.eclipse.jdt.core.IType;
39import org.eclipse.jdt.core.JavaModelException;
40
41import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
42import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
43import org.eclipse.jdt.internal.corext.util.MethodOverrideTester;
44import org.eclipse.jdt.internal.corext.util.Strings;
45import org.eclipse.jdt.internal.corext.util.SuperTypeHierarchyCache;
46
47import org.eclipse.jdt.ui.CodeGeneration;
48import org.eclipse.jdt.ui.IWorkingCopyManager;
49import org.eclipse.jdt.ui.PreferenceConstants;
50
51import org.eclipse.jdt.internal.ui.JavaPlugin;
52import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
53
54
55/**
56 * Auto indent strategy for Javadoc comments.
57 */
58public class JavaDocAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy {
59
60 /** The partitioning that this strategy operates on. */
61 private final String fPartitioning;
62
63 /**
64 * Creates a new Javadoc auto indent strategy for the given document partitioning.
65 *
66 * @param partitioning the document partitioning
67 */
68 public JavaDocAutoIndentStrategy(String partitioning) {
69 fPartitioning= partitioning;
70 }
71
72 /**
73 * Copies the indentation of the previous line and adds a star.
74 * If the Javadoc just started on this line add standard method tags
75 * and close the Javadoc.
76 *
77 * @param d the document to work on
78 * @param c the command to deal with
79 */
80 private void indentAfterNewLine(IDocument d, DocumentCommand c) {
81
82 int offset= c.offset;
83 if (offset == -1 || d.getLength() == 0)
84 return;
85
86 try {
87 int p= (offset == d.getLength() ? offset - 1 : offset);
88 IRegion line= d.getLineInformationOfOffset(p);
89
90 int lineOffset= line.getOffset();
91 int firstNonWS= findEndOfWhiteSpace(d, lineOffset, offset);
92 Assert.isTrue(firstNonWS >= lineOffset, "indentation must not be negative"); //$NON-NLS-1$
93
94 StringBuffer buf= new StringBuffer(c.text);
95 IRegion prefix= findPrefixRange(d, line);
96 String indentation= d.get(prefix.getOffset(), prefix.getLength());
97 int lengthToAdd= Math.min(offset - prefix.getOffset(), prefix.getLength());
98
99 buf.append(indentation.substring(0, lengthToAdd));
100
101 if (firstNonWS < offset) {
102 if (d.getChar(firstNonWS) == '/') {
103 // Javadoc started on this line
104 buf.append(" * "); //$NON-NLS-1$
105
106 if (isPreferenceTrue(PreferenceConstants.EDITOR_CLOSE_JAVADOCS) && isNewComment(d, offset)) {
107 c.shiftsCaret= false;
108 c.caretOffset= c.offset + buf.length();
109 String lineDelimiter= TextUtilities.getDefaultLineDelimiter(d);
110
111 int eolOffset= lineOffset + line.getLength();
112 int replacementLength= eolOffset - p;
113 String restOfLine= d.get(p, replacementLength);
114 String endTag= lineDelimiter + indentation + " */"; //$NON-NLS-1$
115
116 if (isPreferenceTrue(PreferenceConstants.EDITOR_ADD_JAVADOC_TAGS)) {
117 // we need to close the comment before computing
118 // the correct tags in order to get the method
119 d.replace(offset, replacementLength, endTag);
120
121 // evaluate method signature
122 ICompilationUnit unit= getCompilationUnit();
123
124 if (unit != null) {
125 try {
126 JavaModelUtil.reconcile(unit);
127 String string= createJavaDocTags(d, c, indentation, lineDelimiter, unit);
128 buf.append(restOfLine);
129 // only add tags if they are non-empty - the empty line has already been added above.
130 if (string != null && !string.trim().equals("*")) //$NON-NLS-1$
131 buf.append(string);
132 } catch (CoreException e) {
133 // ignore
134 }
135 }
136 } else {
137 c.length= replacementLength;
138 buf.append(restOfLine);
139 buf.append(endTag);
140 }
141 }
142
143 }
144 }
145
146 // move the caret behind the prefix, even if we do not have to insert it.
147 if (lengthToAdd < prefix.getLength())
148 c.caretOffset= offset + prefix.getLength() - lengthToAdd;
149 c.text= buf.toString();
150
151 } catch (BadLocationException excp) {
152 // stop work
153 }
154 }
155
156 /**
157 * Returns the value of the given boolean-typed preference.
158 *
159 * @param preference the preference to look up
160 * @return the value of the given preference in the Java plug-in's default preference store
161 */
162 private boolean isPreferenceTrue(String preference) {
163 return JavaPlugin.getDefault().getPreferenceStore().getBoolean(preference);
164 }
165
166 /**
167 * Returns the range of the Javadoc prefix on the given line in
168 * <code>document</code>. The prefix greedily matches the following regex
169 * pattern: <code>\w*\*\w*</code>, that is, any number of whitespace
170 * characters, followed by an asterisk ('*'), followed by any number of
171 * whitespace characters.
172 *
173 * @param document the document to which <code>line</code> refers
174 * @param line the line from which to extract the prefix range
175 * @return an <code>IRegion</code> describing the range of the prefix on the given line
176 * @throws BadLocationException if accessing the document fails
177 */
178 private IRegion findPrefixRange(IDocument document, IRegion line) throws BadLocationException {
179 int lineOffset= line.getOffset();
180 int lineEnd= lineOffset + line.getLength();
181 int indentEnd= findEndOfWhiteSpace(document, lineOffset, lineEnd);
182 if (indentEnd < lineEnd && document.getChar(indentEnd) == '*') {
183 indentEnd++;
184 while (indentEnd < lineEnd && document.getChar(indentEnd) == ' ')
185 indentEnd++;
186 }
187 return new Region(lineOffset, indentEnd - lineOffset);
188 }
189
190 /**
191 * Creates the Javadoc tags for newly inserted comments.
192 *
193 * @param document the document
194 * @param command the command
195 * @param indentation the base indentation to use
196 * @param lineDelimiter the line delimiter to use
197 * @param unit the compilation unit shown in the editor
198 * @return the tags to add to the document
199 * @throws CoreException if accessing the Java model fails
200 * @throws BadLocationException if accessing the document fails
201 */
202 private String createJavaDocTags(IDocument document, DocumentCommand command, String indentation, String lineDelimiter, ICompilationUnit unit)
203 throws CoreException, BadLocationException
204 {
205 IJavaElement element= unit.getElementAt(command.offset);
206 if (element == null)
207 return null;
208
209 switch (element.getElementType()) {
210 case IJavaElement.TYPE:
211 return createTypeTags(document, command, indentation, lineDelimiter, (IType) element);
212
213 case IJavaElement.METHOD:
214 return createMethodTags(document, command, indentation, lineDelimiter, (IMethod) element);
215
216 default:
217 return null;
218 }
219 }
220
221 /**
222 * Removes start and end of a comment and corrects indentation and line
223 * delimiters.
224 *
225 * @param comment the computed comment
226 * @param indentation the base indentation
227 * @param project the Java project for the formatter settings, or <code>null</code> for global preferences
228 * @param lineDelimiter the line delimiter
229 * @return a trimmed version of <code>comment</code>
230 */
231 private String prepareTemplateComment(String comment, String indentation, IJavaProject project, String lineDelimiter) {
232 // trim comment start and end if any
233 if (comment.endsWith("*/")) //$NON-NLS-1$
234 comment= comment.substring(0, comment.length() - 2);
235 comment= comment.trim();
236 if (comment.startsWith("/*")) { //$NON-NLS-1$
237 if (comment.length() > 2 && comment.charAt(2) == '*') {
238 comment= comment.substring(3); // remove '/**'
239 } else {
240 comment= comment.substring(2); // remove '/*'
241 }
242 }
243 // trim leading spaces, but not new lines
244 int nonSpace= 0;
245 int len= comment.length();
246 while (nonSpace < len && Character.getType(comment.charAt(nonSpace)) == Character.SPACE_SEPARATOR)
247 nonSpace++;
248 comment= comment.substring(nonSpace);
249
250 return Strings.changeIndent(comment, 0, project, indentation, lineDelimiter);
251 }
252
253 private String createTypeTags(IDocument document, DocumentCommand command, String indentation, String lineDelimiter, IType type)
254 throws CoreException, BadLocationException
255 {
256 String[] typeParamNames= StubUtility.getTypeParameterNames(type.getTypeParameters());
257 String comment= CodeGeneration.getTypeComment(type.getCompilationUnit(), type.getTypeQualifiedName('.'), typeParamNames, lineDelimiter);
258 if (comment != null) {
259 boolean javadocComment= comment.startsWith("/**"); //$NON-NLS-1$
260 if (!isFirstComment(document, command, type, javadocComment))
261 return null;
262 return prepareTemplateComment(comment.trim(), indentation, type.getJavaProject(), lineDelimiter);
263 }
264 return null;
265 }
266
267 private String createMethodTags(IDocument document, DocumentCommand command, String indentation, String lineDelimiter, IMethod method)
268 throws CoreException, BadLocationException
269 {
270 IRegion partition= TextUtilities.getPartition(document, fPartitioning, command.offset, false);
271 IMethod inheritedMethod= getInheritedMethod(method);
272 String comment= CodeGeneration.getMethodComment(method, inheritedMethod, lineDelimiter);
273 if (comment != null) {
274 comment= comment.trim();
275 boolean javadocComment= comment.startsWith("/**"); //$NON-NLS-1$
276 if (!isFirstComment(document, command, method, javadocComment))
277 return null;
278 boolean isJavaDoc= partition.getLength() >= 3 && document.get(partition.getOffset(), 3).equals("/**"); //$NON-NLS-1$
279 if (javadocComment == isJavaDoc) {
280 return prepareTemplateComment(comment, indentation, method.getJavaProject(), lineDelimiter);
281 }
282 }
283 return null;
284 }
285
286 /**
287 * Returns <code>true</code> if the comment being inserted at
288 * <code>command.offset</code> is the first comment (the first
289 * Javadoc comment if <code>ignoreJavadoc</code> is
290 * <code>true</code>) of the given member.
291 * <p>
292 * see also https://bugs.eclipse.org/bugs/show_bug.cgi?id=55325 (don't add parameters if the member already has a comment)
293 * </p>
294 * @param document the document
295 * @param command the document command
296 * @param member the Java member
297 * @param ignoreNonJavadoc <code>true</code> if non Javadoc should be ignored
298 * @return <code>true</code> if it is the first comment
299 * @throws JavaModelException if accessing the Java model fails
300 * @throws BadLocationException if accessing the document fails
301 */
302 private boolean isFirstComment(IDocument document, DocumentCommand command, IMember member, boolean ignoreNonJavadoc) throws BadLocationException, JavaModelException {
303 IRegion partition= TextUtilities.getPartition(document, fPartitioning, command.offset, false);
304 ISourceRange sourceRange= member.getSourceRange();
305 if (sourceRange == null)
306 return false;
307 int srcOffset= sourceRange.getOffset();
308 int srcLength= sourceRange.getLength();
309 int nameRelativeOffset= member.getNameRange().getOffset() - srcOffset;
310 int partitionRelativeOffset= partition.getOffset() - srcOffset;
311 String token= ignoreNonJavadoc ? "/**" : "/*"; //$NON-NLS-1$ //$NON-NLS-2$
312 return document.get(srcOffset, srcLength).lastIndexOf(token, nameRelativeOffset) == partitionRelativeOffset;
313 }
314
315 /**
316 * Unindents a typed slash ('/') if it forms the end of a comment.
317 *
318 * @param d the document
319 * @param c the command
320 */
321 private void indentAfterCommentEnd(IDocument d, DocumentCommand c) {
322 if (c.offset < 2 || d.getLength() == 0) {
323 return;
324 }
325 try {
326 if ("* ".equals(d.get(c.offset - 2, 2))) { //$NON-NLS-1$
327 // modify document command
328 c.length++;
329 c.offset--;
330 }
331 } catch (BadLocationException excp) {
332 // stop work
333 }
334 }
335
336 /**
337 * Guesses if the command operates within a newly created Javadoc comment or not.
338 * If in doubt, it will assume that the Javadoc is new.
339 *
340 * @param document the document
341 * @param commandOffset the command offset
342 * @return <code>true</code> if the comment should be closed, <code>false</code> if not
343 */
344 private boolean isNewComment(IDocument document, int commandOffset) {
345
346 try {
347 int lineIndex= document.getLineOfOffset(commandOffset) + 1;
348 if (lineIndex >= document.getNumberOfLines())
349 return true;
350
351 IRegion line= document.getLineInformation(lineIndex);
352 ITypedRegion partition= TextUtilities.getPartition(document, fPartitioning, commandOffset, false);
353 int partitionEnd= partition.getOffset() + partition.getLength();
354 if (line.getOffset() >= partitionEnd)
355 return false;
356
357 if (document.getLength() == partitionEnd)
358 return true; // partition goes to end of document - probably a new comment
359
360 String comment= document.get(partition.getOffset(), partition.getLength());
361 if (comment.indexOf("/*", 2) != -1) //$NON-NLS-1$
362 return true; // enclosed another comment -> probably a new comment
363
364 return false;
365
366 } catch (BadLocationException e) {
367 return false;
368 }
369 }
370
371 private boolean isSmartMode() {
372 IWorkbenchPage page= JavaPlugin.getActivePage();
373 if (page != null) {
374 IEditorPart part= page.getActiveEditor();
375 if (part instanceof ITextEditorExtension3) {
376 ITextEditorExtension3 extension= (ITextEditorExtension3) part;
377 return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT;
378 } else if (EditorUtility.isCompareEditorInput(part.getEditorInput())) {
379 ITextEditorExtension3 extension= (ITextEditorExtension3)part.getAdapter(ITextEditorExtension3.class);
380 if (extension != null)
381 return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT;
382 }
383 }
384 return false;
385 }
386
387 /*
388 * @see IAutoIndentStrategy#customizeDocumentCommand
389 */
390 @Override
391 public void customizeDocumentCommand(IDocument document, DocumentCommand command) {
392
393 if (!isSmartMode())
394 return;
395
396 if (command.text != null) {
397 if (command.length == 0) {
398 String[] lineDelimiters= document.getLegalLineDelimiters();
399 int index= TextUtilities.endsWith(lineDelimiters, command.text);
400 if (index > -1) {
401 // ends with line delimiter
402 if (lineDelimiters[index].equals(command.text))
403 // just the line delimiter
404 indentAfterNewLine(document, command);
405 return;
406 }
407 }
408
409 if (command.text.equals("/")) { //$NON-NLS-1$
410 indentAfterCommentEnd(document, command);
411 return;
412 }
413 }
414 }
415
416 /**
417 * Returns the method inherited from, <code>null</code> if method is newly defined.
418 * @param method the method being written
419 * @return the ancestor method, or <code>null</code> if none
420 * @throws JavaModelException if accessing the Java model fails
421 */
422 private static IMethod getInheritedMethod(IMethod method) throws JavaModelException {
423 IType declaringType= method.getDeclaringType();
424 MethodOverrideTester tester= SuperTypeHierarchyCache.getMethodOverrideTester(declaringType);
425 return tester.findOverriddenMethod(method, true);
426 }
427
428 /**
429 * Returns the compilation unit of the compilation unit editor invoking the <code>AutoIndentStrategy</code>,
430 * might return <code>null</code> on error.
431 * @return the compilation unit represented by the document
432 */
433 private static ICompilationUnit getCompilationUnit() {
434
435 IWorkbenchWindow window= PlatformUI.getWorkbench().getActiveWorkbenchWindow();
436 if (window == null)
437 return null;
438
439 IWorkbenchPage page= window.getActivePage();
440 if (page == null)
441 return null;
442
443 IEditorPart editor= page.getActiveEditor();
444 if (editor == null)
445 return null;
446
447 IWorkingCopyManager manager= JavaPlugin.getDefault().getWorkingCopyManager();
448 ICompilationUnit unit= manager.getWorkingCopy(editor.getEditorInput());
449 if (unit == null)
450 return null;
451
452 return unit;
453 }
454
455}