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}