]> git.uio.no Git - ifi-stolz-refaktor.git/blob - case-study/jdt-before/ui/org/eclipse/jdt/internal/ui/text/java/JavaAutoIndentStrategy.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-before / ui / org / eclipse / jdt / internal / ui / text / java / JavaAutoIndentStrategy.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2011 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *     Nikolay Metchev - Fixed https://bugs.eclipse.org/bugs/show_bug.cgi?id=29909
11  *     Tom Eicher (Avaloq Evolution AG) - block selection mode
12  *******************************************************************************/
13 package org.eclipse.jdt.internal.ui.text.java;
14
15 import org.eclipse.core.runtime.Assert;
16
17 import org.eclipse.jface.preference.IPreferenceStore;
18
19 import org.eclipse.jface.text.BadLocationException;
20 import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
21 import org.eclipse.jface.text.Document;
22 import org.eclipse.jface.text.DocumentCommand;
23 import org.eclipse.jface.text.DocumentRewriteSession;
24 import org.eclipse.jface.text.DocumentRewriteSessionType;
25 import org.eclipse.jface.text.IDocument;
26 import org.eclipse.jface.text.IRegion;
27 import org.eclipse.jface.text.ITypedRegion;
28 import org.eclipse.jface.text.Region;
29 import org.eclipse.jface.text.TextUtilities;
30 import org.eclipse.jface.text.rules.FastPartitioner;
31 import org.eclipse.jface.text.source.ISourceViewer;
32
33 import org.eclipse.ui.IEditorPart;
34 import org.eclipse.ui.IWorkbenchPage;
35
36 import org.eclipse.ui.texteditor.ITextEditorExtension3;
37
38 import org.eclipse.jdt.core.IJavaProject;
39 import org.eclipse.jdt.core.JavaCore;
40 import org.eclipse.jdt.core.ToolFactory;
41 import org.eclipse.jdt.core.compiler.IProblem;
42 import org.eclipse.jdt.core.compiler.IScanner;
43 import org.eclipse.jdt.core.compiler.ITerminalSymbols;
44 import org.eclipse.jdt.core.compiler.InvalidInputException;
45 import org.eclipse.jdt.core.dom.ASTNode;
46 import org.eclipse.jdt.core.dom.ASTParser;
47 import org.eclipse.jdt.core.dom.CompilationUnit;
48 import org.eclipse.jdt.core.dom.DoStatement;
49 import org.eclipse.jdt.core.dom.Expression;
50 import org.eclipse.jdt.core.dom.ForStatement;
51 import org.eclipse.jdt.core.dom.IfStatement;
52 import org.eclipse.jdt.core.dom.NodeFinder;
53 import org.eclipse.jdt.core.dom.Statement;
54 import org.eclipse.jdt.core.dom.WhileStatement;
55 import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
56
57 import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
58
59 import org.eclipse.jdt.ui.PreferenceConstants;
60 import org.eclipse.jdt.ui.text.IJavaPartitions;
61
62 import org.eclipse.jdt.internal.ui.JavaPlugin;
63 import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider;
64 import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
65 import org.eclipse.jdt.internal.ui.text.FastJavaPartitionScanner;
66 import org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner;
67 import org.eclipse.jdt.internal.ui.text.JavaIndenter;
68 import org.eclipse.jdt.internal.ui.text.Symbols;
69
70
71 /**
72  * Auto indent strategy sensitive to brackets.
73  */
74 public class JavaAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy {
75
76         /** The line comment introducer. Value is "{@value}" */
77         private static final String LINE_COMMENT= "//"; //$NON-NLS-1$
78
79                 private static class CompilationUnitInfo {
80
81                         char[] buffer;
82                         int delta;
83
84                         CompilationUnitInfo(char[] buffer, int delta) {
85                                 this.buffer= buffer;
86                                 this.delta= delta;
87                         }
88                 }
89
90
91         private boolean fCloseBrace;
92         private boolean fIsSmartMode;
93         private boolean fIsSmartTab;
94         private boolean fIsSmartIndentAfterNewline;
95
96         private String fPartitioning;
97         private final IJavaProject fProject;
98         private static IScanner fgScanner= ToolFactory.createScanner(false, false, false, false);
99         /**
100          * The viewer.
101          * @since 3.5
102          */
103         private final ISourceViewer fViewer;
104
105         /**
106          * Creates a new Java auto indent strategy for the given document partitioning.
107          *
108          * @param partitioning the document partitioning
109          * @param project the project to get formatting preferences from, or null to use default preferences
110          * @param viewer the source viewer that this strategy is attached to
111          */
112         public JavaAutoIndentStrategy(String partitioning, IJavaProject project, ISourceViewer viewer) {
113                 fPartitioning= partitioning;
114                 fProject= project;
115                 fViewer= viewer;
116         }
117
118         private int getBracketCount(IDocument d, int startOffset, int endOffset, boolean ignoreCloseBrackets) throws BadLocationException {
119
120                 int bracketCount= 0;
121                 while (startOffset < endOffset) {
122                         char curr= d.getChar(startOffset);
123                         startOffset++;
124                         switch (curr) {
125                                 case '/' :
126                                         if (startOffset < endOffset) {
127                                                 char next= d.getChar(startOffset);
128                                                 if (next == '*') {
129                                                         // a comment starts, advance to the comment end
130                                                         startOffset= getCommentEnd(d, startOffset + 1, endOffset);
131                                                 } else if (next == '/') {
132                                                         // '//'-comment: nothing to do anymore on this line
133                                                         startOffset= endOffset;
134                                                 }
135                                         }
136                                         break;
137                                 case '*' :
138                                         if (startOffset < endOffset) {
139                                                 char next= d.getChar(startOffset);
140                                                 if (next == '/') {
141                                                         // we have been in a comment: forget what we read before
142                                                         bracketCount= 0;
143                                                         startOffset++;
144                                                 }
145                                         }
146                                         break;
147                                 case '{' :
148                                         bracketCount++;
149                                         ignoreCloseBrackets= false;
150                                         break;
151                                 case '}' :
152                                         if (!ignoreCloseBrackets) {
153                                                 bracketCount--;
154                                         }
155                                         break;
156                                 case '"' :
157                                 case '\'' :
158                                         startOffset= getStringEnd(d, startOffset, endOffset, curr);
159                                         break;
160                                 default :
161                                         }
162                 }
163                 return bracketCount;
164         }
165
166         // ----------- bracket counting ------------------------------------------------------
167
168         private int getCommentEnd(IDocument d, int offset, int endOffset) throws BadLocationException {
169                 while (offset < endOffset) {
170                         char curr= d.getChar(offset);
171                         offset++;
172                         if (curr == '*') {
173                                 if (offset < endOffset && d.getChar(offset) == '/') {
174                                         return offset + 1;
175                                 }
176                         }
177                 }
178                 return endOffset;
179         }
180
181         private String getIndentOfLine(IDocument d, int line) throws BadLocationException {
182                 if (line > -1) {
183                         int start= d.getLineOffset(line);
184                         int end= start + d.getLineLength(line) - 1;
185                         int whiteEnd= findEndOfWhiteSpace(d, start, end);
186                         return d.get(start, whiteEnd - start);
187                 } else {
188                         return ""; //$NON-NLS-1$
189                 }
190         }
191
192         private int getStringEnd(IDocument d, int offset, int endOffset, char ch) throws BadLocationException {
193                 while (offset < endOffset) {
194                         char curr= d.getChar(offset);
195                         offset++;
196                         if (curr == '\\') {
197                                 // ignore escaped characters
198                                 offset++;
199                         } else if (curr == ch) {
200                                 return offset;
201                         }
202                 }
203                 return endOffset;
204         }
205
206         private void smartIndentAfterClosingBracket(IDocument d, DocumentCommand c) {
207                 if (c.offset == -1 || d.getLength() == 0)
208                         return;
209
210                 try {
211                         int p= (c.offset == d.getLength() ? c.offset - 1 : c.offset);
212                         int line= d.getLineOfOffset(p);
213                         int start= d.getLineOffset(line);
214                         int whiteend= findEndOfWhiteSpace(d, start, c.offset);
215
216                         JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
217                         JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
218
219                         // shift only when line does not contain any text up to the closing bracket
220                         if (whiteend == c.offset) {
221                                 // evaluate the line with the opening bracket that matches out closing bracket
222                                 int reference= indenter.findReferencePosition(c.offset, false, true, false, false);
223                                 int indLine= d.getLineOfOffset(reference);
224                                 if (indLine != -1 && indLine != line) {
225                                         // take the indent of the found line
226                                         StringBuffer replaceText= new StringBuffer(getIndentOfLine(d, indLine));
227                                         // add the rest of the current line including the just added close bracket
228                                         replaceText.append(d.get(whiteend, c.offset - whiteend));
229                                         replaceText.append(c.text);
230                                         // modify document command
231                                         c.length += c.offset - start;
232                                         c.offset= start;
233                                         c.text= replaceText.toString();
234                                 }
235                         }
236                 } catch (BadLocationException e) {
237                         JavaPlugin.log(e);
238                 }
239         }
240
241         private void smartIndentAfterOpeningBracket(IDocument d, DocumentCommand c) {
242                 if (c.offset < 1 || d.getLength() == 0)
243                         return;
244
245                 JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
246
247                 int p= (c.offset == d.getLength() ? c.offset - 1 : c.offset);
248
249                 try {
250                         // current line
251                         int line= d.getLineOfOffset(p);
252                         int lineOffset= d.getLineOffset(line);
253
254                         // make sure we don't have any leading comments etc.
255                         if (d.get(lineOffset, p - lineOffset).trim().length() != 0)
256                                 return;
257
258                         // line of last Java code
259                         int pos= scanner.findNonWhitespaceBackward(p, JavaHeuristicScanner.UNBOUND);
260                         if (pos == -1)
261                                 return;
262                         int lastLine= d.getLineOfOffset(pos);
263
264                         // only shift if the last java line is further up and is a braceless block candidate
265                         if (lastLine < line) {
266
267                                 JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
268                                 StringBuffer indent= indenter.computeIndentation(p, true);
269                                 String toDelete= d.get(lineOffset, c.offset - lineOffset);
270                                 if (indent != null && !indent.toString().equals(toDelete)) {
271                                         c.text= indent.append(c.text).toString();
272                                         c.length += c.offset - lineOffset;
273                                         c.offset= lineOffset;
274                                 }
275                         }
276
277                 } catch (BadLocationException e) {
278                         JavaPlugin.log(e);
279                 }
280
281         }
282
283         private void smartIndentAfterNewLine(IDocument d, DocumentCommand c) {
284                 JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
285                 JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
286                 StringBuffer indent= indenter.computeIndentation(c.offset);
287                 if (indent == null)
288                         indent= new StringBuffer();
289
290                 int docLength= d.getLength();
291                 if (c.offset == -1 || docLength == 0)
292                         return;
293
294                 try {
295                         int p= (c.offset == docLength ? c.offset - 1 : c.offset);
296                         int line= d.getLineOfOffset(p);
297
298                         StringBuffer buf= new StringBuffer(c.text + indent);
299
300
301                         IRegion reg= d.getLineInformation(line);
302                         int lineEnd= reg.getOffset() + reg.getLength();
303
304                         int contentStart= findEndOfWhiteSpace(d, c.offset, lineEnd);
305                         c.length=  Math.max(contentStart - c.offset, 0);
306
307                         int start= reg.getOffset();
308                         ITypedRegion region= TextUtilities.getPartition(d, fPartitioning, start, true);
309                         if (IJavaPartitions.JAVA_DOC.equals(region.getType()))
310                                 start= d.getLineInformationOfOffset(region.getOffset()).getOffset();
311
312                         // insert closing brace on new line after an unclosed opening brace
313                         if (getBracketCount(d, start, c.offset, true) > 0 && closeBrace() && !isClosed(d, c.offset, c.length)) {
314                                 c.caretOffset= c.offset + buf.length();
315                                 c.shiftsCaret= false;
316
317                                 // copy old content of line behind insertion point to new line
318                                 // unless we think we are inserting an anonymous type definition
319
320                                 if (c.offset == 0 || computeAnonymousPosition(d, c.offset - 1, fPartitioning, lineEnd) == -1) {
321                                         if (lineEnd - contentStart > 0) {
322                                                 c.length=  lineEnd - c.offset;
323                                                 buf.append(d.get(contentStart, lineEnd - contentStart).toCharArray());
324                                         }
325                                 }
326
327                                 buf.append(TextUtilities.getDefaultLineDelimiter(d));
328                                 StringBuffer reference= null;
329                                 int nonWS= findEndOfWhiteSpace(d, start, lineEnd);
330                                 if (nonWS < c.offset && d.getChar(nonWS) == '{')
331                                         reference= new StringBuffer(d.get(start, nonWS - start));
332                                 else
333                                         reference= indenter.getReferenceIndentation(c.offset);
334                                 if (reference != null)
335                                         buf.append(reference);
336                                 buf.append('}');
337                         }
338                         // insert extra line upon new line between two braces
339                         else if (c.offset > start && contentStart < lineEnd && d.getChar(contentStart) == '}') {
340                                 int firstCharPos= scanner.findNonWhitespaceBackward(c.offset - 1, start);
341                                 if (firstCharPos != JavaHeuristicScanner.NOT_FOUND && d.getChar(firstCharPos) == '{') {
342                                         c.caretOffset= c.offset + buf.length();
343                                         c.shiftsCaret= false;
344
345                                         StringBuffer reference= null;
346                                         int nonWS= findEndOfWhiteSpace(d, start, lineEnd);
347                                         if (nonWS < c.offset && d.getChar(nonWS) == '{')
348                                                 reference= new StringBuffer(d.get(start, nonWS - start));
349                                         else
350                                                 reference= indenter.getReferenceIndentation(c.offset);
351
352                                         buf.append(TextUtilities.getDefaultLineDelimiter(d));
353
354                                         if (reference != null)
355                                                 buf.append(reference);
356                                 }
357                         }
358                         c.text= buf.toString();
359
360                 } catch (BadLocationException e) {
361                         JavaPlugin.log(e);
362                 }
363         }
364
365         /**
366          * Computes an insert position for an opening brace if <code>offset</code> maps to a position in
367          * <code>document</code> with a expression in parenthesis that will take a block after the closing parenthesis.
368          *
369          * @param document the document being modified
370          * @param offset the offset of the caret position, relative to the line start.
371          * @param partitioning the document partitioning
372          * @param max the max position
373          * @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
374          */
375         private static int computeAnonymousPosition(IDocument document, int offset, String partitioning,  int max) {
376                 // find the opening parenthesis for every closing parenthesis on the current line after offset
377                 // return the position behind the closing parenthesis if it looks like a method declaration
378                 // or an expression for an if, while, for, catch statement
379
380                 JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
381                 int pos= offset;
382                 int length= max;
383                 int scanTo= scanner.scanForward(pos, length, '}');
384                 if (scanTo == -1)
385                         scanTo= length;
386
387                 int closingParen= findClosingParenToLeft(scanner, pos) - 1;
388                 boolean hasNewToken= looksLikeAnonymousClassDef(document, partitioning, scanner, pos);
389                 int openingParen= -1;
390                 while (true) {
391                         int startScan= closingParen + 1;
392                         closingParen= scanner.scanForward(startScan, scanTo, ')');
393                         if (closingParen == -1) {
394                                 if (hasNewToken && openingParen != -1)
395                                         return openingParen + 1;
396                                 break;
397                         }
398
399                         openingParen= scanner.findOpeningPeer(closingParen - 1, '(', ')');
400
401                         // no way an expression at the beginning of the document can mean anything
402                         if (openingParen < 1)
403                                 break;
404
405                         // only select insert positions for parenthesis currently embracing the caret
406                         if (openingParen > pos)
407                                 continue;
408
409                         if (looksLikeAnonymousClassDef(document, partitioning, scanner, openingParen - 1))
410                                 return closingParen + 1;
411
412                 }
413
414                 return -1;
415         }
416
417         /**
418          * Finds a closing parenthesis to the left of <code>position</code> in document, where that parenthesis is only
419          * separated by whitespace from <code>position</code>. If no such parenthesis can be found, <code>position</code> is returned.
420          * 
421          * @param scanner the java heuristic scanner set up on the document
422          * @param position the first character position in <code>document</code> to be considered
423          * @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
424          */
425         private static int findClosingParenToLeft(JavaHeuristicScanner scanner, int position) {
426                 if (position < 1)
427                         return position;
428
429                 if (scanner.previousToken(position - 1, JavaHeuristicScanner.UNBOUND) == Symbols.TokenRPAREN)
430                         return scanner.getPosition() + 1;
431                 return position;
432         }
433
434         /**
435          * Checks whether the content of <code>document</code> in the range (<code>offset</code>, <code>length</code>)
436          * contains the <code>new</code> keyword.
437          *
438          * @param document the document being modified
439          * @param offset the first character position in <code>document</code> to be considered
440          * @param length the length of the character range to be considered
441          * @param partitioning the document partitioning
442          * @return <code>true</code> if the specified character range contains a <code>new</code> keyword, <code>false</code> otherwise.
443          */
444         private static boolean isNewMatch(IDocument document, int offset, int length, String partitioning) {
445                 Assert.isTrue(length >= 0);
446                 Assert.isTrue(offset >= 0);
447                 Assert.isTrue(offset + length < document.getLength() + 1);
448
449                 try {
450                         String text= document.get(offset, length);
451                         int pos= text.indexOf("new"); //$NON-NLS-1$
452
453                         while (pos != -1 && !isDefaultPartition(document, pos + offset, partitioning))
454                                 pos= text.indexOf("new", pos + 2); //$NON-NLS-1$
455
456                         if (pos < 0)
457                                 return false;
458
459                         if (pos != 0 && Character.isJavaIdentifierPart(text.charAt(pos - 1)))
460                                 return false;
461
462                         if (pos + 3 < length && Character.isJavaIdentifierPart(text.charAt(pos + 3)))
463                                 return false;
464
465                         return true;
466
467                 } catch (BadLocationException e) {
468                 }
469                 return false;
470         }
471
472         /**
473          * Checks whether the content of <code>document</code> at <code>position</code> looks like an
474          * anonymous class definition. <code>position</code> must be to the left of the opening
475          * parenthesis of the definition's parameter list.
476          *
477          * @param document the document being modified
478          * @param partitioning the document partitioning
479          * @param scanner the scanner
480          * @param position the first character position in <code>document</code> to be considered
481          * @return <code>true</code> if the content of <code>document</code> looks like an anonymous class definition, <code>false</code> otherwise
482          */
483         private static boolean looksLikeAnonymousClassDef(IDocument document, String partitioning, JavaHeuristicScanner scanner, int position) {
484                 int previousCommaParenEqual= scanner.scanBackward(position - 1, JavaHeuristicScanner.UNBOUND, new char[] {',', '(', '='});
485                 if (previousCommaParenEqual == -1 || position < previousCommaParenEqual + 5) // 2 for borders, 3 for "new"
486                         return false;
487
488                 if (isNewMatch(document, previousCommaParenEqual + 1, position - previousCommaParenEqual - 2, partitioning))
489                         return true;
490
491                 return false;
492         }
493
494         /**
495          * Checks whether <code>position</code> resides in a default (Java) partition of <code>document</code>.
496          *
497          * @param document the document being modified
498          * @param position the position to be checked
499          * @param partitioning the document partitioning
500          * @return <code>true</code> if <code>position</code> is in the default partition of <code>document</code>, <code>false</code> otherwise
501          */
502         private static boolean isDefaultPartition(IDocument document, int position, String partitioning) {
503                 Assert.isTrue(position >= 0);
504                 Assert.isTrue(position <= document.getLength());
505
506                 try {
507                         ITypedRegion region= TextUtilities.getPartition(document, partitioning, position, false);
508                         return region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE);
509
510                 } catch (BadLocationException e) {
511                 }
512
513                 return false;
514         }
515
516         private boolean isClosed(IDocument document, int offset, int length) {
517
518                 CompilationUnitInfo info= getCompilationUnitForMethod(document, offset);
519                 if (info == null)
520                         return false;
521
522                 CompilationUnit compilationUnit= null;
523                 try {
524                         ASTParser parser= ASTParser.newParser(ASTProvider.SHARED_AST_LEVEL);
525                         parser.setSource(info.buffer);
526                         compilationUnit= (CompilationUnit) parser.createAST(null);
527                 } catch (ArrayIndexOutOfBoundsException x) {
528                         // work around for parser problem
529                         return false;
530                 }
531
532                 IProblem[] problems= compilationUnit.getProblems();
533                 for (int i= 0; i != problems.length; ++i) {
534                         if (problems[i].getID() == IProblem.UnmatchedBracket)
535                                 return true;
536                 }
537
538                 final int relativeOffset= offset - info.delta;
539
540                 ASTNode node= NodeFinder.perform(compilationUnit, relativeOffset, length);
541
542                 if (length == 0) {
543                         while (node != null && (relativeOffset == node.getStartPosition() || relativeOffset == node.getStartPosition() + node.getLength()))
544                                 node= node.getParent();
545                 }
546
547                 if (node == null)
548                         return false;
549
550                 switch (node.getNodeType()) {
551                         case ASTNode.BLOCK:
552                                 return getBlockBalance(document, offset, fPartitioning) <= 0;
553
554                         case ASTNode.IF_STATEMENT:
555                         {
556                                 IfStatement ifStatement= (IfStatement) node;
557                                 Expression expression= ifStatement.getExpression();
558                                 IRegion expressionRegion= createRegion(expression, info.delta);
559                                 Statement thenStatement= ifStatement.getThenStatement();
560                                 IRegion thenRegion= createRegion(thenStatement, info.delta);
561
562                                 // between expression and then statement
563                                 if (expressionRegion.getOffset() + expressionRegion.getLength() <= offset && offset + length <= thenRegion.getOffset())
564                                         return thenStatement != null;
565
566                                 Statement elseStatement= ifStatement.getElseStatement();
567                                 IRegion elseRegion= createRegion(elseStatement, info.delta);
568
569                                 if (elseStatement != null) {
570                                         int sourceOffset= thenRegion.getOffset() + thenRegion.getLength();
571                                         int sourceLength= elseRegion.getOffset() - sourceOffset;
572                                         IRegion elseToken= getToken(document, new Region(sourceOffset, sourceLength), ITerminalSymbols.TokenNameelse);
573                                         return elseToken != null && elseToken.getOffset() + elseToken.getLength() <= offset && offset + length < elseRegion.getOffset();
574                                 }
575                         }
576                         break;
577
578                         case ASTNode.WHILE_STATEMENT:
579                         case ASTNode.FOR_STATEMENT:
580                         {
581                                 Expression expression= node.getNodeType() == ASTNode.WHILE_STATEMENT ? ((WhileStatement) node).getExpression() : ((ForStatement) node).getExpression();
582                                 IRegion expressionRegion= createRegion(expression, info.delta);
583                                 Statement body= node.getNodeType() == ASTNode.WHILE_STATEMENT ? ((WhileStatement) node).getBody() : ((ForStatement) node).getBody();
584                                 IRegion bodyRegion= createRegion(body, info.delta);
585
586                                 // between expression and body statement
587                                 if (expressionRegion.getOffset() + expressionRegion.getLength() <= offset && offset + length <= bodyRegion.getOffset())
588                                         return body != null;
589                         }
590                         break;
591
592                         case ASTNode.DO_STATEMENT:
593                         {
594                                 DoStatement doStatement= (DoStatement) node;
595                                 IRegion doRegion= createRegion(doStatement, info.delta);
596                                 Statement body= doStatement.getBody();
597                                 IRegion bodyRegion= createRegion(body, info.delta);
598
599                                 if (doRegion.getOffset() + doRegion.getLength() <= offset && offset + length <= bodyRegion.getOffset())
600                                         return body != null;
601                         }
602                         break;
603                 }
604
605                 return true;
606         }
607
608         /**
609          * Installs a java partitioner with <code>document</code>.
610          *
611          * @param document the document
612          */
613         private static void installJavaStuff(Document document) {
614                 String[] types= new String[] {
615                                                                           IJavaPartitions.JAVA_DOC,
616                                                                           IJavaPartitions.JAVA_MULTI_LINE_COMMENT,
617                                                                           IJavaPartitions.JAVA_SINGLE_LINE_COMMENT,
618                                                                           IJavaPartitions.JAVA_STRING,
619                                                                           IJavaPartitions.JAVA_CHARACTER,
620                                                                           IDocument.DEFAULT_CONTENT_TYPE
621                 };
622                 FastPartitioner partitioner= new FastPartitioner(new FastJavaPartitionScanner(), types);
623                 partitioner.connect(document);
624                 document.setDocumentPartitioner(IJavaPartitions.JAVA_PARTITIONING, partitioner);
625         }
626
627         /**
628          * Installs a java partitioner with <code>document</code>.
629          *
630          * @param document the document
631          */
632         private static void removeJavaStuff(Document document) {
633                 document.setDocumentPartitioner(IJavaPartitions.JAVA_PARTITIONING, null);
634         }
635
636         private void smartPaste(IDocument document, DocumentCommand command) {
637                 int newOffset= command.offset;
638                 int newLength= command.length;
639                 String newText= command.text;
640
641                 try {
642                         JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
643                         JavaIndenter indenter= new JavaIndenter(document, scanner, fProject);
644                         int offset= newOffset;
645
646                         // reference position to get the indent from
647                         int refOffset= indenter.findReferencePosition(offset);
648                         if (refOffset == JavaHeuristicScanner.NOT_FOUND)
649                                 return;
650                         int peerOffset= getPeerPosition(document, command);
651                         peerOffset= indenter.findReferencePosition(peerOffset);
652                         if (peerOffset != JavaHeuristicScanner.NOT_FOUND)
653                                 refOffset= Math.min(refOffset, peerOffset);
654
655                         // eat any WS before the insertion to the beginning of the line
656                         int firstLine= 1; // don't format the first line per default, as it has other content before it
657                         IRegion line= document.getLineInformationOfOffset(offset);
658                         String notSelected= document.get(line.getOffset(), offset - line.getOffset());
659                         if (notSelected.trim().length() == 0) {
660                                 newLength += notSelected.length();
661                                 newOffset= line.getOffset();
662                                 firstLine= 0;
663                         }
664
665                         // prefix: the part we need for formatting but won't paste
666                         IRegion refLine= document.getLineInformationOfOffset(refOffset);
667                         String prefix= document.get(refLine.getOffset(), newOffset - refLine.getOffset());
668
669                         // handle the indentation computation inside a temporary document
670                         Document temp= new Document(prefix + newText);
671                         DocumentRewriteSession session= temp.startRewriteSession(DocumentRewriteSessionType.STRICTLY_SEQUENTIAL);
672                         scanner= new JavaHeuristicScanner(temp);
673                         indenter= new JavaIndenter(temp, scanner, fProject);
674                         installJavaStuff(temp);
675
676                         // indent the first and second line
677                         // compute the relative indentation difference from the second line
678                         // (as the first might be partially selected) and use the value to
679                         // indent all other lines.
680                         boolean isIndentDetected= false;
681                         StringBuffer addition= new StringBuffer();
682                         int insertLength= 0;
683                         int firstLineInsertLength= 0;
684                         int firstLineIndent= 0;
685                         int first= document.computeNumberOfLines(prefix) + firstLine; // don't format first line
686                         int lines= temp.getNumberOfLines();
687                         int tabLength= getVisualTabLengthPreference();
688                         boolean changed= false;
689                         for (int l= first; l < lines; l++) { // we don't change the number of lines while adding indents
690
691                                 IRegion r= temp.getLineInformation(l);
692                                 int lineOffset= r.getOffset();
693                                 int lineLength= r.getLength();
694
695                                 if (lineLength == 0) // don't modify empty lines
696                                         continue;
697
698                                 if (!isIndentDetected) {
699
700                                         // indent the first pasted line
701                                         String current= getCurrentIndent(temp, l);
702                                         StringBuffer correct= indenter.computeIndentation(lineOffset);
703                                         if (correct == null)
704                                                 return; // bail out
705
706                                         insertLength= subtractIndent(correct, current, addition, tabLength);
707                                         if (l == first) {
708                                                 firstLineInsertLength= insertLength;
709                                                 firstLineIndent= current.length();
710                                         }
711                                         if (l != first && temp.get(lineOffset, lineLength).trim().length() != 0) {
712                                                 isIndentDetected= true;
713                                                 if (firstLineIndent >= current.length())
714                                                         insertLength= firstLineInsertLength;
715                                                 if (insertLength == 0) {
716                                                          // no adjustment needed, bail out
717                                                         if (firstLine == 0) {
718                                                                 // but we still need to adjust the first line
719                                                                 command.offset= newOffset;
720                                                                 command.length= newLength;
721                                                                 if (changed)
722                                                                         break; // still need to get the leading indent of the first line
723                                                         }
724                                                         return;
725                                                 }
726                                         } else {
727                                                 changed= insertLength != 0;
728                                         }
729                                 }
730
731                                 // relatively indent all pasted lines
732                                 if (insertLength > 0)
733                                         addIndent(temp, l, addition, tabLength);
734                                 else if (insertLength < 0)
735                                         cutIndent(temp, l, -insertLength, tabLength);
736
737                         }
738
739                         removeJavaStuff(temp);
740                         temp.stopRewriteSession(session);
741                         newText= temp.get(prefix.length(), temp.getLength() - prefix.length());
742
743                         command.offset= newOffset;
744                         command.length= newLength;
745                         command.text= newText;
746
747                 } catch (BadLocationException e) {
748                         JavaPlugin.log(e);
749                 }
750
751         }
752
753         /**
754          * Returns the indentation of the line <code>line</code> in <code>document</code>.
755          * The returned string may contain pairs of leading slashes that are considered
756          * part of the indentation. The space before the asterisk in a javadoc-like
757          * comment is not considered part of the indentation.
758          *
759          * @param document the document
760          * @param line the line
761          * @return the indentation of <code>line</code> in <code>document</code>
762          * @throws BadLocationException if the document is changed concurrently
763          */
764         private static String getCurrentIndent(Document document, int line) throws BadLocationException {
765                 IRegion region= document.getLineInformation(line);
766                 int from= region.getOffset();
767                 int endOffset= region.getOffset() + region.getLength();
768
769                 // go behind line comments
770                 int to= from;
771                 while (to < endOffset - 2 && document.get(to, 2).equals(LINE_COMMENT))
772                         to += 2;
773
774                 while (to < endOffset) {
775                         char ch= document.getChar(to);
776                         if (!Character.isWhitespace(ch))
777                                 break;
778                         to++;
779                 }
780
781                 // don't count the space before javadoc like, asterisk-style comment lines
782                 if (to > from && to < endOffset - 1 && document.get(to - 1, 2).equals(" *")) { //$NON-NLS-1$
783                         String type= TextUtilities.getContentType(document, IJavaPartitions.JAVA_PARTITIONING, to, true);
784                         if (type.equals(IJavaPartitions.JAVA_DOC) || type.equals(IJavaPartitions.JAVA_MULTI_LINE_COMMENT))
785                                 to--;
786                 }
787
788                 return document.get(from, to - from);
789         }
790
791         /**
792          * Computes the difference of two indentations and returns the difference in
793          * length of current and correct. If the return value is positive, <code>addition</code>
794          * is initialized with a substring of that length of <code>correct</code>.
795          *
796          * @param correct the correct indentation
797          * @param current the current indentation (might contain non-whitespace)
798          * @param difference a string buffer - if the return value is positive, it will be cleared and set to the substring of <code>current</code> of that length
799          * @param tabLength the length of a tab
800          * @return the difference in length of <code>correct</code> and <code>current</code>
801          */
802         private int subtractIndent(CharSequence correct, CharSequence current, StringBuffer difference, int tabLength) {
803                 int c1= computeVisualLength(correct, tabLength);
804                 int c2= computeVisualLength(current, tabLength);
805                 int diff= c1 - c2;
806                 if (diff <= 0)
807                         return diff;
808
809                 difference.setLength(0);
810                 int len= 0, i= 0;
811                 while (len < diff) {
812                         char c= correct.charAt(i++);
813                         difference.append(c);
814                         len += computeVisualLength(c, tabLength);
815                 }
816
817
818                 return diff;
819         }
820
821         /**
822          * Indents line <code>line</code> in <code>document</code> with <code>indent</code>.
823          * Leaves leading comment signs alone.
824          *
825          * @param document the document
826          * @param line the line
827          * @param indent the indentation to insert
828          * @param tabLength the length of a tab
829          * @throws BadLocationException on concurrent document modification
830          */
831         private void addIndent(Document document, int line, CharSequence indent, int tabLength) throws BadLocationException {
832                 IRegion region= document.getLineInformation(line);
833                 int insert= region.getOffset();
834                 int endOffset= region.getOffset() + region.getLength();
835
836                 // Compute insert after all leading line comment markers
837                 int newInsert= insert;
838                 while (newInsert < endOffset - 2 && document.get(newInsert, 2).equals(LINE_COMMENT))
839                         newInsert += 2;
840
841                 // Heuristic to check whether it is commented code or just a comment
842                 if (newInsert > insert) {
843                         int whitespaceCount= 0;
844                         int i= newInsert;
845                         while (i < endOffset - 1) {
846                                  char ch= document.get(i, 1).charAt(0);
847                                  if (!Character.isWhitespace(ch))
848                                          break;
849                                  whitespaceCount= whitespaceCount + computeVisualLength(ch, tabLength);
850                                  i++;
851                         }
852
853                         if (whitespaceCount != 0 && whitespaceCount >= CodeFormatterUtil.getIndentWidth(fProject))
854                                 insert= newInsert;
855                 }
856
857                 // Insert indent
858                 document.replace(insert, 0, indent.toString());
859         }
860
861         /**
862          * Cuts the visual equivalent of <code>toDelete</code> characters out of the
863          * indentation of line <code>line</code> in <code>document</code>. Leaves
864          * leading comment signs alone.
865          *
866          * @param document the document
867          * @param line the line
868          * @param toDelete the number of space equivalents to delete
869          * @param tabLength the length of a tab
870          * @throws BadLocationException on concurrent document modification
871          */
872         private void cutIndent(Document document, int line, int toDelete, int tabLength) throws BadLocationException {
873                 IRegion region= document.getLineInformation(line);
874                 int from= region.getOffset();
875                 int endOffset= region.getOffset() + region.getLength();
876
877                 // go behind line comments
878                 while (from < endOffset - 2 && document.get(from, 2).equals(LINE_COMMENT))
879                         from += 2;
880
881                 int to= from;
882                 while (toDelete > 0 && to < endOffset) {
883                         char ch= document.getChar(to);
884                         if (!Character.isWhitespace(ch))
885                                 break;
886                         toDelete -= computeVisualLength(ch, tabLength);
887                         if (toDelete >= 0)
888                                 to++;
889                         else
890                                 break;
891                 }
892
893                 document.replace(from, to - from, ""); //$NON-NLS-1$
894         }
895
896         /**
897          * Returns the visual length of a given <code>CharSequence</code> taking into
898          * account the visual tabulator length.
899          *
900          * @param seq the string to measure
901          * @param tabLength the length of a tab
902          * @return the visual length of <code>seq</code>
903          */
904         private int computeVisualLength(CharSequence seq, int tabLength) {
905                 int size= 0;
906
907                 for (int i= 0; i < seq.length(); i++) {
908                         char ch= seq.charAt(i);
909                         if (ch == '\t') {
910                                 if (tabLength != 0)
911                                         size += tabLength - size % tabLength;
912                                 // else: size stays the same
913                         } else {
914                                 size++;
915                         }
916                 }
917                 return size;
918         }
919
920         /**
921          * Returns the visual length of a given character taking into
922          * account the visual tabulator length.
923          *
924          * @param ch the character to measure
925          * @param tabLength the length of a tab
926          * @return the visual length of <code>ch</code>
927          */
928         private int computeVisualLength(char ch, int tabLength) {
929                 if (ch == '\t')
930                         return tabLength;
931                 else
932                         return 1;
933         }
934
935         /**
936          * The preference setting for the visual tabulator display.
937          *
938          * @return the number of spaces displayed for a tabulator in the editor
939          */
940         private int getVisualTabLengthPreference() {
941                 return CodeFormatterUtil.getTabWidth(fProject);
942         }
943
944         /**
945          * The preference setting that tells whether to insert spaces when pressing the Tab key.
946          *
947          * @return <code>true</code> if spaces are inserted when pressing the Tab key
948          * @since 3.5
949          */
950         private boolean isInsertingSpacesForTab() {
951                 return JavaCore.SPACE.equals(getCoreOption(fProject, DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR));
952         }
953
954         /**
955          * Returns the possibly <code>project</code>-specific core preference defined under
956          * <code>key</code>.
957          *
958          * @param project the project to get the preference from, or <code>null</code> to get the global
959          *            preference
960          * @param key the key of the preference
961          * @return the value of the preference
962          * @since 3.5
963          */
964         private static String getCoreOption(IJavaProject project, String key) {
965                 if (project == null)
966                         return JavaCore.getOption(key);
967                 return project.getOption(key, true);
968         }
969
970         private int getPeerPosition(IDocument document, DocumentCommand command) {
971                 if (document.getLength() == 0)
972                         return 0;
973         /*
974          * Search for scope closers in the pasted text and find their opening peers
975          * in the document.
976          */
977         Document pasted= new Document(command.text);
978         installJavaStuff(pasted);
979         int firstPeer= command.offset;
980
981         JavaHeuristicScanner pScanner= new JavaHeuristicScanner(pasted);
982         JavaHeuristicScanner dScanner= new JavaHeuristicScanner(document);
983
984         // add scope relevant after context to peer search
985         int afterToken= dScanner.nextToken(command.offset + command.length, JavaHeuristicScanner.UNBOUND);
986         try {
987                         switch (afterToken) {
988                         case Symbols.TokenRBRACE:
989                                 pasted.replace(pasted.getLength(), 0, "}"); //$NON-NLS-1$
990                                 break;
991                         case Symbols.TokenRPAREN:
992                                 pasted.replace(pasted.getLength(), 0, ")"); //$NON-NLS-1$
993                                 break;
994                         case Symbols.TokenRBRACKET:
995                                 pasted.replace(pasted.getLength(), 0, "]"); //$NON-NLS-1$
996                                 break;
997                         }
998                 } catch (BadLocationException e) {
999                         // cannot happen
1000                         Assert.isTrue(false);
1001                 }
1002
1003         int pPos= 0; // paste text position (increasing from 0)
1004         int dPos= Math.max(0, command.offset - 1); // document position (decreasing from paste offset)
1005         while (true) {
1006                 int token= pScanner.nextToken(pPos, JavaHeuristicScanner.UNBOUND);
1007                         pPos= pScanner.getPosition();
1008                 switch (token) {
1009                         case Symbols.TokenLBRACE:
1010                         case Symbols.TokenLBRACKET:
1011                         case Symbols.TokenLPAREN:
1012                                 pPos= skipScope(pScanner, pPos, token);
1013                                 if (pPos == JavaHeuristicScanner.NOT_FOUND)
1014                                         return firstPeer;
1015                                 break; // closed scope -> keep searching
1016                         case Symbols.TokenRBRACE:
1017                                 int peer= dScanner.findOpeningPeer(dPos, '{', '}');
1018                                 dPos= peer - 1;
1019                                 if (peer == JavaHeuristicScanner.NOT_FOUND)
1020                                         return firstPeer;
1021                                 firstPeer= peer;
1022                                 break; // keep searching
1023                         case Symbols.TokenRBRACKET:
1024                                 peer= dScanner.findOpeningPeer(dPos, '[', ']');
1025                                 dPos= peer - 1;
1026                                 if (peer == JavaHeuristicScanner.NOT_FOUND)
1027                                         return firstPeer;
1028                                 firstPeer= peer;
1029                                 break; // keep searching
1030                         case Symbols.TokenRPAREN:
1031                                 peer= dScanner.findOpeningPeer(dPos, '(', ')');
1032                                 dPos= peer - 1;
1033                                 if (peer == JavaHeuristicScanner.NOT_FOUND)
1034                                         return firstPeer;
1035                                 firstPeer= peer;
1036                                 break; // keep searching
1037                         case Symbols.TokenCASE:
1038                         case Symbols.TokenDEFAULT:
1039                                 JavaIndenter indenter= new JavaIndenter(document, dScanner, fProject);
1040                                 peer= indenter.findReferencePosition(dPos, false, false, false, true);
1041                                 if (peer == JavaHeuristicScanner.NOT_FOUND)
1042                                         return firstPeer;
1043                                 firstPeer= peer;
1044                                 break; // keep searching
1045
1046                         case Symbols.TokenEOF:
1047                                 return firstPeer;
1048                         default:
1049                                 // keep searching
1050                 }
1051         }
1052     }
1053
1054     /**
1055      * Skips the scope opened by <code>token</code>.
1056      *
1057      * @param scanner the scanner
1058      * @param start the start position
1059      * @param token the token
1060      * @return the position after the scope or <code>JavaHeuristicScanner.NOT_FOUND</code>
1061      */
1062     private static int skipScope(JavaHeuristicScanner scanner, int start, int token) {
1063         int openToken= token;
1064         int closeToken;
1065         switch (token) {
1066                 case Symbols.TokenLPAREN:
1067                         closeToken= Symbols.TokenRPAREN;
1068                         break;
1069                 case Symbols.TokenLBRACKET:
1070                         closeToken= Symbols.TokenRBRACKET;
1071                         break;
1072                 case Symbols.TokenLBRACE:
1073                         closeToken= Symbols.TokenRBRACE;
1074                         break;
1075                 default:
1076                         Assert.isTrue(false);
1077                         return -1; // dummy
1078         }
1079
1080         int depth= 1;
1081         int p= start;
1082
1083         while (true) {
1084                 int tok= scanner.nextToken(p, JavaHeuristicScanner.UNBOUND);
1085                 p= scanner.getPosition();
1086
1087                 if (tok == openToken) {
1088                         depth++;
1089                 } else if (tok == closeToken) {
1090                         depth--;
1091                         if (depth == 0)
1092                                 return p + 1;
1093                 } else if (tok == Symbols.TokenEOF) {
1094                         return JavaHeuristicScanner.NOT_FOUND;
1095                 }
1096         }
1097     }
1098
1099     private boolean isLineDelimiter(IDocument document, String text) {
1100                 String[] delimiters= document.getLegalLineDelimiters();
1101                 if (delimiters != null)
1102                         return TextUtilities.equals(delimiters, text) > -1;
1103                 return false;
1104         }
1105
1106         private void smartIndentOnKeypress(IDocument document, DocumentCommand command) {
1107                 switch (command.text.charAt(0)) {
1108                         case '}':
1109                                 smartIndentAfterClosingBracket(document, command);
1110                                 break;
1111                         case '{':
1112                                 smartIndentAfterOpeningBracket(document, command);
1113                                 break;
1114                         case 'e':
1115                                 smartIndentUponE(document, command);
1116                                 break;
1117                 }
1118         }
1119
1120         private void smartIndentUponE(IDocument d, DocumentCommand c) {
1121                 if (c.offset < 4 || d.getLength() == 0)
1122                         return;
1123
1124                 try {
1125                         String content= d.get(c.offset - 3, 3);
1126                         if (content.equals("els")) { //$NON-NLS-1$
1127                                 JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
1128                                 int p= c.offset - 3;
1129
1130                                 // current line
1131                                 int line= d.getLineOfOffset(p);
1132                                 int lineOffset= d.getLineOffset(line);
1133
1134                                 // make sure we don't have any leading comments etc.
1135                                 if (d.get(lineOffset, p - lineOffset).trim().length() != 0)
1136                                         return;
1137
1138                                 // line of last Java code
1139                                 int pos= scanner.findNonWhitespaceBackward(p - 1, JavaHeuristicScanner.UNBOUND);
1140                                 if (pos == -1)
1141                                         return;
1142                                 int lastLine= d.getLineOfOffset(pos);
1143
1144                                 // only shift if the last java line is further up and is a braceless block candidate
1145                                 if (lastLine < line) {
1146
1147                                         JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
1148                                         int ref= indenter.findReferencePosition(p, true, false, false, false);
1149                                         if (ref == JavaHeuristicScanner.NOT_FOUND)
1150                                                 return;
1151                                         int refLine= d.getLineOfOffset(ref);
1152                                         String indent= getIndentOfLine(d, refLine);
1153
1154                                         if (indent != null) {
1155                                                 c.text= indent.toString() + "else"; //$NON-NLS-1$
1156                                                 c.length += c.offset - lineOffset;
1157                                                 c.offset= lineOffset;
1158                                         }
1159                                 }
1160
1161                                 return;
1162                         }
1163
1164                         if (content.equals("cas")) { //$NON-NLS-1$
1165                                 JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
1166                                 int p= c.offset - 3;
1167
1168                                 // current line
1169                                 int line= d.getLineOfOffset(p);
1170                                 int lineOffset= d.getLineOffset(line);
1171
1172                                 // make sure we don't have any leading comments etc.
1173                                 if (d.get(lineOffset, p - lineOffset).trim().length() != 0)
1174                                         return;
1175
1176                                 // line of last Java code
1177                                 int pos= scanner.findNonWhitespaceBackward(p - 1, JavaHeuristicScanner.UNBOUND);
1178                                 if (pos == -1)
1179                                         return;
1180                                 int lastLine= d.getLineOfOffset(pos);
1181
1182                                 // only shift if the last java line is further up and is a braceless block candidate
1183                                 if (lastLine < line) {
1184
1185                                         JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
1186                                         int ref= indenter.findReferencePosition(p, false, false, false, true);
1187                                         if (ref == JavaHeuristicScanner.NOT_FOUND)
1188                                                 return;
1189                                         int refLine= d.getLineOfOffset(ref);
1190                                         int nextToken= scanner.nextToken(ref, JavaHeuristicScanner.UNBOUND);
1191                                         String indent;
1192                                         if (nextToken == Symbols.TokenCASE || nextToken == Symbols.TokenDEFAULT)
1193                                                 indent= getIndentOfLine(d, refLine);
1194                                         else // at the brace of the switch
1195                                                 indent= indenter.computeIndentation(p).toString();
1196
1197                                         if (indent != null) {
1198                                                 c.text= indent.toString() + "case"; //$NON-NLS-1$
1199                                                 c.length += c.offset - lineOffset;
1200                                                 c.offset= lineOffset;
1201                                         }
1202                                 }
1203
1204                                 return;
1205                         }
1206
1207                 } catch (BadLocationException e) {
1208                         JavaPlugin.log(e);
1209                 }
1210         }
1211
1212         /*
1213          * @see org.eclipse.jface.text.IAutoIndentStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand)
1214          */
1215         @Override
1216         public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
1217                 if (c.doit == false)
1218                         return;
1219
1220                 clearCachedValues();
1221
1222                 if (!fIsSmartMode) {
1223                         super.customizeDocumentCommand(d, c);
1224                         return;
1225                 }
1226
1227                 if (!fIsSmartTab && isRepresentingTab(c.text))
1228                         return;
1229
1230                 if (c.length == 0 && c.text != null && isLineDelimiter(d, c.text)) {
1231                         if (fIsSmartIndentAfterNewline)
1232                                 smartIndentAfterNewLine(d, c);
1233                         else
1234                                 super.customizeDocumentCommand(d, c);
1235                 }
1236                 else if (c.text.length() == 1)
1237                         smartIndentOnKeypress(d, c);
1238                 else if (c.text.length() > 1 && getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SMART_PASTE))
1239                         if (fViewer == null || fViewer.getTextWidget() == null || !fViewer.getTextWidget().getBlockSelection())
1240                                 smartPaste(d, c); // no smart backspace for paste
1241
1242         }
1243
1244         /**
1245          * Tells whether the given inserted string represents hitting the Tab key.
1246          *
1247          * @param text the text to check
1248          * @return <code>true</code> if the text represents hitting the Tab key
1249          * @since 3.5
1250          */
1251         private boolean isRepresentingTab(String text) {
1252                 if (text == null)
1253                         return false;
1254
1255                 if (isInsertingSpacesForTab()) {
1256                         if (text.length() == 0 || text.length() > getVisualTabLengthPreference())
1257                                 return false;
1258                         for (int i= 0; i < text.length(); i++) {
1259                                 if (text.charAt(i) != ' ')
1260                                         return false;
1261                         }
1262                         return true;
1263                 } else
1264                         return text.length() == 1 && text.charAt(0) == '\t';
1265         }
1266
1267         private static IPreferenceStore getPreferenceStore() {
1268                 return JavaPlugin.getDefault().getCombinedPreferenceStore();
1269         }
1270
1271         private boolean closeBrace() {
1272                 return fCloseBrace;
1273         }
1274
1275         private void clearCachedValues() {
1276         IPreferenceStore preferenceStore= getPreferenceStore();
1277                 fCloseBrace= preferenceStore.getBoolean(PreferenceConstants.EDITOR_CLOSE_BRACES);
1278                 fIsSmartTab= preferenceStore.getBoolean(PreferenceConstants.EDITOR_SMART_TAB);
1279                 fIsSmartIndentAfterNewline= preferenceStore.getBoolean(PreferenceConstants.EDITOR_SMART_INDENT_AFTER_NEWLINE);
1280                 fIsSmartMode= computeSmartMode();
1281         }
1282
1283         private boolean computeSmartMode() {
1284                 IWorkbenchPage page= JavaPlugin.getActivePage();
1285                 if (page != null)  {
1286                         IEditorPart part= page.getActiveEditor();
1287                         if (part instanceof ITextEditorExtension3) {
1288                                 ITextEditorExtension3 extension= (ITextEditorExtension3) part;
1289                                 return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT;
1290                         } else if (part != null && EditorUtility.isCompareEditorInput(part.getEditorInput())) {
1291                                 ITextEditorExtension3 extension = (ITextEditorExtension3)part.getAdapter(ITextEditorExtension3.class);
1292                                 if (extension != null)
1293                                         return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT;
1294                         }
1295                 }
1296                 return false;
1297         }
1298
1299         private static CompilationUnitInfo getCompilationUnitForMethod(IDocument document, int offset) {
1300                 try {
1301                         JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
1302
1303                         IRegion sourceRange= scanner.findSurroundingBlock(offset);
1304                         if (sourceRange == null)
1305                                 return null;
1306                         String source= document.get(sourceRange.getOffset(), sourceRange.getLength());
1307
1308                         StringBuffer contents= new StringBuffer();
1309                         contents.append("class ____C{void ____m()"); //$NON-NLS-1$
1310                         final int methodOffset= contents.length();
1311                         contents.append(source);
1312                         contents.append('}');
1313
1314                         char[] buffer= contents.toString().toCharArray();
1315
1316                         return new CompilationUnitInfo(buffer, sourceRange.getOffset() - methodOffset);
1317
1318                 } catch (BadLocationException e) {
1319                         JavaPlugin.log(e);
1320                 }
1321
1322                 return null;
1323         }
1324
1325         /**
1326          * Returns the block balance, i.e. zero if the blocks are balanced at <code>offset</code>, a
1327          * negative number if there are more closing than opening braces, and a positive number if there
1328          * are more opening than closing braces.
1329          * 
1330          * @param document the document
1331          * @param offset the offset
1332          * @param partitioning the partitioning
1333          * @return the block balance
1334          */
1335         private static int getBlockBalance(IDocument document, int offset, String partitioning) {
1336                 if (offset < 1)
1337                         return -1;
1338                 if (offset >= document.getLength())
1339                         return 1;
1340
1341                 int begin= offset;
1342                 int end= offset - 1;
1343
1344                 JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
1345
1346                 while (true) {
1347                         begin= scanner.findOpeningPeer(begin - 1, '{', '}');
1348                         end= scanner.findClosingPeer(end + 1, '{', '}');
1349                         if (begin == -1 && end == -1)
1350                                 return 0;
1351                         if (begin == -1)
1352                                 return -1;
1353                         if (end == -1)
1354                                 return 1;
1355                 }
1356         }
1357
1358         private static IRegion createRegion(ASTNode node, int delta) {
1359                 return node == null ? null : new Region(node.getStartPosition() + delta, node.getLength());
1360         }
1361
1362         private static IRegion getToken(IDocument document, IRegion scanRegion, int tokenId)  {
1363
1364                 try {
1365
1366                         final String source= document.get(scanRegion.getOffset(), scanRegion.getLength());
1367
1368                         fgScanner.setSource(source.toCharArray());
1369
1370                         int id= fgScanner.getNextToken();
1371                         while (id != ITerminalSymbols.TokenNameEOF && id != tokenId)
1372                                 id= fgScanner.getNextToken();
1373
1374                         if (id == ITerminalSymbols.TokenNameEOF)
1375                                 return null;
1376
1377                         int tokenOffset= fgScanner.getCurrentTokenStartPosition();
1378                         int tokenLength= fgScanner.getCurrentTokenEndPosition() + 1 - tokenOffset; // inclusive end
1379                         return new Region(tokenOffset + scanRegion.getOffset(), tokenLength);
1380
1381                 } catch (InvalidInputException x) {
1382                         return null;
1383                 } catch (BadLocationException x) {
1384                         return null;
1385                 }
1386         }
1387 }