package org.checkerframework.checker.i18nformatter.qual;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
/**
* Elements of this enumeration are used in a {@link I18nFormat} annotation to indicate the valid
* types that may be passed as a format parameter. For example:
*
* <blockquote>
*
* <pre>{@literal @}I18nFormat({I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER})
* String f = "{0}{1, number}";
* MessageFormat.format(f, "Example", 0) // valid</pre>
*
* </blockquote>
*
* The annotation indicates that the format string requires any object as the first parameter
* ({@link I18nConversionCategory#GENERAL}) and a number as the second parameter ({@link
* I18nConversionCategory#NUMBER}).
*
* @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
* @author Siwakorn Srisakaokul
*/
public enum I18nConversionCategory {
/**
* Use if a parameter is not used by the formatter.
*
* <pre>
* MessageFormat.format("{1}", a, b);
* </pre>
*
* Only the second argument ("b") is used. The first argument ("a") is ignored
*/
UNUSED(null /* everything */, null),
/** Use if the parameter can be of any type. */
GENERAL(null /* everything */, null),
/** Use if the parameter can be of date, time, or number types */
DATE(new Class<?>[] {Date.class, Number.class}, new String[] {"date", "time"}),
/**
* Use if the parameter can be of number or choice types. An example of choice:
*
* <pre>{@code
* format("{0, choice, 0#zero|1#one|1<{0, number} is more than 1}", 2)
* }</pre>
*
* This will print "2 is more than 1".
*/
NUMBER(new Class<?>[] {Number.class}, new String[] {"number", "choice"});
public final Class<? extends Object>[] types;
public final String[] strings;
I18nConversionCategory(Class<? extends Object>[] types, String[] strings) {
this.types = types;
this.strings = strings;
}
/**
*
*
* <pre>
* I18nConversionCategory.stringToI18nConversionCategory("number") == I18nConversionCategory.NUMBER;
* </pre>
*
* @return the I18nConversionCategory associated with the given string
*/
public static I18nConversionCategory stringToI18nConversionCategory(String string) {
string = string.toLowerCase();
for (I18nConversionCategory v : new I18nConversionCategory[] {DATE, NUMBER}) {
for (String s : v.strings) {
if (s.equals(string)) {
return v;
}
}
}
throw new IllegalArgumentException("Invalid format type.");
}
private static <E> Set<E> arrayToSet(E[] a) {
return new HashSet<E>(Arrays.asList(a));
}
/** @return true if a is a subset of b */
public static boolean isSubsetOf(I18nConversionCategory a, I18nConversionCategory b) {
return intersect(a, b) == a;
}
/**
* Returns the intersection of the two given I18nConversionCategories
*
* <blockquote>
*
* <pre>
* I18nConversionCategory.intersect(DATE, NUMBER) == NUMBER;
* </pre>
*
* </blockquote>
*/
public static I18nConversionCategory intersect(
I18nConversionCategory a, I18nConversionCategory b) {
if (a == UNUSED) {
return b;
}
if (b == UNUSED) {
return a;
}
if (a == GENERAL) {
return b;
}
if (b == GENERAL) {
return a;
}
Set<Class<? extends Object>> as = arrayToSet(a.types);
Set<Class<? extends Object>> bs = arrayToSet(b.types);
as.retainAll(bs); // intersection
for (I18nConversionCategory v : new I18nConversionCategory[] {DATE, NUMBER}) {
Set<Class<? extends Object>> vs = arrayToSet(v.types);
if (vs.equals(as)) {
return v;
}
}
// this should never happen
throw new RuntimeException();
}
/**
* Returns the union of the two given I18nConversionCategories
*
* <blockquote>
*
* <pre>
* I18nConversionCategory.intersect(DATE, NUMBER) == DATE;
* </pre>
*
* </blockquote>
*/
public static I18nConversionCategory union(I18nConversionCategory a, I18nConversionCategory b) {
if (a == UNUSED || b == UNUSED) {
return UNUSED;
}
if (a == GENERAL || b == GENERAL) {
return GENERAL;
}
if (a == DATE || b == DATE) {
return DATE;
}
return NUMBER;
}
/** Returns a pretty printed {@link I18nConversionCategory}. */
@Override
public String toString() {
StringBuilder sb = new StringBuilder(this.name());
if (this.types == null) {
sb.append(" conversion category (all types)");
} else {
sb.append(" conversion category (one of: ");
boolean first = true;
for (Class<? extends Object> cls : this.types) {
if (!first) {
sb.append(", ");
}
sb.append(cls.getCanonicalName());
first = false;
}
sb.append(")");
}
return sb.toString();
}
}