blob: f659b9b31e0ccecb7fee92cc8edee7d1c3b9bd92 [file] [log] [blame]
/*
* Copyright 2003-2010 Dave Griffith, Bas Leijdekkers
*
* 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.bugs;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.PsiType;
import com.intellij.util.containers.ContainerUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@SuppressWarnings({"CollectionDeclaredAsConcreteClass", "ObjectEquality", "HardCodedStringLiteral"})
class FormatDecode {
private static final String FORMAT_SPECIFIER =
"%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
private static final Pattern fsPattern = Pattern.compile(FORMAT_SPECIFIER);
private FormatDecode() {
super();
}
private static final Validator ALL_VALIDATOR = new AllValidator();
private static final Validator DATE_VALIDATOR = new DateValidator();
private static final Validator CHAR_VALIDATOR = new CharValidator();
private static final Validator INT_VALIDATOR = new IntValidator();
private static final Validator FLOAT_VALIDATOR = new FloatValidator();
/**
* Holds information about validator replacement rules, i.e. allows to answer if validator of particular type may be
* safely replaced by validator of another particular type.
* <p/>
* For example, validator of type {@link AllValidator#type() 'all'} may be safely replaced by validator of any other
* type, e.g. {@link DateValidator#type() Date/Time} or {@link CharValidator#type() 'char validator'} may be replaced
* by {@link IntValidator#type() 'int validator'} because {@link Formatter java formatter} knows how to
* {@link Formatter.FormatSpecifier#printCharacter(Object) print character from integer} etc.
* <p/>
* Generally, current collection holds set of mappings where the key is type of validator that may be safely replaced
* by validator of type that is contained at <code>'values'</code> collection.
*/
private static final Map<String, Set<String>> REPLACEABLE_VALIDATOR_TYPES = new HashMap<String, Set<String>>();
static {
REPLACEABLE_VALIDATOR_TYPES.put(
ALL_VALIDATOR.type(),
ContainerUtil.set(DATE_VALIDATOR.type(), CHAR_VALIDATOR.type(), INT_VALIDATOR.type(), FLOAT_VALIDATOR.type())
);
REPLACEABLE_VALIDATOR_TYPES.put(CHAR_VALIDATOR.type(), ContainerUtil.set(INT_VALIDATOR.type()));
}
public static Validator[] decode(String formatString, int argumentCount) {
final ArrayList<Validator> parameters = new ArrayList<Validator>();
final Matcher matcher = fsPattern.matcher(formatString);
int implicit = 0;
int pos = 0;
for (int i = 0; matcher.find(i); i = matcher.end()) {
final String posSpec = matcher.group(1);
final String flags = matcher.group(2);
final String dateSpec = matcher.group(5);
final String spec = matcher.group(6);
// check this first because it should not affect "implicit"
if ("n".equals(spec) || "%".equals(spec)) {
continue;
}
if (posSpec != null) {
final String num = posSpec.substring(0, posSpec.length() - 1);
pos = Integer.parseInt(num) - 1;
}
else if (flags == null || flags.indexOf('<') < 0) {
pos = implicit++;
}
// else if the flag has "<" reuse the last pos
final Validator allowed;
if (dateSpec != null) { // a t or T
allowed = DATE_VALIDATOR;
}
else {
switch (Character.toLowerCase(spec.charAt(0))) {
case 'b':
case 'h':
case 's':
allowed = ALL_VALIDATOR;
break;
case 'c':
allowed = CHAR_VALIDATOR;
break;
case 'd':
case 'o':
case 'x':
allowed = INT_VALIDATOR;
break;
case 'e':
case 'f':
case 'g':
case 'a':
allowed = FLOAT_VALIDATOR;
break;
default:
throw new UnknownFormatException(matcher.group());
}
}
argAt(allowed, pos, parameters, argumentCount);
}
return parameters.toArray(new Validator[parameters.size()]);
}
private static void argAt(Validator val, int pos,
ArrayList<Validator> parameters,
int argumentCount) {
if (pos < parameters.size()) {
final Validator old = parameters.get(pos);
Set<String> replaceableTypes = REPLACEABLE_VALIDATOR_TYPES.get(old.type());
if (replaceableTypes != null && replaceableTypes.contains(val.type())) {
parameters.set(pos, val);
}
// it's OK to overwrite ALL with something more specific
// it's OK to ignore overwrite of something else with ALL or itself
else if (val != ALL_VALIDATOR && val != old) {
throw new DuplicateFormatFlagsException(
"requires both " + old.type() + " and " + val.type());
}
}
else {
while (pos > parameters.size() &&
argumentCount > parameters.size()) {
parameters.add(ALL_VALIDATOR);
}
parameters.add(val);
}
}
public static class UnknownFormatException extends RuntimeException {
public UnknownFormatException(String message) {
super(message);
}
}
public static class DuplicateFormatFlagsException extends RuntimeException {
public DuplicateFormatFlagsException(String message) {
super(message);
}
}
private static class AllValidator implements Validator {
@Override
public boolean valid(PsiType type) {
return true;
}
@Override
public String type() {
return "any";
}
}
private static class DateValidator implements Validator {
@Override
public boolean valid(PsiType type) {
final String text = type.getCanonicalText();
return PsiType.LONG.equals(type) ||
CommonClassNames.JAVA_LANG_LONG.equals(text) ||
CommonClassNames.JAVA_UTIL_DATE.equals(text) ||
CommonClassNames.JAVA_UTIL_CALENDAR.equals(text);
}
@Override
public String type() {
return "Date/Time";
}
}
private static class CharValidator implements Validator {
@Override
public boolean valid(PsiType type) {
final String text = type.getCanonicalText();
return PsiType.CHAR.equals(type) ||
CommonClassNames.JAVA_LANG_CHARACTER.equals(text);
}
@Override
public String type() {
return "char";
}
}
private static class IntValidator implements Validator {
@Override
public boolean valid(PsiType type) {
final String text = type.getCanonicalText();
return PsiType.INT.equals(type) ||
CommonClassNames.JAVA_LANG_INTEGER.equals(text) ||
PsiType.LONG.equals(type) ||
CommonClassNames.JAVA_LANG_LONG.equals(text) ||
PsiType.SHORT.equals(type) ||
CommonClassNames.JAVA_LANG_SHORT.equals(text) ||
PsiType.BYTE.equals(type) ||
CommonClassNames.JAVA_LANG_BYTE.equals(text) ||
"java.math.BigInteger".equals(text);
}
@Override
public String type() {
return "integer type";
}
}
private static class FloatValidator implements Validator {
@Override
public boolean valid(PsiType type) {
final String text = type.getCanonicalText();
return PsiType.DOUBLE.equals(type) ||
CommonClassNames.JAVA_LANG_DOUBLE.equals(text) ||
PsiType.FLOAT.equals(type) ||
CommonClassNames.JAVA_LANG_FLOAT.equals(text) ||
"java.math.BigDecimal".equals(text);
}
@Override
public String type() {
return "floating point";
}
}
}