class UniversalContainer

#include "ucontainer.h"

Overview

The UniversalContainer class is an attempt to provide a class which can act in a manner similar to the untyped variables, arrays, and hash-maps found in popular scripting languages such as perl and PHP. The result is a class that can hold a wide variety of data and meta-data in a very flexible structure. UniversalContainers can hold integers, doubles, booleans, single characters, strings, and wide character strings. UniversalContainers may also be used as associative maps between strings and other UniversalContainers, or as arrays of UniversalContainers.

See an example.

To use a UniversalContainer, simply create an instance and assign any scalar value to the class. Alternatively, you can use the container as an associative map, using the overloaded brackets operator to assign a scalar or other UniversalContainer to a string based key. If you use the brackets operator with a numerical index, the UniversalContainer will act as an array which expands the first time you access the first element beyond the end of the list. To retrieve a value from a UniversalContainer, simply cast the UC back to the desired type. Illegal casts result in a run time exception, but every effort is made to make a meaningful cast. For example, you can always cast an integer to a string containing the decimal representation, and strings which hold the decimal representation of a number may be cast to integers; while strings holding other values will result in a runtime exception.

The Libuc library also contains a number of useful routines and classes for working with UniversalContainers. This includes routines for serialization, database access, CGI programming, and REST style remote calls.

Assignment Operators

The UniversalContainer class overloads the assignment operator for types long, int, double, bool, char, char*, std::string, std::wstring, and itself. The first time a value is assigned to a container, the container will adopt the appropriate type. Future attempts to assign values of different types to a container will result in an exception. The exception to this rule is that types int, long, and double allow for other numeric values to be assigned, and the container will switch to properly reflect its new contents.

Internally, if a UniversalContainer is holding an string, wstring, map, or an array, it allocates that appropriate structure on the heap and stores a pointer to it. The copy constructor for UniversalContainer copies only the pointer, resulting in a shallow copy when the UC holds data of these types. This allows for UniversalContainers to be passed to and returned from functions without excessive overhead, but it also means that UniversalContainers holding this type of information are essentially passed by reference. The number of UniversalContainers referring to a given collection or string is tracked so that memory is not leaked when UniversalContainers are copied and destroyed. The clone method can be used when a deep copy is needed.

The assignment operator also does assignments for arrays and maps by reference, so that if one container is set equal to another, any changes to the array or map stored by one are reflected by the other. In the event that a UniversalContainer holding a string or wstring is assigned to hold another string, the reference count is adjusted and the previous string is deleted only if no other containers are holding the string.

Casting Operators

The UniversalContainer class overloads a number of casting operators, allowing instances holding scalars to be used in place of scalars of the matching type. Casting an integer or double value to another value of those types will result in a result similar to a standard cast of the appropriate type. Bools can be cast to chars, resulting in either 't' or 'f'. Attempting to cast any scalar to a string will result in the appropriate conversion, with booleans returning a value of "true" or "false", and null containers return "null". Attempting to cast a container to an unsupported type results in an exception being thrown. Casting a container does not affected the underlying container.

Attempting to cast an array to a boolean will result in the value "true" being returned. Attempting to cast a map to a boolean will result in the value true, unless the map contains the key "#boolean_value". In this case the container will evaluate to the value stored in the map with this key. This convention is used by various routines in the utility library to signal a failure. The #boolean_value key is set to false, and other fields are set to describe the failure. This allows the caller of the routine to check for success or failure by simply testing true/false on the container, and then extracting appropriate information based on the result.

The Brackets[] Operator

The brackets operator on the UniversalContainer class has been overloaded, allowing the container to act as both an associative map and a vector, depending on the key. Using a string within the brackets cause the container to act as a map, mapping between the given string another UniversalContainer. Numerical indexes cause the container to act as zero-based vector. Due to limitations of the underlying std::vector template, a new element is created if an attempt to index one beyond the current size of the vector is made. Thus, the to create an array you should first reference the zeroth element, then the first, and so on. Skipping indexes will result in an exception being thrown. See the section on exceptions below for details.

Maps and Arrays may nest other maps and arrays, accessed by stacking uses of the brackets operator in the same fashion as accessing a multidimensional array. Alternatively, a dot notation may be used to access nested maps and arrays. For instance, if a container uc maps the string "foo" to another map, which in term contains an element mapped to bar, you can access that element as either uc["foo.bar"] or uc[foo][bar]. Array elements may be referenced by numerical index in a similar fashion. For instance, if the second element of a vector contains a map with key "beta", you could access the value associated with beta with my_uc_array["1.beta"].

Constructors

UniversalContainer(void)

UniversalContainer(int)

UniversalContainer(long)

UniversalContainer(double)

UniversalContainer(bool)

UniversalContainer(char)

UniversalContainer(const std::string)

UniversalContainer(const std::string)

UniversalContainer(char*)

UniversalContainer(UniversalContainerType, void*)

UniversalContainer(const UniversalContainer&)

A newly constructed UniversalContainer usually has a null type, waiting for a particular value to be assigned to it. Alternatively, it may be constructed holding a particular scalar value.

It is possible to create a UniversalContainer that holds an arbitrary reference. The constructor that takes a UniversalContainerType and a void* will create a UC that holds the given reference with an arbitrary type associated with it. The provided tid (type id) a type id previously registered with register_new_type or an exception will be thrown with code uce_Unregistered_Type. See Extending UniversalContainer and the example_adapter.cpp file for a discussion and example of how to use this constructor to extend UniversalContainer.

The copy constructor for the UniversalContainer does a shallow copy. Strings, maps, and vectors held by the container are held be reference, and a copy will point to the same underlying data. This increases efficiency when using UniversalContainers in a function call or returning then as results. When a deep copy of a UniversalContainer is required, use the clone method. UniversalContainers maintain a shared count of the data objects they point to, and the last copy to be destroyed will cleanup the memory used.

Methods

void init_map(void)

void init_array(void)

These methods set a null UniversalContainer to be an empty array and an empty map respectively. They are intended to only be called on freshly created containers, to force them to be taken on an identity as arrays or maps respectively; without having to assign a value to them using the [] operator. Useful when you want to return something that is an empty array or empty map.

void string_interpret(const std::string s)

This method is a pseudo-constructor, intended to be invoked on newly created, null valued, UniversalContainers in order to initialize them according to the data type represented by the contents of a string. First, the method attempts to convert the input to a long. If the input can not be converted to a long, it then attempts to convert it to a double. If this also fails, it checks the length of the input. If length is one it stores the single character. If there is more than one character, a case insensitive comparison is then made to the strings "true", "false", and "null". A match results in the appropriate value being set. Otherwise, the input is stored as a string. Attempts to invoke this method on a non-null container will result in an exception of uce_TypeMismatch_Write.

UniversalContainer& added_element(void)

If the container is a vector, appends an element to the end of the vector and returns a reference to it. If the container is not a vector, throws an exception with type uce_Non_Array_as_Array.

virtual UniversalContainer clone(void)

Returns a deep copy of the object. By default, the copy constructor for Universal Container returns a shallow copy. See the notes under the copy constructor for details on why this may be important. Note that this is one of the very few virtual methods in UniversalContainer, to allow UC to be extended to hold new types.

UniversalContainer partial_copy(const char** fields) const

Returns a partial deep copy of the container, assuming it is map or array. The argument field must a null terminated array of C strings. For instance, {"foo", "bar", NULL}. A new UniversalContainer is created, each string in fields is check to see if a matching key exists in the container. If there a match, the result is cloned and inserted into the new object with a matching key.

void clear(void)

Clears the current container. The containers type is reset to null, allowing it to once again contain any type of data assigned to it. If the container previously contained a string, map, or vector the data object will be deleted if and only if it is no longer referenced by another UniversalContainer.

bool exists(std::string const& key) const

Return true if the given key exists in the map, return false otherwise. If used on a container which is not a map it will return false.

UniversalContainerType get_type(void)

Returns the type associated with this particular instance. UniversalContainerType is a logical enumeration, it may take any of the following values. uc_Integer, uc_Boolean, uc_Character, uc_String, uc_Real, uc_Null, uc_Array, uc_Map, uc_Reference, or uc_Unknown.

If get_type returns something not on the previous list, you are probably dealing with a child class holding some other kind of data. This can happen.

std::vector<std::string> keys_for_map(void)

Returns a vector of strings, giving the keys for the map. Otherwise, throws uce_Non_Map_as_Map.

bool remove(const std::string key)

Removes the given key from a map, or throws a uce_Non_Map_as_Map exception if used on a container which is not a map.

size_t size(void)

size_t length(void)

Return the number of keys in the map, or the number of elements in the vector, or zero for containers holding atomic types.

const char* c_str(void) const

If the UniversalContainer is holding a string, this returns the pointer to the underlying characters just like calling string::c_str. Otherwise, it throws uce_TypeMismatch_Read.

virtual string to_string(void) const

The to_string method returns a string which represents the contents of the object. In most cases, it is preferable to cast the UC as a string as the cast operators are not virtual. The to_string method exists to provide a virtual method for child classes to override and provide a string representation of their contents. The various encoder utility methods use to_string rather than a string class so that they can attempt to serialize child objects.

UniversalMap::iterator map_begin(void)

UniversalMap::iterator map_end(void)

UniversalArray::iterator vector_begin(void)

UniversalArray::iterator vector_end(void)

UniversalMap& get_map(void)

UniversalArray& get_vector(void)

When used to store maps and arrays, UniversalContainers do so by pointing to UniversalMap and UniversalArray objects, as appropriate. These are typedefed as std::map<std::string,UniversalContainer> and std::vector<UniversalContainer> respectively. These methods return the corresponding iterators, or the underlying object as appropriate. These may be useful for using UniversalContainer with algorithms from the C++ STL, or otherwise manipulating the structure of the map or vector. If used on a container with the inappropriate type they will throw an exception.

bool is_dirty(void)

void clean(void)

Each UniversalContainer contains a dirty flag, which indicates if its contents have changed. This flag is set to true when the UC is first constructed, and reset to true whenever the container takes on a new atomic value. Array type containers have this flag set whenever an element is added. This flag is only cleared when the clean method is called.

For UCs containing atomic types, is_dirty returns true when the flag is set. For arrays and maps, this method returns true when the flag is set or whenever the is_dirty method of one of their component elements returns true.

static UniversalContainerType register_new_type(UniversalContainerTypeAdapter* uca)

The register_new_type method associates an instance of UniversalContainerTypeAdapter with a new UniversalContainerType, which it returns. See Extending UniversalContainer and the example_adapter.cpp file for a discussion and example of how to to extend UniversalContainer.

void* cast_or_throw(UniversalContainerType type) const;

The cast_or_throw method is used to retrieve an arbitrary reference stored in a UniversalContainer with reference and type constructor. It will check that the type of the UniversalContainer matches the provided type. If it matches, it returns the pointer held by the container. If the type does not match, an exception is thrown with the code uce_TypeMismatch_Read. It is expected that application code will not use this method. Instead, this method will be used adapters which extend the ability of UniversalContainer to hold arbitrary types. See Extending UniversalContainer and the example_adapter.cpp file for a discussion and example of how to to extend UniversalContainer.

ucexception(int code)

This macro takes one of the codes from the table below, and constructs a UniversalContainer representing an exception. The intended use is as part of a throw statement such as : throw ucexception(uce_Unknown);. If debugging information is available this UC will contain information about the function, file, and line where it was invoked.

Exceptions

Many of the methods and overloaded operators defined by the UniversalContainer class can result in an exception being thrown. The thrown exceptions are themselves UniversalContainers, containing a map with a variety of information. Although these are runtime errors, most of the exceptions represent programming errors. In most cases, a top level try/catch block which prints the exception for debugging purposes is sufficient.

Many of the functions which work with UniversalContainers, such as the database routines and the various serialization and deserialization routines also throw UniversalContainers as exceptions.

Contents of a thrown UniversalContainer

Key Value
code An integer error code. See the following table.
message A string describing the error. See the following table.
function The name of the function which originally threw the exception. Only available if the library was built with debugging information.
file The name of the file where the exception was thrown. Only available if the library was built with debugging information.
line The line number that threw the exception. Only available if the library was built with debugging information.
container A copy of the UniversalContainer that threw the exception. Only available if the exception was thrown from within the UniversalContainer exception itself.

Error Codes and Messages

CodeMessage
uce_Unknown Unknown UniversalContainer exception.
uce_TypeMismatch_Write Attempt to assign a variable to a container which already contains a variable of another type.
uce_TypeMismatch_Read Attempt to cast a variable to an incompatible type.
uce_Collection_as_Scalar Attempt to treat a collection as a scalar variable.
uce_Scalar_as_Collection Attempt to treat a scalar variable as a collection.
uce_Non_Map_as_Map Attempt to treat a non-map container as a map.
uce_Non_Array_as_Array Attempt to treat a non-array container as an array.
uce_Array_Subscript_Out_of_Bounds Array subscript out of bounds.
uce_Deserialization_Error Input is not a valid serialized UniversalContainer.
uce_Serialization_Error Unable to serialize containers with elements of an unknown type.
uce_ContractViolation The given container does not conform to the contract.
uce_DB_Connection Error connecting to the database.