Skip to content
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

Closed
wants to merge 6 commits into from

Conversation

peihe
Copy link
Contributor

@peihe peihe commented Oct 22, 2016

No description provided.

@peihe
Copy link
Contributor Author

peihe commented Oct 22, 2016

R: @lukecwik

@peihe peihe force-pushed the validate-default-2 branch 6 times, most recently from 9f9168e to d18d4fa Compare October 24, 2016 23:36
/**
* Validates that getters don't have mixed annotation.
*/
private static void validateGettersNoMixedAnnotations(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateGettersNoMixedAnnotations -> validateGettersHaveConsistentAnnotation

Copy link
Contributor Author

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<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incompletelyAnnotatedGetters -> inconsistentlyAnnotatedGetters

Copy link
Contributor Author

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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateSettersNoAnnotations -> validateSettersDoNotHaveAnnotation

Copy link
Contributor Author

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);
Copy link
Member

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)

Copy link
Contributor Author

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 {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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);
}

Copy link
Member

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.

Copy link
Contributor Author

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) {
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

@peihe peihe left a 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
Copy link
Member

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

Copy link
Contributor Author

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why final?

Copy link
Contributor Author

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);
Copy link
Member

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)

Copy link
Contributor Author

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.",
Copy link
Member

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], ...

Copy link
Contributor Author

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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetterWithInconsistentDefault -> GetterWithInconsistentDefaultType

Copy link
Contributor Author

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",
Copy link
Member

@lukecwik lukecwik Oct 26, 2016

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

Copy link
Contributor Author

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()));
Copy link
Member

@lukecwik lukecwik Oct 26, 2016

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

Copy link
Contributor Author

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(),
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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()));
Copy link
Member

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.

Copy link
Contributor Author

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), "
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor Author

@peihe peihe left a 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
Copy link
Contributor Author

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.",
Copy link
Contributor Author

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) {
Copy link
Contributor Author

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);
Copy link
Contributor Author

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 {
Copy link
Contributor Author

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), "
Copy link
Contributor Author

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(),
Copy link
Contributor Author

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()));
Copy link
Contributor Author

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",
Copy link
Contributor Author

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()));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@Override
public String apply(@Nonnull Annotation annotation) {
return String.format(
"[%s on %s.%s]",
Copy link
Member

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.

Copy link
Contributor Author

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) {
Copy link
Member

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.

Copy link
Contributor Author

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(),
Copy link
Member

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('$', '.');
Copy link
Member

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 $?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

* @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) {
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@peihe
Copy link
Contributor Author

peihe commented Nov 1, 2016

PTAL

Copy link
Member

@lukecwik lukecwik left a 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 =
Copy link
Member

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

Copy link
Contributor Author

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 {
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@peihe
Copy link
Contributor Author

peihe commented Nov 3, 2016

PTAL

@lukecwik
Copy link
Member

lukecwik commented Nov 3, 2016

LGTM, merging

@asfgit asfgit closed this in bdd71c4 Nov 3, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants