Skip to content
This repository has been archived by the owner on May 12, 2020. It is now read-only.

Commit

Permalink
Support any nullability annotation. (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
naturalwarren authored May 25, 2017
1 parent 4059653 commit f290611
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 50 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ Android apps consume data from a variety of sources (network, disk, etc.) that y

## Installation
#### Gradle
To integrate RAVE into your project add the following to your dependencies in your 'build.gradle' file:
To integrate RAVE into your project add the following to your 'build.gradle' file:

```
// Add RAVE dependencies
buildscript {
repositories {
maven { url 'https://maven.google.com' }
}
}
dependencies {
annotationProcessor 'com.uber:rave-compiler:1.0.1'
compile 'com.uber:rave:1.0.1'
Expand Down
4 changes: 2 additions & 2 deletions rave-compiler/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ dependencies {
compile 'com.google.auto:auto-common:0.8'
compile 'com.google.guava:guava:21.0'
compile 'com.squareup:javapoet:1.8.0'
compile deps.supportAnnotations

compileOnly 'com.google.auto.service:auto-service:1.0-rc3'
compileOnly deps.jsr305
compileOnly deps.supportAnnotations

testCompile project(":rave-test")
testCompile deps.jsr305
Expand All @@ -32,7 +32,7 @@ dependencies {
testCompile 'com.google.testing.compile:compile-testing:0.10'
testCompile 'org.slf4j:slf4j-api:1.7.24'
testCompile files(org.gradle.internal.jvm.Jvm.current().getToolsJar())
testCompile "org.mockito:mockito-core:2.7.12"
testCompile 'org.mockito:mockito-core:2.7.12'
}

cobertura {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,9 @@ private void verifyAnnotationConflicts(TypeElement typeElement) {
annotationList.clear();
for (AnnotationMirror mirror : elements.getAllAnnotationMirrors(executableElement)) {
String annotationName = mirror.getAnnotationType().toString();
if (CompilerUtils.annotationsIsSupported(mirror.getAnnotationType().toString())) {
if (CompilerUtils.isSupportedAnnotation(mirror.getAnnotationType().toString())) {
for (String a : annotationList) {
if (CompilerUtils.areConflicting(CompilerUtils.getAnnotation(a), CompilerUtils
.getAnnotation(annotationName))) {
if (CompilerUtils.areConflicting(a, annotationName)) {
abortWithError("Annotations " + annotationName + " cannot be used with " + a, typeElement);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@
import javax.lang.model.util.Types;

/**
* Utitlies class for annotations.
* Utilities class for annotations.
*/
final class CompilerUtils {

private static final List<Class<? extends Annotation>> SUPPORTED_ANNOTATIONS = ImmutableList.of(
private static final List<Class<? extends Annotation>> SUPPORT_ANNOTATIONS = ImmutableList.of(
NonNull.class,
Nullable.class,
Size.class,
Expand All @@ -63,13 +63,11 @@ final class CompilerUtils {
FloatRange.class
);

private static final List<Pair<Class<? extends Annotation>, Class<? extends Annotation>>> CONFLICTING_ANNOTATIONS =
ImmutableList.of(
private static final List<Pair<Class<? extends Annotation>, Class<? extends Annotation>>>
CONFLICTING_SUPPORT_ANNOTATIONS = ImmutableList.of(
new Pair<Class<? extends Annotation>, Class<? extends Annotation>>(MustBeTrue.class,
MustBeFalse.class),
new Pair<Class<? extends Annotation>, Class<? extends Annotation>>(NonNull.class, Nullable.class),
new Pair<Class<? extends Annotation>, Class<? extends Annotation>>(IntRange.class, IntDef.class)
);
new Pair<Class<? extends Annotation>, Class<? extends Annotation>>(IntRange.class, IntDef.class));
private static Map<Class<? extends Annotation>, Set<Class<? extends Annotation>>> sConflictingAnnotations;
private static Map<String, Class<? extends Annotation>> sAnnotationMap;

Expand All @@ -92,21 +90,21 @@ private static void build(

private static void init() {
ImmutableMap.Builder<String, Class<? extends Annotation>> annotationBuilder = ImmutableMap.builder();
for (Class<? extends Annotation> cls : SUPPORTED_ANNOTATIONS) {
for (Class<? extends Annotation> cls : SUPPORT_ANNOTATIONS) {
annotationBuilder.put(cls.getCanonicalName(), cls);
}
sAnnotationMap = annotationBuilder.build();

Map<Class<? extends Annotation>, ImmutableSet.Builder<Class<? extends Annotation>>> mapBuilder =
new HashMap<>();
for (Pair<Class<? extends Annotation>, Class<? extends Annotation>> pair : CONFLICTING_ANNOTATIONS) {
for (Pair<Class<? extends Annotation>, Class<? extends Annotation>> pair : CONFLICTING_SUPPORT_ANNOTATIONS) {
build(mapBuilder, pair.first, pair.second);
build(mapBuilder, pair.second, pair.first);
}

ImmutableMap.Builder<Class<? extends Annotation>, Set<Class<? extends Annotation>>> builder = ImmutableMap
.builder();
for (Class<? extends Annotation> annotation : SUPPORTED_ANNOTATIONS) {
for (Class<? extends Annotation> annotation : SUPPORT_ANNOTATIONS) {
ImmutableSet.Builder<Class<? extends Annotation>> conflicting = mapBuilder.get(annotation);
if (conflicting == null) {
conflicting = new ImmutableSet.Builder<>();
Expand Down Expand Up @@ -138,31 +136,47 @@ static boolean typeMirrorInCollection(
}

/**
* @param annotationName the annotation, by name, to check.
* @return true if the annotation is supported false otherwise
* @param annotationName the annotation, by fully qualified name, to check.
* @return true if the annotation is an Android support annotation, false otherwise.
*/
static boolean annotationsIsSupported(String annotationName) {
static boolean isSupportAnnotation(String annotationName) {
return sAnnotationMap.containsKey(annotationName);
}

/**
* @param annotationName the annotation, by fully qualified name, to check.
* @return true if the annotation is an supported by RAVE, false otherwise.
*/
static boolean isSupportedAnnotation(String annotationName) {
return annotationName.toLowerCase().contains("nullable")
|| annotationName.toLowerCase().contains("nonnull")
|| isSupportAnnotation(annotationName);
}

/**
* @param annotationName the annotation (canonical name) to retrieve.
* @return the {@link Class} of the annotation.
* @return the {@link Class} of the Android support annotation.
*/
static Class<? extends Annotation> getAnnotation(String annotationName) {
static Class<? extends Annotation> getSupportAnnotation(String annotationName) {
return sAnnotationMap.get(annotationName);
}

/**
* This method checks to see if two annotations are conflicting or not.
*
* @param a1 the first annotation to check.
* @param a2 the second annotation to check.
* @param annotation1 the first annotation, by fully qualified name to check.
* @param annotation2 the second annotation by fully qualified name to check.
* @return true if the annotations are conflicting, false otherwise.
*/
static boolean areConflicting(Class<? extends Annotation> a1, Class<? extends Annotation> a2) {
Set<Class<? extends Annotation>> set = sConflictingAnnotations.get(a1);
return set.contains(a2);
static boolean areConflicting(String annotation1, String annotation2) {
String a1 = annotation1.toLowerCase();
String a2 = annotation2.toLowerCase();
if ((a1.contains("nullable") && a2.contains("nonnull"))
|| (a1.contains("nonnull") && a2.contains("nullable"))) {
return true;
}
Set<Class<? extends Annotation>> set = sConflictingAnnotations.get(getSupportAnnotation(annotation1));
return set.contains(getSupportAnnotation(annotation2));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,16 @@ private ClassIR extractClassInfo(TypeElement typeElement, Validator.Mode mode) {
MethodIR methodIR = new MethodIR(executableElement.getSimpleName().toString());
for (AnnotationMirror mirror : elementUtils.getAllAnnotationMirrors(executableElement)) {
String annotationName = mirror.getAnnotationType().toString();
if (CompilerUtils.annotationsIsSupported(mirror.getAnnotationType().toString())) {
if (CompilerUtils.isSupportAnnotation(mirror.getAnnotationType().toString())) {
Annotation annotation =
executableElement.getAnnotation(CompilerUtils.getAnnotation(annotationName));
executableElement.getAnnotation(CompilerUtils.getSupportAnnotation(annotationName));
methodIR.addAnnotation(annotation);
} else if (mirror.getAnnotationType().asElement()
.getSimpleName().toString().toLowerCase().equals("nullable")) {
methodIR.addAnnotation(() -> android.support.annotation.Nullable.class);
} else if (mirror.getAnnotationType().asElement()
.getSimpleName().toString().toLowerCase().equals("nonnull")) {
methodIR.addAnnotation(() -> android.support.annotation.NonNull.class);
} else {
Annotation annotation = extractDefTypeAnnotations(mirror.getAnnotationType().asElement());
if (annotation != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@

package com.uber.rave.compiler;

import com.uber.rave.annotation.MustBeFalse;
import com.uber.rave.annotation.MustBeTrue;

import org.junit.Test;

import static org.junit.Assert.assertTrue;
Expand All @@ -31,7 +28,9 @@ public class CompilerUtilTest {

@Test
public void areConflicting_whenTrueAndFalseConflict_shouldReturnTrue() {
assertTrue(CompilerUtils.areConflicting(MustBeFalse.class, MustBeTrue.class));
assertTrue(CompilerUtils.areConflicting(MustBeTrue.class, MustBeFalse.class));
assertTrue(CompilerUtils.areConflicting("com.uber.rave.annotation.MustBeFalse",
"com.uber.rave.annotation.MustBeTrue"));
assertTrue(CompilerUtils.areConflicting("com.uber.rave.annotation.MustBeTrue",
"com.uber.rave.annotation.MustBeFalse"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,31 @@ public void testVoidReturnMethod_shouldNotValidate() {
"fixtures/voidreturn/SampleFactory_Generated_Validator.java"));
}

@Test
public void testJSR305AnnotatedMethods_shouldValidate() {
sources.add(JavaFileObjects.forResource("fixtures/jsr305/UseOfJSR305.java"));
sources.add(myValidator);

assertAbout(javaSources()).that(sources)
.processedWith(raveProcessor)
.compilesWithoutError()
.and()
.generatesSources(JavaFileObjects.forResource(
"fixtures/jsr305/SampleFactory_Generated_Validator.java"));
}

@Test
public void testJSR305ConflictingAnnotations_shouldFailCompile() {
sources.add(JavaFileObjects.forResource("fixtures/jsr305/JSR305Conflict.java"));
sources.add(myValidator);

assertAbout(javaSources()).that(sources)
.processedWith(raveProcessor)
.failsToCompile()
.withErrorContaining("Annotations javax.annotation.Nullable cannot be used with javax.annotation"
+ ".Nonnull");
}

static String readFile(String path) throws IOException {
byte[] encoded = Files.readAllBytes(Paths.get(path));
return new String(encoded);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import com.uber.rave.annotation.Validated;

@Validated(factory = SampleFactory.class)
public class UseOfExcluded implements NothingInterface {
public class UseOfExcluded {
String notNullField;
String canBeNullField;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package fixtures.jsr305;

import fixtures.SampleFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.uber.rave.annotation.Validated;

@Validated(factory = SampleFactory.class)
public class JSR305Conflict {
String canBeNullField;

public JSR305Conflict (String canBeNullField) {
this.canBeNullField = canBeNullField;
}

@Nonnull
@Nullable
public String getCanBeNullField() {
return canBeNullField;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package fixtures;

import com.uber.rave.BaseValidator;
import com.uber.rave.InvalidModelException;
import com.uber.rave.RaveError;
import fixtures.jsr305.UseOfJSR305;
import java.lang.Class;
import java.lang.IllegalArgumentException;
import java.lang.Object;
import java.lang.Override;
import java.util.List;
import javax.annotation.Generated;

@Generated(
value = "com.uber.rave.compiler.RaveProcessor",
comments = "https://github.com/uber-common/rave"
)
public final class SampleFactory_Generated_Validator extends BaseValidator {
SampleFactory_Generated_Validator() {
addSupportedClass(UseOfJSR305.class);
registerSelf();
}

@Override
protected void validateAs(Object object, Class<?> clazz) throws InvalidModelException {
if (!clazz.isInstance(object)) {
throw new IllegalArgumentException(object.getClass().getCanonicalName() + "is not of type" + clazz.getCanonicalName());
}
if (clazz.equals(UseOfJSR305.class)) {
validateAs((UseOfJSR305) object);
return;
}
throw new IllegalArgumentException(object.getClass().getCanonicalName() + " is not supported by validator " + this.getClass().getCanonicalName());
}

private void validateAs(UseOfJSR305 object) throws InvalidModelException {
BaseValidator.ValidationContext context = getValidationContext(UseOfJSR305.class);
List<RaveError> raveErrors = null;
context.setValidatedItemName("getNotNullField()");
raveErrors = mergeErrors(raveErrors, checkNullable(object.getNotNullField(), false, context));
context.setValidatedItemName("getCanBeNullField()");
raveErrors = mergeErrors(raveErrors, checkNullable(object.getCanBeNullField(), true, context));
if (raveErrors != null && !raveErrors.isEmpty()) {
throw new InvalidModelException(raveErrors);
}
}
}
30 changes: 30 additions & 0 deletions rave-compiler/src/test/resources/fixtures/jsr305/UseOfJSR305.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package fixtures.jsr305;

import fixtures.SampleFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.uber.rave.annotation.Validated;

@Validated(factory = SampleFactory.class)
public class UseOfJSR305 {
String notNullField;
String canBeNullField;

public UseOfJSR305 (
String notNullField,
String canBeNullField) {
this.notNullField = notNullField;
this.canBeNullField = canBeNullField;
}

@Nonnull
public String getNotNullField() {
return notNullField;
}

@Nullable
public String getCanBeNullField() {
return canBeNullField;
}
}
1 change: 0 additions & 1 deletion rave/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ targetCompatibility = 1.7

dependencies {
compileOnly deps.jsr305
compileOnly deps.supportAnnotations

testCompile project(":rave-test")
testCompile deps.jsr305
Expand Down
Loading

0 comments on commit f290611

Please sign in to comment.