Files
react-native/packages/react-native-codegen/src/generators/components/GeneratePropsJavaDelegate.js
T
Oleksandr Melnykov d38afb4a9d Use interface instead of abstract class to reference view manager in generated delegate
Summary:
Our JS codegen assumes that all Android view managers extend `BaseViewManager` which allows generated delegates to set base view props using `BaseViewManagerDelegate`, see and example of the generated delegate for `FbReactTTRCStepRenderFlagManager`:
```
public class TTRCStepRenderFlagManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & TTRCStepRenderFlagManagerInterface<T>>{
  public TTRCStepRenderFlagManagerDelegate(U viewManager) {
    super(viewManager);
  }
  Override
  public void setProperty(T view, String propName, Nullable Object value) {
    switch (propName) {
      case "traceId":
        mViewManager.setTraceId(view, value == null ? null : (String) value);
        break;
      case "stepName":
        mViewManager.setStepName(view, value == null ? null : (String) value);
        break;
      default:
        super.setProperty(view, propName, value);
    }
  }
}
```
The problem is that `FbReactTTRCStepRenderFlagManager` doesn't extend `BaseViewManager`, but `ViewManager`, which means that we cannot use it with the generated delegate. We cannot use  `ViewManager` instead of `BaseViewManager` in our JS codegen either, otherwise we will not be able to set base view props.

This diff makes it possible for delegates generated by JS to be used by Android view managers that do not extend `BaseViewManager`. By having a `BaseViewManagerInterface` we will be able to introduce a no-op base view manager implementation and wrap our original view manager in it so that we can pass as a constructor parameter to `BaseViewManagerDelegate`.

See an example of this approach for `FbReactTTRCStepRenderFlagManager`:

```
public class FbReactTTRCStepRenderFlagManager extends ViewManager<FbReactTTRCStepRenderFlag, ReactShadowNodeImpl> implements TTRCStepRenderFlagManagerInterface<FbReactTTRCStepRenderFlag> {

    private final ViewManagerDelegate<FbReactTTRCStepRenderFlag> mDelegate;

    public FbReactTTRCStepRenderFlagManager() {
        mDelegate = new TTRCStepRenderFlagManagerDelegate<>(new ViewManagerWrapper(this));
    }
    ...

    private static class ViewManagerWrapper extends BaseViewManagerAdapter<FbReactTTRCStepRenderFlag> implements TTRCStepRenderFlagManagerInterface<FbReactTTRCStepRenderFlag> {

        private final TTRCStepRenderFlagManagerInterface<FbReactTTRCStepRenderFlag> mViewManager;

        private FbReactTTRCStepRenderFlagManagerAdapter(TTRCStepRenderFlagManagerInterface<FbReactTTRCStepRenderFlag> viewManager) {
          mViewManager = viewManager;
        }

        Override
        public void setTraceId(FbReactTTRCStepRenderFlag view, Nullable String traceId) {
          mViewManager.setTraceId(view, traceId);
        }

        Override
        public void setStepName(FbReactTTRCStepRenderFlag view, Nullable String stepName) {
          mViewManager.setStepName(view, stepName);
        }
    }
}
```

Reviewed By: mdvacca

Differential Revision: D16984121

fbshipit-source-id: ea2761dda68a96ff3ba6ac64153bc4e56e396774
2019-09-04 07:04:08 -07:00

297 lines
8.3 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
CommandTypeShape,
ComponentShape,
PropTypeShape,
SchemaType,
} from '../../CodegenSchema';
const {
getImports,
toSafeJavaString,
getInterfaceJavaClassName,
getDelegateJavaClassName,
} = require('./JavaHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const template = `/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* ${'@'}generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
::_IMPORTS_::
public class ::_CLASSNAME_::<T extends ::_EXTEND_CLASSES_::, U extends BaseViewManagerInterface<T> & ::_INTERFACE_CLASSNAME_::<T>> extends BaseViewManagerDelegate<T, U> {
public ::_CLASSNAME_::(U viewManager) {
super(viewManager);
}
::_METHODS_::
}
`;
const propSetterTemplate = `
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
::_PROP_CASES_::
}
`;
const commandsTemplate = `
public void receiveCommand(::_INTERFACE_CLASSNAME_::<T> viewManager, T view, String commandName, ReadableArray args) {
switch (commandName) {
::_COMMAND_CASES_::
}
}
`;
function getJavaValueForProp(
prop: PropTypeShape,
componentName: string,
): string {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
return `value == null ? ${typeAnnotation.default.toString()} : (boolean) value`;
case 'StringTypeAnnotation':
const defaultValueString =
typeAnnotation.default === null
? 'null'
: `"${typeAnnotation.default}"`;
return `value == null ? ${defaultValueString} : (String) value`;
case 'Int32TypeAnnotation':
return `value == null ? ${
typeAnnotation.default
} : ((Double) value).intValue()`;
case 'DoubleTypeAnnotation':
if (prop.optional) {
return `value == null ? ${
typeAnnotation.default
}f : ((Double) value).doubleValue()`;
} else {
return 'value == null ? Double.NaN : ((Double) value).doubleValue()';
}
case 'FloatTypeAnnotation':
if (prop.optional) {
return `value == null ? ${
typeAnnotation.default
}f : ((Double) value).floatValue()`;
} else {
return 'value == null ? Float.NaN : ((Double) value).floatValue()';
}
case 'NativePrimitiveTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return 'value == null ? null : ((Double) value).intValue()';
case 'ImageSourcePrimitive':
return '(ReadableMap) value';
case 'PointPrimitive':
return '(ReadableMap) value';
default:
(typeAnnotation.name: empty);
throw new Error('Received unknown NativePrimitiveTypeAnnotation');
}
case 'ArrayTypeAnnotation': {
return '(ReadableArray) value';
}
case 'ObjectTypeAnnotation': {
return '(ReadableMap) value';
}
case 'StringEnumTypeAnnotation':
return '(String) value';
default:
(typeAnnotation: empty);
throw new Error('Received invalid typeAnnotation');
}
}
function generatePropCasesString(
component: ComponentShape,
componentName: string,
) {
if (component.props.length === 0) {
return 'super.setProperty(view, propName, value);';
}
const cases = component.props
.map(prop => {
return `case "${prop.name}":
mViewManager.set${toSafeJavaString(
prop.name,
)}(view, ${getJavaValueForProp(prop, componentName)});
break;`;
})
.join('\n' + ' ');
return `switch (propName) {
${cases}
default:
super.setProperty(view, propName, value);
}`;
}
function getCommandArgJavaType(param, index) {
switch (param.typeAnnotation.type) {
case 'BooleanTypeAnnotation':
return `args.getBoolean(${index})`;
case 'DoubleTypeAnnotation':
return `args.getDouble(${index})`;
case 'FloatTypeAnnotation':
return `(float) args.getDouble(${index})`;
case 'Int32TypeAnnotation':
return `args.getInt(${index})`;
case 'StringTypeAnnotation':
return `args.getString(${index})`;
default:
(param.typeAnnotation.type: empty);
throw new Error('Receieved invalid typeAnnotation');
}
}
function getCommandArguments(command: CommandTypeShape): string {
return [
'view',
...command.typeAnnotation.params.map(getCommandArgJavaType),
].join(', ');
}
function generateCommandCasesString(
component: ComponentShape,
componentName: string,
) {
if (component.commands.length === 0) {
return null;
}
const commandMethods = component.commands
.map(command => {
return `case "${command.name}":
viewManager.${toSafeJavaString(
command.name,
false,
)}(${getCommandArguments(command)});
break;`;
})
.join('\n' + ' ');
return commandMethods;
}
function getClassExtendString(component): string {
const extendString = component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'View';
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
})
.join('');
return extendString;
}
function getDelegateImports(component) {
const imports = getImports(component);
// The delegate needs ReadableArray for commands always.
// The interface doesn't always need it
if (component.commands.length > 0) {
imports.add('import com.facebook.react.bridge.ReadableArray;');
}
imports.add('import androidx.annotation.Nullable;');
imports.add('import com.facebook.react.uimanager.BaseViewManagerDelegate;');
imports.add('import com.facebook.react.uimanager.BaseViewManagerInterface;');
imports.add('import com.facebook.react.uimanager.LayoutShadowNode;');
return imports;
}
function generateMethods(propsString, commandsString): string {
return [
propSetterTemplate.trim().replace('::_PROP_CASES_::', propsString),
commandsString != null
? commandsTemplate.trim().replace('::_COMMAND_CASES_::', commandsString)
: '',
]
.join('\n\n ')
.trimRight();
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
moduleSpecName: string,
): FilesOutput {
const files = new Map();
Object.keys(schema.modules).forEach(moduleName => {
const components = schema.modules[moduleName].components;
// No components in this module
if (components == null) {
return;
}
return Object.keys(components).forEach(componentName => {
const component = components[componentName];
const className = getDelegateJavaClassName(componentName);
const interfaceClassName = getInterfaceJavaClassName(componentName);
const fileName = `${className}.java`;
const imports = getDelegateImports(component);
const propsString = generatePropCasesString(component, componentName);
const commandsString = generateCommandCasesString(
component,
componentName,
);
const extendString = getClassExtendString(component);
const replacedTemplate = template
.replace(
/::_IMPORTS_::/g,
Array.from(imports)
.sort()
.join('\n'),
)
.replace(/::_CLASSNAME_::/g, className)
.replace('::_EXTEND_CLASSES_::', extendString)
.replace('::_PROP_CASES_::', propsString)
.replace(
'::_METHODS_::',
generateMethods(propsString, commandsString),
)
.replace(/::_INTERFACE_CLASSNAME_::/g, interfaceClassName);
files.set(fileName, replacedTemplate);
});
});
return files;
},
};