configurable intelliLang performance
[fedora-idea.git] / plugins / IntelliLang / src / org / intellij / plugins / intelliLang / pattern / PatternValidator.java
blob6dbf7b1a5207afaa973ce13fdfa550fce58718d9
1 /*
2 * Copyright 2006 Sascha Weinreuter
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 org.intellij.plugins.intelliLang.pattern;
18 import com.intellij.codeInspection.LocalInspectionTool;
19 import com.intellij.codeInspection.LocalQuickFix;
20 import com.intellij.codeInspection.ProblemDescriptor;
21 import com.intellij.codeInspection.ProblemsHolder;
22 import com.intellij.ide.DataManager;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.util.Comparing;
25 import com.intellij.openapi.util.Key;
26 import com.intellij.openapi.util.text.StringUtil;
27 import com.intellij.psi.*;
28 import com.intellij.psi.util.CachedValue;
29 import com.intellij.psi.util.CachedValueProvider;
30 import com.intellij.psi.util.PsiTreeUtil;
31 import com.intellij.refactoring.JavaRefactoringActionHandlerFactory;
32 import com.intellij.refactoring.RefactoringActionHandler;
33 import com.intellij.util.SmartList;
34 import org.intellij.plugins.intelliLang.Configuration;
35 import org.intellij.plugins.intelliLang.util.AnnotateFix;
36 import org.intellij.plugins.intelliLang.util.AnnotationUtilEx;
37 import org.intellij.plugins.intelliLang.util.PsiUtilEx;
38 import org.intellij.plugins.intelliLang.util.SubstitutedExpressionEvaluationHelper;
39 import org.jetbrains.annotations.NonNls;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
43 import javax.swing.*;
44 import java.awt.*;
45 import java.awt.event.ItemEvent;
46 import java.awt.event.ItemListener;
47 import java.text.MessageFormat;
48 import java.util.List;
49 import java.util.regex.Pattern;
50 import java.util.regex.PatternSyntaxException;
52 /**
53 * Inspection that validates if string literals, compile-time constants or
54 * substituted expressions match the pattern of the context they're used in.
56 public class PatternValidator extends LocalInspectionTool {
57 private static final Key<CachedValue<Pattern>> COMPLIED_PATTERN = Key.create("COMPILED_PATTERN");
59 public boolean CHECK_NON_CONSTANT_VALUES = true;
61 private final Configuration myConfiguration;
63 public PatternValidator() {
64 myConfiguration = Configuration.getInstance();
67 public boolean isEnabledByDefault() {
68 return true;
71 @NotNull
72 public String getGroupDisplayName() {
73 return InspectionProvider.PATTERN_VALIDATION;
76 @NotNull
77 public String getDisplayName() {
78 return "Validate Annotated Patterns";
81 @Nullable
82 public JComponent createOptionsPanel() {
83 final JPanel jPanel = new JPanel(new BorderLayout());
84 final JCheckBox jCheckBox = new JCheckBox("Flag non compile-time constant expressions");
85 jCheckBox.setToolTipText(
86 "If checked, the inspection will flag expressions with unknown values " + "and offer to add a substitution (@Subst) annotation");
87 jCheckBox.setSelected(CHECK_NON_CONSTANT_VALUES);
88 jCheckBox.addItemListener(new ItemListener() {
89 public void itemStateChanged(ItemEvent e) {
90 CHECK_NON_CONSTANT_VALUES = jCheckBox.isSelected();
92 });
93 jPanel.add(jCheckBox, BorderLayout.NORTH);
94 return jPanel;
97 @NotNull
98 @NonNls
99 public String getShortName() {
100 return "PatternValidation";
103 @NotNull
104 public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
105 return new JavaElementVisitor() {
107 public final void visitReferenceExpression(PsiReferenceExpression expression) {
108 visitExpression(expression);
111 @Override
112 public void visitExpression(PsiExpression expression) {
113 final PsiElement element = expression.getParent();
114 if (element instanceof PsiExpressionList) {
115 // this checks method arguments
116 check(expression, holder, false);
118 else if (element instanceof PsiNameValuePair) {
119 final PsiNameValuePair valuePair = (PsiNameValuePair)element;
120 final String name = valuePair.getName();
121 if (name == null || name.equals(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME)) {
122 // check whether @Subst complies with pattern
123 check(expression, holder, true);
128 @Override
129 public void visitReturnStatement(PsiReturnStatement statement) {
130 final PsiExpression returnValue = statement.getReturnValue();
131 if (returnValue != null) {
132 check(returnValue, holder, false);
136 @Override
137 public void visitVariable(PsiVariable var) {
138 final PsiExpression initializer = var.getInitializer();
139 if (initializer != null) {
140 // variable/field initializer
141 check(initializer, holder, false);
145 @Override
146 public void visitAssignmentExpression(PsiAssignmentExpression expression) {
147 final PsiExpression e = expression.getRExpression();
148 if (e != null) {
149 check(e, holder, false);
151 visitExpression(expression);
154 private void check(@NotNull PsiExpression expression, ProblemsHolder holder, boolean isAnnotationValue) {
155 if (expression instanceof PsiConditionalExpression) {
156 final PsiConditionalExpression expr = (PsiConditionalExpression)expression;
157 PsiExpression e = expr.getThenExpression();
158 if (e != null) {
159 check(e, holder, isAnnotationValue);
161 e = expr.getElseExpression();
162 if (e != null) {
163 check(e, holder, isAnnotationValue);
166 else {
167 final PsiType type = expression.getType();
168 // optimiziation: only check expressions of type String
169 if (type != null && PsiUtilEx.isString(type)) {
170 final PsiModifierListOwner element;
171 if (isAnnotationValue) {
172 final PsiAnnotation psiAnnotation = PsiTreeUtil.getParentOfType(expression, PsiAnnotation.class);
173 if (psiAnnotation != null && myConfiguration.getSubstAnnotationClass().equals(psiAnnotation.getQualifiedName())) {
174 element = PsiTreeUtil.getParentOfType(expression, PsiModifierListOwner.class);
176 else {
177 return;
180 else {
181 element = AnnotationUtilEx.getAnnotatedElementFor(expression, AnnotationUtilEx.LookupType.PREFER_CONTEXT);
183 if (element != null && PsiUtilEx.isLanguageAnnotationTarget(element)) {
184 PsiAnnotation[] annotations = AnnotationUtilEx.getAnnotationFrom(element, myConfiguration.getPatternAnnotationPair(), true);
185 checkExpression(expression, annotations, holder);
193 private void checkExpression(PsiExpression expression, final PsiAnnotation[] annotations, ProblemsHolder holder) {
194 if (annotations.length == 0) return;
195 final PsiAnnotation psiAnnotation = annotations[0];
197 // cache compiled pattern with annotation
198 CachedValue<Pattern> p = psiAnnotation.getUserData(COMPLIED_PATTERN);
199 if (p == null) {
200 final CachedValueProvider<Pattern> provider = new CachedValueProvider<Pattern>() {
201 public Result<Pattern> compute() {
202 final String pattern = AnnotationUtilEx.calcAnnotationValue(psiAnnotation, "value");
203 Pattern p = null;
204 if (pattern != null) {
205 try {
206 p = Pattern.compile(pattern);
208 catch (PatternSyntaxException e) {
209 // pattern stays null
212 return Result.create(p, (Object[])annotations);
215 p = expression.getManager().getCachedValuesManager().createCachedValue(provider, false);
216 psiAnnotation.putUserData(COMPLIED_PATTERN, p);
219 final Pattern pattern = p.getValue();
220 if (pattern == null) return;
222 List<PsiExpression> nonConstantElements = new SmartList<PsiExpression>();
223 final Object result = new SubstitutedExpressionEvaluationHelper(expression.getProject()).computeExpression(
224 expression, myConfiguration.isUseDfaIfAvailable(), false, nonConstantElements);
225 final String o = result == null ? null : String.valueOf(result);
226 if (o != null) {
227 if (!pattern.matcher(o).matches()) {
228 if (annotations.length > 1) {
229 // the last element contains the element's actual annotation
230 final String fqn = annotations[annotations.length - 1].getQualifiedName();
231 assert fqn != null;
233 final String name = StringUtil.getShortName(fqn);
234 holder.registerProblem(expression, MessageFormat.format("Expression ''{0}'' doesn''t match ''{1}'' pattern: {2}", o, name,
235 pattern.pattern()));
237 else {
238 holder.registerProblem(expression,
239 MessageFormat.format("Expression ''{0}'' doesn''t match pattern: {1}", o, pattern.pattern()));
243 else if (CHECK_NON_CONSTANT_VALUES) {
244 for (PsiExpression expr : nonConstantElements) {
245 final PsiElement e;
246 if (expr instanceof PsiReferenceExpression) {
247 e = ((PsiReferenceExpression)expr).resolve();
249 else if (expr instanceof PsiMethodCallExpression) {
250 e = ((PsiMethodCallExpression)expr).getMethodExpression().resolve();
252 else {
253 e = expr;
255 final PsiModifierListOwner owner = e instanceof PsiModifierListOwner? (PsiModifierListOwner)e : null;
256 LocalQuickFix quickFix;
257 if (owner != null && PsiUtilEx.isLanguageAnnotationTarget(owner)) {
258 PsiAnnotation[] resolvedAnnos = AnnotationUtilEx.getAnnotationFrom(owner, myConfiguration.getPatternAnnotationPair(), true);
259 if (resolvedAnnos.length == 2 && annotations.length == 2 && Comparing.strEqual(resolvedAnnos[1].getQualifiedName(), annotations[1].getQualifiedName())) {
260 // both target and source annotated indirectly with the same anno
261 return;
264 final String classname = Configuration.getInstance().getSubstAnnotationPair().first;
265 final AnnotateFix fix = new AnnotateFix((PsiModifierListOwner)e, classname);
266 quickFix = fix.canApply() ? fix : new IntroduceVariableFix(expr);
268 else {
269 quickFix = new IntroduceVariableFix(expr);
271 holder.registerProblem(expr, "Unsubstituted expression", quickFix);
276 private static class IntroduceVariableFix implements LocalQuickFix {
277 private final PsiExpression myExpr;
279 public IntroduceVariableFix(PsiExpression expr) {
280 myExpr = expr;
283 @NotNull
284 public String getName() {
285 return "Introduce Variable";
288 @NotNull
289 public String getFamilyName() {
290 return getName();
293 public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
294 final RefactoringActionHandler handler = JavaRefactoringActionHandlerFactory.getInstance().createIntroduceVariableHandler();
295 handler.invoke(project, new PsiElement[]{myExpr}, DataManager.getInstance().getDataContext());
296 // how to automatically annotate the variable after it has been introduced?