com.github.protobufel.grammar.FileDescriptors 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 java.util.Objects;
import com.github.protobufel.grammar.Exceptions.DescriptorValidationRuntimeException;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Type;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorProtoOrBuilder;
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.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;
/**
* Helps in building canonical FileDescriptors.
*
* @author [email protected] David Tesler
*/
//TODO make public when its functionality fully finalized and stable.
final class FileDescriptors {
private FileDescriptors() {}
public static Builder newBuilder() {
return new Builder();
}
public static Builder of(final Builder builder) {
return new Builder(builder);
}
final static class Builder {
private boolean protocCompatible;
private boolean customOptionsAsExtensions;
private ExtensionRegistry registry;
private FileDescriptor fileDescriptor;
private FileDescriptorProto proto;
private final List dependencies;
private boolean isBuilt;
private Builder() {
protocCompatible = false;
customOptionsAsExtensions = true;
registry = ExtensionRegistry.getEmptyRegistry();
fileDescriptor = null;
proto = null;
dependencies = new ArrayList();
isBuilt = false;
}
private Builder(final boolean protocCompatible, final boolean customOptionsAsExtensions,
final ExtensionRegistry registry, final FileDescriptor fileDescriptor,
final FileDescriptorProto proto, final List dependencies) {
this.protocCompatible = protocCompatible;
this.customOptionsAsExtensions = customOptionsAsExtensions;
this.registry = Objects.requireNonNull(registry).getUnmodifiable();
this.fileDescriptor = fileDescriptor;
this.proto = proto;
this.dependencies = new ArrayList(Objects.requireNonNull(dependencies));
isBuilt = false;
}
private Builder(final Builder other) {
protocCompatible = other.protocCompatible;
customOptionsAsExtensions = other.customOptionsAsExtensions;
registry = other.registry;
fileDescriptor = other.fileDescriptor;
proto = other.proto;
dependencies = new ArrayList(other.dependencies);
isBuilt = false;
}
protected boolean isBuilt() {
return isBuilt;
}
public boolean isProtocCompatible() {
return protocCompatible;
}
public Builder setProtocCompatible(final boolean protocCompatible) {
if (this.protocCompatible != Objects.requireNonNull(protocCompatible)) {
isBuilt = false;
this.protocCompatible = protocCompatible;
}
return this;
}
public boolean isCustomOptionsAsExtensions() {
return customOptionsAsExtensions;
}
public Builder setCustomOptionsAsExtensions(final boolean customOptionsAsExtensions) {
if (this.customOptionsAsExtensions != Objects.requireNonNull(customOptionsAsExtensions)) {
isBuilt = false;
this.customOptionsAsExtensions = customOptionsAsExtensions;
}
return this;
}
public ExtensionRegistry getRegistry() {
return registry;
}
public Builder clearRegistry() {
return setRegistry(ExtensionRegistry.getEmptyRegistry());
}
public Builder setRegistry(final ExtensionRegistry registry) {
if (this.registry != Objects.requireNonNull(registry)) {
isBuilt = false;
this.registry = registry;
}
return this;
}
public FileDescriptor getFileDescriptor() {
return fileDescriptor;
}
public Builder setFileDescriptor(final FileDescriptor fileDescriptor) {
if (this.fileDescriptor != Objects.requireNonNull(fileDescriptor)) {
isBuilt = false;
this.fileDescriptor = fileDescriptor;
proto = null;
dependencies.clear();
}
return this;
}
public FileDescriptorProto getProto() {
return proto;
}
public Builder setProto(final FileDescriptorProtoOrBuilder proto) {
if (this.proto != Objects.requireNonNull(proto)) {
isBuilt = false;
if (proto instanceof FileDescriptorProto.Builder) {
this.proto = ((FileDescriptorProto.Builder) proto).build();
} else {
this.proto = (FileDescriptorProto) proto;
}
fileDescriptor = null;
}
return this;
}
public List getDependencies() {
return Collections.unmodifiableList(dependencies);
}
public Builder clearDependencies() {
if (!dependencies.isEmpty()) {
isBuilt = false;
dependencies.clear();
}
return this;
}
public Builder addDependencies(final FileDescriptor... dependencies) {
if (Objects.requireNonNull(dependencies).length == 0) {
return this;
}
isBuilt = false;
for (final FileDescriptor dependency : dependencies) {
this.dependencies.add(Objects.requireNonNull(dependency));
}
return this;
}
public FileDescriptor rebuild() {
isBuilt = false;
return build();
}
public FileDescriptor build() {
if (isBuilt) {
return fileDescriptor;
}
if (fileDescriptor != null) {
fileDescriptor = build(fileDescriptor);
} else {
Objects.requireNonNull(proto, "either proto or fileDescriptor must be set");
fileDescriptor = build(proto, dependencies.toArray(new FileDescriptor[0]));
}
if (registry != ExtensionRegistry.getEmptyRegistry()) {
FileDescriptor.internalUpdateFileDescriptor(fileDescriptor, registry);
} else if (customOptionsAsExtensions) {
final ExtensionRegistry registry = buildFullRegistryOf(fileDescriptor);
FileDescriptor.internalUpdateFileDescriptor(fileDescriptor, registry);
}
isBuilt = true;
return fileDescriptor;
}
private FileDescriptor build(final FileDescriptor fileDescriptor) {
try {
return FileDescriptor.buildFrom(makeCanonicalProto(fileDescriptor), fileDescriptor
.getDependencies().toArray(new FileDescriptor[0]));
} catch (final DescriptorValidationException e) {
throw new DescriptorValidationRuntimeException(e);
}
}
private FileDescriptor build(final FileDescriptorProto proto,
final FileDescriptor[] dependencies) {
try {
final FileDescriptor fileDescriptor = FileDescriptor.buildFrom(proto, dependencies);
return FileDescriptor.buildFrom(makeCanonicalProto(fileDescriptor), dependencies);
} catch (final DescriptorValidationException e) {
throw new DescriptorValidationRuntimeException(e);
}
}
private FileDescriptorProto makeCanonicalProto(final FileDescriptor fileDescriptor) {
final FileDescriptorProto.Builder protoBuilder =
FileDescriptorProto.newBuilder(fileDescriptor.toProto());
for (final FieldDescriptorProto.Builder field : protoBuilder.getExtensionBuilderList()) {
makeCanonicalField(field, fileDescriptor.findExtensionByName(field.getName()));
}
for (final DescriptorProto.Builder message : protoBuilder.getMessageTypeBuilderList()) {
makeCanonicalMessage(message, fileDescriptor.findMessageTypeByName(message.getName()));
}
// for (EnumDescriptorProto.Builder enumProto :
// protoBuilder.getEnumTypeBuilderList()) {
// makeCanonicalEnum(enumProto,
// fileDescriptor.findEnumTypeByName(enumProto.getName()));
// }
for (final ServiceDescriptorProto.Builder serviceProto : protoBuilder.getServiceBuilderList()) {
makeCanonicalService(serviceProto, fileDescriptor.findServiceByName(serviceProto.getName()));
}
// TODO: incorporate options' tree walking into canonicalization to eliminate double walking
return OptionResolver.newBuilder().setCustomOptionsAsExtensions(false)
.resolveAllOptionsFor(fileDescriptor, 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()) {
try {
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;
}
}
}