]> git.uio.no Git - ifi-stolz-refaktor.git/blob - case-study/jdt-after/ui/org/eclipse/jdt/internal/ui/text/java/SmartSemicolonAutoEditStrategy.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-after / ui / org / eclipse / jdt / internal / ui / text / java / SmartSemicolonAutoEditStrategy.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2008 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;
14
15 import org.eclipse.core.runtime.Assert;
16
17 import org.eclipse.text.edits.DeleteEdit;
18 import org.eclipse.text.edits.MalformedTreeException;
19 import org.eclipse.text.edits.ReplaceEdit;
20 import org.eclipse.text.edits.TextEdit;
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
38 import org.eclipse.ui.texteditor.ITextEditorExtension2;
39 import org.eclipse.ui.texteditor.ITextEditorExtension3;
40
41 import org.eclipse.jdt.ui.PreferenceConstants;
42 import org.eclipse.jdt.ui.text.IJavaPartitions;
43
44 import org.eclipse.jdt.internal.ui.JavaPlugin;
45 import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor;
46 import org.eclipse.jdt.internal.ui.text.SmartBackspaceManager;
47 import org.eclipse.jdt.internal.ui.text.SmartBackspaceManager.UndoSpec;
48
49 /**
50  * Modifies <code>DocumentCommand</code>s inserting semicolons and opening braces to place them
51  * smartly, i.e. moving them to the end of a line if that is what the user expects.
52  *
53  * <p>In practice,  semicolons and braces (and the caret) are moved to the end of the line if they are typed
54  * anywhere except for semicolons in a <code>for</code> statements definition. If the line contains a semicolon
55  * or brace after the current caret position, the cursor is moved after it.</p>
56  *
57  * @see org.eclipse.jface.text.DocumentCommand
58  * @since 3.0
59  */
60 public class SmartSemicolonAutoEditStrategy implements IAutoEditStrategy {
61
62         /** String representation of a semicolon. */
63         private static final String SEMICOLON= ";"; //$NON-NLS-1$
64         /** Char representation of a semicolon. */
65         private static final char SEMICHAR= ';';
66         /** String represenattion of a opening brace. */
67         private static final String BRACE= "{"; //$NON-NLS-1$
68         /** Char representation of a opening brace */
69         private static final char BRACECHAR= '{';
70
71         private char fCharacter;
72         private String fPartitioning;
73
74         /**
75          * Creates a new SmartSemicolonAutoEditStrategy.
76          *
77          * @param partitioning the document partitioning
78          */
79         public SmartSemicolonAutoEditStrategy(String partitioning) {
80                 fPartitioning= partitioning;
81         }
82
83         /*
84          * @see org.eclipse.jface.text.IAutoEditStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand)
85          */
86         public void customizeDocumentCommand(IDocument document, DocumentCommand command) {
87                 // 0: early pruning
88                 // also customize if <code>doit</code> is false (so it works in code completion situations)
89                 //              if (!command.doit)
90                 //                      return;
91
92                 if (command.text == null)
93                         return;
94
95                 if (command.text.equals(SEMICOLON))
96                         fCharacter= SEMICHAR;
97                 else if (command.text.equals(BRACE))
98                         fCharacter= BRACECHAR;
99                 else
100                         return;
101
102                 IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore();
103                 if (fCharacter == SEMICHAR && !store.getBoolean(PreferenceConstants.EDITOR_SMART_SEMICOLON))
104                         return;
105                 if (fCharacter == BRACECHAR && !store.getBoolean(PreferenceConstants.EDITOR_SMART_OPENING_BRACE))
106                         return;
107
108                 IWorkbenchPage page= JavaPlugin.getActivePage();
109                 if (page == null)
110                         return;
111                 IEditorPart part= page.getActiveEditor();
112                 if (!(part instanceof CompilationUnitEditor))
113                         return;
114                 CompilationUnitEditor editor= (CompilationUnitEditor)part;
115                 if (editor.getInsertMode() != ITextEditorExtension3.SMART_INSERT || !editor.isEditable())
116                         return;
117                 ITextEditorExtension2 extension= (ITextEditorExtension2)editor.getAdapter(ITextEditorExtension2.class);
118                 if (extension != null && !extension.validateEditorInputState())
119                         return;
120                 if (isMultilineSelection(document, command))
121                         return;
122
123                 // 1: find concerned line / position in java code, location in statement
124                 int pos= command.offset;
125                 ITextSelection line;
126                 try {
127                         IRegion l= document.getLineInformationOfOffset(pos);
128                         line= new TextSelection(document, l.getOffset(), l.getLength());
129                 } catch (BadLocationException e) {
130                         return;
131                 }
132
133                 // 2: choose action based on findings (is for-Statement?)
134                 // for now: compute the best position to insert the new character
135                 int positionInLine= computeCharacterPosition(document, line, pos - line.getOffset(), fCharacter, fPartitioning);
136                 int position= positionInLine + line.getOffset();
137
138                 // never position before the current position!
139                 if (position < pos)
140                         return;
141
142                 // never double already existing content
143                 if (alreadyPresent(document, fCharacter, position))
144                         return;
145
146                 // don't do special processing if what we do is actually the normal behaviour
147                 String insertion= adjustSpacing(document, position, fCharacter);
148                 if (command.offset == position && insertion.equals(command.text))
149                         return;
150
151                 try {
152
153                         final SmartBackspaceManager manager= (SmartBackspaceManager) editor.getAdapter(SmartBackspaceManager.class);
154                         if (manager != null && JavaPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SMART_BACKSPACE)) {
155                                 TextEdit e1= new ReplaceEdit(command.offset, command.text.length(), document.get(command.offset, command.length));
156                                 UndoSpec s1= new UndoSpec(command.offset + command.text.length(),
157                                                 new Region(command.offset, 0),
158                                                 new TextEdit[] {e1},
159                                                 0,
160                                                 null);
161
162                                 DeleteEdit smart= new DeleteEdit(position, insertion.length());
163                                 ReplaceEdit raw= new ReplaceEdit(command.offset, command.length, command.text);
164                                 UndoSpec s2= new UndoSpec(position + insertion.length(),
165                                                 new Region(command.offset + command.text.length(), 0),
166                                                 new TextEdit[] {smart, raw},
167                                                 2,
168                                                 s1);
169                                 manager.register(s2);
170                         }
171
172                         // 3: modify command
173                         command.offset= position;
174                         command.length= 0;
175                         command.caretOffset= position;
176                         command.text= insertion;
177                         command.doit= true;
178                         command.owner= null;
179                 } catch (MalformedTreeException e) {
180                         JavaPlugin.log(e);
181                 } catch (BadLocationException e) {
182                         JavaPlugin.log(e);
183                 }
184
185
186         }
187
188         /**
189          * Returns <code>true</code> if the document command is applied on a multi
190          * line selection, <code>false</code> otherwise.
191          *
192          * @param document the document
193          * @param command the command
194          * @return <code>true</code> if <code>command</code> is a multiline command
195          */
196         private boolean isMultilineSelection(IDocument document, DocumentCommand command) {
197                 try {
198                         return document.getNumberOfLines(command.offset, command.length) > 1;
199                 } catch (BadLocationException e) {
200                         // ignore
201                         return false;
202                 }
203         }
204
205         /**
206          * Adds a space before a brace if it is inserted after a parenthesis, equal sign, or one
207          * of the keywords <code>try, else, do</code>.
208          *
209          * @param doc the document we are working on
210          * @param position the insert position of <code>character</code>
211          * @param character the character to be inserted
212          * @return a <code>String</code> consisting of <code>character</code> plus any additional spacing
213          */
214         private String adjustSpacing(IDocument doc, int position, char character) {
215                 if (character == BRACECHAR) {
216                         if (position > 0 && position <= doc.getLength()) {
217                                 int pos= position - 1;
218                                 if (looksLike(doc, pos, ")") //$NON-NLS-1$
219                                 || looksLike(doc, pos, "=") //$NON-NLS-1$
220                                 || looksLike(doc, pos, "]") //$NON-NLS-1$
221                                 || looksLike(doc, pos, "try") //$NON-NLS-1$
222                                 || looksLike(doc, pos, "else") //$NON-NLS-1$
223                                 || looksLike(doc, pos, "synchronized") //$NON-NLS-1$
224                                 || looksLike(doc, pos, "static") //$NON-NLS-1$
225                                 || looksLike(doc, pos, "finally") //$NON-NLS-1$
226                                 || looksLike(doc, pos, "do")) //$NON-NLS-1$
227                                         return new String(new char[] { ' ', character });
228                         }
229                 }
230
231                 return new String(new char[] { character });
232         }
233
234         /**
235          * Checks whether a character to be inserted is already present at the insert location (perhaps
236          * separated by some whitespace from <code>position</code>.
237          *
238          * @param document the document we are working on
239          * @param position the insert position of <code>ch</code>
240          * @param ch the character to be inserted
241          * @return <code>true</code> if <code>ch</code> is already present at <code>location</code>, <code>false</code> otherwise
242          */
243         private boolean alreadyPresent(IDocument document, char ch, int position) {
244                 int pos= firstNonWhitespaceForward(document, position, fPartitioning, document.getLength());
245                 try {
246                         if (pos != -1 && document.getChar(pos) == ch)
247                                 return true;
248                 } catch (BadLocationException e) {
249                 }
250
251                 return false;
252         }
253
254         /**
255          * Computes the next insert position of the given character in the current line.
256          *
257          * @param document the document we are working on
258          * @param line the line where the change is being made
259          * @param offset the position of the caret in the line when <code>character</code> was typed
260          * @param character the character to look for
261          * @param partitioning the document partitioning
262          * @return the position where <code>character</code> should be inserted / replaced
263          */
264         protected static int computeCharacterPosition(IDocument document, ITextSelection line, int offset, char character, String partitioning) {
265                 String text= line.getText();
266                 if (text == null)
267                         return 0;
268
269                 int insertPos;
270                 if (character == BRACECHAR) {
271
272                         insertPos= computeArrayInitializationPos(document, line, offset, partitioning);
273
274                         if (insertPos == -1) {
275                                 insertPos= computeAfterTryDoElse(document, line, offset);
276                         }
277
278                         if (insertPos == -1) {
279                                 insertPos= computeAfterParenthesis(document, line, offset, partitioning);
280                         }
281
282                 } else if (character == SEMICHAR) {
283
284                         if (isForStatement(text, offset)) {
285                                 insertPos= -1; // don't do anything in for statements, as semis are vital part of these
286                         } else {
287                                 int nextPartitionPos= nextPartitionOrLineEnd(document, line, offset, partitioning);
288                                 insertPos= startOfWhitespaceBeforeOffset(text, nextPartitionPos);
289                                 // if there is a semi present, return its location as alreadyPresent() will take it out this way.
290                                 if (insertPos > 0 && text.charAt(insertPos - 1) == character)
291                                         insertPos= insertPos - 1;
292                                 else if (insertPos > 0 && text.charAt(insertPos - 1) == '}') {
293                                         int opening= scanBackward(document, insertPos - 1 + line.getOffset(), partitioning, -1, new char[] { '{' });
294                                         if (opening > -1 && opening < offset + line.getOffset()) {
295                                                 if (computeArrayInitializationPos(document, line, opening - line.getOffset(), partitioning) == -1) {
296                                                         insertPos= offset;
297                                                 }
298                                         }
299                                 }
300                         }
301
302                 } else {
303                         Assert.isTrue(false);
304                         return -1;
305                 }
306
307                 return insertPos;
308         }
309
310         /**
311          * Computes an insert position for an opening brace if <code>offset</code> maps to a position in
312          * <code>document</code> that looks like being the RHS of an assignment or like an array definition.
313          *
314          * @param document the document being modified
315          * @param line the current line under investigation
316          * @param offset the offset of the caret position, relative to the line start.
317          * @param partitioning the document partitioning
318          * @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
319          */
320         private static int computeArrayInitializationPos(IDocument document, ITextSelection line, int offset, String partitioning) {
321                 // search backward while WS, find = (not != <= >= ==) in default partition
322                 int pos= offset + line.getOffset();
323
324                 if (pos == 0)
325                         return -1;
326
327                 int p= firstNonWhitespaceBackward(document, pos - 1, partitioning, -1);
328
329                 if (p == -1)
330                         return -1;
331
332                 try {
333
334                         char ch= document.getChar(p);
335                         if (ch != '=' && ch != ']')
336                                 return -1;
337
338                         if (p == 0)
339                                 return offset;
340
341                         p= firstNonWhitespaceBackward(document, p - 1, partitioning, -1);
342                         if (p == -1)
343                                 return -1;
344
345                         ch= document.getChar(p);
346                         if (Character.isJavaIdentifierPart(ch) || ch == ']' || ch == '[')
347                                 return offset;
348
349                 } catch (BadLocationException e) {
350                 }
351                 return -1;
352         }
353
354         /**
355          * Computes an insert position for an opening brace if <code>offset</code> maps to a position in
356          * <code>doc</code> involving a keyword taking a block after it. These are: <code>try</code>,
357          * <code>do</code>, <code>synchronized</code>, <code>static</code>, <code>finally</code>, or <code>else</code>.
358          *
359          * @param doc the document being modified
360          * @param line the current line under investigation
361          * @param offset the offset of the caret position, relative to the line start.
362          * @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
363          */
364         private static int computeAfterTryDoElse(IDocument doc, ITextSelection line, int offset) {
365                 // search backward while WS, find 'try', 'do', 'else' in default partition
366                 int p= offset + line.getOffset();
367                 p= firstWhitespaceToRight(doc, p);
368                 if (p == -1)
369                         return -1;
370                 p--;
371
372                 if (looksLike(doc, p, "try") //$NON-NLS-1$
373                                 || looksLike(doc, p, "do")  //$NON-NLS-1$
374                                 || looksLike(doc, p, "synchronized")  //$NON-NLS-1$
375                                 || looksLike(doc, p, "static")  //$NON-NLS-1$
376                                 || looksLike(doc, p, "finally")  //$NON-NLS-1$
377                                 || looksLike(doc, p, "else"))  //$NON-NLS-1$
378                         return p + 1 - line.getOffset();
379
380                 return -1;
381         }
382
383         /**
384          * Computes an insert position for an opening brace if <code>offset</code> maps to a position in
385          * <code>document</code> with a expression in parenthesis that will take a block after the closing parenthesis.
386          *
387          * @param document the document being modified
388          * @param line the current line under investigation
389          * @param offset the offset of the caret position, relative to the line start.
390          * @param partitioning the document partitioning
391          * @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
392          */
393         private static int computeAfterParenthesis(IDocument document, ITextSelection line, int offset, String partitioning) {
394                 // find the opening parenthesis for every closing parenthesis on the current line after offset
395                 // return the position behind the closing parenthesis if it looks like a method declaration
396                 // or an expression for an if, while, for, catch statement
397                 int pos= offset + line.getOffset();
398                 int length= line.getOffset() + line.getLength();
399                 int scanTo= scanForward(document, pos, partitioning, length, '}');
400                 if (scanTo == -1)
401                         scanTo= length;
402
403                 int closingParen= findClosingParenToLeft(document, pos, partitioning) - 1;
404
405                 while (true) {
406                         int startScan= closingParen + 1;
407                         closingParen= scanForward(document, startScan, partitioning, scanTo, ')');
408                         if (closingParen == -1)
409                                 break;
410
411                         int openingParen= findOpeningParenMatch(document, closingParen, partitioning);
412
413                         // no way an expression at the beginning of the document can mean anything
414                         if (openingParen < 1)
415                                 break;
416
417                         // only select insert positions for parenthesis currently embracing the caret
418                         if (openingParen > pos)
419                                 continue;
420
421                         if (looksLikeAnonymousClassDef(document, openingParen - 1, partitioning))
422                                 return closingParen + 1 - line.getOffset();
423
424                         if (looksLikeIfWhileForCatch(document, openingParen - 1, partitioning))
425                                 return closingParen + 1 - line.getOffset();
426
427                         if (looksLikeMethodDecl(document, openingParen - 1, partitioning))
428                                 return closingParen + 1 - line.getOffset();
429
430                 }
431
432                 return -1;
433         }
434
435         /**
436          * Finds a closing parenthesis to the left of <code>position</code> in document, where that parenthesis is only
437          * separated by whitespace from <code>position</code>. If no such parenthesis can be found, <code>position</code> is returned.
438          *
439          * @param document the document being modified
440          * @param position the first character position in <code>document</code> to be considered
441          * @param partitioning the document partitioning
442          * @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
443          */
444         private static int findClosingParenToLeft(IDocument document, int position, String partitioning) {
445                 final char CLOSING_PAREN= ')';
446                 try {
447                         if (position < 1)
448                                 return position;
449
450                         int nonWS= firstNonWhitespaceBackward(document, position - 1, partitioning, -1);
451                         if (nonWS != -1 && document.getChar(nonWS) == CLOSING_PAREN)
452                                 return nonWS;
453                 } catch (BadLocationException e1) {
454                 }
455                 return position;
456         }
457
458         /**
459          * Finds the first whitespace character position to the right of (and including) <code>position</code>.
460          *
461          * @param document the document being modified
462          * @param position the first character position in <code>document</code> to be considered
463          * @return the position of a whitespace character greater or equal than <code>position</code> separated only by whitespace, or -1 if none found
464          */
465         private static int firstWhitespaceToRight(IDocument document, int position) {
466                 int length= document.getLength();
467                 Assert.isTrue(position >= 0);
468                 Assert.isTrue(position <= length);
469
470                 try {
471                         while (position < length) {
472                                 char ch= document.getChar(position);
473                                 if (Character.isWhitespace(ch))
474                                         return position;
475                                 position++;
476                         }
477                         return position;
478                 } catch (BadLocationException e) {
479                 }
480                 return -1;
481         }
482
483         /**
484          * Finds the highest position in <code>document</code> such that the position is &lt;= <code>position</code>
485          * and &gt; <code>bound</code> and <code>Character.isWhitespace(document.getChar(pos))</code> evaluates to <code>false</code>
486          * and the position is in the default partition.
487          *
488          * @param document the document being modified
489          * @param position the first character position in <code>document</code> to be considered
490          * @param partitioning the document partitioning
491          * @param bound the first position in <code>document</code> to not consider any more, with <code>bound</code> &lt; <code>position</code>
492          * @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
493          */
494         private static int firstNonWhitespaceBackward(IDocument document, int position, String partitioning, int bound) {
495                 Assert.isTrue(position < document.getLength());
496                 Assert.isTrue(bound >= -1);
497
498                 try {
499                         while (position > bound) {
500                                 char ch= document.getChar(position);
501                                 if (!Character.isWhitespace(ch) && isDefaultPartition(document, position, partitioning))
502                                         return position;
503                                 position--;
504                         }
505                 } catch (BadLocationException e) {
506                 }
507                 return -1;
508         }
509
510         /**
511          * Finds the smallest position in <code>document</code> such that the position is &gt;= <code>position</code>
512          * and &lt; <code>bound</code> and <code>Character.isWhitespace(document.getChar(pos))</code> evaluates to <code>false</code>
513          * and the position is in the default partition.
514          *
515          * @param document the document being modified
516          * @param position the first character position in <code>document</code> to be considered
517          * @param partitioning the document partitioning
518          * @param bound the first position in <code>document</code> to not consider any more, with <code>bound</code> &gt; <code>position</code>
519          * @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
520          */
521         private static int firstNonWhitespaceForward(IDocument document, int position, String partitioning, int bound) {
522                 Assert.isTrue(position >= 0);
523                 Assert.isTrue(bound <= document.getLength());
524
525                 try {
526                         while (position < bound) {
527                                 char ch= document.getChar(position);
528                                 if (!Character.isWhitespace(ch) && isDefaultPartition(document, position, partitioning))
529                                         return position;
530                                 position++;
531                         }
532                 } catch (BadLocationException e) {
533                 }
534                 return -1;
535         }
536
537         /**
538          * Finds the highest position in <code>document</code> such that the position is &lt;= <code>position</code>
539          * and &gt; <code>bound</code> and <code>document.getChar(position) == ch</code> evaluates to <code>true</code> for at least one
540          * ch in <code>chars</code> and the position is in the default partition.
541          *
542          * @param document the document being modified
543          * @param position the first character position in <code>document</code> to be considered
544          * @param partitioning the document partitioning
545          * @param bound the first position in <code>document</code> to not consider any more, with <code>scanTo</code> &gt; <code>position</code>
546          * @param chars an array of <code>char</code> to search for
547          * @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
548          */
549         private static int scanBackward(IDocument document, int position, String partitioning, int bound, char[] chars) {
550                 Assert.isTrue(bound >= -1);
551                 Assert.isTrue(position < document.getLength() );
552
553                 Arrays.sort(chars);
554
555                 try {
556                         while (position > bound) {
557
558                                 if (Arrays.binarySearch(chars, document.getChar(position)) >= 0 && isDefaultPartition(document, position, partitioning))
559                                         return position;
560
561                                 position--;
562                         }
563                 } catch (BadLocationException e) {
564                 }
565                 return -1;
566         }
567
568 //      /**
569 //       * Finds the highest position in <code>document</code> such that the position is &lt;= <code>position</code>
570 //       * and &gt; <code>bound</code> and <code>document.getChar(position) == ch</code> evaluates to <code>true</code>
571 //       * and the position is in the default partition.
572 //       *
573 //       * @param document the document being modified
574 //       * @param position the first character position in <code>document</code> to be considered
575 //       * @param bound the first position in <code>document</code> to not consider any more, with <code>scanTo</code> &gt; <code>position</code>
576 //       * @param chars an array of <code>char</code> to search for
577 //       * @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
578 //       */
579 //      private static int scanBackward(IDocument document, int position, int bound, char ch) {
580 //              return scanBackward(document, position, bound, new char[] {ch});
581 //      }
582 //
583         /**
584          * Finds the lowest position in <code>document</code> such that the position is &gt;= <code>position</code>
585          * and &lt; <code>bound</code> and <code>document.getChar(position) == ch</code> evaluates to <code>true</code> for at least one
586          * ch in <code>chars</code> and the position is in the default partition.
587          *
588          * @param document the document being modified
589          * @param position the first character position in <code>document</code> to be considered
590          * @param partitioning the document partitioning
591          * @param bound the first position in <code>document</code> to not consider any more, with <code>scanTo</code> &gt; <code>position</code>
592          * @param chars an array of <code>char</code> to search for
593          * @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
594          */
595         private static int scanForward(IDocument document, int position, String partitioning, int bound, char[] chars) {
596                 Assert.isTrue(position >= 0);
597                 Assert.isTrue(bound <= document.getLength());
598
599                 Arrays.sort(chars);
600
601                 try {
602                         while (position < bound) {
603
604                                 if (Arrays.binarySearch(chars, document.getChar(position)) >= 0 && isDefaultPartition(document, position, partitioning))
605                                         return position;
606
607                                 position++;
608                         }
609                 } catch (BadLocationException e) {
610                 }
611                 return -1;
612         }
613
614         /**
615          * Finds the lowest position in <code>document</code> such that the position is &gt;= <code>position</code>
616          * and &lt; <code>bound</code> and <code>document.getChar(position) == ch</code> evaluates to <code>true</code>
617          * and the position is in the default partition.
618          *
619          * @param document the document being modified
620          * @param position the first character position in <code>document</code> to be considered
621          * @param partitioning the document partitioning
622          * @param bound the first position in <code>document</code> to not consider any more, with <code>scanTo</code> &gt; <code>position</code>
623          * @param ch a <code>char</code> to search for
624          * @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
625          */
626         private static int scanForward(IDocument document, int position, String partitioning, int bound, char ch) {
627                 return scanForward(document, position, partitioning, bound, new char[] {ch});
628         }
629
630         /**
631          * Checks whether the content of <code>document</code> in the range (<code>offset</code>, <code>length</code>)
632          * contains the <code>new</code> keyword.
633          *
634          * @param document the document being modified
635          * @param offset the first character position in <code>document</code> to be considered
636          * @param length the length of the character range to be considered
637          * @param partitioning the document partitioning
638          * @return <code>true</code> if the specified character range contains a <code>new</code> keyword, <code>false</code> otherwise.
639          */
640         private static boolean isNewMatch(IDocument document, int offset, int length, String partitioning) {
641                 Assert.isTrue(length >= 0);
642                 Assert.isTrue(offset >= 0);
643                 Assert.isTrue(offset + length < document.getLength() + 1);
644
645                 try {
646                         String text= document.get(offset, length);
647                         int pos= text.indexOf("new"); //$NON-NLS-1$
648
649                         while (pos != -1 && !isDefaultPartition(document, pos + offset, partitioning))
650                                 pos= text.indexOf("new", pos + 2); //$NON-NLS-1$
651
652                         if (pos < 0)
653                                 return false;
654
655                         if (pos != 0 && Character.isJavaIdentifierPart(text.charAt(pos - 1)))
656                                 return false;
657
658                         if (pos + 3 < length && Character.isJavaIdentifierPart(text.charAt(pos + 3)))
659                                 return false;
660
661                         return true;
662
663                 } catch (BadLocationException e) {
664                 }
665                 return false;
666         }
667
668         /**
669          * Checks whether the content of <code>document</code> at <code>position</code> looks like an
670          * anonymous class definition. <code>position</code> must be to the left of the opening
671          * parenthesis of the definition's parameter list.
672          *
673          * @param document the document being modified
674          * @param position the first character position in <code>document</code> to be considered
675          * @param partitioning the document partitioning
676          * @return <code>true</code> if the content of <code>document</code> looks like an anonymous class definition, <code>false</code> otherwise
677          */
678         private static boolean looksLikeAnonymousClassDef(IDocument document, int position, String partitioning) {
679                 int previousCommaParenEqual= scanBackward(document, position - 1, partitioning, -1, new char[] {',', '(', '='});
680                 if (previousCommaParenEqual == -1 || position < previousCommaParenEqual + 5) // 2 for borders, 3 for "new"
681                         return false;
682
683                 if (isNewMatch(document, previousCommaParenEqual + 1, position - previousCommaParenEqual - 2, partitioning))
684                         return true;
685
686                 return false;
687         }
688
689         /**
690          * Checks whether <code>position</code> resides in a default (Java) partition of <code>document</code>.
691          *
692          * @param document the document being modified
693          * @param position the position to be checked
694          * @param partitioning the document partitioning
695          * @return <code>true</code> if <code>position</code> is in the default partition of <code>document</code>, <code>false</code> otherwise
696          */
697         private static boolean isDefaultPartition(IDocument document, int position, String partitioning) {
698                 Assert.isTrue(position >= 0);
699                 Assert.isTrue(position <= document.getLength());
700
701                 try {
702                         // don't use getPartition2 since we're interested in the scanned character's partition
703                         ITypedRegion region= TextUtilities.getPartition(document, partitioning, position, false);
704                         return region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE);
705
706                 } catch (BadLocationException e) {
707                 }
708
709                 return false;
710         }
711
712         /**
713          * Finds the position of the parenthesis matching the closing parenthesis at <code>position</code>.
714          *
715          * @param document the document being modified
716          * @param position the position in <code>document</code> of a closing parenthesis
717          * @param partitioning the document partitioning
718          * @return the position in <code>document</code> of the matching parenthesis, or -1 if none can be found
719          */
720         private static int findOpeningParenMatch(IDocument document, int position, String partitioning) {
721                 final char CLOSING_PAREN= ')';
722                 final char OPENING_PAREN= '(';
723
724                 Assert.isTrue(position < document.getLength());
725                 Assert.isTrue(position >= 0);
726                 Assert.isTrue(isDefaultPartition(document, position, partitioning));
727
728                 try {
729
730                         Assert.isTrue(document.getChar(position) == CLOSING_PAREN);
731
732                         int depth= 1;
733                         while (true) {
734                                 position= scanBackward(document, position - 1, partitioning, -1, new char[] {CLOSING_PAREN, OPENING_PAREN});
735                                 if (position == -1)
736                                         return -1;
737
738                                 if (document.getChar(position) == CLOSING_PAREN)
739                                         depth++;
740                                 else
741                                         depth--;
742
743                                 if (depth == 0)
744                                         return position;
745                         }
746
747                 } catch (BadLocationException e) {
748                         return -1;
749                 }
750         }
751
752         /**
753          * Checks whether, to the left of <code>position</code> and separated only by whitespace,
754          * <code>document</code> contains a keyword taking a parameter list and a block after it.
755          * These are: <code>if</code>, <code>while</code>, <code>catch</code>, <code>for</code>, <code>synchronized</code>, <code>switch</code>.
756          *
757          * @param document the document being modified
758          * @param position the first character position in <code>document</code> to be considered
759          * @param partitioning the document partitioning
760          * @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
761          */
762         private static boolean looksLikeIfWhileForCatch(IDocument document, int position, String partitioning) {
763                 position= firstNonWhitespaceBackward(document, position, partitioning, -1);
764                 if (position == -1)
765                         return false;
766
767                 return looksLike(document, position, "if") //$NON-NLS-1$
768                                 || looksLike(document, position, "while") //$NON-NLS-1$
769                                 || looksLike(document, position, "catch") //$NON-NLS-1$
770                                 || looksLike(document, position, "synchronized") //$NON-NLS-1$
771                                 || looksLike(document, position, "switch") //$NON-NLS-1$
772                                 || looksLike(document, position, "for"); //$NON-NLS-1$
773         }
774
775         /**
776          * Checks whether code>document</code> contains the <code>String</code> <code>like</code> such
777          * that its last character is at <code>position</code>. If <code>like</code> starts with a
778          * identifier part (as determined by {@link Character#isJavaIdentifierPart(char)}), it is also made
779          * sure that <code>like</code> is preceded by some non-identifier character or stands at the
780          * document start.
781          *
782          * @param document the document being modified
783          * @param position the first character position in <code>document</code> to be considered
784          * @param like the <code>String</code> to look for.
785          * @return <code>true</code> if  <code>document</code> contains <code>like</code> such that it ends at <code>position</code>, <code>false</code> otherwise
786          */
787         private static boolean looksLike(IDocument document, int position, String like) {
788                 int length= like.length();
789                 if (position < length - 1)
790                         return false;
791
792                 try {
793                         if (!like.equals(document.get(position - length + 1, length)))
794                                 return false;
795
796                         if (position >= length && Character.isJavaIdentifierPart(like.charAt(0)) && Character.isJavaIdentifierPart(document.getChar(position - length)))
797                                 return false;
798
799                 } catch (BadLocationException e) {
800                         return false;
801                 }
802
803                 return true;
804         }
805
806         /**
807          * Checks whether the content of <code>document</code> at <code>position</code> looks like a
808          * method declaration header (i.e. only the return type and method name). <code>position</code>
809          * must be just left of the opening parenthesis of the parameter list.
810          *
811          * @param document the document being modified
812          * @param position the first character position in <code>document</code> to be considered
813          * @param partitioning the document partitioning
814          * @return <code>true</code> if the content of <code>document</code> looks like a method definition, <code>false</code> otherwise
815          */
816         private static boolean looksLikeMethodDecl(IDocument document, int position, String partitioning) {
817
818                 // method name
819                 position= eatIdentToLeft(document, position, partitioning);
820                 if (position < 1)
821                         return false;
822
823                 position= eatBrackets(document, position - 1, partitioning);
824                 if (position < 1)
825                         return false;
826
827                 position= eatIdentToLeft(document, position - 1, partitioning);
828
829                 return position != -1;
830         }
831
832         /**
833          * From <code>position</code> to the left, eats any whitespace and then a pair of brackets
834          * as used to declare an array return type like <pre>String [ ]</pre>.
835          * The return value is either the position of the opening bracket or <code>position</code> if no
836          * pair of brackets can be parsed.
837          *
838          * @param document the document being modified
839          * @param position the first character position in <code>document</code> to be considered
840          * @param partitioning the document partitioning
841          * @return the smallest character position of bracket pair or <code>position</code>
842          */
843         private static int eatBrackets(IDocument document, int position, String partitioning) {
844                 // accept array return type
845                 int pos= firstNonWhitespaceBackward(document, position, partitioning, -1);
846                 try {
847                         if (pos > 1 && document.getChar(pos) == ']') {
848                                 pos= firstNonWhitespaceBackward(document, pos - 1, partitioning, -1);
849                                 if (pos > 0 && document.getChar(pos) == '[')
850                                         return pos;
851                         }
852                 } catch (BadLocationException e) {
853                         // won't happen
854                 }
855                 return position;
856         }
857
858         /**
859          * From <code>position</code> to the left, eats any whitespace and the first identifier, returning
860          * the position of the first identifier character (in normal read order).
861          * <p>When called on a document with content <code>" some string  "</code> and positionition 13, the
862          * return value will be 6 (the first letter in <code>string</code>).
863          * </p>
864          *
865          * @param document the document being modified
866          * @param position the first character position in <code>document</code> to be considered
867          * @param partitioning the document partitioning
868          * @return the smallest character position of an identifier or -1 if none can be found; always &lt;= <code>position</code>
869          */
870         private static int eatIdentToLeft(IDocument document, int position, String partitioning) {
871                 if (position < 0)
872                         return -1;
873                 Assert.isTrue(position < document.getLength());
874
875                 int p= firstNonWhitespaceBackward(document, position, partitioning, -1);
876                 if (p == -1)
877                         return -1;
878
879                 try {
880                         while (p >= 0) {
881
882                                 char ch= document.getChar(p);
883                                 if (Character.isJavaIdentifierPart(ch)) {
884                                         p--;
885                                         continue;
886                                 }
887
888                                 // length must be > 0
889                                 if (Character.isWhitespace(ch) && p != position)
890                                         return p + 1;
891                                 else
892                                         return -1;
893
894                         }
895
896                         // start of document reached
897                         return 0;
898
899                 } catch (BadLocationException e) {
900                 }
901                 return -1;
902         }
903
904         /**
905          * Returns a position in the first java partition after the last non-empty and non-comment partition.
906          * There is no non-whitespace from the returned position to the end of the partition it is contained in.
907          *
908          * @param document the document being modified
909          * @param line the line under investigation
910          * @param offset the caret offset into <code>line</code>
911          * @param partitioning the document partitioning
912          * @return the position of the next Java partition, or the end of <code>line</code>
913          */
914         private static int nextPartitionOrLineEnd(IDocument document, ITextSelection line, int offset, String partitioning) {
915                 // run relative to document
916                 final int docOffset= offset + line.getOffset();
917                 final int eol= line.getOffset() + line.getLength();
918                 int nextPartitionPos= eol; // init with line end
919                 int validPosition= docOffset;
920
921                 try {
922                         ITypedRegion partition= TextUtilities.getPartition(document, partitioning, nextPartitionPos, true);
923                         validPosition= getValidPositionForPartition(document, partition, eol);
924                         while (validPosition == -1) {
925                                 nextPartitionPos= partition.getOffset() - 1;
926                                 if (nextPartitionPos < docOffset) {
927                                         validPosition= docOffset;
928                                         break;
929                                 }
930                                 partition= TextUtilities.getPartition(document, partitioning, nextPartitionPos, false);
931                                 validPosition= getValidPositionForPartition(document, partition, eol);
932                         }
933                 } catch (BadLocationException e) {
934                 }
935
936                 validPosition= Math.max(validPosition, docOffset);
937                 // make relative to line
938                 validPosition -= line.getOffset();
939                 return validPosition;
940         }
941
942         /**
943          * Returns a valid insert location (except for whitespace) in <code>partition</code> or -1 if
944          * there is no valid insert location.
945          * An valid insert location is right after any java string or character partition, or at the end
946          * of a java default partition, but never behind <code>maxOffset</code>. Comment partitions or
947          * empty java partitions do never yield valid insert positions.
948          *
949          * @param doc the document being modified
950          * @param partition the current partition
951          * @param maxOffset the maximum offset to consider
952          * @return a valid insert location in <code>partition</code>, or -1 if there is no valid insert location
953          */
954         private static int getValidPositionForPartition(IDocument doc, ITypedRegion partition, int maxOffset) {
955                 final int INVALID= -1;
956
957                 if (IJavaPartitions.JAVA_DOC.equals(partition.getType()))
958                         return INVALID;
959                 if (IJavaPartitions.JAVA_MULTI_LINE_COMMENT.equals(partition.getType()))
960                         return INVALID;
961                 if (IJavaPartitions.JAVA_SINGLE_LINE_COMMENT.equals(partition.getType()))
962                         return INVALID;
963
964                 int endOffset= Math.min(maxOffset, partition.getOffset() + partition.getLength());
965
966                 if (IJavaPartitions.JAVA_CHARACTER.equals(partition.getType()))
967                         return endOffset;
968                 if (IJavaPartitions.JAVA_STRING.equals(partition.getType()))
969                         return endOffset;
970                 if (IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())) {
971                         try {
972                                 if (doc.get(partition.getOffset(), endOffset - partition.getOffset()).trim().length() == 0)
973                                         return INVALID;
974                                 else
975                                         return endOffset;
976                         } catch (BadLocationException e) {
977                                 return INVALID;
978                         }
979                 }
980                 // default: we don't know anything about the partition - assume valid
981                 return endOffset;
982         }
983
984         /**
985          * Determines whether the current line contains a for statement.
986          * Algorithm: any "for" word in the line is a positive, "for" contained in a string literal will
987          * produce a false positive.
988          *
989          * @param line the line where the change is being made
990          * @param offset the position of the caret
991          * @return <code>true</code> if <code>line</code> contains <code>for</code>, <code>false</code> otherwise
992          */
993         private static boolean isForStatement(String line, int offset) {
994                 /* searching for (^|\s)for(\s|$) */
995                 int forPos= line.indexOf("for"); //$NON-NLS-1$
996                 if (forPos != -1) {
997                         if ((forPos == 0 || !Character.isJavaIdentifierPart(line.charAt(forPos - 1))) && (line.length() == forPos + 3 || !Character.isJavaIdentifierPart(line.charAt(forPos + 3))))
998                                 return true;
999                 }
1000                 return false;
1001         }
1002
1003         /**
1004          * Returns the position in <code>text</code> after which there comes only whitespace, up to
1005          * <code>offset</code>.
1006          *
1007          * @param text the text being searched
1008          * @param offset the maximum offset to search for
1009          * @return the smallest value <code>v</code> such that <code>text.substring(v, offset).trim() == 0</code>
1010          */
1011         private static int startOfWhitespaceBeforeOffset(String text, int offset) {
1012                 int i= Math.min(offset, text.length());
1013                 for (; i >= 1; i--) {
1014                         if (!Character.isWhitespace(text.charAt(i - 1)))
1015                                 break;
1016                 }
1017                 return i;
1018         }
1019 }