| /* |
| * Copyright 2003-2007 Dave Griffith, Bas Leijdekkers, Mark Scott |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.siyeh.ig.portability; |
| |
| import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiLiteralExpression; |
| import com.intellij.psi.PsiMethodCallExpression; |
| import com.intellij.psi.PsiType; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.siyeh.InspectionGadgetsBundle; |
| import com.siyeh.ig.BaseInspection; |
| import com.siyeh.ig.BaseInspectionVisitor; |
| import com.siyeh.ig.portability.mediatype.*; |
| import com.siyeh.ig.psiutils.MethodCallUtils; |
| import com.siyeh.ig.psiutils.TypeUtils; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| |
| import javax.swing.*; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.TimeZone; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| public class HardcodedFileSeparatorsInspection extends BaseInspection { |
| |
| private static final char BACKSLASH = '\\'; |
| private static final char SLASH = '/'; |
| /** |
| * The regular expression pattern that matches strings which are likely to |
| * be date formats. <code>Pattern</font></b> instances are immutable, so |
| * caching the pattern like this is still thread-safe. |
| */ |
| @NonNls private static final Pattern DATE_FORMAT_PATTERN = |
| Pattern.compile("\\b[dDmM]+/[dDmM]+(/[yY]+)?"); |
| /** |
| * A regular expression that matches strings which represent example MIME |
| * media types. |
| */ |
| @NonNls private static final String EXAMPLE_MIME_MEDIA_TYPE_PATTERN = |
| "example/\\p{Alnum}+(?:[\\.\\-\\\\+]\\p{Alnum}+)*"; |
| /** |
| * A regular expression pattern that matches strings which start with a URL |
| * protocol, as they're likely to actually be URLs. |
| */ |
| @NonNls private static final Pattern URL_PATTERN = |
| Pattern.compile("^[a-z][a-z0-9+\\-:]+://.*$"); |
| |
| /** |
| * All mimetypes, see http://www.iana.org/assignments/media-types/ |
| */ |
| private static final Set<String> mimeTypes = new HashSet(); |
| |
| static { |
| for (ImageMediaType imageMediaType : ImageMediaType.values()) { |
| mimeTypes.add(imageMediaType.toString()); |
| } |
| for (ApplicationMediaType applicationMediaType : |
| ApplicationMediaType.values()) { |
| mimeTypes.add(applicationMediaType.toString()); |
| } |
| for (AudioMediaType audioMediaType : AudioMediaType.values()) { |
| mimeTypes.add(audioMediaType.toString()); |
| } |
| for (MessageMediaType messageMediaType : MessageMediaType.values()) { |
| mimeTypes.add(messageMediaType.toString()); |
| } |
| for (ModelMediaType modelMediaType : ModelMediaType.values()) { |
| mimeTypes.add(modelMediaType.toString()); |
| } |
| for (MultipartMediaType multipartMediaType : |
| MultipartMediaType.values()) { |
| mimeTypes.add(multipartMediaType.toString()); |
| } |
| for (TextMediaType textMediaType : TextMediaType.values()) { |
| mimeTypes.add(textMediaType.toString()); |
| } |
| for (VideoMediaType videoContentTypeMediaType : |
| VideoMediaType.values()) { |
| mimeTypes.add(videoContentTypeMediaType.toString()); |
| } |
| } |
| |
| /** |
| * All {@link TimeZone} IDs. |
| */ |
| private static final Set<String> timeZoneIds = new HashSet(); |
| |
| static { |
| ContainerUtil.addAll(timeZoneIds, TimeZone.getAvailableIDs()); |
| } |
| |
| /** |
| * @noinspection PublicField |
| */ |
| public boolean m_recognizeExampleMediaType = false; |
| |
| @Override |
| @NotNull |
| public String getID() { |
| return "HardcodedFileSeparator"; |
| } |
| |
| @Override |
| @NotNull |
| public String getDisplayName() { |
| return InspectionGadgetsBundle.message( |
| "hardcoded.file.separator.display.name"); |
| } |
| |
| @Override |
| @NotNull |
| public String buildErrorString(Object... infos) { |
| return InspectionGadgetsBundle.message( |
| "hardcoded.file.separator.problem.descriptor"); |
| } |
| |
| @Override |
| public JComponent createOptionsPanel() { |
| return new SingleCheckboxOptionsPanel( |
| InspectionGadgetsBundle.message( |
| "hardcoded.file.separator.include.option"), |
| this, "m_recognizeExampleMediaType"); |
| } |
| |
| @Override |
| public BaseInspectionVisitor buildVisitor() { |
| return new HardcodedFileSeparatorsVisitor(); |
| } |
| |
| private class HardcodedFileSeparatorsVisitor |
| extends BaseInspectionVisitor { |
| |
| @Override |
| public void visitLiteralExpression( |
| @NotNull PsiLiteralExpression expression) { |
| super.visitLiteralExpression(expression); |
| final PsiType type = expression.getType(); |
| if (TypeUtils.isJavaLangString(type)) { |
| final String value = (String)expression.getValue(); |
| if (!isHardcodedFilenameString(value)) { |
| return; |
| } |
| final PsiElement parent = expression.getParent(); |
| final PsiElement grandParent = parent.getParent(); |
| if (grandParent instanceof PsiMethodCallExpression) { |
| final PsiMethodCallExpression methodCallExpression = |
| (PsiMethodCallExpression)grandParent; |
| if (MethodCallUtils.isCallToRegexMethod( |
| methodCallExpression)) { |
| return; |
| } |
| } |
| registerError(expression); |
| } |
| else if (type != null && type.equals(PsiType.CHAR)) { |
| final Character value = (Character)expression.getValue(); |
| if (value == null) { |
| return; |
| } |
| final char unboxedValue = value.charValue(); |
| if (unboxedValue == BACKSLASH || unboxedValue == SLASH) { |
| registerError(expression); |
| } |
| } |
| } |
| |
| /** |
| * Check whether a string is likely to be a filename containing one or more |
| * hard-coded file separator characters. The method does some simple |
| * analysis of the string to determine whether it's likely to be some other |
| * type of data - a URL, a date format, or an XML fragment - before deciding |
| * that the string is a filename. |
| * |
| * @param string The string to examine. |
| * @return <code>true</code> if the string is likely to be a filename |
| * with hardcoded file separators, <code>false</code> |
| * otherwise. |
| */ |
| private boolean isHardcodedFilenameString(String string) { |
| if (string == null) { |
| return false; |
| } |
| if (string.indexOf((int)'/') == -1 && |
| string.indexOf((int)'\\') == -1) { |
| return false; |
| } |
| final char startChar = string.charAt(0); |
| if (Character.isLetter(startChar) && string.charAt(1) == ':') { |
| return true; |
| } |
| if (isXMLString(string)) { |
| return false; |
| } |
| if (isDateFormatString(string)) { |
| return false; |
| } |
| if (isURLString(string)) { |
| return false; |
| } |
| if (isMediaTypeString(string)) { |
| return false; |
| } |
| return !isTimeZoneIdString(string); |
| } |
| |
| /** |
| * Check whether a string containing at least one '/' or '\' character is |
| * likely to be a fragment of XML. |
| * |
| * @param string The string to examine. |
| * @return <code>true</code> if the string is likely to be an XML |
| * fragment, or <code>false</code> if not. |
| */ |
| private boolean isXMLString(String string) { |
| return string.contains("</") || string.contains("/>"); |
| } |
| |
| /** |
| * Check whether a string containing at least one '/' or '\' character is |
| * likely to be a date format string. |
| * |
| * @param string The string to check. |
| * @return <code>true</code> if the string is likely to be a date |
| * string, <code>false</code> if not. |
| */ |
| private boolean isDateFormatString(String string) { |
| if (string.length() < 3) { |
| // A string this short is very unlikely to be a date format. |
| return false; |
| } |
| final int strLength = string.length(); |
| final char startChar = string.charAt(0); |
| final char endChar = string.charAt(strLength - 1); |
| if (startChar == '/' || endChar == '/') { |
| // Most likely it's a filename if the string starts or ends |
| // with a slash. |
| return false; |
| } |
| else if (Character.isLetter(startChar) && string.charAt(1) == ':') { |
| // Most likely this is a Windows-style full file name. |
| return false; |
| } |
| final Matcher dateFormatMatcher = DATE_FORMAT_PATTERN.matcher(string); |
| return dateFormatMatcher.find(); |
| } |
| |
| /** |
| * Checks whether a string containing at least one '/' or '\' character is |
| * likely to be a URL. |
| * |
| * @param string The string to check. |
| * @return <code>true</code> if the string is likely to be a URL, |
| * <code>false</code> if not. |
| */ |
| private boolean isURLString(String string) { |
| final Matcher urlMatcher = URL_PATTERN.matcher(string); |
| return urlMatcher.find(); |
| } |
| |
| /** |
| * Checks whether a string containing at least one '/' character is |
| * likely to be a MIME media type. See the |
| * <a href="http://www.iana.org/assignments/media-types/">IANA</a> |
| * documents for registered MIME media types. |
| * |
| * @param string The string to check. |
| * @return <code>true</code> if the string is likely to be a MIME |
| * media type, <code>false</code> if not. |
| */ |
| private boolean isMediaTypeString(String string) { |
| // IANA doesn't specify a pattern for the subtype of example content |
| // types but other subtypes seem to be one or more groups of |
| // alphanumerics characters, the groups being separated by a single |
| // period (.), hyphen (-) or plus (+) character |
| // |
| // Valid examples: |
| // |
| // "example/foo" |
| // "example/foo.bar" |
| // "example/foo-bar+baz" |
| // "example/foo1.2006-bar" |
| // |
| // Invalid examples: |
| // |
| // "example/foo$bar" ($ isn't a valid separator) |
| // "example/foo." (can't end with a separator) |
| // |
| if (m_recognizeExampleMediaType && |
| string.matches(EXAMPLE_MIME_MEDIA_TYPE_PATTERN)) { |
| return true; |
| } |
| return mimeTypes.contains(string); |
| } |
| |
| /** |
| * Checks whether a string containing at least one '/' character is |
| * likely to be a {@link TimeZone} ID. |
| * |
| * @param string The string to check. |
| * @return <code>true</code> if the string is likely to be a |
| * TimeZone ID, <code>false</code> if not. |
| */ |
| private boolean isTimeZoneIdString(String string) { |
| return timeZoneIds.contains(string); |
| } |
| } |
| } |