package org.checkerframework.checker.units;
import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.util.Elements;
import org.checkerframework.checker.units.qual.Prefix;
import org.checkerframework.checker.units.qual.UnknownUnits;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.util.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
*/
/**
* A helper class for UnitsRelations, providing numerous methods which help process Annotations and
* Annotated Types representing various units
*/
public class UnitsRelationsTools {
/**
* Creates an AnnotationMirror representing a unit defined by annoClass, with the default Prefix
* of Prefix.one
*
* @param env the Checker Processing Environment, provided as a parameter in init() of a
* UnitsRelations implementation
* @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters)
* @return an AnnotationMirror of the Unit with Prefix.one, or null if it cannot be constructed
*/
public static /*@Nullable*/ AnnotationMirror buildAnnoMirrorWithDefaultPrefix(
final ProcessingEnvironment env, final Class<? extends Annotation> annoClass) {
if (env == null || annoClass == null) {
return null;
}
return buildAnnoMirrorWithSpecificPrefix(env, annoClass, Prefix.one);
}
/**
* Creates an AnnotationMirror representing a unit defined by annoClass, with the specific
* Prefix p
*
* @param env the Checker Processing Environment, provided as a parameter in init() of a
* UnitsRelations implementation
* @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters)
* @param p a Prefix value
* @return an AnnotationMirror of the Unit with the Prefix p, or null if it cannot be
* constructed
*/
public static /*@Nullable*/ AnnotationMirror buildAnnoMirrorWithSpecificPrefix(
final ProcessingEnvironment env,
final Class<? extends Annotation> annoClass,
final Prefix p) {
if (env == null || annoClass == null || p == null) {
return null;
}
AnnotationBuilder builder = new AnnotationBuilder(env, annoClass);
builder.setValue("value", p);
return builder.build();
}
/**
* Creates an AnnotationMirror representing a unit defined by annoClass, with no prefix
*
* @param env checker Processing Environment, provided as a parameter in init() of a
* UnitsRelations implementation
* @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters)
* @return an AnnotationMirror of the Unit with no prefix, or null if it cannot be constructed
*/
public static /*@Nullable*/ AnnotationMirror buildAnnoMirrorWithNoPrefix(
final ProcessingEnvironment env, final Class<? extends Annotation> annoClass) {
if (env == null || annoClass == null) {
return null;
}
return AnnotationUtils.fromClass(env.getElementUtils(), annoClass);
}
/**
* Retrieves the SI Prefix of an Annotated Type
*
* @param annoType an AnnotatedTypeMirror representing a Units Annotated Type
* @return a Prefix value (including Prefix.one), or null if it has none
*/
public static /*@Nullable*/ Prefix getPrefix(final AnnotatedTypeMirror annoType) {
if (annoType == null) {
return null;
}
Prefix result = null;
// go through each Annotation of an Annotated Type, find the prefix and return it
for (AnnotationMirror mirror : annoType.getAnnotations()) {
// try to get a prefix
result = getPrefix(mirror);
// if it is not null, then return the retrieved prefix immediately
if (result != null) {
return result;
}
}
// if it can't find any prefix at all, then return null
return result;
}
/**
* Retrieves the SI Prefix of an Annotation
*
* @param unitsAnnotation an AnnotationMirror representing a Units Annotation
* @return a Prefix value (including Prefix.one), or null if it has none
*/
public static /*@Nullable*/ Prefix getPrefix(
/*@Nullable*/ final AnnotationMirror unitsAnnotation) {
AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation);
// if this Annotation has no prefix, return null
if (hasNoPrefix(annotationValue)) {
return null;
}
// if the Annotation has a value, then detect and match the string name of the prefix, and return the matching Prefix
String prefixString = annotationValue.getValue().toString();
for (Prefix prefix : Prefix.values()) {
if (prefixString.equals(prefix.toString())) {
return prefix;
}
}
// if none of the strings match, then return null
return null;
}
/**
* Checks to see if an Annotated Type has no prefix
*
* @param annoType an AnnotatedTypeMirror representing a Units Annotated Type
* @return true if it has no prefix, false otherwise
*/
public static boolean hasNoPrefix(/*@Nullable*/ final AnnotatedTypeMirror annoType) {
if (annoType == null) {
return true;
}
for (AnnotationMirror mirror : annoType.getAnnotations()) {
// if any Annotation has a prefix, return false
if (!hasNoPrefix(mirror)) {
return false;
}
}
return true;
}
/**
* Checks to see if an Annotation has no prefix
*
* @param unitsAnnotation an AnnotationMirror representing a Units Annotation
* @return true if it has no prefix, false otherwise
*/
public static boolean hasNoPrefix(/*@Nullable*/ final AnnotationMirror unitsAnnotation) {
AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation);
return hasNoPrefix(annotationValue);
}
private static boolean hasNoPrefix(/*@Nullable*/ final AnnotationValue annotationValue) {
// Annotation has no element value (ie no SI prefix)
if (annotationValue == null) {
return true;
} else {
return false;
}
}
/**
* Given an Annotation, returns the prefix (eg kilo) as an AnnotationValue if there is any,
* otherwise returns null.
*/
private static /*@Nullable*/ AnnotationValue getAnnotationMirrorPrefix(
/*@Nullable*/ final AnnotationMirror unitsAnnotation) {
if (unitsAnnotation == null) {
return null;
}
Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues =
unitsAnnotation.getElementValues();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
elementValues.entrySet()) {
if (entry.getKey().getSimpleName().toString().equals("value")) {
return entry.getValue();
}
}
return null;
}
/**
* Removes the Prefix value from an Annotation, by constructing and returning a copy of its base
* SI unit's Annotation
*
* @param elements the Element Utilities from a checker's processing environment, typically
* obtained by calling env.getElementUtils() in init() of a Units Relations implementation
* @param unitsAnnotation an AnnotationMirror representing a Units Annotation
* @return the base SI Unit's AnnotationMirror, or null if the base SI Unit cannot be
* constructed
*/
public static /*@Nullable*/ AnnotationMirror removePrefix(
/*@Nullable*/ final Elements elements,
/*@Nullable*/ final AnnotationMirror unitsAnnotation) {
if (elements == null) {
return null;
}
if (hasNoPrefix(unitsAnnotation)) {
return unitsAnnotation;
} else {
// the only value is the prefix value in Units Checker
// TODO: refine sensitivity of removal for extension units, in case extension Annotations have more than just Prefix in its values.
return AnnotationUtils.fromName(
elements, unitsAnnotation.getAnnotationType().toString());
}
}
/**
* Removes the Prefix value from an Annotated Type, by constructing and returning a copy of the
* Annotated Type without the prefix
*
* @param elements the Element Utilities from a checker's processing environment, typically
* obtained by calling env.getElementUtils() in init() of a Units Relations implementation
* @param annoType an AnnotatedTypeMirror representing a Units Annotated Type
* @return a copy of the Annotated Type without the prefix
*/
public static AnnotatedTypeMirror removePrefix(
/*@Nullable*/ final Elements elements,
/*@Nullable*/ final AnnotatedTypeMirror annoType) {
// deep copy the Annotated Type Mirror without any of the Annotations
AnnotatedTypeMirror result = annoType.deepCopy(false);
// get all of the original Annotations in the Annotated Type
Set<AnnotationMirror> annos = annoType.getAnnotations();
// loop through all the Annotations to see if they use Prefix.one, remove Prefix.one if it does
for (AnnotationMirror anno : annos) {
// try to clean the Annotation Mirror of the Prefix
AnnotationMirror cleanedMirror = removePrefix(elements, anno);
// if successful, add the cleaned annotation to the deep copy
if (cleanedMirror != null) {
result.addAnnotation(cleanedMirror);
}
// if unsuccessful, add the original annotation
else {
result.addAnnotation(anno);
}
}
return result;
}
/**
* Checks to see if a particular Annotated Type has no units, such as scalar constants in
* calculations.
*
* <p>Any number that isn't assigned a unit will automatically get the Annotation UnknownUnits.
* eg: int x = 5; // x has @UnknownUnits
*
* @param annoType an AnnotatedTypeMirror representing a Units Annotated Type
* @return true if the Type has no units, false otherwise
*/
public static boolean hasNoUnits(/*@Nullable*/ final AnnotatedTypeMirror annoType) {
if (annoType == null) {
return false;
}
return (annoType.getAnnotation(UnknownUnits.class) != null);
}
/**
* Checks to see if a particular Annotated Type has a specific unit (represented by its
* Annotation)
*
* @param annoType an AnnotatedTypeMirror representing a Units Annotated Type
* @param unitsAnnotation an AnnotationMirror representing a Units Annotation of a specific unit
* @return true if the Type has the specific unit, false otherwise
*/
public static boolean hasSpecificUnit(
/*@Nullable*/ final AnnotatedTypeMirror annoType,
/*@Nullable*/ final AnnotationMirror unitsAnnotation) {
if (annoType == null || unitsAnnotation == null) {
return false;
}
return AnnotationUtils.containsSame(annoType.getAnnotations(), unitsAnnotation);
}
/**
* Checks to see if a particular Annotated Type has a particular base unit (represented by its
* Annotation)
*
* @param annoType an AnnotatedTypeMirror representing a Units Annotated Type
* @param unitsAnnotation an AnnotationMirror representing a Units Annotation of the base unit
* @return true if the Type has the specific unit, false otherwise
*/
public static boolean hasSpecificUnitIgnoringPrefix(
/*@Nullable*/ final AnnotatedTypeMirror annoType,
/*@Nullable*/ final AnnotationMirror unitsAnnotation) {
if (annoType == null || unitsAnnotation == null) {
return false;
}
return AnnotationUtils.containsSameIgnoringValues(
annoType.getAnnotations(), unitsAnnotation);
}
}