package org.checkerframework.common.value;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.common.value.qual.ArrayLen;
import org.checkerframework.common.value.qual.BoolVal;
import org.checkerframework.common.value.qual.BottomVal;
import org.checkerframework.common.value.qual.DoubleVal;
import org.checkerframework.common.value.qual.IntRange;
import org.checkerframework.common.value.qual.IntVal;
import org.checkerframework.common.value.qual.StringVal;
import org.checkerframework.common.value.qual.UnknownVal;
import org.checkerframework.common.value.util.NumberUtils;
import org.checkerframework.common.value.util.Range;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TypesUtils;
public class ValueCheckerUtils {
public static Class<?> getClassFromType(TypeMirror type) {
switch (type.getKind()) {
case INT:
return int.class;
case LONG:
return long.class;
case SHORT:
return short.class;
case BYTE:
return byte.class;
case CHAR:
return char.class;
case DOUBLE:
return double.class;
case FLOAT:
return float.class;
case BOOLEAN:
return boolean.class;
case ARRAY:
return getArrayClassObject(((ArrayType) type).getComponentType());
case DECLARED:
String stringType = TypesUtils.getQualifiedName((DeclaredType) type).toString();
if (stringType.equals("<nulltype>")) {
return Object.class;
}
try {
return Class.forName(stringType);
} catch (ClassNotFoundException | UnsupportedClassVersionError e) {
return Object.class;
}
default:
return Object.class;
}
}
public static Class<?> getArrayClassObject(TypeMirror componentType) {
switch (componentType.getKind()) {
case INT:
return int[].class;
case LONG:
return long[].class;
case SHORT:
return short[].class;
case BYTE:
return byte[].class;
case CHAR:
return char[].class;
case DOUBLE:
return double[].class;
case FLOAT:
return float[].class;
case BOOLEAN:
return boolean[].class;
default:
return Object[].class;
}
}
/**
* Get a list of values of annotation, and then cast them to a given type
*
* @param anno the annotation that contains values
* @param castTo the type that is casted to
* @return a list of values after the casting
*/
public static List<?> getValuesCastedToType(AnnotationMirror anno, TypeMirror castTo) {
Class<?> castType = ValueCheckerUtils.getClassFromType(castTo);
List<?> values = null;
if (AnnotationUtils.areSameByClass(anno, DoubleVal.class)) {
values = convertDoubleVal(anno, castType, castTo);
} else if (AnnotationUtils.areSameByClass(anno, IntVal.class)) {
List<Long> longs = ValueAnnotatedTypeFactory.getIntValues(anno);
values = convertIntVal(longs, castType, castTo);
} else if (AnnotationUtils.areSameByClass(anno, IntRange.class)) {
Range range = ValueAnnotatedTypeFactory.getRange(anno);
List<Long> longs = getValuesFromRange(range, Long.class);
values = convertIntVal(longs, castType, castTo);
} else if (AnnotationUtils.areSameByClass(anno, StringVal.class)) {
values = convertStringVal(anno, castType);
} else if (AnnotationUtils.areSameByClass(anno, BoolVal.class)) {
values = convertBoolVal(anno, castType);
} else if (AnnotationUtils.areSameByClass(anno, BottomVal.class)) {
values = new ArrayList<>();
} else if (AnnotationUtils.areSameByClass(anno, UnknownVal.class)) {
values = null;
} else if (AnnotationUtils.areSameByClass(anno, ArrayLen.class)) {
values = new ArrayList<>();
}
return values;
}
/** Get the minimum and maximum of a list and return a range bounded by them. */
public static Range getRangeFromValues(List<? extends Number> values) {
if (values == null) {
return null;
} else if (values.isEmpty()) {
return Range.NOTHING;
}
// The number elements in the values list should not exceed MAX_VALUES (10).
List<Long> longValues = new ArrayList<>();
for (Number value : values) {
longValues.add(value.longValue());
}
return new Range(Collections.min(longValues), Collections.max(longValues));
}
/**
* Get all possible values from the given type and cast them into Long type, Double type, or
* Character type accordingly. Only support casting to integral type and double type.
*
* @param range the given range
* @param expectedType the expected type
* @return a list of all the values in the range
*/
public static <T> List<T> getValuesFromRange(Range range, Class<T> expectedType) {
if (range == null || range.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) {
return null;
}
List<T> values = new ArrayList<>();
if (range.isNothing()) {
return values;
}
if (expectedType == Integer.class
|| expectedType == int.class
|| expectedType == Long.class
|| expectedType == long.class
|| expectedType == Short.class
|| expectedType == short.class
|| expectedType == Byte.class
|| expectedType == byte.class) {
for (Long value = range.from; value <= range.to; value++) {
values.add(expectedType.cast(value.longValue()));
}
} else if (expectedType == Double.class
|| expectedType == double.class
|| expectedType == Float.class
|| expectedType == float.class) {
for (Long value = range.from; value <= range.to; value++) {
values.add(expectedType.cast(value.doubleValue()));
}
} else if (expectedType == Character.class || expectedType == char.class) {
for (Long value = range.from; value <= range.to; value++) {
values.add(expectedType.cast((char) value.intValue()));
}
} else {
throw new UnsupportedOperationException(
"ValueCheckerUtils: unexpected class: " + expectedType);
}
return values;
}
private static List<?> convertToStringVal(List<?> origValues) {
if (origValues == null) {
return null;
}
List<String> strings = new ArrayList<>();
for (Object value : origValues) {
strings.add(value.toString());
}
return strings;
}
private static List<?> convertBoolVal(AnnotationMirror anno, Class<?> newClass) {
List<Boolean> bools =
AnnotationUtils.getElementValueArray(anno, "value", Boolean.class, true);
if (newClass == String.class) {
return convertToStringVal(bools);
}
return bools;
}
private static List<?> convertStringVal(AnnotationMirror anno, Class<?> newClass) {
List<String> strings =
AnnotationUtils.getElementValueArray(anno, "value", String.class, true);
if (newClass == byte[].class) {
List<byte[]> bytes = new ArrayList<>();
for (String s : strings) {
bytes.add(s.getBytes());
}
return bytes;
} else if (newClass == char[].class) {
List<char[]> chars = new ArrayList<>();
for (String s : strings) {
chars.add(s.toCharArray());
}
return chars;
}
return strings;
}
private static List<?> convertIntVal(List<Long> longs, Class<?> newClass, TypeMirror newType) {
if (longs == null) {
return null;
}
if (newClass == String.class) {
return convertToStringVal(longs);
} else if (newClass == Character.class || newClass == char.class) {
List<Character> chars = new ArrayList<>();
for (Long l : longs) {
chars.add((char) l.longValue());
}
return chars;
} else if (newClass == Boolean.class) {
throw new UnsupportedOperationException(
"ValueAnnotatedTypeFactory: can't convert int to boolean");
}
return NumberUtils.castNumbers(newType, longs);
}
private static List<?> convertDoubleVal(
AnnotationMirror anno, Class<?> newClass, TypeMirror newType) {
List<Double> doubles = ValueAnnotatedTypeFactory.getDoubleValues(anno);
if (doubles == null) {
return null;
}
if (newClass == String.class) {
return convertToStringVal(doubles);
} else if (newClass == Character.class || newClass == char.class) {
List<Character> chars = new ArrayList<>();
for (Double l : doubles) {
chars.add((char) l.doubleValue());
}
return chars;
} else if (newClass == Boolean.class) {
throw new UnsupportedOperationException(
"ValueAnnotatedTypeFactory: can't convert double to boolean");
}
return NumberUtils.castNumbers(newType, doubles);
}
public static <T extends Comparable<T>> List<T> removeDuplicates(List<T> values) {
Set<T> set = new TreeSet<>(values);
return new ArrayList<T>(set);
}
}