writeProxyApiBaseCodec method

  1. @override
void writeProxyApiBaseCodec(
  1. SwiftOptions 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(
  SwiftOptions generatorOptions,
  Root root,
  Indent indent,
) {
  final Iterable<AstProxyApi> allProxyApis =
      root.apis.whereType<AstProxyApi>();

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

  final String filePrefix =
      generatorOptions.fileSpecificClassNameComponent ?? '';

  final String registrarName = proxyApiRegistrarName(generatorOptions);

  indent.writeScoped(
    'private class ${proxyApiReaderWriterName(generatorOptions)}: FlutterStandardReaderWriter {',
    '}',
    () {
      indent.writeln(
        'unowned let pigeonRegistrar: $registrarName',
      );
      indent.newln();

      indent.writeScoped(
        'private class $filePrefix${classNamePrefix}ProxyApiCodecReader: ${_getCodecName(generatorOptions)}Reader {',
        '}',
        () {
          indent.writeln('unowned let pigeonRegistrar: $registrarName');
          indent.newln();

          indent.writeScoped(
            'init(data: Data, pigeonRegistrar: $registrarName) {',
            '}',
            () {
              indent.writeln('self.pigeonRegistrar = pigeonRegistrar');
              indent.writeln('super.init(data: data)');
            },
          );
          indent.newln();

          indent.writeScoped(
            'override func readValue(ofType type: UInt8) -> Any? {',
            '}',
            () {
              indent.format(
                '''
                switch type {
                case $proxyApiCodecInstanceManagerKey:
                  let identifier = self.readValue()
                  let instance: AnyObject? = pigeonRegistrar.instanceManager.instance(
                    forIdentifier: identifier is Int64 ? identifier as! Int64 : Int64(identifier as! Int32))
                  return instance
                default:
                  return super.readValue(ofType: type)
                }''',
              );
            },
          );
        },
      );
      indent.newln();

      indent.writeScoped(
        'private class $filePrefix${classNamePrefix}ProxyApiCodecWriter: ${_getCodecName(generatorOptions)}Writer {',
        '}',
        () {
          indent.writeln(
            'unowned let pigeonRegistrar: $registrarName',
          );
          indent.newln();

          indent.writeScoped(
            'init(data: NSMutableData, pigeonRegistrar: $registrarName) {',
            '}',
            () {
              indent.writeln('self.pigeonRegistrar = pigeonRegistrar');
              indent.writeln('super.init(data: data)');
            },
          );
          indent.newln();

          indent.writeScoped(
            'override func writeValue(_ value: Any) {',
            '}',
            () {
              final List<String> nonProxyApiTypes = <String>[
                '[Any]',
                'Bool',
                'Data',
                '[AnyHashable: Any]',
                'Double',
                'FlutterStandardTypedData',
                'Int64',
                'String',
                ...root.enums.map((Enum anEnum) => anEnum.name),
              ];
              final String isBuiltinExpression = nonProxyApiTypes
                  .map((String swiftType) => 'value is $swiftType')
                  .join(' || ');
              // Non ProxyApi types are checked first to prevent the scenario
              // where a client wraps the `NSObject` class which all the
              // classes above extend.
              indent.writeScoped('if $isBuiltinExpression {', '}', () {
                indent.writeln('super.writeValue(value)');
                indent.writeln('return');
              });
              indent.newln();

              // 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!,
                    ),
                  ];
                },
              );

              enumerate(
                sortedApis,
                (int index, AstProxyApi api) {
                  final TypeDeclaration apiAsTypeDecl = TypeDeclaration(
                    baseName: api.name,
                    isNullable: false,
                    associatedProxyApi: api,
                  );
                  final String? availability = _tryGetAvailabilityAnnotation(
                    <TypeDeclaration>[apiAsTypeDecl],
                  );
                  final String? unsupportedPlatforms =
                      _tryGetUnsupportedPlatformsCondition(
                    <TypeDeclaration>[apiAsTypeDecl],
                  );
                  final String className = api.swiftOptions?.name ?? api.name;
                  indent.format(
                    '''
                    ${unsupportedPlatforms != null ? '#if $unsupportedPlatforms' : ''}
                    if ${availability != null ? '#$availability, ' : ''}let instance = value as? $className {
                      pigeonRegistrar.apiDelegate.pigeonApi${api.name}(pigeonRegistrar).pigeonNewInstance(
                        pigeonInstance: instance
                      ) { _ in }
                      super.writeByte($proxyApiCodecInstanceManagerKey)
                      super.writeValue(
                        pigeonRegistrar.instanceManager.identifierWithStrongReference(forInstance: instance as AnyObject)!)
                      return
                    }
                    ${unsupportedPlatforms != null ? '#endif' : ''}''',
                  );
                },
              );
              indent.newln();

              indent.format(
                '''
                if let instance = value as AnyObject?, pigeonRegistrar.instanceManager.containsInstance(instance)
                {
                  super.writeByte($proxyApiCodecInstanceManagerKey)
                  super.writeValue(
                    pigeonRegistrar.instanceManager.identifierWithStrongReference(forInstance: instance)!)
                } else {
                  print("Unsupported value: \\(value) of \\(type(of: value))")
                  assert(false, "Unsupported value for $filePrefix${classNamePrefix}ProxyApiCodecWriter")
                }
                ''',
              );
            },
          );
        },
      );
      indent.newln();

      indent.format(
        '''
        init(pigeonRegistrar: $registrarName) {
          self.pigeonRegistrar = pigeonRegistrar
        }''',
      );
      indent.newln();

      indent.format(
        '''
        override func reader(with data: Data) -> FlutterStandardReader {
          return $filePrefix${classNamePrefix}ProxyApiCodecReader(data: data, pigeonRegistrar: pigeonRegistrar)
        }''',
      );
      indent.newln();

      indent.format(
        '''
        override func writer(with data: NSMutableData) -> FlutterStandardWriter {
          return $filePrefix${classNamePrefix}ProxyApiCodecWriter(data: data, pigeonRegistrar: pigeonRegistrar)
        }''',
      );
    },
  );
}