IDEA-146951 (Structural Search fails with '<' in search in pattern)
[idea/community.git] / java / structuralsearch-java / src / com / intellij / structuralsearch / JavaStructuralSearchProfile.java
1 package com.intellij.structuralsearch;
2
3 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
4 import com.intellij.codeInsight.template.JavaCodeContextType;
5 import com.intellij.codeInsight.template.TemplateContextType;
6 import com.intellij.codeInsight.template.TemplateManager;
7 import com.intellij.dupLocator.iterators.NodeIterator;
8 import com.intellij.lang.Language;
9 import com.intellij.lang.java.JavaLanguage;
10 import com.intellij.openapi.editor.Document;
11 import com.intellij.openapi.editor.Editor;
12 import com.intellij.openapi.fileEditor.FileEditorManager;
13 import com.intellij.openapi.fileTypes.FileType;
14 import com.intellij.openapi.fileTypes.LanguageFileType;
15 import com.intellij.openapi.fileTypes.StdFileTypes;
16 import com.intellij.openapi.project.Project;
17 import com.intellij.openapi.util.text.StringUtil;
18 import com.intellij.psi.*;
19 import com.intellij.psi.util.PsiTreeUtil;
20 import com.intellij.psi.util.PsiUtilCore;
21 import com.intellij.structuralsearch.impl.matcher.*;
22 import com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor;
23 import com.intellij.structuralsearch.impl.matcher.compiler.JavaCompilingVisitor;
24 import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler;
25 import com.intellij.structuralsearch.impl.matcher.filters.JavaLexicalNodesFilter;
26 import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
27 import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
28 import com.intellij.structuralsearch.plugin.replace.impl.ParameterInfo;
29 import com.intellij.structuralsearch.plugin.replace.impl.ReplacementBuilder;
30 import com.intellij.structuralsearch.plugin.replace.impl.ReplacementContext;
31 import com.intellij.structuralsearch.plugin.replace.impl.Replacer;
32 import com.intellij.structuralsearch.plugin.ui.Configuration;
33 import com.intellij.structuralsearch.plugin.ui.SearchContext;
34 import com.intellij.structuralsearch.plugin.ui.UIUtil;
35 import com.intellij.util.IncorrectOperationException;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38
39 import java.util.*;
40
41 /**
42  * @author Eugene.Kudelevsky
43  */
44 public class JavaStructuralSearchProfile extends StructuralSearchProfile {
45   private JavaLexicalNodesFilter myJavaLexicalNodesFilter;
46
47   public String getText(PsiElement match, int start,int end) {
48     if (match instanceof PsiIdentifier) {
49       PsiElement parent = match.getParent();
50       if (parent instanceof PsiJavaCodeReferenceElement && !(parent instanceof PsiExpression)) {
51         match = parent; // care about generic
52       }
53     }
54     final String matchText = match.getText();
55     if (start==0 && end==-1) return matchText;
56     return matchText.substring(start,end == -1? matchText.length():end);
57   }
58
59   public Class getElementContextByPsi(PsiElement element) {
60     if (element instanceof PsiIdentifier) {
61       element = element.getParent();
62     }
63
64     if (element instanceof PsiMember) {
65       return PsiMember.class;
66     } else {
67       return PsiExpression.class;
68     }
69   }
70
71   @NotNull
72   public String getTypedVarString(final PsiElement element) {
73     String text;
74
75     if (element instanceof PsiNamedElement) {
76       text = ((PsiNamedElement)element).getName();
77     }
78     else if (element instanceof PsiAnnotation) {
79       PsiJavaCodeReferenceElement referenceElement = ((PsiAnnotation)element).getNameReferenceElement();
80       text = referenceElement == null ? null : referenceElement.getQualifiedName();
81     }
82     else if (element instanceof PsiNameValuePair) {
83       text = ((PsiNameValuePair)element).getName();
84     }
85     else {
86       text = element.getText();
87       if (StringUtil.startsWithChar(text, '@')) {
88         text = text.substring(1);
89       }
90       if (StringUtil.endsWithChar(text, ';')) text = text.substring(0, text.length() - 1);
91       else if (element instanceof PsiExpressionStatement) {
92         int i = text.indexOf(';');
93         if (i != -1) text = text.substring(0, i);
94       }
95     }
96
97     if (text==null) text = element.getText();
98
99     return text;
100   }
101
102   @Override
103   public String getMeaningfulText(PsiElement element) {
104     if (element instanceof PsiReferenceExpression &&
105         ((PsiReferenceExpression)element).getQualifierExpression() != null) {
106       final PsiElement resolve = ((PsiReferenceExpression)element).resolve();
107       if (resolve instanceof PsiClass) return element.getText();
108
109       final PsiElement referencedElement = ((PsiReferenceExpression)element).getReferenceNameElement();
110       String text = referencedElement != null ? referencedElement.getText() : "";
111
112       if (resolve == null && text.length() > 0 && Character.isUpperCase(text.charAt(0))) {
113         return element.getText();
114       }
115       return text;
116     }
117     return super.getMeaningfulText(element);
118   }
119
120   @Override
121   public PsiElement updateCurrentNode(PsiElement targetNode) {
122     if (targetNode instanceof PsiCodeBlock && ((PsiCodeBlock)targetNode).getStatements().length == 1) {
123       PsiElement targetNodeParent = targetNode.getParent();
124       if (targetNodeParent instanceof PsiBlockStatement) {
125         targetNodeParent = targetNodeParent.getParent();
126       }
127
128       if (targetNodeParent instanceof PsiIfStatement || targetNodeParent instanceof PsiLoopStatement) {
129         targetNode = targetNodeParent;
130       }
131     }
132     return targetNode;
133   }
134
135   @Override
136   public PsiElement extendMatchedByDownUp(PsiElement targetNode) {
137     if (targetNode instanceof PsiIdentifier) {
138       targetNode = targetNode.getParent();
139       final PsiElement parent = targetNode.getParent();
140       if (parent instanceof PsiTypeElement || parent instanceof PsiStatement) targetNode = parent;
141     }
142     return targetNode;
143   }
144
145   @Override
146   public PsiElement extendMatchOnePsiFile(PsiElement file) {
147     if (file instanceof PsiIdentifier) {
148       // Searching in previous results
149       file = file.getParent();
150     }
151     return file;
152   }
153
154   public void compile(PsiElement[] elements, @NotNull GlobalCompilingVisitor globalVisitor) {
155     elements[0].getParent().accept(new JavaCompilingVisitor(globalVisitor));
156   }
157
158   @NotNull
159   public PsiElementVisitor createMatchingVisitor(@NotNull GlobalMatchingVisitor globalVisitor) {
160     return new JavaMatchingVisitor(globalVisitor);
161   }
162
163   @NotNull
164   @Override
165   public PsiElementVisitor getLexicalNodesFilter(@NotNull LexicalNodesFilter filter) {
166     if (myJavaLexicalNodesFilter == null) {
167       myJavaLexicalNodesFilter = new JavaLexicalNodesFilter(filter);
168     }
169     return myJavaLexicalNodesFilter;
170   }
171
172   @NotNull
173   public CompiledPattern createCompiledPattern() {
174     return new JavaCompiledPattern();
175   }
176
177   public boolean isMyLanguage(@NotNull Language language) {
178     return language == JavaLanguage.INSTANCE;
179   }
180
181   @Override
182   public StructuralReplaceHandler getReplaceHandler(@NotNull ReplacementContext context) {
183     return new JavaReplaceHandler(context);
184   }
185
186   @NotNull
187   @Override
188   public PsiElement[] createPatternTree(@NotNull String text,
189                                         @NotNull PatternTreeContext context,
190                                         @NotNull FileType fileType,
191                                         @Nullable Language language,
192                                         String contextName, @Nullable String extension,
193                                         @NotNull Project project,
194                                         boolean physical) {
195     if (physical) {
196       throw new UnsupportedOperationException(getClass() + " cannot create physical PSI");
197     }
198     final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory();
199     if (context == PatternTreeContext.Block) {
200       final PsiElement element = elementFactory.createStatementFromText("{\n" + text + "\n}", null);
201       final PsiElement[] children = ((PsiBlockStatement)element).getCodeBlock().getChildren();
202       final int extraChildCount = 4;
203
204       if (children.length > extraChildCount) {
205         PsiElement[] result = new PsiElement[children.length - extraChildCount];
206         System.arraycopy(children, 2, result, 0, children.length - extraChildCount);
207
208         if (shouldTryExpressionPattern(result)) {
209           try {
210             final PsiElement[] expressionPattern =
211               createPatternTree(text, PatternTreeContext.Expression, fileType, language, contextName, extension, project, false);
212             if (expressionPattern.length == 1) {
213               result = expressionPattern;
214             }
215           } catch (IncorrectOperationException ignore) {}
216         }
217         else if (shouldTryClassPattern(result)) {
218           final PsiElement[] classPattern =
219             createPatternTree(text, PatternTreeContext.Class, fileType, language, contextName, extension, project, false);
220           if (classPattern.length == 1) {
221             result = classPattern;
222           }
223         }
224         return result;
225       }
226       else {
227         return PsiElement.EMPTY_ARRAY;
228       }
229     }
230     else if (context == PatternTreeContext.Class) {
231       final PsiClass clazz = elementFactory.createClassFromText(text, null);
232       PsiElement startChild = clazz.getLBrace();
233       if (startChild != null) startChild = startChild.getNextSibling();
234
235       PsiElement endChild = clazz.getRBrace();
236       if (endChild != null) endChild = endChild.getPrevSibling();
237       if (startChild == endChild) return PsiElement.EMPTY_ARRAY; // nothing produced
238
239       final PsiCodeBlock codeBlock = elementFactory.createCodeBlock();
240       final List<PsiElement> result = new ArrayList<PsiElement>(3);
241       assert startChild != null;
242       for (PsiElement el = startChild.getNextSibling(); el != endChild && el != null; el = el.getNextSibling()) {
243         if (el instanceof PsiErrorElement) continue;
244         result.add(codeBlock.add(el));
245       }
246
247       return PsiUtilCore.toPsiElementArray(result);
248     }
249     else if (context == PatternTreeContext.Expression) {
250       final PsiExpression expression = elementFactory.createExpressionFromText(text, null);
251       final PsiBlockStatement statement = (PsiBlockStatement)elementFactory.createStatementFromText("{\na\n}", null);
252       final PsiElement[] children = statement.getCodeBlock().getChildren();
253       if (children.length != 5) return PsiElement.EMPTY_ARRAY;
254       final PsiExpressionStatement childStatement = (PsiExpressionStatement)children[2];
255       childStatement.getExpression().replace(expression);
256       return new PsiElement[] { childStatement };
257     }
258     else {
259       return PsiFileFactory.getInstance(project).createFileFromText("__dummy.java", text).getChildren();
260     }
261   }
262
263   private static boolean shouldTryExpressionPattern(PsiElement[] elements) {
264     if (elements.length >= 1 && elements.length <= 3) {
265       final PsiElement firstElement = elements[0];
266       if (firstElement instanceof PsiDeclarationStatement) {
267         final PsiElement lastChild = firstElement.getLastChild();
268         if (lastChild instanceof PsiErrorElement && PsiTreeUtil.prevLeaf(lastChild) instanceof PsiErrorElement) {
269           // Because an identifier followed by < (less than) is parsed as the start of a declaration
270           // in com.intellij.lang.java.parser.StatementParser.parseStatement() line 236
271           // but it could just be a comparison
272           return true;
273         }
274       }
275     }
276     return false;
277   }
278
279   private static boolean shouldTryClassPattern(PsiElement[] result) {
280     if (result.length == 3 && PsiModifier.STATIC.equals(result[0].getText()) && result[1] instanceof PsiWhiteSpace &&
281         result[2] instanceof PsiBlockStatement) {
282       // looks like static initializer
283       return true;
284     }
285     else if (result.length > 1 && result[0] instanceof PsiDeclarationStatement && !result[0].getText().endsWith(";")) {
286       // might be method
287       return true;
288     }
289     return false;
290   }
291
292   @NotNull
293   @Override
294   public Editor createEditor(@NotNull SearchContext searchContext,
295                              @NotNull FileType fileType,
296                              Language dialect,
297                              String text,
298                              boolean useLastConfiguration) {
299     // provides autocompletion
300
301     PsiElement element = searchContext.getFile();
302
303     if (element != null && !useLastConfiguration) {
304       final Editor selectedEditor = FileEditorManager.getInstance(searchContext.getProject()).getSelectedTextEditor();
305
306       if (selectedEditor != null) {
307         int caretPosition = selectedEditor.getCaretModel().getOffset();
308         PsiElement positionedElement = searchContext.getFile().findElementAt(caretPosition);
309
310         if (positionedElement == null) {
311           positionedElement = searchContext.getFile().findElementAt(caretPosition + 1);
312         }
313
314         if (positionedElement != null) {
315           element = PsiTreeUtil.getParentOfType(
316             positionedElement,
317             PsiClass.class, PsiCodeBlock.class
318           );
319         }
320       }
321     }
322
323     final PsiManager psimanager = PsiManager.getInstance(searchContext.getProject());
324     final Project project = psimanager.getProject();
325     final PsiCodeFragment file = createCodeFragment(project, text, element);
326     final Document doc = PsiDocumentManager.getInstance(searchContext.getProject()).getDocument(file);
327     DaemonCodeAnalyzer.getInstance(searchContext.getProject()).setHighlightingEnabled(file, false);
328     return UIUtil.createEditor(doc, searchContext.getProject(), true, true, getTemplateContextType());
329   }
330
331   @Override
332   public Class<? extends TemplateContextType> getTemplateContextTypeClass() {
333     return JavaCodeContextType.class;
334   }
335
336   public PsiCodeFragment createCodeFragment(Project project, String text, PsiElement context) {
337     final JavaCodeFragmentFactory factory = JavaCodeFragmentFactory.getInstance(project);
338     return factory.createCodeBlockCodeFragment(text, context, true);
339   }
340
341   @Override
342   public void checkSearchPattern(Project project, MatchOptions options) {
343     class ValidatingVisitor extends JavaRecursiveElementWalkingVisitor {
344       private PsiElement myCurrent;
345
346       @Override public void visitAnnotation(PsiAnnotation annotation) {
347         final PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement();
348
349         if (nameReferenceElement == null ||
350             !nameReferenceElement.getText().equals(MatchOptions.MODIFIER_ANNOTATION_NAME)) {
351           return;
352         }
353
354         for(PsiNameValuePair pair:annotation.getParameterList().getAttributes()) {
355           final PsiAnnotationMemberValue value = pair.getValue();
356
357           if (value instanceof PsiArrayInitializerMemberValue) {
358             for(PsiAnnotationMemberValue v:((PsiArrayInitializerMemberValue)value).getInitializers()) {
359               final String name = StringUtil.stripQuotesAroundValue(v.getText());
360               checkModifier(name);
361             }
362
363           } else if (value != null) {
364             final String name = StringUtil.stripQuotesAroundValue(value.getText());
365             checkModifier(name);
366           }
367         }
368       }
369
370       private void checkModifier(final String name) {
371         if (!MatchOptions.INSTANCE_MODIFIER_NAME.equals(name) &&
372             !PsiModifier.PACKAGE_LOCAL.equals(name) &&
373             Arrays.binarySearch(JavaMatchingVisitor.MODIFIERS, name) < 0
374           ) {
375           throw new MalformedPatternException(SSRBundle.message("invalid.modifier.type",name));
376         }
377       }
378
379       @Override
380       public void visitErrorElement(PsiErrorElement element) {
381         super.visitErrorElement(element);
382         //final PsiElement parent = element.getParent();
383         //if (parent != myCurrent || !"';' expected".equals(element.getErrorDescription())) {
384         //  throw new MalformedPatternException(element.getErrorDescription());
385         //}
386       }
387
388       public void setCurrent(PsiElement current) {
389         myCurrent = current;
390       }
391     }
392     ValidatingVisitor visitor = new ValidatingVisitor();
393     final CompiledPattern compiledPattern = PatternCompiler.compilePattern(project, options);
394     final int nodeCount = compiledPattern.getNodeCount();
395     final NodeIterator nodes = compiledPattern.getNodes();
396     while (nodes.hasNext()) {
397       final PsiElement current = nodes.current();
398       visitor.setCurrent(nodeCount == 1 && current instanceof PsiExpressionStatement ? current : null);
399       current.accept(visitor);
400       nodes.advance();
401     }
402     nodes.reset();
403   }
404
405   @Override
406   public void checkReplacementPattern(Project project, ReplaceOptions options) {
407     MatchOptions matchOptions = options.getMatchOptions();
408     FileType fileType = matchOptions.getFileType();
409     PsiElement[] statements = MatcherImplUtil.createTreeFromText(
410       matchOptions.getSearchPattern(),
411       PatternTreeContext.Block,
412       fileType,
413       project
414     );
415     final boolean searchIsExpression = statements.length == 1 && statements[0].getLastChild() instanceof PsiErrorElement;
416
417     PsiElement[] statements2 = MatcherImplUtil.createTreeFromText(
418       options.getReplacement(),
419       PatternTreeContext.Block,
420       fileType,
421       project
422     );
423     final boolean replaceIsExpression = statements2.length == 1 && statements2[0].getLastChild() instanceof PsiErrorElement;
424
425     if (searchIsExpression && statements[0].getFirstChild() instanceof PsiModifierList && statements2.length == 0) {
426       return;
427     }
428     if (searchIsExpression != replaceIsExpression) {
429       throw new UnsupportedPatternException(
430         searchIsExpression ? SSRBundle.message("replacement.template.is.not.expression.error.message") :
431         SSRBundle.message("search.template.is.not.expression.error.message")
432       );
433     }
434   }
435
436   @Override
437   public LanguageFileType getDefaultFileType(LanguageFileType currentDefaultFileType) {
438     return StdFileTypes.JAVA;
439   }
440
441   @Override
442   Configuration[] getPredefinedTemplates() {
443     return JavaPredefinedConfigurations.createPredefinedTemplates();
444   }
445
446   @Override
447   public void provideAdditionalReplaceOptions(@NotNull PsiElement node, final ReplaceOptions options, final ReplacementBuilder builder) {
448     final String templateText = TemplateManager.getInstance(node.getProject()).createTemplate("", "", options.getReplacement()).getTemplateText();
449     node.accept(new JavaRecursiveElementWalkingVisitor() {
450       @Override
451       public void visitReferenceExpression(PsiReferenceExpression expression) {
452         visitElement(expression);
453       }
454
455       @Override
456       public void visitClass(PsiClass aClass) {
457         super.visitClass(aClass);
458
459         MatchVariableConstraint constraint =
460           options.getMatchOptions().getVariableConstraint(CompiledPattern.ALL_CLASS_UNMATCHED_CONTENT_VAR_ARTIFICIAL_NAME);
461         if (constraint != null) {
462           ParameterInfo e = new ParameterInfo();
463           e.setName(CompiledPattern.ALL_CLASS_UNMATCHED_CONTENT_VAR_ARTIFICIAL_NAME);
464           e.setStartIndex(templateText.lastIndexOf('}'));
465           builder.addParametrization(e);
466         }
467       }
468
469       @Override
470       public void visitParameter(PsiParameter parameter) {
471         super.visitParameter(parameter);
472
473         String name = parameter.getName();
474         String type = parameter.getType().getCanonicalText();
475
476         if (StructuralSearchUtil.isTypedVariable(name)) {
477           name = Replacer.stripTypedVariableDecoration(name);
478
479           if (StructuralSearchUtil.isTypedVariable(type)) {
480             type = Replacer.stripTypedVariableDecoration(type);
481           }
482           ParameterInfo nameInfo = builder.findParameterization(name);
483           ParameterInfo typeInfo = builder.findParameterization(type);
484
485           final PsiElement scope = parameter.getDeclarationScope();
486           if (nameInfo != null && typeInfo != null && !(scope instanceof PsiCatchSection) && !(scope instanceof PsiForeachStatement)) {
487             nameInfo.setArgumentContext(false);
488             typeInfo.setArgumentContext(false);
489             typeInfo.setMethodParameterContext(true);
490             nameInfo.setMethodParameterContext(true);
491             typeInfo.setElement(parameter.getTypeElement());
492           }
493         }
494       }
495     });
496   }
497
498   @Override
499   public int handleSubstitution(final ParameterInfo info,
500                                 MatchResult match,
501                                 StringBuilder result,
502                                 int offset,
503                                 HashMap<String, MatchResult> matchMap) {
504     if (info.getName().equals(match.getName())) {
505       String replacementString = match.getMatchImage();
506       boolean forceAddingNewLine = false;
507
508       if (info.isMethodParameterContext()) {
509         StringBuilder buf = new StringBuilder();
510         handleMethodParameter(buf, info, matchMap);
511         replacementString = buf.toString();
512       }
513       else if (match.getAllSons().size() > 0 && !match.isScopeMatch()) {
514         // compound matches
515         StringBuilder buf = new StringBuilder();
516         MatchResult r = null;
517
518         for (final MatchResult matchResult : match.getAllSons()) {
519           MatchResult previous = r;
520           r = matchResult;
521
522           final PsiElement currentElement = r.getMatch();
523
524           if (buf.length() > 0) {
525             final PsiElement parent = currentElement.getParent();
526             if (parent instanceof PsiVariable) {
527               final PsiElement prevSibling = PsiTreeUtil.skipSiblingsBackward(parent, PsiWhiteSpace.class);
528               if (prevSibling instanceof PsiJavaToken && JavaTokenType.COMMA.equals(((PsiJavaToken)prevSibling).getTokenType())) {
529                 buf.append(',');
530               }
531             }
532             else if (info.isStatementContext()) {
533               final PsiElement previousElement = previous.getMatchRef().getElement();
534
535               if (!(previousElement instanceof PsiComment) &&
536                   ( buf.charAt(buf.length() - 1) != '}' ||
537                     previousElement instanceof PsiDeclarationStatement
538                   )
539                 ) {
540                 buf.append(';');
541               }
542
543               final PsiElement prevSibling = currentElement.getPrevSibling();
544
545               if (prevSibling instanceof PsiWhiteSpace &&
546                   prevSibling.getPrevSibling() == previous.getMatch()
547                 ) {
548                 // consequent statements matched so preserve whitespacing
549                 buf.append(prevSibling.getText());
550               }
551               else {
552                 buf.append('\n');
553               }
554             }
555             else if (info.isArgumentContext()) {
556               buf.append(',');
557             }
558             else if (parent instanceof PsiClass) {
559               final PsiElement prevSibling = PsiTreeUtil.skipSiblingsBackward(currentElement, PsiWhiteSpace.class);
560               if (prevSibling instanceof PsiJavaToken && JavaTokenType.COMMA.equals(((PsiJavaToken)prevSibling).getTokenType())) {
561                 buf.append(',');
562               }
563               else {
564                 buf.append('\n');
565               }
566             }
567             else if (parent instanceof PsiReferenceList) {
568               buf.append(',');
569             }
570             else if (parent instanceof PsiPolyadicExpression) {
571               final PsiPolyadicExpression expression = (PsiPolyadicExpression)parent;
572               final PsiJavaToken token = expression.getTokenBeforeOperand(expression.getOperands()[1]);
573               if (token != null) {
574                 buf.append(token.getText());
575               }
576             }
577             else {
578               buf.append(' ');
579             }
580           }
581
582           buf.append(r.getMatchImage());
583           removeExtraSemicolonForSingleVarInstanceInMultipleMatch(info, r, buf);
584           forceAddingNewLine = currentElement instanceof PsiComment;
585         }
586
587         replacementString = buf.toString();
588       } else {
589         if (info.isStatementContext()) {
590           forceAddingNewLine = match.getMatch() instanceof PsiComment;
591         }
592         StringBuilder buf = new StringBuilder(replacementString);
593         removeExtraSemicolonForSingleVarInstanceInMultipleMatch(info, match, buf);
594         replacementString = buf.toString();
595       }
596
597       offset = Replacer.insertSubstitution(result, offset, info, replacementString);
598       offset = removeExtraSemicolon(info, offset, result, match);
599       if (forceAddingNewLine && info.isStatementContext()) {
600         result.insert(info.getStartIndex() + offset + 1, '\n');
601         offset ++;
602       }
603     }
604     return offset;
605   }
606
607   @Override
608   public int handleNoSubstitution(ParameterInfo info, int offset, StringBuilder result) {
609     final PsiElement element = info.getElement();
610     final PsiElement prevSibling = PsiTreeUtil.skipSiblingsBackward(element, PsiWhiteSpace.class);
611     if (prevSibling instanceof PsiJavaToken && isRemovableToken(prevSibling)) {
612       final int start = info.getBeforeDelimiterPos() + offset - (prevSibling.getTextLength() - 1);
613       final int end = info.getStartIndex() + offset;
614       result.delete(start, end);
615       return offset - (end - start);
616     }
617     final PsiElement nextSibling = PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class);
618     if (nextSibling instanceof PsiJavaToken && isRemovableToken(nextSibling)) {
619       final int start = info.getStartIndex() + offset;
620       final int end = info.getAfterDelimiterPos() + nextSibling.getTextLength() + offset;
621       result.delete(start, end);
622       return offset - 1;
623     }
624     if (element == null || !(element.getParent() instanceof PsiForStatement)) {
625       return removeExtraSemicolon(info, offset, result, null);
626     }
627     return offset;
628   }
629
630   private static boolean isRemovableToken(PsiElement element) {
631     final PsiElement parent = element.getParent();
632     if (!(parent instanceof PsiAnnotationParameterList || // ',' between annotation parameters
633           parent instanceof PsiAssertStatement || // ':' before assertion message
634           parent instanceof PsiExpressionList || // ',' between expressions
635           parent instanceof PsiParameterList || // ',' between parameters
636           parent instanceof PsiPolyadicExpression || // '+', '*', '&&' etcetera
637           parent instanceof PsiReferenceList || // ','
638           parent instanceof PsiReferenceParameterList || // ','
639           parent instanceof PsiResourceList || // ';'
640           parent instanceof PsiTypeParameterList || // ','
641           parent instanceof PsiVariable)) { // '=' before initializer
642       return false;
643     }
644     final String text = element.getText();
645     if (text.length() != 1) {
646       return true;
647     }
648     switch(text.charAt(0)) {
649       case '<':
650       case '>':
651       case '(':
652       case ')':
653       case '{':
654       case '}':
655       case '[':
656       case ']':
657         return false;
658       default:
659         return true;
660     }
661   }
662
663   @Override
664   public boolean isIdentifier(PsiElement element) {
665     return element instanceof PsiIdentifier;
666   }
667
668   @Override
669   public Collection<String> getReservedWords() {
670     return Collections.singleton(PsiModifier.PACKAGE_LOCAL);
671   }
672
673   @Override
674   public boolean isDocCommentOwner(PsiElement match) {
675     return match instanceof PsiMember;
676   }
677
678   private static void handleMethodParameter(StringBuilder buf, ParameterInfo info, HashMap<String, MatchResult> matchMap) {
679     if(!(info.getElement() instanceof PsiTypeElement)) {
680       // no specific handling for name of method parameter since it is handled with type
681       return;
682     }
683
684     String name = ((PsiParameter)info.getElement().getParent()).getName();
685     name = StructuralSearchUtil.isTypedVariable(name) ? Replacer.stripTypedVariableDecoration(name):name;
686
687     final MatchResult matchResult = matchMap.get(name);
688     if (matchResult == null) return;
689
690     if (matchResult.isMultipleMatch()) {
691       for (MatchResult result : matchResult.getAllSons()) {
692         if (buf.length() > 0) {
693           buf.append(',');
694         }
695
696         appendParameter(buf, result);
697       }
698     } else {
699       appendParameter(buf, matchResult);
700     }
701   }
702
703   private static void appendParameter(final StringBuilder buf, final MatchResult _matchResult) {
704     for(Iterator<MatchResult> j = _matchResult.getAllSons().iterator();j.hasNext();) {
705       buf.append(j.next().getMatchImage()).append(' ').append(j.next().getMatchImage());
706     }
707   }
708
709   private static void removeExtraSemicolonForSingleVarInstanceInMultipleMatch(final ParameterInfo info, MatchResult r, StringBuilder buf) {
710     if (info.isStatementContext()) {
711       final PsiElement element = r.getMatchRef().getElement();
712
713       // remove extra ;
714       if (buf.charAt(buf.length()-1)==';' &&
715           r.getMatchImage().charAt(r.getMatchImage().length()-1)==';' &&
716           ( element instanceof PsiReturnStatement ||
717             element instanceof PsiDeclarationStatement ||
718             element instanceof PsiExpressionStatement ||
719             element instanceof PsiAssertStatement ||
720             element instanceof PsiBreakStatement ||
721             element instanceof PsiContinueStatement ||
722             element instanceof PsiMember ||
723             element instanceof PsiIfStatement && !(((PsiIfStatement)element).getThenBranch() instanceof PsiBlockStatement) ||
724             element instanceof PsiLoopStatement && !(((PsiLoopStatement)element).getBody() instanceof PsiBlockStatement)
725           )
726         ) {
727         // contains extra ;
728         buf.deleteCharAt(buf.length()-1);
729       }
730     }
731   }
732
733   private static int removeExtraSemicolon(ParameterInfo info, int offset, StringBuilder result, MatchResult match) {
734     if (info.isStatementContext()) {
735       int index = offset+ info.getStartIndex();
736       if (result.charAt(index)==';' &&
737           ( match == null ||
738             ( result.charAt(index-1)=='}' &&
739               !(match.getMatch() instanceof PsiDeclarationStatement) && // array init in dcl
740               !(match.getMatch() instanceof PsiNewExpression) // array initializer
741             ) ||
742             ( !match.isMultipleMatch() &&                                                // ; in comment
743               match.getMatch() instanceof PsiComment
744             ) ||
745             ( match.isMultipleMatch() &&                                                 // ; in comment
746               match.getAllSons().get( match.getAllSons().size() - 1 ).getMatch() instanceof PsiComment
747             )
748           )
749         ) {
750         result.deleteCharAt(index);
751         --offset;
752       }
753     }
754
755     return offset;
756   }
757 }