override fun consume()

in integration-test/compiler/src/main/kotlin/com/uber/crumb/integration/compiler/GsonSupport.kt [281:434]


  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, GsonSupportMeta>>()
    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 result = ParameterizedTypeName.get(ClassName.get(TypeAdapter::class.java), t)
    val gson = ParameterSpec.builder(Gson::class.java, "gson").build()

    // A utility createTypeAdapter method for methods to use and not worry about reflection stuff
    val typeAdapterCreator = MethodSpec.methodBuilder("createTypeAdapter")
        .addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java)
            .addMember("value", "\$S", "unchecked")
            .build())
        .addModifiers(PRIVATE, STATIC)
        .addTypeVariable(t)
        .returns(result)
        .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)",
            result,
            Class::class.java)
        .nextControlFlow("else")
        .addStatement("\treturn (\$T) \$T.forName(modelName).getMethod(methodName).invoke(null)",
            result,
            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 typeAdapter " +
                "invocation failed.")
        .endControlFlow()
        .build()

    val nameResolver = MethodSpec.methodBuilder("resolveNameGsonSupport")
        .addModifiers(PRIVATE, 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()

    // Create the main create() method for the TypeAdapterFactory
    val typeParam = ParameterSpec
        .builder(ParameterizedTypeName.get(ClassName.get(TypeToken::class.java), t), "type")
        .build()
    val create = MethodSpec.methodBuilder("create")
        .addModifiers(PUBLIC)
        .addTypeVariable(t)
        .addAnnotation(Nullable::class.java)
        .addAnnotation(Override::class.java)
        .addParameters(ImmutableSet.of(gson, typeParam))
        .returns(result)
        /*
         * 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.
         */
        .addStatement("\$T<\$T> rawType = \$N.getRawType()",
            Class::class.java,
            WildcardTypeName.supertypeOf(t),
            typeParam)
        .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)")
    modelsByPackage.forEach { (packageName, entries) ->
      // Create the package-specific method
      val packageCreatorMethod = MethodSpec.methodBuilder(
          nameAllocator.newName("${packageName}TypeAdapter"))
          .addAnnotation(Nullable::class.java)
          .addModifiers(PRIVATE, STATIC)
          .addTypeVariable(t)
          .returns(result)
          .addParameter(Gson::class.java, "gson")
          .addParameter(String::class.java, "name")
          .addCode(createPackageSwitch(packageName, entries, gson))
          .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, \$N(rawType.getSimpleName()))",
          packageCreatorMethod,
          gson,
          nameResolver)
      methods += packageCreatorMethod
    }

    // Default is always to return null in adapters
    create.addCode("default:\n")
        .addStatement("return null")
        .endControlFlow()

    methods += nameResolver
    methods += typeAdapterCreator
    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(TypeAdapterFactory::class.java))
        .addMethods(methods)
        .addOriginatingElement(type)
        .build()
    JavaFile.builder(packageName, factorySpec).build()
        .writeTo(context.processingEnv.filer)
  }