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 scopes - Removes ApplicationScope & RequestScope in favour of "custom scopes" #134

Merged
merged 31 commits into from
Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
84c9d68
Remove SystemContext
rbygrave Jul 19, 2021
a2f4907
Remove ApplicationScope (by moving to test code)
rbygrave Jul 19, 2021
079c626
Remove RequestScope
rbygrave Jul 19, 2021
414c182
Remove deprecated methods from BeanScope
rbygrave Jul 19, 2021
be9f1ca
Add BeanScopeBuilder.ForTesting interface and move withMock() and wit…
rbygrave Jul 19, 2021
46f0c21
Remove deprecated BeanScopeBuilder.withNoShutdownHook()
rbygrave Jul 19, 2021
55daafe
BeanScope extends AutoCloseable rather than Closeable
rbygrave Jul 19, 2021
4b7372a
Generator - Extract ScopeInfo from Processor
rbygrave Jul 19, 2021
2394785
Generator module name change e.g. ExampleModule rather than _DI$BeanS…
rbygrave Jul 19, 2021
7d71a24
Refactor rename BeanScopeFactory to Module
rbygrave Jul 19, 2021
d03321f
Change InjectModule and Module to use Class<?> for provides() and req…
rbygrave Jul 19, 2021
71dc7fa
Tidy javadoc
rbygrave Jul 19, 2021
07553ad
Initial support for custom scopes
rbygrave Jul 20, 2021
098cdd6
BeanScopeBuilder javadoc update
rbygrave Jul 20, 2021
12a029f
Add Module.Custom marker interface, read @InjectModule on custom scop…
rbygrave Jul 20, 2021
8bd6257
Add writing META-INF services file for Module.Custom
rbygrave Jul 20, 2021
69063a3
Support partial compile for Module.Custom
rbygrave Jul 20, 2021
b1066d8
Test for custom scope with externally provided dependency
rbygrave Jul 20, 2021
95155fa
Support custom scope with dependency provided by the "default" scope
rbygrave Jul 20, 2021
873236f
Refactor rename CustomScopes to AllScopes
rbygrave Jul 20, 2021
22340bb
Add support for parent scopes, plus scopes can "require" other scopes…
rbygrave Jul 20, 2021
aed5899
Fix MetaDataOrdering missingDependencies() with external dependencies…
rbygrave Jul 20, 2021
1a94784
Support @Factory with custom scope
rbygrave Jul 20, 2021
4f00641
Refactor ScopeInfo names (moduleFullName, factoryFullName, topPackage…
rbygrave Jul 20, 2021
0da064a
Fix for #128 Unnecessary compiler warning on every build
rbygrave Jul 20, 2021
95fd520
Change back to write build methods on "Module" (rather than on separa…
rbygrave Jul 20, 2021
1515748
Generator internals only - refactor rename to defaultScope etc
rbygrave Jul 21, 2021
d69781f
Add BeanScope.all() method to return all BeanEntry
rbygrave Jul 21, 2021
6bf30f0
Fix to allow nothing in the default scope, only custom scopes being used
rbygrave Jul 21, 2021
b01b35f
Javadoc improvements
rbygrave Jul 21, 2021
4ca7f58
Javadoc improvements
rbygrave Jul 21, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ module org.example {

requires io.avaje.inject;

// register org.example._DI$BeanScopeFactory from generated sources
provides io.avaje.inject.spi.BeanScopeFactory with org.example._DI$BeanScopeFactory;
provides io.avaje.inject.spi.Module with org.example.ExampleModule;
}
```

Expand Down
4 changes: 2 additions & 2 deletions inject-generator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject-parent</artifactId>
<version>6.2</version>
<version>6.5-RC0</version>
</parent>

<artifactId>avaje-inject-generator</artifactId>
Expand All @@ -16,7 +16,7 @@
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject</artifactId>
<version>6.2</version>
<version>6.5-RC0</version>
</dependency>

<!-- test dependencies -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package io.avaje.inject.generator;

import io.avaje.inject.InjectModule;

import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.FileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class AllScopes {

private final Map<String, Data> scopeAnnotations = new HashMap<>();
private final ProcessingContext context;
private final ScopeInfo defaultScope;

AllScopes(ProcessingContext context) {
this.context = context;
this.defaultScope = new ScopeInfo(context);
}

ScopeInfo defaultScope() {
return defaultScope;
}

ScopeInfo addScopeAnnotation(TypeElement type) {
final Data data = new Data(type, context, this);
scopeAnnotations.put(type.getQualifiedName().toString(), data);
return data.scopeInfo;
}

boolean providedByDefaultModule(String dependency) {
return defaultScope.providesDependency(dependency);
}

void readBeans(RoundEnvironment roundEnv) {
for (Data data : scopeAnnotations.values()) {
for (Element customBean : roundEnv.getElementsAnnotatedWith(data.type)) {
// context.logWarn("read custom scope bean " + customBean + " for scope " + entry.getKey());
data.scopeInfo.read((TypeElement) customBean, false);
}
}
}

void write(boolean processingOver) {
for (Data value : scopeAnnotations.values()) {
value.write(processingOver);
}
if (processingOver) {
writeModuleCustomServicesFile();
}
}

private void writeModuleCustomServicesFile() {
if (scopeAnnotations.isEmpty()) {
return;
}
try {
FileObject jfo = context.createMetaInfModuleCustom();
if (jfo != null) {
Writer writer = jfo.openWriter();
for (Data value : scopeAnnotations.values()) {
writer.write(value.moduleFullName());
writer.write("\n");
}
writer.close();
}

} catch (IOException e) {
e.printStackTrace();
context.logError("Failed to write services file " + e.getMessage());
}
}

void readModules(List<String> customScopeModules) {
for (String customScopeModule : customScopeModules) {
final TypeElement module = context.element(customScopeModule);
if (module != null) {
final InjectModule injectModule = module.getAnnotation(InjectModule.class);
if (injectModule != null) {
final String customScopeType = injectModule.customScopeType();
final TypeElement scopeType = context.element(customScopeType);
if (scopeType == null) {
context.logError(module, "customScopeType [" + customScopeType + "] is invalid? on " + module);
} else {
final ScopeInfo scopeInfo = addScopeAnnotation(scopeType);
scopeInfo.readModuleMetaData(module);
}
}
}
}
}

/**
* Find the scope by scope annotation type.
*/
ScopeInfo get(String fullType) {
final Data data = scopeAnnotations.get(fullType);
return data == null ? null : data.scopeInfo;
}

static class Data {
final TypeElement type;
final ScopeInfo scopeInfo;

Data(TypeElement type, ProcessingContext context, AllScopes allScopes) {
this.type = type;
this.scopeInfo = new ScopeInfo(context, type, allScopes);
this.scopeInfo.details(null, type);
}

void write(boolean processingOver) {
scopeInfo.write(processingOver);
}

String moduleFullName() {
return scopeInfo.moduleFullName();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,6 @@ boolean isExtraInjectionRequired() {
}

void buildAddFor(Append writer) {
if (requestParams.isRequestParam()) {
context.logError(beanType, "@Singleton %s is not allowed to have a @Request scope dependency %s", shortName, requestParams.getRequestParamType());
}
writer.append(" if (builder.isAddBeanFor(");
if (name != null && !name.isEmpty()) {
writer.append("\"%s\", ", name);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package io.avaje.inject.generator;

import io.avaje.inject.Request;

import javax.lang.model.element.TypeElement;
import java.util.Set;

/**
Expand All @@ -15,22 +13,13 @@ class BeanRequestParams {
private final boolean requestScopedBean;

private RequestScope.Handler reqScopeHandler;
private String requestParamType;

BeanRequestParams(ProcessingContext context, String parentType, boolean requestScopedBean) {
this.context = context;
this.parentType = parentType;
this.requestScopedBean = requestScopedBean;
}

boolean isRequestParam() {
return requestParamType != null;
}

String getRequestParamType() {
return requestParamType;
}

/**
* Return true if this type is a request scoped type (e.g. Javalin Context).
*/
Expand All @@ -45,14 +34,6 @@ boolean check(String paramType) {
}
return true;
}
if (paramType != null && requestParamType == null) {
final TypeElement element = context.element(paramType);
if (element != null) {
if (element.getAnnotation(Request.class) != null) {
requestParamType = paramType;
}
}
}
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class Constants {

static final String AT_SINGLETON = "@Singleton";
static final String AT_GENERATED = "@Generated(\"io.avaje.inject.generator\")";
static final String META_INF_FACTORY = "META-INF/services/io.avaje.inject.spi.BeanScopeFactory";
static final String META_INF_FACTORY = "META-INF/services/io.avaje.inject.spi.Module";
static final String META_INF_CUSTOM = "META-INF/services/io.avaje.inject.spi.Module.Custom";

static final String REQUESTSCOPE = "io.avaje.inject.RequestScope";
static final String BEANCONTEXT = "io.avaje.inject.BeanScope";
Expand All @@ -31,5 +32,5 @@ class Constants {
static final String BEAN_FACTORY2 = "io.avaje.inject.spi.BeanFactory2";
static final String BUILDER = "io.avaje.inject.spi.Builder";
static final String DEPENDENCYMETA = "io.avaje.inject.spi.DependencyMeta";
static final String BEANSCOPEFACTORY = "io.avaje.inject.spi.BeanScopeFactory";
static final String MODULE = "io.avaje.inject.spi.Module";
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class MetaData {
private final String type;
private final String shortType;
private final String name;

private final List<String> externallyProvided = new ArrayList<>();
private String method;
private boolean wired;
private boolean requestScope;
Expand Down Expand Up @@ -229,4 +229,7 @@ void setMethod(String method) {
this.method = method;
}

void externallyProvided(String dependency) {
externallyProvided.add(dependency);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,17 @@ class MetaDataOrdering {
"\n See https://avaje.io/inject/#circular";

private final ProcessingContext context;

private final ScopeInfo scopeInfo;
private final List<MetaData> orderedList = new ArrayList<>();
private final List<MetaData> requestScope = new ArrayList<>();
private final List<MetaData> queue = new ArrayList<>();

private final Map<String, ProviderList> providers = new HashMap<>();

private final List<DependencyLink> circularDependencies = new ArrayList<>();

private final Set<String> missingDependencyTypes = new LinkedHashSet<>();

private String topPackage;

MetaDataOrdering(Collection<MetaData> values, ProcessingContext context) {
MetaDataOrdering(Collection<MetaData> values, ProcessingContext context, ScopeInfo scopeInfo) {
this.context = context;
this.scopeInfo = scopeInfo;
for (MetaData metaData : values) {
if (metaData.isRequestScope()) {
// request scoped expected to have externally provided dependencies
Expand All @@ -37,21 +33,20 @@ class MetaDataOrdering {
} else {
queue.add(metaData);
}
topPackage = Util.commonParent(topPackage, metaData.getTopPackage());
// register into map keyed by provider
providers.computeIfAbsent(metaData.getType(), s -> new ProviderList()).add(metaData);
for (String provide : metaData.getProvides()) {
providers.computeIfAbsent(provide, s -> new ProviderList()).add(metaData);
}
}
externallyRequiredDependencies(context);
externallyRequiredDependencies();
}

/**
* These if defined are expected to be required at wiring time probably via another module.
*/
private void externallyRequiredDependencies(ProcessingContext context) {
for (String requireType : context.contextRequires()) {
private void externallyRequiredDependencies() {
for (String requireType : scopeInfo.requires()) {
providers.computeIfAbsent(requireType, s -> new ProviderList());
}
}
Expand Down Expand Up @@ -120,32 +115,38 @@ private void errorOnCircularDependencies() {
* Build list of specific dependencies that are missing.
*/
void missingDependencies() {
for (MetaData m : queue) {
for (String dependency : m.getDependsOn()) {
if (providers.get(dependency) == null) {
TypeElement element = context.elementMaybe(m.getType());
context.logError(element, "No dependency provided for " + dependency);
missingDependencyTypes.add(dependency);
}
}
for (MetaData metaData : queue) {
checkMissingDependencies(metaData);
}
if (missingDependencyTypes.isEmpty()) {
// only look for circular dependencies if there are no missing dependencies
detectCircularDependency(queue);
}
}

private void checkMissingDependencies(MetaData metaData) {
for (String dependency : metaData.getDependsOn()) {
if (providers.get(dependency) == null && !scopeInfo.providedByOtherModule(dependency)) {
TypeElement element = context.elementMaybe(metaData.getType());
context.logError(element, "No dependency provided for " + dependency + " on " + metaData.getType());
missingDependencyTypes.add(dependency);
}
}
}

/**
* Log a warning on unsatisfied dependencies that are expected to be provided by another module.
*/
private void warnOnDependencies() {
if (missingDependencyTypes.isEmpty()) {
context.logWarn("There are " + queue.size() + " beans with unsatisfied dependencies (assuming external dependencies)");
for (MetaData m : queue) {
context.logWarn("Unsatisfied dependencies on %s dependsOn %s", m, m.getDependsOn());
}
} else {
if (!missingDependencyTypes.isEmpty()) {
context.logError("Dependencies %s are not provided - missing @Singleton or @Factory/@Bean or specify external dependency via @InjectModule requires attribute", missingDependencyTypes);
} else {
if (!queue.isEmpty()) {
context.logWarn("There are " + queue.size() + " beans with unsatisfied dependencies (assuming external dependencies)");
for (MetaData m : queue) {
context.logWarn("Unsatisfied dependencies on %s dependsOn %s", m, m.getDependsOn());
}
}
}
}

Expand All @@ -157,10 +158,6 @@ void logWarnings() {
}
}

String getTopPackage() {
return topPackage;
}

private int processQueueRound() {
// loop queue looking for entry that has all provides marked as included
int count = 0;
Expand All @@ -183,8 +180,7 @@ private boolean allDependenciesWired(MetaData queuedMeta) {
// check non-provider dependency is satisfied
ProviderList providerList = providers.get(dependency);
if (providerList == null) {
// dependency not yet satisfied
return false;
return scopeInfo.providedByOtherModule(dependency);
} else {
if (!providerList.isAllWired()) {
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.avaje.inject.generator;

import java.util.Collection;

class MetaTopPackage {

private String topPackage;

static String of(Collection<MetaData> values) {
return new MetaTopPackage(values).value();
}

private String value() {
return topPackage;
}

private MetaTopPackage(Collection<MetaData> values) {
for (MetaData metaData : values) {
topPackage = Util.commonParent(topPackage, metaData.getTopPackage());
}
}
}
Loading