in integration-test/compiler/src/main/kotlin/com/uber/crumb/integration/compiler/MoshiSupport.kt [320:498]
override fun consume(context: CrumbContext,
type: TypeElement,
annotations: Collection<AnnotationMirror>,
metadata: Set<ConsumerMetadata>) {
// Get a mapping of model names -> GsonSupportMeta
val metaMaps = metadata
.filter { it.contains(EXTRAS_KEY) }
.map { metaMapAdapter.fromJson(it[EXTRAS_KEY]!!)!! }
.flatMap { it.entries }
.associateBy({ it.key }, { it.value })
// Organize them by package, so packageName -> Map<ModelName, GsonSupportMeta>
val modelsByPackage = mutableMapOf<String, MutableMap<String, MoshiSupportMeta>>()
metaMaps.entries
.forEach {
val (packageName, name) = it.key.asPackageAndName()
modelsByPackage.getOrPut(packageName, { mutableMapOf() })[name] = it.value
}
val methods = mutableSetOf<MethodSpec>()
// NameAllocator to create valid method names
val nameAllocator = NameAllocator()
// Some other boilerplate we'll need
val t = TypeVariableName.get("T")
val returnType = ParameterizedTypeName.get(ClassName.get(JsonAdapter::class.java), t)
// A utility createTypeAdapter method for methods to use and not worry about reflection stuff
val jsonAdapterCreator = MethodSpec.methodBuilder("createJsonAdapter")
.addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java)
.addMember("value", "\$S", "unchecked")
.build())
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
.addTypeVariable(t)
.returns(returnType)
.addParameter(String::class.java, "modelName")
.addParameter(String::class.java, "methodName")
.addParameter(ArrayTypeName.of(Object::class.java), "args")
.varargs()
.beginControlFlow("try")
// If we have args, we need to create a Class[] to give the getMethod() call to properly resolve
.beginControlFlow("if (args != null && args.length > 0)")
.addStatement("\$1T[] params = new \$1T[args.length]", Class::class.java)
.beginControlFlow("for (int i = 0; i < args.length; i++)")
.addStatement("params[i] = args[i].getClass()")
.endControlFlow()
.addStatement("\treturn (\$T) \$T.forName(modelName)" +
".getMethod(methodName, params).invoke(null, args)",
returnType,
Class::class.java)
.nextControlFlow("else")
.addStatement("\treturn (\$T) \$T.forName(modelName).getMethod(methodName).invoke(null)",
returnType,
Class::class.java)
.endControlFlow()
.nextControlFlow("catch (\$T e)",
Exception::class.java) // Can't use ReflectiveOperationException till API 19
.addStatement("throw new \$T(\$S, e)", RuntimeException::class.java,
"Cortex reflective jsonAdapter " +
"invocation failed.")
.endControlFlow()
.build()
val factoryCreator = MethodSpec.methodBuilder("getJsonAdapterFactory")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
.returns(JsonAdapter.Factory::class.java)
.addParameter(String::class.java, "modelName")
.addParameter(String::class.java, "methodName")
.beginControlFlow("try")
.addStatement("\treturn (\$T) \$T.forName(modelName).getMethod(methodName).invoke(null)",
JsonAdapter.Factory::class.java,
Class::class.java)
.nextControlFlow("catch (\$T e)",
Exception::class.java) // Can't use ReflectiveOperationException till API 19
.addStatement("throw new \$T(\$S, e)", RuntimeException::class.java, "Cortex reflective " +
"jsonAdapterFactory invocation failed.")
.endControlFlow()
.build()
val nameResolver = MethodSpec.methodBuilder("resolveNameMoshiSupport")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
.returns(String::class.java)
.addParameter(String::class.java, "simpleName")
.beginControlFlow("if (simpleName.startsWith(\$S))",
AV_PREFIX)
.addStatement("return simpleName.substring(\$S.length())",
AV_PREFIX)
.nextControlFlow("else")
.addStatement("return simpleName")
.endControlFlow()
.build()
val create = MethodSpec.methodBuilder("create")
.addModifiers(PUBLIC)
.addAnnotation(Nullable::class.java)
.addAnnotation(Override::class.java)
.addParameters(ImmutableSet.of(
TYPE_SPEC,
ANNOTATIONS_SPEC,
MOSHI_SPEC))
.returns(
FACTORY_RETURN_TYPE_NAME)
/*
* First we want to pull out the package and simple names
* The idea here is that we'll split on package names initially and then split on simple names in each
* package-specific method's switch statements.
*
* This only covers the model name and autovalue name, but maybe some day we can make this more flexible
* by going up and finding the first neuron-annotated class as the root if we need to.
*
* Note that we only get the package name first. If we get a match, then we snag the simple name and
* possibly strip the AutoValue_ prefix if necessary.
*/
.addComment("Avoid providing an adapter for an annotated type.")
.addStatement("if (!\$N.isEmpty()) return null",
ANNOTATIONS_SPEC)
.addStatement("\$T<?> rawType = \$T.getRawType(\$N)",
Class::class.java,
TypeName.get(MoshiTypes::class.java),
TYPE_SPEC)
.addStatement("if (rawType.isPrimitive()) return null")
.addStatement("if (!rawType.isAnnotationPresent(\$T.class)) return null",
CrumbConsumable::class.java)
.addStatement("String packageName = rawType.getPackage().getName()")
// Begin the switch
create.beginControlFlow("switch (packageName)",
TYPE_SPEC)
modelsByPackage.forEach { (packageName, entries) ->
// Create the package-specific method
val packageCreatorMethod = MethodSpec.methodBuilder(
nameAllocator.newName("${packageName}JsonAdapter"))
.addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java)
.addMember("value", "\$S", "unchecked")
.build())
.addAnnotation(Nullable::class.java)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
.addTypeVariable(t)
.returns(returnType)
.addParameter(String::class.java, "name")
.addParameters(ImmutableSet.of(
TYPE_SPEC,
ANNOTATIONS_SPEC,
MOSHI_SPEC))
.addCode(createPackageSwitch(packageName, entries, returnType))
.build()
// Switch on the package name and return the result from the corresponding method
create.addCode("case \$S:\n", packageName)
create.addStatement("\treturn \$N(\$N(rawType.getSimpleName()), \$N, \$N, \$N)",
packageCreatorMethod,
nameResolver,
TYPE_SPEC,
ANNOTATIONS_SPEC,
MOSHI_SPEC)
methods += packageCreatorMethod
}
// Default is always to return null in adapters
create.addCode("default:\n")
.addStatement("return null")
.endControlFlow()
methods += nameResolver
methods += factoryCreator
methods += jsonAdapterCreator
methods += create.build()
val adapterName = type.classNameOf()
val packageName = type.packageName()
val factorySpec = TypeSpec.classBuilder(
ClassName.get(packageName, CONSUMER_PREFIX + adapterName))
.addModifiers(FINAL)
.addSuperinterface(TypeName.get(JsonAdapter.Factory::class.java))
.addMethods(methods)
.build()
JavaFile.builder(packageName, factorySpec).build()
.writeTo(context.processingEnv.filer)
}