;;; Copyright 2012-2020 Google LLC ;;; ;;; Use of this source code is governed by an MIT-style ;;; license that can be found in the LICENSE file or at ;;; https://opensource.org/licenses/MIT. (in-package #:cl-protobufs.implementation) (defun object-initialized-p (object message) "Check if OBJECT with proto-message MESSAGE is initialized. The definition of initialized is all required-fields are set." (loop for field in (proto-fields message) when (eq (proto-label field) :required) do (when (= (bit (slot-value object '%%is-set) (proto-field-offset field)) 0) (return-from object-initialized-p nil)) when (and (member (proto-kind field) '(:message :group :extends)) (or (eq (proto-label field) :repeated) (= (bit (slot-value object '%%is-set) (proto-field-offset field)) 1))) do (let ((lisp-type (proto-class field)) (field-value (slot-value object (proto-internal-field-name field)))) (when (and (not (keywordp lisp-type)) (find-message-descriptor lisp-type)) (doseq (msg (if (eq (proto-label field) :repeated) field-value (list field-value))) (unless (object-initialized-p msg (find-message-descriptor lisp-type)) (return-from object-initialized-p nil)))))) t) (defun is-initialized (object) "Returns true if all of the fields of OBJECT are initialized." (let* ((class (type-of object)) (desc (find-message-descriptor class :error-p t))) (object-initialized-p object desc))) (defun map-field-equal (map-1 map-2 map-descriptor exact) "Returns true if two maps with the same map-descriptor are equal. Parameters: MAP-1: The first map to compare. MAP-2: The second map to compare. MAP-DESCRIPTOR: The map descriptor for the two maps. EXACT: If true consider the messages to be equal only if the same fields have been explicitly set." (and (= (hash-table-count map-1) (hash-table-count map-2)) (loop for key being the hash-keys of map-1 using (hash-value map-1-value) for map-2-value = (gethash key map-2) always (if (or (scalarp (proto-value-type map-descriptor)) (find-enum-descriptor (proto-value-type map-descriptor))) (scalar-field-equal map-1-value map-2-value) (proto-equal map-1-value map-2-value :exact exact))))) (defun oneof-field-equal (oneof-1 oneof-2 oneof-descriptor exact) "Returns true if two maps with the same map-descriptor are equal. Parameters: ONEOF-1: The first oneof to compare. ONEOF-2: The second oneof to compare. ONEOF-DESCRIPTOR: The oneof descriptor for the two oneofs. EXACT: If true consider the messages to be equal only if the same fields have been explicitly set." (let ((set-field-1 (oneof-set-field oneof-1)) (set-field-2 (oneof-set-field oneof-2))) ;; Check if one of the fields aren't set. (unless (and set-field-1 set-field-2) (return-from oneof-field-equal (not (or set-field-1 set-field-2)))) ;; Check the same field is set. (unless (eql (oneof-set-field oneof-1) (oneof-set-field oneof-2)) (return-from oneof-field-equal nil)) ;; Check for field equality. (let* ((lisp-type (proto-class (aref (oneof-descriptor-fields oneof-descriptor) set-field-1)))) (if (or (scalarp lisp-type) (find-enum-descriptor lisp-type)) (scalar-field-equal (oneof-value oneof-1) (oneof-value oneof-2)) (proto-equal (oneof-value oneof-1) (oneof-value oneof-2) :exact exact))))) (defun non-bool-field-equal (field-1 field-2 field-descriptor exact) "Returns true if two proto-fields which aren't bools or oneofs are equal. Parameters: FIELD-1: The first field to compare. FIELD-2: The second field to compare. FIELD-DESCRIPTOR: The field descriptor for the two fields. EXACT: If true consider the messages to be equal only if the same fields have been explicitly set." (declare (type field-descriptor field-descriptor)) (let ((lisp-type (proto-class field-descriptor))) (assert (not (eql lisp-type 'boolean))) (unless (and field-1 field-2) (return-from non-bool-field-equal (not (or field-1 field-2)))) (when (or (scalarp lisp-type) (find-enum-descriptor lisp-type)) (return-from non-bool-field-equal (scalar-field-equal field-1 field-2))) (when (eql (proto-kind field-descriptor) :map) (return-from non-bool-field-equal (map-field-equal field-1 field-2 (find-map-descriptor lisp-type) exact)))) (if (proto-container field-descriptor) (and (= (length field-1) (length field-2)) (every (lambda (x y) (proto-equal x y :exact exact)) field-1 field-2)) (proto-equal field-1 field-2 :exact exact))) (defun scalar-field-equal (object-1 object-2) "Check if two objects with scalar type are equal. Parameters: OBJECT-1: The first scalar object. OBJECT-2: The second scalar object." (typecase object-1 (string (string= object-1 object-2)) (byte-vector (equalp object-1 object-2)) ((or list vector) (and (= (length object-1) (length object-2)) (every #'scalar-field-equal object-1 object-2))) (t (eql object-1 object-2)))) (defun proto-equal (message-1 message-2 &key exact) "Check if MESSAGE-1 and MESSAGE-2 are the same. By default two messages are equal if calling the getter on each field would retrieve the same value. This means that a message with a field explicitly set to the default value is considered the same as a message with that field not set. If EXACT is true the messages are considered equal only if the same fields have been explicitly set." (let* ((class-1 (type-of message-1)) (desc (find-message-descriptor class-1))) (and ;; Check the messages are the same. desc (eq (type-of message-2) class-1) ;; Check same fields are set if exact is specified. (or (not exact) (equalp (slot-value message-1 '%%is-set) (slot-value message-2 '%%is-set))) ;; Bool values are stored in a vector. (or (not (slot-exists-p message-1 '%%bool-values)) (equalp (slot-value message-1 '%%bool-values) (slot-value message-2 '%%bool-values))) ;; oneofs (loop for oneof in (proto-oneofs desc) for slot-value-1 = (slot-value message-1 (oneof-descriptor-internal-name oneof)) for slot-value-2 = (slot-value message-2 (oneof-descriptor-internal-name oneof)) always (oneof-field-equal slot-value-1 slot-value-2 oneof exact)) ;; regular fields (loop for field in (proto-fields desc) for lisp-type = (proto-class field) for boolp = (eq lisp-type 'boolean) for slot-value-1 = (unless boolp (slot-value message-1 (proto-internal-field-name field))) for slot-value-2 = (unless boolp (slot-value message-2 (proto-internal-field-name field))) always (or boolp (non-bool-field-equal slot-value-1 slot-value-2 field exact)))))) (defgeneric clear (object) (:documentation "Initialize all fields of OBJECT to their default values.")) (defun-inline has-field (object field) "Check if OBJECT has FIELD set." (funcall (field-accessors-has (get field (type-of object))) object)) (defun-inline clear-field (object field) "Check if OBJECT has FIELD set." (funcall (field-accessors-clear (get field (type-of object))) object)) (defun-inline proto-slot-value (object slot) "Get the value of a field in a protobuf object. Parameters: OBJECT: The protobuf object. SLOT: The slot in object to retrieve the value from." (funcall (field-accessors-get (get slot (type-of object))) object)) (defun-inline (setf proto-slot-value) (value object slot) "Set the value of a field in a protobuf object. Parameters: VALUE: The new value for the field. OBJECT: The protobuf object. SLOT: The slot in object to retrieve the value from." (funcall (fdefinition (field-accessors-set (get slot (type-of object)))) value object)) (defgeneric encoded-field (object field-name) (:documentation "Returns the encoded value of the field FIELD-NAME, or signals protobuf-error if the field doesn't exist. For repeated fields, returns a list of the encoded values, which may contain NILs.") (:method ((object structure-object) slot) (let* ((class (type-of object)) (message (find-message-descriptor class :error-p t)) (field (find slot (proto-fields message) :key #'proto-external-field-name))) (unless field (let* ((lisp-package (or (symbol-package class) (protobuf-error "Lisp package not found for message ~A" (proto-name message)))) (lazy-slot (intern (nstring-upcase (format nil "%~A" slot)) lisp-package))) (setf field (%find-field-descriptor message lazy-slot)) (when field (setf slot lazy-slot)))) (unless field (protobuf-error "There is no protobuf field with the name ~S" slot)) (let ((value (slot-value object (proto-internal-field-name field)))) (if (eq (proto-label field) :repeated) (map 'list #'proto-%%bytes value) (proto-%%bytes value)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun merge-from (from-message to-message) "Merge messages. Taken from https://github.com/protocolbuffers/protobuf-go/blob/master/proto/merge.go: Populated scalar fields in FROM-MESSAGE are copied to TO-MESSAGE, while populated singular messages in FROM-MESSAGE are merged into TO-MESSAGE by recursively calling Merge. The elements of every list field in FROM-MESSAGE is appended to the corresponded list fields in TO-MESSAGE. The entries of every map field in FROM-MESSAGE is copied into the corresponding map field in TO-MESSAGE, possibly replacing existing entries." (labels ((create-message-of-same-type (message) (let ((class (find-class (type-of message)))) (funcall (get-constructor-name (class-name class))))) (copy-message (message) (let ((new-message (create-message-of-same-type message))) (merge-from message new-message) new-message)) (concatenate-repeated-field (from-field to-field field-container field-type field-kind) (if (eq field-container :vector) (let ((new-vector (make-array `(,(+ (length from-field) (length to-field))) :element-type field-type :adjustable t :fill-pointer (+ (length from-field) (length to-field))))) (loop for i from 0 for el across to-field do (setf (aref new-vector i) el)) (loop for i from (length to-field) for el across from-field do (setf (aref new-vector i) (if (member field-kind '(:message :group)) (copy-message el) el))) new-vector) (append to-field (mapcar (if (member field-kind '(:message :group)) #'copy-message #'identity) from-field))))) (let* ((class (type-of from-message)) (desc (find-message-descriptor class))) ;; Check the messages are the same. (and desc (eq (type-of to-message) class) (loop :for field-desc :in (proto-fields desc) :for field-name = (proto-external-field-name field-desc) :for from-field-value = (proto-slot-value from-message field-name) :when (has-field from-message field-name) :do (cond ((eq (proto-label field-desc) :repeated) (setf (proto-slot-value to-message field-name) (concatenate-repeated-field from-field-value (proto-slot-value to-message field-name) (proto-container field-desc) (proto-type field-desc) (proto-kind field-desc)))) ((member (proto-kind field-desc) '(:message :group)) (if (has-field to-message field-name) (merge-from from-field-value (proto-slot-value to-message field-name)) (setf (proto-slot-value to-message field-name) (copy-message from-field-value)))) ((eq (proto-kind field-desc) :map) (loop with map-descriptor = (find-map-descriptor (proto-class field-desc)) with to-hash-map = (proto-slot-value to-message field-name) for key being the hash-keys of from-field-value using (hash-value from-value) do (setf (gethash key to-hash-map) (if (eq (proto-value-kind map-descriptor) :message) (copy-message from-value) from-value)))) (t (setf (proto-slot-value to-message field-name) from-field-value))))))))