KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > ui > text > java > SmartSemicolonAutoEditStrategy


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.ui.text.java;
12
13 import java.util.Arrays JavaDoc;
14
15 import org.eclipse.text.edits.DeleteEdit;
16 import org.eclipse.text.edits.MalformedTreeException;
17 import org.eclipse.text.edits.ReplaceEdit;
18 import org.eclipse.text.edits.TextEdit;
19
20 import org.eclipse.core.runtime.Assert;
21
22 import org.eclipse.jface.preference.IPreferenceStore;
23
24 import org.eclipse.jface.text.BadLocationException;
25 import org.eclipse.jface.text.DocumentCommand;
26 import org.eclipse.jface.text.IAutoEditStrategy;
27 import org.eclipse.jface.text.IDocument;
28 import org.eclipse.jface.text.IRegion;
29 import org.eclipse.jface.text.ITextSelection;
30 import org.eclipse.jface.text.ITypedRegion;
31 import org.eclipse.jface.text.Region;
32 import org.eclipse.jface.text.TextSelection;
33 import org.eclipse.jface.text.TextUtilities;
34
35 import org.eclipse.ui.IEditorPart;
36 import org.eclipse.ui.IWorkbenchPage;
37 import org.eclipse.ui.texteditor.ITextEditorExtension2;
38 import org.eclipse.ui.texteditor.ITextEditorExtension3;
39
40 import org.eclipse.jdt.ui.PreferenceConstants;
41 import org.eclipse.jdt.ui.text.IJavaPartitions;
42
43 import org.eclipse.jdt.internal.ui.JavaPlugin;
44 import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor;
45 import org.eclipse.jdt.internal.ui.text.SmartBackspaceManager;
46 import org.eclipse.jdt.internal.ui.text.SmartBackspaceManager.UndoSpec;
47
48 /**
49  * Modifies <code>DocumentCommand</code>s inserting semicolons and opening braces to place them
50  * smartly, i.e. moving them to the end of a line if that is what the user expects.
51  *
52  * <p>In practice, semicolons and braces (and the caret) are moved to the end of the line if they are typed
53  * anywhere except for semicolons in a <code>for</code> statements definition. If the line contains a semicolon
54  * or brace after the current caret position, the cursor is moved after it.</p>
55  *
56  * @see org.eclipse.jface.text.DocumentCommand
57  * @since 3.0
58  */

59 public class SmartSemicolonAutoEditStrategy implements IAutoEditStrategy {
60
61     /** String representation of a semicolon. */
62     private static final String JavaDoc SEMICOLON= ";"; //$NON-NLS-1$
63
/** Char representation of a semicolon. */
64     private static final char SEMICHAR= ';';
65     /** String represenattion of a opening brace. */
66     private static final String JavaDoc BRACE= "{"; //$NON-NLS-1$
67
/** Char representation of a opening brace */
68     private static final char BRACECHAR= '{';
69
70     private char fCharacter;
71     private String JavaDoc fPartitioning;
72
73     /**
74      * Creates a new SmartSemicolonAutoEditStrategy.
75      *
76      * @param partitioning the document partitioning
77      */

78     public SmartSemicolonAutoEditStrategy(String JavaDoc partitioning) {
79         fPartitioning= partitioning;
80     }
81
82     /*
83      * @see org.eclipse.jface.text.IAutoEditStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand)
84      */

85     public void customizeDocumentCommand(IDocument document, DocumentCommand command) {
86         // 0: early pruning
87
// also customize if <code>doit</code> is false (so it works in code completion situations)
88
// if (!command.doit)
89
// return;
90

91         if (command.text == null)
92             return;
93
94         if (command.text.equals(SEMICOLON))
95             fCharacter= SEMICHAR;
96         else if (command.text.equals(BRACE))
97             fCharacter= BRACECHAR;
98         else
99             return;
100
101         IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore();
102         if (fCharacter == SEMICHAR && !store.getBoolean(PreferenceConstants.EDITOR_SMART_SEMICOLON))
103             return;
104         if (fCharacter == BRACECHAR && !store.getBoolean(PreferenceConstants.EDITOR_SMART_OPENING_BRACE))
105             return;
106
107         IWorkbenchPage page= JavaPlugin.getActivePage();
108         if (page == null)
109             return;
110         IEditorPart part= page.getActiveEditor();
111         if (!(part instanceof CompilationUnitEditor))
112             return;
113         CompilationUnitEditor editor= (CompilationUnitEditor)part;
114         if (editor.getInsertMode() != ITextEditorExtension3.SMART_INSERT || !editor.isEditable())
115             return;
116         ITextEditorExtension2 extension= (ITextEditorExtension2)editor.getAdapter(ITextEditorExtension2.class);
117         if (extension != null && !extension.validateEditorInputState())
118             return;
119         if (isMultilineSelection(document, command))
120             return;
121
122         // 1: find concerned line / position in java code, location in statement
123
int pos= command.offset;
124         ITextSelection line;
125         try {
126             IRegion l= document.getLineInformationOfOffset(pos);
127             line= new TextSelection(document, l.getOffset(), l.getLength());
128         } catch (BadLocationException e) {
129             return;
130         }
131
132         // 2: choose action based on findings (is for-Statement?)
133
// for now: compute the best position to insert the new character
134
int positionInLine= computeCharacterPosition(document, line, pos - line.getOffset(), fCharacter, fPartitioning);
135         int position= positionInLine + line.getOffset();
136
137         // never position before the current position!
138
if (position < pos)
139             return;
140
141         // never double already existing content
142
if (alreadyPresent(document, fCharacter, position))
143             return;
144
145         // don't do special processing if what we do is actually the normal behaviour
146
String JavaDoc insertion= adjustSpacing(document, position, fCharacter);
147         if (command.offset == position && insertion.equals(command.text))
148             return;
149
150         try {
151
152             final SmartBackspaceManager manager= (SmartBackspaceManager) editor.getAdapter(SmartBackspaceManager.class);
153             if (manager != null && JavaPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SMART_BACKSPACE)) {
154                 TextEdit e1= new ReplaceEdit(command.offset, command.text.length(), document.get(command.offset, command.length));
155                 UndoSpec s1= new UndoSpec(command.offset + command.text.length(),
156                         new Region(command.offset, 0),
157                         new TextEdit[] {e1},
158                         0,
159                         null);
160
161                 DeleteEdit smart= new DeleteEdit(position, insertion.length());
162                 ReplaceEdit raw= new ReplaceEdit(command.offset, command.length, command.text);
163                 UndoSpec s2= new UndoSpec(position + insertion.length(),
164                         new Region(command.offset + command.text.length(), 0),
165                         new TextEdit[] {smart, raw},
166                         2,
167                         s1);
168                 manager.register(s2);
169             }
170
171             // 3: modify command
172
command.offset= position;
173             command.length= 0;
174             command.caretOffset= position;
175             command.text= insertion;
176             command.doit= true;
177             command.owner= null;
178         } catch (MalformedTreeException e) {
179             JavaPlugin.log(e);
180         } catch (BadLocationException e) {
181             JavaPlugin.log(e);
182         }
183
184
185     }
186
187     /**
188      * Returns <code>true</code> if the document command is applied on a multi
189      * line selection, <code>false</code> otherwise.
190      *
191      * @param document the document
192      * @param command the command
193      * @return <code>true</code> if <code>command</code> is a multiline command
194      */

195     private boolean isMultilineSelection(IDocument document, DocumentCommand command) {
196         try {
197             return document.getNumberOfLines(command.offset, command.length) > 1;
198         } catch (BadLocationException e) {
199             // ignore
200
return false;
201         }
202     }
203
204     /**
205      * Adds a space before a brace if it is inserted after a parenthesis, equal sign, or one
206      * of the keywords <code>try, else, do</code>.
207      *
208      * @param doc the document we are working on
209      * @param position the insert position of <code>character</code>
210      * @param character the character to be inserted
211      * @return a <code>String</code> consisting of <code>character</code> plus any additional spacing
212      */

213     private String JavaDoc adjustSpacing(IDocument doc, int position, char character) {
214         if (character == BRACECHAR) {
215             if (position > 0 && position <= doc.getLength()) {
216                 int pos= position - 1;
217                 if (looksLike(doc, pos, ")") //$NON-NLS-1$
218
|| looksLike(doc, pos, "=") //$NON-NLS-1$
219
|| looksLike(doc, pos, "]") //$NON-NLS-1$
220
|| looksLike(doc, pos, "try") //$NON-NLS-1$
221
|| looksLike(doc, pos, "else") //$NON-NLS-1$
222
|| looksLike(doc, pos, "synchronized") //$NON-NLS-1$
223
|| looksLike(doc, pos, "static") //$NON-NLS-1$
224
|| looksLike(doc, pos, "finally") //$NON-NLS-1$
225
|| looksLike(doc, pos, "do")) //$NON-NLS-1$
226
return new String JavaDoc(new char[] { ' ', character });
227             }
228         }
229
230         return new String JavaDoc(new char[] { character });
231     }
232
233     /**
234      * Checks whether a character to be inserted is already present at the insert location (perhaps
235      * separated by some whitespace from <code>position</code>.
236      *
237      * @param document the document we are working on
238      * @param position the insert position of <code>ch</code>
239      * @param ch the character to be inserted
240      * @return <code>true</code> if <code>ch</code> is already present at <code>location</code>, <code>false</code> otherwise
241      */

242     private boolean alreadyPresent(IDocument document, char ch, int position) {
243         int pos= firstNonWhitespaceForward(document, position, fPartitioning, document.getLength());
244         try {
245             if (pos != -1 && document.getChar(pos) == ch)
246                 return true;
247         } catch (BadLocationException e) {
248         }
249
250         return false;
251     }
252
253     /**
254      * Computes the next insert position of the given character in the current line.
255      *
256      * @param document the document we are working on
257      * @param line the line where the change is being made
258      * @param offset the position of the caret in the line when <code>character</code> was typed
259      * @param character the character to look for
260      * @param partitioning the document partitioning
261      * @return the position where <code>character</code> should be inserted / replaced
262      */

263     protected static int computeCharacterPosition(IDocument document, ITextSelection line, int offset, char character, String JavaDoc partitioning) {
264         String JavaDoc text= line.getText();
265         if (text == null)
266             return 0;
267
268         int insertPos;
269         if (character == BRACECHAR) {
270
271             insertPos= computeArrayInitializationPos(document, line, offset, partitioning);
272
273             if (insertPos == -1) {
274                 insertPos= computeAfterTryDoElse(document, line, offset);
275             }
276
277             if (insertPos == -1) {
278                 insertPos= computeAfterParenthesis(document, line, offset, partitioning);
279             }
280
281         } else if (character == SEMICHAR) {
282
283             if (isForStatement(text, offset)) {
284                 insertPos= -1; // don't do anything in for statements, as semis are vital part of these
285
} else {
286                 int nextPartitionPos= nextPartitionOrLineEnd(document, line, offset, partitioning);
287                 insertPos= startOfWhitespaceBeforeOffset(text, nextPartitionPos);
288                 // if there is a semi present, return its location as alreadyPresent() will take it out this way.
289
if (insertPos > 0 && text.charAt(insertPos - 1) == character)
290                     insertPos= insertPos - 1;
291                 else if (insertPos > 0 && text.charAt(insertPos - 1) == '}') {
292                     int opening= scanBackward(document, insertPos - 1 + line.getOffset(), partitioning, -1, new char[] { '{' });
293                     if (opening > -1 && opening < offset + line.getOffset()) {
294                         if (computeArrayInitializationPos(document, line, opening - line.getOffset(), partitioning) == -1) {
295                             insertPos= offset;
296                         }
297                     }
298                 }
299             }
300
301         } else {
302             Assert.isTrue(false);
303             return -1;
304         }
305
306         return insertPos;
307     }
308
309     /**
310      * Computes an insert position for an opening brace if <code>offset</code> maps to a position in
311      * <code>document</code> that looks like being the RHS of an assignment or like an array definition.
312      *
313      * @param document the document being modified
314      * @param line the current line under investigation
315      * @param offset the offset of the caret position, relative to the line start.
316      * @param partitioning the document partitioning
317      * @return an insert position relative to the line start if <code>line</code> looks like being an array initialization at <code>offset</code>, -1 otherwise
318      */

319     private static int computeArrayInitializationPos(IDocument document, ITextSelection line, int offset, String JavaDoc partitioning) {
320         // search backward while WS, find = (not != <= >= ==) in default partition
321
int pos= offset + line.getOffset();
322
323         if (pos == 0)
324             return -1;
325
326         int p= firstNonWhitespaceBackward(document, pos - 1, partitioning, -1);
327
328         if (p == -1)
329             return -1;
330
331         try {
332
333             char ch= document.getChar(p);
334             if (ch != '=' && ch != ']')
335                 return -1;
336
337             if (p == 0)
338                 return offset;
339
340             p= firstNonWhitespaceBackward(document, p - 1, partitioning, -1);
341             if (p == -1)
342                 return -1;
343
344             ch= document.getChar(p);
345             if (Character.isJavaIdentifierPart(ch) || ch == ']' || ch == '[')
346                 return offset;
347
348         } catch (BadLocationException e) {
349         }
350         return -1;
351     }
352
353     /**
354      * Computes an insert position for an opening brace if <code>offset</code> maps to a position in
355      * <code>doc</code> involving a keyword taking a block after it. These are: <code>try</code>,
356      * <code>do</code>, <code>synchronized</code>, <code>static</code>, <code>finally</code>, or <code>else</code>.
357      *
358      * @param doc the document being modified
359      * @param line the current line under investigation
360      * @param offset the offset of the caret position, relative to the line start.
361      * @return an insert position relative to the line start if <code>line</code> contains one of the above keywords at or before <code>offset</code>, -1 otherwise
362      */

363     private static int computeAfterTryDoElse(IDocument doc, ITextSelection line, int offset) {
364         // search backward while WS, find 'try', 'do', 'else' in default partition
365
int p= offset + line.getOffset();
366         p= firstWhitespaceToRight(doc, p);
367         if (p == -1)
368             return -1;
369         p--;
370
371         if (looksLike(doc, p, "try") //$NON-NLS-1$
372
|| looksLike(doc, p, "do") //$NON-NLS-1$
373
|| looksLike(doc, p, "synchronized") //$NON-NLS-1$
374
|| looksLike(doc, p, "static") //$NON-NLS-1$
375
|| looksLike(doc, p, "finally") //$NON-NLS-1$
376
|| looksLike(doc, p, "else")) //$NON-NLS-1$
377
return p + 1 - line.getOffset();
378
379         return -1;
380     }
381
382     /**
383      * Computes an insert position for an opening brace if <code>offset</code> maps to a position in
384      * <code>document</code> with a expression in parenthesis that will take a block after the closing parenthesis.
385      *
386      * @param document the document being modified
387      * @param line the current line under investigation
388      * @param offset the offset of the caret position, relative to the line start.
389      * @param partitioning the document partitioning
390      * @return an insert position relative to the line start if <code>line</code> contains a parenthesized expression that can be followed by a block, -1 otherwise
391      */

392     private static int computeAfterParenthesis(IDocument document, ITextSelection line, int offset, String JavaDoc partitioning) {
393         // find the opening parenthesis for every closing parenthesis on the current line after offset
394
// return the position behind the closing parenthesis if it looks like a method declaration
395
// or an expression for an if, while, for, catch statement
396
int pos= offset + line.getOffset();
397         int length= line.getOffset() + line.getLength();
398         int scanTo= scanForward(document, pos, partitioning, length, '}');
399         if (scanTo == -1)
400             scanTo= length;
401
402         int closingParen= findClosingParenToLeft(document, pos, partitioning) - 1;
403
404         while (true) {
405             int startScan= closingParen + 1;
406             closingParen= scanForward(document, startScan, partitioning, scanTo, ')');
407             if (closingParen == -1)
408                 break;
409
410             int openingParen= findOpeningParenMatch(document, closingParen, partitioning);
411
412             // no way an expression at the beginning of the document can mean anything
413
if (openingParen < 1)
414                 break;
415
416             // only select insert positions for parenthesis currently embracing the caret
417
if (openingParen > pos)
418                 continue;
419
420             if (looksLikeAnonymousClassDef(document, openingParen - 1, partitioning))
421                 return closingParen + 1 - line.getOffset();
422
423             if (looksLikeIfWhileForCatch(document, openingParen - 1, partitioning))
424                 return closingParen + 1 - line.getOffset();
425
426             if (looksLikeMethodDecl(document, openingParen - 1, partitioning))
427                 return closingParen + 1 - line.getOffset();
428
429         }
430
431         return -1;
432     }
433
434     /**
435      * Finds a closing parenthesis to the left of <code>position</code> in document, where that parenthesis is only
436      * separated by whitespace from <code>position</code>. If no such parenthesis can be found, <code>position</code> is returned.
437      *
438      * @param document the document being modified
439      * @param position the first character position in <code>document</code> to be considered
440      * @param partitioning the document partitioning
441      * @return the position of a closing parenthesis left to <code>position</code> separated only by whitespace, or <code>position</code> if no parenthesis can be found
442      */

443     private static int findClosingParenToLeft(IDocument document, int position, String JavaDoc partitioning) {
444         final char CLOSING_PAREN= ')';
445         try {
446             if (position < 1)
447                 return position;
448
449             int nonWS= firstNonWhitespaceBackward(document, position - 1, partitioning, -1);
450             if (nonWS != -1 && document.getChar(nonWS) == CLOSING_PAREN)
451                 return nonWS;
452         } catch (BadLocationException e1) {
453         }
454         return position;
455     }
456
457     /**
458      * Finds the first whitespace character position to the right of (and including) <code>position</code>.
459      *
460      * @param document the document being modified
461      * @param position the first character position in <code>document</code> to be considered
462      * @return the position of a whitespace character greater or equal than <code>position</code> separated only by whitespace, or -1 if none found
463      */

464     private static int firstWhitespaceToRight(IDocument document, int position) {
465         int length= document.getLength();
466         Assert.isTrue(position >= 0);
467         Assert.isTrue(position <= length);
468
469         try {
470             while (position < length) {
471                 char ch= document.getChar(position);
472                 if (Character.isWhitespace(ch))
473                     return position;
474                 position++;
475             }
476             return position;
477         } catch (BadLocationException e) {
478         }
479         return -1;
480     }
481
482     /**
483      * Finds the highest position in <code>document</code> such that the position is &lt;= <code>position</code>
484      * and &gt; <code>bound</code> and <code>Character.isWhitespace(document.getChar(pos))</code> evaluates to <code>false</code>
485      * and the position is in the default partition.
486      *
487      * @param document the document being modified
488      * @param position the first character position in <code>document</code> to be considered
489      * @param partitioning the document partitioning
490      * @param bound the first position in <code>document</code> to not consider any more, with <code>bound</code> &lt; <code>position</code>
491      * @return the highest position of one element in <code>chars</code> in [<code>position</code>, <code>scanTo</code>) that resides in a Java partition, or <code>-1</code> if none can be found
492      */

493     private static int firstNonWhitespaceBackward(IDocument document, int position, String JavaDoc partitioning, int bound) {
494         Assert.isTrue(position < document.getLength());
495         Assert.isTrue(bound >= -1);
496
497         try {
498             while (position > bound) {
499                 char ch= document.getChar(position);
500                 if (!Character.isWhitespace(ch) && isDefaultPartition(document, position, partitioning))
501                     return position;
502                 position--;
503             }
504         } catch (BadLocationException e) {
505         }
506         return -1;
507     }
508
509     /**
510      * Finds the smallest position in <code>document</code> such that the position is &gt;= <code>position</code>
511      * and &lt; <code>bound</code> and <code>Character.isWhitespace(document.getChar(pos))</code> evaluates to <code>false</code>
512      * and the position is in the default partition.
513      *
514      * @param document the document being modified
515      * @param position the first character position in <code>document</code> to be considered
516      * @param partitioning the document partitioning
517      * @param bound the first position in <code>document</code> to not consider any more, with <code>bound</code> &gt; <code>position</code>
518      * @return the smallest position of one element in <code>chars</code> in [<code>position</code>, <code>scanTo</code>) that resides in a Java partition, or <code>-1</code> if none can be found
519      */

520     private static int firstNonWhitespaceForward(IDocument document, int position, String JavaDoc partitioning, int bound) {
521         Assert.isTrue(position >= 0);
522         Assert.isTrue(bound <= document.getLength());
523
524         try {
525             while (position < bound) {
526                 char ch= document.getChar(position);
527                 if (!Character.isWhitespace(ch) && isDefaultPartition(document, position, partitioning))
528                     return position;
529                 position++;
530             }
531         } catch (BadLocationException e) {
532         }
533         return -1;
534     }
535
536     /**
537      * Finds the highest position in <code>document</code> such that the position is &lt;= <code>position</code>
538      * and &gt; <code>bound</code> and <code>document.getChar(position) == ch</code> evaluates to <code>true</code> for at least one
539      * ch in <code>chars</code> and the position is in the default partition.
540      *
541      * @param document the document being modified
542      * @param position the first character position in <code>document</code> to be considered
543      * @param partitioning the document partitioning
544      * @param bound the first position in <code>document</code> to not consider any more, with <code>scanTo</code> &gt; <code>position</code>
545      * @param chars an array of <code>char</code> to search for
546      * @return the highest position of one element in <code>chars</code> in (<code>bound</code>, <code>position</code>] that resides in a Java partition, or <code>-1</code> if none can be found
547      */

548     private static int scanBackward(IDocument document, int position, String JavaDoc partitioning, int bound, char[] chars) {
549         Assert.isTrue(bound >= -1);
550         Assert.isTrue(position < document.getLength() );
551
552         Arrays.sort(chars);
553
554         try {
555             while (position > bound) {
556
557                 if (Arrays.binarySearch(chars, document.getChar(position)) >= 0 && isDefaultPartition(document, position, partitioning))
558                     return position;
559
560                 position--;
561             }
562         } catch (BadLocationException e) {
563         }
564         return -1;
565     }
566
567 // /**
568
// * Finds the highest position in <code>document</code> such that the position is &lt;= <code>position</code>
569
// * and &gt; <code>bound</code> and <code>document.getChar(position) == ch</code> evaluates to <code>true</code>
570
// * and the position is in the default partition.
571
// *
572
// * @param document the document being modified
573
// * @param position the first character position in <code>document</code> to be considered
574
// * @param bound the first position in <code>document</code> to not consider any more, with <code>scanTo</code> &gt; <code>position</code>
575
// * @param chars an array of <code>char</code> to search for
576
// * @return the highest position of one element in <code>chars</code> in [<code>position</code>, <code>scanTo</code>) that resides in a Java partition, or <code>-1</code> if none can be found
577
// */
578
// private static int scanBackward(IDocument document, int position, int bound, char ch) {
579
// return scanBackward(document, position, bound, new char[] {ch});
580
// }
581
//
582
/**
583      * Finds the lowest position in <code>document</code> such that the position is &gt;= <code>position</code>
584      * and &lt; <code>bound</code> and <code>document.getChar(position) == ch</code> evaluates to <code>true</code> for at least one
585      * ch in <code>chars</code> and the position is in the default partition.
586      *
587      * @param document the document being modified
588      * @param position the first character position in <code>document</code> to be considered
589      * @param partitioning the document partitioning
590      * @param bound the first position in <code>document</code> to not consider any more, with <code>scanTo</code> &gt; <code>position</code>
591      * @param chars an array of <code>char</code> to search for
592      * @return the lowest position of one element in <code>chars</code> in [<code>position</code>, <code>bound</code>) that resides in a Java partition, or <code>-1</code> if none can be found
593      */

594     private static int scanForward(IDocument document, int position, String JavaDoc partitioning, int bound, char[] chars) {
595         Assert.isTrue(position >= 0);
596         Assert.isTrue(bound <= document.getLength());
597
598         Arrays.sort(chars);
599
600         try {
601             while (position < bound) {
602
603                 if (Arrays.binarySearch(chars, document.getChar(position)) >= 0 && isDefaultPartition(document, position, partitioning))
604                     return position;
605
606                 position++;
607             }
608         } catch (BadLocationException e) {
609         }
610         return -1;
611     }
612
613     /**
614      * Finds the lowest position in <code>document</code> such that the position is &gt;= <code>position</code>
615      * and &lt; <code>bound</code> and <code>document.getChar(position) == ch</code> evaluates to <code>true</code>
616      * and the position is in the default partition.
617      *
618      * @param document the document being modified
619      * @param position the first character position in <code>document</code> to be considered
620      * @param partitioning the document partitioning
621      * @param bound the first position in <code>document</code> to not consider any more, with <code>scanTo</code> &gt; <code>position</code>
622      * @param ch a <code>char</code> to search for
623      * @return the lowest position of one element in <code>chars</code> in [<code>position</code>, <code>bound</code>) that resides in a Java partition, or <code>-1</code> if none can be found
624      */

625     private static int scanForward(IDocument document, int position, String JavaDoc partitioning, int bound, char ch) {
626         return scanForward(document, position, partitioning, bound, new char[] {ch});
627     }
628
629     /**
630      * Checks whether the content of <code>document</code> in the range (<code>offset</code>, <code>length</code>)
631      * contains the <code>new</code> keyword.
632      *
633      * @param document the document being modified
634      * @param offset the first character position in <code>document</code> to be considered
635      * @param length the length of the character range to be considered
636      * @param partitioning the document partitioning
637      * @return <code>true</code> if the specified character range contains a <code>new</code> keyword, <code>false</code> otherwise.
638      */

639     private static boolean isNewMatch(IDocument document, int offset, int length, String JavaDoc partitioning) {
640         Assert.isTrue(length >= 0);
641         Assert.isTrue(offset >= 0);
642         Assert.isTrue(offset + length < document.getLength() + 1);
643
644         try {
645             String JavaDoc text= document.get(offset, length);
646             int pos= text.indexOf("new"); //$NON-NLS-1$
647

648             while (pos != -1 && !isDefaultPartition(document, pos + offset, partitioning))
649                 pos= text.indexOf("new", pos + 2); //$NON-NLS-1$
650

651             if (pos < 0)
652                 return false;
653
654             if (pos != 0 && Character.isJavaIdentifierPart(text.charAt(pos - 1)))
655                 return false;
656
657             if (pos + 3 < length && Character.isJavaIdentifierPart(text.charAt(pos + 3)))
658                 return false;
659
660             return true;
661
662         } catch (BadLocationException e) {
663         }
664         return false;
665     }
666
667     /**
668      * Checks whether the content of <code>document</code> at <code>position</code> looks like an
669      * anonymous class definition. <code>position</code> must be to the left of the opening
670      * parenthesis of the definition's parameter list.
671      *
672      * @param document the document being modified
673      * @param position the first character position in <code>document</code> to be considered
674      * @param partitioning the document partitioning
675      * @return <code>true</code> if the content of <code>document</code> looks like an anonymous class definition, <code>false</code> otherwise
676      */

677     private static boolean looksLikeAnonymousClassDef(IDocument document, int position, String JavaDoc partitioning) {
678         int previousCommaParenEqual= scanBackward(document, position - 1, partitioning, -1, new char[] {',', '(', '='});
679         if (previousCommaParenEqual == -1 || position < previousCommaParenEqual + 5) // 2 for borders, 3 for "new"
680
return false;
681
682         if (isNewMatch(document, previousCommaParenEqual + 1, position - previousCommaParenEqual - 2, partitioning))
683             return true;
684
685         return false;
686     }
687
688     /**
689      * Checks whether <code>position</code> resides in a default (Java) partition of <code>document</code>.
690      *
691      * @param document the document being modified
692      * @param position the position to be checked
693      * @param partitioning the document partitioning
694      * @return <code>true</code> if <code>position</code> is in the default partition of <code>document</code>, <code>false</code> otherwise
695      */

696     private static boolean isDefaultPartition(IDocument document, int position, String JavaDoc partitioning) {
697         Assert.isTrue(position >= 0);
698         Assert.isTrue(position <= document.getLength());
699
700         try {
701             // don't use getPartition2 since we're interested in the scanned character's partition
702
ITypedRegion region= TextUtilities.getPartition(document, partitioning, position, false);
703             return region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE);
704
705         } catch (BadLocationException e) {
706         }
707
708         return false;
709     }
710
711     /**
712      * Finds the position of the parenthesis matching the closing parenthesis at <code>position</code>.
713      *
714      * @param document the document being modified
715      * @param position the position in <code>document</code> of a closing parenthesis
716      * @param partitioning the document partitioning
717      * @return the position in <code>document</code> of the matching parenthesis, or -1 if none can be found
718      */

719     private static int findOpeningParenMatch(IDocument document, int position, String JavaDoc partitioning) {
720         final char CLOSING_PAREN= ')';
721         final char OPENING_PAREN= '(';
722
723         Assert.isTrue(position < document.getLength());
724         Assert.isTrue(position >= 0);
725         Assert.isTrue(isDefaultPartition(document, position, partitioning));
726
727         try {
728
729             Assert.isTrue(document.getChar(position) == CLOSING_PAREN);
730
731             int depth= 1;
732             while (true) {
733                 position= scanBackward(document, position - 1, partitioning, -1, new char[] {CLOSING_PAREN, OPENING_PAREN});
734                 if (position == -1)
735                     return -1;
736
737                 if (document.getChar(position) == CLOSING_PAREN)
738                     depth++;
739                 else
740                     depth--;
741
742                 if (depth == 0)
743                     return position;
744             }
745
746         } catch (BadLocationException e) {
747             return -1;
748         }
749     }
750
751     /**
752      * Checks whether, to the left of <code>position</code> and separated only by whitespace,
753      * <code>document</code> contains a keyword taking a parameter list and a block after it.
754      * These are: <code>if</code>, <code>while</code>, <code>catch</code>, <code>for</code>, <code>synchronized</code>, <code>switch</code>.
755      *
756      * @param document the document being modified
757      * @param position the first character position in <code>document</code> to be considered
758      * @param partitioning the document partitioning
759      * @return <code>true</code> if <code>document</code> contains any of the above keywords to the left of <code>position</code>, <code>false</code> otherwise
760      */

761     private static boolean looksLikeIfWhileForCatch(IDocument document, int position, String JavaDoc partitioning) {
762         position= firstNonWhitespaceBackward(document, position, partitioning, -1);
763         if (position == -1)
764             return false;
765
766         return looksLike(document, position, "if") //$NON-NLS-1$
767
|| looksLike(document, position, "while") //$NON-NLS-1$
768
|| looksLike(document, position, "catch") //$NON-NLS-1$
769
|| looksLike(document, position, "synchronized") //$NON-NLS-1$
770
|| looksLike(document, position, "switch") //$NON-NLS-1$
771
|| looksLike(document, position, "for"); //$NON-NLS-1$
772
}
773
774     /**
775      * Checks whether code>document</code> contains the <code>String</code> <code>like</code> such
776      * that its last character is at <code>position</code>. If <code>like</code> starts with a
777      * identifier part (as determined by {@link Character#isJavaIdentifierPart(char)}), it is also made
778      * sure that <code>like</code> is preceded by some non-identifier character or stands at the
779      * document start.
780      *
781      * @param document the document being modified
782      * @param position the first character position in <code>document</code> to be considered
783      * @param like the <code>String</code> to look for.
784      * @return <code>true</code> if <code>document</code> contains <code>like</code> such that it ends at <code>position</code>, <code>false</code> otherwise
785      */

786     private static boolean looksLike(IDocument document, int position, String JavaDoc like) {
787         int length= like.length();
788         if (position < length - 1)
789             return false;
790
791         try {
792             if (!like.equals(document.get(position - length + 1, length)))
793                 return false;
794
795             if (position >= length && Character.isJavaIdentifierPart(like.charAt(0)) && Character.isJavaIdentifierPart(document.getChar(position - length)))
796                 return false;
797
798         } catch (BadLocationException e) {
799             return false;
800         }
801
802         return true;
803     }
804
805     /**
806      * Checks whether the content of <code>document</code> at <code>position</code> looks like a
807      * method declaration header (i.e. only the return type and method name). <code>position</code>
808      * must be just left of the opening parenthesis of the parameter list.
809      *
810      * @param document the document being modified
811      * @param position the first character position in <code>document</code> to be considered
812      * @param partitioning the document partitioning
813      * @return <code>true</code> if the content of <code>document</code> looks like a method definition, <code>false</code> otherwise
814      */

815     private static boolean looksLikeMethodDecl(IDocument document, int position, String JavaDoc partitioning) {
816
817         // method name
818
position= eatIdentToLeft(document, position, partitioning);
819         if (position < 1)
820             return false;
821
822         position= eatBrackets(document, position - 1, partitioning);
823         if (position < 1)
824             return false;
825
826         position= eatIdentToLeft(document, position - 1, partitioning);
827
828         return position != -1;
829     }
830
831     /**
832      * From <code>position</code> to the left, eats any whitespace and then a pair of brackets
833      * as used to declare an array return type like <pre>String [ ]</pre>.
834      * The return value is either the position of the opening bracket or <code>position</code> if no
835      * pair of brackets can be parsed.
836      *
837      * @param document the document being modified
838      * @param position the first character position in <code>document</code> to be considered
839      * @param partitioning the document partitioning
840      * @return the smallest character position of bracket pair or <code>position</code>
841      */

842     private static int eatBrackets(IDocument document, int position, String JavaDoc partitioning) {
843         // accept array return type
844
int pos= firstNonWhitespaceBackward(document, position, partitioning, -1);
845         try {
846             if (pos > 1 && document.getChar(pos) == ']') {
847                 pos= firstNonWhitespaceBackward(document, pos - 1, partitioning, -1);
848                 if (pos > 0 && document.getChar(pos) == '[')
849                     return pos;
850             }
851         } catch (BadLocationException e) {
852             // won't happen
853
}
854         return position;
855     }
856
857     /**
858      * From <code>position</code> to the left, eats any whitespace and the first identifier, returning
859      * the position of the first identifier character (in normal read order).
860      * <p>When called on a document with content <code>" some string "</code> and positionition 13, the
861      * return value will be 6 (the first letter in <code>string</code>).
862      * </p>
863      *
864      * @param document the document being modified
865      * @param position the first character position in <code>document</code> to be considered
866      * @param partitioning the document partitioning
867      * @return the smallest character position of an identifier or -1 if none can be found; always &lt;= <code>position</code>
868      */

869     private static int eatIdentToLeft(IDocument document, int position, String JavaDoc partitioning) {
870         if (position < 0)
871             return -1;
872         Assert.isTrue(position < document.getLength());
873
874         int p= firstNonWhitespaceBackward(document, position, partitioning, -1);
875         if (p == -1)
876             return -1;
877
878         try {
879             while (p >= 0) {
880
881                 char ch= document.getChar(p);
882                 if (Character.isJavaIdentifierPart(ch)) {
883                     p--;
884                     continue;
885                 }
886
887                 // length must be > 0
888
if (Character.isWhitespace(ch) && p != position)
889                     return p + 1;
890                 else
891                     return -1;
892
893             }
894
895             // start of document reached
896
return 0;
897
898         } catch (BadLocationException e) {
899         }
900         return -1;
901     }
902
903     /**
904      * Returns a position in the first java partition after the last non-empty and non-comment partition.
905      * There is no non-whitespace from the returned position to the end of the partition it is contained in.
906      *
907      * @param document the document being modified
908      * @param line the line under investigation
909      * @param offset the caret offset into <code>line</code>
910      * @param partitioning the document partitioning
911      * @return the position of the next Java partition, or the end of <code>line</code>
912      */

913     private static int nextPartitionOrLineEnd(IDocument document, ITextSelection line, int offset, String JavaDoc partitioning) {
914         // run relative to document
915
final int docOffset= offset + line.getOffset();
916         final int eol= line.getOffset() + line.getLength();
917         int nextPartitionPos= eol; // init with line end
918
int validPosition= docOffset;
919
920         try {
921             ITypedRegion partition= TextUtilities.getPartition(document, partitioning, nextPartitionPos, true);
922             validPosition= getValidPositionForPartition(document, partition, eol);
923             while (validPosition == -1) {
924                 nextPartitionPos= partition.getOffset() - 1;
925                 if (nextPartitionPos < docOffset) {
926                     validPosition= docOffset;
927                     break;
928                 }
929                 partition= TextUtilities.getPartition(document, partitioning, nextPartitionPos, false);
930                 validPosition= getValidPositionForPartition(document, partition, eol);
931             }
932         } catch (BadLocationException e) {
933         }
934
935         validPosition= Math.max(validPosition, docOffset);
936         // make relative to line
937
validPosition -= line.getOffset();
938         return validPosition;
939     }
940
941     /**
942      * Returns a valid insert location (except for whitespace) in <code>partition</code> or -1 if
943      * there is no valid insert location.
944      * An valid insert location is right after any java string or character partition, or at the end
945      * of a java default partition, but never behind <code>maxOffset</code>. Comment partitions or
946      * empty java partitions do never yield valid insert positions.
947      *
948      * @param doc the document being modified
949      * @param partition the current partition
950      * @param maxOffset the maximum offset to consider
951      * @return a valid insert location in <code>partition</code>, or -1 if there is no valid insert location
952      */

953     private static int getValidPositionForPartition(IDocument doc, ITypedRegion partition, int maxOffset) {
954         final int INVALID= -1;
955
956         if (IJavaPartitions.JAVA_DOC.equals(partition.getType()))
957             return INVALID;
958         if (IJavaPartitions.JAVA_MULTI_LINE_COMMENT.equals(partition.getType()))
959             return INVALID;
960         if (IJavaPartitions.JAVA_SINGLE_LINE_COMMENT.equals(partition.getType()))
961             return INVALID;
962
963         int endOffset= Math.min(maxOffset, partition.getOffset() + partition.getLength());
964
965         if (IJavaPartitions.JAVA_CHARACTER.equals(partition.getType()))
966             return endOffset;
967         if (IJavaPartitions.JAVA_STRING.equals(partition.getType()))
968             return endOffset;
969         if (IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())) {
970             try {
971                 if (doc.get(partition.getOffset(), endOffset - partition.getOffset()).trim().length() == 0)
972                     return INVALID;
973                 else
974                     return endOffset;
975             } catch (BadLocationException e) {
976                 return INVALID;
977             }
978         }
979         // default: we don't know anything about the partition - assume valid
980
return endOffset;
981     }
982
983     /**
984      * Determines whether the current line contains a for statement.
985      * Algorithm: any "for" word in the line is a positive, "for" contained in a string literal will
986      * produce a false positive.
987      *
988      * @param line the line where the change is being made
989      * @param offset the position of the caret
990      * @return <code>true</code> if <code>line</code> contains <code>for</code>, <code>false</code> otherwise
991      */

992     private static boolean isForStatement(String JavaDoc line, int offset) {
993         /* searching for (^|\s)for(\s|$) */
994         int forPos= line.indexOf("for"); //$NON-NLS-1$
995
if (forPos != -1) {
996             if ((forPos == 0 || !Character.isJavaIdentifierPart(line.charAt(forPos - 1))) && (line.length() == forPos + 3 || !Character.isJavaIdentifierPart(line.charAt(forPos + 3))))
997                 return true;
998         }
999         return false;
1000    }
1001
1002    /**
1003     * Returns the position in <code>text</code> after which there comes only whitespace, up to
1004     * <code>offset</code>.
1005     *
1006     * @param text the text being searched
1007     * @param offset the maximum offset to search for
1008     * @return the smallest value <code>v</code> such that <code>text.substring(v, offset).trim() == 0</code>
1009     */

1010    private static int startOfWhitespaceBeforeOffset(String JavaDoc text, int offset) {
1011        int i= Math.min(offset, text.length());
1012        for (; i >= 1; i--) {
1013            if (!Character.isWhitespace(text.charAt(i - 1)))
1014                break;
1015        }
1016        return i;
1017    }
1018}
1019
Popular Tags