-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BEAM-790] Validate PipelineOptions Default annotation. #1159
Conversation
0e3d981
to
ea63661
Compare
R: @lukecwik |
9f9168e
to
d18d4fa
Compare
/** | ||
* Validates that getters don't have mixed annotation. | ||
*/ | ||
private static void validateGettersNoMixedAnnotations( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
validateGettersNoMixedAnnotations -> validateGettersHaveConsistentAnnotation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
SortedSetMultimap<Method, Method> methodNameToAllMethodMap, | ||
List<PropertyDescriptor> descriptors, | ||
AnnotationPredicate annotationPredicate) { | ||
List<InconsistentlyAnnotatedGetters> incompletelyAnnotatedGetters = new ArrayList<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
incompletelyAnnotatedGetters -> inconsistentlyAnnotatedGetters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
/** | ||
* Validates that setters don't have the given annotation. | ||
*/ | ||
private static void validateSettersNoAnnotations( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
validateSettersNoAnnotations -> validateSettersDoNotHaveAnnotation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
SortedSet<Method> getters = methodNameToAllMethodMap.get(descriptor.getReadMethod()); | ||
SortedSet<Method> gettersWithJsonIgnore = Sets.filter(getters, JsonIgnorePredicate.INSTANCE); | ||
SortedSet<Method> gettersWithTheAnnotation = Sets.filter(getters, annotationPredicate); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the case of @default, this is missing the logic to make sure that the two distinct annotation instances are not equal.
So this will currently say that @Default.String("a") and @Default.String("b") are equivalent which is incorrect.
Conveniently you can use Annotation#equals as it does the right thing:
https://docs.oracle.com/javase/7/docs/api/java/lang/annotation/Annotation.html#equals(java.lang.Object)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
I think I didn't check the equivalency of the Default at all. Having @Default.String("a") and @Default.Integer(0) on a getter was considered okay.
fixed
+ " optionsId: %d\n" | ||
+ " string: overriddenValue\n", optionsId), | ||
deserializedOptions.toString()); | ||
public void testMultipleDefaultsThrow() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this test here and why are the other tests removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All three tests becomes illegal (cast between Simple.class and DefaultAnnotations.class).
So, only one is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your removing the purpose of those existing tests which was to testToString in various cases. It seems as though using two valid combinations of classes which are compatible is all that needs to be done to fix the test.
Void getConsistent(); | ||
void setConsistent(Void consistent); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a test case where the default annotation is the same class but has a different value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done + more cases
private final Predicate<Method> predicate; | ||
private final Class<? extends Annotation> annotationClass; | ||
|
||
AnnotationPredicate(Predicate<Method> predicate, Class<? extends Annotation> annotationClass) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you make the AnnotationPredicate an abstract class, you don't need to pass in the Predicate into the constructor and don't need the delegate calling implementation defined below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found I need two predicates, one for method and another for annotation.
So, I need to use composition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure?, your currently not using composition within the AnnotationPredicates class.
@@ -45,6 +45,7 @@ matrix: | |||
|
|||
before_install: | |||
# Workaround for https://github.com/travis-ci/travis-ci/issues/4629 | |||
- touch ~/.m2/settings.xml |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this included in this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
It was to test the fix for travis.
d18d4fa
to
3e87aa6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PTAL
@Nullable | ||
@Override | ||
public Annotation apply(@Nonnull Method method) { | ||
FluentIterable<Annotation> methodAnnotations = FluentIterable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of doing the filtering here, why not chain this into the FluentIterable above
.from(...)
.transformAndConcat(Method -> Iterable)
.filter(annotationPredicates.forAnnotation)
would also allow you to drop the 'final' declaration on annotationPredicates
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
but need to keep final for another code
SortedSetMultimap<Method, Method> methodNameToAllMethodMap, | ||
List<PropertyDescriptor> descriptors, | ||
AnnotationPredicate annotationPredicate) { | ||
final AnnotationPredicates annotationPredicates) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why final?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
new Predicate<Annotation>() { | ||
@Override | ||
public boolean apply(@Nonnull Annotation input) { | ||
return input.annotationType().equals(JsonIgnore.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use instanceof or swap the direction so its JsonIgnore.class.equals(input.annotationType)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
.filter(Predicates.notNull())); | ||
|
||
checkArgument(propertyAnnotations.size() <= 1, | ||
"Property [%s] is marked with multiple annotations: %s.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the error isn't that there are multiple annotations but they aren't all equivalent.
with multiple annotations -> with contradictory annotations
also, your going to make people search for where they are if you don't include the mapping from annotation to methods.
Something like:
Property [%s] is marked with contradictory annotations. Found [annotation1] on [methods], [annotation2] on [other methods], ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
* This class is inconsistent with {@link GetterWithDefault} that has a different | ||
* {@link Default @Default}. | ||
*/ | ||
private interface GetterWithInconsistentDefault extends PipelineOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetterWithInconsistentDefault -> GetterWithInconsistentDefaultType
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
setter.descriptor.getName(), setter.settersWithJsonIgnoreClassNames)); | ||
AnnotatedSetter setter = setters.get(0); | ||
throw new IllegalArgumentException(String.format( | ||
"Expected setter for property [%s] to not be marked with @%s on %s", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using the simple name drops the outer class name, would be good if it said @Default.String and not just @default
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
+ "on %s", | ||
setter.descriptor.getName(), setter.settersWithJsonIgnoreClassNames)); | ||
StringBuilder builder = new StringBuilder( | ||
String.format("Found setters marked with @%s:", annotationClass.getSimpleName())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto here on usage of simplename since it will drop the outer class name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
getter.descriptor.getName(), getter.getterClassNames, | ||
getter.gettersWithJsonIgnoreClassNames)); | ||
getter.descriptor.getName(), | ||
annotationClass.getSimpleName(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simple name will drop inner class name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think inner name is less useful here. When users see this error it is because one getter missed @Default.something.
After users added it, we have validation to verify the all Default annotations have to be the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using toStringForPrint here (and elsewhere) would improve the consistency in how we represent information to the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The annotationClass is Default.class.
It doesn't fit toStringForPrint, which takes a annotation instance.
new StringBuilder("Property getters are inconsistently marked with @JsonIgnore:"); | ||
for (InconsistentlyIgnoredGetters getter : getters) { | ||
StringBuilder errorBuilder = new StringBuilder(String.format( | ||
"Property getters are inconsistently marked with @%s:", annotationClass.getSimpleName())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simple name will drop inner class name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
expectedException.expect(IllegalArgumentException.class); | ||
expectedException.expectMessage( | ||
"Property [object] is marked with multiple annotations: [" | ||
+ "@org.apache.beam.sdk.options.Default$Integer(value=1), " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use the canonical name and drop the org.apache.sdk.options inside then this would say @Default.Integer(value=1) which is easier to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PTAL
@Nullable | ||
@Override | ||
public Annotation apply(@Nonnull Method method) { | ||
FluentIterable<Annotation> methodAnnotations = FluentIterable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
but need to keep final for another code
.filter(Predicates.notNull())); | ||
|
||
checkArgument(propertyAnnotations.size() <= 1, | ||
"Property [%s] is marked with multiple annotations: %s.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
SortedSetMultimap<Method, Method> methodNameToAllMethodMap, | ||
List<PropertyDescriptor> descriptors, | ||
AnnotationPredicate annotationPredicate) { | ||
final AnnotationPredicates annotationPredicates) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
new Predicate<Annotation>() { | ||
@Override | ||
public boolean apply(@Nonnull Annotation input) { | ||
return input.annotationType().equals(JsonIgnore.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
* This class is inconsistent with {@link GetterWithDefault} that has a different | ||
* {@link Default @Default}. | ||
*/ | ||
private interface GetterWithInconsistentDefault extends PipelineOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
expectedException.expect(IllegalArgumentException.class); | ||
expectedException.expectMessage( | ||
"Property [object] is marked with multiple annotations: [" | ||
+ "@org.apache.beam.sdk.options.Default$Integer(value=1), " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
getter.descriptor.getName(), getter.getterClassNames, | ||
getter.gettersWithJsonIgnoreClassNames)); | ||
getter.descriptor.getName(), | ||
annotationClass.getSimpleName(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think inner name is less useful here. When users see this error it is because one getter missed @Default.something.
After users added it, we have validation to verify the all Default annotations have to be the same.
new StringBuilder("Property getters are inconsistently marked with @JsonIgnore:"); | ||
for (InconsistentlyIgnoredGetters getter : getters) { | ||
StringBuilder errorBuilder = new StringBuilder(String.format( | ||
"Property getters are inconsistently marked with @%s:", annotationClass.getSimpleName())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
setter.descriptor.getName(), setter.settersWithJsonIgnoreClassNames)); | ||
AnnotatedSetter setter = setters.get(0); | ||
throw new IllegalArgumentException(String.format( | ||
"Expected setter for property [%s] to not be marked with @%s on %s", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
+ "on %s", | ||
setter.descriptor.getName(), setter.settersWithJsonIgnoreClassNames)); | ||
StringBuilder builder = new StringBuilder( | ||
String.format("Found setters marked with @%s:", annotationClass.getSimpleName())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
f886096
to
f8b672e
Compare
@Override | ||
public String apply(@Nonnull Annotation annotation) { | ||
return String.format( | ||
"[%s on %s.%s]", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please rely on ReflectHelper.CLASS_AND_METHOD_FORMATTER for consistency in how we print out class+method names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
} | ||
|
||
@VisibleForTesting | ||
static String toStringForPrint(Annotation annotation) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add this as a function to ReflectHelpers like the other to string methods so we get pretty printed annotations done consistently elsewhere when it comes up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
getter.descriptor.getName(), getter.getterClassNames, | ||
getter.gettersWithJsonIgnoreClassNames)); | ||
getter.descriptor.getName(), | ||
annotationClass.getSimpleName(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using toStringForPrint here (and elsewhere) would improve the consistency in how we represent information to the user.
@VisibleForTesting | ||
static String toStringForPrint(Annotation annotation) { | ||
String toString = annotation.toString(); | ||
return toString.substring(toString.lastIndexOf('.') + 1).replace('$', '.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if the annotation is a string containing . and or $?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
684c5e0
to
75ccba8
Compare
* @param annotation The {@link Annotation} to print. | ||
* @return a concise string representation that doesn't contain the package name. | ||
*/ | ||
public static String toStringForPrint(Annotation annotation) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make this a function object like the others in this class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
PTAL |
3514a89
to
f7b108b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor comments and then LGTM
/** | ||
* A {@link Function} that returns a concise string for a {@link Annotation}. | ||
*/ | ||
public static final Function<Annotation, String> ANNOTATION_CONCISE_STRING = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ANNOTATION_CONCISE_STRING -> ANNOTATION_FORMATTER
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -139,12 +139,12 @@ public void testTypeFormatterWithGenerics() throws Exception { | |||
public void testToStringForPrint() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
testToStringForPrint -> testAnnotationFormatter
Add a test for a default value with a . and ( and $ in it so that we remember why we have string parsing logic setup the way we do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
PTAL |
LGTM, merging |
No description provided.