KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > ui > text > javadoc > JavaDocAutoIndentStrategy


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
12 package org.eclipse.jdt.internal.ui.text.javadoc;
13
14
15 import org.eclipse.core.runtime.Assert;
16 import org.eclipse.core.runtime.CoreException;
17
18 import org.eclipse.jface.text.BadLocationException;
19 import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
20 import org.eclipse.jface.text.DocumentCommand;
21 import org.eclipse.jface.text.IDocument;
22 import org.eclipse.jface.text.IRegion;
23 import org.eclipse.jface.text.ITypedRegion;
24 import org.eclipse.jface.text.Region;
25 import org.eclipse.jface.text.TextUtilities;
26
27 import org.eclipse.ui.IEditorPart;
28 import org.eclipse.ui.IWorkbenchPage;
29 import org.eclipse.ui.IWorkbenchWindow;
30 import org.eclipse.ui.PlatformUI;
31 import org.eclipse.ui.texteditor.ITextEditorExtension3;
32
33 import org.eclipse.jdt.core.ICompilationUnit;
34 import org.eclipse.jdt.core.IJavaElement;
35 import org.eclipse.jdt.core.IJavaProject;
36 import org.eclipse.jdt.core.IMember;
37 import org.eclipse.jdt.core.IMethod;
38 import org.eclipse.jdt.core.ISourceRange;
39 import org.eclipse.jdt.core.IType;
40 import org.eclipse.jdt.core.JavaModelException;
41
42 import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
43 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
44 import org.eclipse.jdt.internal.corext.util.MethodOverrideTester;
45 import org.eclipse.jdt.internal.corext.util.Strings;
46 import org.eclipse.jdt.internal.corext.util.SuperTypeHierarchyCache;
47
48 import org.eclipse.jdt.ui.CodeGeneration;
49 import org.eclipse.jdt.ui.IWorkingCopyManager;
50 import org.eclipse.jdt.ui.PreferenceConstants;
51
52 import org.eclipse.jdt.internal.ui.JavaPlugin;
53
54
55 /**
56  * Auto indent strategy for Javadoc comments.
57  */

58 public class JavaDocAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy {
59
60     /** The partitioning that this strategy operates on. */
61     private final String JavaDoc 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 JavaDoc 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 JavaDoc buf= new StringBuffer JavaDoc(c.text);
95             IRegion prefix= findPrefixRange(d, line);
96             String JavaDoc 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 JavaDoc lineDelimiter= TextUtilities.getDefaultLineDelimiter(d);
110
111                         String JavaDoc endTag= lineDelimiter + indentation + " */"; //$NON-NLS-1$
112

113                         if (isPreferenceTrue(PreferenceConstants.EDITOR_ADD_JAVADOC_TAGS)) {
114                             // we need to close the comment before computing
115
// the correct tags in order to get the method
116
d.replace(offset, 0, endTag);
117
118                             // evaluate method signature
119
ICompilationUnit unit= getCompilationUnit();
120
121                             if (unit != null) {
122                                 try {
123                                     JavaModelUtil.reconcile(unit);
124                                     String JavaDoc string= createJavaDocTags(d, c, indentation, lineDelimiter, unit);
125                                     // only add tags if they are non-empty - the empty line has already been added above.
126
if (string != null && !string.trim().equals("*")) //$NON-NLS-1$
127
buf.append(string);
128                                 } catch (CoreException e) {
129                                     // ignore
130
}
131                             }
132                         } else {
133                             buf.append(endTag);
134                         }
135                     }
136
137                 }
138             }
139
140             // move the caret behind the prefix, even if we do not have to insert it.
141
if (lengthToAdd < prefix.getLength())
142                 c.caretOffset= offset + prefix.getLength() - lengthToAdd;
143             c.text= buf.toString();
144
145         } catch (BadLocationException excp) {
146             // stop work
147
}
148     }
149
150     /**
151      * Returns the value of the given boolean-typed preference.
152      *
153      * @param preference the preference to look up
154      * @return the value of the given preference in the Java plug-in's default preference store
155      */

156     private boolean isPreferenceTrue(String JavaDoc preference) {
157         return JavaPlugin.getDefault().getPreferenceStore().getBoolean(preference);
158     }
159
160     /**
161      * Returns the range of the Javadoc prefix on the given line in
162      * <code>document</code>. The prefix greedily matches the following regex
163      * pattern: <code>\w*\*\w*</code>, that is, any number of whitespace
164      * characters, followed by an asterix ('*'), followed by any number of
165      * whitespace characters.
166      *
167      * @param document the document to which <code>line</code> refers
168      * @param line the line from which to extract the prefix range
169      * @return an <code>IRegion</code> describing the range of the prefix on
170      * the given line
171      * @throws BadLocationException if accessing the document fails
172      */

173     private IRegion findPrefixRange(IDocument document, IRegion line) throws BadLocationException {
174         int lineOffset= line.getOffset();
175         int lineEnd= lineOffset + line.getLength();
176         int indentEnd= findEndOfWhiteSpace(document, lineOffset, lineEnd);
177         if (indentEnd < lineEnd && document.getChar(indentEnd) == '*') {
178             indentEnd++;
179             while (indentEnd < lineEnd && document.getChar(indentEnd) == ' ')
180                 indentEnd++;
181         }
182         return new Region(lineOffset, indentEnd - lineOffset);
183     }
184
185     /**
186      * Creates the Javadoc tags for newly inserted comments.
187      *
188      * @param document the document
189      * @param command the command
190      * @param indentation the base indentation to use
191      * @param lineDelimiter the line delimiter to use
192      * @param unit the compilation unit shown in the editor
193      * @return the tags to add to the document
194      * @throws CoreException if accessing the java model fails
195      * @throws BadLocationException if accessing the document fails
196      */

197     private String JavaDoc createJavaDocTags(IDocument document, DocumentCommand command, String JavaDoc indentation, String JavaDoc lineDelimiter, ICompilationUnit unit)
198         throws CoreException, BadLocationException
199     {
200         IJavaElement element= unit.getElementAt(command.offset);
201         if (element == null)
202             return null;
203
204         switch (element.getElementType()) {
205         case IJavaElement.TYPE:
206             return createTypeTags(document, command, indentation, lineDelimiter, (IType) element);
207
208         case IJavaElement.METHOD:
209             return createMethodTags(document, command, indentation, lineDelimiter, (IMethod) element);
210
211         default:
212             return null;
213         }
214     }
215
216     /**
217      * Removes start and end of a comment and corrects indentation and line
218      * delimiters.
219      *
220      * @param comment the computed comment
221      * @param indentation the base indentation
222      * @param project the java project for the formatter settings, or
223      * <code>null</code> for global preferences
224      * @param lineDelimiter the line delimiter
225      * @return a trimmed version of <code>comment</code>
226      */

227     private String JavaDoc prepareTemplateComment(String JavaDoc comment, String JavaDoc indentation, IJavaProject project, String JavaDoc lineDelimiter) {
228         // trim comment start and end if any
229
if (comment.endsWith("*/")) //$NON-NLS-1$
230
comment= comment.substring(0, comment.length() - 2);
231         comment= comment.trim();
232         if (comment.startsWith("/*")) { //$NON-NLS-1$
233
if (comment.length() > 2 && comment.charAt(2) == '*') {
234                 comment= comment.substring(3); // remove '/**'
235
} else {
236                 comment= comment.substring(2); // remove '/*'
237
}
238         }
239         // trim leading spaces, but not new lines
240
int nonSpace= 0;
241         int len= comment.length();
242         while (nonSpace < len && Character.getType(comment.charAt(nonSpace)) == Character.SPACE_SEPARATOR)
243                 nonSpace++;
244         comment= comment.substring(nonSpace);
245         
246         return Strings.changeIndent(comment, 0, project, indentation, lineDelimiter);
247     }
248
249     private String JavaDoc createTypeTags(IDocument document, DocumentCommand command, String JavaDoc indentation, String JavaDoc lineDelimiter, IType type)
250         throws CoreException, BadLocationException
251     {
252         String JavaDoc[] typeParamNames= StubUtility.getTypeParameterNames(type.getTypeParameters());
253         String JavaDoc comment= CodeGeneration.getTypeComment(type.getCompilationUnit(), type.getTypeQualifiedName('.'), typeParamNames, lineDelimiter);
254         if (comment != null) {
255             boolean javadocComment= comment.startsWith("/**"); //$NON-NLS-1$
256
if (!isFirstComment(document, command, type, javadocComment))
257                 return null;
258             return prepareTemplateComment(comment.trim(), indentation, type.getJavaProject(), lineDelimiter);
259         }
260         return null;
261     }
262
263     private String JavaDoc createMethodTags(IDocument document, DocumentCommand command, String JavaDoc indentation, String JavaDoc lineDelimiter, IMethod method)
264         throws CoreException, BadLocationException
265     {
266         IRegion partition= TextUtilities.getPartition(document, fPartitioning, command.offset, false);
267         IMethod inheritedMethod= getInheritedMethod(method);
268         String JavaDoc comment= CodeGeneration.getMethodComment(method, inheritedMethod, lineDelimiter);
269         if (comment != null) {
270             comment= comment.trim();
271             boolean javadocComment= comment.startsWith("/**"); //$NON-NLS-1$
272
if (!isFirstComment(document, command, method, javadocComment))
273                 return null;
274             boolean isJavaDoc= partition.getLength() >= 3 && document.get(partition.getOffset(), 3).equals("/**"); //$NON-NLS-1$
275
if (javadocComment == isJavaDoc) {
276                 return prepareTemplateComment(comment, indentation, method.getJavaProject(), lineDelimiter);
277             }
278         }
279         return null;
280     }
281
282     /**
283      * Returns <code>true</code> if the comment being inserted at
284      * <code>command.offset</code> is the first comment (the first
285      * javadoc comment if <code>ignoreJavadoc</code> is
286      * <code>true</code>) of the given member.
287      * <p>
288      * see also https://bugs.eclipse.org/bugs/show_bug.cgi?id=55325 (don't add parameters if the member already has a comment)
289      * </p>
290      */

291     private boolean isFirstComment(IDocument document, DocumentCommand command, IMember member, boolean ignoreNonJavadoc) throws BadLocationException, JavaModelException {
292         IRegion partition= TextUtilities.getPartition(document, fPartitioning, command.offset, false);
293         ISourceRange sourceRange= member.getSourceRange();
294         if (sourceRange == null || sourceRange.getOffset() != partition.getOffset())
295             return false;
296         int srcOffset= sourceRange.getOffset();
297         int srcLength= sourceRange.getLength();
298         int nameRelativeOffset= member.getNameRange().getOffset() - srcOffset;
299         int partitionRelativeOffset= partition.getOffset() - srcOffset;
300         String JavaDoc token= ignoreNonJavadoc ? "/**" : "/*"; //$NON-NLS-1$ //$NON-NLS-2$
301
return document.get(srcOffset, srcLength).lastIndexOf(token, nameRelativeOffset) == partitionRelativeOffset;
302     }
303
304     /**
305      * Unindents a typed slash ('/') if it forms the end of a comment.
306      *
307      * @param d the document
308      * @param c the command
309      */

310     private void indentAfterCommentEnd(IDocument d, DocumentCommand c) {
311         if (c.offset < 2 || d.getLength() == 0) {
312             return;
313         }
314         try {
315             if ("* ".equals(d.get(c.offset - 2, 2))) { //$NON-NLS-1$
316
// modify document command
317
c.length++;
318                 c.offset--;
319             }
320         } catch (BadLocationException excp) {
321             // stop work
322
}
323     }
324
325     /**
326      * Guesses if the command operates within a newly created javadoc comment or not.
327      * If in doubt, it will assume that the javadoc is new.
328      *
329      * @param document the document
330      * @param commandOffset the command offset
331      * @return <code>true</code> if the comment should be closed, <code>false</code> if not
332      */

333     private boolean isNewComment(IDocument document, int commandOffset) {
334
335         try {
336             int lineIndex= document.getLineOfOffset(commandOffset) + 1;
337             if (lineIndex >= document.getNumberOfLines())
338                 return true;
339
340             IRegion line= document.getLineInformation(lineIndex);
341             ITypedRegion partition= TextUtilities.getPartition(document, fPartitioning, commandOffset, false);
342             int partitionEnd= partition.getOffset() + partition.getLength();
343             if (line.getOffset() >= partitionEnd)
344                 return false;
345
346             if (document.getLength() == partitionEnd)
347                 return true; // partition goes to end of document - probably a new comment
348

349             String JavaDoc comment= document.get(partition.getOffset(), partition.getLength());
350             if (comment.indexOf("/*", 2) != -1) //$NON-NLS-1$
351
return true; // enclosed another comment -> probably a new comment
352

353             return false;
354
355         } catch (BadLocationException e) {
356             return false;
357         }
358     }
359
360     private boolean isSmartMode() {
361         IWorkbenchPage page= JavaPlugin.getActivePage();
362         if (page != null) {
363             IEditorPart part= page.getActiveEditor();
364             if (part instanceof ITextEditorExtension3) {
365                 ITextEditorExtension3 extension= (ITextEditorExtension3) part;
366                 return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT;
367             }
368         }
369         return false;
370     }
371
372     /*
373      * @see IAutoIndentStrategy#customizeDocumentCommand
374      */

375     public void customizeDocumentCommand(IDocument document, DocumentCommand command) {
376
377         if (!isSmartMode())
378             return;
379
380         if (command.text != null) {
381             if (command.length == 0) {
382                 String JavaDoc[] lineDelimiters= document.getLegalLineDelimiters();
383                 int index= TextUtilities.endsWith(lineDelimiters, command.text);
384                 if (index > -1) {
385                     // ends with line delimiter
386
if (lineDelimiters[index].equals(command.text))
387                         // just the line delimiter
388
indentAfterNewLine(document, command);
389                     return;
390                 }
391             }
392
393             if (command.text.equals("/")) { //$NON-NLS-1$
394
indentAfterCommentEnd(document, command);
395                 return;
396             }
397         }
398     }
399
400     /**
401      * Returns the method inherited from, <code>null</code> if method is newly defined.
402      * @param method the method being written
403      * @return the ancestor method, or <code>null</code> if none
404      * @throws JavaModelException if accessing the java model fails
405      */

406     private static IMethod getInheritedMethod(IMethod method) throws JavaModelException {
407         IType declaringType= method.getDeclaringType();
408         MethodOverrideTester tester= SuperTypeHierarchyCache.getMethodOverrideTester(declaringType);
409         return tester.findOverriddenMethod(method, true);
410     }
411
412     /**
413      * Returns the compilation unit of the CompilationUnitEditor invoking the AutoIndentStrategy,
414      * might return <code>null</code> on error.
415      * @return the compilation unit represented by the document
416      */

417     private static ICompilationUnit getCompilationUnit() {
418
419         IWorkbenchWindow window= PlatformUI.getWorkbench().getActiveWorkbenchWindow();
420         if (window == null)
421             return null;
422
423         IWorkbenchPage page= window.getActivePage();
424         if (page == null)
425             return null;
426
427         IEditorPart editor= page.getActiveEditor();
428         if (editor == null)
429             return null;
430
431         IWorkingCopyManager manager= JavaPlugin.getDefault().getWorkingCopyManager();
432         ICompilationUnit unit= manager.getWorkingCopy(editor.getEditorInput());
433         if (unit == null)
434             return null;
435
436         return unit;
437     }
438
439 }
440
Popular Tags