Hazelcast C++ Client
Hazelcast C++ Client Library
serialization.cpp
1 /*
2  * Copyright (c) 2008-2022, Hazelcast, Inc. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <boost/concept_check.hpp>
18 #include <utility>
19 
20 #include "hazelcast/client/serialization/serialization.h"
21 #include "hazelcast/client/hazelcast_json_value.h"
22 #include "hazelcast/client/serialization/serialization.h"
23 #include "hazelcast/util/Util.h"
24 #include "hazelcast/util/IOUtil.h"
25 #include "hazelcast/util/Bits.h"
26 #include "hazelcast/util/MurmurHash3.h"
27 #include "hazelcast/client/spi/ClientContext.h"
28 #include "hazelcast/client/client_config.h"
29 
30 namespace hazelcast {
31 namespace client {
33  : json_string_(std::move(json_string))
34 {}
35 
36 hazelcast_json_value::~hazelcast_json_value() = default;
37 
38 const std::string&
40 {
41  return json_string_;
42 }
43 
44 bool
45 hazelcast_json_value::operator==(const hazelcast_json_value& rhs) const
46 {
47  return json_string_ == rhs.json_string_;
48 }
49 
50 bool
51 hazelcast_json_value::operator!=(const hazelcast_json_value& rhs) const
52 {
53  return !(rhs == *this);
54 }
55 
56 bool
57 hazelcast_json_value::operator<(const hazelcast_json_value& rhs) const
58 {
59  return json_string_ < rhs.json_string_;
60 }
61 
62 std::ostream&
63 operator<<(std::ostream& os, const hazelcast_json_value& value)
64 {
65  os << "jsonString: " << value.json_string_;
66  return os;
67 }
68 
69 typed_data::typed_data()
70  : ss_(nullptr)
71 {}
72 
73 typed_data::typed_data(
74  serialization::pimpl::data d,
75  serialization::pimpl::SerializationService& serialization_service)
76  : data_(std::move(d))
77  , ss_(&serialization_service)
78 {}
79 
80 serialization::pimpl::object_type
82 {
83  return ss_->get_object_type(&data_);
84 }
85 
86 const serialization::pimpl::data&
88 {
89  return data_;
90 }
91 
92 bool
93 operator<(const typed_data& lhs, const typed_data& rhs)
94 {
95  const auto& lhs_data = lhs.get_data();
96  const auto& rhs_data = rhs.get_data();
97 
98  return lhs_data < rhs_data;
99 }
100 
101 namespace serialization {
103  pimpl::DefaultPortableWriter* default_portable_writer)
104  : default_portable_writer_(default_portable_writer)
105  , class_definition_writer_(nullptr)
106  , is_default_writer_(true)
107 {}
108 
110  pimpl::ClassDefinitionWriter* class_definition_writer)
111  : default_portable_writer_(nullptr)
112  , class_definition_writer_(class_definition_writer)
113  , is_default_writer_(false)
114 {}
115 
116 void
118 {
119  if (is_default_writer_)
120  return default_portable_writer_->end();
121  return class_definition_writer_->end();
122 }
123 
126 {
127  if (is_default_writer_)
128  return default_portable_writer_->get_raw_data_output();
129  return class_definition_writer_->get_raw_data_output();
130 }
131 
132 ClassDefinitionBuilder::ClassDefinitionBuilder(int factory_id,
133  int class_id,
134  int version)
135  : factory_id_(factory_id)
136  , class_id_(class_id)
137  , version_(version)
138  , index_(0)
139  , done_(false)
140 {}
141 
142 ClassDefinitionBuilder&
143 ClassDefinitionBuilder::add_portable_field(const std::string& field_name,
144  std::shared_ptr<ClassDefinition> def)
145 {
146  check();
147  if (def->get_class_id() == 0) {
148  BOOST_THROW_EXCEPTION(exception::illegal_argument(
149  "ClassDefinitionBuilder::addPortableField",
150  "Portable class id cannot be zero!"));
151  }
152  FieldDefinition fieldDefinition(index_++,
153  field_name,
154  field_type::TYPE_PORTABLE,
155  def->get_factory_id(),
156  def->get_class_id(),
157  def->get_version());
158  field_definitions_.push_back(fieldDefinition);
159  return *this;
160 }
161 
162 ClassDefinitionBuilder&
163 ClassDefinitionBuilder::add_portable_array_field(
164  const std::string& field_name,
165  std::shared_ptr<ClassDefinition> def)
166 {
167  check();
168  if (def->get_class_id() == 0) {
169  BOOST_THROW_EXCEPTION(exception::illegal_argument(
170  "ClassDefinitionBuilder::addPortableField",
171  "Portable class id cannot be zero!"));
172  }
173  FieldDefinition fieldDefinition(index_++,
174  field_name,
175  field_type::TYPE_PORTABLE_ARRAY,
176  def->get_factory_id(),
177  def->get_class_id(),
178  def->get_version());
179  field_definitions_.push_back(fieldDefinition);
180  return *this;
181 }
182 
183 ClassDefinitionBuilder&
184 ClassDefinitionBuilder::add_field(FieldDefinition& field_definition)
185 {
186  check();
187  int defIndex = field_definition.get_index();
188  if (index_ != defIndex) {
189  char buf[100];
190  util::hz_snprintf(buf,
191  100,
192  "Invalid field index. Index in definition:%d, being "
193  "added at index:%d",
194  defIndex,
195  index_);
196  BOOST_THROW_EXCEPTION(
197  exception::illegal_argument("ClassDefinitionBuilder::addField", buf));
198  }
199  index_++;
200  field_definitions_.push_back(field_definition);
201  return *this;
202 }
203 
204 std::shared_ptr<ClassDefinition>
205 ClassDefinitionBuilder::build()
206 {
207  done_ = true;
208  std::shared_ptr<ClassDefinition> cd(
209  new ClassDefinition(factory_id_, class_id_, version_));
210 
211  std::vector<FieldDefinition>::iterator fdIt;
212  for (fdIt = field_definitions_.begin(); fdIt != field_definitions_.end();
213  fdIt++) {
214  cd->add_field_def(*fdIt);
215  }
216  return cd;
217 }
218 
219 void
220 ClassDefinitionBuilder::check()
221 {
222  if (done_) {
223  BOOST_THROW_EXCEPTION(exception::hazelcast_serialization(
224  "ClassDefinitionBuilder::check",
225  "ClassDefinition is already built for " +
226  util::IOUtil::to_string(class_id_)));
227  }
228 }
229 
230 void
231 ClassDefinitionBuilder::add_field(const std::string& field_name,
232  field_type const& field_type)
233 {
234  check();
235  FieldDefinition fieldDefinition(index_++, field_name, field_type, version_);
236  field_definitions_.push_back(fieldDefinition);
237 }
238 
239 int
240 ClassDefinitionBuilder::get_factory_id()
241 {
242  return factory_id_;
243 }
244 
245 int
246 ClassDefinitionBuilder::get_class_id()
247 {
248  return class_id_;
249 }
250 
251 int
252 ClassDefinitionBuilder::get_version()
253 {
254  return version_;
255 }
256 
258  : index_(0)
259  , class_id_(0)
260  , factory_id_(0)
261  , version_(-1)
262 {}
263 
265  const std::string& field_name,
266  field_type const& type,
267  int version)
268  : index_(index)
269  , field_name_(field_name)
270  , type_(type)
271  , class_id_(0)
272  , factory_id_(0)
273  , version_(version)
274 {}
275 
277  const std::string& field_name,
278  field_type const& type,
279  int factory_id,
280  int class_id,
281  int version)
282  : index_(index)
283  , field_name_(field_name)
284  , type_(type)
285  , class_id_(class_id)
286  , factory_id_(factory_id)
287  , version_(version)
288 {}
289 
290 const field_type&
292 {
293  return type_;
294 }
295 
296 std::string
298 {
299  return field_name_;
300 }
301 
302 int
304 {
305  return index_;
306 }
307 
308 int
310 {
311  return factory_id_;
312 }
313 
314 int
316 {
317  return class_id_;
318 }
319 
320 void
321 FieldDefinition::write_data(pimpl::data_output& data_output)
322 {
323  data_output.write<int32_t>(index_);
324  data_output.write<std::string>(field_name_);
325  data_output.write<byte>(static_cast<int32_t>(type_));
326  data_output.write<int32_t>(factory_id_);
327  data_output.write<int32_t>(class_id_);
328 }
329 
330 void
332 {
333  index_ = data_input.read<int32_t>();
334  field_name_ = data_input.read<std::string>();
335  type_ = static_cast<field_type>(data_input.read<byte>());
336  factory_id_ = data_input.read<int32_t>();
337  class_id_ = data_input.read<int32_t>();
338 }
339 
340 bool
341 FieldDefinition::operator==(const FieldDefinition& rhs) const
342 {
343  return field_name_ == rhs.field_name_ && type_ == rhs.type_ &&
344  class_id_ == rhs.class_id_ && factory_id_ == rhs.factory_id_ &&
345  version_ == rhs.version_;
346 }
347 
348 bool
349 FieldDefinition::operator!=(const FieldDefinition& rhs) const
350 {
351  return !(rhs == *this);
352 }
353 
354 std::ostream&
355 operator<<(std::ostream& os, const FieldDefinition& definition)
356 {
357  os << "FieldDefinition{"
358  << "index: " << definition.index_
359  << " fieldName: " << definition.field_name_
360  << " type: " << static_cast<int32_t>(definition.type_)
361  << " classId: " << definition.class_id_
362  << " factoryId: " << definition.factory_id_
363  << " version: " << definition.version_;
364  return os;
365 }
366 
368  boost::endian::order byte_order,
369  const std::vector<byte>& buffer,
370  int offset,
371  pimpl::PortableSerializer& portable_ser,
372  pimpl::compact_stream_serializer& compact_ser,
373  pimpl::DataSerializer& data_ser,
374  std::shared_ptr<serialization::global_serializer> global_serializer)
375  : pimpl::data_input<std::vector<byte>>(byte_order, buffer, offset)
376  , portable_serializer_(portable_ser)
377  , compact_serializer_(compact_ser)
378  , data_serializer_(data_ser)
379  , global_serializer_(std::move(global_serializer))
380 {}
381 
383  boost::endian::order byte_order,
384  bool dont_write,
385  pimpl::PortableSerializer* portable_ser,
386  pimpl::compact_stream_serializer* compact_ser,
387  std::shared_ptr<serialization::global_serializer> global_serializer)
388  : data_output(byte_order, dont_write)
389  , portable_serializer_(portable_ser)
390  , compact_serializer_(compact_ser)
391  , global_serializer_(std::move(global_serializer))
392 {}
393 
394 portable_reader::portable_reader(pimpl::PortableSerializer& portable_ser,
395  object_data_input& input,
396  const std::shared_ptr<ClassDefinition>& cd,
397  bool is_default_reader)
398  : is_default_reader_(is_default_reader)
399 {
400  if (is_default_reader) {
401  default_portable_reader_ = boost::make_optional(
402  pimpl::DefaultPortableReader(portable_ser, input, cd));
403  } else {
404  morphing_portable_reader_ = boost::make_optional(
405  pimpl::MorphingPortableReader(portable_ser, input, cd));
406  }
407 }
408 
409 object_data_input&
411 {
412  if (is_default_reader_)
413  return default_portable_reader_->get_raw_data_input();
414  return morphing_portable_reader_->get_raw_data_input();
415 }
416 
417 template<>
418 void
419 object_data_output::write_object(const char* object)
420 {
421  if (!object) {
422  write<int32_t>(static_cast<int32_t>(
423  pimpl::serialization_constants::CONSTANT_TYPE_NULL));
424  return;
425  }
426  write_object<std::string>(std::string(object));
427 }
428 
429 void
431 {
432  if (is_default_reader_)
433  return default_portable_reader_->end();
434  return morphing_portable_reader_->end();
435 }
436 
438  : factory_id_(0)
439  , class_id_(0)
440  , version_(-1)
441  , binary_(new std::vector<byte>)
442 {}
443 
444 ClassDefinition::ClassDefinition(int factory_id, int class_id, int version)
445  : factory_id_(factory_id)
446  , class_id_(class_id)
447  , version_(version)
448  , binary_(new std::vector<byte>)
449 {}
450 
451 void
453 {
454  field_definitions_map_[fd.get_name()] = fd;
455 }
456 
457 const FieldDefinition&
458 ClassDefinition::get_field(const std::string& name) const
459 {
460  auto it = field_definitions_map_.find(name);
461  if (it != field_definitions_map_.end()) {
462  return it->second;
463  }
464  BOOST_THROW_EXCEPTION(exception::hazelcast_serialization(
465  "ClassDefinition::getField",
466  (boost::format("Invalid field name: '%1%' for ClassDefinition {id: %2%, "
467  "version: %3%}") %
468  name % class_id_ % version_)
469  .str()));
470 }
471 
472 bool
473 ClassDefinition::has_field(const std::string& field_name) const
474 {
475  return field_definitions_map_.find(field_name) !=
476  field_definitions_map_.end();
477 }
478 
479 field_type
480 ClassDefinition::get_field_type(const std::string& field_name) const
481 {
482  FieldDefinition const& fd = get_field(field_name);
483  return fd.get_type();
484 }
485 
486 int
488 {
489  return (int)field_definitions_map_.size();
490 }
491 
492 int
494 {
495  return factory_id_;
496 }
497 
498 int
500 {
501  return class_id_;
502 }
503 
504 int
506 {
507  return version_;
508 }
509 
510 void
512 {
513  if (get_version() < 0) {
514  this->version_ = new_version;
515  }
516 }
517 
518 void
519 ClassDefinition::write_data(pimpl::data_output& data_output)
520 {
521  data_output.write<int32_t>(factory_id_);
522  data_output.write<int32_t>(class_id_);
523  data_output.write<int32_t>(version_);
524  data_output.write<int16_t>(field_definitions_map_.size());
525  for (auto& entry : field_definitions_map_) {
526  entry.second.write_data(data_output);
527  }
528 }
529 
530 void
532 {
533  factory_id_ = data_input.read<int32_t>();
534  class_id_ = data_input.read<int32_t>();
535  version_ = data_input.read<int32_t>();
536  int size = data_input.read<int16_t>();
537  for (int i = 0; i < size; i++) {
538  FieldDefinition fieldDefinition;
539  fieldDefinition.read_data(data_input);
540  add_field_def(fieldDefinition);
541  }
542 }
543 
544 bool
545 ClassDefinition::operator==(const ClassDefinition& rhs) const
546 {
547  return factory_id_ == rhs.factory_id_ && class_id_ == rhs.class_id_ &&
548  version_ == rhs.version_ &&
549  field_definitions_map_ == rhs.field_definitions_map_;
550 }
551 
552 bool
553 ClassDefinition::operator!=(const ClassDefinition& rhs) const
554 {
555  return !(rhs == *this);
556 }
557 
558 std::ostream&
559 operator<<(std::ostream& os, const ClassDefinition& definition)
560 {
561  os << "ClassDefinition{"
562  << "factoryId: " << definition.factory_id_
563  << " classId: " << definition.class_id_
564  << " version: " << definition.version_ << " fieldDefinitions: {";
565 
566  for (auto& entry : definition.field_definitions_map_) {
567  os << entry.second;
568  }
569  os << "} }";
570  return os;
571 }
572 
573 namespace pimpl {
574 ClassDefinitionWriter::ClassDefinitionWriter(PortableContext& portable_context,
575  ClassDefinitionBuilder& builder)
576  : builder_(builder)
577  , context_(portable_context)
578  , empty_data_output_(
579  portable_context.get_serialization_config().get_byte_order(),
580  true)
581 {}
582 
583 std::shared_ptr<ClassDefinition>
584 ClassDefinitionWriter::register_and_get()
585 {
586  std::shared_ptr<ClassDefinition> cd = builder_.build();
587  return context_.register_class_definition(cd);
588 }
589 
590 object_data_output&
591 ClassDefinitionWriter::get_raw_data_output()
592 {
593  return empty_data_output_;
594 }
595 
596 void
597 ClassDefinitionWriter::end()
598 {}
599 
600 data_output::data_output(boost::endian::order byte_order, bool dont_write)
601  : byte_order_(byte_order)
602  , is_no_write_(dont_write)
603 {
604  if (is_no_write_) {
605  output_stream_.reserve(0);
606  } else {
607  output_stream_.reserve(DEFAULT_SIZE);
608  }
609 }
610 
611 template<>
612 void
613 data_output::write(byte i)
614 {
615  if (is_no_write_) {
616  return;
617  }
618  output_stream_.push_back(i);
619 }
620 
621 template<>
622 void
623 data_output::write(char i)
624 {
625  // C++ `char` is one byte only, `char16_t` is two bytes
626  write<int16_t>(i);
627 }
628 
629 template<>
630 void
631 data_output::write(char16_t i)
632 {
633  write<int16_t>(i);
634 }
635 
636 template<>
637 void
638 data_output::write(int16_t value)
639 {
640  if (is_no_write_) {
641  return;
642  }
643  if (byte_order_ == boost::endian::order::big) {
644  boost::endian::native_to_big_inplace(value);
645  } else {
646  boost::endian::native_to_little_inplace(value);
647  }
648  output_stream_.insert(output_stream_.end(),
649  (byte*)&value,
650  (byte*)&value + util::Bits::SHORT_SIZE_IN_BYTES);
651 }
652 
653 void
654 data_output::write(int32_t value, boost::endian::order byte_order)
655 {
656  if (is_no_write_) {
657  return;
658  }
659  if (byte_order == boost::endian::order::big) {
660  boost::endian::native_to_big_inplace(value);
661  } else {
662  boost::endian::native_to_little_inplace(value);
663  }
664  output_stream_.insert(output_stream_.end(),
665  (byte*)&value,
666  (byte*)&value + util::Bits::INT_SIZE_IN_BYTES);
667 }
668 
669 template<>
670 void
671 data_output::write(int32_t value)
672 {
673  write(value, byte_order_);
674 }
675 
676 template<>
677 void
678 data_output::write(int64_t value)
679 {
680  if (is_no_write_) {
681  return;
682  }
683  if (byte_order_ == boost::endian::order::big) {
684  boost::endian::native_to_big_inplace(value);
685  } else {
686  boost::endian::native_to_little_inplace(value);
687  }
688  output_stream_.insert(output_stream_.end(),
689  (byte*)&value,
690  (byte*)&value + util::Bits::LONG_SIZE_IN_BYTES);
691 }
692 
693 template<>
694 void
695 data_output::write(float x)
696 {
697  if (is_no_write_) {
698  return;
699  }
700  union
701  {
702  float f;
703  int32_t i;
704  } u;
705  u.f = x;
706  write<int32_t>(u.i);
707 }
708 
709 template<>
710 void
711 data_output::write(double v)
712 {
713  if (is_no_write_) {
714  return;
715  }
716  union
717  {
718  double d;
719  int64_t l;
720  } u;
721  u.d = v;
722  write<int64_t>(u.l);
723 }
724 
725 template<>
726 void
727 data_output::write(boost::uuids::uuid v)
728 {
729  if (is_no_write_) {
730  return;
731  }
732  if (byte_order_ == boost::endian::order::little) {
733  boost::endian::endian_reverse_inplace<int64_t>(
734  *reinterpret_cast<int64_t*>(v.data));
735  boost::endian::endian_reverse_inplace<int64_t>(
736  *reinterpret_cast<int64_t*>(&v.data[util::Bits::LONG_SIZE_IN_BYTES]));
737  }
738  output_stream_.insert(
739  output_stream_.end(), v.data, v.data + util::Bits::UUID_SIZE_IN_BYTES);
740 }
741 
742 template<>
743 void
744 data_output::write(bool value)
745 {
746  if (is_no_write_) {
747  return;
748  }
749  write<byte>(value);
750 }
751 
752 template<>
753 void
754 data_output::write(int8_t value)
755 {
756  if (is_no_write_) {
757  return;
758  }
759  write<byte>(value);
760 }
761 
762 template<>
763 void
764 data_output::write(const std::string& str)
765 {
766  if (is_no_write_) {
767  return;
768  }
769 
770  write<int32_t>(str.size());
771  output_stream_.insert(output_stream_.end(), str.begin(), str.end());
772 }
773 
774 template<>
775 void
776 data_output::write(const hazelcast_json_value& value)
777 {
778  if (is_no_write_) {
779  return;
780  }
781  write<std::string>(value.to_string());
782 }
783 
784 void
785 data_output::check_available(size_t index, int requested_length)
786 {
787  if (index < 0) {
788  BOOST_THROW_EXCEPTION(exception::illegal_argument(
789  "DataOutput::checkAvailable",
790  (boost::format("Negative pos! -> %1%") % index).str()));
791  }
792 
793  size_t available = output_stream_.size() - index;
794 
795  if (requested_length > (int)available) {
796  BOOST_THROW_EXCEPTION(exception::illegal_argument(
797  "DataOutput::checkAvailable",
798  (boost::format("Cannot write %1% bytes!") % requested_length).str()));
799  }
800 }
801 
802 void
803 data_output::write_boolean_bit_at(size_t index,
804  size_t offset_in_bits,
805  bool value)
806 {
807  if (is_no_write_) {
808  return;
809  }
810  check_available(index, 1);
811  byte b = output_stream_[index];
812  if (value) {
813  b = (byte)(b | (1 << offset_in_bits));
814  } else {
815  b = (byte)(b & ~(1 << offset_in_bits));
816  }
817  output_stream_[index] = b;
818 }
819 
820 object_type::object_type()
821  : type_id(serialization_constants::CONSTANT_TYPE_NULL)
822  , factory_id(-1)
823  , class_id(-1)
824 {}
825 
826 std::ostream&
827 operator<<(std::ostream& os, const object_type& type)
828 {
829  os << "typeId: " << static_cast<int32_t>(type.type_id)
830  << " factoryId: " << type.factory_id << " classId: " << type.class_id;
831  return os;
832 }
833 
834 int32_t
835 DataSerializer::read_int(object_data_input& in) const
836 {
837  return in.read<int32_t>();
838 }
839 
840 PortableContext::PortableContext(const serialization_config& serialization_conf)
841  : serialization_config_(serialization_conf)
842 {}
843 
844 int
845 PortableContext::get_class_version(int factory_id, int class_id)
846 {
847  return get_class_definition_context(factory_id).get_class_version(class_id);
848 }
849 
850 void
851 PortableContext::set_class_version(int factory_id, int class_id, int version)
852 {
853  get_class_definition_context(factory_id)
854  .set_class_version(class_id, version);
855 }
856 
857 std::shared_ptr<ClassDefinition>
858 PortableContext::lookup_class_definition(int factory_id,
859  int class_id,
860  int version)
861 {
862  return get_class_definition_context(factory_id).lookup(class_id, version);
863 }
864 
865 std::shared_ptr<ClassDefinition>
866 PortableContext::read_class_definition(object_data_input& in,
867  int factory_id,
868  int class_id,
869  int version)
870 {
871  bool shouldRegister = true;
872  ClassDefinitionBuilder builder(factory_id, class_id, version);
873 
874  // final position after portable is read
875  in.read<int32_t>();
876 
877  // field count
878  int fieldCount = in.read<int32_t>();
879  int offset = in.position();
880  for (int i = 0; i < fieldCount; i++) {
881  in.position(offset + i * util::Bits::INT_SIZE_IN_BYTES);
882  int pos = in.read<int32_t>();
883  in.position(pos);
884 
885  short len = in.read<int16_t>();
886  std::vector<byte> chars(len);
887  in.read_fully(chars);
888  chars.push_back('\0');
889 
890  field_type type(static_cast<field_type>(in.read<byte>()));
891  std::string name((char*)&(chars[0]));
892  int fieldFactoryId = 0;
893  int fieldClassId = 0;
894  int fieldVersion = version;
895  if (type == field_type::TYPE_PORTABLE) {
896  // is null
897  if (in.read<bool>()) {
898  shouldRegister = false;
899  }
900  fieldFactoryId = in.read<int32_t>();
901  fieldClassId = in.read<int32_t>();
902 
903  // TODO: what if there's a null inner Portable field
904  if (shouldRegister) {
905  fieldVersion = in.read<int32_t>();
906  read_class_definition(
907  in, fieldFactoryId, fieldClassId, fieldVersion);
908  }
909  } else if (type == field_type::TYPE_PORTABLE_ARRAY) {
910  int k = in.read<int32_t>();
911  if (k > 0) {
912  fieldFactoryId = in.read<int32_t>();
913  fieldClassId = in.read<int32_t>();
914 
915  int p = in.read<int32_t>();
916  in.position(p);
917 
918  // TODO: what if there's a null inner Portable field
919  fieldVersion = in.read<int32_t>();
920  read_class_definition(
921  in, fieldFactoryId, fieldClassId, fieldVersion);
922  } else {
923  shouldRegister = false;
924  }
925  }
926  FieldDefinition fieldDef(
927  i, name, type, fieldFactoryId, fieldClassId, fieldVersion);
928  builder.add_field(fieldDef);
929  }
930  std::shared_ptr<ClassDefinition> classDefinition = builder.build();
931  if (shouldRegister) {
932  classDefinition = register_class_definition(classDefinition);
933  }
934  return classDefinition;
935 }
936 
937 std::shared_ptr<ClassDefinition>
938 PortableContext::register_class_definition(std::shared_ptr<ClassDefinition> cd)
939 {
940  return get_class_definition_context(cd->get_factory_id())
941  .register_class_definition(cd);
942 }
943 
944 int
945 PortableContext::get_version()
946 {
947  return serialization_config_.get_portable_version();
948 }
949 
950 ClassDefinitionContext&
951 PortableContext::get_class_definition_context(int factory_id)
952 {
953  std::shared_ptr<ClassDefinitionContext> value =
954  class_def_context_map_.get(factory_id);
955  if (value == NULL) {
956  value = std::shared_ptr<ClassDefinitionContext>(
957  new ClassDefinitionContext(factory_id, this));
958  std::shared_ptr<ClassDefinitionContext> current =
959  class_def_context_map_.put_if_absent(factory_id, value);
960  if (current != NULL) {
961  value = current;
962  }
963  }
964  return *value;
965 }
966 
967 const serialization_config&
968 PortableContext::get_serialization_config() const
969 {
970  return serialization_config_;
971 }
972 
973 SerializationService::SerializationService(const serialization_config& config)
974  : serialization_config_(config)
975  , portable_context_(serialization_config_)
976  , portable_serializer_(portable_context_)
977  , compact_serializer_()
978 {}
979 
980 DefaultPortableWriter::DefaultPortableWriter(
981  PortableSerializer& portable_ser,
982  std::shared_ptr<ClassDefinition> cd,
983  object_data_output& output)
984  : raw_(false)
985  , portable_serializer_(portable_ser)
986  , object_data_output_(output)
987  , begin_(object_data_output_.position())
988  , cd_(cd)
989 {
990  // room for final offset
991  object_data_output_.write<int32_t>(0);
992 
993  object_data_output_.write<int32_t>(cd->get_field_count());
994 
995  offset_ = object_data_output_.position();
996  // one additional for raw data
997  int fieldIndexesLength =
998  (cd->get_field_count() + 1) * util::Bits::INT_SIZE_IN_BYTES;
999  object_data_output_.write_zero_bytes(fieldIndexesLength);
1000 }
1001 
1002 FieldDefinition const&
1003 DefaultPortableWriter::set_position(const std::string& field_name,
1004  field_type field_type)
1005 {
1006  if (raw_) {
1007  BOOST_THROW_EXCEPTION(exception::hazelcast_serialization(
1008  "PortableWriter::setPosition",
1009  "Cannot write Portable fields after getRawDataOutput() is called!"));
1010  }
1011 
1012  try {
1013  FieldDefinition const& fd = cd_->get_field(field_name);
1014 
1015  if (written_fields_.find(field_name) != written_fields_.end()) {
1016  BOOST_THROW_EXCEPTION(exception::hazelcast_serialization(
1017  "PortableWriter::setPosition",
1018  "Field '" + std::string(field_name) +
1019  "' has already been written!"));
1020  }
1021 
1022  written_fields_.insert(field_name);
1023  size_t pos = object_data_output_.position();
1024  int32_t index = fd.get_index();
1025  object_data_output_.write_at(offset_ +
1026  index * util::Bits::INT_SIZE_IN_BYTES,
1027  static_cast<int32_t>(pos));
1028  object_data_output_.write(static_cast<int16_t>(field_name.size()));
1029  object_data_output_.write_bytes(field_name);
1030  object_data_output_.write<byte>(static_cast<byte>(field_type));
1031 
1032  return fd;
1033 
1034  } catch (exception::illegal_argument& iae) {
1035  std::stringstream error;
1036  error << "hazelcast_serialization_exception( Invalid field name: '"
1037  << field_name;
1038  error << "' for ClassDefinition {class id: "
1039  << util::IOUtil::to_string(cd_->get_class_id());
1040  error << ", factoryId:" +
1041  util::IOUtil::to_string(cd_->get_factory_id());
1042  error << ", version: " << util::IOUtil::to_string(cd_->get_version())
1043  << "}. Error:";
1044  error << iae.what();
1045 
1046  BOOST_THROW_EXCEPTION(exception::hazelcast_serialization(
1047  "PortableWriter::setPosition", error.str()));
1048  }
1049 }
1050 
1051 object_data_output&
1052 DefaultPortableWriter::get_raw_data_output()
1053 {
1054  if (!raw_) {
1055  size_t pos = object_data_output_.position();
1056  int32_t index = cd_->get_field_count(); // last index
1057  object_data_output_.write_at(offset_ +
1058  index * util::Bits::INT_SIZE_IN_BYTES,
1059  static_cast<int32_t>(pos));
1060  }
1061  raw_ = true;
1062  return object_data_output_;
1063 }
1064 
1065 void
1066 DefaultPortableWriter::end()
1067 {
1068  object_data_output_.write_at(
1069  begin_, static_cast<int32_t>(object_data_output_.position()));
1070 }
1071 
1072 bool
1073 SerializationService::is_null_data(const data& data)
1074 {
1075  return data.data_size() == 0 &&
1076  data.get_type() ==
1077  static_cast<int32_t>(serialization_constants::CONSTANT_TYPE_NULL);
1078 }
1079 
1080 template<>
1081 data
1082 SerializationService::to_data(const char* object)
1083 {
1084  if (!object) {
1085  return to_data<std::string>(nullptr);
1086  }
1087  std::string str(object);
1088  return to_data<std::string>(str);
1089 }
1090 
1091 byte
1092 SerializationService::get_version() const
1093 {
1094  return 1;
1095 }
1096 
1097 object_type
1098 SerializationService::get_object_type(const data* data)
1099 {
1100  object_type type;
1101 
1102  if (NULL == data) {
1103  type.type_id = serialization_constants::CONSTANT_TYPE_NULL;
1104  return type;
1105  }
1106 
1107  type.type_id = static_cast<serialization_constants>(data->get_type());
1108 
1109  if (serialization_constants::CONSTANT_TYPE_DATA == type.type_id ||
1110  serialization_constants::CONSTANT_TYPE_PORTABLE == type.type_id) {
1111  // 8 (data Header) = Hash(4-bytes) + data TypeId(4 bytes)
1112  data_input<std::vector<byte>> dataInput(
1113  serialization_config_.get_byte_order(), data->to_byte_array(), 8);
1114 
1115  if (serialization_constants::CONSTANT_TYPE_DATA == type.type_id) {
1116  bool identified = dataInput.read<bool>();
1117  if (!identified) {
1118  BOOST_THROW_EXCEPTION(exception::hazelcast_serialization(
1119  "SerializationService::getObjectType",
1120  " DataSerializable is not 'identified data'"));
1121  }
1122  }
1123 
1124  type.factory_id = dataInput.read<int32_t>();
1125  type.class_id = dataInput.read<int32_t>();
1126  }
1127 
1128  return type;
1129 }
1130 
1131 void
1132 SerializationService::dispose()
1133 {}
1134 
1135 PortableSerializer&
1136 SerializationService::get_portable_serializer()
1137 {
1138  return portable_serializer_;
1139 }
1140 
1141 compact_stream_serializer&
1142 SerializationService::get_compact_serializer()
1143 {
1144  return compact_serializer_;
1145 }
1146 
1147 DataSerializer&
1148 SerializationService::get_data_serializer()
1149 {
1150  return data_serializer_;
1151 }
1152 
1153 object_data_output
1154 SerializationService::new_output_stream()
1155 {
1156  return object_data_output(serialization_config_.get_byte_order(),
1157  false,
1158  &portable_serializer_,
1159  &compact_serializer_,
1160  serialization_config_.get_global_serializer());
1161 }
1162 
1163 template<>
1164 data
1165 SerializationService::to_data(const typed_data* object)
1166 {
1167  if (!object) {
1168  return data();
1169  }
1170 
1171  return object->get_data();
1172 }
1173 
1174 // first 4 byte is partition hash code and next last 4 byte is type id
1175 unsigned int data::PARTITION_HASH_OFFSET = 0;
1176 
1177 unsigned int data::TYPE_OFFSET =
1178  data::PARTITION_HASH_OFFSET + util::Bits::INT_SIZE_IN_BYTES;
1179 
1180 unsigned int data::DATA_OFFSET =
1181  data::TYPE_OFFSET + util::Bits::INT_SIZE_IN_BYTES;
1182 
1183 unsigned int data::DATA_OVERHEAD = data::DATA_OFFSET;
1184 
1185 data::data()
1186  : cached_hash_value_(-1)
1187 {}
1188 
1189 data::data(std::vector<byte> buffer)
1190  : data_(std::move(buffer))
1191  , cached_hash_value_(-1)
1192 {
1193  size_t size = data_.size();
1194  if (size > 0 && size < data::DATA_OVERHEAD) {
1195  throw(exception::exception_builder<exception::illegal_argument>(
1196  "Data::setBuffer")
1197  << "Provided buffer should be either empty or should contain "
1198  "more than "
1199  << data::DATA_OVERHEAD << " bytes! Provided buffer size:" << size)
1200  .build();
1201  }
1202 
1203  cached_hash_value_ = calculate_hash();
1204 }
1205 
1206 size_t
1207 data::data_size() const
1208 {
1209  return (size_t)std::max<int>((int)total_size() - (int)data::DATA_OVERHEAD,
1210  0);
1211 }
1212 
1213 size_t
1214 data::total_size() const
1215 {
1216  return data_.size();
1217 }
1218 
1219 int
1220 data::get_partition_hash() const
1221 {
1222  return cached_hash_value_;
1223 }
1224 
1225 bool
1226 data::has_partition_hash() const
1227 {
1228  return data_.size() >= data::DATA_OVERHEAD &&
1229  *reinterpret_cast<const int32_t*>(&data_[PARTITION_HASH_OFFSET]) !=
1230  0;
1231 }
1232 
1233 const std::vector<byte>&
1234 data::to_byte_array() const
1235 {
1236  return data_;
1237 }
1238 
1239 int32_t
1240 data::get_type() const
1241 {
1242  if (total_size() == 0) {
1243  return static_cast<int32_t>(
1244  serialization_constants::CONSTANT_TYPE_NULL);
1245  }
1246  return boost::endian::
1247  endian_load<boost::uint32_t, 4, boost::endian::order::big>(
1248  &data_[data::TYPE_OFFSET]);
1249 }
1250 
1251 int
1252 data::hash() const
1253 {
1254  return cached_hash_value_;
1255 }
1256 
1257 int
1258 data::calculate_hash() const
1259 {
1260  size_t size = data_size();
1261  if (size == 0) {
1262  return 0;
1263  }
1264 
1265  if (has_partition_hash()) {
1266  return boost::endian::
1267  endian_load<boost::uint32_t, 4, boost::endian::order::big>(
1268  &data_[data::PARTITION_HASH_OFFSET]);
1269  }
1270 
1271  return util::murmur_hash3_x86_32((void*)&((data_)[data::DATA_OFFSET]),
1272  (int)size);
1273 }
1274 
1275 bool
1276 data::operator<(const data& rhs) const
1277 {
1278  return cached_hash_value_ < rhs.cached_hash_value_;
1279 }
1280 
1281 bool
1282 operator==(const data& lhs, const data& rhs)
1283 {
1284  return lhs.data_ == rhs.data_;
1285 }
1286 
1287 ClassDefinitionContext::ClassDefinitionContext(
1288  int factory_id,
1289  PortableContext* portable_context)
1290  : factory_id_(factory_id)
1291  , portable_context_(portable_context)
1292 {}
1293 
1294 int
1295 ClassDefinitionContext::get_class_version(int class_id)
1296 {
1297  std::shared_ptr<int> version = current_class_versions_.get(class_id);
1298  return version != NULL ? *version : -1;
1299 }
1300 
1301 void
1302 ClassDefinitionContext::set_class_version(int class_id, int version)
1303 {
1304  std::shared_ptr<int> current = current_class_versions_.put_if_absent(
1305  class_id, std::shared_ptr<int>(new int(version)));
1306  if (current != NULL && *current != version) {
1307  std::stringstream error;
1308  error << "Class-id: " << class_id << " is already registered!";
1309  BOOST_THROW_EXCEPTION(exception::illegal_argument(
1310  "ClassDefinitionContext::setClassVersion", error.str()));
1311  }
1312 }
1313 
1314 std::shared_ptr<ClassDefinition>
1315 ClassDefinitionContext::lookup(int class_id, int version)
1316 {
1317  long long key = combine_to_long(class_id, version);
1318  return versioned_definitions_.get(key);
1319 }
1320 
1321 std::shared_ptr<ClassDefinition>
1322 ClassDefinitionContext::register_class_definition(
1323  std::shared_ptr<ClassDefinition> cd)
1324 {
1325  if (cd.get() == NULL) {
1326  return std::shared_ptr<ClassDefinition>();
1327  }
1328  if (cd->get_factory_id() != factory_id_) {
1329  throw(exception::exception_builder<exception::hazelcast_serialization>(
1330  "ClassDefinitionContext::registerClassDefinition")
1331  << "Invalid factory-id! " << factory_id_ << " -> " << cd)
1332  .build();
1333  }
1334 
1335  cd->set_version_if_not_set(portable_context_->get_version());
1336 
1337  long long versionedClassId =
1338  combine_to_long(cd->get_class_id(), cd->get_version());
1339  std::shared_ptr<ClassDefinition> currentCd =
1340  versioned_definitions_.put_if_absent(versionedClassId, cd);
1341  if (currentCd.get() == NULL) {
1342  return cd;
1343  }
1344 
1345  if (currentCd.get() != cd.get() && *currentCd != *cd) {
1346  throw(exception::exception_builder<exception::hazelcast_serialization>(
1347  "ClassDefinitionContext::registerClassDefinition")
1348  << "Incompatible class-definitions with same class-id: " << *cd
1349  << " VS " << *currentCd)
1350  .build();
1351  }
1352 
1353  return currentCd;
1354 }
1355 
1356 int64_t
1357 ClassDefinitionContext::combine_to_long(int x, int y) const
1358 {
1359  return ((int64_t)x) << 32 | (((int64_t)y) & 0xFFFFFFFL);
1360 }
1361 
1362 DefaultPortableReader::DefaultPortableReader(
1363  PortableSerializer& portable_ser,
1364  object_data_input& input,
1365  std::shared_ptr<ClassDefinition> cd)
1366  : PortableReaderBase(portable_ser, input, cd)
1367 {}
1368 
1369 PortableReaderBase::PortableReaderBase(PortableSerializer& portable_ser,
1370  object_data_input& input,
1371  std::shared_ptr<ClassDefinition> cd)
1372  : cd_(cd)
1373  , data_input_(&input)
1374  , portable_serializer_(&portable_ser)
1375  , raw_(false)
1376 {
1377  int fieldCount;
1378  try {
1379  // final position after portable is read
1380  final_position_ = input.read<int32_t>();
1381  // field count
1382  fieldCount = input.read<int32_t>();
1383  } catch (exception::iexception& e) {
1384  BOOST_THROW_EXCEPTION(exception::hazelcast_serialization(
1385  "[PortableReaderBase::PortableReaderBase]", e.what()));
1386  }
1387  if (fieldCount != cd->get_field_count()) {
1388  char msg[50];
1389  util::hz_snprintf(msg,
1390  50,
1391  "Field count[%d] in stream does not match %d",
1392  fieldCount,
1393  cd->get_field_count());
1394  BOOST_THROW_EXCEPTION(exception::illegal_state(
1395  "[PortableReaderBase::PortableReaderBase]", msg));
1396  }
1397  this->offset_ = input.position();
1398 }
1399 
1400 void
1401 PortableReaderBase::set_position(const std::string& field_name,
1402  field_type const& field_type)
1403 {
1404  if (raw_) {
1405  BOOST_THROW_EXCEPTION(exception::hazelcast_serialization(
1406  "PortableReader::getPosition ",
1407  "Cannot read Portable fields after getRawDataInput() is called!"));
1408  }
1409  if (!cd_->has_field(field_name)) {
1410  // TODO: if no field def found, java client reads nested position:
1411  // readNestedPosition(fieldName, type);
1412  BOOST_THROW_EXCEPTION(exception::hazelcast_serialization(
1413  "PortableReader::getPosition ",
1414  "Don't have a field named " + std::string(field_name)));
1415  }
1416 
1417  if (cd_->get_field_type(field_name) != field_type) {
1418  BOOST_THROW_EXCEPTION(exception::hazelcast_serialization(
1419  "PortableReader::getPosition ",
1420  "Field type did not matched for " + std::string(field_name)));
1421  }
1422 
1423  data_input_->position(offset_ + cd_->get_field(field_name).get_index() *
1424  util::Bits::INT_SIZE_IN_BYTES);
1425  int32_t pos = data_input_->read<int32_t>();
1426 
1427  data_input_->position(pos);
1428  int16_t len = data_input_->read<int16_t>();
1429 
1430  // name + len + type
1431  data_input_->position(pos + util::Bits::SHORT_SIZE_IN_BYTES + len + 1);
1432 }
1433 
1435 PortableReaderBase::get_raw_data_input()
1436 {
1437  if (!raw_) {
1438  data_input_->position(offset_ + cd_->get_field_count() *
1439  util::Bits::INT_SIZE_IN_BYTES);
1440  int32_t pos = data_input_->read<int32_t>();
1441  data_input_->position(pos);
1442  }
1443  raw_ = true;
1444  return *data_input_;
1445 }
1446 
1447 void
1448 PortableReaderBase::end()
1449 {
1450  data_input_->position(final_position_);
1451 }
1452 
1453 void
1454 PortableReaderBase::check_factory_and_class(FieldDefinition fd,
1455  int32_t factory_id,
1456  int32_t class_id) const
1457 {
1458  if (factory_id != fd.get_factory_id()) {
1459  char msg[100];
1460  util::hz_snprintf(msg,
1461  100,
1462  "Invalid factoryId! Expected: %d, Current: %d",
1463  fd.get_factory_id(),
1464  factory_id);
1465  BOOST_THROW_EXCEPTION(exception::hazelcast_serialization(
1466  "DefaultPortableReader::checkFactoryAndClass ", std::string(msg)));
1467  }
1468  if (class_id != fd.get_class_id()) {
1469  char msg[100];
1470  util::hz_snprintf(msg,
1471  100,
1472  "Invalid classId! Expected: %d, Current: %d",
1473  fd.get_class_id(),
1474  class_id);
1475  BOOST_THROW_EXCEPTION(exception::hazelcast_serialization(
1476  "DefaultPortableReader::checkFactoryAndClass ", std::string(msg)));
1477  }
1478 }
1479 
1480 MorphingPortableReader::MorphingPortableReader(
1481  PortableSerializer& portable_ser,
1482  object_data_input& input,
1483  std::shared_ptr<ClassDefinition> cd)
1484  : PortableReaderBase(portable_ser, input, cd)
1485 {}
1486 
1487 PortableSerializer::PortableSerializer(PortableContext& portable_context)
1488  : context_(portable_context)
1489 {}
1490 
1491 portable_reader
1492 PortableSerializer::create_reader(object_data_input& input,
1493  int factory_id,
1494  int class_id,
1495  int version,
1496  int portable_version)
1497 {
1498 
1499  int effectiveVersion = version;
1500  if (version < 0) {
1501  effectiveVersion = context_.get_version();
1502  }
1503 
1504  std::shared_ptr<ClassDefinition> cd =
1505  context_.lookup_class_definition(factory_id, class_id, effectiveVersion);
1506  if (cd == nullptr) {
1507  int begin = input.position();
1508  cd = context_.read_class_definition(
1509  input, factory_id, class_id, effectiveVersion);
1510  input.position(begin);
1511  }
1512 
1513  return portable_reader(
1514  *this, input, cd, effectiveVersion == portable_version);
1515 }
1516 
1517 int32_t
1518 PortableSerializer::read_int(object_data_input& in) const
1519 {
1520  return in.read<int32_t>();
1521 }
1522 
1523 } // namespace pimpl
1524 } // namespace serialization
1525 } // namespace client
1526 } // namespace hazelcast
1527 
1528 namespace std {
1529 std::size_t
1530 hash<hazelcast::client::hazelcast_json_value>::operator()(
1531  const hazelcast::client::hazelcast_json_value& object) const noexcept
1532 {
1533  return std::hash<std::string>{}(object.to_string());
1534 }
1535 
1536 std::size_t
1537 hash<hazelcast::client::serialization::pimpl::data>::operator()(
1538  const hazelcast::client::serialization::pimpl::data& val) const noexcept
1539 {
1540  return std::hash<int>{}(val.hash());
1541 }
1542 
1543 std::size_t
1544 hash<std::shared_ptr<hazelcast::client::serialization::pimpl::data>>::
1545 operator()(const std::shared_ptr<hazelcast::client::serialization::pimpl::data>&
1546  val) const noexcept
1547 {
1548  if (!val) {
1549  return std::hash<int>{}(-1);
1550  }
1551  return std::hash<int>{}(val->hash());
1552 }
1553 
1554 bool
1555 equal_to<std::shared_ptr<hazelcast::client::serialization::pimpl::data>>::
1556 operator()(
1557  std::shared_ptr<hazelcast::client::serialization::pimpl::data> const& lhs,
1558  std::shared_ptr<hazelcast::client::serialization::pimpl::data> const& rhs)
1559  const noexcept
1560 {
1561  if (lhs == rhs) {
1562  return true;
1563  }
1564 
1565  if (!lhs || !rhs) {
1566  return false;
1567  }
1568 
1569  return lhs->to_byte_array() == rhs->to_byte_array();
1570 }
1571 
1572 bool
1573 less<std::shared_ptr<hazelcast::client::serialization::pimpl::data>>::
1574 operator()(
1575  const std::shared_ptr<hazelcast::client::serialization::pimpl::data>& lhs,
1576  const std::shared_ptr<hazelcast::client::serialization::pimpl::data>& rhs)
1577  const noexcept
1578 {
1579  const hazelcast::client::serialization::pimpl::data* leftPtr = lhs.get();
1580  const hazelcast::client::serialization::pimpl::data* rightPtr = rhs.get();
1581  if (leftPtr == rightPtr) {
1582  return false;
1583  }
1584 
1585  if (leftPtr == NULL) {
1586  return true;
1587  }
1588 
1589  if (rightPtr == NULL) {
1590  return false;
1591  }
1592 
1593  return lhs->hash() < rhs->hash();
1594 }
1595 } // namespace std
hazelcast_json_value is a wrapper for Json formatted strings.
hazelcast_json_value(std::string json_string)
Create a hazelcast_json_value from a string.
const std::string & to_string() const
This method returns a Json representation of the object.
void add_field_def(FieldDefinition &field_definition)
Internal API.
field_type get_field_type(const std::string &field_name) const
void write_data(pimpl::data_output &data_output)
Internal API.
const FieldDefinition & get_field(const std::string &field_name) const
void read_data(object_data_input &data_input)
Internal API.
void set_version_if_not_set(int new_version)
Internal API.
bool has_field(const std::string &field_name) const
ClassDefinition defines a class schema for portable classes.
void write_data(pimpl::data_output &data_output)
void read_data(object_data_input &data_input)
object_data_input(boost::endian::order byte_order, const std::vector< byte > &buffer, int offset, pimpl::PortableSerializer &portable_ser, pimpl::compact_stream_serializer &compact_ser, pimpl::DataSerializer &data_ser, std::shared_ptr< serialization::global_serializer > global_serializer)
Internal API.
object_data_output(boost::endian::order byte_order, bool dont_write=false, pimpl::PortableSerializer *portable_ser=nullptr, pimpl::compact_stream_serializer *compact_ser=nullptr, std::shared_ptr< serialization::global_serializer > global_serializer=nullptr)
Internal API Constructor.
object_data_output & get_raw_data_output()
After writing portable fields, one can write remaining fields in old fashioned way consecutively at t...
void end()
Internal api , should not be called by end user.
portable_writer(pimpl::DefaultPortableWriter *default_portable_writer)
Internal api constructor.
typed_data class is a wrapper class for the serialized binary data.
const serialization::pimpl::data & get_data() const
Internal API.
boost::optional< T > get() const
Deserializes the underlying binary data and produces the object of type T.
serialization::pimpl::object_type get_type() const