io.streamnative.pulsar.handlers.kop.schemaregistry.providers.protobuf.ProtobufSchema Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pulsar-kafka-schema-registry Show documentation
Show all versions of pulsar-kafka-schema-registry Show documentation
Kafka Compatible Schema Registry
The newest version!
/**
* Copyright (c) 2019 - 2024 StreamNative, Inc.. All Rights Reserved.
*/
/**
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.streamnative.pulsar.handlers.kop.schemaregistry.providers.protobuf;
import static com.google.common.base.CaseFormat.LOWER_UNDERSCORE;
import static com.google.common.base.CaseFormat.UPPER_CAMEL;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.EnumHashBiMap;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.AnyProto;
import com.google.protobuf.ApiProto;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.DescriptorProto.ExtensionRange;
import com.google.protobuf.DescriptorProtos.DescriptorProto.ReservedRange;
import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
import com.google.protobuf.DescriptorProtos.EnumDescriptorProto.EnumReservedRange;
import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldOptions.CType;
import com.google.protobuf.DescriptorProtos.FieldOptions.JSType;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileOptions.OptimizeMode;
import com.google.protobuf.DescriptorProtos.MethodDescriptorProto;
import com.google.protobuf.DescriptorProtos.MethodOptions.IdempotencyLevel;
import com.google.protobuf.DescriptorProtos.OneofDescriptorProto;
import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Descriptors.GenericDescriptor;
import com.google.protobuf.DurationProto;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.EmptyProto;
import com.google.protobuf.FieldMaskProto;
import com.google.protobuf.GeneratedMessageV3.ExtendableMessage;
import com.google.protobuf.Message;
import com.google.protobuf.SourceContextProto;
import com.google.protobuf.StructProto;
import com.google.protobuf.TimestampProto;
import com.google.protobuf.TypeProto;
import com.google.protobuf.WrappersProto;
import com.google.type.CalendarPeriodProto;
import com.google.type.ColorProto;
import com.google.type.DateProto;
import com.google.type.DateTimeProto;
import com.google.type.DayOfWeekProto;
import com.google.type.ExprProto;
import com.google.type.FractionProto;
import com.google.type.IntervalProto;
import com.google.type.LatLngProto;
import com.google.type.MoneyProto;
import com.google.type.MonthProto;
import com.google.type.PhoneNumberProto;
import com.google.type.PostalAddressProto;
import com.google.type.QuaternionProto;
import com.google.type.TimeOfDayProto;
import com.squareup.wire.Syntax;
import com.squareup.wire.schema.Field;
import com.squareup.wire.schema.Location;
import com.squareup.wire.schema.ProtoType;
import com.squareup.wire.schema.internal.parser.EnumConstantElement;
import com.squareup.wire.schema.internal.parser.EnumElement;
import com.squareup.wire.schema.internal.parser.ExtendElement;
import com.squareup.wire.schema.internal.parser.ExtensionsElement;
import com.squareup.wire.schema.internal.parser.FieldElement;
import com.squareup.wire.schema.internal.parser.MessageElement;
import com.squareup.wire.schema.internal.parser.OneOfElement;
import com.squareup.wire.schema.internal.parser.OptionElement;
import com.squareup.wire.schema.internal.parser.OptionElement.Kind;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import com.squareup.wire.schema.internal.parser.ProtoParser;
import com.squareup.wire.schema.internal.parser.ReservedElement;
import com.squareup.wire.schema.internal.parser.RpcElement;
import com.squareup.wire.schema.internal.parser.ServiceElement;
import com.squareup.wire.schema.internal.parser.TypeElement;
import io.streamnative.pulsar.handlers.kop.schemaregistry.model.ParsedSchema;
import io.streamnative.pulsar.handlers.kop.schemaregistry.model.rest.SchemaReference;
import io.streamnative.pulsar.handlers.kop.schemaregistry.providers.protobuf.ProtobufSchemaUtils.FormatContext;
import io.streamnative.pulsar.handlers.kop.schemaregistry.providers.protobuf.diff.Difference;
import io.streamnative.pulsar.handlers.kop.schemaregistry.providers.protobuf.diff.SchemaDiff;
import io.streamnative.pulsar.handlers.kop.schemaregistry.providers.protobuf.dynamic.DynamicSchema;
import io.streamnative.pulsar.handlers.kop.schemaregistry.providers.protobuf.dynamic.EnumDefinition;
import io.streamnative.pulsar.handlers.kop.schemaregistry.providers.protobuf.dynamic.MessageDefinition;
import io.streamnative.pulsar.handlers.kop.schemaregistry.providers.protobuf.dynamic.ServiceDefinition;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import kotlin.Pair;
import kotlin.ranges.IntRange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProtobufSchema implements ParsedSchema {
private static final Logger log = LoggerFactory.getLogger(ProtobufSchema.class);
public static final String TYPE = "PROTOBUF";
public static final String PROTO2 = "proto2";
public static final String PROTO3 = "proto3";
public static final String DOC_FIELD = "doc";
public static final String PARAMS_FIELD = "params";
public static final String TAGS_FIELD = "tags";
public static final String DEFAULT_NAME = "default";
public static final String MAP_ENTRY_SUFFIX = "Entry"; // Suffix used by protoc
public static final String KEY_FIELD = "key";
public static final String VALUE_FIELD = "value";
private static final String JAVA_PACKAGE = "java_package";
private static final String JAVA_OUTER_CLASSNAME = "java_outer_classname";
private static final String JAVA_MULTIPLE_FILES = "java_multiple_files";
private static final String JAVA_GENERATE_EQUALS_AND_HASH = "java_generate_equals_and_hash";
private static final String JAVA_STRING_CHECK_UTF8 = "java_string_check_utf8";
private static final String OPTIMIZE_FOR = "optimize_for";
private static final String GO_PACKAGE = "go_package";
private static final String CC_GENERIC_SERVICES = "cc_generic_services";
private static final String JAVA_GENERIC_SERVICES = "java_generic_services";
private static final String PY_GENERIC_SERVICES = "py_generic_services";
private static final String PHP_GENERIC_SERVICES = "php_generic_services";
private static final String DEPRECATED = "deprecated";
private static final String CC_ENABLE_ARENAS = "cc_enable_arenas";
private static final String OBJC_CLASS_PREFIX = "objc_class_prefix";
private static final String CSHARP_NAMESPACE = "csharp_namespace";
private static final String SWIFT_PREFIX = "swift_prefix";
private static final String PHP_CLASS_PREFIX = "php_class_prefix";
private static final String PHP_NAMESPACE = "php_namespace";
private static final String PHP_METADATA_NAMESPACE = "php_metadata_namespace";
private static final String RUBY_PACKAGE = "ruby_package";
private static final String NO_STANDARD_DESCRIPTOR_ACCESSOR = "no_standard_descriptor_accessor";
private static final String MAP_ENTRY = "map_entry";
private static final String CTYPE = "ctype";
private static final String PACKED = "packed";
private static final String JSTYPE = "jstype";
private static final String ALLOW_ALIAS = "allow_alias";
private static final String IDEMPOTENCY_LEVEL = "idempotency_level";
public static final Location DEFAULT_LOCATION = Location.get("");
public static final String CALENDAR_PERIOD_LOCATION = "google/type/calendar_period.proto";
public static final String COLOR_LOCATION = "google/type/color.proto";
public static final String DATE_LOCATION = "google/type/date.proto";
public static final String DATETIME_LOCATION = "google/type/datetime.proto";
public static final String DAY_OF_WEEK_LOCATION = "google/type/dayofweek.proto";
public static final String DECIMAL_LOCATION = "google/type/decimal.proto";
public static final String EXPR_LOCATION = "google/type/expr.proto";
public static final String FRACTION_LOCATION = "google/type/fraction.proto";
public static final String INTERVAL_LOCATION = "google/type/interval.proto";
public static final String LATLNG_LOCATION = "google/type/latlng.proto";
public static final String MONEY_LOCATION = "google/type/money.proto";
public static final String MONTH_LOCATION = "google/type/month.proto";
public static final String PHONE_NUMBER_LOCATION = "google/type/phone_number.proto";
public static final String POSTAL_ADDRESS_LOCATION = "google/type/postal_address.proto";
public static final String QUATERNION_LOCATION = "google/type/quaternion.proto";
public static final String TIME_OF_DAY_LOCATION = "google/type/timeofday.proto";
public static final String ANY_LOCATION = "google/protobuf/any.proto";
public static final String API_LOCATION = "google/protobuf/api.proto";
public static final String DESCRIPTOR_LOCATION = "google/protobuf/descriptor.proto";
public static final String DURATION_LOCATION = "google/protobuf/duration.proto";
public static final String EMPTY_LOCATION = "google/protobuf/empty.proto";
public static final String FIELD_MASK_LOCATION = "google/protobuf/field_mask.proto";
public static final String SOURCE_CONTEXT_LOCATION = "google/protobuf/source_context.proto";
public static final String STRUCT_LOCATION = "google/protobuf/struct.proto";
public static final String TIMESTAMP_LOCATION = "google/protobuf/timestamp.proto";
public static final String TYPE_LOCATION = "google/protobuf/type.proto";
public static final String WRAPPER_LOCATION = "google/protobuf/wrappers.proto";
private static final ProtoFileElement CALENDAR_PERIOD_SCHEMA =
toProtoFile(CalendarPeriodProto.getDescriptor().toProto());
private static final ProtoFileElement COLOR_SCHEMA =
toProtoFile(ColorProto.getDescriptor().toProto());
private static final ProtoFileElement DATE_SCHEMA =
toProtoFile(DateProto.getDescriptor().toProto());
private static final ProtoFileElement DATETIME_SCHEMA =
toProtoFile(DateTimeProto.getDescriptor().toProto());
private static final ProtoFileElement DAY_OF_WEEK_SCHEMA =
toProtoFile(DayOfWeekProto.getDescriptor().toProto());
private static final ProtoFileElement DECIMAL_SCHEMA =
toProtoFile(com.google.type.DecimalProto.getDescriptor().toProto());
private static final ProtoFileElement EXPR_SCHEMA =
toProtoFile(ExprProto.getDescriptor().toProto());
private static final ProtoFileElement FRACTION_SCHEMA =
toProtoFile(FractionProto.getDescriptor().toProto());
private static final ProtoFileElement INTERVAL_SCHEMA =
toProtoFile(IntervalProto.getDescriptor().toProto());
private static final ProtoFileElement LATLNG_SCHEMA =
toProtoFile(LatLngProto.getDescriptor().toProto());
private static final ProtoFileElement MONEY_SCHEMA =
toProtoFile(MoneyProto.getDescriptor().toProto());
private static final ProtoFileElement MONTH_SCHEMA =
toProtoFile(MonthProto.getDescriptor().toProto());
private static final ProtoFileElement PHONE_NUMBER_SCHEMA =
toProtoFile(PhoneNumberProto.getDescriptor().toProto());
private static final ProtoFileElement POSTAL_ADDRESS_SCHEMA =
toProtoFile(PostalAddressProto.getDescriptor().toProto());
private static final ProtoFileElement QUATERNION_SCHEMA =
toProtoFile(QuaternionProto.getDescriptor().toProto());
private static final ProtoFileElement TIME_OF_DAY_SCHEMA =
toProtoFile(TimeOfDayProto.getDescriptor().toProto());
private static final ProtoFileElement ANY_SCHEMA =
toProtoFile(AnyProto.getDescriptor().toProto());
private static final ProtoFileElement API_SCHEMA =
toProtoFile(ApiProto.getDescriptor().toProto());
private static final ProtoFileElement DESCRIPTOR_SCHEMA =
toProtoFile(DescriptorProtos.getDescriptor().toProto());
private static final ProtoFileElement DURATION_SCHEMA =
toProtoFile(DurationProto.getDescriptor().toProto());
private static final ProtoFileElement EMPTY_SCHEMA =
toProtoFile(EmptyProto.getDescriptor().toProto());
private static final ProtoFileElement FIELD_MASK_SCHEMA =
toProtoFile(FieldMaskProto.getDescriptor().toProto());
private static final ProtoFileElement SOURCE_CONTEXT_SCHEMA =
toProtoFile(SourceContextProto.getDescriptor().toProto());
private static final ProtoFileElement STRUCT_SCHEMA =
toProtoFile(StructProto.getDescriptor().toProto());
private static final ProtoFileElement TIMESTAMP_SCHEMA =
toProtoFile(TimestampProto.getDescriptor().toProto());
private static final ProtoFileElement TYPE_SCHEMA =
toProtoFile(TypeProto.getDescriptor().toProto());
private static final ProtoFileElement WRAPPER_SCHEMA =
toProtoFile(WrappersProto.getDescriptor().toProto());
private static final HashMap KNOWN_DEPENDENCIES;
static {
KNOWN_DEPENDENCIES = new HashMap<>();
KNOWN_DEPENDENCIES.put(CALENDAR_PERIOD_LOCATION, CALENDAR_PERIOD_SCHEMA);
KNOWN_DEPENDENCIES.put(COLOR_LOCATION, COLOR_SCHEMA);
KNOWN_DEPENDENCIES.put(DATE_LOCATION, DATE_SCHEMA);
KNOWN_DEPENDENCIES.put(DATETIME_LOCATION, DATETIME_SCHEMA);
KNOWN_DEPENDENCIES.put(DAY_OF_WEEK_LOCATION, DAY_OF_WEEK_SCHEMA);
KNOWN_DEPENDENCIES.put(DECIMAL_LOCATION, DECIMAL_SCHEMA);
KNOWN_DEPENDENCIES.put(EXPR_LOCATION, EXPR_SCHEMA);
KNOWN_DEPENDENCIES.put(FRACTION_LOCATION, FRACTION_SCHEMA);
KNOWN_DEPENDENCIES.put(INTERVAL_LOCATION, INTERVAL_SCHEMA);
KNOWN_DEPENDENCIES.put(LATLNG_LOCATION, LATLNG_SCHEMA);
KNOWN_DEPENDENCIES.put(MONEY_LOCATION, MONEY_SCHEMA);
KNOWN_DEPENDENCIES.put(MONTH_LOCATION, MONTH_SCHEMA);
KNOWN_DEPENDENCIES.put(PHONE_NUMBER_LOCATION, PHONE_NUMBER_SCHEMA);
KNOWN_DEPENDENCIES.put(POSTAL_ADDRESS_LOCATION, POSTAL_ADDRESS_SCHEMA);
KNOWN_DEPENDENCIES.put(QUATERNION_LOCATION, QUATERNION_SCHEMA);
KNOWN_DEPENDENCIES.put(TIME_OF_DAY_LOCATION, TIME_OF_DAY_SCHEMA);
KNOWN_DEPENDENCIES.put(ANY_LOCATION, ANY_SCHEMA);
KNOWN_DEPENDENCIES.put(API_LOCATION, API_SCHEMA);
KNOWN_DEPENDENCIES.put(DESCRIPTOR_LOCATION, DESCRIPTOR_SCHEMA);
KNOWN_DEPENDENCIES.put(DURATION_LOCATION, DURATION_SCHEMA);
KNOWN_DEPENDENCIES.put(EMPTY_LOCATION, EMPTY_SCHEMA);
KNOWN_DEPENDENCIES.put(FIELD_MASK_LOCATION, FIELD_MASK_SCHEMA);
KNOWN_DEPENDENCIES.put(SOURCE_CONTEXT_LOCATION, SOURCE_CONTEXT_SCHEMA);
KNOWN_DEPENDENCIES.put(STRUCT_LOCATION, STRUCT_SCHEMA);
KNOWN_DEPENDENCIES.put(TIMESTAMP_LOCATION, TIMESTAMP_SCHEMA);
KNOWN_DEPENDENCIES.put(TYPE_LOCATION, TYPE_SCHEMA);
KNOWN_DEPENDENCIES.put(WRAPPER_LOCATION, WRAPPER_SCHEMA);
}
public static Set knownTypes() {
return KNOWN_DEPENDENCIES.keySet();
}
private final ProtoFileElement schemaObj;
private final Integer version;
private final String name;
private final List references;
private final Map dependencies;
private transient String canonicalString;
private transient DynamicSchema dynamicSchema;
private transient Descriptor descriptor;
private transient int hashCode = NO_HASHCODE;
private static final int NO_HASHCODE = Integer.MIN_VALUE;
private static final Base64.Encoder base64Encoder = Base64.getEncoder();
private static final Base64.Decoder base64Decoder = Base64.getDecoder();
private static final ObjectMapper jsonMapper = new ObjectMapper();
private static volatile Method extensionFields;
public ProtobufSchema(String schemaString) {
this(schemaString, Collections.emptyList(), Collections.emptyMap(), null, null);
}
public ProtobufSchema(
String schemaString,
List references,
Map resolvedReferences,
Integer version,
String name
) {
try {
this.schemaObj = schemaString != null ? toProtoFile(schemaString) : null;
this.version = version;
this.name = name;
this.references = Collections.unmodifiableList(references);
this.dependencies = Collections.unmodifiableMap(resolvedReferences.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> toProtoFile(e.getValue())
)));
} catch (IllegalStateException e) {
log.error("Could not parse Protobuf schema " + schemaString
+ " with references " + references, e);
throw e;
}
}
public ProtobufSchema(
ProtoFileElement protoFileElement,
List references,
Map dependencies
) {
this.schemaObj = protoFileElement;
this.version = null;
this.name = null;
this.references = Collections.unmodifiableList(references);
this.dependencies = Collections.unmodifiableMap(dependencies);
}
public ProtobufSchema(Descriptor descriptor) {
this(descriptor, Collections.emptyList());
}
public ProtobufSchema(Descriptor descriptor, List references) {
Map dependencies = new HashMap<>();
this.schemaObj = toProtoFile(descriptor.getFile(), dependencies);
this.version = null;
this.name = descriptor.getFullName();
this.references = Collections.unmodifiableList(references);
this.dependencies = Collections.unmodifiableMap(dependencies);
this.descriptor = descriptor;
}
public ProtobufSchema(EnumDescriptor enumDescriptor) {
this(enumDescriptor, Collections.emptyList());
}
public ProtobufSchema(EnumDescriptor enumDescriptor, List references) {
Map dependencies = new HashMap<>();
this.schemaObj = toProtoFile(enumDescriptor.getFile(), dependencies);
this.version = null;
this.name = enumDescriptor.getFullName();
this.references = Collections.unmodifiableList(references);
this.dependencies = Collections.unmodifiableMap(dependencies);
this.descriptor = null;
}
public ProtobufSchema(FileDescriptor fileDescriptor) {
this(fileDescriptor, Collections.emptyList());
}
public ProtobufSchema(FileDescriptor fileDescriptor, List references) {
Map dependencies = new HashMap<>();
this.schemaObj = toProtoFile(fileDescriptor, dependencies);
this.version = null;
this.name = null;
this.references = Collections.unmodifiableList(references);
this.dependencies = Collections.unmodifiableMap(dependencies);
this.descriptor = null;
}
private ProtobufSchema(
ProtoFileElement schemaObj,
Integer version,
String name,
List references,
Map dependencies,
String canonicalString,
DynamicSchema dynamicSchema,
Descriptor descriptor
) {
this.schemaObj = schemaObj;
this.version = version;
this.name = name;
this.references = references;
this.dependencies = dependencies;
this.canonicalString = canonicalString;
this.dynamicSchema = dynamicSchema;
this.descriptor = descriptor;
}
@Override
public ProtobufSchema copy() {
return new ProtobufSchema(
this.schemaObj,
this.version,
this.name,
this.references,
this.dependencies,
this.canonicalString,
this.dynamicSchema,
this.descriptor
);
}
@Override
public ProtobufSchema copy(Integer version) {
return new ProtobufSchema(
this.schemaObj,
version,
this.name,
this.references,
this.dependencies,
this.canonicalString,
this.dynamicSchema,
this.descriptor
);
}
public ProtobufSchema copy(String name) {
return new ProtobufSchema(
this.schemaObj,
this.version,
name,
this.references,
this.dependencies,
this.canonicalString,
this.dynamicSchema,
// reset descriptor if names not equal
Objects.equals(this.name, name) ? this.descriptor : null
);
}
public ProtobufSchema copy(List references) {
return new ProtobufSchema(
this.schemaObj,
this.version,
this.name,
references,
this.dependencies,
this.canonicalString,
this.dynamicSchema,
this.descriptor
);
}
public ProtobufSchema copyWithSchema(String schema) {
return new ProtobufSchema(
toProtoFile(schema),
this.version,
this.name,
references,
this.dependencies,
schema,
null,
null
);
}
private ProtoFileElement toProtoFile(String schema) {
try {
return ProtoParser.Companion.parse(DEFAULT_LOCATION, schema);
} catch (Exception e) {
try {
// Attempt to parse binary FileDescriptorProto
byte[] bytes = base64Decoder.decode(schema);
return toProtoFile(FileDescriptorProto.parseFrom(bytes));
} catch (Exception pe) {
throw new IllegalArgumentException("Could not parse Protobuf - " + e.getMessage(), e);
}
}
}
private static ProtoFileElement toProtoFile(
FileDescriptor file, Map dependencies
) {
for (FileDescriptor dependency : file.getDependencies()) {
String depName = dependency.getName();
dependencies.put(depName, toProtoFile(dependency, dependencies));
}
return toProtoFile(file.toProto());
}
private static ProtoFileElement toProtoFile(FileDescriptorProto file) {
String packageName = file.getPackage();
// Don't set empty package name
if ("".equals(packageName)) {
packageName = null;
}
Syntax syntax = null;
switch (file.getSyntax()) {
case PROTO2:
syntax = Syntax.PROTO_2;
break;
case PROTO3:
syntax = Syntax.PROTO_3;
break;
default:
break;
}
ImmutableList.Builder types = ImmutableList.builder();
for (DescriptorProto md : file.getMessageTypeList()) {
MessageElement message = toMessage(file, md);
types.add(message);
}
for (EnumDescriptorProto ed : file.getEnumTypeList()) {
EnumElement enumer = toEnum(ed);
types.add(enumer);
}
ImmutableList.Builder services = ImmutableList.builder();
for (ServiceDescriptorProto sd : file.getServiceList()) {
ServiceElement service = toService(sd);
services.add(service);
}
ImmutableList.Builder imports = ImmutableList.builder();
ImmutableList.Builder publicImports = ImmutableList.builder();
List dependencyList = file.getDependencyList();
Set publicDependencyList = new HashSet<>(file.getPublicDependencyList());
for (int i = 0; i < dependencyList.size(); i++) {
String depName = dependencyList.get(i);
if (publicDependencyList.contains(i)) {
publicImports.add(depName);
} else {
imports.add(depName);
}
}
ImmutableList.Builder options = ImmutableList.builder();
if (file.getOptions().hasJavaPackage()) {
options.add(new OptionElement(
JAVA_PACKAGE, Kind.STRING, file.getOptions().getJavaPackage(), false));
}
if (file.getOptions().hasJavaOuterClassname()) {
options.add(new OptionElement(
JAVA_OUTER_CLASSNAME, Kind.STRING, file.getOptions().getJavaOuterClassname(), false));
}
if (file.getOptions().hasJavaMultipleFiles()) {
options.add(new OptionElement(
JAVA_MULTIPLE_FILES, Kind.BOOLEAN, file.getOptions().getJavaMultipleFiles(), false));
}
if (file.getOptions().hasJavaGenerateEqualsAndHash()) {
options.add(new OptionElement(
JAVA_GENERATE_EQUALS_AND_HASH, Kind.BOOLEAN,
file.getOptions().getJavaGenerateEqualsAndHash(), false));
}
if (file.getOptions().hasJavaStringCheckUtf8()) {
options.add(new OptionElement(
JAVA_STRING_CHECK_UTF8, Kind.BOOLEAN, file.getOptions().getJavaStringCheckUtf8(), false));
}
if (file.getOptions().hasOptimizeFor()) {
options.add(new OptionElement(
OPTIMIZE_FOR, Kind.ENUM, file.getOptions().getOptimizeFor(), false));
}
if (file.getOptions().hasGoPackage()) {
options.add(new OptionElement(
GO_PACKAGE, Kind.STRING, file.getOptions().getGoPackage(), false));
}
if (file.getOptions().hasCcGenericServices()) {
options.add(new OptionElement(
CC_GENERIC_SERVICES, Kind.BOOLEAN, file.getOptions().getCcGenericServices(), false));
}
if (file.getOptions().hasJavaGenericServices()) {
options.add(new OptionElement(
JAVA_GENERIC_SERVICES, Kind.BOOLEAN, file.getOptions().getJavaGenericServices(), false));
}
if (file.getOptions().hasPyGenericServices()) {
options.add(new OptionElement(
PY_GENERIC_SERVICES, Kind.BOOLEAN, file.getOptions().getPyGenericServices(), false));
}
if (file.getOptions().hasPhpGenericServices()) {
options.add(new OptionElement(
PHP_GENERIC_SERVICES, Kind.BOOLEAN, file.getOptions().getPhpGenericServices(), false));
}
if (file.getOptions().hasDeprecated()) {
options.add(new OptionElement(
DEPRECATED, Kind.BOOLEAN, file.getOptions().getDeprecated(), false));
}
if (file.getOptions().hasCcEnableArenas()) {
options.add(new OptionElement(
CC_ENABLE_ARENAS, Kind.BOOLEAN, file.getOptions().getCcEnableArenas(), false));
}
if (file.getOptions().hasObjcClassPrefix()) {
options.add(new OptionElement(
OBJC_CLASS_PREFIX, Kind.STRING, file.getOptions().getObjcClassPrefix(), false));
}
if (file.getOptions().hasCsharpNamespace()) {
options.add(new OptionElement(
CSHARP_NAMESPACE, Kind.STRING, file.getOptions().getCsharpNamespace(), false));
}
if (file.getOptions().hasSwiftPrefix()) {
options.add(new OptionElement(
SWIFT_PREFIX, Kind.STRING, file.getOptions().getSwiftPrefix(), false));
}
if (file.getOptions().hasPhpClassPrefix()) {
options.add(new OptionElement(
PHP_CLASS_PREFIX, Kind.STRING, file.getOptions().getPhpClassPrefix(), false));
}
if (file.getOptions().hasPhpNamespace()) {
options.add(new OptionElement(
PHP_NAMESPACE, Kind.STRING, file.getOptions().getPhpNamespace(), false));
}
if (file.getOptions().hasPhpMetadataNamespace()) {
options.add(new OptionElement(
PHP_METADATA_NAMESPACE, Kind.STRING, file.getOptions().getPhpMetadataNamespace(), false));
}
if (file.getOptions().hasRubyPackage()) {
options.add(new OptionElement(
RUBY_PACKAGE, Kind.STRING, file.getOptions().getRubyPackage(), false));
}
options.addAll(toCustomOptions(file.getOptions()));
ImmutableList.Builder extendElements =
toExtendElements(file, file.getExtensionList());
return new ProtoFileElement(DEFAULT_LOCATION,
packageName,
syntax,
imports.build(),
publicImports.build(),
types.build(),
services.build(),
extendElements.build(),
options.build()
);
}
private static ImmutableList.Builder toExtendElements(
FileDescriptorProto file, List fields) {
Map> extendFieldElements = new LinkedHashMap<>();
for (FieldDescriptorProto fd : fields) {
// Note that the extendee is a fully qualified name
ImmutableList.Builder extendFields = extendFieldElements.computeIfAbsent(
fd.getExtendee(), k -> ImmutableList.builder());
extendFields.add(toField(file, fd, false));
}
ImmutableList.Builder extendElements = ImmutableList.builder();
for (Map.Entry> extendFieldElement :
extendFieldElements.entrySet()) {
extendElements.add(new ExtendElement(DEFAULT_LOCATION,
extendFieldElement.getKey(), "", extendFieldElement.getValue().build()));
}
return extendElements;
}
private static List toCustomOptions(ExtendableMessage> options) {
// Uncomment this in case the getExtensionFields method is deprecated
//return options.getAllFields().entrySet().stream()
return getExtensionFields(options).entrySet().stream()
.filter(e -> e.getKey().isExtension())
.flatMap(e -> toOptionElements(e.getKey().getFullName(), e.getValue()))
.collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
private static Map getExtensionFields(
ExtendableMessage> options) {
// We use reflection to access getExtensionFields as an optimization over calling getAllFields
try {
if (extensionFields == null) {
synchronized (ProtobufSchema.class) {
if (extensionFields == null) {
extensionFields = ExtendableMessage.class.getDeclaredMethod("getExtensionFields");
}
}
extensionFields.setAccessible(true);
}
return (Map) extensionFields.invoke(options);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
private static Stream toOptionElements(String name, Object value) {
if (value instanceof List) {
return ((List>) value).stream().map(v -> toOptionElement(name, v));
} else {
return Stream.of(toOptionElement(name, value));
}
}
private static OptionElement toOptionElement(String name, Object value) {
return new OptionElement(name, toKind(value), toOptionValue(value, false), true);
}
private static Kind toKind(Object value) {
if (value instanceof String) {
return Kind.STRING;
} else if (value instanceof Boolean) {
return Kind.BOOLEAN;
} else if (value instanceof Number) {
return Kind.NUMBER;
} else if (value instanceof Enum || value instanceof EnumValueDescriptor) {
return Kind.ENUM;
} else if (value instanceof List) {
return Kind.LIST;
} else if (value instanceof Message) {
return Kind.MAP;
} else {
throw new IllegalArgumentException("Unsupported option type " + value.getClass().getName());
}
}
private static Object toOptionValue(Object value, boolean isMapValue) {
if (value instanceof List) {
return ((List>) value).stream()
.map(o -> toOptionValue(o, false))
.collect(Collectors.toList());
} else if (value instanceof Message) {
return toOptionMap((Message) value);
} else {
if (isMapValue) {
if (value instanceof Boolean) {
return new OptionElement.OptionPrimitive(Kind.BOOLEAN, value);
} else if (value instanceof Enum) {
return new OptionElement.OptionPrimitive(Kind.ENUM, value);
} else if (value instanceof Number) {
return new OptionElement.OptionPrimitive(Kind.NUMBER, value);
}
}
return value;
}
}
private static Map toOptionMap(Message message) {
return message.getAllFields().entrySet().stream()
.map(e -> new Pair<>(toOptionMapKey(e.getKey()), toOptionValue(e.getValue(), true)))
.filter(p -> p.getSecond() != null)
.collect(Collectors.toMap(Pair::getFirst, Pair::getSecond,
(e1, e2) -> e1, LinkedHashMap::new));
}
private static String toOptionMapKey(FieldDescriptor field) {
return field.isExtension() ? "[" + field.getFullName() + "]" : field.getName();
}
private static MessageElement toMessage(FileDescriptorProto file, DescriptorProto descriptor) {
String name = descriptor.getName();
log.trace("*** msg name: {}", name);
ImmutableList.Builder fields = ImmutableList.builder();
ImmutableList.Builder nested = ImmutableList.builder();
ImmutableList.Builder reserved = ImmutableList.builder();
ImmutableList.Builder extensions = ImmutableList.builder();
LinkedHashMap> oneofsMap = new LinkedHashMap<>();
for (OneofDescriptorProto od : descriptor.getOneofDeclList()) {
oneofsMap.put(od.getName(), ImmutableList.builder());
}
List>> oneofs =
new ArrayList<>(oneofsMap.entrySet());
for (FieldDescriptorProto fd : descriptor.getFieldList()) {
if (fd.hasOneofIndex() && !fd.getProto3Optional()) {
FieldElement field = toField(file, fd, true);
oneofs.get(fd.getOneofIndex()).getValue().add(field);
} else {
FieldElement field = toField(file, fd, false);
fields.add(field);
}
}
for (DescriptorProto nestedDesc : descriptor.getNestedTypeList()) {
MessageElement nestedMessage = toMessage(file, nestedDesc);
nested.add(nestedMessage);
}
for (EnumDescriptorProto nestedDesc : descriptor.getEnumTypeList()) {
EnumElement nestedEnum = toEnum(nestedDesc);
nested.add(nestedEnum);
}
for (ReservedRange range : descriptor.getReservedRangeList()) {
ReservedElement reservedElem = toReserved(range);
reserved.add(reservedElem);
}
for (String reservedName : descriptor.getReservedNameList()) {
ReservedElement reservedElem = new ReservedElement(
DEFAULT_LOCATION,
"",
Collections.singletonList(reservedName)
);
reserved.add(reservedElem);
}
for (ExtensionRange extensionRange : descriptor.getExtensionRangeList()) {
ExtensionsElement extension = toExtension(extensionRange);
extensions.add(extension);
}
ImmutableList.Builder options = ImmutableList.builder();
if (descriptor.getOptions().hasNoStandardDescriptorAccessor()) {
OptionElement option = new OptionElement(
NO_STANDARD_DESCRIPTOR_ACCESSOR, Kind.BOOLEAN,
descriptor.getOptions().getNoStandardDescriptorAccessor(), false
);
options.add(option);
}
if (descriptor.getOptions().hasDeprecated()) {
OptionElement option = new OptionElement(
DEPRECATED, Kind.BOOLEAN,
descriptor.getOptions().getDeprecated(), false
);
options.add(option);
}
if (descriptor.getOptions().hasMapEntry()) {
OptionElement option = new OptionElement(
MAP_ENTRY, Kind.BOOLEAN,
descriptor.getOptions().getMapEntry(), false
);
options.add(option);
}
options.addAll(toCustomOptions(descriptor.getOptions()));
ImmutableList.Builder extendElements =
toExtendElements(file, descriptor.getExtensionList());
// NOTE: skip groups
return new MessageElement(DEFAULT_LOCATION,
name,
"",
nested.build(),
options.build(),
reserved.build(),
fields.build(),
oneofs.stream()
.map(e -> toOneof(e.getKey(), e.getValue()))
.filter(e -> !e.getFields().isEmpty())
.collect(Collectors.toList()),
extensions.build(),
Collections.emptyList(),
extendElements.build()
);
}
private static OneOfElement toOneof(String name, ImmutableList.Builder fields) {
log.trace("*** oneof name: {}", name);
// NOTE: skip groups
return new OneOfElement(name, "", fields.build(),
Collections.emptyList(), Collections.emptyList(), Location.get("base"));
}
private static EnumElement toEnum(EnumDescriptorProto ed) {
String name = ed.getName();
log.trace("*** enum name: {}", name);
ImmutableList.Builder constants = ImmutableList.builder();
for (EnumValueDescriptorProto ev : ed.getValueList()) {
ImmutableList.Builder options = ImmutableList.builder();
if (ev.getOptions().hasDeprecated()) {
OptionElement option = new OptionElement(
DEPRECATED, Kind.BOOLEAN,
ev.getOptions().getDeprecated(), false
);
options.add(option);
}
options.addAll(toCustomOptions(ev.getOptions()));
constants.add(new EnumConstantElement(
DEFAULT_LOCATION,
ev.getName(),
ev.getNumber(),
"",
options.build()
));
}
ImmutableList.Builder reserved = ImmutableList.builder();
for (EnumReservedRange range : ed.getReservedRangeList()) {
ReservedElement reservedElem = toReserved(range);
reserved.add(reservedElem);
}
for (String reservedName : ed.getReservedNameList()) {
ReservedElement reservedElem = new ReservedElement(
DEFAULT_LOCATION,
"",
Collections.singletonList(reservedName)
);
reserved.add(reservedElem);
}
ImmutableList.Builder options = ImmutableList.builder();
if (ed.getOptions().hasAllowAlias()) {
OptionElement option = new OptionElement(
ALLOW_ALIAS, Kind.BOOLEAN,
ed.getOptions().getAllowAlias(), false
);
options.add(option);
}
if (ed.getOptions().hasDeprecated()) {
OptionElement option = new OptionElement(
DEPRECATED, Kind.BOOLEAN,
ed.getOptions().getDeprecated(), false
);
options.add(option);
}
options.addAll(toCustomOptions(ed.getOptions()));
return new EnumElement(DEFAULT_LOCATION, name, "",
options.build(), constants.build(), reserved.build());
}
private static ReservedElement toReserved(ReservedRange range) {
List