com.github.protobufel.grammar.FileDescriptorEx Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of protobufel-grammar Show documentation
Show all versions of protobufel-grammar Show documentation
ProtoBuf Java Parser and FileDescriptor Builder
//
// Copyright © 2014, David Tesler (https://github.com/protobufel)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
package com.github.protobufel.grammar;
import static com.github.protobufel.grammar.ExtensionRegistries.buildFullRegistryOf;
import static com.github.protobufel.grammar.PrimitiveTypesUtil.getSimpleFieldValue;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.github.protobufel.grammar.Exceptions.DescriptorValidationRuntimeException;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Type;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileOptions;
import com.google.protobuf.DescriptorProtos.MethodDescriptorProto;
import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.DescriptorValidationException;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Descriptors.MethodDescriptor;
import com.google.protobuf.Descriptors.ServiceDescriptor;
import com.google.protobuf.ExtensionRegistry;
/**
* FileDescriptor/Proto canonicalization utility. To be removed/refactored when no longer needed.
*
* @author [email protected] David Tesler
*/
// FIXME most of it wrong and obsolete! isCanonical/deeeplyCanononical might be useful in the
// future.
// FIXME remove junk and move this into FileDescriptors!
final class FileDescriptorEx implements Comparable {
private static final FileDescriptorEx DESCRIPTOR_PROTO = new FileDescriptorEx();
private static boolean protocCompatible = false;
private final transient FileDescriptor delegate;
private FileDescriptorProto proto;
private transient volatile int hashCode = 0;
private transient volatile int deeplyCanonical = -1;
private final List dependencies;
private final boolean reparseCustomOptions;
private FileDescriptorEx(final FileDescriptorEx other) {
delegate = other.delegate;
proto = other.proto;
dependencies = other.dependencies; // share reference to entire list!
deeplyCanonical = other.deeplyCanonical;
hashCode = other.hashCode;
reparseCustomOptions = other.reparseCustomOptions;
}
private FileDescriptorEx() {
delegate = DescriptorProtos.getDescriptor();
proto = delegate.toProto();
dependencies = Collections.emptyList();
deeplyCanonical = 1;
hashCode = delegate.hashCode();
reparseCustomOptions = false;
}
private FileDescriptorEx(final FileDescriptor delegate, final boolean reparseCustomOptions) {
if (delegate == null) {
throw new NullPointerException();
}
if (delegate.getDependencies().isEmpty()) {
dependencies = Collections.emptyList();
} else {
dependencies = new ArrayList(delegate.getDependencies().size());
}
this.delegate = delegate;
this.reparseCustomOptions = reparseCustomOptions;
proto = null;
}
private static FileDescriptorEx getDescriptorProto() {
return DESCRIPTOR_PROTO;
}
private static FileDescriptorEx getInstance(final FileDescriptor delegate,
final boolean reparseCustomOptions) {
return delegate == DESCRIPTOR_PROTO.delegate ? DESCRIPTOR_PROTO : new FileDescriptorEx(
delegate, reparseCustomOptions);
}
static boolean isProtocCompatible() {
return protocCompatible;
}
static void setProtocCompatible(final boolean protocCompatible) {
FileDescriptorEx.protocCompatible = protocCompatible;
}
private static FileDescriptorEx buildFrom(final FileDescriptorProto proto,
final FileDescriptor[] dependencies, final boolean reparseCustomOptions) {
try {
return getInstance(FileDescriptor.buildFrom(proto, dependencies), reparseCustomOptions);
} catch (final DescriptorValidationException e) {
throw new DescriptorValidationRuntimeException(e);
}
}
private static FileDescriptor buildCanonicalFileDescriptor(final FileDescriptor file,
final boolean reparseCustomOptions) {
return getInstance(file, reparseCustomOptions).getCanonicalFileDescriptor(true);
}
private static FileDescriptor buildCanonicalFileDescriptor(final FileDescriptorProto proto,
final FileDescriptor[] dependencies, final boolean reparseCustomOptions) {
return FileDescriptorEx.buildFrom(proto, dependencies, reparseCustomOptions)
.getCanonicalFileDescriptor(true);
}
private static FileDescriptor buildBasicCanonicalFileDescriptor(final FileDescriptorProto proto,
final FileDescriptor[] dependencies, final boolean reparseCustomOptions) {
return FileDescriptorEx.buildFrom(proto, dependencies, reparseCustomOptions)
.getBasicCanonicalFileDescriptor(true);
}
public String getName() {
return delegate.getName();
}
public String getPackage() {
return delegate.getPackage();
}
public FileOptions getOptions() {
return delegate.getOptions();
}
public List getMessageTypes() {
return delegate.getMessageTypes();
}
public List getEnumTypes() {
return delegate.getEnumTypes();
}
public List getServices() {
return delegate.getServices();
}
public List getExtensions() {
return delegate.getExtensions();
}
public List getDependencies() {
if (dependencies != Collections.EMPTY_LIST && dependencies.isEmpty()) {
synchronized (dependencies) {
if (dependencies.isEmpty()) {
for (final FileDescriptor dependency : delegate.getDependencies()) {
dependencies.add(getInstance(dependency, reparseCustomOptions));
}
}
}
}
return Collections.unmodifiableList(dependencies);
}
public List getPublicDependencies() {
final int publicCount = delegate.toProto().getPublicDependencyCount();
if (publicCount == 0) {
return Collections.emptyList();
}
final List publicDependencies = new ArrayList(publicCount);
final List dependencies = getDependencies();
for (final int publicIndex : delegate.toProto().getPublicDependencyList()) {
publicDependencies.add(dependencies.get(publicIndex));
}
return Collections.unmodifiableList(publicDependencies);
}
public Descriptor findMessageTypeByName(final String name) {
return delegate.findMessageTypeByName(name);
}
public EnumDescriptor findEnumTypeByName(final String name) {
return delegate.findEnumTypeByName(name);
}
public ServiceDescriptor findServiceByName(final String name) {
return delegate.findServiceByName(name);
}
public FieldDescriptor findExtensionByName(final String name) {
return delegate.findExtensionByName(name);
}
@Override
public String toString() {
return delegate.toString();
}
@Override
public int hashCode() {
if (hashCode != 0) {
return hashCode;
}
final int prime = 31;
int result = 1;
result = prime * result + delegate.hashCode();
result = prime * result + toProto().hashCode();
hashCode = result;
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof FileDescriptorEx)) {
return false;
}
final FileDescriptorEx other = (FileDescriptorEx) obj;
return delegate == other.delegate || getName().equals(other.getName())
&& toProto().equals(other.toProto());
}
@Override
public int compareTo(final FileDescriptorEx other) {
if (this == other || delegate == other.delegate) {
return 0;
}
// this is just an optimization, as TextFormat will output the name as the
// first variable
final int compareTo = delegate.getName().compareTo(other.delegate.getName());
if (compareTo != 0) {
return compareTo;
}
return toProto().toString().compareTo(other.toProto().toString());
}
public FileDescriptor getFileDescriptor() {
return delegate;
}
public FileDescriptor getBasicCanonicalFileDescriptor(final boolean forceRebuild) {
if (!forceRebuild && isCanonical()) {
return delegate;
}
try {
return FileDescriptor.buildFrom(toCanonicalProto(),
delegate.getDependencies().toArray(new FileDescriptor[0]));
} catch (final DescriptorValidationException e) {
throw new DescriptorValidationRuntimeException(e);
}
}
public FileDescriptor getCanonicalFileDescriptor(final boolean forceRebuild) {
if (!forceRebuild && isCanonical()) {
return delegate;
}
return buildFileDescriptorWithReserializedProto(toCanonicalProto(), delegate.getDependencies()
.toArray(new FileDescriptor[0]));
}
public FileDescriptor getDeepCanonicalFileDescriptor(final boolean forceRebuild) {
if (deeplyCanonical == 1 || !forceRebuild && isDeeplyCanonical()) {
return delegate;
}
final List dependenciesEx = getDependencies();
final FileDescriptor[] dependencies = new FileDescriptor[dependenciesEx.size()];
int i = 0;
for (final FileDescriptorEx dependency : dependenciesEx) {
dependencies[i++] = dependency.getDeepCanonicalFileDescriptor(forceRebuild);
}
return buildFileDescriptorWithReserializedProto(toProto(), dependencies);
}
private FileDescriptor getDeepCanonicalFileDescriptor(final FileDescriptor file,
final boolean forceRebuild) throws DescriptorValidationException {
if (!forceRebuild && isDeeplyCanonical(file)) {
return file;
}
final FileDescriptor[] dependencies = new FileDescriptor[file.getDependencies().size()];
int i = 0;
for (final FileDescriptor dependency : file.getDependencies()) {
dependencies[i++] = getDeepCanonicalFileDescriptor(dependency, forceRebuild);
}
final FileDescriptorProto proto = isCanonical(file) ? file.toProto() : makeCanonicalProto(file);
return buildFileDescriptorWithReserializedProto(proto, dependencies);
}
private FileDescriptor buildFileDescriptorWithReserializedProto(final FileDescriptorProto proto,
final FileDescriptor[] dependencies) {
try {
final FileDescriptor fileDescriptor = FileDescriptor.buildFrom(proto, dependencies);
final ExtensionRegistry registry = buildFullRegistryOf(fileDescriptor);
FileDescriptor.internalUpdateFileDescriptor(fileDescriptor, registry);
return fileDescriptor;
} catch (final DescriptorValidationException e) {
throw new DescriptorValidationRuntimeException(e);
}
}
public boolean isDeeplyCanonical() {
if (deeplyCanonical == -1) {
if (!isCanonical()) {
deeplyCanonical = 0;
return false;
}
for (final FileDescriptorEx dependency : getDependencies()) {
if (!dependency.isDeeplyCanonical()) {
deeplyCanonical = 0;
return false;
}
}
deeplyCanonical = 1;
return true;
}
return deeplyCanonical == 0 ? false : true;
}
boolean isDeeplyCanonicalNoDependenciesBuilt() {
if (deeplyCanonical == -1) {
if (dependencies == Collections.EMPTY_LIST) {
deeplyCanonical = isCanonical() ? 1 : 0;
} else if (!dependencies.isEmpty()) {
if (!isCanonical()) {
deeplyCanonical = 0;
return false;
}
for (final FileDescriptorEx dependency : dependencies) {
if (!dependency.isDeeplyCanonicalNoDependenciesBuilt()) {
deeplyCanonical = 0;
return false;
}
}
deeplyCanonical = 1;
return true;
} else { // either build and query dependencies, or query FileDescriptors
// directly
// we query FileDescriptors directly here
deeplyCanonical = isDeeplyCanonical(delegate) ? 1 : 0;
}
}
return deeplyCanonical == 0 ? false : true;
}
private boolean isDeeplyCanonical(final FileDescriptor file) {
if (!isCanonical(file)) {
return false;
}
for (final FileDescriptor dependency : file.getDependencies()) {
if (!isDeeplyCanonical(dependency)) {
return false;
}
}
return true;
}
public boolean isCanonical() {
return toProto() == delegate.toProto();
}
public FileDescriptorProto toProto() {
if (proto == null) {
synchronized (delegate) {
if (proto == null) {
if (!protocCompatible && isCanonical(delegate)) {
proto = delegate.toProto();
} else {
proto = makeCanonicalProto(delegate);
}
}
}
}
return proto;
}
public FileDescriptorProto toCanonicalProto() {
if (proto == null) {
synchronized (delegate) {
if (proto == null) {
proto = makeCanonicalProto(delegate);
}
}
}
return proto;
}
// TODO: should do defaultValue.equals(convertToCanonical(defaultValue)) check;
// very consuming and problematic!
private boolean isCanonical(final FileDescriptor file) {
final FileDescriptorProto proto = file.toProto();
if (proto.hasOptions() && proto.getOptions().getUninterpretedOptionCount() > 0) {
return false;
}
for (final FieldDescriptorProto field : proto.getExtensionList()) {
if (!isFieldCanonical(field)) {
return false;
}
}
for (final ServiceDescriptorProto serviceProto : proto.getServiceList()) {
if (!isCanonical(serviceProto)) {
return false;
}
}
for (final EnumDescriptorProto enumProto : proto.getEnumTypeList()) {
if (!isCanonical(enumProto)) {
return false;
}
}
for (final DescriptorProto message : proto.getMessageTypeList()) {
if (!isMessageRefsCanonical(message)) {
return false;
}
}
return true;
}
private boolean isCanonical(final ServiceDescriptorProto serviceProto) {
if (serviceProto.hasOptions() && serviceProto.getOptions().getUninterpretedOptionCount() > 0) {
return false;
}
for (final MethodDescriptorProto methodProto : serviceProto.getMethodList()) {
if (methodProto.hasOptions() && methodProto.getOptions().getUninterpretedOptionCount() > 0) {
return false;
}
}
return true;
}
private boolean isMessageRefsCanonical(final DescriptorProto message) {
if (message.hasOptions() && message.getOptions().getUninterpretedOptionCount() > 0) {
return false;
}
for (final FieldDescriptorProto field : message.getExtensionList()) {
if (!isFieldCanonical(field)) {
return false;
}
}
for (final FieldDescriptorProto field : message.getFieldList()) {
if (!isFieldCanonical(field)) {
return false;
}
}
for (final EnumDescriptorProto enumProto : message.getEnumTypeList()) {
if (!isCanonical(enumProto)) {
return false;
}
}
for (final DescriptorProto child : message.getNestedTypeList()) {
if (!isMessageRefsCanonical(child)) {
return false;
}
}
return true;
}
private boolean isCanonical(final EnumDescriptorProto enumProto) {
if (enumProto.hasOptions() && enumProto.getOptions().getUninterpretedOptionCount() > 0) {
return false;
}
for (final EnumValueDescriptorProto enumValue : enumProto.getValueList()) {
if (enumValue.hasOptions() && enumValue.getOptions().getUninterpretedOptionCount() > 0) {
return false;
}
}
return true;
}
private boolean isFieldCanonical(final FieldDescriptorProto field) {
return (!field.hasExtendee() || field.getExtendee().startsWith("."))
&& (!field.hasTypeName() || field.getTypeName().startsWith(".") && field.hasType())
&& !(field.hasOptions() && field.getOptions().getUninterpretedOptionCount() > 0);
}
private FileDescriptorProto makeCanonicalProto(final FileDescriptor file) {
final FileDescriptorProto.Builder protoBuilder = FileDescriptorProto.newBuilder(file.toProto());
for (final FieldDescriptorProto.Builder field : protoBuilder.getExtensionBuilderList()) {
makeCanonicalField(field, file.findExtensionByName(field.getName()));
}
for (final DescriptorProto.Builder message : protoBuilder.getMessageTypeBuilderList()) {
makeCanonicalMessage(message, file.findMessageTypeByName(message.getName()));
}
// for (EnumDescriptorProto.Builder enumProto :
// protoBuilder.getEnumTypeBuilderList()) {
// makeCanonicalEnum(enumProto,
// file.findEnumTypeByName(enumProto.getName()));
// }
for (final ServiceDescriptorProto.Builder serviceProto : protoBuilder.getServiceBuilderList()) {
makeCanonicalService(serviceProto, file.findServiceByName(serviceProto.getName()));
}
return OptionResolver.newBuilder().setCustomOptionsAsExtensions(reparseCustomOptions)
.resolveAllOptionsFor(file, protoBuilder).build();
}
private void makeCanonicalService(final ServiceDescriptorProto.Builder service,
final ServiceDescriptor serviceDescriptor) {
for (final MethodDescriptorProto.Builder method : service.getMethodBuilderList()) {
final MethodDescriptor methodDescriptor =
serviceDescriptor.findMethodByName(method.getName());
method.setInputType(ensureLeadingDot(methodDescriptor.getInputType().getFullName()));
method.setOutputType(ensureLeadingDot(methodDescriptor.getOutputType().getFullName()));
}
}
private void makeCanonicalMessage(final DescriptorProto.Builder message,
final Descriptor messageDescriptor) {
final List extensionsList = messageDescriptor.getExtensions();
if (extensionsList.size() != message.getExtensionBuilderList().size()) {
throw new IllegalStateException(String.format(
"Message %s has a size of extensions differ from its Descriptor!", message.getName()));
}
if (!extensionsList.isEmpty()) {
final Map extensionMap = new HashMap();
for (final FieldDescriptor field : extensionsList) {
extensionMap.put(field.getNumber(), field);
}
for (final FieldDescriptorProto.Builder field : message.getExtensionBuilderList()) {
final FieldDescriptor extensionField = extensionMap.get(field.getNumber());
makeCanonicalField(field, extensionField);
}
}
// for (FieldDescriptorProto.Builder field :
// message.getExtensionBuilderList()) {
// makeCanonicalField(field,
// messageDescriptor.findFieldByNumber(field.getNumber()));
// }
for (final FieldDescriptorProto.Builder field : message.getFieldBuilderList()) {
makeCanonicalField(field, messageDescriptor.findFieldByNumber(field.getNumber()));
}
for (final DescriptorProto.Builder child : message.getNestedTypeBuilderList()) {
makeCanonicalMessage(child, messageDescriptor.findNestedTypeByName(child.getName()));
}
}
private void makeCanonicalField(final FieldDescriptorProto.Builder field,
final FieldDescriptor fieldDescriptor) {
if (field.hasExtendee() && !field.getExtendee().startsWith(".")) {
field.setExtendee(ensureLeadingDot(fieldDescriptor.getContainingType().getFullName()));
}
if (field.hasTypeName() && !field.getTypeName().startsWith(".")) {
if (fieldDescriptor.getJavaType() == JavaType.ENUM) {
field.setTypeName(ensureLeadingDot(fieldDescriptor.getEnumType().getFullName()));
field.setType(Type.TYPE_ENUM);
} else {
// this must be a Message/Group field
field.setTypeName(ensureLeadingDot(fieldDescriptor.getMessageType().getFullName()));
if (!field.hasType()) {
switch (fieldDescriptor.getType()) {
case MESSAGE:
field.setType(Type.TYPE_MESSAGE);
break;
case GROUP:
field.setType(Type.TYPE_GROUP);
break;
default:
field.setType(Type.valueOf("TYPE_" + fieldDescriptor.getType().name()));
}
}
}
}
// reparsing of the default value if present - protoc does this contrary to the spec!
if (protocCompatible) {
if (field.hasDefaultValue()) {
final StringBuilder sb = new StringBuilder();
try {
// TextFormat.printFieldValue(fieldDescriptor, fieldDescriptor.getDefaultValue(), sb);
// field.setDefaultValue(sb.toString());
final String defaultValue =
getSimpleFieldValue(fieldDescriptor, fieldDescriptor.getDefaultValue());
field.setDefaultValue(defaultValue);
} catch (final IOException e) {
// this should not happen!
throw new RuntimeException(e);
}
}
}
}
private String ensureLeadingDot(final String name) {
return name.startsWith(".") ? name : "." + name;
}
}