Inspections - pass onTheFly into ProblemDescriptors & use it to create LAZY refs...
[idea/community.git] / java / java-impl / src / com / intellij / codeInspection / javaDoc / JavaDocLocalInspection.java
1 /*
2  * Copyright (c) 2005 Jet Brains. All Rights Reserved.
3  */
4 package com.intellij.codeInspection.javaDoc;
5
6 import com.intellij.ExtensionPoints;
7 import com.intellij.codeInsight.CodeInsightUtil;
8 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
9 import com.intellij.codeInsight.daemon.QuickFixBundle;
10 import com.intellij.codeInspection.*;
11 import com.intellij.codeInspection.ex.BaseLocalInspectionTool;
12 import com.intellij.codeInspection.reference.RefJavaUtil;
13 import com.intellij.openapi.diagnostic.Logger;
14 import com.intellij.openapi.editor.Editor;
15 import com.intellij.openapi.editor.ScrollType;
16 import com.intellij.openapi.extensions.ExtensionPoint;
17 import com.intellij.openapi.extensions.Extensions;
18 import com.intellij.openapi.fileEditor.FileEditorManager;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.*;
21 import com.intellij.profile.codeInspection.InspectionProfileManager;
22 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
23 import com.intellij.psi.*;
24 import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
25 import com.intellij.psi.impl.source.jsp.jspJava.JspClass;
26 import com.intellij.psi.impl.source.jsp.jspJava.JspHolderMethod;
27 import com.intellij.psi.javadoc.*;
28 import com.intellij.psi.util.InheritanceUtil;
29 import com.intellij.psi.util.PsiTreeUtil;
30 import com.intellij.ui.DocumentAdapter;
31 import com.intellij.ui.FieldPanel;
32 import com.intellij.ui.IdeBorderFactory;
33 import com.intellij.util.IJSwingUtilities;
34 import com.intellij.util.IncorrectOperationException;
35 import com.intellij.util.ui.UIUtil;
36 import org.jdom.Element;
37 import org.jetbrains.annotations.NonNls;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40
41 import javax.swing.*;
42 import javax.swing.event.ChangeEvent;
43 import javax.swing.event.ChangeListener;
44 import javax.swing.event.DocumentEvent;
45 import javax.swing.text.BadLocationException;
46 import javax.swing.text.Document;
47 import java.awt.*;
48 import java.awt.event.ActionEvent;
49 import java.awt.event.ActionListener;
50 import java.util.*;
51
52 public class JavaDocLocalInspection extends BaseLocalInspectionTool {
53   private static final String REQUIRED_JAVADOC_IS_ABSENT = InspectionsBundle.message("inspection.javadoc.problem.descriptor");
54
55   @NonNls private static final String NONE = "none";
56   @NonNls private static final String PUBLIC = "public";
57   @NonNls private static final String PROTECTED = "protected";
58   @NonNls private static final String PACKAGE_LOCAL = "package";
59   @NonNls private static final String PRIVATE = "private";
60   @NonNls private static final Set<String> ourUniqueTags = new HashSet<String>();
61   @NonNls public static final String SHORT_NAME = "JavaDoc";
62
63   static {
64     ourUniqueTags.add("return");
65     ourUniqueTags.add("deprecated");
66     ourUniqueTags.add("serial");
67     ourUniqueTags.add("serialData");
68   }
69
70
71   public static class Options implements JDOMExternalizable {
72     @NonNls public String ACCESS_JAVADOC_REQUIRED_FOR = NONE;
73     @NonNls public String REQUIRED_TAGS = "";
74
75     public Options() {
76     }
77
78     public Options(String ACCESS_JAVADOC_REQUIRED_FOR, String REQUIRED_TAGS) {
79       this.ACCESS_JAVADOC_REQUIRED_FOR = ACCESS_JAVADOC_REQUIRED_FOR;
80       this.REQUIRED_TAGS = REQUIRED_TAGS;
81     }
82
83     public void readExternal(Element element) throws InvalidDataException {
84       DefaultJDOMExternalizer.readExternal(this, element);
85     }
86
87     public void writeExternal(Element element) throws WriteExternalException {
88       DefaultJDOMExternalizer.writeExternal(this, element);
89     }
90   }
91
92   @NonNls public Options TOP_LEVEL_CLASS_OPTIONS = new Options("none", "");
93   @NonNls public Options INNER_CLASS_OPTIONS = new Options("none", "");
94   @NonNls public Options METHOD_OPTIONS = new Options("none", "@return@param@throws or @exception");
95   @NonNls public Options FIELD_OPTIONS = new Options("none", "");
96   public boolean IGNORE_DEPRECATED = false;
97   public boolean IGNORE_JAVADOC_PERIOD = true;
98   public String myAdditionalJavadocTags = "";
99
100   private static final Logger LOG = Logger.getInstance("com.intellij.codeInspection.javaDoc.JavaDocLocalInspection");
101
102   private class OptionsPanel extends JPanel {
103     private JPanel createOptionsPanel(String[] modifiers, String[] tags, Options options) {
104       JPanel pane = new JPanel(new GridLayout(1, tags == null ? 1 : 2));
105
106       pane.add(createScopePanel(modifiers, options));
107       if (tags != null) {
108         pane.add(createTagsPanel(tags, options));
109       }
110
111       pane.validate();
112
113       return pane;
114     }
115
116     private JPanel createTagsPanel(String[] tags, Options options) {
117       JPanel panel = new JPanel(new GridBagLayout());
118       panel.setBorder(BorderFactory.createCompoundBorder(IdeBorderFactory.createTitledBorder(InspectionsBundle.message("inspection.javadoc.required.tags.option.title")),
119                                                          BorderFactory.createEmptyBorder(0, 3, 3, 3)));
120
121       GridBagConstraints gc = new GridBagConstraints();
122       gc.weightx = 1;
123       gc.weighty = 0;
124       gc.fill = GridBagConstraints.HORIZONTAL;
125       gc.anchor = GridBagConstraints.NORTHWEST;
126
127
128       for (int i = 0; i < tags.length; i++) {
129         JCheckBox box = new JCheckBox(tags[i]);
130         gc.gridy = i;
131         if (i == tags.length - 1) gc.weighty = 1;
132         panel.add(box, gc);
133         box.setSelected(isTagRequired(options, tags[i]));
134         box.addChangeListener(new MyChangeListener(box, options, tags[i]));
135       }
136
137       return panel;
138     }
139
140     private class MyChangeListener implements ChangeListener {
141       private final JCheckBox myCheckBox;
142       private final Options myOptions;
143       private final String myTagName;
144
145       public MyChangeListener(JCheckBox checkBox, Options options, String tagName) {
146         myCheckBox = checkBox;
147         myOptions = options;
148         myTagName = tagName;
149       }
150
151       public void stateChanged(ChangeEvent e) {
152         if (myCheckBox.isSelected()) {
153           if (!isTagRequired(myOptions,myTagName)) {
154             myOptions.REQUIRED_TAGS += myTagName;
155           }
156         }
157         else {
158           myOptions.REQUIRED_TAGS = myOptions.REQUIRED_TAGS.replaceAll(myTagName, "");
159         }
160       }
161     }
162
163     private JPanel createScopePanel(final String[] modifiers, final Options options) {
164       JPanel panel = new JPanel(new BorderLayout());
165       panel.setBorder(BorderFactory.createCompoundBorder(IdeBorderFactory.createTitledBorder(InspectionsBundle.message("inspection.scope.for.title")),
166                                                          BorderFactory.createEmptyBorder(0, 3, 3, 3)));
167
168       final Hashtable<Integer, JLabel> sliderLabels = new Hashtable<Integer, JLabel>();
169       for (int i = 0; i < modifiers.length; i++) {
170         sliderLabels.put(i + 1, new JLabel(modifiers[i]));
171       }
172
173       final JSlider slider = new JSlider(SwingConstants.VERTICAL, 1, modifiers.length, 1);
174
175       slider.setLabelTable(sliderLabels);
176       slider.putClientProperty(UIUtil.JSLIDER_ISFILLED, Boolean.TRUE);
177       slider.setPreferredSize(new Dimension(80, 50));
178       slider.setPaintLabels(true);
179       slider.setSnapToTicks(true);
180       slider.addChangeListener(new ChangeListener() {
181         public void stateChanged(ChangeEvent e) {
182           int value = slider.getValue();
183           options.ACCESS_JAVADOC_REQUIRED_FOR = modifiers[value - 1];
184           for (Integer key : sliderLabels.keySet()) {
185             sliderLabels.get(key).setForeground(key.intValue() <= value ? Color.black : new Color(100, 100, 100));
186           }
187         }
188       });
189
190       Color fore = Color.black;
191       for (int i = 0; i < modifiers.length; i++) {
192         sliderLabels.get(i + 1).setForeground(fore);
193
194         if (modifiers[i].equals(options.ACCESS_JAVADOC_REQUIRED_FOR)) {
195           slider.setValue(i + 1);
196           fore = new Color(100, 100, 100);
197         }
198       }
199
200       panel.add(slider, BorderLayout.WEST);
201
202       return panel;
203     }
204
205     public OptionsPanel() {
206       super(new GridBagLayout());
207       GridBagConstraints gc = new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1, 1, GridBagConstraints.NORTH, GridBagConstraints.BOTH, new Insets(0,0,0,0),0,0 );
208       gc.weighty = 0;
209       add(createAdditionalJavadocTagsPanel(), gc);
210       JTabbedPane tabs = new JTabbedPane(SwingConstants.BOTTOM);
211       @NonNls String[] tags = new String[]{"@author", "@version", "@since", "@param"};
212       tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title"), createOptionsPanel(new String[]{NONE, PUBLIC, PACKAGE_LOCAL},
213                                                                                                     tags,
214                                                                                                     TOP_LEVEL_CLASS_OPTIONS));
215       tags = new String[]{"@return", "@param", InspectionsBundle.message("inspection.javadoc.throws.or.exception.option")};
216       tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.method"), createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE},
217                                                                                                            tags,
218                                                                                                            METHOD_OPTIONS));
219       tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.field"), createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE},
220                                                                                                           null,
221                                                                                                           FIELD_OPTIONS));
222       tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.inner.class"), createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE},
223                                                                                                                 null,
224                                                                                                                 INNER_CLASS_OPTIONS));
225       add(tabs, gc);
226
227       final JCheckBox checkBox = new JCheckBox(InspectionsBundle.message("inspection.javadoc.option.ignore.deprecated"),
228                                                IGNORE_DEPRECATED);
229       checkBox.addActionListener(new ActionListener() {
230         public void actionPerformed(ActionEvent e) {
231           IGNORE_DEPRECATED = checkBox.isSelected();
232         }
233       });
234       gc.gridwidth = 1;
235       add(checkBox, gc);
236       final JCheckBox periodCheckBox = new JCheckBox(InspectionsBundle.message("inspection.javadoc.option.ignore.period"),
237                                                      IGNORE_JAVADOC_PERIOD);
238       periodCheckBox.addActionListener(new ActionListener() {
239         public void actionPerformed(ActionEvent e) {
240           IGNORE_JAVADOC_PERIOD = periodCheckBox.isSelected();
241         }
242       });
243       add(periodCheckBox, gc);
244     }
245
246     public FieldPanel createAdditionalJavadocTagsPanel(){
247       FieldPanel additionalTagsPanel = new FieldPanel(InspectionsBundle.message("inspection.javadoc.label.text"), InspectionsBundle.message("inspection.javadoc.dialog.title"), null, null);
248       additionalTagsPanel.setPreferredSize(new Dimension(150, additionalTagsPanel.getPreferredSize().height));
249       additionalTagsPanel.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
250         protected void textChanged(DocumentEvent e) {
251           final Document document = e.getDocument();
252           try {
253             final String text = document.getText(0, document.getLength());
254             if (text != null) {
255               myAdditionalJavadocTags = text.trim();
256             }
257           }
258           catch (BadLocationException e1) {
259              LOG.error(e1);
260           }
261         }
262       });
263       additionalTagsPanel.setText(myAdditionalJavadocTags);
264       return additionalTagsPanel;
265     }
266   }
267
268   public JComponent createOptionsPanel() {
269     return new OptionsPanel();
270   }
271
272   private static ProblemDescriptor createDescriptor(@NotNull PsiElement element, String template, InspectionManager manager,
273                                                     boolean onTheFly) {
274     return manager.createProblemDescriptor(element, template, (LocalQuickFix [])null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
275                                            onTheFly);
276   }
277
278   private static ProblemDescriptor createDescriptor(@NotNull PsiElement element, String template, @NotNull LocalQuickFix fix,
279                                                     InspectionManager manager, boolean onTheFly) {
280     return manager.createProblemDescriptor(element, template, fix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, onTheFly);
281   }
282
283   private static class AddMissingTagFix implements LocalQuickFix {
284     private final String myTag;
285     private final String myValue;
286
287     public AddMissingTagFix(@NonNls String tag, String value) {
288       myTag = tag;
289       myValue = value;
290     }
291     public AddMissingTagFix(String tag) {
292       this(tag, "");
293     }
294
295     @NotNull
296     public String getName() {
297       return InspectionsBundle.message("inspection.javadoc.problem.add.tag", myTag, myValue);
298     }
299
300     public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
301       final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
302       try {
303         final PsiDocCommentOwner owner = PsiTreeUtil.getParentOfType(descriptor.getEndElement(), PsiDocCommentOwner.class);
304         if (owner != null) {
305           if (!CodeInsightUtil.preparePsiElementsForWrite(owner)) return;
306           final PsiDocComment docComment = owner.getDocComment();
307           final PsiDocTag tag = factory.createDocTagFromText("@" + myTag+" "+myValue, docComment);
308           if (docComment != null) {
309             PsiElement addedTag;
310             final PsiElement anchor = getAnchor();
311             if (anchor != null) {
312               addedTag = docComment.addBefore(tag, anchor);
313             }
314             else {
315               addedTag = docComment.add(tag);
316             }
317             moveCaretTo(addedTag);
318           }
319         }
320       }
321       catch (IncorrectOperationException e) {
322         LOG.error(e);
323       }
324     }
325
326     @Nullable
327     protected PsiElement getAnchor() {
328       return null;
329     }
330
331     private static void moveCaretTo(final PsiElement newCaretPosition) {
332       Project project = newCaretPosition.getProject();
333       final PsiFile psiFile = newCaretPosition.getContainingFile();
334       final Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
335       if (editor != null && IJSwingUtilities.hasFocus(editor.getComponent())) {
336         final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
337         if (file == psiFile) {
338           editor.getCaretModel().moveToOffset(newCaretPosition.getTextRange().getEndOffset());
339           editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
340         }
341       }
342     }
343
344     @NotNull
345     public String getFamilyName() {
346       return InspectionsBundle.message("inspection.javadoc.problem.add.tag.family");
347     }
348   }
349   @Nullable
350   public ProblemDescriptor[] checkClass(@NotNull PsiClass psiClass, @NotNull InspectionManager manager, boolean isOnTheFly) {
351     if (psiClass instanceof PsiAnonymousClass) return null;
352     if (psiClass instanceof JspClass) return null;
353     if (psiClass instanceof PsiTypeParameter) return null;
354     if (IGNORE_DEPRECATED && psiClass.isDeprecated()) {
355       return null;
356     }
357     PsiDocComment docComment = psiClass.getDocComment();
358     final PsiIdentifier nameIdentifier = psiClass.getNameIdentifier();
359     final PsiElement elementToHighlight = nameIdentifier != null ? nameIdentifier : psiClass;
360     if (docComment == null) {
361       return isJavaDocRequired(psiClass)
362              ? new ProblemDescriptor[]{createDescriptor(elementToHighlight, REQUIRED_JAVADOC_IS_ABSENT, manager, isOnTheFly)}
363              : null;
364     }
365
366     PsiDocTag[] tags = docComment.getTags();
367     @NonNls String[] tagsToCheck = {"author", "version", "since"};
368     @NonNls String[] absentDescriptionKeys = {
369       "inspection.javadoc.problem.missing.author.description",
370       "inspection.javadoc.problem.missing.version.description",
371       "inspection.javadoc.problem.missing.since.description"};
372
373     boolean[] isTagRequired = new boolean[tagsToCheck.length];
374     boolean[] isTagPresent = new boolean[tagsToCheck.length];
375
376     boolean someTagsAreRequired = false;
377     for (int i = 0; i < tagsToCheck.length; i++) {
378       final String tag = tagsToCheck[i];
379       someTagsAreRequired |= isTagRequired[i] = isTagRequired(psiClass, tag);
380     }
381
382     if (someTagsAreRequired) {
383       for (PsiDocTag tag : tags) {
384         String tagName = tag.getName();
385         for (int i = 0; i < tagsToCheck.length; i++) {
386           final String tagToCheck = tagsToCheck[i];
387           if (tagToCheck.equals(tagName)) {
388             isTagPresent[i] = true;
389           }
390         }
391       }
392     }
393
394     final ArrayList<ProblemDescriptor> problems = new ArrayList<ProblemDescriptor>(2);
395
396     for (int i = 0; i < tagsToCheck.length; i++) {
397       final String tagToCheck = tagsToCheck[i];
398       if (isTagRequired[i] && !isTagPresent[i]) {
399         problems.add(createMissingTagDescriptor(elementToHighlight, tagToCheck, manager, isOnTheFly));
400       }
401     }
402     ArrayList<ProblemDescriptor> tagProblems = getTagValuesProblems(psiClass, tags, manager, isOnTheFly);
403     if (tagProblems != null) {
404       problems.addAll(tagProblems);
405     }
406     checkForPeriodInDoc(docComment, problems, manager, isOnTheFly);
407     checkInlineTags(manager, problems, docComment.getDescriptionElements(),
408                     JavaPsiFacade.getInstance(docComment.getProject()).getJavadocManager(), isOnTheFly);
409
410     for (PsiDocTag tag : tags) {
411       for (int i = 0; i < tagsToCheck.length; i++) {
412         final String tagToCheck = tagsToCheck[i];
413         if (tagToCheck.equals(tag.getName()) && extractTagDescription(tag).length() == 0) {
414           problems.add(createDescriptor(elementToHighlight, InspectionsBundle.message(absentDescriptionKeys[i]), manager, isOnTheFly));
415         }
416       }
417     }
418
419     checkDuplicateTags(tags, problems, manager, isOnTheFly);
420
421     if (isTagRequired(psiClass, "param") && psiClass.hasTypeParameters() && nameIdentifier != null) {
422       ArrayList<PsiTypeParameter> absentParameters = null;
423       final PsiTypeParameter[] typeParameters = psiClass.getTypeParameters();
424       for (PsiTypeParameter typeParameter : typeParameters) {
425         if (!isFound(tags, typeParameter)) {
426           if (absentParameters == null) absentParameters = new ArrayList<PsiTypeParameter>(1);
427           absentParameters.add(typeParameter);
428         }
429       }
430       if (absentParameters != null) {
431         for (PsiTypeParameter psiTypeParameter : absentParameters) {
432           problems.add(createMissingParamTagDescriptor(nameIdentifier, psiTypeParameter, manager, isOnTheFly));
433         }
434       }
435     }
436
437     return problems.isEmpty()
438            ? null
439            : problems.toArray(new ProblemDescriptor[problems.size()]);
440   }
441
442   private static ProblemDescriptor createMissingParamTagDescriptor(final PsiIdentifier nameIdentifier,
443                                                                    final PsiTypeParameter psiTypeParameter,
444                                                                    final InspectionManager manager, boolean isOnTheFly) {
445     String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", "<code>@param</code>");
446     return createDescriptor(nameIdentifier, message, new AddMissingTagFix("param", "<" + psiTypeParameter.getName() + ">"), manager,
447                             isOnTheFly);
448   }
449
450   @Nullable
451   public ProblemDescriptor[] checkField(@NotNull PsiField psiField, @NotNull InspectionManager manager, boolean isOnTheFly) {
452     if (IGNORE_DEPRECATED && (psiField.isDeprecated() || psiField.getContainingClass().isDeprecated())) {
453       return null;
454     }
455
456     PsiDocComment docComment = psiField.getDocComment();
457     if (docComment == null) {
458       return isJavaDocRequired(psiField)
459              ? new ProblemDescriptor[]{createDescriptor(psiField.getNameIdentifier(), REQUIRED_JAVADOC_IS_ABSENT, manager, isOnTheFly)}
460              : null;
461     }
462
463     final ArrayList<ProblemDescriptor> problems = new ArrayList<ProblemDescriptor>(2);
464     ArrayList<ProblemDescriptor> tagProblems = getTagValuesProblems(psiField, docComment.getTags(), manager, isOnTheFly);
465     if (tagProblems != null) {
466       problems.addAll(tagProblems);
467     }
468     checkInlineTags(manager, problems, docComment.getDescriptionElements(),
469                     JavaPsiFacade.getInstance(docComment.getProject()).getJavadocManager(), isOnTheFly);
470     checkForPeriodInDoc(docComment, problems, manager, isOnTheFly);
471     checkDuplicateTags(docComment.getTags(), problems, manager, isOnTheFly);
472     return problems.isEmpty()
473            ? null
474            : problems.toArray(new ProblemDescriptor[problems.size()]);
475   }
476
477   @Nullable
478   public ProblemDescriptor[] checkMethod(@NotNull PsiMethod psiMethod, @NotNull InspectionManager manager, boolean isOnTheFly) {
479     if (psiMethod instanceof JspHolderMethod) return null;
480     if (IGNORE_DEPRECATED && (psiMethod.isDeprecated() || psiMethod.getContainingClass().isDeprecated())) {
481       return null;
482     }
483     PsiDocComment docComment = psiMethod.getDocComment();
484     final PsiMethod[] superMethods = psiMethod.findSuperMethods();
485     if (docComment == null) {
486       if (isJavaDocRequired(psiMethod)) {
487         if (superMethods.length > 0) return null;
488         ExtensionPoint<Condition<PsiMember>> point = Extensions.getRootArea().getExtensionPoint(ExtensionPoints.JAVADOC_LOCAL);
489         final Condition<PsiMember>[] addins = point.getExtensions();
490         for (Condition<PsiMember> addin : addins) {
491           if (addin.value(psiMethod)) return null;
492         }
493         if (superMethods.length == 0) {
494           final PsiIdentifier nameIdentifier = psiMethod.getNameIdentifier();
495           return nameIdentifier != null ? new ProblemDescriptor[] { createDescriptor(nameIdentifier, REQUIRED_JAVADOC_IS_ABSENT, manager,
496                                                                                      isOnTheFly)} : null;
497         }
498         else {
499           return null;
500         }
501       }
502       else {
503         return null;
504       }
505     }
506
507     final PsiElement[] descriptionElements = docComment.getDescriptionElements();
508     for (PsiElement descriptionElement : descriptionElements) {
509       if (descriptionElement instanceof PsiInlineDocTag) {
510         if ("inheritDoc".equals(((PsiInlineDocTag)descriptionElement).getName())) return null;
511       }
512     }
513
514     final ArrayList<ProblemDescriptor> problems = new ArrayList<ProblemDescriptor>(2);
515
516     checkInlineTags(manager, problems, descriptionElements,
517                     JavaPsiFacade.getInstance(docComment.getProject()).getJavadocManager(), isOnTheFly);
518
519     final PsiDocTag tagByName = docComment.findTagByName("inheritDoc");
520     if (tagByName != null) {
521       final String tagName = tagByName.getName();
522       final JavadocTagInfo tagInfo = JavaPsiFacade.getInstance(tagByName.getProject()).getJavadocManager().getTagInfo(tagName);
523       if (tagInfo != null && tagInfo.isValidInContext(psiMethod)){
524         return null;
525       }
526     }
527
528     PsiDocTag[] tags = docComment.getTags();
529
530     boolean isReturnRequired = false;
531     boolean isReturnAbsent = true;
532     if (superMethods.length == 0 && !psiMethod.isConstructor() && PsiType.VOID != psiMethod.getReturnType() && isTagRequired(psiMethod, "return")) {
533       isReturnRequired = true;
534       for (PsiDocTag tag : tags) {
535         if ("return".equals(tag.getName())) {
536           isReturnAbsent = false;
537           break;
538         }
539       }
540     }
541
542     ArrayList<PsiParameter> absentParameters = null;
543     if (superMethods.length == 0 && isTagRequired(psiMethod, "param") ) {
544       PsiParameter[] params = psiMethod.getParameterList().getParameters();
545       for (PsiParameter param : params) {
546         if (!isFound(tags, param)) {
547           if (absentParameters == null) absentParameters = new ArrayList<PsiParameter>(2);
548           absentParameters.add(param);
549         }
550       }
551     }
552
553
554
555     if (isReturnRequired && isReturnAbsent) {
556       final PsiIdentifier psiIdentifier = psiMethod.getNameIdentifier();
557       if (psiIdentifier != null) {
558         problems.add(createMissingTagDescriptor(psiIdentifier, "return", manager, isOnTheFly));
559       }
560     }
561
562     if (absentParameters != null) {
563       for (PsiParameter psiParameter : absentParameters) {
564         final PsiIdentifier nameIdentifier = psiMethod.getNameIdentifier();
565         if (nameIdentifier != null) {
566           problems.add(createMissingParamTagDescriptor(nameIdentifier, psiParameter, manager, isOnTheFly));
567         }
568       }
569     }
570
571     for (PsiDocTag tag : tags) {
572       if ("param".equals(tag.getName())) {
573         final PsiElement[] dataElements = tag.getDataElements();
574         final PsiDocTagValue valueElement = tag.getValueElement();
575         boolean hasProblemsWithTag = dataElements.length < 2;
576         if (!hasProblemsWithTag) {
577           final StringBuilder buf = new StringBuilder();
578           for (PsiElement element : dataElements) {
579             if (element != valueElement){
580               buf.append(element.getText());
581             }
582           }
583           hasProblemsWithTag = buf.toString().trim().length() == 0;
584         }
585         if (hasProblemsWithTag) {
586           if (valueElement != null) {
587             problems.add(createDescriptor(valueElement,
588                                           InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", "<code>@param " + valueElement.getText() + "</code>"),
589                                           manager, isOnTheFly));
590           }
591
592         }
593       }
594     }
595
596     if (superMethods.length == 0 && isTagRequired(psiMethod, "@throws") && psiMethod.getThrowsList().getReferencedTypes().length > 0) {
597       final Map<PsiClassType, PsiClass> declaredExceptions = new HashMap<PsiClassType, PsiClass>();
598       final PsiClassType[] classTypes = psiMethod.getThrowsList().getReferencedTypes();
599       for (PsiClassType classType : classTypes) {
600         final PsiClass psiClass = classType.resolve();
601         if (psiClass != null){
602           declaredExceptions.put(classType, psiClass);
603         }
604       }
605       processThrowsTags(tags, declaredExceptions, manager, problems, isOnTheFly);
606       if (!declaredExceptions.isEmpty()) {
607         for (PsiClassType declaredException : declaredExceptions.keySet()) {
608           problems.add(createMissingThrowsTagDescriptor(psiMethod, manager, declaredException, isOnTheFly));
609         }
610       }
611     }
612
613     ArrayList<ProblemDescriptor> tagProblems = getTagValuesProblems(psiMethod, tags, manager, isOnTheFly);
614     if (tagProblems != null) {
615       problems.addAll(tagProblems);
616     }
617
618     checkForPeriodInDoc(docComment, problems, manager, isOnTheFly);
619
620     for (PsiDocTag tag : tags) {
621       if ("param".equals(tag.getName())) {
622         if (extractTagDescription(tag).length() == 0) {
623           PsiDocTagValue value = tag.getValueElement();
624           if (value instanceof PsiDocParamRef) {
625             PsiDocParamRef paramRef = (PsiDocParamRef)value;
626             PsiParameter[] params = psiMethod.getParameterList().getParameters();
627             for (PsiParameter param : params) {
628               if (paramRef.getReference().isReferenceTo(param)) {
629                 problems.add(createDescriptor(value,
630                                               InspectionsBundle.message("inspection.javadoc.method.problem.descriptor", "<code>@param</code>", "<code>" + param.getName() + "</code>"),
631                                               manager, isOnTheFly));
632               }
633             }
634           }
635         }
636       }
637       else
638         if ("return".equals(tag.getName())) {
639           if (extractTagDescription(tag).length() == 0) {
640             String message = InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", "<code>@return</code>");
641             ProblemDescriptor descriptor = manager.createProblemDescriptor(tag.getNameElement(), message, null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
642                                                                            isOnTheFly, true);
643             problems.add(descriptor);
644           }
645         }
646     }
647
648     checkDuplicateTags(tags, problems, manager, isOnTheFly);
649
650     return problems.isEmpty()
651            ? null
652            : problems.toArray(new ProblemDescriptor[problems.size()]);
653   }
654
655   private static boolean isFound(final PsiDocTag[] tags, final PsiElement param) {
656     for (PsiDocTag tag : tags) {
657       if ("param".equals(tag.getName())) {
658         PsiDocTagValue value = tag.getValueElement();
659         if (value instanceof PsiDocParamRef) {
660           PsiDocParamRef paramRef = (PsiDocParamRef)value;
661           final PsiReference psiReference = paramRef.getReference();
662           if (psiReference != null && psiReference.isReferenceTo(param)) {
663             return true;
664           }
665         }
666       }
667     }
668     return false;
669   }
670
671   private static void processThrowsTags(final PsiDocTag[] tags,
672                                         final Map<PsiClassType, PsiClass> declaredExceptions,
673                                         final InspectionManager mananger,
674                                         @NotNull final ArrayList<ProblemDescriptor> problems, boolean isOnTheFly) {
675     for (PsiDocTag tag : tags) {
676       if ("throws".equals(tag.getName()) || "exception".equals(tag.getName())) {
677         final PsiDocTagValue value = tag.getValueElement();
678         if (value == null) continue;
679         final PsiElement firstChild = value.getFirstChild();
680         if (firstChild == null) continue;
681         final PsiElement psiElement = firstChild.getFirstChild();
682         if (!(psiElement instanceof PsiJavaCodeReferenceElement)) continue;
683         final PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)psiElement;
684         final PsiElement element = ref.resolve();
685         if (element instanceof PsiClass){
686           final PsiClass exceptionClass = (PsiClass)element;
687           for (Iterator<PsiClassType> it = declaredExceptions.keySet().iterator(); it.hasNext();) {
688             PsiClassType classType = it.next();
689             final PsiClass psiClass = declaredExceptions.get(classType);
690             if (InheritanceUtil.isInheritorOrSelf(exceptionClass, psiClass, true)) {
691               if (extractThrowsTagDescription(tag).length() == 0) {
692                 problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", "<code>" + tag.getName() + "</code>"), mananger,
693                                               isOnTheFly));
694               }
695               it.remove();
696             }
697           }
698         }
699       }
700     }
701   }
702
703   @Nullable
704   private static ProblemDescriptor createMissingThrowsTagDescriptor(final PsiMethod method,
705                                                                     final InspectionManager manager,
706                                                                     final PsiClassType exceptionClassType, boolean isOnTheFly) {
707     @NonNls String tag = "throws";
708     String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", "<code>@" + tag + "</code> " + exceptionClassType.getCanonicalText());
709     final String firstDeclaredException = exceptionClassType.getCanonicalText();
710     final PsiIdentifier nameIdentifier = method.getNameIdentifier();
711     return nameIdentifier != null ? createDescriptor(nameIdentifier, message,new AddMissingTagFix(tag, firstDeclaredException), manager,
712                                                      isOnTheFly) : null;
713   }
714
715   private static ProblemDescriptor createMissingTagDescriptor(PsiElement elementToHighlight,
716                                                               @NonNls String tag,
717                                                               final InspectionManager manager, boolean isOnTheFly) {
718     String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", "<code>@" + tag + "</code>");
719     return createDescriptor(elementToHighlight, message,new AddMissingTagFix(tag), manager, isOnTheFly);
720   }
721   private static ProblemDescriptor createMissingParamTagDescriptor(PsiElement elementToHighlight,
722                                                                    PsiParameter param,
723                                                                    final InspectionManager manager, boolean isOnTheFly) {
724     String message = InspectionsBundle.message("inspection.javadoc.method.problem.missing.param.tag", "<code>@param</code>", "<code>" + param.getName() + "</code>");
725     return createDescriptor(elementToHighlight, message, new AddMissingParamTagFix(param), manager, isOnTheFly);
726   }
727
728   private static class AddMissingParamTagFix extends AddMissingTagFix {
729     private final PsiParameter myParam;
730
731     public AddMissingParamTagFix(final PsiParameter param) {
732       super("param", param.getName());
733       myParam = param;
734     }
735
736     @NotNull
737     public String getName() {
738       return InspectionsBundle.message("inspection.javadoc.problem.add.param.tag", myParam.getName());
739     }
740
741     @Nullable
742     protected PsiElement getAnchor() {
743       final PsiMethod psiMethod = PsiTreeUtil.getParentOfType(myParam, PsiMethod.class);
744       LOG.assertTrue(psiMethod != null);
745       final PsiDocComment docComment = psiMethod.getDocComment();
746       LOG.assertTrue(docComment != null);
747       PsiDocTag[] tags = docComment.findTagsByName("param");
748       if (tags.length == 0) { //insert as first tag or append to description
749         tags = docComment.getTags();
750         if (tags.length == 0) return null;
751         return tags[0];
752       }
753
754       PsiParameter nextParam = PsiTreeUtil.getNextSiblingOfType(myParam, PsiParameter.class);
755       while (nextParam != null) {
756         for (PsiDocTag tag : tags) {
757           if (matches(nextParam, tag)) {
758             return tag;
759           }
760         }
761         nextParam = PsiTreeUtil.getNextSiblingOfType(nextParam, PsiParameter.class);
762       }
763
764       PsiParameter prevParam = PsiTreeUtil.getPrevSiblingOfType(myParam, PsiParameter.class);
765       while (prevParam != null) {
766         for (PsiDocTag tag : tags) {
767           if (matches(prevParam, tag)) {
768             return PsiTreeUtil.getNextSiblingOfType(tag, PsiDocTag.class);
769           }
770         }
771         prevParam = PsiTreeUtil.getPrevSiblingOfType(prevParam, PsiParameter.class);
772       }
773
774       return null;
775     }
776
777     private static boolean matches(final PsiParameter param, final PsiDocTag tag) {
778       return tag.getValueElement().getText().trim().startsWith(param.getName());
779     }
780   }
781
782   private static String extractTagDescription(PsiDocTag tag) {
783     StringBuilder buf = new StringBuilder();
784     PsiElement[] children = tag.getChildren();
785     for (PsiElement child : children) {
786       if (child instanceof PsiDocToken) {
787         PsiDocToken token = (PsiDocToken)child;
788         if (token.getTokenType() == JavaDocTokenType.DOC_COMMENT_DATA) {
789           buf.append(token.getText());
790         }
791       }
792       else if (child instanceof PsiDocTagValue) {
793         buf.append(child.getText());
794       } else if (child instanceof PsiInlineDocTag) {
795         buf.append(child.getText());
796       }
797     }
798
799     String s = buf.toString();
800     return s.trim();
801   }
802
803   private static String extractThrowsTagDescription(PsiDocTag tag) {
804     StringBuilder buf = new StringBuilder();
805     PsiElement[] children = tag.getChildren();
806     for (PsiElement child : children) {
807       if (child instanceof PsiDocToken) {
808         PsiDocToken token = (PsiDocToken)child;
809         if (token.getTokenType() == JavaDocTokenType.DOC_COMMENT_DATA) {
810           buf.append(token.getText());
811         }
812       }
813     }
814
815     return buf.toString().trim();
816   }
817
818   private void checkForPeriodInDoc(PsiDocComment docComment,
819                                    ArrayList<ProblemDescriptor> problems,
820                                    InspectionManager manager, boolean onTheFly) {
821     if (IGNORE_JAVADOC_PERIOD) return;
822     PsiDocTag[] tags = docComment.getTags();
823     int dotIndex = docComment.getText().indexOf('.');
824     int tagOffset = 0;
825     if (dotIndex >= 0) {      //need to find first valid tag
826       final PsiDocCommentOwner owner = PsiTreeUtil.getParentOfType(docComment, PsiDocCommentOwner.class);
827       for (PsiDocTag tag : tags) {
828         final String tagName = tag.getName();
829         final JavadocTagInfo tagInfo = JavaPsiFacade.getInstance(tag.getProject()).getJavadocManager().getTagInfo(tagName);
830         if (tagInfo != null && tagInfo.isValidInContext(owner) && !tagInfo.isInline()) {
831           tagOffset = tag.getTextOffset();
832           break;
833         }
834       }
835     }
836
837     if (dotIndex == -1 || tagOffset > 0 && dotIndex + docComment.getTextOffset() > tagOffset) {
838       problems.add(manager.createProblemDescriptor(docComment.getFirstChild(),
839                                                    InspectionsBundle.message("inspection.javadoc.problem.descriptor1"),
840                                                    null,
841                                                    ProblemHighlightType.GENERIC_ERROR_OR_WARNING, onTheFly, false));
842     }
843   }
844
845   @Nullable
846   private ArrayList<ProblemDescriptor> getTagValuesProblems(PsiDocCommentOwner context, PsiDocTag[] tags, InspectionManager inspectionManager,
847                                                             boolean isOnTheFly) {
848     final ArrayList<ProblemDescriptor> problems = new ArrayList<ProblemDescriptor>(2);
849     nextTag:
850     for (PsiDocTag tag : tags) {
851       final JavadocManager manager = JavaPsiFacade.getInstance(tag.getProject()).getJavadocManager();
852       String tagName = tag.getName();
853       JavadocTagInfo tagInfo = manager.getTagInfo(tagName);
854
855       if (tagInfo == null || !tagInfo.isValidInContext(context)) {
856         final StringTokenizer tokenizer = new StringTokenizer(myAdditionalJavadocTags, ", ");
857         while (tokenizer.hasMoreTokens()) {
858           if (Comparing.strEqual(tagName, tokenizer.nextToken())) continue nextTag;
859         }
860
861         if (tagInfo == null){
862           problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.wrong.tag", "<code>" + tagName + "</code>"), new AddUnknownTagToCustoms(tag), inspectionManager,
863                                         isOnTheFly));
864         } else {
865           problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.disallowed.tag", "<code>" + tagName + "</code>"), new AddUnknownTagToCustoms(tag), inspectionManager,
866                                         isOnTheFly));
867         }
868
869       }
870
871       PsiDocTagValue value = tag.getValueElement();
872       final JavadocTagInfo info = manager.getTagInfo(tagName);
873       if (info != null && !info.isValidInContext(context)) continue;
874       String message = info == null ? null : info.checkTagValue(value);
875
876       final PsiReference reference = value != null ? value.getReference() : null;
877       if (message == null && reference != null) {
878         PsiElement element = reference.resolve();
879         if (element == null) {
880           final int textOffset = value.getTextOffset();
881
882           if (textOffset == value.getTextRange().getEndOffset()) {
883             problems.add(inspectionManager.createProblemDescriptor(tag, InspectionsBundle.message("inspection.javadoc.problem.name.expected"), null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
884                                                                    isOnTheFly, true));
885           }
886         }
887       }
888
889       if (message != null) {
890         final PsiDocTagValue valueElement = tag.getValueElement();
891         if (valueElement == null){
892           problems.add(inspectionManager.createProblemDescriptor(tag, InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", "<code>" + tag.getName() + "</code>"), null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
893                                                                  isOnTheFly, true));
894         } else {
895           problems.add(createDescriptor(valueElement, message, inspectionManager, isOnTheFly));
896         }
897       }
898       checkInlineTags(inspectionManager, problems, tag.getDataElements(), manager, isOnTheFly);
899     }
900
901     return problems.isEmpty() ? null : problems;
902   }
903
904   private void checkInlineTags(final InspectionManager inspectionManager,
905                                final ArrayList<ProblemDescriptor> problems,
906                                final PsiElement[] dataElements,
907                                final JavadocManager manager, boolean isOnTheFly) {
908     for (PsiElement dataElement : dataElements) {
909       if (dataElement instanceof PsiInlineDocTag) {
910         final PsiInlineDocTag inlineDocTag = (PsiInlineDocTag)dataElement;
911         final PsiElement nameElement = inlineDocTag.getNameElement();
912         if (manager.getTagInfo(inlineDocTag.getName()) == null) {
913           if (nameElement != null) {
914             problems.add(createDescriptor(nameElement, InspectionsBundle.message("inspection.javadoc.problem.wrong.tag", "<code>" + inlineDocTag.getName() + "</code>"), new AddUnknownTagToCustoms(inlineDocTag), inspectionManager,
915                                           isOnTheFly));
916           }
917         }
918         final PsiDocTagValue value = inlineDocTag.getValueElement();
919         if (value != null) {
920           final PsiReference reference = value.getReference();
921           if (reference != null) {
922             final PsiElement ref = reference.resolve();
923             if (ref != null){
924               if (PsiTreeUtil.getParentOfType(inlineDocTag, PsiDocCommentOwner.class) == PsiTreeUtil.getParentOfType(ref, PsiDocCommentOwner.class, false)) {
925                 if (nameElement != null) {
926                   problems.add(createDescriptor(nameElement, InspectionsBundle.message("inspection.javadoc.problem.pointing.to.itself"), inspectionManager,
927                                                 isOnTheFly));
928                 }
929               }
930             }
931           }
932         }
933       }
934     }
935   }
936
937   @SuppressWarnings({"SimplifiableIfStatement"})
938   private boolean isTagRequired(PsiElement context, @NonNls String tag) {
939     if (context instanceof PsiClass) {
940       if (PsiTreeUtil.getParentOfType(context, PsiClass.class) != null) {
941         return isTagRequired(INNER_CLASS_OPTIONS, tag);
942       }
943
944       return isTagRequired(TOP_LEVEL_CLASS_OPTIONS, tag);
945     }
946
947     if (context instanceof PsiMethod) {
948       return isTagRequired(METHOD_OPTIONS, tag);
949     }
950
951     if (context instanceof PsiField) {
952       return isTagRequired(FIELD_OPTIONS, tag);
953     }
954
955     return false;
956   }
957
958   private static boolean isTagRequired(Options options, String tag) {
959     return options.REQUIRED_TAGS.contains(tag);
960   }
961
962   private boolean isJavaDocRequired(PsiModifierListOwner psiElement) {
963     final RefJavaUtil refUtil = RefJavaUtil.getInstance();
964     int actualAccess = getAccessNumber(refUtil.getAccessModifier(psiElement));
965     if (psiElement instanceof PsiClass) {
966       PsiClass psiClass = (PsiClass)psiElement;
967       if (PsiTreeUtil.getParentOfType(psiClass, PsiClass.class) != null) {
968         return actualAccess <= getAccessNumber(INNER_CLASS_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR);
969       }
970
971       return actualAccess <= getAccessNumber(TOP_LEVEL_CLASS_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR);
972     }
973
974     if (psiElement instanceof PsiMethod) {
975       psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class);
976       while (psiElement != null) {
977         actualAccess = Math.max(actualAccess, getAccessNumber(refUtil.getAccessModifier(psiElement)));
978         psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class);
979       }
980
981       return actualAccess <= getAccessNumber(METHOD_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR);
982     }
983
984     if (psiElement instanceof PsiField) {
985       psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class);
986       while (psiElement != null) {
987         actualAccess = Math.max(actualAccess, getAccessNumber(refUtil.getAccessModifier(psiElement)));
988         psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class);
989       }
990
991       return actualAccess <= getAccessNumber(FIELD_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR);
992     }
993
994     return false;
995   }
996
997   private static void checkDuplicateTags(final PsiDocTag[] tags,
998                                          ArrayList<ProblemDescriptor> problems,
999                                          final InspectionManager manager, boolean isOnTheFly) {
1000     Set<String> documentedParamNames = null;
1001     Set<String> documentedExceptions = null;
1002     Set<String> uniqueTags = null;
1003     for(PsiDocTag tag: tags) {
1004       if ("param".equals(tag.getName())) {
1005         PsiDocTagValue value = tag.getValueElement();
1006         if (value instanceof PsiDocParamRef) {
1007           PsiDocParamRef paramRef = (PsiDocParamRef)value;
1008           final PsiReference reference = paramRef.getReference();
1009           if (reference != null) {
1010             final String paramName = reference.getCanonicalText();
1011             if (documentedParamNames == null) {
1012               documentedParamNames = new HashSet<String>();
1013             }
1014             if (documentedParamNames.contains(paramName)) {
1015               problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.duplicate.param", paramName), manager,
1016                                             isOnTheFly));
1017             }
1018             documentedParamNames.add(paramName);
1019           }
1020         }
1021       }
1022       else if ("throws".equals(tag.getName()) || "exception".equals(tag.getName())) {
1023         PsiDocTagValue value = tag.getValueElement();
1024         if (value != null) {
1025           final PsiElement firstChild = value.getFirstChild();
1026           if (firstChild != null && firstChild.getFirstChild() instanceof PsiJavaCodeReferenceElement) {
1027             PsiJavaCodeReferenceElement refElement = (PsiJavaCodeReferenceElement) firstChild.getFirstChild();
1028             if (refElement != null) {
1029               PsiElement element = refElement.resolve();
1030               if (element instanceof PsiClass) {
1031                 String fqName = ((PsiClass)element).getQualifiedName();
1032                 if (documentedExceptions == null) {
1033                   documentedExceptions = new HashSet<String>();
1034                 }
1035                 if (documentedExceptions.contains(fqName)) {
1036                   problems.add(createDescriptor(tag.getNameElement(),
1037                                                 InspectionsBundle.message("inspection.javadoc.problem.duplicate.throws", fqName),
1038                                                 manager, isOnTheFly));
1039                 }
1040                 documentedExceptions.add(fqName);
1041               }
1042             }
1043           }
1044         }
1045       }
1046       else if (JavaDocLocalInspection.ourUniqueTags.contains(tag.getName())) {
1047         if (uniqueTags == null) {
1048           uniqueTags = new HashSet<String>();
1049         }
1050         if (uniqueTags.contains(tag.getName())) {
1051           problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.duplicate.tag", tag.getName()), manager,
1052                                         isOnTheFly));
1053         }
1054         uniqueTags.add(tag.getName());
1055       }
1056     }
1057   }
1058
1059   private static int getAccessNumber(@NonNls String accessModifier) {
1060     if (accessModifier.startsWith("none")) return 0;
1061     if (accessModifier.startsWith("public")) return 1;
1062     if (accessModifier.startsWith("protected")) return 2;
1063     if (accessModifier.startsWith("package")) return 3;
1064     if (accessModifier.startsWith("private")) return 4;
1065
1066     return 5;
1067   }
1068
1069   @NotNull
1070   public String getDisplayName() {
1071     return InspectionsBundle.message("inspection.javadoc.display.name");
1072   }
1073
1074   @NotNull
1075   public String getGroupDisplayName() {
1076     return "";
1077   }
1078
1079   @NotNull
1080   public String getShortName() {
1081     return SHORT_NAME;
1082   }
1083
1084   private class AddUnknownTagToCustoms implements LocalQuickFix {
1085     PsiDocTag myTag;
1086
1087     public AddUnknownTagToCustoms(PsiDocTag tag) {
1088       myTag = tag;
1089     }
1090
1091     @NotNull
1092     public String getName() {
1093       return QuickFixBundle.message("add.doctag.to.custom.tags", myTag.getName());
1094     }
1095
1096     @NotNull
1097     public String getFamilyName() {
1098      return QuickFixBundle.message("fix.javadoc.family");
1099    }
1100
1101     public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
1102       if (myTag == null || !myTag.isValid()) return;
1103       if (myAdditionalJavadocTags.length() > 0) {
1104         myAdditionalJavadocTags += "," + myTag.getName();
1105       }
1106       else {
1107         myAdditionalJavadocTags = myTag.getName();
1108       }
1109       final InspectionProfile inspectionProfile =
1110         InspectionProjectProfileManager.getInstance(project).getInspectionProfile();
1111       //correct save settings
1112       ((ModifiableModel)inspectionProfile).isProperSetting(HighlightDisplayKey.find(SHORT_NAME));
1113       InspectionProfileManager.getInstance().fireProfileChanged(inspectionProfile);
1114       //TODO lesya
1115
1116       /*
1117
1118       try {
1119         inspectionProfile.save();
1120       }
1121       catch (IOException e) {
1122         Messages.showErrorDialog(project, e.getMessage(), CommonBundle.getErrorTitle());
1123       }
1124
1125       */
1126     }
1127   }
1128 }