properties: filter nulls while checking of incomplete properties
[idea/community.git] / plugins / properties / src / com / intellij / lang / properties / editor / inspections / incomplete / IncompletePropertyInspection.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.lang.properties.editor.inspections.incomplete;
17
18 import com.intellij.codeInspection.InspectionProfile;
19 import com.intellij.codeInspection.LocalInspectionTool;
20 import com.intellij.codeInspection.ProblemHighlightType;
21 import com.intellij.codeInspection.QuickFix;
22 import com.intellij.codeInspection.ex.DisableInspectionToolAction;
23 import com.intellij.codeInspection.ex.UnfairLocalInspectionTool;
24 import com.intellij.lang.properties.*;
25 import com.intellij.lang.properties.ResourceBundle;
26 import com.intellij.lang.properties.editor.inspections.ResourceBundleEditorInspection;
27 import com.intellij.lang.properties.editor.inspections.ResourceBundleEditorProblemDescriptor;
28 import com.intellij.lang.properties.psi.PropertiesFile;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.util.InvalidDataException;
31 import com.intellij.openapi.util.WriteExternalException;
32 import com.intellij.openapi.util.text.StringUtil;
33 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
34 import com.intellij.psi.PsiElement;
35 import com.intellij.psi.SmartPointerManager;
36 import com.intellij.psi.SmartPsiElementPointer;
37 import com.intellij.util.containers.ContainerUtil;
38 import org.jdom.Element;
39 import org.jetbrains.annotations.Nls;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42
43 import javax.swing.*;
44 import java.util.*;
45 import java.util.function.Function;
46
47 /**
48  * @author Dmitry Batkovich
49  */
50 public class IncompletePropertyInspection extends LocalInspectionTool implements ResourceBundleEditorInspection,
51                                                                                  UnfairLocalInspectionTool {
52   private static final String SUFFIXES_TAG_NAME = "suffixes";
53   private static final String TOOL_KEY = "IncompleteProperty";
54
55   SortedSet<String> mySuffixes = new TreeSet<>();
56
57   @Nullable
58   @Override
59   public JComponent createOptionsPanel() {
60     return new IncompletePropertyInspectionOptionsPanel(mySuffixes).buildPanel();
61   }
62
63   @Override
64   public void readSettings(@NotNull Element node) throws InvalidDataException {
65     mySuffixes.clear();
66     final Element element = node.getChild(SUFFIXES_TAG_NAME);
67     if (element != null) {
68       mySuffixes.addAll(StringUtil.split(element.getText(), ",", true, false));
69     }
70   }
71
72   @Override
73   public void writeSettings(@NotNull Element node) throws WriteExternalException {
74     if (!mySuffixes.isEmpty()) {
75       node.addContent(new Element(SUFFIXES_TAG_NAME).setText(StringUtil.join(mySuffixes, ",")));
76     }
77   }
78
79   @NotNull
80   @Override
81   public Function<IProperty[], ResourceBundleEditorProblemDescriptor[]> buildPropertyGroupVisitor(@NotNull ResourceBundle resourceBundle) {
82     return properties -> !isPropertyComplete(properties, resourceBundle)
83     ? new ResourceBundleEditorProblemDescriptor[]{new ResourceBundleEditorProblemDescriptor(ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
84                                                                                             PropertiesBundle.message(
85                                                                                               "incomplete.property.inspection.description",
86                                                                                               properties[0].getName()),
87                                                                                             new IgnoreLocalesQuickFix(properties[0],
88                                                                                                                       resourceBundle))}
89     : null;
90   }
91
92   @NotNull
93   public static IncompletePropertyInspection getInstance(PsiElement element) {
94     final InspectionProjectProfileManager profileManager = InspectionProjectProfileManager.getInstance(element.getProject());
95     InspectionProfile inspectionProfile = profileManager.getCurrentProfile();
96     return (IncompletePropertyInspection) inspectionProfile.getUnwrappedTool(TOOL_KEY, element);
97   }
98
99   private static class IgnoreLocalesQuickFix implements QuickFix<ResourceBundleEditorProblemDescriptor> {
100     private final ResourceBundle myResourceBundle;
101     private final SmartPsiElementPointer<PsiElement> myElementPointer;
102
103     public IgnoreLocalesQuickFix(IProperty property, ResourceBundle bundle) {
104       myElementPointer = SmartPointerManager.getInstance(bundle.getProject()).createSmartPsiElementPointer(property.getPsiElement());
105       myResourceBundle = bundle;
106     }
107
108     @Nls
109     @NotNull
110     @Override
111     public String getName() {
112       return getFamilyName();
113     }
114
115     @Nls
116     @NotNull
117     @Override
118     public String getFamilyName() {
119       return PropertiesBundle.message("incomplete.property.quick.fix.name");
120     }
121
122     @Override
123     public void applyFix(@NotNull Project project, @NotNull ResourceBundleEditorProblemDescriptor descriptor) {
124       final PsiElement element = myElementPointer.getElement();
125       if (element == null) {
126         return;
127       }
128       final IProperty property = PropertiesImplUtil.getProperty(element);
129       if (property == null) {
130         return;
131       }
132
133       final IncompletePropertyInspection inspection = getInstance(element);
134       final List<PropertiesFile> allFilesWithoutTranslation = inspection.getPropertiesFilesWithoutTranslation(myResourceBundle, property.getKey());
135
136       if (allFilesWithoutTranslation.isEmpty()) {
137         return;
138       }
139
140
141       final TreeSet<String> suffixesToIgnore = new TreeSet<>(ContainerUtil.map(allFilesWithoutTranslation,
142                                                                                file -> PropertiesUtil.getSuffix(file)));
143       if (new IncompletePropertyInspectionOptionsPanel(suffixesToIgnore).showDialogAndGet(project)) {
144         DisableInspectionToolAction.modifyAndCommitProjectProfile(
145           modifiableModel -> ((IncompletePropertyInspection)modifiableModel.getInspectionTool(TOOL_KEY, element).getTool()).addSuffixes(suffixesToIgnore), project);
146       }
147     }
148   }
149
150   public boolean isPropertyComplete(final String key, final ResourceBundle resourceBundle) {
151     return isPropertyComplete(resourceBundle.getPropertiesFiles().stream().map(f -> f.findPropertyByKey(key)).filter(Objects::nonNull).toArray(IProperty[]::new), resourceBundle);
152   }
153
154   private boolean isPropertyComplete(final IProperty[] properties, final ResourceBundle resourceBundle) {
155     final Set<PropertiesFile> existed = ContainerUtil.map2Set(properties, IProperty::getPropertiesFile);
156     for (PropertiesFile file : resourceBundle.getPropertiesFiles()) {
157       if (!existed.contains(file) && !getIgnoredSuffixes().contains(PropertiesUtil.getSuffix(file))) {
158         return false;
159       }
160     }
161     return true;
162   }
163
164   public Set<String> getIgnoredSuffixes() {
165     return mySuffixes;
166   }
167
168   public List<PropertiesFile> getPropertiesFilesWithoutTranslation(final ResourceBundle resourceBundle, final String key) {
169     return ContainerUtil.filter(resourceBundle.getPropertiesFiles(), propertiesFile -> propertiesFile.findPropertyByKey(key) == null &&
170                                                                                    !getIgnoredSuffixes().contains(PropertiesUtil.getSuffix(propertiesFile)));
171   }
172
173   public void addSuffixes(Collection<String> suffixes) {
174     mySuffixes.addAll(suffixes);
175   }
176 }