blob: af83e7c7e4dee6776d459c2844a76eabdc02270e [file] [log] [blame]
/*
* 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);
}
}
}