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
This commit is contained in:
Kevin Gozali 2020-08-25 10:34:41 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 4aa69e634d
Коммит 301e149f97
5 изменённых файлов: 137 добавлений и 109 удалений

Просмотреть файл

@ -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")

Просмотреть файл

@ -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<Type> 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<NativeModuleType.Property> 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<Type> 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<NativeModuleType.Property> 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<FunctionType.ArgumentType> 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<ObjectType.Property> 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;
}

Просмотреть файл

@ -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<String, Map<String, NativeModuleType>> modules;
public RawSchema(final Map<String, Map<String, NativeModuleType>> modules) {
this.modules = Collections.unmodifiableMap(modules);
}
@Override
public String toString() {
return modules.toString();
}
}

Просмотреть файл

@ -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<TypeId, Type> 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();
}
}

Просмотреть файл

@ -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(