Skip to content

Commit

Permalink
Add ScopeFactory API
Browse files Browse the repository at this point in the history
  • Loading branch information
Leland Takamine committed Dec 3, 2019
1 parent 423d3b5 commit 21343e5
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 1 deletion.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ Motif is a DI library that offers a simple API optimized for nested scopes. Unde
| [![Maven Central](https://img.shields.io/maven-central/v/com.uber.motif/motif-compiler.svg)](https://search.maven.org/artifact/com.uber.motif/motif-compiler)<br>[![Maven Central](https://img.shields.io/maven-central/v/com.uber.motif/motif.svg)](https://search.maven.org/artifact/com.uber.motif/motif) | <pre>annotationProcessor 'com.uber.motif:motif-compiler:x.y.z'<br>implementation 'com.uber.motif:motif:x.y.z'</pre> |
|-|:-|

## Proguard

```proguard
-keepnames @motif.Scope interface *
-keepnames @motif.ScopeImpl class * {
<init>(...);
}
```

## The Basics

This is a Motif Scope. It serves as a container for objects that can be created by this Scope:
Expand Down Expand Up @@ -206,6 +215,13 @@ interface MainScope extends Creatable<MainDependencies> {
interface MainDependencies {}
```

Extending `Creatable<D>` also enables instantiation of a "root" `Scope` without referencing generated code using Motif's `ScopeFactory.create` API:

```java
MainDependencies dependencies = ...;
MainScope mainScope = ScopeFactory.create(MainScope.class, dependencies)
```

## Convenience APIs

Factory methods that pass parameters through to a constructor without modification can be converted to parameterless abstract methods:
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/main/kotlin/motif/compiler/ScopeImplFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import motif.ast.compiler.CompilerMethod
import motif.ast.compiler.CompilerType
import motif.core.ResolvedGraph
import motif.core.ScopeEdge
import motif.internal.Constants
import motif.models.*
import javax.annotation.processing.ProcessingEnvironment

Expand Down Expand Up @@ -342,7 +343,7 @@ class ScopeImplFactory private constructor(
get() = scopeImplClassNames.computeIfAbsent(this) { scope ->
val scopeClassName = scope.clazz.typeName
val prefix = scopeClassName.kt.simpleNames.joinToString("")
ClassName.get(scopeClassName.kt.packageName, "${prefix}Impl")
ClassName.get(scopeClassName.kt.packageName, "$prefix${Constants.SCOPE_IMPL_SUFFIX}")
}

private val Scope.dependenciesClassName: ClassName
Expand Down
84 changes: 84 additions & 0 deletions lib/src/main/java/motif/ScopeFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright (c) 2018-2019 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package motif;

import motif.internal.Constants;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class ScopeFactory {

private ScopeFactory() {}

private static final Map<Class<?>, Constructor<?>> scopeClassToCreateMethod = new HashMap<>();

public static <S extends Creatable<D>, D> S create(Class<S> scopeClass, D dependencies) {
Constructor<?> constructor = getConstructor(scopeClass);
try {
return (S) constructor.newInstance(dependencies);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
}

private synchronized static Constructor<?> getConstructor(Class<?> scopeClass) {
Constructor<?> constructor = scopeClassToCreateMethod.get(scopeClass);
if (constructor == null) {
Class<?> scopeImplClass = getScopeImplClass(scopeClass);
constructor = scopeImplClass.getDeclaredConstructors()[0];
scopeClassToCreateMethod.put(scopeClass, constructor);
}
return constructor;
}

private static Class<?> getScopeImplClass(Class<?> scopeClass) {
String scopeImplClassName = getScopeImplClassName(scopeClass);
try {
return Class.forName(scopeImplClassName, true, scopeClass.getClassLoader());
} catch (ClassNotFoundException e) {
throw new RuntimeException(
"Could not find Scope implementation class " + scopeImplClassName + ". Ensure that the Motif " +
"annotation processor is enabled and that proguard is configured correctly (See README for details).");
}
}

// Inspired by https://github.com/square/javapoet/issues/295
private static String getScopeImplClassName(Class<?> scopeClass) {
StringBuilder sb = new StringBuilder();
sb.append(Constants.SCOPE_IMPL_SUFFIX);
Class<?> clazz = scopeClass;
while (true) {
sb.insert(0, clazz.getSimpleName());
Class<?> enclosing = clazz.getEnclosingClass();
if (enclosing == null) break;
clazz = enclosing;
}
int lastDot = clazz.getName().lastIndexOf('.');
if (lastDot != -1) {
sb.insert(0, '.');
sb.insert(0, clazz.getName().substring(0, lastDot));
}
return sb.toString();
}
}
23 changes: 23 additions & 0 deletions lib/src/main/java/motif/internal/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2018-2019 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package motif.internal;

public class Constants {

public static final String SCOPE_IMPL_SUFFIX = "Impl";

private Constants() {}
}
32 changes: 32 additions & 0 deletions tests/src/main/java/testcases/T071_scope_factory/GRAPH.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
########################################################################
# #
# This file is auto-generated by running the Motif compiler tests and #
# serves a as validation of graph correctness. IntelliJ plugin tests #
# also rely on this file to ensure that the plugin graph understanding #
# is equivalent to the compiler's. #
# #
# - Do not edit manually. #
# - Commit changes to source control. #
# - Since this file is autogenerated, code review changes carefully to #
# ensure correctness. #
# #
########################################################################

-------
| Scope |
-------

==== Required ====

---- String ----
[ Provided By ]
[ Consumed By ]
* Scope | Scope.string()

==== Provides ====

---- Scope | implicit ----
[ Required ]
[ Consumed By ]


29 changes: 29 additions & 0 deletions tests/src/main/java/testcases/T071_scope_factory/Scope.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2018-2019 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package testcases.T071_scope_factory;

import motif.Creatable;

@motif.Scope
public interface Scope extends Creatable<Scope.Dependencies> {

String string();

interface Dependencies {

String s();
}
}
33 changes: 33 additions & 0 deletions tests/src/main/java/testcases/T071_scope_factory/Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2018-2019 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package testcases.T071_scope_factory;

import motif.ScopeFactory;

import static com.google.common.truth.Truth.assertThat;

public class Test {

public static void run() {
Scope scope = ScopeFactory.create(Scope.class, new Scope.Dependencies() {
@Override
public String s() {
return "s";
}
});
assertThat(scope.string()).isEqualTo("s");
}
}
9 changes: 9 additions & 0 deletions tests/src/main/java/testcases/T071_scope_factory/config.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-dontshrink
-keep class **Test {
public static void run();
}

-keepnames @motif.Scope interface *
-keepnames @motif.ScopeImpl class * {
<init>(...);
}

0 comments on commit 21343e5

Please sign in to comment.