Skip to content

Refactor processor to make extension easier#53

Open
bndbsh wants to merge 2 commits intodanielnorberg:masterfrom
bndbsh:refactor
Open

Refactor processor to make extension easier#53
bndbsh wants to merge 2 commits intodanielnorberg:masterfrom
bndbsh:refactor

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.
}

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.

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

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.

}

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