17 #include "dcAtomicField.h"
18 #include "hashGenerator.h"
20 #include "dcmsgtypes.h"
22 #include "dcClassParameter.h"
26 #include "pStatTimer.h"
29 PStatCollector DCClass::_update_pcollector(
"App:Show code:readerPollTask:Update");
30 PStatCollector DCClass::_generate_pcollector(
"App:Show code:readerPollTask:Generate");
34 (
"dc-multiple-inheritance",
true,
35 PRC_DESC(
"Set this true to support multiple inheritance in the dc file. "
36 "If this is false, the old way, multiple inheritance is not "
37 "supported, but field numbers will be numbered sequentially, "
38 "which may be required to support old code that assumed this."));
41 (
"dc-virtual-inheritance",
true,
42 PRC_DESC(
"Set this true to support proper virtual inheritance in the "
43 "dc file, so that diamond-of-death type constructs can be used. "
44 "This also enables shadowing (overloading) of inherited method "
45 "names from a base class."));
48 (
"dc-sort-inheritance-by-file",
true,
49 PRC_DESC(
"This is a temporary hack. This should be true if you are using "
50 "version 1.42 of the otp_server.exe binary, which sorted inherited "
51 "fields based on the order of the classes within the DC file, "
52 "rather than based on the order in which the references are made "
53 "within the class."));
56 #endif // WITHIN_PANDA
60 inline bool operator ()(
const DCField *a,
const DCField *b)
const {
71 DCClass(
DCFile *dc_file,
const string &name,
bool is_struct,
bool bogus_class) :
73 _class_update_pcollector(_update_pcollector, name),
74 _class_generate_pcollector(_generate_pcollector, name),
78 _is_struct(is_struct),
79 _bogus_class(bogus_class)
86 _owner_class_def = NULL;
97 if (_constructor != (
DCField *)NULL) {
102 for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
107 Py_XDECREF(_class_def);
108 Py_XDECREF(_owner_class_def);
140 return _parents.size();
151 nassertr(n >= 0 && n < (
int)_parents.size(), NULL);
163 return (_constructor != (
DCField *)NULL);
186 return _fields.size();
200 if (n < 0 || n >= (
int)_fields.size()) {
202 <<
"n:" << n <<
" _fields.size():"
203 << (int)_fields.size() << endl;
207 nassertr_always(n >= 0 && n < (
int)_fields.size(), NULL);
223 FieldsByName::const_iterator ni;
224 ni = _fields_by_name.find(name);
225 if (ni != _fields_by_name.end()) {
230 Parents::const_iterator pi;
231 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
232 DCField *result = (*pi)->get_field_by_name(name);
233 if (result != (
DCField *)NULL) {
254 FieldsByIndex::const_iterator ni;
255 ni = _fields_by_index.find(index_number);
256 if (ni != _fields_by_index.end()) {
261 Parents::const_iterator pi;
262 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
263 DCField *result = (*pi)->get_field_by_index(index_number);
264 if (result != (
DCField *)NULL) {
266 ((
DCClass *)
this)->_fields_by_index[index_number] = result;
283 if (dc_multiple_inheritance && dc_virtual_inheritance &&
284 _dc_file != (
DCFile *)NULL) {
286 if (_inherited_fields.empty()) {
287 ((
DCClass *)
this)->rebuild_inherited_fields();
293 return (
int)_inherited_fields.size();
298 Parents::const_iterator pi;
299 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
300 num_fields += (*pi)->get_num_inherited_fields();
321 if (dc_multiple_inheritance && dc_virtual_inheritance &&
322 _dc_file != (
DCFile *)NULL) {
324 if (_inherited_fields.empty()) {
325 ((
DCClass *)
this)->rebuild_inherited_fields();
327 nassertr(n >= 0 && n < (
int)_inherited_fields.size(), NULL);
328 return _inherited_fields[n];
331 Parents::const_iterator pi;
332 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
333 int psize = (*pi)->get_num_inherited_fields();
335 return (*pi)->get_inherited_field(n);
359 Parents::const_iterator pi;
360 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
361 if ((*pi)->inherits_from_bogus_class()) {
382 if (!_name.empty()) {
395 has_class_def()
const {
396 return (_class_def != NULL);
398 #endif // HAVE_PYTHON
409 set_class_def(PyObject *class_def) {
410 Py_XINCREF(class_def);
411 Py_XDECREF(_class_def);
412 _class_def = class_def;
414 #endif // HAVE_PYTHON
425 get_class_def()
const {
426 if (_class_def == NULL) {
431 Py_INCREF(_class_def);
434 #endif // HAVE_PYTHON
444 has_owner_class_def()
const {
445 return (_owner_class_def != NULL);
447 #endif // HAVE_PYTHON
458 set_owner_class_def(PyObject *owner_class_def) {
459 Py_XINCREF(owner_class_def);
460 Py_XDECREF(_owner_class_def);
461 _owner_class_def = owner_class_def;
463 #endif // HAVE_PYTHON
474 get_owner_class_def()
const {
475 if (_owner_class_def == NULL) {
480 Py_INCREF(_owner_class_def);
481 return _owner_class_def;
483 #endif // HAVE_PYTHON
505 if (field == (
DCField *)NULL) {
508 <<
"Received update for field " << field_id <<
", not in class "
510 nassert_raise(strm.str());
515 field->receive_update(packer, distobj);
521 #endif // HAVE_PYTHON
533 receive_update_broadcast_required(PyObject *distobj,
DatagramIterator &di)
const {
543 for (
int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
548 field->receive_update(packer, distobj);
557 #endif // HAVE_PYTHON
570 receive_update_broadcast_required_owner(PyObject *distobj,
581 for (
int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
587 field->receive_update(packer, distobj);
601 #endif // HAVE_PYTHON
613 receive_update_all_required(PyObject *distobj,
DatagramIterator &di)
const {
623 for (
int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
628 field->receive_update(packer, distobj);
637 #endif // HAVE_PYTHON
652 for (
int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
653 receive_update(distobj, di);
656 #endif // HAVE_PYTHON
666 direct_update(PyObject *distobj,
const string &field_name,
667 const string &value_blob) {
669 nassertv_always(field != NULL);
674 field->receive_update(packer, distobj);
677 #endif // HAVE_PYTHON
687 direct_update(PyObject *distobj,
const string &field_name,
689 direct_update(distobj, field_name, datagram.
get_message());
691 #endif // HAVE_PYTHON
707 pack_required_field(
Datagram &datagram, PyObject *distobj,
711 if (!pack_required_field(packer, distobj, field)) {
721 #endif // HAVE_PYTHON
737 pack_required_field(
DCPacker &packer, PyObject *distobj,
739 const DCParameter *parameter = field->as_parameter();
743 string field_name = field->
get_name();
745 if (!PyObject_HasAttrString(distobj, (
char *)field_name.c_str())) {
755 strm <<
"Data element " << field_name
756 <<
", required by dc file for dclass " <<
get_name()
757 <<
", not defined on object";
758 nassert_raise(strm.str());
762 PyObject_GetAttrString(distobj, (
char *)field_name.c_str());
763 nassertr(result != (PyObject *)NULL,
false);
766 bool pack_ok = parameter->pack_args(packer, result);
774 strm <<
"Cannot pack molecular field " << field->
get_name()
776 nassert_raise(strm.str());
787 string setter_name = atom->
get_name();
789 if (setter_name.empty()) {
791 strm <<
"Required field is unnamed!";
792 nassert_raise(strm.str());
800 strm <<
"Required field " << setter_name <<
" has no parameters!";
801 nassert_raise(strm.str());
805 string getter_name = setter_name;
806 if (setter_name.substr(0, 3) ==
"set") {
809 getter_name[0] =
'g';
814 getter_name =
"get" + setter_name;
815 getter_name[3] = toupper(getter_name[3]);
820 if (!PyObject_HasAttrString(distobj, (
char *)getter_name.c_str())) {
830 strm <<
"Distributed class " <<
get_name()
831 <<
" doesn't have getter named " << getter_name
832 <<
" to match required field " << setter_name;
833 nassert_raise(strm.str());
837 PyObject_GetAttrString(distobj, (
char *)getter_name.c_str());
838 nassertr(func != (PyObject *)NULL,
false);
840 PyObject *empty_args = PyTuple_New(0);
841 PyObject *result = PyObject_CallObject(func, empty_args);
842 Py_DECREF(empty_args);
844 if (result == (PyObject *)NULL) {
847 cerr <<
"Error when calling " << getter_name <<
"\n";
854 PyObject *tuple = PyTuple_New(1);
855 PyTuple_SET_ITEM(tuple, 0, result);
861 if (!PySequence_Check(result)) {
863 strm <<
"Since dclass " <<
get_name() <<
" method " << setter_name
864 <<
" is declared to have multiple parameters, Python function "
865 << getter_name <<
" must return a list or tuple.\n";
866 nassert_raise(strm.str());
872 bool pack_ok = atom->pack_args(packer, result);
877 #endif // HAVE_PYTHON
888 client_format_update(
const string &field_name, DOID_TYPE do_id,
889 PyObject *args)
const {
891 if (field == (
DCField *)NULL) {
893 strm <<
"No field named " << field_name <<
" in class " <<
get_name()
895 nassert_raise(strm.str());
899 return field->client_format_update(do_id, args);
901 #endif // HAVE_PYTHON
912 ai_format_update(
const string &field_name, DOID_TYPE do_id,
913 CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args)
const {
915 if (field == (
DCField *)NULL) {
917 strm <<
"No field named " << field_name <<
" in class " <<
get_name()
919 nassert_raise(strm.str());
923 return field->ai_format_update(do_id, to_id, from_id, args);
925 #endif // HAVE_PYTHON
937 ai_format_update_msg_type(
const string &field_name, DOID_TYPE do_id,
938 CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
int msg_type, PyObject *args)
const {
940 if (field == (
DCField *)NULL) {
942 strm <<
"No field named " << field_name <<
" in class " <<
get_name()
944 nassert_raise(strm.str());
948 return field->ai_format_update_msg_type(do_id, to_id, from_id, msg_type, args);
950 #endif // HAVE_PYTHON
967 client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
969 PyObject *optional_fields)
const {
980 for (
int i = 0; i < num_fields; ++i) {
984 if (!pack_required_field(packer, distobj, field)) {
992 int num_optional_fields = 0;
993 if (PyObject_IsTrue(optional_fields)) {
994 num_optional_fields = PySequence_Size(optional_fields);
998 for (
int i = 0; i < num_optional_fields; i++) {
999 PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
1000 #if PY_MAJOR_VERSION >= 3
1001 string field_name = PyUnicode_AsUTF8(py_field_name);
1003 string field_name = PyString_AsString(py_field_name);
1005 Py_XDECREF(py_field_name);
1008 if (field == (
DCField *)NULL) {
1010 strm <<
"No field named " << field_name <<
" in class " <<
get_name()
1012 nassert_raise(strm.str());
1017 if (!pack_required_field(packer, distobj, field)) {
1025 #endif // HAVE_PYTHON
1040 ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
1041 DOID_TYPE parent_id, ZONEID_TYPE zone_id,
1042 CHANNEL_TYPE district_channel_id, CHANNEL_TYPE from_channel_id,
1043 PyObject *optional_fields)
const {
1047 packer.RAW_PACK_CHANNEL(district_channel_id);
1048 packer.RAW_PACK_CHANNEL(from_channel_id);
1051 bool has_optional_fields = (PyObject_IsTrue(optional_fields) != 0);
1053 if (has_optional_fields) {
1054 packer.
raw_pack_uint16(STATESERVER_OBJECT_GENERATE_WITH_REQUIRED_OTHER);
1071 for (
int i = 0; i < num_fields; ++i) {
1075 if (!pack_required_field(packer, distobj, field)) {
1083 if (has_optional_fields) {
1084 int num_optional_fields = PySequence_Size(optional_fields);
1087 for (
int i = 0; i < num_optional_fields; ++i) {
1088 PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
1089 #if PY_MAJOR_VERSION >= 3
1090 string field_name = PyUnicode_AsUTF8(py_field_name);
1092 string field_name = PyString_AsString(py_field_name);
1094 Py_XDECREF(py_field_name);
1097 if (field == (
DCField *)NULL) {
1099 strm <<
"No field named " << field_name <<
" in class " <<
get_name()
1101 nassert_raise(strm.str());
1108 if (!pack_required_field(packer, distobj, field)) {
1117 #endif // HAVE_PYTHON
1129 ai_database_generate_context(
1130 unsigned int context_id, DOID_TYPE parent_id, ZONEID_TYPE zone_id,
1131 CHANNEL_TYPE owner_channel,
1132 CHANNEL_TYPE database_server_id, CHANNEL_TYPE from_channel_id)
const
1136 packer.RAW_PACK_CHANNEL(database_server_id);
1137 packer.RAW_PACK_CHANNEL(from_channel_id);
1139 packer.
raw_pack_uint16(STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT);
1142 packer.RAW_PACK_CHANNEL(owner_channel);
1148 for (
int i = 0; i < num_fields; ++i) {
1159 #endif // HAVE_PYTHON
1173 ai_database_generate_context_old(
1174 unsigned int context_id, DOID_TYPE parent_id, ZONEID_TYPE zone_id,
1175 CHANNEL_TYPE database_server_id, CHANNEL_TYPE from_channel_id)
const
1179 packer.RAW_PACK_CHANNEL(database_server_id);
1180 packer.RAW_PACK_CHANNEL(from_channel_id);
1182 packer.
raw_pack_uint16(STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT);
1190 for (
int i = 0; i < num_fields; ++i) {
1201 #endif // HAVE_PYTHON
1221 write(ostream &out,
bool brief,
int indent_level)
const {
1222 indent(out, indent_level);
1228 if (!_name.empty()) {
1229 out <<
" " << _name;
1232 if (!_parents.empty()) {
1233 Parents::const_iterator pi = _parents.begin();
1234 out <<
" : " << (*pi)->_name;
1236 while (pi != _parents.end()) {
1237 out <<
", " << (*pi)->_name;
1243 if (!brief && _number >= 0) {
1244 out <<
" // index " << _number;
1248 if (_constructor != (
DCField *)NULL) {
1249 _constructor->write(out, brief, indent_level + 2);
1252 Fields::const_iterator fi;
1253 for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
1254 if (!(*fi)->is_bogus_field()) {
1255 (*fi)->write(out, brief, indent_level + 2);
1273 indent(out, indent_level) <<
"};\n";
1284 const string &name,
const string &postname)
const {
1290 if (!_name.empty()) {
1291 out <<
" " << _name;
1294 if (!_parents.empty()) {
1295 Parents::const_iterator pi = _parents.begin();
1296 out <<
" : " << (*pi)->_name;
1298 while (pi != _parents.end()) {
1299 out <<
", " << (*pi)->_name;
1306 if (_constructor != (
DCField *)NULL) {
1307 _constructor->output(out, brief);
1311 Fields::const_iterator fi;
1312 for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
1313 if (!(*fi)->is_bogus_field()) {
1314 (*fi)->output(out, brief);
1320 if (!prename.empty() || !name.empty() || !postname.empty()) {
1321 out <<
" " << prename << name << postname;
1339 hashgen.
add_int(_parents.size());
1340 Parents::const_iterator pi;
1341 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
1342 hashgen.
add_int((*pi)->get_number());
1345 if (_constructor != (
DCField *)NULL) {
1349 hashgen.
add_int(_fields.size());
1350 Fields::const_iterator fi;
1351 for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
1352 (*fi)->generate_hash(hashgen);
1365 _inherited_fields.clear();
1378 _inherited_fields.clear();
1382 Parents::const_iterator pi;
1383 for (pi = _parents.begin(); pi != _parents.end(); ++pi) {
1384 const DCClass *parent = (*pi);
1386 for (
int i = 0; i < num_inherited_fields; ++i) {
1390 if (!dc_sort_inheritance_by_file) {
1391 _inherited_fields.push_back(field);
1395 bool inserted = names.insert(field->
get_name()).second;
1398 _inherited_fields.push_back(field);
1408 Fields::const_iterator fi;
1409 for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
1413 _inherited_fields.push_back(field);
1416 bool inserted = names.insert(field->
get_name()).second;
1420 shadow_inherited_field(field->
get_name());
1424 _inherited_fields.push_back(field);
1428 if (dc_sort_inheritance_by_file) {
1443 shadow_inherited_field(
const string &name) {
1444 Fields::iterator fi;
1445 for (fi = _inherited_fields.begin(); fi != _inherited_fields.end(); ++fi) {
1448 _inherited_fields.erase(fi);
1470 if (_dc_file != (
DCFile *)NULL) {
1477 if (_constructor != (
DCField *)NULL) {
1485 _constructor = field;
1486 _fields_by_name.insert
1487 (FieldsByName::value_type(field->
get_name(), field));
1491 bool inserted = _fields_by_name.insert
1492 (FieldsByName::value_type(field->
get_name(), field)).second;
1499 if (_dc_file != (
DCFile *)NULL &&
1500 ((dc_virtual_inheritance && dc_sort_inheritance_by_file) || !
is_struct())) {
1501 if (dc_multiple_inheritance) {
1507 bool inserted = _fields_by_index.insert
1508 (FieldsByIndex::value_type(field->
get_number(), field)).second;
1511 nassertr(inserted,
false);
1514 _fields.push_back(field);
1526 _parents.push_back(parent);
bool is_struct() const
Returns true if the class has been identified with the "struct" keyword in the dc file...
void add_string(const string &str)
Adds a string to the hash, by breaking it down into a sequence of integers.
virtual void output(ostream &out) const
Write a string representation of this instance to <out>.
virtual void generate_hash(HashGenerator &hashgen) const
Accumulates the properties of this field into the hash.
string get_message() const
Returns the datagram's data as a string.
const string & get_name() const
Returns the name of this field, or empty string if the field is unnamed.
void generate_hash(HashGenerator &hashgen) const
Accumulates the properties of this class into the hash.
const string & get_name() const
Returns the name of this class.
void output_instance(ostream &out, bool brief, const string &prename, const string &name, const string &postname) const
Generates a parseable description of the object to the indicated output stream.
void append_data(const void *data, size_t size)
Appends some more raw data to the end of the datagram.
int get_num_elements() const
Returns the number of elements (parameters) of the atomic field.
void clear_inherited_fields()
Empties the list of inherited fields for the class, so that it may be rebuilt.
int get_num_parents() const
Returns the number of base classes this class inherits from.
DCField * get_inherited_field(int n) const
Returns the nth field field in the class and all of its ancestors.
void add_int(int num)
Adds another integer to the hash so far.
A single field of a Distributed Class, either atomic or molecular.
This is a convenience class to specialize ConfigVariable as a boolean type.
unsigned int raw_unpack_uint16()
Unpacks the data from the buffer between unpacking sessions.
void raw_pack_uint8(unsigned int value)
Packs the data into the buffer between packing sessions.
void set_class(DCClass *dclass)
Assigns the class pointer to this field.
bool has_default_value() const
Returns true if a default value has been explicitly established for this field, false otherwise...
void check_inherited_fields()
Rebuilds all of the inherited fields tables, if necessary.
void set_number(int number)
Assigns the unique number to this field.
virtual DCAtomicField * as_atomic_field()
Returns the same field pointer converted to an atomic field pointer, if this is in fact an atomic fie...
Defines a particular DistributedClass as read from an input .dc file.
void set_new_index_number(DCField *field)
Sets the next sequential available index number on the indicated field.
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
int get_num_inherited_fields() const
Returns the total number of field fields defined in this class and all ancestor classes.
A single atomic field of a Distributed Class, as read from a .dc file.
DCField * get_constructor() const
Returns the constructor method for this class if it is defined, or NULL if the class uses the default...
virtual DCMolecularField * as_molecular_field()
Returns the same field pointer converted to a molecular field pointer, if this is in fact a molecular...
DCClass * get_class() const
Returns the DCClass pointer for the class that contains this field.
size_t get_length() const
Returns the current length of the buffer.
Represents the complete list of Distributed Class descriptions as read from a .dc file...
PN_uint16 get_uint16()
Extracts an unsigned 16-bit integer.
void raw_pack_uint32(unsigned int value)
Packs the data into the buffer between packing sessions.
int get_num_fields() const
Returns the number of fields defined directly in this class, ignoring inheritance.
int get_remaining_size() const
Return the bytes left in the datagram.
A lightweight class that represents a single element that may be timed and/or counted via stats...
Represents the type specification for a single parameter within a field specification.
void pack_default_value()
Adds the default value for the current element into the stream.
bool is_required() const
Returns true if the "required" flag is set for this field, false otherwise.
size_t get_current_index() const
Returns the current position within the datagram of the next piece of data to extract.
void raw_pack_uint16(unsigned int value)
Packs the data into the buffer between packing sessions.
void skip_bytes(size_t size)
Skips over the indicated number of bytes in the datagram.
void add_parent(DCClass *parent)
Adds a new parent to the inheritance hierarchy of the class.
virtual void write(ostream &out, bool brief, int indent_level) const
Generates a parseable description of the object to the indicated output stream.
This class generates an arbitrary hash number from a sequence of ints.
DCField * get_field_by_name(const string &name) const
Returns a pointer to the DCField that shares the indicated name.
const Datagram & get_datagram() const
Return the datagram of this iterator.
void mark_inherited_fields_stale()
Indicates that something has changed in one or more of the inheritance chains or the set of fields; t...
void begin_pack(const DCPackerInterface *root)
Begins a packing session.
bool is_bogus_class() const
Returns true if the class has been flagged as a bogus class.
const char * get_data() const
Returns the beginning of the data buffer.
This class can be used for packing a series of numeric and string data into a binary stream...
bool is_broadcast() const
Returns true if the "broadcast" flag is set for this field, false otherwise.
DCField * get_field(int n) const
Returns the nth field in the class.
void unpack_skip()
Skips the current field without unpacking it and advances to the next field.
const void * get_data() const
Returns a pointer to the beginning of the datagram's data.
A class to retrieve the individual data elements previously stored in a Datagram. ...
DCClass * get_parent(int n) const
Returns the nth parent class this class inherits from.
bool add_field(DCField *field)
Adds the newly-allocated field to the class.
bool has_constructor() const
Returns true if this class has a constructor method, false if it just uses the default constructor...
int get_number() const
Returns a unique index number associated with this field.
size_t get_num_unpacked_bytes() const
Returns the number of bytes that have been unpacked so far, or after unpack_end(), the total number of bytes that were unpacked at all.
bool end_pack()
Finishes a packing session.
void set_unpack_data(const string &data)
Sets up the unpack_data pointer.
bool inherits_from_bogus_class() const
Returns true if this class, or any class in the inheritance heirarchy for this class, is a "bogus" class–a forward reference to an as-yet-undefined class.
A single molecular field of a Distributed Class, as read from a .dc file.
void rebuild_inherited_fields()
Recomputes the list of inherited fields for the class.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
bool is_ownrecv() const
Returns true if the "ownrecv" flag is set for this field, false otherwise.
void begin_unpack(const DCPackerInterface *root)
Begins an unpacking session.
bool end_unpack()
Finishes the unpacking session.
void set_number(int number)
Assigns the unique number to this class.
DCField * get_field_by_index(int index_number) const
Returns a pointer to the DCField that has the indicated index number.