java & platform: write action is unnecessary in some quick fixes
[idea/community.git] / plugins / java-i18n / src / com / intellij / codeInspection / i18n / I18nInspection.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * @author cdr
19  */
20 package com.intellij.codeInspection.i18n;
21
22 import com.intellij.ToolExtensionPoints;
23 import com.intellij.codeInsight.AnnotationUtil;
24 import com.intellij.codeInsight.CodeInsightBundle;
25 import com.intellij.codeInsight.daemon.GroupNames;
26 import com.intellij.codeInsight.intention.AddAnnotationFix;
27 import com.intellij.codeInspection.*;
28 import com.intellij.codeInspection.ex.BaseLocalInspectionTool;
29 import com.intellij.ide.util.TreeClassChooser;
30 import com.intellij.ide.util.TreeClassChooserFactory;
31 import com.intellij.openapi.editor.Document;
32 import com.intellij.openapi.extensions.ExtensionPoint;
33 import com.intellij.openapi.extensions.Extensions;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.project.ProjectManager;
36 import com.intellij.openapi.ui.DialogWrapper;
37 import com.intellij.openapi.util.Comparing;
38 import com.intellij.openapi.util.InvalidDataException;
39 import com.intellij.openapi.util.WriteExternalException;
40 import com.intellij.openapi.util.text.StringUtil;
41 import com.intellij.psi.*;
42 import com.intellij.psi.search.GlobalSearchScope;
43 import com.intellij.psi.util.MethodSignature;
44 import com.intellij.psi.util.MethodSignatureUtil;
45 import com.intellij.psi.util.PsiTreeUtil;
46 import com.intellij.psi.util.PsiUtil;
47 import com.intellij.refactoring.introduceField.IntroduceConstantHandler;
48 import com.intellij.refactoring.util.RefactoringChangeUtil;
49 import com.intellij.ui.AddDeleteListPanel;
50 import com.intellij.ui.DocumentAdapter;
51 import com.intellij.ui.FieldPanel;
52 import com.intellij.ui.ScrollPaneFactory;
53 import com.intellij.util.ArrayUtil;
54 import com.intellij.util.containers.ContainerUtil;
55 import gnu.trove.THashSet;
56 import org.jdom.Element;
57 import org.jetbrains.annotations.NonNls;
58 import org.jetbrains.annotations.NotNull;
59 import org.jetbrains.annotations.Nullable;
60
61 import javax.swing.*;
62 import javax.swing.event.ChangeEvent;
63 import javax.swing.event.ChangeListener;
64 import javax.swing.event.DocumentEvent;
65 import java.awt.*;
66 import java.awt.event.ActionEvent;
67 import java.awt.event.ActionListener;
68 import java.util.ArrayList;
69 import java.util.HashMap;
70 import java.util.List;
71 import java.util.Set;
72 import java.util.regex.Matcher;
73 import java.util.regex.Pattern;
74
75 public class I18nInspection extends BaseLocalInspectionTool {
76   public boolean ignoreForAssertStatements = true;
77   public boolean ignoreForExceptionConstructors = true;
78   @NonNls
79   public String ignoreForSpecifiedExceptionConstructors = "";
80   public boolean ignoreForJUnitAsserts = true;
81   public boolean ignoreForClassReferences = true;
82   public boolean ignoreForPropertyKeyReferences = true;
83   public boolean ignoreForNonAlpha = true;
84   public boolean ignoreAssignedToConstants;
85   public boolean ignoreToString;
86   @NonNls public String nonNlsCommentPattern = "NON-NLS";
87   private boolean ignoreForEnumConstants;
88
89   private static final LocalQuickFix I18N_QUICK_FIX = new I18nizeQuickFix();
90   private static final LocalQuickFix I18N_CONCATENATION_QUICK_FIX = new I18nizeConcatenationQuickFix();
91
92   @Nullable private Pattern myCachedNonNlsPattern;
93   @NonNls private static final String TO_STRING = "toString";
94
95   public I18nInspection() {
96     cacheNonNlsCommentPattern();
97   }
98
99   @NotNull
100   @Override
101   public SuppressIntentionAction[] getSuppressActions(PsiElement element) {
102     SuppressIntentionAction[] actions = {};
103     if (myCachedNonNlsPattern != null) {
104       actions = new SuppressIntentionAction[]{new SuppressByCommentOutAction(nonNlsCommentPattern)};
105     }
106     return ArrayUtil.mergeArrays(actions, super.getSuppressActions(element));
107   }
108
109   private static final String SKIP_FOR_ENUM = "ignoreForEnumConstant";
110   @Override
111   public void writeSettings(@NotNull Element node) throws WriteExternalException {
112     super.writeSettings(node);
113     if (ignoreForEnumConstants) {
114       final Element e = new Element("option");
115       e.setAttribute("name", SKIP_FOR_ENUM);
116       e.setAttribute("value", Boolean.toString(ignoreForEnumConstants));
117       node.addContent(e);
118     }
119   }
120
121   @Override
122   public void readSettings(@NotNull Element node) throws InvalidDataException {
123     super.readSettings(node);
124     for (Object o : node.getChildren()) {
125       if (o instanceof Element && Comparing.strEqual(((Element)o).getAttributeValue("name"), SKIP_FOR_ENUM)) {
126         final String ignoreForConstantsAttr = ((Element)o).getAttributeValue("value");
127         if (ignoreForConstantsAttr != null) {
128           ignoreForEnumConstants = Boolean.parseBoolean(ignoreForConstantsAttr);
129         }
130         break;
131       }
132     }
133     cacheNonNlsCommentPattern();
134   }
135
136   @Override
137   @NotNull
138   public String getGroupDisplayName() {
139     return GroupNames.INTERNATIONALIZATION_GROUP_NAME;
140   }
141
142   @Override
143   @NotNull
144   public String getDisplayName() {
145     return CodeInsightBundle.message("inspection.i18n.display.name");
146   }
147
148   @Override
149   @NotNull
150   public String getShortName() {
151     return "HardCodedStringLiteral";
152   }
153
154   @Override
155   public JComponent createOptionsPanel() {
156     final GridBagLayout layout = new GridBagLayout();
157     final JPanel panel = new JPanel(layout);
158     final JCheckBox assertStatementsCheckbox = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.assert"), ignoreForAssertStatements);
159     assertStatementsCheckbox.addChangeListener(new ChangeListener() {
160       @Override
161       public void stateChanged(@NotNull ChangeEvent e) {
162         ignoreForAssertStatements = assertStatementsCheckbox.isSelected();
163       }
164     });
165     final JCheckBox exceptionConstructorCheck =
166       new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.for.exception.constructor.arguments"),
167                     ignoreForExceptionConstructors);
168     exceptionConstructorCheck.addChangeListener(new ChangeListener() {
169       @Override
170       public void stateChanged(@NotNull ChangeEvent e) {
171         ignoreForExceptionConstructors = exceptionConstructorCheck.isSelected();
172       }
173     });
174
175     final JTextField specifiedExceptions = new JTextField(ignoreForSpecifiedExceptionConstructors);
176     specifiedExceptions.getDocument().addDocumentListener(new DocumentAdapter(){
177       @Override
178       protected void textChanged(DocumentEvent e) {
179         ignoreForSpecifiedExceptionConstructors = specifiedExceptions.getText();
180       }
181     });
182
183     final JCheckBox junitAssertCheckbox = new JCheckBox(
184       CodeInsightBundle.message("inspection.i18n.option.ignore.for.junit.assert.arguments"), ignoreForJUnitAsserts);
185     junitAssertCheckbox.addChangeListener(new ChangeListener() {
186       @Override
187       public void stateChanged(@NotNull ChangeEvent e) {
188         ignoreForJUnitAsserts = junitAssertCheckbox.isSelected();
189       }
190     });
191     final JCheckBox classRef = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.qualified.class.names"), ignoreForClassReferences);
192     classRef.addChangeListener(new ChangeListener() {
193       @Override
194       public void stateChanged(@NotNull ChangeEvent e) {
195         ignoreForClassReferences = classRef.isSelected();
196       }
197     });
198     final JCheckBox propertyRef = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.property.keys"), ignoreForPropertyKeyReferences);
199     propertyRef.addChangeListener(new ChangeListener() {
200       @Override
201       public void stateChanged(@NotNull ChangeEvent e) {
202         ignoreForPropertyKeyReferences = propertyRef.isSelected();
203       }
204     });
205     final JCheckBox nonAlpha = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.nonalphanumerics"), ignoreForNonAlpha);
206     nonAlpha.addChangeListener(new ChangeListener() {
207       @Override
208       public void stateChanged(@NotNull ChangeEvent e) {
209         ignoreForNonAlpha = nonAlpha.isSelected();
210       }
211     });
212     final JCheckBox assignedToConstants = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.assigned.to.constants"), ignoreAssignedToConstants);
213     assignedToConstants.addChangeListener(new ChangeListener() {
214       @Override
215       public void stateChanged(@NotNull ChangeEvent e) {
216         ignoreAssignedToConstants = assignedToConstants.isSelected();
217       }
218     });
219     final JCheckBox chkToString = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.tostring"), ignoreToString);
220     chkToString.addChangeListener(new ChangeListener() {
221       @Override
222       public void stateChanged(@NotNull ChangeEvent e) {
223         ignoreToString = chkToString.isSelected();
224       }
225     });
226
227     final JCheckBox ignoreEnumConstants = new JCheckBox("Ignore enum constants", ignoreForEnumConstants);
228     ignoreEnumConstants.addChangeListener(new ChangeListener() {
229       @Override
230       public void stateChanged(@NotNull ChangeEvent e) {
231         ignoreForEnumConstants = ignoreEnumConstants.isSelected();
232       }
233     });
234
235     final GridBagConstraints gc = new GridBagConstraints();
236     gc.fill = GridBagConstraints.HORIZONTAL;
237     gc.insets.bottom = 2;
238
239     gc.gridx = GridBagConstraints.REMAINDER;
240     gc.gridy = 0;
241     gc.weightx = 1;
242     gc.weighty = 0;
243     panel.add(assertStatementsCheckbox, gc);
244
245     gc.gridy ++;
246     panel.add(junitAssertCheckbox, gc);
247
248     gc.gridy ++;
249     panel.add(exceptionConstructorCheck, gc);
250
251     gc.gridy ++;
252     final Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
253     panel.add(new FieldPanel(specifiedExceptions,
254                              null,
255                              CodeInsightBundle.message("inspection.i18n.option.ignore.for.specified.exception.constructor.arguments"),
256                              openProjects.length == 0 ? null :
257                              new ActionListener() {
258                                @Override
259                                public void actionPerformed(@NotNull ActionEvent e) {
260                                  createIgnoreExceptionsConfigurationDialog(openProjects[0], specifiedExceptions).show();
261                                }
262                              },
263                              null), gc);
264
265     gc.gridy ++;
266     panel.add(classRef, gc);
267
268     gc.gridy ++;
269     panel.add(propertyRef, gc);
270
271     gc.gridy++;
272     panel.add(assignedToConstants, gc);
273
274     gc.gridy++;
275     panel.add(chkToString, gc);
276
277     gc.gridy ++;
278     panel.add(nonAlpha, gc);
279
280     gc.gridy ++;
281     panel.add(ignoreEnumConstants, gc);
282
283     gc.gridy ++;
284     gc.anchor = GridBagConstraints.NORTHWEST;
285     gc.weighty = 1;
286     final JTextField text = new JTextField(nonNlsCommentPattern);
287     final FieldPanel nonNlsCommentPatternComponent =
288       new FieldPanel(text, CodeInsightBundle.message("inspection.i18n.option.ignore.comment.pattern"),
289                      CodeInsightBundle.message("inspection.i18n.option.ignore.comment.title"), null, () -> {
290                        nonNlsCommentPattern = text.getText();
291                        cacheNonNlsCommentPattern();
292                      });
293     panel.add(nonNlsCommentPatternComponent, gc);
294
295     final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(panel);
296     scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
297     scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
298     scrollPane.setBorder(null);
299     scrollPane.setPreferredSize(new Dimension(panel.getPreferredSize().width + scrollPane.getVerticalScrollBar().getPreferredSize().width,
300                                               panel.getPreferredSize().height +
301                                               scrollPane.getHorizontalScrollBar().getPreferredSize().height));
302     return scrollPane;
303   }
304
305   @SuppressWarnings("NonStaticInitializer")
306   private DialogWrapper createIgnoreExceptionsConfigurationDialog(final Project project, final JTextField specifiedExceptions) {
307     return new DialogWrapper(true) {
308       private AddDeleteListPanel myPanel;
309       {
310         setTitle(CodeInsightBundle.message(
311           "inspection.i18n.option.ignore.for.specified.exception.constructor.arguments"));
312         init();
313       }
314
315       @Override
316       protected JComponent createCenterPanel() {
317         final String[] ignored = ignoreForSpecifiedExceptionConstructors.split(",");
318         final List<String> initialList = new ArrayList<>();
319         for (String e : ignored) {
320           if (!e.isEmpty()) initialList.add(e);
321         }
322         myPanel = new AddDeleteListPanel<String>(null, initialList) {
323           @Override
324           protected String findItemToAdd() {
325             final GlobalSearchScope scope = GlobalSearchScope.allScope(project);
326             TreeClassChooser chooser = TreeClassChooserFactory.getInstance(project).
327               createInheritanceClassChooser(
328                 CodeInsightBundle.message("inspection.i18n.option.ignore.for.specified.exception.constructor.arguments"), scope,
329                 JavaPsiFacade.getInstance(project).findClass("java.lang.Throwable", scope), true, true, null);
330             chooser.showDialog();
331             PsiClass selectedClass = chooser.getSelected();
332             return selectedClass != null ? selectedClass.getQualifiedName() : null;
333           }
334         };
335         return myPanel;
336       }
337
338       @Override
339       protected void doOKAction() {
340         StringBuilder buf = new StringBuilder();
341         final Object[] exceptions = myPanel.getListItems();
342         for (Object exception : exceptions) {
343           buf.append(",").append(exception);
344         }
345         specifiedExceptions.setText(buf.length() > 0 ? buf.substring(1) : buf.toString());
346         super.doOKAction();
347       }
348     };
349   }
350
351   @Override
352   @Nullable
353   public ProblemDescriptor[] checkMethod(@NotNull PsiMethod method, @NotNull InspectionManager manager, boolean isOnTheFly) {
354     PsiClass containingClass = method.getContainingClass();
355     if (containingClass == null || isClassNonNls(containingClass)) {
356       return null;
357     }
358     final PsiCodeBlock body = method.getBody();
359     if (body != null) {
360       return checkElement(body, manager, isOnTheFly);
361     }
362     return null;
363   }
364
365   @Override
366   @Nullable
367   public ProblemDescriptor[] checkClass(@NotNull PsiClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) {
368     if (isClassNonNls(aClass)) {
369       return null;
370     }
371     final PsiClassInitializer[] initializers = aClass.getInitializers();
372     List<ProblemDescriptor> result = new ArrayList<>();
373     for (PsiClassInitializer initializer : initializers) {
374       final ProblemDescriptor[] descriptors = checkElement(initializer, manager, isOnTheFly);
375       if (descriptors != null) {
376         ContainerUtil.addAll(result, descriptors);
377       }
378     }
379
380     return result.isEmpty() ? null : result.toArray(new ProblemDescriptor[result.size()]);
381   }
382
383   @Override
384   @Nullable
385   public ProblemDescriptor[] checkField(@NotNull PsiField field, @NotNull InspectionManager manager, boolean isOnTheFly) {
386     PsiClass containingClass = field.getContainingClass();
387     if (containingClass == null || isClassNonNls(containingClass)) {
388       return null;
389     }
390     if (AnnotationUtil.isAnnotated(field, AnnotationUtil.NON_NLS, false, false)) {
391       return null;
392     }
393     final PsiExpression initializer = field.getInitializer();
394     if (initializer != null) return checkElement(initializer, manager, isOnTheFly);
395
396     if (field instanceof PsiEnumConstant) {
397       return checkElement(((PsiEnumConstant)field).getArgumentList(), manager, isOnTheFly);
398     }
399     return null;
400   }
401
402   @Nullable
403   @Override
404   public String getAlternativeID() {
405     return "nls";
406   }
407
408   @Override
409   @Nullable
410   public ProblemDescriptor[] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) {
411     ExtensionPoint<FileCheckingInspection> point = Extensions.getRootArea().getExtensionPoint(ToolExtensionPoints.I18N_INSPECTION_TOOL);
412     final FileCheckingInspection[] fileCheckingInspections = point.getExtensions();
413     for(FileCheckingInspection obj: fileCheckingInspections) {
414       ProblemDescriptor[] descriptors = obj.checkFile(file, manager, isOnTheFly);
415       if (descriptors != null) {
416         return descriptors;
417       }
418     }
419
420     return null;
421   }
422
423   private ProblemDescriptor[] checkElement(@NotNull PsiElement element, @NotNull InspectionManager manager, boolean isOnTheFly) {
424     StringI18nVisitor visitor = new StringI18nVisitor(manager, isOnTheFly);
425     element.accept(visitor);
426     List<ProblemDescriptor> problems = visitor.getProblems();
427     return problems.isEmpty() ? null : problems.toArray(new ProblemDescriptor[problems.size()]);
428   }
429
430   @NotNull
431   private static LocalQuickFix createIntroduceConstantFix() {
432     return new LocalQuickFix() {
433       @Override
434       @NotNull
435       public String getFamilyName() {
436         return IntroduceConstantHandler.REFACTORING_NAME;
437       }
438
439       @Override
440       public boolean startInWriteAction() {
441         return false;
442       }
443
444       @Override
445       public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
446         PsiElement element = descriptor.getPsiElement();
447         if (!(element instanceof PsiExpression)) return;
448
449         PsiExpression[] expressions = {(PsiExpression)element};
450         new IntroduceConstantHandler().invoke(project, expressions);
451       }
452     };
453   }
454
455   private class StringI18nVisitor extends JavaRecursiveElementWalkingVisitor {
456     private final List<ProblemDescriptor> myProblems = new ArrayList<>();
457     private final InspectionManager myManager;
458     private final boolean myOnTheFly;
459
460     private StringI18nVisitor(@NotNull InspectionManager manager, boolean onTheFly) {
461       myManager = manager;
462       myOnTheFly = onTheFly;
463     }
464
465     @Override
466     public void visitAnonymousClass(PsiAnonymousClass aClass) {
467       visitElement(aClass); // visit argument list but anon. class members should be not visited
468     }
469
470     @Override
471     public void visitClass(PsiClass aClass) {
472     }
473
474     @Override
475     public void visitField(PsiField field) {
476     }
477
478     @Override
479     public void visitMethod(PsiMethod method) {
480     }
481
482     @Override
483     public void visitLiteralExpression(PsiLiteralExpression expression) {
484       Object value = expression.getValue();
485       if (!(value instanceof String)) return;
486       String stringValue = (String)value;
487       if (stringValue.trim().isEmpty()) {
488         return;
489       }
490
491       Set<PsiModifierListOwner> nonNlsTargets = new THashSet<>();
492       if (canBeI18ned(myManager.getProject(), expression, stringValue, nonNlsTargets)) {
493         PsiField parentField = PsiTreeUtil.getParentOfType(expression, PsiField.class);
494         if (parentField != null) {
495           nonNlsTargets.add(parentField);
496         }
497
498         final String description = CodeInsightBundle.message("inspection.i18n.message.general.with.value", "#ref");
499
500         List<LocalQuickFix> fixes = new ArrayList<>();
501         if (I18nizeConcatenationQuickFix.getEnclosingLiteralConcatenation(expression) != null) {
502           fixes.add(I18N_CONCATENATION_QUICK_FIX);
503         }
504         fixes.add(I18N_QUICK_FIX);
505
506         if (!isNotConstantFieldInitializer(expression)) {
507           fixes.add(createIntroduceConstantFix());
508         }
509
510         final Project project = expression.getManager().getProject();
511         final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
512         if (PsiUtil.isLanguageLevel5OrHigher(expression)) {
513           for (PsiModifierListOwner element : nonNlsTargets) {
514             if (!AnnotationUtil.isAnnotated(element, AnnotationUtil.NLS, true, false)) {
515               if (!element.getManager().isInProject(element) ||
516                   facade.findClass(AnnotationUtil.NON_NLS, element.getResolveScope()) != null) {
517                 fixes.add(new AddAnnotationFix(AnnotationUtil.NON_NLS, element));
518               }
519             }
520           }
521         }
522
523         LocalQuickFix[] farr = fixes.toArray(new LocalQuickFix[fixes.size()]);
524         final ProblemDescriptor problem = myManager.createProblemDescriptor(expression,
525                                                                             description, myOnTheFly, farr,
526                                                                             ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
527         myProblems.add(problem);
528       }
529     }
530
531     private boolean isNotConstantFieldInitializer(final PsiExpression expression) {
532       PsiField parentField = expression.getParent() instanceof PsiField ? (PsiField)expression.getParent() : null;
533       return parentField != null && expression == parentField.getInitializer() &&
534              parentField.hasModifierProperty(PsiModifier.FINAL) &&
535              parentField.hasModifierProperty(PsiModifier.STATIC);
536     }
537
538
539     @Override
540     public void visitAnnotation(PsiAnnotation annotation) {
541       //prevent from @SuppressWarnings
542       if (!BatchSuppressManager.SUPPRESS_INSPECTIONS_ANNOTATION_NAME.equals(annotation.getQualifiedName())) {
543         super.visitAnnotation(annotation);
544       }
545     }
546
547     private List<ProblemDescriptor> getProblems() {
548       return myProblems;
549     }
550   }
551
552   private boolean canBeI18ned(@NotNull Project project,
553                               @NotNull PsiLiteralExpression expression,
554                               @NotNull String value,
555                               @NotNull Set<PsiModifierListOwner> nonNlsTargets) {
556     if (ignoreForNonAlpha && !StringUtil.containsAlphaCharacters(value)) {
557       return false;
558     }
559
560     if (JavaI18nUtil.isPassedToAnnotatedParam(expression, AnnotationUtil.NON_NLS, new HashMap<>(), nonNlsTargets)) {
561       return false;
562     }
563
564     if (isInNonNlsCall(expression, nonNlsTargets)) {
565       return false;
566     }
567
568     if (isInNonNlsEquals(expression, nonNlsTargets)) {
569       return false;
570     }
571
572     if (isPassedToNonNlsVariable(expression, nonNlsTargets)) {
573       return false;
574     }
575
576     if (JavaI18nUtil.mustBePropertyKey(expression, new HashMap<>())) {
577       return false;
578     }
579
580     if (isReturnedFromNonNlsMethod(expression, nonNlsTargets)) {
581       return false;
582     }
583     if (ignoreForAssertStatements && isArgOfAssertStatement(expression)) {
584       return false;
585     }
586     if (ignoreForExceptionConstructors && isArgOfExceptionConstructor(expression)) {
587       return false;
588     }
589     if (ignoreForEnumConstants && isArgOfEnumConstant(expression)) {
590       return false;
591     }
592     if (!ignoreForExceptionConstructors && isArgOfSpecifiedExceptionConstructor(expression, ignoreForSpecifiedExceptionConstructors.split(","))) {
593       return false;
594     }
595     if (ignoreForJUnitAsserts && isArgOfJUnitAssertion(expression)) {
596       return false;
597     }
598     if (ignoreForClassReferences && isClassRef(expression, value)) {
599       return false;
600     }
601     if (ignoreForPropertyKeyReferences && JavaI18nUtil.isPropertyRef(expression, value, null)) {
602       return false;
603     }
604     if (ignoreToString && isToString(expression)) {
605       return false;
606     }
607
608     Pattern pattern = myCachedNonNlsPattern;
609     if (pattern != null) {
610       PsiFile file = expression.getContainingFile();
611       Document document = PsiDocumentManager.getInstance(project).getDocument(file);
612       int line = document.getLineNumber(expression.getTextRange().getStartOffset());
613       int lineStartOffset = document.getLineStartOffset(line);
614       CharSequence lineText = document.getCharsSequence().subSequence(lineStartOffset, document.getLineEndOffset(line));
615
616       Matcher matcher = pattern.matcher(lineText);
617       int start = 0;
618       while (matcher.find(start)) {
619         start = matcher.start();
620         PsiElement element = file.findElementAt(lineStartOffset + start);
621         if (PsiTreeUtil.getParentOfType(element, PsiComment.class, false) != null) return false;
622         if (start == lineText.length() - 1) break;
623         start++;
624       }
625     }
626
627     return true;
628   }
629
630   private static boolean isArgOfEnumConstant(PsiLiteralExpression expression) {
631     final PsiElement parent = PsiTreeUtil.getParentOfType(expression, PsiExpressionList.class, PsiClass.class);
632     if (!(parent instanceof PsiExpressionList)) {
633       return false;
634     }
635     final PsiElement grandparent = parent.getParent();
636     return grandparent instanceof PsiEnumConstant;
637   }
638
639   public void cacheNonNlsCommentPattern() {
640     myCachedNonNlsPattern = nonNlsCommentPattern.trim().isEmpty() ? null : Pattern.compile(nonNlsCommentPattern);
641   }
642
643   private static boolean isClassRef(final PsiLiteralExpression expression, String value) {
644     if (StringUtil.startsWithChar(value,'#')) {
645       value = value.substring(1); // A favor for JetBrains team to catch common Logger usage practice.
646     }
647
648     return JavaPsiFacade.getInstance(expression.getProject()).findClass(value, GlobalSearchScope.allScope(expression.getProject())) != null;
649   }
650
651   @Override
652   public boolean isEnabledByDefault() {
653     return false;
654   }
655
656   private static boolean isClassNonNls(@NotNull PsiClass clazz) {
657     final PsiDirectory directory = clazz.getContainingFile().getContainingDirectory();
658     return directory != null && isPackageNonNls(JavaDirectoryService.getInstance().getPackage(directory));
659   }
660
661   public static boolean isPackageNonNls(final PsiPackage psiPackage) {
662     if (psiPackage == null || psiPackage.getName() == null) {
663       return false;
664     }
665     final PsiModifierList pkgModifierList = psiPackage.getAnnotationList();
666     return pkgModifierList != null && pkgModifierList.findAnnotation(AnnotationUtil.NON_NLS) != null
667            || isPackageNonNls(psiPackage.getParentPackage());
668   }
669
670   private boolean isPassedToNonNlsVariable(@NotNull PsiLiteralExpression expression,
671                                            final Set<PsiModifierListOwner> nonNlsTargets) {
672     PsiExpression toplevel = JavaI18nUtil.getTopLevelExpression(expression);
673     PsiVariable var = null;
674     if (toplevel instanceof PsiAssignmentExpression) {
675       PsiExpression lExpression = ((PsiAssignmentExpression)toplevel).getLExpression();
676       while (lExpression instanceof PsiArrayAccessExpression) {
677         lExpression = ((PsiArrayAccessExpression)lExpression).getArrayExpression();
678       }
679       if (lExpression instanceof PsiReferenceExpression) {
680         final PsiElement resolved = ((PsiReferenceExpression)lExpression).resolve();
681         if (resolved instanceof PsiVariable) var = (PsiVariable)resolved;
682       }
683     }
684
685     if (var == null) {
686       PsiElement parent = toplevel.getParent();
687       if (parent instanceof PsiVariable && toplevel.equals(((PsiVariable)parent).getInitializer())) {
688         var = (PsiVariable)parent;
689       } else if (parent instanceof PsiSwitchLabelStatement) {
690         final PsiSwitchStatement switchStatement = ((PsiSwitchLabelStatement)parent).getEnclosingSwitchStatement();
691         if (switchStatement != null) {
692           final PsiExpression switchStatementExpression = switchStatement.getExpression();
693           if (switchStatementExpression instanceof PsiReferenceExpression) {
694             final PsiElement resolved = ((PsiReferenceExpression)switchStatementExpression).resolve();
695             if (resolved instanceof PsiVariable) var = (PsiVariable)resolved;
696           }
697         }
698       }
699     }
700
701     if (var != null) {
702       if (annotatedAsNonNls(var)) {
703         return true;
704       }
705       if (ignoreAssignedToConstants &&
706           var.hasModifierProperty(PsiModifier.STATIC) &&
707           var.hasModifierProperty(PsiModifier.FINAL)) {
708         return true;
709       }
710       nonNlsTargets.add(var);
711     }
712     return false;
713   }
714
715   private static boolean annotatedAsNonNls(final PsiModifierListOwner parent) {
716     if (parent instanceof PsiParameter) {
717       final PsiParameter parameter = (PsiParameter)parent;
718       final PsiElement declarationScope = parameter.getDeclarationScope();
719       if (declarationScope instanceof PsiMethod) {
720         final PsiMethod method = (PsiMethod)declarationScope;
721         final int index = method.getParameterList().getParameterIndex(parameter);
722         return JavaI18nUtil.isMethodParameterAnnotatedWith(method, index, null, AnnotationUtil.NON_NLS, null, null);
723       }
724     }
725     return AnnotationUtil.isAnnotated(parent, AnnotationUtil.NON_NLS, false, false);
726   }
727
728   private static boolean isInNonNlsEquals(PsiExpression expression, final Set<PsiModifierListOwner> nonNlsTargets) {
729     if (!(expression.getParent().getParent() instanceof PsiMethodCallExpression)) {
730       return false;
731     }
732     final PsiMethodCallExpression call = (PsiMethodCallExpression)expression.getParent().getParent();
733     final PsiReferenceExpression methodExpression = call.getMethodExpression();
734     final PsiExpression qualifier = methodExpression.getQualifierExpression();
735     if (qualifier != expression) {
736       return false;
737     }
738     if (!"equals".equals(methodExpression.getReferenceName())) {
739       return false;
740     }
741     final PsiElement resolved = methodExpression.resolve();
742     if (!(resolved instanceof PsiMethod)) {
743       return false;
744     }
745     PsiType objectType = PsiType.getJavaLangObject(resolved.getManager(), resolved.getResolveScope());
746     MethodSignature equalsSignature = MethodSignatureUtil.createMethodSignature("equals",
747                                                                                 new PsiType[]{objectType},
748                                                                                 PsiTypeParameter.EMPTY_ARRAY,
749                                                                                 PsiSubstitutor.EMPTY);
750     if (!equalsSignature.equals(((PsiMethod)resolved).getSignature(PsiSubstitutor.EMPTY))) {
751       return false;
752     }
753     final PsiExpression[] expressions = call.getArgumentList().getExpressions();
754     if (expressions.length != 1) {
755       return false;
756     }
757     final PsiExpression arg = expressions[0];
758     PsiReferenceExpression ref = null;
759     if (arg instanceof PsiReferenceExpression) {
760       ref = (PsiReferenceExpression)arg;
761     }
762     else if (arg instanceof PsiMethodCallExpression) ref = ((PsiMethodCallExpression)arg).getMethodExpression();
763     if (ref != null) {
764       final PsiElement resolvedEntity = ref.resolve();
765       if (resolvedEntity instanceof PsiModifierListOwner) {
766         PsiModifierListOwner modifierListOwner = (PsiModifierListOwner)resolvedEntity;
767         if (annotatedAsNonNls(modifierListOwner)) {
768           return true;
769         }
770         nonNlsTargets.add(modifierListOwner);
771       }
772     }
773     return false;
774   }
775
776   private static boolean isInNonNlsCall(@NotNull PsiExpression expression,
777                                         final Set<PsiModifierListOwner> nonNlsTargets) {
778     expression = JavaI18nUtil.getTopLevelExpression(expression);
779     final PsiElement parent = expression.getParent();
780     if (parent instanceof PsiExpressionList) {
781       final PsiElement grParent = parent.getParent();
782       if (grParent instanceof PsiMethodCallExpression) {
783         return isNonNlsCall((PsiMethodCallExpression)grParent, nonNlsTargets);
784       }
785       else if (grParent instanceof PsiNewExpression) {
786         final PsiElement parentOfNew = grParent.getParent();
787         if (parentOfNew instanceof PsiLocalVariable) {
788           final PsiLocalVariable newVariable = (PsiLocalVariable)parentOfNew;
789           if (annotatedAsNonNls(newVariable)) {
790             return true;
791           }
792           nonNlsTargets.add(newVariable);
793           return false;
794         }
795         else if (parentOfNew instanceof PsiAssignmentExpression) {
796           final PsiExpression lExpression = ((PsiAssignmentExpression)parentOfNew).getLExpression();
797           if (lExpression instanceof PsiReferenceExpression) {
798             final PsiElement resolved = ((PsiReferenceExpression)lExpression).resolve();
799             if (resolved instanceof PsiModifierListOwner) {
800               final PsiModifierListOwner modifierListOwner = (PsiModifierListOwner)resolved;
801               if (annotatedAsNonNls(modifierListOwner)) {
802                 return true;
803               }
804               nonNlsTargets.add(modifierListOwner);
805               return false;
806             }
807           }
808         }
809       }
810     }
811
812     return false;
813   }
814
815   private static boolean isNonNlsCall(PsiMethodCallExpression grParent, Set<PsiModifierListOwner> nonNlsTargets) {
816     final PsiReferenceExpression methodExpression = grParent.getMethodExpression();
817     final PsiExpression qualifier = methodExpression.getQualifierExpression();
818     if (qualifier instanceof PsiReferenceExpression) {
819       final PsiElement resolved = ((PsiReferenceExpression)qualifier).resolve();
820       if (resolved instanceof PsiModifierListOwner) {
821         final PsiModifierListOwner modifierListOwner = (PsiModifierListOwner)resolved;
822         if (annotatedAsNonNls(modifierListOwner)) {
823           return true;
824         }
825         nonNlsTargets.add(modifierListOwner);
826         return false;
827       }
828     } else if (qualifier instanceof PsiMethodCallExpression) {
829       final PsiType type = qualifier.getType();
830       if (type != null && type.equals(methodExpression.getType())) {
831         return isNonNlsCall((PsiMethodCallExpression)qualifier, nonNlsTargets);
832       }
833     }
834     return false;
835   }
836
837   private static boolean isReturnedFromNonNlsMethod(final PsiLiteralExpression expression, final Set<PsiModifierListOwner> nonNlsTargets) {
838     PsiElement parent = expression.getParent();
839     PsiMethod method;
840     if (parent instanceof PsiNameValuePair) {
841       method = AnnotationUtil.getAnnotationMethod((PsiNameValuePair)parent);
842     }
843     else {
844       final PsiElement returnStmt = PsiTreeUtil.getParentOfType(expression, PsiReturnStatement.class, PsiMethodCallExpression.class);
845       if (!(returnStmt instanceof PsiReturnStatement)) {
846         return false;
847       }
848       method = PsiTreeUtil.getParentOfType(expression, PsiMethod.class);
849     }
850     if (method == null) return false;
851
852     if (AnnotationUtil.isAnnotated(method, AnnotationUtil.NON_NLS, true, false)) {
853       return true;
854     }
855     nonNlsTargets.add(method);
856     return false;
857   }
858
859   private static boolean isToString(final PsiLiteralExpression expression) {
860     final PsiMethod method = PsiTreeUtil.getParentOfType(expression, PsiMethod.class);
861     if (method == null) return false;
862     final PsiType returnType = method.getReturnType();
863     return TO_STRING.equals(method.getName())
864            && method.getParameterList().getParametersCount() == 0
865            && returnType != null
866            && "java.lang.String".equals(returnType.getCanonicalText());
867   }
868
869   private static boolean isArgOfJUnitAssertion(PsiExpression expression) {
870     final PsiElement parent = expression.getParent();
871     if (!(parent instanceof PsiExpressionList)) {
872       return false;
873     }
874     final PsiElement grandparent = parent.getParent();
875     if (!(grandparent instanceof PsiMethodCallExpression)) {
876       return false;
877     }
878     final PsiMethodCallExpression call = (PsiMethodCallExpression)grandparent;
879     final PsiReferenceExpression methodExpression = call.getMethodExpression();
880     @NonNls final String methodName = methodExpression.getReferenceName();
881     if (methodName == null) {
882       return false;
883     }
884
885     if (!methodName.startsWith("assert") && !methodName.equals("fail")) {
886       return false;
887     }
888     final PsiMethod method = call.resolveMethod();
889     if (method == null) {
890       return false;
891     }
892     final PsiClass containingClass = method.getContainingClass();
893     if (containingClass == null) {
894       return false;
895     }
896     final Project project = expression.getProject();
897     final GlobalSearchScope scope = GlobalSearchScope.allScope(project);
898     final PsiClass junitAssert = JavaPsiFacade.getInstance(project).findClass("junit.framework.Assert", scope);
899     return junitAssert != null && !containingClass.isInheritor(junitAssert, true);
900   }
901
902   private static boolean isArgOfExceptionConstructor(PsiExpression expression) {
903     final PsiElement parent = PsiTreeUtil.getParentOfType(expression, PsiExpressionList.class, PsiClass.class);
904     if (!(parent instanceof PsiExpressionList)) {
905       return false;
906     }
907     final PsiElement grandparent = parent.getParent();
908     final PsiClass aClass;
909     if (RefactoringChangeUtil.isSuperOrThisMethodCall(grandparent)) {
910       final PsiMethod method = ((PsiMethodCallExpression)grandparent).resolveMethod();
911       if (method != null) {
912         aClass = method.getContainingClass();
913       } else {
914         return false;
915       }
916     } else {
917       if (!(grandparent instanceof PsiNewExpression)) {
918         return false;
919       }
920       final PsiJavaCodeReferenceElement reference = ((PsiNewExpression)grandparent).getClassReference();
921       if (reference == null) {
922         return false;
923       }
924       final PsiElement referent = reference.resolve();
925       if (!(referent instanceof PsiClass)) {
926         return false;
927       }
928
929       aClass = (PsiClass)referent;
930     }
931     final Project project = expression.getProject();
932     final GlobalSearchScope scope = GlobalSearchScope.allScope(project);
933     final PsiClass throwable = JavaPsiFacade.getInstance(project).findClass(CommonClassNames.JAVA_LANG_THROWABLE, scope);
934     return throwable != null && aClass.isInheritor(throwable, true);
935   }
936
937   private static boolean isArgOfSpecifiedExceptionConstructor(PsiExpression expression, String[] specifiedExceptions) {
938     if (specifiedExceptions.length == 0) return false;
939
940     final PsiElement parent = PsiTreeUtil.getParentOfType(expression, PsiExpressionList.class, PsiClass.class);
941     if (!(parent instanceof PsiExpressionList)) {
942       return false;
943     }
944     final PsiElement grandparent = parent.getParent();
945     if (!(grandparent instanceof PsiNewExpression)) {
946       return false;
947     }
948     final PsiJavaCodeReferenceElement reference =
949       ((PsiNewExpression)grandparent).getClassReference();
950     if (reference == null) {
951       return false;
952     }
953     final PsiElement referent = reference.resolve();
954     if (!(referent instanceof PsiClass)) {
955       return false;
956     }
957     final PsiClass aClass = (PsiClass)referent;
958
959     for (String specifiedException : specifiedExceptions) {
960       if (specifiedException.equals(aClass.getQualifiedName())) return true;
961
962     }
963
964     return false;
965   }
966
967   private static boolean isArgOfAssertStatement(PsiExpression expression) {
968     return PsiTreeUtil.getParentOfType(expression, PsiAssertStatement.class, PsiClass.class) instanceof PsiAssertStatement;
969   }
970
971
972 }