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
;
33 import java
.util
.Arrays
;
34 import java
.util
.HashSet
;
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
= '/';
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]+)?");
52 * A regular expression that matches strings which represent example MIME
55 @NonNls private static final String EXAMPLE_MIME_MEDIA_TYPE_PATTERN
=
56 "example/\\p{Alnum}+(?:[\\.\\-\\\\+]\\p{Alnum}+)*";
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+\\-:]+://.*$");
65 * All mimetypes, see http://www.iana.org/assignments/media-types/
67 private static final Set
<String
> mimeTypes
= new HashSet();
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());
99 * All {@link TimeZone} IDs.
101 private static final Set
<String
> timeZoneIds
= new HashSet();
103 timeZoneIds
.addAll(Arrays
.asList(TimeZone
.getAvailableIDs()));
107 * @noinspection PublicField
109 public boolean m_recognizeExampleMediaType
= false;
112 public String
getID(){
113 return "HardcodedFileSeparator";
117 public String
getDisplayName(){
118 return InspectionGadgetsBundle
.message(
119 "hardcoded.file.separator.display.name");
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
)) {
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
)) {
161 registerError(expression
);
162 } else if(type
!= null && type
.equals(PsiType
.CHAR
)){
163 final Character value
= (Character
) expression
.getValue();
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>
186 private boolean isHardcodedFilenameString(String string
){
190 if(string
.indexOf((int) '/') == -1 &&
191 string
.indexOf((int) '\\') == -1){
194 final char startChar
= string
.charAt(0);
195 if(Character
.isLetter(startChar
) && string
.charAt(1) == ':'){
198 if(isXMLString(string
)){
201 if(isDateFormatString(string
)){
204 if(isURLString(string
)){
207 if(isMediaTypeString(string
)){
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.
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
245 } else if(Character
.isLetter(startChar
) && string
.charAt(1) == ':'){
246 // Most likely this is a Windows-style full file name.
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
286 // "example/foo-bar+baz"
287 // "example/foo1.2006-bar"
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
)){
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
);