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