001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.util.protobuf;
006
007import java.util.function.BiConsumer;
008import java.util.function.Predicate;
009import us.hebi.quickbuf.Descriptors.Descriptor;
010import us.hebi.quickbuf.Descriptors.FileDescriptor;
011import us.hebi.quickbuf.ProtoMessage;
012
013/**
014 * Interface for Protobuf serialization.
015 *
016 * <p>This is designed for serialization of more complex data structures including forward/backwards
017 * compatibility and repeated/nested/variable length members, etc. Serialization and deserialization
018 * code is auto-generated from .proto interface descriptions (the MessageType generic parameter).
019 *
020 * <p>Idiomatically, classes that support protobuf serialization should provide a static final
021 * member named "proto" that provides an instance of an implementation of this interface.
022 *
023 * @param <T> object type
024 * @param <MessageType> protobuf message type
025 */
026public interface Protobuf<T, MessageType extends ProtoMessage<?>> {
027  /**
028   * Gets the Class object for the stored value.
029   *
030   * @return Class
031   */
032  Class<T> getTypeClass();
033
034  /**
035   * Gets the type string (e.g. for NetworkTables). This should be globally unique and start with
036   * "proto:".
037   *
038   * @return type string
039   */
040  default String getTypeString() {
041    return "proto:" + getDescriptor().getFullName();
042  }
043
044  /**
045   * Gets the protobuf descriptor.
046   *
047   * @return descriptor
048   */
049  Descriptor getDescriptor();
050
051  /**
052   * Gets the list of protobuf types referenced by this protobuf.
053   *
054   * @return list of protobuf types
055   */
056  default Protobuf<?, ?>[] getNested() {
057    return new Protobuf<?, ?>[] {};
058  }
059
060  /**
061   * Creates protobuf message.
062   *
063   * @return protobuf message
064   */
065  MessageType createMessage();
066
067  /**
068   * Deserializes an object from a protobuf message.
069   *
070   * @param msg protobuf message
071   * @return New object
072   */
073  T unpack(MessageType msg);
074
075  /**
076   * Copies the object contents into a protobuf message. Implementations should call either
077   * msg.setMember(member) or member.copyToProto(msg.getMutableMember()) for each member.
078   *
079   * @param msg protobuf message
080   * @param value object to serialize
081   */
082  void pack(MessageType msg, T value);
083
084  /**
085   * Updates the object contents from a protobuf message. Implementations should call
086   * msg.getMember(member), MemberClass.makeFromProto(msg.getMember()), or
087   * member.updateFromProto(msg.getMember()) for each member.
088   *
089   * <p>Immutable classes cannot and should not implement this function. The default implementation
090   * throws UnsupportedOperationException.
091   *
092   * @param out object to update
093   * @param msg protobuf message
094   * @throws UnsupportedOperationException if the object is immutable
095   */
096  default void unpackInto(T out, MessageType msg) {
097    throw new UnsupportedOperationException("object does not support unpackInto");
098  }
099
100  /**
101   * Loops over all protobuf descriptors including nested/referenced descriptors.
102   *
103   * @param exists function that returns false if fn should be called for the given type string
104   * @param fn function to call for each descriptor
105   */
106  default void forEachDescriptor(Predicate<String> exists, BiConsumer<String, byte[]> fn) {
107    forEachDescriptorImpl(getDescriptor().getFile(), exists, fn);
108  }
109
110  private static void forEachDescriptorImpl(
111      FileDescriptor desc, Predicate<String> exists, BiConsumer<String, byte[]> fn) {
112    String name = "proto:" + desc.getFullName();
113    if (exists.test(name)) {
114      return;
115    }
116    for (FileDescriptor dep : desc.getDependencies()) {
117      forEachDescriptorImpl(dep, exists, fn);
118    }
119    fn.accept(name, desc.toProtoBytes());
120  }
121}