enhanced API for nullness
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInspection / javaDoc / JavaDocReferenceInspection.java
blob982910c89a0074713e1b1097bab05bc132945639
1 /*
2 * Copyright 2000-2009 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.
16 package com.intellij.codeInspection.javaDoc;
18 import com.intellij.codeHighlighting.HighlightDisplayLevel;
19 import com.intellij.codeInsight.daemon.QuickFixBundle;
20 import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFix;
21 import com.intellij.codeInspection.*;
22 import com.intellij.codeInspection.ex.BaseLocalInspectionTool;
23 import com.intellij.ide.DataManager;
24 import com.intellij.ide.util.FQNameCellRenderer;
25 import com.intellij.openapi.actionSystem.PlatformDataKeys;
26 import com.intellij.openapi.application.Result;
27 import com.intellij.openapi.command.WriteCommandAction;
28 import com.intellij.openapi.editor.Editor;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.ui.popup.PopupChooserBuilder;
31 import com.intellij.psi.*;
32 import com.intellij.psi.javadoc.*;
33 import com.intellij.psi.util.proximity.PsiProximityComparator;
34 import org.jetbrains.annotations.NonNls;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
38 import javax.swing.*;
39 import java.util.*;
41 public class JavaDocReferenceInspection extends BaseLocalInspectionTool {
42 @NonNls public static final String SHORT_NAME = "JavadocReference";
43 public static final String DISPLAY_NAME = InspectionsBundle.message("inspection.javadoc.ref.display.name");
46 private static ProblemDescriptor createDescriptor(@NotNull PsiElement element, String template, InspectionManager manager,
47 boolean onTheFly) {
48 return manager.createProblemDescriptor(element, template, onTheFly, (LocalQuickFix [])null, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
51 @Nullable
52 public ProblemDescriptor[] checkMethod(@NotNull PsiMethod psiMethod, @NotNull InspectionManager manager, boolean isOnTheFly) {
53 return checkMember(psiMethod, manager, isOnTheFly);
56 @Nullable
57 public ProblemDescriptor[] checkField(@NotNull PsiField field, @NotNull InspectionManager manager, boolean isOnTheFly) {
58 return checkMember(field, manager, isOnTheFly);
61 @Nullable
62 private ProblemDescriptor[] checkMember(final PsiDocCommentOwner docCommentOwner, final InspectionManager manager, final boolean isOnTheFly) {
63 ArrayList<ProblemDescriptor> problems = new ArrayList<ProblemDescriptor>();
64 final PsiDocComment docComment = docCommentOwner.getDocComment();
65 if (docComment == null) return null;
67 final Set<PsiJavaCodeReferenceElement> references = new HashSet<PsiJavaCodeReferenceElement>();
68 docComment.accept(getVisitor(references, docCommentOwner, problems, manager, isOnTheFly));
69 for (PsiJavaCodeReferenceElement reference : references) {
70 final List<PsiClass> classesToImport = new ImportClassFix(reference).getClassesToImport();
71 problems.add(manager.createProblemDescriptor(reference, InspectionsBundle.message("inspection.javadoc.problem.cannot.resolve",
72 "<code>" + reference.getText() + "</code>"),
73 !isOnTheFly || classesToImport.isEmpty() ? null : new AddImportFix(classesToImport), ProblemHighlightType.LIKE_UNKNOWN_SYMBOL,
74 isOnTheFly));
77 return problems.isEmpty()
78 ? null
79 : problems.toArray(new ProblemDescriptor[problems.size()]);
82 @Nullable
83 public ProblemDescriptor[] checkClass(@NotNull PsiClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) {
84 return checkMember(aClass, manager, isOnTheFly);
88 private PsiElementVisitor getVisitor(final Set<PsiJavaCodeReferenceElement> references,
89 final PsiElement context,
90 final ArrayList<ProblemDescriptor> problems,
91 final InspectionManager manager, final boolean onTheFly) {
92 return new JavaElementVisitor() {
93 @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
94 visitElement(expression);
97 @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
98 super.visitReferenceElement(reference);
99 JavaResolveResult result = reference.advancedResolve(false);
100 if (result.getElement() == null && !result.isPackagePrefixPackageReference()) {
101 references.add(reference);
105 @Override public void visitDocTag(PsiDocTag tag) {
106 super.visitDocTag(tag);
107 final JavadocManager javadocManager = JavaPsiFacade.getInstance(tag.getProject()).getJavadocManager();
108 final JavadocTagInfo info = javadocManager.getTagInfo(tag.getName());
109 if (info == null || !info.isInline()) {
110 visitRefInDocTag(tag, javadocManager, context, problems, manager, onTheFly);
114 @Override public void visitInlineDocTag(PsiInlineDocTag tag) {
115 super.visitInlineDocTag(tag);
116 final JavadocManager javadocManager = JavaPsiFacade.getInstance(tag.getProject()).getJavadocManager();
117 visitRefInDocTag(tag, javadocManager, context, problems, manager, onTheFly);
120 @Override public void visitElement(PsiElement element) {
121 PsiElement[] children = element.getChildren();
122 for (PsiElement child : children) {
123 //do not visit method javadoc twice
124 if (!(child instanceof PsiDocCommentOwner)) {
125 child.accept(this);
132 public static void visitRefInDocTag(final PsiDocTag tag, final JavadocManager manager, final PsiElement context, ArrayList<ProblemDescriptor> problems,
133 InspectionManager inspectionManager,
134 boolean onTheFly) {
135 String tagName = tag.getName();
136 PsiDocTagValue value = tag.getValueElement();
137 if (value == null) return;
138 final JavadocTagInfo info = manager.getTagInfo(tagName);
139 if (info != null && !info.isValidInContext(context)) return;
140 String message = info == null || !info.isInline() ? null : info.checkTagValue(value);
141 if (message != null){
142 problems.add(createDescriptor(value, message, inspectionManager, onTheFly));
144 final PsiReference reference = value.getReference();
145 if (reference != null) {
146 PsiElement element = reference.resolve();
147 if (element == null) {
148 final int textOffset = value.getTextOffset();
150 if (textOffset != value.getTextRange().getEndOffset()) {
151 final PsiDocTagValue valueElement = tag.getValueElement();
152 if (valueElement != null) {
153 @NonNls String params = "<code>" + value.getContainingFile().getViewProvider().getContents().subSequence(textOffset, value.getTextRange().getEndOffset()) + "</code>";
154 problems.add(createDescriptor(valueElement, InspectionsBundle.message("inspection.javadoc.problem.cannot.resolve", params), inspectionManager,
155 onTheFly));
163 @NotNull
164 public String getDisplayName() {
165 return DISPLAY_NAME;
168 @NotNull
169 public String getGroupDisplayName() {
170 return "";
173 @NotNull
174 public String getShortName() {
175 return SHORT_NAME;
178 @NotNull
179 public HighlightDisplayLevel getDefaultLevel() {
180 return HighlightDisplayLevel.ERROR;
183 private class AddImportFix implements LocalQuickFix{
184 private final List<PsiClass> myClassesToImport;
186 public AddImportFix(final List<PsiClass> classesToImport) {
187 myClassesToImport = classesToImport;
190 @NotNull
191 public String getName() {
192 return QuickFixBundle.message("import.class.fix");
195 @NotNull
196 public String getFamilyName() {
197 return QuickFixBundle.message("import.class.fix");
200 public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
201 final PsiElement element = descriptor.getPsiElement();
202 if (element instanceof PsiJavaCodeReferenceElement) {
203 final PsiJavaCodeReferenceElement referenceElement = (PsiJavaCodeReferenceElement)element;
204 Collections.sort(myClassesToImport, new PsiProximityComparator(referenceElement.getElement()));
205 final JList list = new JList(myClassesToImport.toArray(new PsiClass[myClassesToImport.size()]));
206 list.setCellRenderer(new FQNameCellRenderer());
207 Runnable runnable = new Runnable() {
208 public void run() {
209 if (!element.isValid()) return;
210 final int index = list.getSelectedIndex();
211 if (index < 0) return;
212 new WriteCommandAction(project, element.getContainingFile()){
213 protected void run(final Result result) throws Throwable {
214 final PsiClass psiClass = myClassesToImport.get(index);
215 if (psiClass.isValid()) {
216 PsiDocumentManager.getInstance(project).commitAllDocuments();
217 referenceElement.bindToElement(psiClass);
220 }.execute();
223 final Editor editor = PlatformDataKeys.EDITOR.getData(DataManager.getInstance().getDataContext());
224 assert editor != null; //available for on the fly mode only
225 new PopupChooserBuilder(list).
226 setTitle(QuickFixBundle.message("class.to.import.chooser.title")).
227 setItemChoosenCallback(runnable).
228 createPopup().
229 showInBestPositionFor(editor);