![]() |
das2C
das core C utilities (v3)
|
Describes the stream itself, in particular the compression used, current packetDescriptors, etc. More...
#include <das3/stream.h>


Public Member Functions | |
| #define | DasStream_getEmbedAsBytes(P) ((P)->bEmbedAsBytes) |
| True if embedded formats with no registered codec should be preserved as opaque bytes rather than erroring out. | |
| DAS_API DasStream * | new_DasStream (void) |
| Creates a new blank StreamDesc. | |
| DAS_API char * | DasStream_info (const DasStream *pSd, char *sBuf, int nLen) |
| Print a short description of the stream to a string buffer, This is not a serialization, just an overview. | |
| DAS_API DasStream * | DasStream_copy (const DasStream *pThis) |
| Creates a deep-copy of an existing DasStream object. | |
| DAS_API void | del_DasStream (DasStream *pThis) |
| Delete a stream descriptor and all it's sub objects. | |
| DAS_API size_t | DasStream_getNPktDesc (const DasStream *pThis) |
| Get the number of packet descriptors defined for this stream. | |
| DAS_API DasDesc * | DasStream_nextDesc (const DasStream *pThis, int *pPrevPktId) |
| Iterate over packet descriptors. | |
| DAS_API DasErrCode | DasStream_addDesc (DasStream *pThis, DasDesc *pDesc, int nPktId) |
| Attach a packet descriptor to this stream. | |
| DAS_API DasErrCode | DasStream_shadowPktDesc (DasStream *pThis, DasDesc *pDesc, int nPktId) |
| Loosely attach a dataset (DasDs or PktDesc) to this stream. | |
| DAS_API DasErrCode | DasStream_takePktDesc (DasStream *pThis, DasDesc *pDesc, int nPktId) |
| Take ownership of a dataset (DasDs or PktDesc) | |
| DAS_API DasErrCode | DasStream_rmDesc (DasStream *pThis, DasDesc *pDesc, int nPktId) |
| Detach a packet descriptor from this stream. | |
| DAS_API void | DasStream_setMonotonic (DasStream *pThis, bool isMonotonic) |
| Indicates if the xtags on the stream are monotonic, in which case there might be optimal ways of processing the stream. | |
| DAS_API void | DasStream_addStdProps (DasStream *pThis) |
| Adds metadata into the property set of the DasStream. | |
| DAS_API void | DasStream_addCmdLineProp (DasStream *pThis, int argc, char *argv[]) |
| Adds the command line into the property set of the DasStream. | |
| DAS_API PktDesc * | DasStream_createPktDesc (DasStream *pThis, DasEncoding *pXEncoder, das_units xUnits) |
| Creates a descriptor structure that for a stream packet type. | |
| DAS_API PktDesc * | DasStream_clonePktDesc (DasStream *pThis, const PktDesc *pd) |
| Make a deep copy of a PacketDescriptor on a new stream. | |
| DAS_API PktDesc * | DasStream_clonePktDescById (DasStream *pThis, const DasStream *pOther, int nPktId) |
| Deepcopy a PacketDescriptor from one stream to another. | |
| DAS_API PktDesc * | DasStream_getPktDesc (const DasStream *pThis, int id) |
| Get the packet descriptor associated with an ID. | |
| DAS_API DasDesc * | DasStream_getDesc (const DasStream *pThis, int id) |
| Get any descriptor associated with a packet ID. | |
| DAS_API int | DasStream_getPktId (DasStream *pThis, const DasDesc *pDesc) |
| If a data container is owned by this stream object, return it's packet ID. | |
| DAS_API int | DasStream_getOffset (DasStream *pThis) |
| An I/O function that makes sense to use for either operation. | |
| DAS_API DasErrCode | DasStream_encode2 (DasStream *pThis, DasBuf *pBuf) |
| Encode a DasStream to an version 2 XML string. | |
| DAS_API DasErrCode | DasStream_encode3 (DasStream *pThis, DasBuf *pBuf) |
| Encode a DasStream to an version 3 XML string. | |
| #define | DasDesc_type(P) ((P)->type) |
| Get the type of this descriptor. | |
| DAS_API void | DasDesc_init (DasDesc *pThis, desc_type_t type) |
| Initialize a memory location as a valid das descriptor. | |
| DAS_API char * | DasDesc_info (const DasDesc *pThis, char *sBuf, int nLen, char *sIndent) |
| Print 1-line versions of each property in a descriptor. | |
| DAS_API void | DasDesc_freeProps (DasDesc *pThis) |
| For use in derived destructors, frees the property array. | |
| DAS_API void | DasDesc_clearProps (DasDesc *pThis) |
| Resets the property count to 0, but frees no memory. | |
| DAS_API bool | DasDesc_equals (const DasDesc *pThis, const DasDesc *pOther) |
| Check to see if two descriptors contain the same properties Note, the order of the properties may be different between the descriptors but if the contents are the same then the descriptors are considered to be equal. | |
| DAS_API const DasDesc * | DasDesc_parent (const DasDesc *pThis) |
| The the parent of a Descriptor. | |
| DAS_API size_t | DasDesc_length (const DasDesc *pThis) |
| Get the number of properties in a descriptor. | |
| DAS_API const DasProp * | DasDesc_getPropByIdx (const DasDesc *pThis, size_t uIdx) |
| Get a property name by an index. | |
| DAS_API const char * | DasDesc_getNameByIdx (const DasDesc *pThis, size_t uIdx) |
| Get a property name by an index. | |
| DAS_API const char * | DasDesc_getValByIdx (const DasDesc *pThis, size_t uIdx) |
| Get a property value by an index. | |
| DAS_API const char * | DasDesc_getTypeByIdx (const DasDesc *pThis, size_t uIdx) |
| Get a data type of a property by an index. | |
| DAS_API const char * | DasDesc_getTypeByIdx3 (const DasDesc *pThis, size_t uIdx) |
| Get a data type of a property by an index, das3 convention. | |
| DAS_API DasErrCode | DasDesc_set (DasDesc *pThis, const char *sType, const char *sName, const char *sVal) |
| Generic property setter. | |
| DAS_API DasErrCode | DasDesc_flexSet (DasDesc *pThis, const char *sType, ubyte uType, const char *sName, const char *sVal, char cSep, das_units units, int nStandard) |
| Create or set a existing property. | |
| DAS_API DasErrCode | DasDesc_setProp (DasDesc *pThis, const DasProp *pProp) |
| Overwrite, or copy-in a fully formatted property. | |
| DAS_API const char * | DasDesc_getType (const DasDesc *pThis, const char *sName) |
| Get the type string for a property. | |
| DAS_API const char * | DasDesc_get (const DasDesc *pThis, const char *sName) |
| Get a raw property string. | |
| DAS_API bool | DasDesc_has (const DasDesc *pThis, const char *sName) |
| Determine if a property is present in a Descriptor or it's ancestors. | |
| DAS_API bool | DasDesc_hasLocal (const DasDesc *pThis, const char *sName) |
| Does this descriptor alone have this property. | |
| DAS_API bool | DasDesc_remove (DasDesc *pThis, const char *sName) |
| Remove a property from a descriptor, if preset. | |
| DAS_API const char * | DasDesc_getStr (const DasDesc *pThis, const char *sName) |
| read the property of type String named sName. | |
| DAS_API size_t | DasDesc_getStrAry (DasDesc *pThis, const char *sName, char *pBuf, size_t uBufSz, char **psVals, size_t uMaxVals) |
| Get a multi-valued string property. | |
| DAS_API DasErrCode | DasDesc_setStr (DasDesc *pThis, const char *sName, const char *sVal) |
| SetProperty methods add properties to any Descriptor (stream,packet,plane). | |
| DAS_API DasErrCode | DasDesc_vSetStr (DasDesc *pThis, const char *sName, const char *sFmt,...) |
| Set a string property in the manner of sprintf. | |
| DAS_API double | DasDesc_getDouble (const DasDesc *pThis, const char *sName) |
| Read the property of type double named sName. | |
| DAS_API DasErrCode | DasDesc_setDouble (DasDesc *pThis, const char *sName, double value) |
| Set property of type double. | |
| DAS_API double | DasDesc_getDatum (DasDesc *pThis, const char *sName, das_units units) |
| Get the a numeric property in the specified units. | |
| DAS_API DasErrCode | DasDesc_setDatum (DasDesc *pThis, const char *sName, double rVal, das_units units) |
| Set property of type Datum (double, UnitType pair) | |
| DAS_API double * | DasDesc_getDoubleAry (DasDesc *pThis, const char *sName, int *pNumItems) |
| Get the values of an array property. | |
| DAS_API DasErrCode | DasDesc_setDoubleArray (DasDesc *pThis, const char *sName, int nItems, double *pValues) |
| Set the property of type double array. | |
| DAS_API int | DasDesc_getInt (const DasDesc *pThis, const char *sName) |
| Get a property integer value. | |
| DAS_API DasErrCode | DasDesc_setInt (DasDesc *pThis, const char *sName, int nVal) |
| Set the property of type int. | |
| DAS_API bool | DasDesc_getBool (DasDesc *pThis, const char *sName) |
| Get a property boolean value. | |
| DAS_API DasErrCode | DasDesc_setDatumRng (DasDesc *pThis, const char *sName, double beg, double end, das_units units) |
| Set property of type DatumRange (double, double, UnitType triple) | |
| DAS_API DasErrCode | DasDesc_getStrRng (DasDesc *pThis, const char *sName, char *sMin, char *sMax, das_units *pUnits, size_t uLen) |
| Get a property of type DatumRange with unconverted strings. | |
| DAS_API DasErrCode | DasDesc_setFloatAry (DasDesc *pThis, const char *sName, int nItems, float *pValues) |
| Set the property of type float array. | |
| DAS_API void | DasDesc_copyIn (DasDesc *pThis, const DasDesc *pOther) |
| Deepcopy properties into a descriptor. | |
| DAS_API DasErrCode | DasDesc_encode2 (DasDesc *pThis, DasBuf *pBuf, const char *sIndent) |
| Encode a generic set of properties to a buffer. | |
| DAS_API DasErrCode | DasDesc_encode3Bare (DasDesc *pThis, DasBuf *pBuf, const char *sIndent) |
| Encode das3 properties WITHOUT the enclosing properties element. | |
| DAS_API bool | DasDesc_hasAnyProps (const DasDesc *pThis) |
| Does this descriptor hold at least one valid property? | |
Data Fields | |
| DasDesc | base |
| The base structure. | |
| void * | pUser |
| User data pointer. | |
Describes the stream itself, in particular the compression used, current packetDescriptors, etc.
This is a container for top-level stream descriptor objects. The data owner ship model for das2C is:
DasStream -> PktDesc -> PlaneDesc -> 1-row of data -> DasDs -> DasAry -> arbitrary rows of data -> DasDim -> DasVar (Structure access to DasDs Arrays)
Anything not owned by DasStream is considered OOB (Out Of Band) data.
All top level descriptors my be accessed by an integer ID. There is one ID space for all descriptors, not a separate one for datasets (das3) versus packets (das2)
ID 0 is reserved for the stream descriptor itself.
Descriptor IDs: The lookup ID is the same value used as the header & data IDs in the stream. The legal packet ID range depends on the stream serialization method. For the das2 format, the valid range is 1 to 99.
For the das3 format, packet ID's must be positive and fit into a short unsigned integer, so the valid range is 1 to 65,535.
Note that das v2 Streams can re-use packet ID's. So the PacketDescriptor at, for example, ID 2 may be completely different from one invocation of a stream handler callback to another.
Since das v3 streams have no packet length limitations, ID reuse is not permitted on a single stream.
| DAS_API DasStream * new_DasStream | ( | void | ) |
Creates a new blank StreamDesc.
The returned structure has no packet descriptors, no properties are defined. The compression attribute is set to 'none' and the version is set to 2.2
Creates a deep-copy of an existing DasStream object.
An existing stream descriptor, probably one initialized automatically by reading standard input, can be used as a template for generating a second stream descriptor. This is a deep copy for properties, but that's it. Any owned frames and packet definitions are not copied.
The stream encoding is saved, though this should probably be moved out to a separate serializers.
| pThis | The stream descriptor to copy |
| DAS_API void del_DasStream | ( | DasStream * | pThis | ) |
Delete a stream descriptor and all it's sub objects.
| pThis | The stream descriptor to erase, the pointer should be set to NULL by the caller. |
| DAS_API size_t DasStream_getNPktDesc | ( | const DasStream * | pThis | ) |
Get the number of packet descriptors defined for this stream.
| pThis | The stream descriptor to query |
Iterate over packet descriptors.
Here's one way to use this function in a loop:
| pThis | A stream descriptor structure |
| pPrevPktId | A pointer to the ID of a previous packet descriptor. Will be incremented to the next valid packet ID |
| DAS_API DasErrCode DasStream_addDesc | ( | DasStream * | pThis, |
| DasDesc * | pDesc, | ||
| int | nPktId | ||
| ) |
Attach a packet descriptor to this stream.
The stream takes ownership of the packet (or dataset) descriptor. It will be deleted when the stream is deleted.
| pThis | The stream to receive the packet descriptor. The PkdDesc object will have it's parent pointer set to this object. |
| pDesc | Must be a pointer to a descriptor of type PktDesc or DasDs. If the descriptor has a parent and it's not this stream object, an error will be thrown. |
| nPktId | The ID for the new packet descriptor. |
| DAS_API DasErrCode DasStream_shadowPktDesc | ( | DasStream * | pThis, |
| DasDesc * | pDesc, | ||
| int | nPktId | ||
| ) |
Loosely attach a dataset (DasDs or PktDesc) to this stream.
The stream does not take ownership of the dataset (or PktDesc) descriptor. It will not be deleted when the stream is deleted unless DasStream_ownPktDesc() is called later.
| pThis | the stream to track the packet descriptor. |
| pDesc | a pointer to either a PktDesc or a DasDs. |
| nPktId | The ID to assigne to the packet for this stream object |
| DAS_API DasErrCode DasStream_takePktDesc | ( | DasStream * | pThis, |
| DasDesc * | pDesc, | ||
| int | nPktId | ||
| ) |
Take ownership of a dataset (DasDs or PktDesc)
The dataset must already be tracked if this stream object, typically by calling DasStream_shadowPktDesc() earlier on.
Take together, DasStream_shadowPktDesc and DasStream_ownPktDesc has the same effect as calling DasStream_addPktDesc, but separated the operation into two stages
| pThis | the stream to track the packet descriptor. |
| pDesc | a pointer to either a PktDesc or a DasDs, may be NULL if nPktId is set. |
| nPktId | The ID under which to find the descriptor. Only used if pDesc is NULL. |
| DAS_API DasErrCode DasStream_rmDesc | ( | DasStream * | pThis, |
| DasDesc * | pDesc, | ||
| int | nPktId | ||
| ) |
Detach a packet descriptor from this stream.
The stream no longer has ownership of the packet (or dataset), in fact it will no longer have any record of the descriptor of any kind so it will not be deleted when the stream descriptor is deleted.
| pThis | The stream from which the descriptor should be detached. |
| pDesc,if | not NULL the pointer value will be compared with stored descriptor pointers. |
| nPktId,if | not 0, the ID will be compared against stored IDs. This check is not performed if pDesc is not NULL. |
| DAS_API void DasStream_addStdProps | ( | DasStream * | pThis | ) |
Adds metadata into the property set of the DasStream.
These include the creation time, the source Id, the process id, the command line, and hostname.
| DAS_API void DasStream_addCmdLineProp | ( | DasStream * | pThis, |
| int | argc, | ||
| char * | argv[] | ||
| ) |
Adds the command line into the property set of the DasStream.
This can be useful when debugging.
| DAS_API PktDesc * DasStream_createPktDesc | ( | DasStream * | pThis, |
| DasEncoding * | pXEncoder, | ||
| das_units | xUnits | ||
| ) |
Creates a descriptor structure that for a stream packet type.
Initially this descriptor will only have xtags, but additional data planes are added. The packet ID for the new descriptor is automatically assigned so to be the lowest legal ID not currently in use.
| pThis | The stream descriptor object that will receive the new packet type. |
| xUnits | is a UnitType (currently char *) that describes the data. Generally this is used to identify times (e.g.UNIT_MJ1958,UNIT_US2000) or is UNIT_DIMENSIONLESS, but other UnitTypes are defined (e.g. UNIT_HERTZ, UNIT_DB). |
| pXEncoder | The encoder for X-plane values on this stream. The DasStream object takes ownership of the encoder's memory. |
Make a deep copy of a PacketDescriptor on a new stream.
This function makes a deep copy of the given packet descriptor and places it on the provided stream. Note, packet ID's are not preserved in this copy. The newly allocated PacketDescriptor may not have the same packet ID as the old one.
| pThis | the stream to get the new packet descriptor |
| pd | The packet descriptor to clone onto the stream |
| DAS_API PktDesc * DasStream_clonePktDescById | ( | DasStream * | pThis, |
| const DasStream * | pOther, | ||
| int | nPktId | ||
| ) |
Deepcopy a PacketDescriptor from one stream to another.
The copy made by this function handles recursing down to all the planes and properties owned by the given packet descriptor. Unlike the the function clonePacketDescriptor() the packet ID is preserved across the copy.
| pThis | the stream descriptor to get the new packet descriptor |
| pOther | the stream descriptor who's packet descriptor is copied |
| nPktId | the id of the packet to copy, a value in the range of 0 to 99 inclusive. |
Get the packet descriptor associated with an ID.
| pThis | The stream object which contains the packet descriptors. |
| id | The numeric packet ID, a value from 1 to 99 inclusive. |
Get any descriptor associated with a packet ID.
| pThis | The stream object which contains the packet descriptors. |
| id | The numeric packet ID, currently a value from 1 to 99 inclusive |
If a data container is owned by this stream object, return it's packet ID.
| pThis | The stream that is being asked to provide the ID |
| pDesc | The owned object descriptor |
| DAS_API DasErrCode DasStream_encode2 | ( | DasStream * | pThis, |
| DasBuf * | pBuf | ||
| ) |
Encode a DasStream to an version 2 XML string.
| pThis | The stream descriptor to encode |
| pBuf | A DasBuffer item to receive the bytes |
| DAS_API DasErrCode DasStream_encode3 | ( | DasStream * | pThis, |
| DasBuf * | pBuf | ||
| ) |
Encode a DasStream to an version 3 XML string.
| pThis | The stream descriptor to encode |
| pBuf | A DasBuffer item to receive the bytes |
|
inherited |
For use in derived destructors, frees the property array.
After calling this function, no new properties can be added
|
inherited |
Resets the property count to 0, but frees no memory.
Call this to re-use a descriptor.
Check to see if two descriptors contain the same properties Note, the order of the properties may be different between the descriptors but if the contents are the same then the descriptors are considered to be equal.
Note that parent descriptor properties are not checked when handling the comparison.
| pThis | The first descriptor |
| pOther | The second descriptor |
The the parent of a Descriptor.
Plane descriptors are owned by packet descriptors and packet descriptors are owned by stream descriptors. This function lets you craw the ownership hierarchy
| pThis |
|
inherited |
Get the number of properties in a descriptor.
Descriptor's have a hierarchy. In general when a property is requested, if a given Descriptor does not have a property the request is passed to the parent descriptor. This function only returns the number of properties in the given descriptor. It does not include properties owned by parents or ancestors.
This is useful when iterating over all properties in a descriptor.
| pThis | A pointer to the descriptor to query |
Get a property name by an index.
This is useful when iterating over all properties in a Descriptor. Only valid properties owed by a descriptor are queried in this manner. Parent descriptors are not consulted.
| pThis | A pointer to the descriptor to query |
| uIdx | The index of the property, will be a value between 0 and the return value from Desc_length(). For efficient storage properties that have been erased or over-written are left in place internally and just marked as invalid. |
|
inherited |
Get a property name by an index.
This is useful when iterating over all properties in a Descriptor. Only properties owed by a descriptor are queried in this manner. Parent descriptors are not consulted.
| pThis | A pointer to the descriptor to query |
| uIdx | The index of the property, will be a value between 0 and the return value from Desc_length() |
|
inherited |
Get a property value by an index.
This is useful when iterating over all properties in a Descriptor. Only properties owned by a descriptor are queried in this manner. Parent descriptors are not consulted.
| pThis | A pointer to the descriptor to query |
| uIdx | The number of the property, will be a value from 0 and 1 less than the return value from Desc_length() |
|
inherited |
Generic property setter.
All properties are stored internally as strings. The various typed Desc_setProp* functions all call this function after converting their arguments to strings.
| pThis | The Descriptor to receive the property |
| sType | The Type of property. This value is passed down to DasProp_init2(), see that function for a list of known values. |
| sName | The property name. For das2 & das3 this can't contain spaces. |
| sVal | The value, which may be anything including NULL |
|
inherited |
Create or set a existing property.
Other then memory handling, this is just a wrapper on DasProp_init. See @DasProp_init for the argument description
|
inherited |
Overwrite, or copy-in a fully formatted property.
|
inherited |
Get the type string for a property.
|
inherited |
Get a raw property string.
This function performs no transforms from the backing store.
|
inherited |
Determine if a property is present in a Descriptor or it's ancestors.
| pThis | the descriptor object to query |
| sName | the name of the property to retrieve. |
|
inherited |
Does this descriptor alone have this property.
The question does not cascade up the tree
|
inherited |
Remove a property from a descriptor, if preset.
It is safe to call this function for properties not present on the descriptor, it simply does nothing and returns false.
|
inherited |
Get a multi-valued string property.
Some properties, especially those from DSDF files, contain multiple string values in a single field separated by pipe "|" characters. For example:
data_01 = 'efield | Electric field intensity | V m**-1'
This function breaks these values into multiple strings without requiring heap memory.
Output bytes are copied into the given val_buf. Then null values are then written over all leading and trailing whitespace for each element as well as the pipe characters.
Finally a pointer to each starting string is copied into ptr_buf. If an element contains not data, for example:
coord_01 = 'frequency | | Hz'
then the corresponding character pointer will be NULL, but the number of character pointers is unchanged.
| [in] | pThis | the descriptor to query |
| [in] | name | the name of the property to retrieve |
| [out] | val_buf | will hold the full output property data |
| [in] | val_buf_sz | the maximum number of bytes to copy out including the terminating null character. |
| [out] | ptr_buf | will hold pointers to the start of each property value. If a value is empty, the corresponding pointer is null. |
| [in] | ptr_buf_sz | the maximum number of string pointers write to ptr_buf. |
|
inherited |
SetProperty methods add properties to any Descriptor (stream,packet,plane).
The typed methods (e.g. setPropertyDatum) property tag the property with a type so that it will be parsed into the given type.
|
inherited |
Read the property of type double named sName.
The property value is parsed using sscanf.
|
inherited |
Set property of type double.
Get the a numeric property in the specified units.
Descriptor properties my be provided as Datums. Datums are a double value along with a specified measurement unit.
| pThis | The Descriptor containing the property in question. |
| sName | The name of the property to retrieve. |
| units | The units of measure in which the return value will be represented. If the property value is stored in a different set of units than those indicated by this parameter than the output will be converted to the given unit type. |
|
inherited |
Set property of type Datum (double, UnitType pair)
If a property with this name already exists it is 1st deleted and then the new property is added in its place.
| pThis | The descriptor to receive the property |
| sName | The name of the property to set |
| rVal | The numeric value of the property |
| units | The units of measure for the property |
|
inherited |
Get the values of an array property.
Space for the array is allocated by getDoubleArrayFromString. and nitems is set to indicate the size of the array.
| [in] | pThis | the descriptor object to query |
| [in] | sName | the name of the property to retrieve |
| [out] | nitems | a pointer to a an integer containing the number of values in the returned array. |
|
inherited |
Get a property integer value.
| pThis | the descriptor object to query |
| sName | the name of the property to retrieve |
|
inherited |
Get a property boolean value.
| pThis | the descriptor object to query |
| sName | the name of the property to retrieve |
|
inherited |
Get a property of type DatumRange with unconverted strings.
This version is handy if you just want to know the intrinsic units of the range without converting the values to some specific type of double value.
|
inherited |
Set the property of type float array.
Note the array is cast to a double array before encoding.
Deepcopy properties into a descriptor.
| pThis | the descriptor to receive a copy of the properties |
| pOther | the descriptor with the properties to be copied. |
|
inherited |
Encode a generic set of properties to a buffer.
| pThis | The descriptors who's properties should be encoded |
| pBuf | A buffer object to receive the XML data |
| sIndent | An indent level for the property strings, makes 'em look nice |
|
inherited |
Encode das3 properties WITHOUT the enclosing properties element.
For elements that ARE property arrays (stream context entries), whose
children ride bare.
| void* pUser |
User data pointer.
The stream->packet->plane hierarchy provides a good organizational structure for application data, especially for applications whose purpose is to filter streams. This pointer can be used to hold a reference to information that is not serialized. It is initialized to NULL when a PacketDescriptor is created otherwise the library doesn't deal with it in any other way.