Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ObjectMap #1638

Merged
merged 11 commits into from
Feb 25, 2019
6 changes: 3 additions & 3 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,9 @@ For a complete example take a look at the [Basic_rBoot](samples/Basic_rBoot/app/
### Embedded HTTP Web Server
```c++
server.listen(80);
server.addPath("/", onIndex);
server.addPath("/hello", onHello);
server.setDefaultHandler(onFile);
server.paths.set("/", onIndex);
server.paths.set("/hello", onHello);
server.paths.setDefault(onFile);

Serial.println("=== WEB SERVER STARTED ===");
Serial.println(WifiStation.getIP());
Expand Down
271 changes: 271 additions & 0 deletions Sming/SmingCore/Data/ObjectMap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* ObjectMap.h
*
* @author: 31 Jul 2018 - Mikee47 <[email protected]>
*
*/

#ifndef _SMING_CORE_DATA_OBJECT_MAP_H_
#define _SMING_CORE_DATA_OBJECT_MAP_H_

#include "WVector.h"

/** @brief Implementation of a HashMap for owned objects, i.e. anything created with new().
* @note Once added to the map the object is destroyed when no longer required.
*
* To free an object, use one of:
*
* ```
* map.remove(key);
* map.removeAt(index);
* map[key] = nullptr; // Free existing object and set to null
* ```
*/
template <typename K, typename V> class ObjectMap
{
public:
ObjectMap()
{
}

~ObjectMap()
{
clear();
}

/* Allows operator[] to be used to safely set values */
class Value
{
public:
/* Functor to provide guarded access to values */
Value(V*& value) : value(value)
{
}

Value& operator=(V* newValue)
{
delete value;
value = newValue;
return *this;
}

operator const V*() const
{
return value;
}

operator V*()
{
return value;
}

V* operator->()
{
return value;
}

private:
V*& value;
};

/**
* @brief Get the number of entries in this map
* @retval int Entry count
*/
unsigned count() const
{
return entries.count();
}

/*
* @brief Get a key at a specified index, non-modifiable
* @param idx the index to get the key at
* @return The key at index idx
*/
const K& keyAt(unsigned idx) const
{
return entries[idx].key;
}

/*
* @brief Get a key at a specified index
* @param idx the index to get the key at
* @return Reference to the key at index idx
*/
K& keyAt(unsigned idx)
{
return entries[idx].key;
}

/*
* @brief Get a value at a specified index, non-modifiable
* @param idx the index to get the value at
* @retval The value at index idx
* @note The caller must not use `delete` on the returned value
*/
const V* valueAt(unsigned idx) const
{
return entries[idx].value;
}

/*
* @brief Get a value at a specified index
* @param idx the index to get the value at
* @retval The value at index idx
* @note Because a reference is returned any existing value must be `delete`d first
* @see `operator[]`
*/
Value valueAt(unsigned idx)
{
return entries[idx].value;
}

/**
* @brief Get value for given key, if it exists
* @param key
* @retval const V* Will be null if not found in the map
*/
const V* operator[](const K& key) const
{
return find(key);
}

/** @brief Access map entry by reference
* @param key
* @retval Value Guarded access to mapped value corresponding to given key
* @note If the given key does not exist in the map then it will be created and a null value entry returned.
Copy link
Contributor Author

@mikee47 mikee47 Feb 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid this (usually) un-desirable side-effect we could take advantage of our Value class and use it to create an entry in the map only when a value is actually assigned.

*
* Example:
*
* ```
* void test()
* {
* ObjectMap<String, MyType> map;
* MyType* object1 = new MyType();
* map["key1"] = object1;
* auto value = map["key1"]; // value now refers to object1
* value = nullptr; // Free object1
* MyType* object2 = new MyType();
* value = object2;
* // As soon as `map` goes out of scope, object2 is released
* }
* ```
*
* @see `valueAt()`
*
*/
Value operator[](const K& key)
{
int i = indexOf(key);
if(i >= 0) {
return entries[i].value;
}

auto entry = new Entry(key, nullptr);
entries.addElement(entry);
return entry->value;
}

/** @brief Set a key value
* @param key
* @param value
*/
void set(const K& key, V* value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That set will cause confusion. We have two ways to add things to the map that behave diffently:

ObjectMap<String, MyType> map;
map["key1"] = object1;
// and 
map.set("key1, object1);

The best would have been to make map["key1"] = object1; behave as set. But probably it would be better to remove completely V& operator[](const K& key) since I cannot think of a proper way to implement the above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The best would have been to make map["key1"] = object1; behave as set

Done :-)

{
operator[](key) = value;
}

/**
* @brief Find the value for a given key, if it exists
* @param key
* @retval V* Points to the object if it exists, otherwise nullptr
* @note If you need to modify the existing map entry, use `operator[]` or `valueAt()`
*/
V* find(const K& key) const
{
int index = indexOf(key);
return (index < 0) ? nullptr : entries[index].value;
}

/**
* @brief Get the index of a key
* @param key
* @retval int The index of the key, or -1 if key does not exist
*/
int indexOf(const K& key) const
{
for(unsigned i = 0; i < entries.count(); i++) {
if(entries[i].key == key) {
return i;
}
}
return -1;
}

/**
* @brief Check if a key is contained within this map
* @param key the key to check
* @retval bool true if key exists
*/
bool contains(const K& key) const
{
return indexOf(key) >= 0;
}

/**
* @brief Remove entry at given index
* @param index location to remove from this map
*/
void removeAt(unsigned index)
{
entries.remove(index);
}

/**
* @brief Remove a key from this map
* @param key The key identifying the entry to remove
*/
void remove(const K& key)
{
int index = indexOf(key);
if(index >= 0) {
removeAt(index);
}
}

/**
* @brief Clear the map of all entries
*/
void clear()
{
entries.clear();
}

protected:
struct Entry {
K key;
V* value = nullptr;

Entry(const K& key, V* value) : key(key), value(value)
{
}

~Entry()
{
delete value;
}
};

Vector<Entry> entries;

private:
// Copy constructor unsafe, so prevent access
ObjectMap(const ObjectMap<K, V>& that);
};

#endif // _SMING_CORE_DATA_OBJECT_MAP_H_
5 changes: 0 additions & 5 deletions Sming/SmingCore/Network/Http/HttpRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,6 @@ void HttpRequest::reset()
responseStream = nullptr;

postParams.clear();
for(unsigned i = 0; i < files.count(); i++) {
String key = files.keyAt(i);
delete files[key];
files[key] = nullptr;
}
files.clear();
}

Expand Down
5 changes: 3 additions & 2 deletions Sming/SmingCore/Network/Http/HttpRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "Data/Stream/MultipartStream.h"
#include "HttpHeaders.h"
#include "HttpParams.h"
#include "Data/ObjectMap.h"

class HttpClient;
class HttpServerConnection;
Expand Down Expand Up @@ -111,7 +112,7 @@ class HttpRequest
*/
HttpRequest* setFile(const String& formElementName, IDataSourceStream* stream)
{
if(stream) {
if(stream != nullptr) {
files[formElementName] = stream;
}
return this;
Expand Down Expand Up @@ -288,7 +289,7 @@ class HttpRequest
#endif

private:
HashMap<String, IDataSourceStream*> files;
ObjectMap<String, IDataSourceStream> files;

HttpParams* queryParams = nullptr; // << @todo deprecate
};
Expand Down
27 changes: 3 additions & 24 deletions Sming/SmingCore/Network/Http/HttpResource.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#define _SMING_CORE_NETWORK_HTTP_HTTP_RESOURCE_H_

#include "WString.h"
#include "WHashMap.h"
#include "Data/ObjectMap.h"
#include "Delegate.h"

#include "HttpResponse.h"
Expand All @@ -26,8 +26,8 @@ typedef Delegate<int(HttpServerConnection& connection, HttpRequest&, const char*
HttpServerConnectionBodyDelegate;
typedef Delegate<int(HttpServerConnection& connection, HttpRequest&, char* at, int length)>
HttpServerConnectionUpgradeDelegate;
typedef Delegate<int(HttpServerConnection&, HttpRequest&, HttpResponse&)> HttpResourceDelegate;
typedef Delegate<void(HttpRequest&, HttpResponse&)> HttpPathDelegate;
typedef Delegate<int(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response)>
HttpResourceDelegate;

class HttpResource
{
Expand All @@ -50,25 +50,4 @@ class HttpResource
HttpServerConnectionUpgradeDelegate onUpgrade = nullptr; ///< request is upgraded and raw data is passed to it
};

class HttpCompatResource : public HttpResource
{
public:
HttpCompatResource(const HttpPathDelegate& callback) : callback(callback)
{
onRequestComplete = HttpResourceDelegate(&HttpCompatResource::requestComplete, this);
}

private:
int requestComplete(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response)
{
callback(request, response);
return 0;
}

private:
HttpPathDelegate callback;
};

typedef HashMap<String, HttpResource*> ResourceTree;

#endif /* _SMING_CORE_NETWORK_HTTP_HTTP_RESOURCE_H_ */
Loading