IDEADEV-31824 (Incorrect "manual array copy" warning)
[fedora-idea.git] / plugins / InspectionGadgets / src / com / siyeh / ig / portability / HardcodedFileSeparatorsInspection.java
blob17c570768b382a89ce1dd346e559ee3f0f4f3145
1 /*
2 * Copyright 2003-2007 Dave Griffith, Bas Leijdekkers, Mark Scott
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.siyeh.ig.portability;
18 import com.intellij.psi.PsiElement;
19 import com.intellij.psi.PsiLiteralExpression;
20 import com.intellij.psi.PsiMethodCallExpression;
21 import com.intellij.psi.PsiType;
22 import com.siyeh.InspectionGadgetsBundle;
23 import com.siyeh.ig.BaseInspection;
24 import com.siyeh.ig.BaseInspectionVisitor;
25 import com.siyeh.ig.portability.mediatype.*;
26 import com.siyeh.ig.psiutils.MethodCallUtils;
27 import com.siyeh.ig.psiutils.TypeUtils;
28 import com.siyeh.ig.ui.SingleCheckboxOptionsPanel;
29 import org.jetbrains.annotations.NonNls;
30 import org.jetbrains.annotations.NotNull;
32 import javax.swing.*;
33 import java.util.Arrays;
34 import java.util.HashSet;
35 import java.util.Set;
36 import java.util.TimeZone;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
40 public class HardcodedFileSeparatorsInspection extends BaseInspection {
42 private static final char BACKSLASH = '\\';
43 private static final char SLASH = '/';
44 /**
45 * The regular expression pattern that matches strings which are likely to
46 * be date formats. <code>Pattern</font></b> instances are immutable, so
47 * caching the pattern like this is still thread-safe.
49 @NonNls private static final Pattern DATE_FORMAT_PATTERN =
50 Pattern.compile("\\b[dDmM]+/[dDmM]+(/[yY]+)?");
51 /**
52 * A regular expression that matches strings which represent example MIME
53 * media types.
55 @NonNls private static final String EXAMPLE_MIME_MEDIA_TYPE_PATTERN =
56 "example/\\p{Alnum}+(?:[\\.\\-\\\\+]\\p{Alnum}+)*";
57 /**
58 * A regular expression pattern that matches strings which start with a URL
59 * protocol, as they're likely to actually be URLs.
61 @NonNls private static final Pattern URL_PATTERN =
62 Pattern.compile("^[a-z][a-z0-9+\\-:]+://.*$");
64 /**
65 * All mimetypes, see http://www.iana.org/assignments/media-types/
67 private static final Set<String> mimeTypes = new HashSet();
68 static {
69 for (ImageMediaType imageMediaType : ImageMediaType.values()){
70 mimeTypes.add(imageMediaType.toString());
72 for (ApplicationMediaType applicationMediaType :
73 ApplicationMediaType.values()){
74 mimeTypes.add(applicationMediaType.toString());
76 for (AudioMediaType audioMediaType : AudioMediaType.values()){
77 mimeTypes.add(audioMediaType.toString());
79 for (MessageMediaType messageMediaType : MessageMediaType.values()){
80 mimeTypes.add(messageMediaType.toString());
82 for (ModelMediaType modelMediaType : ModelMediaType.values()){
83 mimeTypes.add(modelMediaType.toString());
85 for (MultipartMediaType multipartMediaType :
86 MultipartMediaType.values()){
87 mimeTypes.add(multipartMediaType.toString());
89 for (TextMediaType textMediaType : TextMediaType.values()){
90 mimeTypes.add(textMediaType.toString());
92 for (VideoMediaType videoContentTypeMediaType :
93 VideoMediaType.values()){
94 mimeTypes.add(videoContentTypeMediaType.toString());
98 /**
99 * All {@link TimeZone} IDs.
101 private static final Set<String> timeZoneIds = new HashSet();
102 static {
103 timeZoneIds.addAll(Arrays.asList(TimeZone.getAvailableIDs()));
107 * @noinspection PublicField
109 public boolean m_recognizeExampleMediaType = false;
111 @NotNull
112 public String getID(){
113 return "HardcodedFileSeparator";
116 @NotNull
117 public String getDisplayName(){
118 return InspectionGadgetsBundle.message(
119 "hardcoded.file.separator.display.name");
122 @NotNull
123 public String buildErrorString(Object... infos){
124 return InspectionGadgetsBundle.message(
125 "hardcoded.file.separator.problem.descriptor");
128 public JComponent createOptionsPanel() {
129 return new SingleCheckboxOptionsPanel(
130 InspectionGadgetsBundle.message(
131 "hardcoded.file.separator.include.option"),
132 this, "m_recognizeExampleMediaType");
135 public BaseInspectionVisitor buildVisitor(){
136 return new HardcodedFileSeparatorsVisitor();
139 private class HardcodedFileSeparatorsVisitor
140 extends BaseInspectionVisitor{
142 @Override public void visitLiteralExpression(
143 @NotNull PsiLiteralExpression expression){
144 super.visitLiteralExpression(expression);
145 final PsiType type = expression.getType();
146 if(TypeUtils.isJavaLangString(type)){
147 final String value = (String) expression.getValue();
148 if (!isHardcodedFilenameString(value)) {
149 return;
151 final PsiElement parent = expression.getParent();
152 final PsiElement grandParent = parent.getParent();
153 if (grandParent instanceof PsiMethodCallExpression) {
154 final PsiMethodCallExpression methodCallExpression =
155 (PsiMethodCallExpression)grandParent;
156 if (MethodCallUtils.isCallToRegexMethod(
157 methodCallExpression)) {
158 return;
161 registerError(expression);
162 } else if(type != null && type.equals(PsiType.CHAR)){
163 final Character value = (Character) expression.getValue();
164 if(value == null){
165 return;
167 final char unboxedValue = value.charValue();
168 if(unboxedValue == BACKSLASH || unboxedValue == SLASH){
169 registerError(expression);
175 * Check whether a string is likely to be a filename containing one or more
176 * hard-coded file separator characters. The method does some simple
177 * analysis of the string to determine whether it's likely to be some other
178 * type of data - a URL, a date format, or an XML fragment - before deciding
179 * that the string is a filename.
181 * @param string The string to examine.
182 * @return <code>true</code> if the string is likely to be a filename
183 * with hardcoded file separators, <code>false</code>
184 * otherwise.
186 private boolean isHardcodedFilenameString(String string){
187 if(string == null){
188 return false;
190 if(string.indexOf((int) '/') == -1 &&
191 string.indexOf((int) '\\') == -1){
192 return false;
194 final char startChar = string.charAt(0);
195 if(Character.isLetter(startChar) && string.charAt(1) == ':'){
196 return true;
198 if(isXMLString(string)){
199 return false;
201 if(isDateFormatString(string)){
202 return false;
204 if(isURLString(string)){
205 return false;
207 if(isMediaTypeString(string)){
208 return false;
210 return !isTimeZoneIdString(string);
214 * Check whether a string containing at least one '/' or '\' character is
215 * likely to be a fragment of XML.
217 * @param string The string to examine.
218 * @return <code>true</code> if the string is likely to be an XML
219 * fragment, or <code>false</code> if not.
221 private boolean isXMLString(String string){
222 return string.contains("</") || string.contains("/>");
226 * Check whether a string containing at least one '/' or '\' character is
227 * likely to be a date format string.
229 * @param string The string to check.
230 * @return <code>true</code> if the string is likely to be a date
231 * string, <code>false</code> if not.
233 private boolean isDateFormatString(String string){
234 if(string.length() < 3){
235 // A string this short is very unlikely to be a date format.
236 return false;
238 final int strLength = string.length();
239 final char startChar = string.charAt(0);
240 final char endChar = string.charAt(strLength - 1);
241 if(startChar == '/' || endChar == '/'){
242 // Most likely it's a filename if the string starts or ends
243 // with a slash.
244 return false;
245 } else if(Character.isLetter(startChar) && string.charAt(1) == ':'){
246 // Most likely this is a Windows-style full file name.
247 return false;
249 final Matcher dateFormatMatcher = DATE_FORMAT_PATTERN.matcher(string);
250 return dateFormatMatcher.find();
254 * Checks whether a string containing at least one '/' or '\' character is
255 * likely to be a URL.
257 * @param string The string to check.
258 * @return <code>true</code> if the string is likely to be a URL,
259 * <code>false</code> if not.
261 private boolean isURLString(String string){
262 final Matcher urlMatcher = URL_PATTERN.matcher(string);
263 return urlMatcher.find();
267 * Checks whether a string containing at least one '/' character is
268 * likely to be a MIME media type. See the
269 * <a href="http://www.iana.org/assignments/media-types/">IANA</a>
270 * documents for registered MIME media types.
272 * @param string The string to check.
273 * @return <code>true</code> if the string is likely to be a MIME
274 * media type, <code>false</code> if not.
276 private boolean isMediaTypeString(String string){
277 // IANA doesn't specify a pattern for the subtype of example content
278 // types but other subtypes seem to be one or more groups of
279 // alphanumerics characters, the groups being separated by a single
280 // period (.), hyphen (-) or plus (+) character
282 // Valid examples:
284 // "example/foo"
285 // "example/foo.bar"
286 // "example/foo-bar+baz"
287 // "example/foo1.2006-bar"
289 // Invalid examples:
291 // "example/foo$bar" ($ isn't a valid separator)
292 // "example/foo." (can't end with a separator)
294 if(m_recognizeExampleMediaType &&
295 string.matches(EXAMPLE_MIME_MEDIA_TYPE_PATTERN)){
296 return true;
298 return mimeTypes.contains(string);
302 * Checks whether a string containing at least one '/' character is
303 * likely to be a {@link TimeZone} ID.
305 * @param string The string to check.
306 * @return <code>true</code> if the string is likely to be a
307 * TimeZone ID, <code>false</code> if not.
309 private boolean isTimeZoneIdString(String string){
310 return timeZoneIds.contains(string);