IDEA-CR-880 (utility method used)
[idea/community.git] / java / java-impl / src / com / intellij / codeInspection / defaultFileTemplateUsage / FileHeaderChecker.java
1 /*
2  * Copyright 2000-2014 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.codeInspection.defaultFileTemplateUsage;
17
18 import com.intellij.codeInsight.CodeInsightUtil;
19 import com.intellij.codeInspection.*;
20 import com.intellij.ide.fileTemplates.FileTemplate;
21 import com.intellij.ide.fileTemplates.FileTemplateManager;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.psi.JavaPsiFacade;
26 import com.intellij.psi.PsiComment;
27 import com.intellij.psi.PsiElement;
28 import com.intellij.psi.PsiFile;
29 import com.intellij.psi.javadoc.PsiDocComment;
30 import com.intellij.psi.util.PsiTreeUtil;
31 import com.intellij.util.containers.ContainerUtil;
32 import gnu.trove.TIntObjectHashMap;
33 import org.jetbrains.annotations.NotNull;
34
35 import java.io.IOException;
36 import java.util.Arrays;
37 import java.util.List;
38 import java.util.Properties;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41
42 /**
43  * @author cdr
44  */
45 public class FileHeaderChecker {
46   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.defaultFileTemplateUsage.FileHeaderChecker");
47
48   static ProblemDescriptor checkFileHeader(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean onTheFly) {
49     TIntObjectHashMap<String> offsetToProperty = new TIntObjectHashMap<String>();
50     FileTemplate defaultTemplate = FileTemplateManager.getInstance().getDefaultTemplate(FileTemplateManager.FILE_HEADER_TEMPLATE_NAME);
51     Pattern pattern = getTemplatePattern(defaultTemplate, file.getProject(), offsetToProperty);
52     Matcher matcher = pattern.matcher(file.getViewProvider().getContents());
53     if (!matcher.matches()) {
54       return null;
55     }
56
57     PsiComment element = PsiTreeUtil.findElementOfClassAtRange(file, matcher.start(1), matcher.end(1), PsiComment.class);
58     if (element == null) {
59       return null;
60     }
61
62     LocalQuickFix[] fixes = createQuickFix(matcher, offsetToProperty);
63     String description = InspectionsBundle.message("default.file.template.description");
64     return manager.createProblemDescriptor(element, description, onTheFly, fixes, ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
65   }
66
67   public static Pattern getTemplatePattern(@NotNull FileTemplate template,
68                                            @NotNull Project project,
69                                            @NotNull TIntObjectHashMap<String> offsetToProperty) {
70     String templateText = template.getText().trim();
71     String regex = templateToRegex(templateText, offsetToProperty, project);
72     regex = StringUtil.replace(regex, "with", "(?:with|by)");
73     regex = ".*(" + regex + ").*";
74     return Pattern.compile(regex, Pattern.DOTALL);
75   }
76
77   private static Properties computeProperties(final Matcher matcher, final TIntObjectHashMap<String> offsetToProperty) {
78     Properties properties = new Properties(FileTemplateManager.getInstance().getDefaultProperties());
79
80     int[] offsets = offsetToProperty.keys();
81     Arrays.sort(offsets);
82     for (int i = 0; i < offsets.length; i++) {
83       final int offset = offsets[i];
84       String propName = offsetToProperty.get(offset);
85       int groupNum = i + 2; // first group is whole doc comment
86       String propValue = matcher.group(groupNum);
87       properties.setProperty(propName, propValue);
88     }
89
90     return properties;
91   }
92
93   private static LocalQuickFix[] createQuickFix(final Matcher matcher, final TIntObjectHashMap<String> offsetToProperty) {
94     final FileTemplate template = FileTemplateManager.getInstance().getPattern(FileTemplateManager.FILE_HEADER_TEMPLATE_NAME);
95
96     ReplaceWithFileTemplateFix replaceTemplateFix = new ReplaceWithFileTemplateFix() {
97       @Override
98       public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
99         PsiElement element = descriptor.getPsiElement();
100         if (element == null || !element.isValid()) return;
101         if (!CodeInsightUtil.preparePsiElementsForWrite(element)) return;
102
103         String newText;
104         try {
105           newText = template.getText(computeProperties(matcher, offsetToProperty));
106         }
107         catch (IOException e) {
108           LOG.error(e);
109           return;
110         }
111
112         PsiDocComment newComment = JavaPsiFacade.getElementFactory(project).createDocCommentFromText(newText);
113         element.replace(newComment);
114       }
115     };
116
117     LocalQuickFix editFileTemplateFix = DefaultFileTemplateUsageInspection.createEditFileTemplateFix(template, replaceTemplateFix);
118     return template.isDefault() ? new LocalQuickFix[]{editFileTemplateFix} : new LocalQuickFix[]{replaceTemplateFix, editFileTemplateFix};
119   }
120
121   private static String templateToRegex(String text, TIntObjectHashMap<String> offsetToProperty, Project project) {
122     List<Object> properties = ContainerUtil.newArrayList(FileTemplateManager.getInstance().getDefaultProperties(project).keySet());
123     properties.add("PACKAGE_NAME");
124
125     String regex = escapeRegexChars(text);
126     // first group is a whole file header
127     int groupNumber = 1;
128     for (Object property : properties) {
129       String name = property.toString();
130       String escaped = escapeRegexChars("${" + name + "}");
131       boolean first = true;
132       for (int i = regex.indexOf(escaped); i != -1 && i < regex.length(); i = regex.indexOf(escaped, i + 1)) {
133         String replacement = first ? "([^\\n]*)" : "\\" + groupNumber;
134         int delta = escaped.length() - replacement.length();
135         int[] offs = offsetToProperty.keys();
136         for (int off : offs) {
137           if (off > i) {
138             String prop = offsetToProperty.remove(off);
139             offsetToProperty.put(off - delta, prop);
140           }
141         }
142         offsetToProperty.put(i, name);
143         regex = regex.substring(0, i) + replacement + regex.substring(i + escaped.length());
144         if (first) {
145           groupNumber++;
146           first = false;
147         }
148       }
149     }
150     return regex;
151   }
152
153   private static String escapeRegexChars(String regex) {
154     regex = StringUtil.replace(regex, "|", "\\|");
155     regex = StringUtil.replace(regex, ".", "\\.");
156     regex = StringUtil.replace(regex, "*", "\\*");
157     regex = StringUtil.replace(regex, "+", "\\+");
158     regex = StringUtil.replace(regex, "?", "\\?");
159     regex = StringUtil.replace(regex, "$", "\\$");
160     regex = StringUtil.replace(regex, "(", "\\(");
161     regex = StringUtil.replace(regex, ")", "\\)");
162     regex = StringUtil.replace(regex, "[", "\\[");
163     regex = StringUtil.replace(regex, "]", "\\]");
164     regex = StringUtil.replace(regex, "{", "\\{");
165     regex = StringUtil.replace(regex, "}", "\\}");
166     return regex;
167   }
168 }