Maps

Introduction

A FSTR::Map is analogous to the Wiring HashMap class, allowing content to be indexed using a key value.

The Map contains an array of FSTR::MapPair structures:

struct MapPair<KeyType, ContentType> {
   KeyType key_;
   ContentType* content_;
};

KeyType can be any simple type such as char, int, float, enum etc. It may also be a String Object (or, more precisely, String*).

ContentType can be any Object type (String, Array, Vector or Map). This allows hierarchical structures to be created.

Example: int ⇒ String

Here’s a basic example using integer keys:

#include <FlashString/Map.hpp>

IMPORT_FSTR_LOCAL(content1, PROJECT_DIR "/files/index.html");
IMPORT_FSTR_LOCAL(content2, PROJECT_DIR "/files/favicon.html");

DEFINE_FSTR_MAP(intmap, int, FSTR::String,
   {35, &content1},
   {180, &content2}
);

You should generally use IMPORT_FSTR_LOCAL() when referencing imported objects. If you need global access to imported data as well, then use IMPORT_FSTR().

Note

Older toolchains (generally GCC earlier than version 6) will fail to compile with error: the value of ‘FS_content1’ is not usable in a constant expression.

You can work around this as follows:

IMPORT_FSTR(content1, PROJECT_DIR "/files/index.html"); // Make this a global reference
IMPORT_FSTR_LOCAL(content2, PROJECT_DIR "/files/favicon.html");

DEFINE_FSTR_MAP(intmap, int, FSTR::String,
   {35, &FSTR_DATA_NAME(FS_content1).as<FSTR::String>()}, // Cast the actual content
   {180, &content2}
);

We can now do this:

void printValue(int key)
{
   auto value = intmap[key];
   if(value) {
      Serial.printf("Found '%u' in map, containing %u chars\n", value.key(), value.content().length());
      Serial.println(value.printer());
   } else {
      Serial.printf("Couldn't find '%u' in map\n", key);
   }
}

Example: String ⇒ String

Both the key and the content are stored as Strings:

#include <FlashString/Map.hpp>

DEFINE_FSTR_LOCAL(key1, "index.html");
DEFINE_FSTR_LOCAL(key2, "favicon.ico");
IMPORT_FSTR_LOCAL(content1, PROJECT_DIR "/files/index.html");
IMPORT_FSTR_LOCAL(content2, PROJECT_DIR "/files/favicon.html");

DEFINE_FSTR_MAP(fileMap, FlashString, FlashString,
   {&key1, &content1},
   {&key2, &content2},
);

We can now do this:

void onFile(HttpRequest& request, HttpResponse& response)
{
   String fileName = request.uri.getRelativePath();
   auto& value = fileMap[fileName];
   if(value) {
      // Found
      Serial.printf("Found '%s' in fileMap\n", String(value.key()).c_str());
      auto stream = new FlashMemoryStream(value);
      response.sendDataStream(stream, ContentType::fromFullFileName(fileName));
   } else {
      Serial.printf("File '%s' not found\n", fileName.c_str());
   }
}

Note

As with Vector<String>, Map<String, ...> lookups are by default case-insensitive.

If you require a case-sensitive lookup, use the indexOf method with ignoreCase = false.

Structure

The macro in the first example above produces a structure like this:

constexpr const struct {
   ObjectBase object;
   MapPair<int, String> data[2];
} __fstr__intmap PROGMEM = {
   {16},
   {35, &content1},
   {180, &content2},
};
const Map<int, String>& intmap = __fstr__intmap.object.as<Map<int, String>>();

Note: FSTR:: namespace qualifier omitted for clarity.

Usually, each MapPair is 8 bytes, but if the key is a double or int64 it would be 12 bytes.

Macros

DECLARE_FSTR_MAP(name, KeyType, ContentType)

Declare a global Map& reference.

Note

Use DEFINE_FSTR_MAP to instantiate the global object

Parameters
  • name – Name of the Map& reference to define

  • KeyType – Integral type to use for key

  • ContentType – Object type to declare for content

DEFINE_FSTR_MAP(name, KeyType, ContentType, ...)

Define a Map Object with global reference.

Note

Size will be calculated

Parameters
  • name – Name of the Map& reference to define

  • KeyType – Integral type to use for key

  • ContentType – Object type to declare for content

  • ... – List of MapPair definitions { key, &content }

DEFINE_FSTR_MAP_LOCAL(name, KeyType, ContentType, ...)

Like DEFINE_FSTR_MAP except reference is declared static constexpr.

DEFINE_FSTR_MAP_SIZED(name, KeyType, ContentType, size, ...)

Define a Map Object with global reference, specifying the number of elements.

Parameters
  • name – Name of the Map& reference to define

  • KeyType – Integral type to use for key

  • ContentType – Object type to declare for content

  • size – Number of elements

  • ... – List of MapPair definitions { key, &content }

DEFINE_FSTR_MAP_SIZED_LOCAL(name, KeyType, ContentType, size, ...)

Like DEFINE_FSTR_MAP_SIZED except reference is declared static.

DEFINE_FSTR_MAP_DATA(name, KeyType, ContentType, ...)

Define a Map data structure.

Note

Size will be calculated

Parameters
  • name – Name of data structure

  • KeyType – Integral type to use for key

  • ContentType – Object type to declare for content

  • ... – List of MapPair definitions { key, &content }

DEFINE_FSTR_MAP_DATA_SIZED(name, KeyType, ContentType, size, ...)

Define a Map data structure, specifying the number of elements.

Parameters
  • name – Name of data structure

  • KeyType – Integral type to use for key

  • ContentType – Object type to declare for content

  • size – Number of elements

  • ... – List of MapPair definitions { key, &content }

template<typename KeyType, class ContentType>
class MapPair
#include <MapPair.hpp>

describes a pair mapping key => data for a specified key type

tparam KeyType

Integral, floating point, enum or String

tparam ContentType

Object type to use for content

Class Templates

template<typename KeyType, class ContentType, class Pair = MapPair<KeyType, ContentType>>
class FSTR::Map : public FSTR::Object<Map<KeyType, ContentType>, MapPair<KeyType, ContentType>>

Class template to access an associative map.

tparam KeyType

tparam ContentType

Public Functions

inline const Pair valueAt(unsigned index) const

Get a map entry by index, if it exists.

Note

Result validity can be checked using if()

template<typename TRefKey, typename T = KeyType>
inline std::enable_if<!std::is_class<T>::value, int>::type indexOf(const TRefKey &key) const

Lookup an integral key and return the index.

Parameters

key – Key to locate, must be compatible with KeyType for equality comparison

Returns

int – If key isn’t found, return -1

template<typename TRefKey, typename T = KeyType>
inline std::enable_if<std::is_same<T, String>::value, int>::type indexOf(const TRefKey &key, bool ignoreCase = true) const

Lookup a String key and return the index.

Parameters
  • key

  • ignoreCase – Whether search is case-sensitive (default: true)

Returns

int – If key isn’t found, return -1

template<typename TRefKey>
inline const Pair operator[](const TRefKey &key) const

Lookup a key and return the entry, if found.

Note

Result validity can be checked using if()

Parameters

key

inline MapPrinter<Map> printer() const

Returns a printer object for this array.

Note

ElementType must be supported by Print

template<typename KeyType, class ContentType>
class FSTR::MapPair

describes a pair mapping key => data for a specified key type

tparam KeyType

Integral, floating point, enum or String

tparam ContentType

Object type to use for content

Public Functions

inline operator IfHelperType() const

Provides bool() operator to determine if Pair is valid.

template<typename T = KeyType>
inline std::enable_if<!std::is_class<T>::value, KeyType>::type key() const

Get the key (non-class key types)

template<typename T = KeyType>
inline std::enable_if<std::is_same<T, String>::value, const KeyType&>::type key() const

Get the key (String key type)

inline const ContentType &content() const

Accessor to get a reference to the content.

Public Static Functions

static inline const MapPair empty()

Get an empty Pair object, identifies as invalid when lookup fails.