From 301e149f97abf902b03c8a3231a057653e9abfb3 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Tue, 25 Aug 2020 10:34:41 -0700 Subject: [PATCH] Codegen: JavaGenerator [4] - collect all parsed types in a flat list of "TypeData" Summary: Instead of recreating the schema structure, the generator only needs to collect the list of types that it needs to generate for. So instead, let's just add each parsed type into a map using TypeId as the key. This means every inner types in the schema needs its own unique TypeId. This was a change from the previous commit where we didn't assign unique names to the types. Here's the reasoning: * In Java, any generated class needs to be in its own file. * If a NativeModule spec defines a few aliases, and or inner types (function args, return type shape, etc) that needs representation with a dedicated class, we need to track them as well for code generation. * This means, the schema format is no longer relevant for the code generation step, so let's produce a structure that's more efficient for code generation Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D23287818 fbshipit-source-id: 7caf4e95aeafe5c8ba336af290179b85bf87ad6d --- .../codegen/generator/JavaGenerator.java | 6 +- .../codegen/generator/SchemaJsonParser.java | 167 +++++++++--------- .../codegen/generator/model/RawSchema.java | 26 --- .../codegen/generator/model/TypeData.java | 43 +++++ .../react/codegen/generator/model/TypeId.java | 4 + 5 files changed, 137 insertions(+), 109 deletions(-) delete mode 100644 packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/model/RawSchema.java create mode 100644 packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/model/TypeData.java diff --git a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/JavaGenerator.java b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/JavaGenerator.java index 2ad0aa51cd..2f15f43ced 100644 --- a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/JavaGenerator.java +++ b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/JavaGenerator.java @@ -7,7 +7,7 @@ package com.facebook.react.codegen.generator; -import com.facebook.react.codegen.generator.model.RawSchema; +import com.facebook.react.codegen.generator.model.TypeData; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; @@ -29,9 +29,7 @@ public final class JavaGenerator { } public void build() throws FileNotFoundException, IOException { - final RawSchema rawSchema = SchemaJsonParser.parse(mSchemaFile); - // TODO (T71663018): remove - this is for debugging - System.out.println(rawSchema); + TypeData typeData = SchemaJsonParser.parse(mSchemaFile); final MethodSpec main = MethodSpec.methodBuilder("main") diff --git a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/SchemaJsonParser.java b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/SchemaJsonParser.java index fa9c67352c..b8deb6ed48 100644 --- a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/SchemaJsonParser.java +++ b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/SchemaJsonParser.java @@ -21,12 +21,13 @@ import com.facebook.react.codegen.generator.model.NullableType; import com.facebook.react.codegen.generator.model.NumberType; import com.facebook.react.codegen.generator.model.ObjectType; import com.facebook.react.codegen.generator.model.PromiseType; -import com.facebook.react.codegen.generator.model.RawSchema; import com.facebook.react.codegen.generator.model.ReservedFunctionValueType; import com.facebook.react.codegen.generator.model.StringType; import com.facebook.react.codegen.generator.model.Type; +import com.facebook.react.codegen.generator.model.TypeData; import com.facebook.react.codegen.generator.model.TypeId; import com.facebook.react.codegen.generator.model.VoidType; +import com.google.common.base.CaseFormat; import com.google.common.collect.ImmutableList; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -41,8 +42,16 @@ import java.util.Map; import java.util.stream.Collectors; public final class SchemaJsonParser { + private final TypeData mTypeData = new TypeData(); - public static RawSchema parse(final File schemaFile) throws FileNotFoundException, IOException { + public static TypeData parse(final File schemaFile) throws FileNotFoundException, IOException { + final SchemaJsonParser parser = new SchemaJsonParser(); + parser.buildTypeData(schemaFile); + System.out.println(parser.mTypeData); + return parser.mTypeData; + } + + private void buildTypeData(final File schemaFile) throws FileNotFoundException, IOException { final JsonParser parser = new JsonParser(); final JsonElement rootElement = parser.parse(new FileReader(schemaFile)); @@ -63,62 +72,25 @@ public final class SchemaJsonParser { return; } - collection.put( - jsModuleName, - nativeModules.entrySet().stream() - .collect( - Collectors.toMap( - Map.Entry::getKey, - e -> { - return parseNativeModule( - // TODO (T71955395): NativeModule spec type name does not - // exist in the schema. For now assume it's "Spec". - TypeId.of(jsModuleName, "Spec"), - e.getValue().getAsJsonObject()); - }))); + nativeModules + .entrySet() + .forEach( + e -> { + final Type parsedType = + parseNativeModule( + // TODO (T71955395): NativeModule spec type name does not + // exist in the schema. For now assume it's "Spec". + // The final type name will be the output class name. + TypeId.of(jsModuleName, "Native" + e.getKey() + "Spec"), + e.getValue().getAsJsonObject()); + mTypeData.addType(parsedType); + }); }); } - - return new RawSchema(collection); - } - - private static NativeModuleType parseNativeModule(final TypeId typeId, final JsonObject json) { - final JsonObject aliases = json.getAsJsonObject("aliases"); - final JsonArray properties = json.getAsJsonArray("properties"); - - final ImmutableList collectedAliases = - ImmutableList.copyOf( - aliases.entrySet().stream() - .map( - entry -> { - final String typeName = entry.getKey(); - final JsonObject typeAnnotation = entry.getValue().getAsJsonObject(); - // The alias name is the type name that other types can refer to. - return parseTypeAnnotation( - TypeId.of(typeId.moduleName, typeName), typeAnnotation); - }) - .collect(Collectors.toList())); - - ImmutableList.Builder collectedPropertiesBuilder = - new ImmutableList.Builder<>(); - properties.forEach( - p -> { - final JsonObject node = p.getAsJsonObject(); - final String name = node.has("name") ? node.get("name").getAsString() : null; - final JsonObject typeAnnotation = node.getAsJsonObject("typeAnnotation"); - // TODO (T71845349): "optional" field shouldn't be part of the Function's typeAnnotation. - final boolean optional = typeAnnotation.get("optional").getAsBoolean(); - final TypeId propertyTypeId = TypeId.of(typeId.moduleName); - collectedPropertiesBuilder.add( - new NativeModuleType.Property( - name, parseTypeAnnotation(propertyTypeId, typeAnnotation), optional)); - }); - - return new NativeModuleType(typeId, collectedAliases, collectedPropertiesBuilder.build()); } // Parse type information from a JSON "typeAnnotation" node. - private static Type parseTypeAnnotation(final TypeId typeId, final JsonObject typeAnnotation) + private Type parseTypeAnnotation(final TypeId typeId, final JsonObject typeAnnotation) throws IllegalStateException { final String type = typeAnnotation.get("type").getAsString(); // TODO (T71824250): Support NullableTypeAnnotation in the schema instead of a field here. @@ -176,60 +148,96 @@ public final class SchemaJsonParser { throw new IllegalStateException("Found invalid type annotation: " + type); } - return maybeCreateNullableType(nullable, parsedType); + final Type finalType = maybeCreateNullableType(nullable, parsedType); + mTypeData.addType(finalType); + return finalType; } - private static Type parseAliasTypeAnnotation( - final TypeId typeId, final JsonObject typeAnnotation) { + private NativeModuleType parseNativeModule(final TypeId typeId, final JsonObject json) { + final JsonObject aliases = json.getAsJsonObject("aliases"); + final JsonArray properties = json.getAsJsonArray("properties"); + + final ImmutableList collectedAliases = + ImmutableList.copyOf( + aliases.entrySet().stream() + .map( + entry -> { + final String typeName = entry.getKey(); + final JsonObject typeAnnotation = entry.getValue().getAsJsonObject(); + // The alias name is the type name that other types can refer to. + return parseTypeAnnotation( + TypeId.of(typeId.moduleName, typeName), typeAnnotation); + }) + .collect(Collectors.toList())); + + ImmutableList.Builder collectedPropertiesBuilder = + new ImmutableList.Builder<>(); + properties.forEach( + p -> { + final JsonObject node = p.getAsJsonObject(); + final String name = node.has("name") ? node.get("name").getAsString() : null; + final JsonObject typeAnnotation = node.getAsJsonObject("typeAnnotation"); + // TODO (T71845349): "optional" field shouldn't be part of the Function's typeAnnotation. + final boolean optional = typeAnnotation.get("optional").getAsBoolean(); + final TypeId propertyTypeId = + TypeId.expandOf(typeId, CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, name)); + collectedPropertiesBuilder.add( + new NativeModuleType.Property( + name, parseTypeAnnotation(propertyTypeId, typeAnnotation), optional)); + }); + + return new NativeModuleType(typeId, collectedAliases, collectedPropertiesBuilder.build()); + } + + private Type parseAliasTypeAnnotation(final TypeId typeId, final JsonObject typeAnnotation) { // For now, assume the alias lives inside the same file. return new AliasType( typeId, TypeId.of(typeId.moduleName, typeAnnotation.get("name").getAsString())); } - private static Type parseArrayTypeAnnotation( - final TypeId typeId, final JsonObject typeAnnotation) { + private Type parseArrayTypeAnnotation(final TypeId typeId, final JsonObject typeAnnotation) { final JsonObject elementTypeAnnotation = typeAnnotation.getAsJsonObject("elementType"); + final TypeId elementTypeId = TypeId.expandOf(typeId, "ElementType"); // TODO (T71847026): Some array types are missing elementType annotation. final Type elementType = elementTypeAnnotation != null - ? parseTypeAnnotation(TypeId.of(typeId.moduleName), elementTypeAnnotation) - : new AnyType(TypeId.of(typeId.moduleName)); - + ? parseTypeAnnotation(elementTypeId, elementTypeAnnotation) + : new AnyType(elementTypeId); return new ArrayType(typeId, elementType); } - private static Type parseFunctionTypeAnnotation( - final TypeId typeId, final JsonObject typeAnnotation) { + private Type parseFunctionTypeAnnotation(final TypeId typeId, final JsonObject typeAnnotation) { final JsonArray params = typeAnnotation.getAsJsonArray("params"); ImmutableList.Builder paramsList = new ImmutableList.Builder<>(); // TODO (T71846321): Some functions are missing params specification. if (params != null) { - params.forEach( - p -> { - final JsonObject node = p.getAsJsonObject(); - final String name = node.has("name") ? node.get("name").getAsString() : null; - paramsList.add( - FunctionType.createArgument( - name, - parseTypeAnnotation( - TypeId.of(typeId.moduleName), node.getAsJsonObject("typeAnnotation")))); - }); + for (int i = 0; i < params.size(); i++) { + final JsonElement p = params.get(i); + final JsonObject node = p.getAsJsonObject(); + final String name = node.has("name") ? node.get("name").getAsString() : ("Arg" + i); + paramsList.add( + FunctionType.createArgument( + name, + parseTypeAnnotation( + TypeId.expandOf( + typeId, CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, name)), + node.getAsJsonObject("typeAnnotation")))); + } } // TODO (T71846321): Some functions are missing a return type. final JsonObject returnTypeAnnotation = typeAnnotation.getAsJsonObject("returnTypeAnnotation"); final Type returnType = returnTypeAnnotation != null - ? parseTypeAnnotation(TypeId.of(typeId.moduleName), returnTypeAnnotation) + ? parseTypeAnnotation(TypeId.expandOf(typeId, "ReturnType"), returnTypeAnnotation) : VoidType.VOID; return new FunctionType(typeId, paramsList.build(), returnType); } - private static Type parseObjectTypeAnnotation( - final TypeId typeId, final JsonObject typeAnnotation) { + private Type parseObjectTypeAnnotation(final TypeId typeId, final JsonObject typeAnnotation) { final JsonArray properties = typeAnnotation.getAsJsonArray("properties"); ImmutableList.Builder propertiesList = new ImmutableList.Builder<>(); @@ -239,7 +247,8 @@ public final class SchemaJsonParser { final String name = node.has("name") ? node.get("name").getAsString() : null; final boolean optional = node.get("optional").getAsBoolean(); final JsonObject propertyTypeAnnotation = node.getAsJsonObject("typeAnnotation"); - final TypeId propertyTypeId = TypeId.of(typeId.moduleName); + final TypeId propertyTypeId = + TypeId.expandOf(typeId, CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, name)); // TODO (T67898313): Some object properties are missing typeAnnotation. final Type propertyType = @@ -253,14 +262,14 @@ public final class SchemaJsonParser { return new ObjectType(typeId, propertiesList.build()); } - private static Type parseReservedFunctionValueTypeAnnotation( + private Type parseReservedFunctionValueTypeAnnotation( final TypeId typeId, final JsonObject typeAnnotation) { return new ReservedFunctionValueType( typeId, ReservedFunctionValueType.ReservedName.valueOf(typeAnnotation.get("name").getAsString())); } - private static Type maybeCreateNullableType(final boolean nullable, final Type original) { + private Type maybeCreateNullableType(final boolean nullable, final Type original) { if (!nullable || original instanceof VoidType) { return original; } diff --git a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/model/RawSchema.java b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/model/RawSchema.java deleted file mode 100644 index 969164e4a5..0000000000 --- a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/model/RawSchema.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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. - */ - -package com.facebook.react.codegen.generator.model; - -import java.util.Collections; -import java.util.Map; - -/** Represents the parsed JSON schema without any type resolution. */ -public final class RawSchema { - - public final Map> modules; - - public RawSchema(final Map> modules) { - this.modules = Collections.unmodifiableMap(modules); - } - - @Override - public String toString() { - return modules.toString(); - } -} diff --git a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/model/TypeData.java b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/model/TypeData.java new file mode 100644 index 0000000000..1b19bfee0a --- /dev/null +++ b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/model/TypeData.java @@ -0,0 +1,43 @@ +/* + * 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. + */ + +package com.facebook.react.codegen.generator.model; + +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; + +/** A collection of all types information based on the parsed schema. */ +public final class TypeData { + private final Map mTypes = new HashMap<>(); + + public void addType(final TypeId typeId, final Type type) throws IllegalStateException { + if (getType(typeId) != null) { + throw new IllegalStateException("Found duplicated TypeId: " + typeId + " for: " + type); + } + mTypes.put(typeId, type); + } + + public void addType(final Type type) { + addType(type.getTypeId(), type); + } + + public @Nullable Type getType(final TypeId typeId) { + return mTypes.get(typeId); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + mTypes.forEach( + (k, v) -> { + builder.append(v.toString()); + builder.append("\n"); + }); + return builder.toString(); + } +} diff --git a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/model/TypeId.java b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/model/TypeId.java index a8c5208c02..ef9831c525 100644 --- a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/model/TypeId.java +++ b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/model/TypeId.java @@ -42,6 +42,10 @@ public final class TypeId { return of(typeId.moduleName, typeId.typeName); } + public static TypeId expandOf(final TypeId typeId, String suffix) { + return of(typeId.moduleName, typeId.typeName + suffix); + } + @Override public String toString() { return String.format(