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.lang.reflect.Array;
008import java.util.function.BiConsumer;
009import java.util.function.Predicate;
010import us.hebi.quickbuf.Descriptors.Descriptor;
011import us.hebi.quickbuf.Descriptors.FileDescriptor;
012import us.hebi.quickbuf.ProtoMessage;
013import us.hebi.quickbuf.RepeatedDouble;
014import us.hebi.quickbuf.RepeatedMessage;
015
016/**
017 * Interface for Protobuf serialization.
018 *
019 * <p>This is designed for serialization of more complex data structures including forward/backwards
020 * compatibility and repeated/nested/variable length members, etc. Serialization and deserialization
021 * code is auto-generated from .proto interface descriptions (the MessageType generic parameter).
022 *
023 * <p>Idiomatically, classes that support protobuf serialization should provide a static final
024 * member named "proto" that provides an instance of an implementation of this interface, or a
025 * static final method named "getProto" if the class is generic.
026 *
027 * @param <T> object type
028 * @param <MessageType> protobuf message type
029 */
030public interface Protobuf<T, MessageType extends ProtoMessage<?>> {
031  /**
032   * Gets the Class object for the stored value.
033   *
034   * @return Class
035   */
036  Class<T> getTypeClass();
037
038  /**
039   * Gets the type string (e.g. for NetworkTables). This should be globally unique and start with
040   * "proto:".
041   *
042   * @return type string
043   */
044  default String getTypeString() {
045    return "proto:" + getDescriptor().getFullName();
046  }
047
048  /**
049   * Gets the protobuf descriptor.
050   *
051   * @return descriptor
052   */
053  Descriptor getDescriptor();
054
055  /**
056   * Creates protobuf message.
057   *
058   * @return protobuf message
059   */
060  MessageType createMessage();
061
062  /**
063   * Deserializes an object from a protobuf message.
064   *
065   * @param msg protobuf message
066   * @return New object
067   */
068  T unpack(MessageType msg);
069
070  /**
071   * Copies the object contents into a protobuf message. Implementations should call either
072   * msg.setMember(member) or member.copyToProto(msg.getMutableMember()) for each member.
073   *
074   * @param msg protobuf message
075   * @param value object to serialize
076   */
077  void pack(MessageType msg, T value);
078
079  /**
080   * Updates the object contents from a protobuf message. Implementations should call
081   * msg.getMember(member), MemberClass.makeFromProto(msg.getMember()), or
082   * member.updateFromProto(msg.getMember()) for each member.
083   *
084   * <p>Immutable classes cannot and should not implement this function. The default implementation
085   * throws UnsupportedOperationException.
086   *
087   * @param out object to update
088   * @param msg protobuf message
089   * @throws UnsupportedOperationException if the object is immutable
090   */
091  default void unpackInto(T out, MessageType msg) {
092    throw new UnsupportedOperationException("object does not support unpackInto");
093  }
094
095  /**
096   * Returns whether or not objects are immutable. Immutable objects must also be comparable using
097   * the equals() method. Default implementation returns false.
098   *
099   * @return True if object is immutable
100   */
101  default boolean isImmutable() {
102    return false;
103  }
104
105  /**
106   * Returns whether or not objects are cloneable using the clone() method. Cloneable objects must
107   * also be comparable using the equals() method. Default implementation returns false.
108   *
109   * @return True if object is cloneable
110   */
111  default boolean isCloneable() {
112    return false;
113  }
114
115  /**
116   * Creates a (deep) clone of the object. May also return the object directly if the object is
117   * immutable. Default implementation throws CloneNotSupportedException. Typically this should be
118   * implemented by implementing clone() on the object itself, and calling it from here.
119   *
120   * @param obj object to clone
121   * @return Clone of object (if immutable, may be same object)
122   * @throws CloneNotSupportedException if clone not supported
123   */
124  default T clone(T obj) throws CloneNotSupportedException {
125    throw new CloneNotSupportedException();
126  }
127
128  /**
129   * Loops over all protobuf descriptors including nested/referenced descriptors.
130   *
131   * @param exists function that returns false if fn should be called for the given type string
132   * @param fn function to call for each descriptor
133   */
134  default void forEachDescriptor(Predicate<String> exists, BiConsumer<String, byte[]> fn) {
135    forEachDescriptorImpl(getDescriptor().getFile(), exists, fn);
136  }
137
138  private static void forEachDescriptorImpl(
139      FileDescriptor desc, Predicate<String> exists, BiConsumer<String, byte[]> fn) {
140    String name = "proto:" + desc.getFullName();
141    if (exists.test(name)) {
142      return;
143    }
144    for (FileDescriptor dep : desc.getDependencies()) {
145      forEachDescriptorImpl(dep, exists, fn);
146    }
147    fn.accept(name, desc.toProtoBytes());
148  }
149
150  /**
151   * Unpack a serialized protobuf array message.
152   *
153   * @param <T> object type
154   * @param <MessageType> element type of the protobuf array
155   * @param msg protobuf array message
156   * @param proto protobuf implementation
157   * @return Deserialized array
158   */
159  static <T, MessageType extends ProtoMessage<MessageType>> T[] unpackArray(
160      RepeatedMessage<MessageType> msg, Protobuf<T, MessageType> proto) {
161    @SuppressWarnings("unchecked")
162    T[] result = (T[]) Array.newInstance(proto.getTypeClass(), msg.length());
163    for (int i = 0; i < result.length; i++) {
164      result[i] = proto.unpack(msg.get(i));
165    }
166    return result;
167  }
168
169  /**
170   * Unpack a serialized protobuf double array message.
171   *
172   * @param msg protobuf double array message
173   * @return Deserialized array
174   */
175  static double[] unpackArray(RepeatedDouble msg) {
176    double[] result = new double[msg.length()];
177    for (int i = 0; i < result.length; i++) {
178      result[i] = msg.get(i);
179    }
180    return result;
181  }
182
183  /**
184   * Pack a serialized protobuf array message.
185   *
186   * @param <T> object type
187   * @param <MessageType> element type of the protobuf array
188   * @param msg protobuf array message
189   * @param arr array of objects
190   * @param proto protobuf implementation
191   */
192  static <T, MessageType extends ProtoMessage<MessageType>> void packArray(
193      RepeatedMessage<MessageType> msg, T[] arr, Protobuf<T, MessageType> proto) {
194    msg.clear();
195    msg.reserve(arr.length);
196    for (var obj : arr) {
197      proto.pack(msg.next(), obj);
198    }
199  }
200
201  /**
202   * Pack a serialized protobuf double array message.
203   *
204   * @param msg protobuf double array message
205   * @param arr array of objects
206   */
207  static void packArray(RepeatedDouble msg, double[] arr) {
208    msg.clear();
209    msg.reserve(arr.length);
210    msg.addAll(arr);
211  }
212}