org.checkerframework.checker.formatter.qual.ConversionCategory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checker Show documentation
Show all versions of checker Show documentation
The Checker Framework enhances Java's type system to
make it more powerful and useful. This lets software developers
detect and prevent errors in their Java programs.
The Checker Framework includes compiler plug-ins ("checkers")
that find bugs or verify their absence. It also permits you to
write your own compiler plug-ins.
package org.checkerframework.checker.formatter.qual;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.checkerframework.dataflow.qual.Pure;
/**
* Elements of this enumeration are used in a {@link Format Format} annotation to indicate the valid
* types that may be passed as a format parameter. For example:
*
*
*
* {@literal @}Format({ConversionCategory.GENERAL, ConversionCategory.INT})
* String f = "String '%s' has length %d";
* String.format(f, "Example", 7);
*
*
*
* The annotation indicates that the format string requires any Object as the first parameter
* ({@link ConversionCategory#GENERAL}) and an integer as the second parameter ({@link
* ConversionCategory#INT}).
*
* @see Format
* @checker_framework.manual #formatter-checker Format String Checker
* @author Konstantin Weitz
*/
public enum ConversionCategory {
/** Use if the parameter can be of any type. Applicable for conversions b, B, h, H, s, S. */
GENERAL(null /* everything */, "bBhHsS"),
/**
* Use if the parameter is of a basic types which represent Unicode characters: char, Character,
* byte, Byte, short, and Short. This conversion may also be applied to the types int and
* Integer when Character.isValidCodePoint(int) returns true. Applicable for conversions c, C.
*/
CHAR(new Class[] {Character.class, Byte.class, Short.class, Integer.class}, "cC"),
/**
* Use if the parameter is an integral type: byte, Byte, short, Short, int and Integer, long,
* Long, and BigInteger. Applicable for conversions d, o, x, X.
*/
INT(
new Class[] {Byte.class, Short.class, Integer.class, Long.class, BigInteger.class},
"doxX"),
/**
* Use if the parameter is a floating-point type: float, Float, double, Double, and BigDecimal.
* Applicable for conversions e, E, f, g, G, a, A.
*/
FLOAT(new Class[] {Float.class, Double.class, BigDecimal.class}, "eEfgGaA"),
/**
* Use if the parameter is a type which is capable of encoding a date or time: long, Long,
* Calendar, and Date. Applicable for conversions t, T.
*/
TIME(new Class[] {Long.class, Calendar.class, Date.class}, "tT"),
/**
* In a format string, multiple conversions may be applied to the same parameter. This is
* seldomly needed, but the following is an example of such use:
*
*
* format("Test %1$c %1$d", (int)42);
*
*
* In this example, the first parameter is interpreted as both a character and an int, therefore
* the parameter must be compatible with both conversion, and can therefore neither be char nor
* long. This intersection of conversions is called CHAR_AND_INT.
*
* One other conversion intersection is interesting, namely the intersection of INT and TIME,
* resulting in INT_AND_TIME.
*
*
All other intersection either lead to an already existing type, or NULL, in which case it
* is illegal to pass object's of any type as parameter.
*/
CHAR_AND_INT(new Class[] {Byte.class, Short.class, Integer.class}, null),
INT_AND_TIME(new Class[] {Long.class}, null),
/**
* Use if no object of any type can be passed as parameter. In this case, the only legal value
* is null. This is seldomly needed, and indicates an error in most cases. For example:
*
*
* format("Test %1$f %1$d", null);
*
*
* Only null can be legally passed, passing a value such as 4 or 4.2 would lead to an exception.
*/
NULL(new Class[0], null),
/**
* Use if a parameter is not used by the formatter. This is seldomly needed, and indicates an
* error in most cases. For example:
*
*
* format("Test %1$s %3$s", "a","unused","b");
*
*
* Only the first "a" and third "b" parameters are used, the second "unused" parameter is
* ignored.
*/
UNUSED(null /* everything */, null);
ConversionCategory(Class[] types, String chars) {
this.types = types;
this.chars = chars;
}
@SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up!
public final Class[] types;
public final String chars;
/**
* Use this function to get the category associated with a conversion character. For example:
*
*
*
*
* ConversionCategory.fromConversionChar('d') == ConversionCategory.INT;
*
*
*
*/
public static ConversionCategory fromConversionChar(char c) {
for (ConversionCategory v : new ConversionCategory[] {GENERAL, CHAR, INT, FLOAT, TIME}) {
if (v.chars.contains(String.valueOf(c))) {
return v;
}
}
throw new IllegalArgumentException();
}
private static Set arrayToSet(E[] a) {
return new HashSet(Arrays.asList(a));
}
public static boolean isSubsetOf(ConversionCategory a, ConversionCategory b) {
return intersect(a, b) == a;
}
/**
* Use this function to get the intersection of two categories. This is seldomly needed.
*
*
*
*
* ConversionCategory.intersect(INT, TIME) == INT_AND_TIME;
*
*
*
*/
public static ConversionCategory intersect(ConversionCategory a, ConversionCategory b) {
if (a == UNUSED) {
return b;
}
if (b == UNUSED) {
return a;
}
if (a == GENERAL) {
return b;
}
if (b == GENERAL) {
return a;
}
Set> as = arrayToSet(a.types);
Set> bs = arrayToSet(b.types);
as.retainAll(bs); // intersection
for (ConversionCategory v :
new ConversionCategory[] {
CHAR, INT, FLOAT, TIME, CHAR_AND_INT, INT_AND_TIME, NULL
}) {
Set> vs = arrayToSet(v.types);
if (vs.equals(as)) {
return v;
}
}
// this should never happen
throw new RuntimeException();
}
/**
* Use this function to get the union of two categories. This is seldomly needed.
*
*
*
*
* ConversionCategory.union(INT, TIME) == GENERAL;
*
*
*
*/
public static ConversionCategory union(ConversionCategory a, ConversionCategory b) {
if (a == UNUSED || b == UNUSED) {
return UNUSED;
}
if (a == GENERAL || b == GENERAL) {
return GENERAL;
}
if ((a == CHAR_AND_INT && b == INT_AND_TIME) || (a == INT_AND_TIME && b == CHAR_AND_INT)) {
// This is special-cased because the union of a.types and b.types
// does not include BigInteger.class, whereas the types for INT does.
// Returning INT here to prevent returning GENERAL below.
return INT;
}
Set> as = arrayToSet(a.types);
Set> bs = arrayToSet(b.types);
as.addAll(bs); // union
for (ConversionCategory v :
new ConversionCategory[] {
NULL, CHAR_AND_INT, INT_AND_TIME, CHAR, INT, FLOAT, TIME
}) {
Set> vs = arrayToSet(v.types);
if (vs.equals(as)) {
return v;
}
}
return GENERAL;
}
private String className(Class cls) {
if (cls == Boolean.class) {
return "boolean";
}
if (cls == Character.class) {
return "char";
}
if (cls == Byte.class) {
return "byte";
}
if (cls == Short.class) {
return "short";
}
if (cls == Integer.class) {
return "int";
}
if (cls == Long.class) {
return "long";
}
if (cls == Float.class) {
return "float";
}
if (cls == Double.class) {
return "double";
}
return cls.getSimpleName();
}
/** Returns a pretty printed {@link ConversionCategory}. */
@Pure
@Override
public String toString() {
StringBuilder sb = new StringBuilder(this.name());
sb.append(" conversion category (one of: ");
boolean first = true;
for (Class cls : this.types) {
if (!first) {
sb.append(", ");
}
sb.append(className(cls));
first = false;
}
sb.append(")");
return sb.toString();
}
}