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

Refactor processor to make extension easier #53

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

bndbsh
Copy link
Contributor

@bndbsh bndbsh commented Jun 1, 2017

This delegates most of the field-specific processing to FieldProcessor,
which can be implemented to customize handling of different types.
Collections are handled by CollectionProcessor, optionals by
OptionalProcessor. Everything else is handled by DefaultProcessor.

A data-driven approach was taken, with FieldProcessor taking the
descriptor and field as input and outputting Javapoet specs or
statements. However, in this patch CollectionProcessor is dependent on
AutoMatterProcessor for the implementation of singular.

There is also some duplication and inefficiencies in the processor as a
result of this refactor. Since doing away with them would change text
order in some places and thus require edits to most of the test files,
I prefer leaving that work to a followup commit to reduce review burden.

Other notes:

  • It's not clear where common code should live. For now most of it is
    in default methods on FieldProcessor, but I'm open to suggestions.

  • Due to the above use of default, source level 1.8 is now required

  • I wasn't sure how to organize the classes, so I've left them all at
    top level. It might become clearer once common code is centralized
    and other processors are added.

  • Not sure if this is feasible, but it could be interesting to allow
    users to supply their own processors via some annotation, or to
    register them somehow with AutoMatter. This would allow extensions to
    live in separate packages.

This delegates most of the field-specific processing to `FieldProcessor`,
which can be implemented to customize handling of different types.
Collections are handled by `CollectionProcessor`, optionals by
`OptionalProcessor`. Everything else is handled by `DefaultProcessor`.

A data-driven approach was taken, with `FieldProcessor` taking the
descriptor and field as input and outputting Javapoet specs or
statements. However, in this patch `CollectionProcessor` is dependent on
`AutoMatterProcessor` for the implementation of `singular`.

There is also some duplication and inefficiencies in the processor as a
result of this refactor. Since doing away with them would change text
order in some places and thus require edits to most of the test files,
I prefer leaving that work to a followup commit to reduce review burden.

Other notes:

 * It's not clear where common code should live. For now most of it is
   in default methods on `FieldProcessor`, but I'm open to suggestions.

 * Due to the above use of default, source level 1.8 is now required

 * I wasn't sure how to organize the classes, so I've left them all at
   top level. It might become clearer once common code is centralized
   and other processors are added.

 * Not sure if this is feasible, but it could be interesting to allow
   users to supply their own processors via some annotation, or to
   register them somehow with AutoMatter. This would allow extensions to
   live in separate packages.
@@ -123,6 +124,11 @@ private void process(final Element element) throws IOException, AutoMatterProces
javaFile.writeTo(filer);
}

private FieldProcessor processorForField(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException {
String prefix = fieldType(d, field).toString().split("<")[0];
Copy link
Owner

@danielnorberg danielnorberg Jun 5, 2017

Choose a reason for hiding this comment

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

Use guava Splitter instead of compiling a regex for every invocation.

@@ -86,6 +74,8 @@
private Elements elements;
private Messager messager;
private Types types;
private Map<String, FieldProcessor> processors;
private DefaultProcessor defaultProcessor = new DefaultProcessor();
Copy link
Owner

Choose a reason for hiding this comment

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

final

@@ -86,6 +74,8 @@
private Elements elements;
private Messager messager;
private Types types;
private Map<String, FieldProcessor> processors;
Copy link
Owner

Choose a reason for hiding this comment

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

Would it make sense for this to be a Map<Class, FieldProcessor> instead?

OptionalProcessor optionalProcessor = new OptionalProcessor();

processors = ImmutableMap.of(
"java.util.Map", collectionProcessor,
Copy link
Owner

Choose a reason for hiding this comment

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

Would be nice if we could avoid these handcrafted strings.

@@ -950,7 +491,7 @@ private void assertNotNull(MethodSpec.Builder spec, String name, String msg) {
.endControlFlow();
}

private TypeName builderType(final Descriptor d) {
public static TypeName builderType(final Descriptor d) {
Copy link
Owner

Choose a reason for hiding this comment

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

Move to a separate class and avoid circular imports?

@@ -0,0 +1,11 @@
package io.norberg.automatter.processor;

public class BuildStatements {
Copy link
Owner

Choose a reason for hiding this comment

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

package-private

package io.norberg.automatter.processor;

public class BuildStatements {
Iterable<Statement> statements;
Copy link
Owner

Choose a reason for hiding this comment

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

final

} else {
parameters.add(fieldName(field));
BuildStatements buildStatements = processorForField(d, field).build(d, field);
for (Statement s: buildStatements.statements) {
Copy link
Owner

Choose a reason for hiding this comment

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

Whitespace around :

import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.type.TypeKind.DECLARED;

public class CollectionProcessor implements FieldProcessor {
Copy link
Owner

Choose a reason for hiding this comment

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

package-private

public class CollectionProcessor implements FieldProcessor {
private final AutoMatterProcessor processor;

public CollectionProcessor(AutoMatterProcessor processor) {
Copy link
Owner

Choose a reason for hiding this comment

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

Can we factor things to avoid this circular dependency of passing in the AutoMatterProcessor into field processors?

BuildStatements build(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException;
Iterable<Statement> valueConstructor(Descriptor d, ExecutableElement field) throws AutoMatterProcessorException;

default TypeName fieldType(final Descriptor d, final ExecutableElement field) throws AutoMatterProcessorException {
Copy link
Owner

Choose a reason for hiding this comment

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

I guess these methods aren't actually part of the FieldProcessor interface. I.e., FieldProcessor users aren't expected to call them.

How about grouping and moving them to separate classes based on functionality? E.g. put the field introspection methods into e.g. a Fields class etc.

import static io.norberg.automatter.processor.AutoMatterProcessor.builderType;
import static javax.lang.model.element.Modifier.PUBLIC;

public class OptionalProcessor extends DefaultProcessor {
Copy link
Owner

Choose a reason for hiding this comment

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

package-private

@@ -0,0 +1,11 @@
package io.norberg.automatter.processor;

public class Statement {
Copy link
Owner

Choose a reason for hiding this comment

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

package-private

package io.norberg.automatter.processor;

public class Statement {
String statement;
Copy link
Owner

Choose a reason for hiding this comment

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

final

@danielnorberg
Copy link
Owner

This is a much-needed refactoring. Kudos!

Looks great overall, just a few small comments.

  • Eliminate circular imports and dependencies.
  • Everything should be package-private or private.
  • Leave everything at top level for now.

Allowing extensions in separate packages at sounds great. AutoMatterProcessor can use j.u.ServiceLoader to discover extensions and extensions can use AutoService to register themselves. Should be straightforward but let's do it in a separate PR.

@bndbsh
Copy link
Contributor Author

bndbsh commented Jul 12, 2017

Re: using Map<Class, FieldProcessor> instead of string, that would require having the necessary classes in the classpath wouldn't it? Maybe we can do ClassName instead?

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.

2 participants