writeProxyApiBaseCodec method

  1. @override
void writeProxyApiBaseCodec(
  1. KotlinOptions generatorOptions,
  2. Root root,
  3. Indent indent
)
override

Writes the base codec to be used by all ProxyApis.

This codec should use 128 as the identifier for objects that exist in an InstanceManager. The write implementation should convert an instance to an identifier. The read implementation should covert the identifier to an instance.

This will serve as the default codec for all ProxyApis. If a ProxyApi needs to create its own codec (it has methods/fields/constructor that use a data class) it should extend this codec and not StandardMessageCodec.

Implementation

@override
void writeProxyApiBaseCodec(
  KotlinOptions generatorOptions,
  Root root,
  Indent indent,
) {
  final Iterable<AstProxyApi> allProxyApis =
      root.apis.whereType<AstProxyApi>();

  _writeProxyApiRegistrar(
    indent,
    generatorOptions: generatorOptions,
    allProxyApis: allProxyApis,
  );

  // Sort APIs where edges are an API's super class and interfaces.
  //
  // This sorts the APIs to have child classes be listed before their parent
  // classes. This prevents the scenario where a method might return the super
  // class of the actual class, so the incorrect Dart class gets created
  // because the 'value is <SuperClass>' was checked first in the codec. For
  // example:
  //
  // class Shape {}
  // class Circle extends Shape {}
  //
  // class SomeClass {
  //   Shape giveMeAShape() => Circle();
  // }
  final List<AstProxyApi> sortedApis = topologicalSort(
    allProxyApis,
    (AstProxyApi api) {
      return <AstProxyApi>[
        if (api.superClass?.associatedProxyApi != null)
          api.superClass!.associatedProxyApi!,
        ...api.interfaces.map(
          (TypeDeclaration interface) => interface.associatedProxyApi!,
        ),
      ];
    },
  );

  indent.writeScoped(
    'private class ${proxyApiCodecName(generatorOptions)}(val registrar: ${proxyApiRegistrarName(generatorOptions)}) : '
        '${generatorOptions.fileSpecificClassNameComponent}$_codecName() {',
    '}',
    () {
      indent.format(
        '''
        override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
          return when (type) {
            $proxyApiCodecInstanceManagerKey.toByte() -> {
              return registrar.instanceManager.getInstance(readValue(buffer) as Long)
            }
            else -> super.readValueOfType(type, buffer)
          }
        }''',
      );
      indent.newln();

      indent.writeScoped(
        'override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {',
        '}',
        () {
          final List<String> nonProxyApiTypes = <String>[
            'Boolean',
            'ByteArray',
            'Double',
            'DoubleArray',
            'FloatArray',
            'Int',
            'IntArray',
            'List<*>',
            'Long',
            'LongArray',
            'Map<*, *>',
            'String',
            ...root.enums.map((Enum anEnum) => anEnum.name),
          ];
          final String isSupportedExpression = nonProxyApiTypes
              .map((String kotlinType) => 'value is $kotlinType')
              .followedBy(<String>['value == null']).join(' || ');
          // Non ProxyApi types are checked first to handle the scenario
          // where a client wraps the `Object` class which all the
          // classes above extend.
          indent.writeScoped('if ($isSupportedExpression) {', '}', () {
            indent.writeln('super.writeValue(stream, value)');
            indent.writeln('return');
          });
          indent.newln();

          enumerate(
            sortedApis,
            (int index, AstProxyApi api) {
              final String className =
                  api.kotlinOptions?.fullClassName ?? api.name;

              final int? minApi = api.kotlinOptions?.minAndroidApi;
              final String versionCheck = minApi != null
                  ? 'android.os.Build.VERSION.SDK_INT >= $minApi && '
                  : '';

              indent.format(
                '''
                ${index > 0 ? ' else ' : ''}if (${versionCheck}value is $className) {
                  registrar.get$hostProxyApiPrefix${api.name}().${classMemberNamePrefix}newInstance(value) { }
                }''',
              );
            },
          );
          indent.newln();

          indent.format(
            '''
            when {
              registrar.instanceManager.containsInstance(value) -> {
                stream.write($proxyApiCodecInstanceManagerKey)
                writeValue(stream, registrar.instanceManager.getIdentifierForStrongReference(value))
              }
              else -> throw IllegalArgumentException("Unsupported value: '\$value' of type '\${value.javaClass.name}'")
            }''',
          );
        },
      );
    },
  );
}