2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 package com.intellij.codeInspection.i18n;
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;
62 import javax.swing.event.ChangeEvent;
63 import javax.swing.event.ChangeListener;
64 import javax.swing.event.DocumentEvent;
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;
72 import java.util.regex.Matcher;
73 import java.util.regex.Pattern;
75 public class I18nInspection extends BaseLocalInspectionTool {
76 public boolean ignoreForAssertStatements = true;
77 public boolean ignoreForExceptionConstructors = true;
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;
89 private static final LocalQuickFix I18N_QUICK_FIX = new I18nizeQuickFix();
90 private static final LocalQuickFix I18N_CONCATENATION_QUICK_FIX = new I18nizeConcatenationQuickFix();
92 @Nullable private Pattern myCachedNonNlsPattern;
93 @NonNls private static final String TO_STRING = "toString";
95 public I18nInspection() {
96 cacheNonNlsCommentPattern();
101 public SuppressIntentionAction[] getSuppressActions(PsiElement element) {
102 SuppressIntentionAction[] actions = {};
103 if (myCachedNonNlsPattern != null) {
104 actions = new SuppressIntentionAction[]{new SuppressByCommentOutAction(nonNlsCommentPattern)};
106 return ArrayUtil.mergeArrays(actions, super.getSuppressActions(element));
109 private static final String SKIP_FOR_ENUM = "ignoreForEnumConstant";
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));
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);
133 cacheNonNlsCommentPattern();
138 public String getGroupDisplayName() {
139 return GroupNames.INTERNATIONALIZATION_GROUP_NAME;
144 public String getDisplayName() {
145 return CodeInsightBundle.message("inspection.i18n.display.name");
150 public String getShortName() {
151 return "HardCodedStringLiteral";
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() {
161 public void stateChanged(@NotNull ChangeEvent e) {
162 ignoreForAssertStatements = assertStatementsCheckbox.isSelected();
165 final JCheckBox exceptionConstructorCheck =
166 new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.for.exception.constructor.arguments"),
167 ignoreForExceptionConstructors);
168 exceptionConstructorCheck.addChangeListener(new ChangeListener() {
170 public void stateChanged(@NotNull ChangeEvent e) {
171 ignoreForExceptionConstructors = exceptionConstructorCheck.isSelected();
175 final JTextField specifiedExceptions = new JTextField(ignoreForSpecifiedExceptionConstructors);
176 specifiedExceptions.getDocument().addDocumentListener(new DocumentAdapter(){
178 protected void textChanged(DocumentEvent e) {
179 ignoreForSpecifiedExceptionConstructors = specifiedExceptions.getText();
183 final JCheckBox junitAssertCheckbox = new JCheckBox(
184 CodeInsightBundle.message("inspection.i18n.option.ignore.for.junit.assert.arguments"), ignoreForJUnitAsserts);
185 junitAssertCheckbox.addChangeListener(new ChangeListener() {
187 public void stateChanged(@NotNull ChangeEvent e) {
188 ignoreForJUnitAsserts = junitAssertCheckbox.isSelected();
191 final JCheckBox classRef = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.qualified.class.names"), ignoreForClassReferences);
192 classRef.addChangeListener(new ChangeListener() {
194 public void stateChanged(@NotNull ChangeEvent e) {
195 ignoreForClassReferences = classRef.isSelected();
198 final JCheckBox propertyRef = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.property.keys"), ignoreForPropertyKeyReferences);
199 propertyRef.addChangeListener(new ChangeListener() {
201 public void stateChanged(@NotNull ChangeEvent e) {
202 ignoreForPropertyKeyReferences = propertyRef.isSelected();
205 final JCheckBox nonAlpha = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.nonalphanumerics"), ignoreForNonAlpha);
206 nonAlpha.addChangeListener(new ChangeListener() {
208 public void stateChanged(@NotNull ChangeEvent e) {
209 ignoreForNonAlpha = nonAlpha.isSelected();
212 final JCheckBox assignedToConstants = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.assigned.to.constants"), ignoreAssignedToConstants);
213 assignedToConstants.addChangeListener(new ChangeListener() {
215 public void stateChanged(@NotNull ChangeEvent e) {
216 ignoreAssignedToConstants = assignedToConstants.isSelected();
219 final JCheckBox chkToString = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.tostring"), ignoreToString);
220 chkToString.addChangeListener(new ChangeListener() {
222 public void stateChanged(@NotNull ChangeEvent e) {
223 ignoreToString = chkToString.isSelected();
227 final JCheckBox ignoreEnumConstants = new JCheckBox("Ignore enum constants", ignoreForEnumConstants);
228 ignoreEnumConstants.addChangeListener(new ChangeListener() {
230 public void stateChanged(@NotNull ChangeEvent e) {
231 ignoreForEnumConstants = ignoreEnumConstants.isSelected();
235 final GridBagConstraints gc = new GridBagConstraints();
236 gc.fill = GridBagConstraints.HORIZONTAL;
237 gc.insets.bottom = 2;
239 gc.gridx = GridBagConstraints.REMAINDER;
243 panel.add(assertStatementsCheckbox, gc);
246 panel.add(junitAssertCheckbox, gc);
249 panel.add(exceptionConstructorCheck, gc);
252 final Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
253 panel.add(new FieldPanel(specifiedExceptions,
255 CodeInsightBundle.message("inspection.i18n.option.ignore.for.specified.exception.constructor.arguments"),
256 openProjects.length == 0 ? null :
257 new ActionListener() {
259 public void actionPerformed(@NotNull ActionEvent e) {
260 createIgnoreExceptionsConfigurationDialog(openProjects[0], specifiedExceptions).show();
266 panel.add(classRef, gc);
269 panel.add(propertyRef, gc);
272 panel.add(assignedToConstants, gc);
275 panel.add(chkToString, gc);
278 panel.add(nonAlpha, gc);
281 panel.add(ignoreEnumConstants, gc);
284 gc.anchor = GridBagConstraints.NORTHWEST;
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();
293 panel.add(nonNlsCommentPatternComponent, gc);
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));
305 @SuppressWarnings("NonStaticInitializer")
306 private DialogWrapper createIgnoreExceptionsConfigurationDialog(final Project project, final JTextField specifiedExceptions) {
307 return new DialogWrapper(true) {
308 private AddDeleteListPanel myPanel;
310 setTitle(CodeInsightBundle.message(
311 "inspection.i18n.option.ignore.for.specified.exception.constructor.arguments"));
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);
322 myPanel = new AddDeleteListPanel<String>(null, initialList) {
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;
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);
345 specifiedExceptions.setText(buf.length() > 0 ? buf.substring(1) : buf.toString());
353 public ProblemDescriptor[] checkMethod(@NotNull PsiMethod method, @NotNull InspectionManager manager, boolean isOnTheFly) {
354 PsiClass containingClass = method.getContainingClass();
355 if (containingClass == null || isClassNonNls(containingClass)) {
358 final PsiCodeBlock body = method.getBody();
360 return checkElement(body, manager, isOnTheFly);
367 public ProblemDescriptor[] checkClass(@NotNull PsiClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) {
368 if (isClassNonNls(aClass)) {
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);
380 return result.isEmpty() ? null : result.toArray(new ProblemDescriptor[result.size()]);
385 public ProblemDescriptor[] checkField(@NotNull PsiField field, @NotNull InspectionManager manager, boolean isOnTheFly) {
386 PsiClass containingClass = field.getContainingClass();
387 if (containingClass == null || isClassNonNls(containingClass)) {
390 if (AnnotationUtil.isAnnotated(field, AnnotationUtil.NON_NLS, false, false)) {
393 final PsiExpression initializer = field.getInitializer();
394 if (initializer != null) return checkElement(initializer, manager, isOnTheFly);
396 if (field instanceof PsiEnumConstant) {
397 return checkElement(((PsiEnumConstant)field).getArgumentList(), manager, isOnTheFly);
404 public String getAlternativeID() {
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) {
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()]);
431 private static LocalQuickFix createIntroduceConstantFix() {
432 return new LocalQuickFix() {
435 public String getFamilyName() {
436 return IntroduceConstantHandler.REFACTORING_NAME;
440 public boolean startInWriteAction() {
445 public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
446 PsiElement element = descriptor.getPsiElement();
447 if (!(element instanceof PsiExpression)) return;
449 PsiExpression[] expressions = {(PsiExpression)element};
450 new IntroduceConstantHandler().invoke(project, expressions);
455 private class StringI18nVisitor extends JavaRecursiveElementWalkingVisitor {
456 private final List<ProblemDescriptor> myProblems = new ArrayList<>();
457 private final InspectionManager myManager;
458 private final boolean myOnTheFly;
460 private StringI18nVisitor(@NotNull InspectionManager manager, boolean onTheFly) {
462 myOnTheFly = onTheFly;
466 public void visitAnonymousClass(PsiAnonymousClass aClass) {
467 visitElement(aClass); // visit argument list but anon. class members should be not visited
471 public void visitClass(PsiClass aClass) {
475 public void visitField(PsiField field) {
479 public void visitMethod(PsiMethod method) {
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()) {
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);
498 final String description = CodeInsightBundle.message("inspection.i18n.message.general.with.value", "#ref");
500 List<LocalQuickFix> fixes = new ArrayList<>();
501 if (I18nizeConcatenationQuickFix.getEnclosingLiteralConcatenation(expression) != null) {
502 fixes.add(I18N_CONCATENATION_QUICK_FIX);
504 fixes.add(I18N_QUICK_FIX);
506 if (!isNotConstantFieldInitializer(expression)) {
507 fixes.add(createIntroduceConstantFix());
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));
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);
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);
540 public void visitAnnotation(PsiAnnotation annotation) {
541 //prevent from @SuppressWarnings
542 if (!BatchSuppressManager.SUPPRESS_INSPECTIONS_ANNOTATION_NAME.equals(annotation.getQualifiedName())) {
543 super.visitAnnotation(annotation);
547 private List<ProblemDescriptor> getProblems() {
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)) {
560 if (JavaI18nUtil.isPassedToAnnotatedParam(expression, AnnotationUtil.NON_NLS, new HashMap<>(), nonNlsTargets)) {
564 if (isInNonNlsCall(expression, nonNlsTargets)) {
568 if (isInNonNlsEquals(expression, nonNlsTargets)) {
572 if (isPassedToNonNlsVariable(expression, nonNlsTargets)) {
576 if (JavaI18nUtil.mustBePropertyKey(expression, new HashMap<>())) {
580 if (isReturnedFromNonNlsMethod(expression, nonNlsTargets)) {
583 if (ignoreForAssertStatements && isArgOfAssertStatement(expression)) {
586 if (ignoreForExceptionConstructors && isArgOfExceptionConstructor(expression)) {
589 if (ignoreForEnumConstants && isArgOfEnumConstant(expression)) {
592 if (!ignoreForExceptionConstructors && isArgOfSpecifiedExceptionConstructor(expression, ignoreForSpecifiedExceptionConstructors.split(","))) {
595 if (ignoreForJUnitAsserts && isArgOfJUnitAssertion(expression)) {
598 if (ignoreForClassReferences && isClassRef(expression, value)) {
601 if (ignoreForPropertyKeyReferences && JavaI18nUtil.isPropertyRef(expression, value, null)) {
604 if (ignoreToString && isToString(expression)) {
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));
616 Matcher matcher = pattern.matcher(lineText);
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;
630 private static boolean isArgOfEnumConstant(PsiLiteralExpression expression) {
631 final PsiElement parent = PsiTreeUtil.getParentOfType(expression, PsiExpressionList.class, PsiClass.class);
632 if (!(parent instanceof PsiExpressionList)) {
635 final PsiElement grandparent = parent.getParent();
636 return grandparent instanceof PsiEnumConstant;
639 public void cacheNonNlsCommentPattern() {
640 myCachedNonNlsPattern = nonNlsCommentPattern.trim().isEmpty() ? null : Pattern.compile(nonNlsCommentPattern);
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.
648 return JavaPsiFacade.getInstance(expression.getProject()).findClass(value, GlobalSearchScope.allScope(expression.getProject())) != null;
652 public boolean isEnabledByDefault() {
656 private static boolean isClassNonNls(@NotNull PsiClass clazz) {
657 final PsiDirectory directory = clazz.getContainingFile().getContainingDirectory();
658 return directory != null && isPackageNonNls(JavaDirectoryService.getInstance().getPackage(directory));
661 public static boolean isPackageNonNls(final PsiPackage psiPackage) {
662 if (psiPackage == null || psiPackage.getName() == null) {
665 final PsiModifierList pkgModifierList = psiPackage.getAnnotationList();
666 return pkgModifierList != null && pkgModifierList.findAnnotation(AnnotationUtil.NON_NLS) != null
667 || isPackageNonNls(psiPackage.getParentPackage());
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();
679 if (lExpression instanceof PsiReferenceExpression) {
680 final PsiElement resolved = ((PsiReferenceExpression)lExpression).resolve();
681 if (resolved instanceof PsiVariable) var = (PsiVariable)resolved;
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;
702 if (annotatedAsNonNls(var)) {
705 if (ignoreAssignedToConstants &&
706 var.hasModifierProperty(PsiModifier.STATIC) &&
707 var.hasModifierProperty(PsiModifier.FINAL)) {
710 nonNlsTargets.add(var);
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);
725 return AnnotationUtil.isAnnotated(parent, AnnotationUtil.NON_NLS, false, false);
728 private static boolean isInNonNlsEquals(PsiExpression expression, final Set<PsiModifierListOwner> nonNlsTargets) {
729 if (!(expression.getParent().getParent() instanceof PsiMethodCallExpression)) {
732 final PsiMethodCallExpression call = (PsiMethodCallExpression)expression.getParent().getParent();
733 final PsiReferenceExpression methodExpression = call.getMethodExpression();
734 final PsiExpression qualifier = methodExpression.getQualifierExpression();
735 if (qualifier != expression) {
738 if (!"equals".equals(methodExpression.getReferenceName())) {
741 final PsiElement resolved = methodExpression.resolve();
742 if (!(resolved instanceof PsiMethod)) {
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))) {
753 final PsiExpression[] expressions = call.getArgumentList().getExpressions();
754 if (expressions.length != 1) {
757 final PsiExpression arg = expressions[0];
758 PsiReferenceExpression ref = null;
759 if (arg instanceof PsiReferenceExpression) {
760 ref = (PsiReferenceExpression)arg;
762 else if (arg instanceof PsiMethodCallExpression) ref = ((PsiMethodCallExpression)arg).getMethodExpression();
764 final PsiElement resolvedEntity = ref.resolve();
765 if (resolvedEntity instanceof PsiModifierListOwner) {
766 PsiModifierListOwner modifierListOwner = (PsiModifierListOwner)resolvedEntity;
767 if (annotatedAsNonNls(modifierListOwner)) {
770 nonNlsTargets.add(modifierListOwner);
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);
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)) {
792 nonNlsTargets.add(newVariable);
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)) {
804 nonNlsTargets.add(modifierListOwner);
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)) {
825 nonNlsTargets.add(modifierListOwner);
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);
837 private static boolean isReturnedFromNonNlsMethod(final PsiLiteralExpression expression, final Set<PsiModifierListOwner> nonNlsTargets) {
838 PsiElement parent = expression.getParent();
840 if (parent instanceof PsiNameValuePair) {
841 method = AnnotationUtil.getAnnotationMethod((PsiNameValuePair)parent);
844 final PsiElement returnStmt = PsiTreeUtil.getParentOfType(expression, PsiReturnStatement.class, PsiMethodCallExpression.class);
845 if (!(returnStmt instanceof PsiReturnStatement)) {
848 method = PsiTreeUtil.getParentOfType(expression, PsiMethod.class);
850 if (method == null) return false;
852 if (AnnotationUtil.isAnnotated(method, AnnotationUtil.NON_NLS, true, false)) {
855 nonNlsTargets.add(method);
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());
869 private static boolean isArgOfJUnitAssertion(PsiExpression expression) {
870 final PsiElement parent = expression.getParent();
871 if (!(parent instanceof PsiExpressionList)) {
874 final PsiElement grandparent = parent.getParent();
875 if (!(grandparent instanceof PsiMethodCallExpression)) {
878 final PsiMethodCallExpression call = (PsiMethodCallExpression)grandparent;
879 final PsiReferenceExpression methodExpression = call.getMethodExpression();
880 @NonNls final String methodName = methodExpression.getReferenceName();
881 if (methodName == null) {
885 if (!methodName.startsWith("assert") && !methodName.equals("fail")) {
888 final PsiMethod method = call.resolveMethod();
889 if (method == null) {
892 final PsiClass containingClass = method.getContainingClass();
893 if (containingClass == null) {
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);
902 private static boolean isArgOfExceptionConstructor(PsiExpression expression) {
903 final PsiElement parent = PsiTreeUtil.getParentOfType(expression, PsiExpressionList.class, PsiClass.class);
904 if (!(parent instanceof PsiExpressionList)) {
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();
917 if (!(grandparent instanceof PsiNewExpression)) {
920 final PsiJavaCodeReferenceElement reference = ((PsiNewExpression)grandparent).getClassReference();
921 if (reference == null) {
924 final PsiElement referent = reference.resolve();
925 if (!(referent instanceof PsiClass)) {
929 aClass = (PsiClass)referent;
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);
937 private static boolean isArgOfSpecifiedExceptionConstructor(PsiExpression expression, String[] specifiedExceptions) {
938 if (specifiedExceptions.length == 0) return false;
940 final PsiElement parent = PsiTreeUtil.getParentOfType(expression, PsiExpressionList.class, PsiClass.class);
941 if (!(parent instanceof PsiExpressionList)) {
944 final PsiElement grandparent = parent.getParent();
945 if (!(grandparent instanceof PsiNewExpression)) {
948 final PsiJavaCodeReferenceElement reference =
949 ((PsiNewExpression)grandparent).getClassReference();
950 if (reference == null) {
953 final PsiElement referent = reference.resolve();
954 if (!(referent instanceof PsiClass)) {
957 final PsiClass aClass = (PsiClass)referent;
959 for (String specifiedException : specifiedExceptions) {
960 if (specifiedException.equals(aClass.getQualifiedName())) return true;
967 private static boolean isArgOfAssertStatement(PsiExpression expression) {
968 return PsiTreeUtil.getParentOfType(expression, PsiAssertStatement.class, PsiClass.class) instanceof PsiAssertStatement;