blob: a4238ef6208f1eab406e9a12a3510cc95348e274 [file] [log] [blame]
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.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.FullyQualifiedName;
import org.checkerframework.checker.units.qual.Prefix;
import org.checkerframework.checker.units.qual.UnknownUnits;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
/**
* 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 specific Prefix
* p.
*
* @param env the Checker Processing Environment, provided as a parameter in init() of a
* UnitsRelations implementation
* @param annoClass the fully-qualified name 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 @FullyQualifiedName CharSequence annoClass,
final Prefix p) {
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 getElementValueClassname 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 @FullyQualifiedName CharSequence annoClass) {
return AnnotationBuilder.fromName(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) {
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(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(final AnnotatedTypeMirror annoType) {
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 (ie, no value element).
*
* @param unitsAnnotation an AnnotationMirror representing a Units Annotation
* @return true if it has no prefix, false otherwise
*/
public static boolean hasNoPrefix(final AnnotationMirror unitsAnnotation) {
AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation);
return hasNoPrefix(annotationValue);
}
private static boolean hasNoPrefix(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(
final AnnotationMirror unitsAnnotation) {
Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues =
unitsAnnotation.getElementValues();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
elementValues.entrySet()) {
if (entry.getKey().getSimpleName().contentEquals("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(
final Elements elements, final AnnotationMirror unitsAnnotation) {
if (hasNoPrefix(unitsAnnotation)) {
// Optimization, though the else case would also work.
return unitsAnnotation;
} else {
String unitsAnnoName = AnnotationUtils.annotationName(unitsAnnotation);
// In the Units Checker, the only annotation value is the prefix value. Therefore,
// fromName (which creates an annotation with no values) is acceptable.
// TODO: refine sensitivity of removal for extension units, in case extension
// Annotations have more than just Prefix in its values.
return AnnotationBuilder.fromName(elements, unitsAnnoName);
}
}
/**
* 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(
final Elements elements, 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(final AnnotatedTypeMirror annoType) {
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(
final AnnotatedTypeMirror annoType, final AnnotationMirror unitsAnnotation) {
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(
final AnnotatedTypeMirror annoType, final AnnotationMirror unitsAnnotation) {
return AnnotationUtils.containsSameByName(annoType.getAnnotations(), unitsAnnotation);
}
/**
* Creates an AnnotationMirror representing a unit defined by annoClass, with the specific Prefix
* p.
*
* <p>This interface is intended only for subclasses of UnitsRelations; other clients should use
* {@link #buildAnnoMirrorWithSpecificPrefix(ProcessingEnvironment, CharSequence, Prefix)}
*
* @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) {
AnnotationBuilder builder = new AnnotationBuilder(env, annoClass);
builder.setValue("value", p);
return builder.build();
}
/**
* Creates an AnnotationMirror representing a unit defined by annoClass, with the default Prefix
* of {@code Prefix.one}.
*
* <p>This interface is intended only for subclasses of UnitsRelations; other clients should not
* use it.
*
* @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) {
return buildAnnoMirrorWithSpecificPrefix(env, annoClass, Prefix.one);
}
/**
* Creates an AnnotationMirror representing a unit defined by annoClass, with no prefix.
*
* <p>This interface is intended only for subclasses of UnitsRelations; other clients should use
* {@link #buildAnnoMirrorWithNoPrefix(ProcessingEnvironment, CharSequence)}.
*
* @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
*/
static @Nullable AnnotationMirror buildAnnoMirrorWithNoPrefix(
final ProcessingEnvironment env, final Class<? extends Annotation> annoClass) {
return AnnotationBuilder.fromClass(env.getElementUtils(), annoClass);
}
}