-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement simple plugin to generate client
Fix #4
- Loading branch information
Dmitry Antonyuk
committed
Sep 19, 2019
1 parent
7bd2b0c
commit ec3e8d9
Showing
17 changed files
with
525 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
plugins { | ||
`java-library` | ||
id("java-gradle-plugin") | ||
} | ||
|
||
gradlePlugin { | ||
plugins { | ||
create("voorheesPlugin") { | ||
id = "voorhees" | ||
displayName = "Plugin for generating Voorhees clients" | ||
description = "Gradle plugin that helps to generate and publish client library for Voorhees services" | ||
implementationClass = "com.hylamobile.voorhees.gradle.VoorheesPlugin" | ||
} | ||
} | ||
} | ||
|
||
dependencies { | ||
compileOnly(gradleApi()) | ||
api("org.reflections:reflections:0.9.11") | ||
api("net.bytebuddy:byte-buddy:1.10.1") | ||
|
||
testCompileOnly(gradleTestKit()) | ||
testImplementation("junit:junit:4.12") | ||
} | ||
|
||
repositories { | ||
jcenter() | ||
} |
9 changes: 9 additions & 0 deletions
9
...gradle-plugin/src/main/kotlin/com/hylamobile/voorhees/client/annotation/JsonRpcService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.hylamobile.voorhees.client.annotation | ||
|
||
/** | ||
* Just a fake annotation to not depend on voorhees-client | ||
*/ | ||
@Target(AnnotationTarget.CLASS) | ||
@Retention(AnnotationRetention.RUNTIME) | ||
@MustBeDocumented | ||
annotation class JsonRpcService(val location: String) |
98 changes: 98 additions & 0 deletions
98
voorhees-gradle-plugin/src/main/kotlin/com/hylamobile/voorhees/gradle/ClientGenerator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package com.hylamobile.voorhees.gradle | ||
|
||
import com.hylamobile.voorhees.server.annotation.DontExpose | ||
import com.hylamobile.voorhees.server.annotation.JsonRpcService as ServerJsonRpcService | ||
import com.hylamobile.voorhees.client.annotation.JsonRpcService as ClientJsonRpcService | ||
import net.bytebuddy.ByteBuddy | ||
import net.bytebuddy.description.annotation.AnnotationDescription | ||
import net.bytebuddy.description.modifier.Visibility | ||
import org.reflections.Reflections | ||
import org.reflections.util.ClasspathHelper | ||
import java.io.File | ||
import java.lang.reflect.* | ||
import java.net.URL | ||
import java.net.URLClassLoader | ||
|
||
class ClientGenerator( | ||
private val packageToScan: String, | ||
private val classPath: Array<URL>, | ||
private val genDir: File) { | ||
|
||
private val namingPattern = "%s.%s" | ||
|
||
private val classesToGenerate: MutableSet<Class<*>> = mutableSetOf() | ||
|
||
init { | ||
genDir.apply { | ||
if (!exists()) { | ||
mkdirs() | ||
} | ||
|
||
check(isDirectory) { "${genDir.canonicalPath} is not a directory" } | ||
} | ||
} | ||
|
||
fun generate() { | ||
val buildClassLoader = URLClassLoader(classPath, ClasspathHelper.contextClassLoader()) | ||
val serviceClasses = Reflections(packageToScan, buildClassLoader) | ||
.getTypesAnnotatedWith(ServerJsonRpcService::class.java) | ||
|
||
for (serviceClass in serviceClasses) { | ||
val location = serviceClass.getAnnotation(ServerJsonRpcService::class.java).locations.get(0) | ||
val jsonRpcServiceAnno = AnnotationDescription.Builder | ||
.ofType(ClientJsonRpcService::class.java) | ||
.define("location", location) | ||
.build() | ||
|
||
var remoteInterface = ByteBuddy().makeInterface() | ||
.name(namingPattern.format(serviceClass.`package`.name, serviceClass.simpleName)) | ||
.annotateType(jsonRpcServiceAnno) | ||
.merge(Visibility.PUBLIC) | ||
|
||
for (method in serviceClass.publicMethods) { | ||
if (method.isAnnotationPresent(DontExpose::class.java)) continue | ||
|
||
remoteInterface = remoteInterface | ||
.defineMethod(method.name, method.genericReturnType, Visibility.PUBLIC) | ||
.withParameters(*method.genericParameterTypes).withoutCode() | ||
|
||
gatherClassesFromMethod(method) | ||
} | ||
|
||
remoteInterface.make().saveIn(genDir) | ||
} | ||
|
||
classesToGenerate.forEach { | ||
ByteBuddy().rebase(it).make().saveIn(genDir) | ||
} | ||
} | ||
|
||
private fun gatherClassesFromMethod(method: Method) { | ||
classesFrom(method.genericReturnType) | ||
method.genericParameterTypes.asSequence().forEach(this::classesFrom) | ||
} | ||
|
||
private fun classesFrom(type: Type?) { | ||
if (classesToGenerate.contains(type)) return | ||
|
||
when (type) { | ||
is Class<*> -> when { | ||
type.isArray -> classesFrom(type.componentType) | ||
type.name.startsWith(packageToScan) -> { | ||
classesToGenerate.add(type) | ||
classesFrom(type.genericSuperclass) | ||
type.genericInterfaces.forEach(this::classesFrom) | ||
type.publicMethods.forEach(this::gatherClassesFromMethod) | ||
} | ||
} | ||
is GenericArrayType -> classesFrom(type.genericComponentType) | ||
is TypeVariable<*> -> { | ||
type.genericDeclaration.typeParameters.forEach(this::classesFrom) | ||
type.bounds.asSequence().forEach(this::classesFrom) | ||
} | ||
} | ||
} | ||
|
||
private val Class<*>.publicMethods | ||
get() = declaredMethods.filter { Modifier.isPublic(it.modifiers) } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
voorhees-gradle-plugin/src/main/kotlin/com/hylamobile/voorhees/gradle/VoorheesExtension.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.hylamobile.voorhees.gradle | ||
|
||
import org.gradle.api.Project | ||
import org.gradle.api.internal.provider.DefaultProvider | ||
import org.gradle.api.provider.Property | ||
|
||
open class VoorheesExtension(private val project: Project) { | ||
var packageToScan: String = "" | ||
val group = stringProperty { project.group.toString() } | ||
val artifact = stringProperty { project.name + "-client" } | ||
val version = stringProperty { project.version.toString() } | ||
|
||
private fun stringProperty(defaultValue: () -> String): Property<String> = | ||
project.objects.property(String::class.java).convention(DefaultProvider(defaultValue)) | ||
} |
80 changes: 75 additions & 5 deletions
80
voorhees-gradle-plugin/src/main/kotlin/com/hylamobile/voorhees/gradle/VoorheesPlugin.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,83 @@ | ||
package com.hylamobile.voorhees.gradle | ||
|
||
import org.gradle.api.Plugin | ||
import org.gradle.api.Project | ||
import org.gradle.api.artifacts.type.ArtifactTypeDefinition | ||
import org.gradle.api.component.SoftwareComponentFactory | ||
import org.gradle.api.internal.artifacts.ArtifactAttributes | ||
import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency | ||
import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact | ||
import org.gradle.api.internal.project.ProjectInternal | ||
import org.gradle.jvm.tasks.Jar | ||
import java.io.File | ||
import javax.inject.Inject | ||
import org.gradle.api.plugins.internal.JavaConfigurationVariantMapping | ||
import org.gradle.api.publish.PublishingExtension | ||
import org.gradle.api.publish.maven.MavenPublication | ||
import org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication | ||
|
||
class VoorheesPlugin : Plugin<Project> { | ||
const val CLIENT_NAME = "JsonRpcClient" | ||
|
||
override fun apply(project: Project) { | ||
project.tasks.register("generateJsonRpcClient", GenerateClientTask::class.java) { | ||
task -> task.dependsOn("compileJava") | ||
private fun capitalize(s: String) = | ||
s[0].toUpperCase() + s.substring(1) | ||
|
||
class VoorheesPlugin @Inject constructor( | ||
private val softwareComponentFactory: SoftwareComponentFactory) : Plugin<ProjectInternal> { | ||
|
||
override fun apply(project: ProjectInternal) { | ||
val extension = project.extensions.create("voorhees", VoorheesExtension::class.java, project) | ||
|
||
val generateTask = project.tasks.register("generate${capitalize(CLIENT_NAME)}", GenerateClientTask::class.java) { task -> | ||
task.dependsOn("classes") | ||
task.group = "jsonrpc" | ||
task.description = "Generate JSON RPC client classes" | ||
} | ||
|
||
val jarTask = project.tasks.register("jar${capitalize(CLIENT_NAME)}", Jar::class.java) { task -> | ||
task.dependsOn(generateTask) | ||
task.group = "jsonrpc" | ||
task.description = "Build a jar out of JSON RPC client classes" | ||
|
||
task.archiveFileName.set("${extension.artifact.get()}-${extension.version.get()}.jar") | ||
task.destinationDirectory.set(File(project.buildDir, "libs")) | ||
|
||
task.from(generateTask.get().genDir) | ||
} | ||
|
||
if (project.pluginManager.hasPlugin("maven-publish")) { | ||
val library = softwareComponentFactory.adhoc("${CLIENT_NAME}Library") | ||
val elements = project.configurations.create("jsonRpcElements") { conf -> | ||
conf.dependencies.add( | ||
DefaultExternalModuleDependency("com.hylamobile", "voorhees-client", "1.0.0")) | ||
conf.outgoing.apply { | ||
artifacts.add(LazyPublishArtifact(jarTask)) | ||
attributes.attribute(ArtifactAttributes.ARTIFACT_FORMAT, ArtifactTypeDefinition.JAR_TYPE) | ||
} | ||
} | ||
|
||
library.addVariantsFromConfiguration( | ||
elements, JavaConfigurationVariantMapping("compile", false)) | ||
|
||
project.components.add(library) | ||
|
||
project.plugins.withId("maven-publish") { | ||
val publishing = project.extensions.getByType(PublishingExtension::class.java) | ||
publishing.publications.create(CLIENT_NAME, MavenPublication::class.java) { publication -> | ||
check(publication is DefaultMavenPublication) | ||
|
||
publication.from(library) | ||
publication.mavenProjectIdentity.apply { | ||
artifactId.set(extension.artifact) | ||
groupId.set(extension.group) | ||
version.set(extension.version) | ||
} | ||
} | ||
|
||
project.tasks.register("publish${capitalize(CLIENT_NAME)}") { task -> | ||
task.dependsOn("publish${capitalize(CLIENT_NAME)}PublicationToMavenRepository") | ||
task.group = "jsonrpc" | ||
task.description = "Publish JSON RPC client to Maven repository" | ||
} | ||
} | ||
} | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
...ees-gradle-plugin/src/main/kotlin/com/hylamobile/voorhees/server/annotation/DontExpose.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.hylamobile.voorhees.server.annotation | ||
|
||
@Target(AnnotationTarget.FUNCTION) | ||
@Retention(AnnotationRetention.RUNTIME) | ||
@MustBeDocumented | ||
annotation class DontExpose |
9 changes: 9 additions & 0 deletions
9
...gradle-plugin/src/main/kotlin/com/hylamobile/voorhees/server/annotation/JsonRpcService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.hylamobile.voorhees.server.annotation | ||
|
||
/** | ||
* Just a fake annotation to not depend on voorhees-server | ||
*/ | ||
@Target(AnnotationTarget.CLASS) | ||
@Retention(AnnotationRetention.RUNTIME) | ||
@MustBeDocumented | ||
annotation class JsonRpcService(val locations: Array<String>) |
31 changes: 31 additions & 0 deletions
31
voorhees-gradle-plugin/src/test/java/com/hylamobile/voorhees/gradle/test/RemoteService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.hylamobile.voorhees.gradle.test; | ||
|
||
import com.hylamobile.voorhees.gradle.test.beans.GenericBean; | ||
import com.hylamobile.voorhees.gradle.test.beans.InputBean; | ||
import com.hylamobile.voorhees.gradle.test.beans.OutputBean; | ||
import com.hylamobile.voorhees.gradle.test.beans.RecursiveBean1; | ||
import com.hylamobile.voorhees.server.annotation.JsonRpcService; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
@JsonRpcService(locations = "/api") | ||
public class RemoteService { | ||
|
||
public OutputBean handle(InputBean bean) { | ||
return null; | ||
} | ||
|
||
public GenericBean<OutputBean> handleGenerics(GenericBean<InputBean> input) { | ||
return null; | ||
} | ||
|
||
public List<OutputBean> handleCollections(Map<String, InputBean> input) { | ||
return null; | ||
} | ||
|
||
public RecursiveBean1 handleRecursive() { | ||
return null; | ||
} | ||
} | ||
|
14 changes: 14 additions & 0 deletions
14
...es-gradle-plugin/src/test/java/com/hylamobile/voorhees/gradle/test/beans/GenericBean.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.hylamobile.voorhees.gradle.test.beans; | ||
|
||
public class GenericBean<T> { | ||
|
||
private T underlying; | ||
|
||
public T getUnderlying() { | ||
return underlying; | ||
} | ||
|
||
public void setUnderlying(T underlying) { | ||
this.underlying = underlying; | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
...hees-gradle-plugin/src/test/java/com/hylamobile/voorhees/gradle/test/beans/InputBean.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package com.hylamobile.voorhees.gradle.test.beans; | ||
|
||
public class InputBean { | ||
|
||
private String stringVar; | ||
private String stringVal; | ||
private int intVar; | ||
private int intVal; | ||
private int[] intArray; | ||
private TestEnum enumVar; | ||
|
||
public String getStringVar() { | ||
return stringVar; | ||
} | ||
|
||
public void setStringVar(String stringVar) { | ||
this.stringVar = stringVar; | ||
} | ||
|
||
public String getStringVal() { | ||
return stringVal; | ||
} | ||
|
||
public int getIntVar() { | ||
return intVar; | ||
} | ||
|
||
public void setIntVar(int intVar) { | ||
this.intVar = intVar; | ||
} | ||
|
||
public int getIntVal() { | ||
return intVal; | ||
} | ||
|
||
public int[] getIntArray() { | ||
return intArray; | ||
} | ||
|
||
public void setIntArray(int[] intArray) { | ||
this.intArray = intArray; | ||
} | ||
|
||
public TestEnum getEnumVar() { | ||
return enumVar; | ||
} | ||
|
||
public void setEnumVar(TestEnum enumVar) { | ||
this.enumVar = enumVar; | ||
} | ||
} |
Oops, something went wrong.