All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.protobufel.grammar.FileDescriptorEx Maven / Gradle / Ivy

There is a newer version: 0.7.1
Show newest version
//
// 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;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy