Table of Contents Previous Next
Logo
Freeze : 39.5 The Freeze Map
Copyright © 2003-2010 ZeroC, Inc.

39.5 The Freeze Map

A Freeze map is a persistent, associative container in which the key and value types can be any primitive or user-defined Slice types. For each pair of key and value types, the developer uses a code-generation tool to produce a language-specific class that conforms to the standard conventions for maps in that language. For example, in C++, the generated class resembles a std::map, and in Java it implements the java.util.SortedMap interface. Most of the logic for storing and retrieving state to and from the database is implemented in a Freeze base class. The generated map classes derive from this base class, so they contain little code and therefore are efficient in terms of code size.
You can only store data types that are defined in Slice in a Freeze map. Types without a Slice definition (that is, arbitrary C++ or Java types) cannot be stored because a Freeze map reuses the Ice-generated marshaling code to create the persistent representation of the data in the database. This is especially important to remember when defining a Slice class whose instances will be stored in a Freeze map; only the “public” (Slice-defined) data members will be stored, not the private state members of any derived implementation class.

39.5.1 Freeze Connections

In order to create a Freeze map object, you first need to obtain a Freeze Connection object by connecting to a database environment.
As illustrated in Figure 39.3, a Freeze map is associated with a single connec­tion and a single database file. Connection and map objects are not thread-safe: if you want to use a connection or any of its associated maps from multiple threads, you must serialize access to them. If your application requires concurrent access to the same database file (persistent map), you must create several connections and associated maps.
Figure 39.3. Freeze connections and maps.
Freeze connections provide operations that allow you to begin a transaction, access the current transaction, get the communicator associated with a connection, close a connection, and remove a map index. See the online Slice reference for more information on these operations.

39.5.2 Transactions

You may optionally use transactions with Freeze maps. Freeze transactions provide the usual ACID (atomicity, concurrency, isolation, durability) properties. For example, a transaction allows you to group several database updates in one atomic unit: either all or none of the updates within the transaction occur.
You start a transaction by calling beginTransaction on the Connection object. Once a connection has an associated transaction, all operations on the map objects associated with this connection use this transaction. Eventually, you end the transaction by calling commit or rollback: commit saves all your updates while rollback undoes them. The currentTransaction operation returns the transaction associated with a connection, if any; otherwise, it returns nil.
module Freeze {

local interface Transaction {
    void commit();
    void rollback();
}; 

local interface Connection {
    Transaction beginTransaction();
    idempotent Transaction currentTransaction();
    // ...
};
};
If you do not use transactions, every non-iterator update is enclosed in its own internal transaction, and every read-write iterator has an associated internal trans­action that is committed when the iterator is closed.

Using Transactions with C++

You must ensure that you either commit or roll back each transaction that you begin (otherwise, locks will be held by the database until they time out):
ConnectionPtr connection = ...;

TransactionPtr tx = connection>beginTransaction();
try {

    // DB updates that might throw here...

    tx>commit();

    // More code that might throw here...

} catch (...) {
    try {
        tx>rollback();
    } catch (...) {
    }
    throw;
}
The outer try-catch blocks are necessary because, if the code encounters an excep­tion, we must roll back any updates that were made. In turn, the attempt to roll back might throw itself, namely, if the code following the commit throws an exception (in which case the transaction cannot be rolled back because it is already committed).
Code such as this is difficult to maintain: for example, an early return state­ment can cause the transaction to be neither committed nor rolled back. The TransactionHolder class ensures that such errors cannot happen:
namespace Freeze {
    class TransactionHolder {
    public:
        TransactionHolder(const ConnectionPtr&);
        ~TransactionHolder();

        void commit();
        void rollback();

    private:
        // Copy and assignment are forbidden.
        TransactionHolder(const TransactionHolder&);
        TransactionHolder& operator=(const TransactionHolder&);
    };
}
The constructor calls beginTransaction if the connection does not already have a transaction in progress, so instantiating the holder also starts a transaction. When the holder instance goes out of scope, its destructor calls rollback on the transaction and suppresses any exceptions that the rollback attempt might throw. This ensures that the transaction is rolled back if it was not previously committed or rolled back and ensures that an early return or an exception cannot cause the transaction to remain open:
ConnectionPtr connection = ...;

{ // Open scope

    TransactionHolder tx(connection); // Begins transaction

    // DB updates that might throw here...

    tx.commit();

    // More code that might throw here...

} // Transaction rolled back here if not previously
  // committed or rolled back.
If you instantiate a TransactionHolder when a transaction is already in progress, it does nothing: the constructor notices that it could not begin a new transaction and turns commit, rollback, and the destructor into no‑ops. For example, the nested TransactionHolder instance in the following code is benign and does nothing:
ConnectionPtr connection = ...;

{ // Open scope

    TransactionHolder tx(connection); // Begins transaction

    // DB updates that might throw here...

    { // Open nested scope

        TransactionHolder tx2(connection); // Does nothing

        // DB updates that might throw here...

        tx2.commit(); // Does nothing

        // More code that might throw here...

    } // Destructor of tx2 does nothing

    tx.commit();

    // More code that might throw here...

} // Transaction rolled back here if not previously
  // committed or rolled back.

Using Transactions with Java

You must ensure that you either commit or roll back each transaction that you begin (otherwise, locks will be held by the database until they time out):
Connection connection = ...;

Transaction tx = connection.beginTransaction();
try {

    // DB updates that might throw here...

    tx.commit();

    // More code that might throw here...

} catch (java.lang.RuntimeException ex) {
    try {
        tx.rollback();
    } catch (DatabaseException e) {
    }
    throw ex;
}
The catch handler ensures that the transaction is rolled back before re-throwing the exception. Note that the nested try-catch blocks are necessary: if the transac­tion committed successfully but the code following the commit throws an excep­tion, the rollback attempt will fail therefore we need to suppress the corresponding DatabaseException that is raised in that case.
Also use caution with early return statements:
Connection connection = ...;

Transaction tx = connection.beginTransaction();
try {

    // DB updates that might throw here...

    if (error) {
        // ...
        return; // Oops, bad news!
    }

    // ...

    tx.commit();

    // More code that might throw here...

} catch (java.lang.RuntimeException ex) {
    try {
        tx.rollback();
    } catch (DatabaseException e) {
    }
    throw ex;
}
The early return statement in the preceding code causes the transaction to be neither committed nor rolled back. To deal with this situation, avoid early return statements or ensure that you either commit or roll back the transaction before returning. Alternatively, you can use a finally block to ensure that the transac­tion is rolled back:
Connection connection = ...;

try {

    Transaction tx = connection.beginTransaction();

    // DB updates that might throw here...

    if (error) {
        // ...
        return; // No problem, see finally block.
    }

    // ...

    tx.commit();

    // More code that might throw here...

} finally {
    if (connection.currentTransaction() != null)
        connection.currentTransaction().rollback();
}

39.5.3 Iterators

Iterators allow you to traverse the contents of a Freeze map. Iterators are imple­mented using Berkeley DB cursors and acquire locks on the underlying database page files. In C++, both read-only (const_iterator) and read-write iterators (iterator) are available. In Java, an iterator is read-write if it is obtained in the context of a transaction and read-only if it is obtained outside a transaction.
Locks held by an iterator are released when the iterator is closed (if you do not use transactions) or when the enclosing transaction ends. Releasing locks held by iterators is very important to let other threads access the database file through other connection and map objects. Occasionally, it is even necessary to release locks to avoid self-deadlock (waiting forever for a lock held by an iterator created by the same thread).
To improve ease of use and make self-deadlocks less likely, Freeze often closes iterators automatically. If you close a map or connection, associated itera­tors are closed. Similarly, when you start or end a transaction, Freeze closes all the iterators associated with the corresponding maps. If you do not use transactions, any write operation on a map (such as inserting a new element) automatically closes all iterators opened on the same map object, except for the current iterator when the write operation is performed through that iterator. In Java, Freeze also closes a read-only iterator when no more elements are available.
There is, however, one situation in C++ where an explicit iterator close is needed to avoid self-deadlock:
• you do not use transactions, and
• you have an open iterator that was used to update a map (it holds a write lock), and
• in the same thread, you read that map.
Read operations in C++ never close iterators automatically: you need to either use transactions or explicitly close the iterator that holds the write lock. This is not an issue in Java because you cannot use an iterator to update a map outside of a trans­action.

39.5.4 Recovering from Deadlock Exceptions

If you use multiple threads to access a database file, Berkeley DB may acquire locks in conflicting orders (on behalf of different transactions or iterators). For example, an iterator could have a read-lock on page P1 and attempt to acquire a write-lock on page P2, while another iterator (on a different map object associated with the same database file) could have a read-lock on P2 and attempt to acquire a write-lock on P1.
When this occurs, Berkeley DB detects a deadlock and resolves it by returning a “deadlock” error to one or more threads. For all non-iterator operations performed outside any transaction, such as an insertion into a map, Freeze catches such errors and automatically retries the operation until it succeeds. (In that case, the most-recently acquired lock is released before retrying.) For other operations, Freeze reports this deadlock by raising Freeze::DeadlockException. In that case, the associated transaction or iterator is also automatically rolled back or closed. A properly written application is expected to catch deadlock exceptions and retry the transaction or iteration.

39.5.5 Key Sorting

Keys in Freeze maps and indexes are always sorted. By default, Freeze sorts keys according to their Ice-encoded binary representation; this is very efficient but the resulting order is rarely meaningful for the application. Starting with Ice 3.0, Freeze offers the ability to specify your own comparator objects so that you can customize the traversal order of your maps. Note however that the comparator of a Freeze map should remain the same throughout the life of the map. Berkeley DB stores records according to the key order provided by this comparator; switching to another comparator will cause undefined behavior.

C++

In C++, you specify the name of your comparator objects during code generation. The generated map provides the standard features of std::map, so that iterators return entries according to the order you have defined for the main key with your comparator object. The lower_bound, upper_bound, and equal_range provide range-searches (see the definition of these functions on std::map).
Apart from these standard features, the generated map provides additional functions and methods to perform range searches using secondary keys. The addi­tional functions are lowerBoundForMember, upperBoundForMember, and equalRangeForMember, where Member is the name of the secondary-key member. These functions return regular iterators on the Freeze map.

Java

In Java, you supply comparator objects (instances of the standard Java interface java.util.Comparator) at run time when instantiating the generated map class. The map constructor accepts a comparator for the main key and optionally a collection of comparators for secondary keys. The map also provides a number of methods for performing range searches on the main key and on secondary keys (see page 1559).

39.5.6 Indexing a Map

Freeze maps support efficient reverse lookups: if you define an index when you generate your map (with slice2freeze or slice2freezej), the generated code provides additional methods for performing reverse lookups. If your value type is a structure or a class, you can also index on a member of the value, and several such indexes can be associated with the same Freeze map.
Indexed searches are easy to use and very efficient. However, be aware that an index adds significant write overhead: with Berkeley DB, every update triggers a read from the database to get the old index entry and, if necessary, replace it.
If you later add an index to an existing map, Freeze automatically populates the index the next time you open the map. Freeze populates the index by instanti­ating each map entry, so it is important that you register the object factories for any class types in your map before you open the map.
Note that the index key comparator of a Freeze map index should remain the same throughout the life of the index. Berkeley DB stores records according to the key order provided by this comparator; switching to another comparator will cause undefined behavior.

39.5.7 Using a Freeze Map in C++

This section describes the code generator and demonstrates how to use a Freeze map in a C++ program.

slice2freeze Command-Line Options

The Slice-to-Freeze compiler, slice2freeze, creates C++ classes for Freeze maps. The compiler offers the following command-line options in addition to the standard options described in Section 4.20:
• headerext EXT
Changes the file extension for the generated header files from the default h to the extension specified by EXT.
• sourceext EXT
Changes the file extension for the generated source files from the default cpp to the extension specified by EXT.
• addheader HDR[,GUARD]
This option adds an include directive for the specified header at the beginning of the generated source file (preceding any other include directives). If GUARD is specified, the include directive is protected by the specified guard. For example, addheader precompiled.h,__PRECOMPILED_H__ results in the following directives at the beginning of the generated source file:
#ifndef __PRECOMPILED_H__
#define __PRECOMPILED_H__
#include <precompiled.h>
#endif
As this example demonstrates, the addheader option is useful mainly for integrating the generated code with a compiler’s precompiled header mechanism.
This option can be repeated to create include directives for several files.
• includedir DIR
Modifies #include directives in source files to prepend the path name of each header file with the directory DIR. See Section 6.16.1 for more informa­tion.
• dllexport SYMBOL
Use SYMBOL to control DLL exports or imports. See the slice2cpp description for details.
• dict NAME,KEY,VALUE[,sort[,COMPARE]]
Generate a Freeze map class named NAME using KEY as key and VALUE as value. This option may be specified multiple times to generate several Freeze maps. NAME may be a scoped C++ name, such as Demo::Struct1ObjectMap. By default, keys are sorted using their binary Ice-encoded representation. Include sort to sort with the COMPARE functor class. If COMPARE is not specified, the default value is std::less<KEY>.
• dictindex MAP[,MEMBER]
[,casesensitive|caseinsensitive][,sort[,COMPARE]]
Add an index to the Freeze map named MAP. If MEMBER is specified, the map value type must be a structure or a class, and MEMBER must be a member of this structure or class. Otherwise, the entire value is indexed. When the indexed member (or entire value) is a string, the index can be case-sensitive (default) or case-insensitive. An index adds additional member functions to the generated C++ map:
iterator findByMEMBER(MEMBER_TYPE, bool = true);
const_iterator findByMEMBER(MEMBER_TYPE,
                            bool = true) const;
iterator beginForMEMBER();
const_iterator beginForMEMBER() const;
iterator endForMEMBER();
const_iterator endForMEMBER() const;
iterator lowerBoundForMEMBER(MEMBER_TYPE);
const_iterator lowerBoundForMEMBER(MEMBER_TYPE) const;
iterator upperBoundForMEMBER(MEMBER_TYPE);
const_iterator upperBoundForMEMBER(MEMBER_TYPE) const;
std::pair<iterator, iterator>
equalRangeForMEMBER(MEMBER_TYPE);
std::pair<const_iterator, const_iterator>
equalRangeForMEMBER(MEMBER_TYPE) const;
int MEMBERCount(MEMBER_TYPE) const;
When MEMBER is not specified, these functions are findByValue (const and non-const), lowerBoundForValue (const and non-const), valueCount, and so on. When MEMBER is specified, its first letter is capi­talized in the findBy function name. MEMBER_TYPE corresponds to an in-parameter of the type of MEMBER (or the type of the value when MEMBER is not specified). For example, if MEMBER is a string, MEMBER_TYPE is a const std::string&.
By default, keys are sorted using their binary Ice-encoded representation. Include sort to sort with the COMPARE functor class. If COMPARE is not specified, the default value is std::less<MEMBER_TYPE>.
findByMEMBER returns an iterator to the first element in the Freeze map that matches the given index value. It returns end() if there is no match. When the second parameter is true (the default), the returned iterator provides only the elements with an exact match (and then skips to end()). Otherwise, the returned iterator sets a starting position and then provides all elements until the end of the map, sorted according to the index comparator.
lowerBoundForMEMBER returns an iterator to the first element in the Freeze map whose index value is not less than the given index value. It returns end() if there is no such element. The returned iterator provides all elements until the end of the map, sorted according to the index comparator.
upperBoundForMEMBER returns an iterator to the first element in the Freeze map whose index value is greater than the given index value. It returns end() if there is no such element. The returned iterator provides all elements until the end of the map, sorted according to the index comparator.
beginForMEMBER returns an iterator to the first element in the map.
endForMEMBER returns an iterator to the last element in the map.
equalRangeForMEMBER returns a range (pair of iterators) of all the elements whose index value matches the given index value. This function is similar to findByMEMBER (see above).
MEMBERCount returns the number of elements in the Freeze map whose index value matches the given index value.
Please note that index-derived iterators do not allow you to set new values in the underlying map.
• index CLASS,TYPE,MEMBER
[,casesensitive|caseinsensitive]
Generate an index class for a Freeze evictor (see Section 39.3.7). CLASS is the name of the class to be generated. TYPE denotes the type of class to be indexed (objects of different classes are not included in this index). MEMBER is the name of the data member in TYPE to index. When MEMBER has type string, it is possible to specify whether the index is case-sensitive or not. The default is case-sensitive.
Section 6.16.1 provides a discussion of the semantics of #include directives that is also relevant for users of slice2freeze.

Generating a Simple Map

As an example, the following command generates a simple map:
$ slice2freeze dict StringIntMap,string,int StringIntMap
This command directs the compiler to create a map named StringIntMap, with the Slice key type string and the Slice value type int. The final argument is the base name for the output files, to which the compiler appends the .h and .cpp suffixes. As a result, this command produces two C++ source files, StringIntMap.h and StringIntMap.cpp.

The Map Class

If you examine the contents of the header file created by the example in the previous section, you will discover that a Freeze map is an instance of the template class Freeze::Map:
// StringIntMap.h
typedef Freeze::Map<std::string, Ice::Int, ...> StringIntMap;
The Freeze::Map template class closely resembles the STL container class std::map, as shown in the following class definition:
namespace Freeze {
template<...> class Map {
public:
    typedef ... value_type;
    typedef ... iterator;
    typedef ... const_iterator;

    typedef size_t size_type;
    typedef ptrdiff_t difference_type;

    Map(const Freeze::ConnectionPtr& connection, 
        const std::string& dbName,
        bool createDb = true,
        const Compare& compare = Compare());

    template<class _InputIterator>
    Map(const Freeze::ConnectionPtr& connection, 
        const std::string& dbName, 
        bool createDb,
        _InputIterator first, _InputIterator last,
        const Compare& compare = Compare());

    static void recreate(const Freeze::ConnectionPtr& connection,
                         const std::string& dbName,
                         const Compare& compare = Compare());

    bool operator==(const Map& rhs) const;
    bool operator!=(const Map& rhs) const;

    void swap(Map& rhs);

    iterator begin();
    const_iterator begin() const;

    iterator end();
    const_iterator end() const;

    bool empty() const;
    size_type size() const;
    size_type max_size() const;

    iterator insert(iterator /*position*/,
                    const value_type& elem);

    std::pair<iterator, bool> insert(const value_type& elem);

    template <typename InputIterator>
    void insert(InputIterator first, InputIterator last);

    void put(const value_type& elem);

    template <typename InputIterator>
    void put(InputIterator first, InputIterator last);

    void erase(iterator position);
    size_type erase(const key_type& key);
    void erase(iterator first, iterator last);

    void clear();

    void destroy(); // Nonstandard.

    iterator find(const key_type& key);
    const_iterator find(const key_type& key) const;

    size_type count(const key_type& key) const;

    iterator lower_bound(const key_type& key);
    const_iterator lower_bound(const key_type& key) const;
    iterator upper_bound(const key_type& key);
    const_iterator upper_bound(const key_type& key) const;

    std::pair<iterator, iterator>
    equal_range(const key_type& key);

    std::pair<const_iterator, const_iterator> 
    equal_range(const key_type& key) const;

    const Ice::CommunicatorPtr&
    communicator() const;

    ...
};
}
The semantics of the Freeze::Map methods are identical to those of std::map unless otherwise noted. In particular, the overloaded insert method shown below ignores the position argument:
iterator insert(iterator /*position*/,
                const value_type& elem);
A Freeze map class supports only those methods shown above; other features of std::map, such as allocators and overloaded array operators, are not available.
Non-standard methods that are specific to Freeze maps are discussed below:
• Constructors
The following overloaded constructors are provided:
Map(const Freeze::ConnectionPtr& connection, 
    const std::string& dbName,
    bool createDb = true,
    const Compare& compare = Compare());

template<class _InputIterator>
Map(const Freeze::ConnectionPtr& connection, 
    const std::string& dbName, 
    bool createDb,
    _InputIterator first, _InputIterator last,
    const Compare& compare = Compare());
The first constructor accepts a connection, the database name, a flag indicating whether to create the database if it does not exist, and an object used to compare keys. The second constructor accepts all of the parameters of the first, with the addition of iterators from which elements are copied into the map.
Note that a database can only contain the persistent state of one map type. Any attempt to instantiate maps of different types on the same database results in undefined behavior.
• Map copy
The recreate function copies an existing database:
static void recreate(const Freeze::ConnectionPtr& connection,
                     const std::string& dbName,
                     const Compare& compare = Compare())
The dbName parameter specifies an existing database name. The copy has the name<dbName>.old-<uuid>. For example, if the database name is MyDB, the copy might be named
MyDB.old-edefd55a-e66a-478d-a77b-f6d53292b873. (Obvi­ously, a different UUID is used each time you recreate a database).
• destroy
This method deletes the database from its environment and from the Freeze catalog (see Section 39.7). If a transaction is not currently open, the method creates its own transaction in which to perform this task.
• communicator
This method returns the communicator with which the map’s connection is associated.

Iterators

A Freeze map’s iterator works like its counterpart in std::map. The iterator class supports one convenient (but nonstandard) method:
void set(const mapped_type& value)
Using this method, a program can replace the value at the iterator’s current posi­tion.

Sample Program

The program below demonstrates how to use a StringIntMap to store <stringint> pairs in a database. You will notice that there are no explicit read or write operations called by the program; instead, simply using the map has the side effect of accessing the database.
#include <Freeze/Freeze.h>
#include <StringIntMap.h>

int
main(int argc, char* argv[])
{
    // Initialize the Communicator.
    //
    Ice::CommunicatorPtr communicator =
        Ice::initialize(argc, argv);

    // Create a Freeze database connection.
    //
    Freeze::ConnectionPtr connection =
        Freeze::createConnection(communicator, "db");

    // Instantiate the map.
    //
    StringIntMap map(connection, "simple");

    // Clear the map.
    //
    map.clear();

    Ice::Int i;
    StringIntMap::iterator p;

    // Populate the map.
    //
    for (i = 0; i < 26; i++) {
        std::string key(1, 'a' + i);
        map.insert(make_pair(key, i));
    }

    // Iterate over the map and change the values.
    //
    for (p = map.begin(); p != map.end(); ++p)
        p.set(p>second + 1);

    // Find and erase the last element.
    //
    p = map.find("z");
    assert(p != map.end());
    map.erase(p);

    // Clean up.
    //
    connection>close();
    communicator>destroy();

    return 0;
}
Prior to instantiating a Freeze map, the application must connect to a Berkeley DB database environment:
Freeze::ConnectionPtr connection =
    Freeze::createConnection(communicator, "db");
The second argument is the name of a Berkeley DB database environment; by default, this is also the file system directory in which Berkeley DB creates all database and administrative files. Note that properties with the prefix Freeze.DbEnv can modify a number of environment settings (see page 1946), including the file system directory. For the preceding example, you could change the directory to FreezeDir by setting the property Freeze.DbEnv.db.DbHome to FreezeDir.
Next, the code instantiates the StringIntMap on the connection. The constructor’s second argument supplies the name of the database file, which by default is created if it does not exist:
StringIntMap map(connection, "simple");
After instantiating the map, we clear it to make sure it is empty in case the program is run more than once:
map.clear();
Next, we populate the map using a single-character string as the key:
for (i = 0; i < 26; i++) {
    std::string key(1, 'a' + i);
    map.insert(make_pair(key, i));
}
Iterating over the map will look familiar to std::map users. However, to modify a value at the iterator’s current position, we use the nonstandard set method:
for (p = map.begin(); p != map.end(); ++p)
    p.set(p‑>second + 1);
Next, the program obtains an iterator positioned at the element with key z, and erases it:
p = map.find("z");
assert(p != map.end());
map.erase(p);
Finally, the program closes the database connection:
connection‑>close();
It is not necessary to explicitly close the database connection, but we demonstrate it here for the sake of completeness.

39.5.8 Using a Freeze Map in Java

This section describes the code generator and demonstrates how to use a Freeze map in a Java program.

slice2freezej Command-Line Options

The Slice-to-Freeze compiler, slice2freezej, creates Java classes for Freeze maps. The compiler offers the following command-line options in addition to the standard options described in Section 4.20:
• dict NAME,KEY,VALUE
Generate a Freeze map class named NAME using KEY as key and VALUE as value. This option may be specified multiple times to generate several Freeze maps. NAME may be a scoped Java name, such as Demo.Struct1ObjectMap.
• dictindex MAP[,MEMBER]
[,casesensitive|caseinsensitive]
Add an index to the Freeze map named MAP. If MEMBER is specified, the map value type must be a structure or a class, and MEMBER must be the name of a member of that type. If MEMBER is not specified, the entire value is indexed. When the indexed member (or entire value) is a string, the index can be case-sensitive (default) or case-insensitive. See page 1565 for more information on the Freeze map API for indices.
• index CLASS,TYPE,MEMBER
[,casesensitive|caseinsensitive]
Generate an index class for a Freeze evictor (see Section 39.3.7). CLASS is the name of the index class to be generated. TYPE denotes the type of class to be indexed (objects of different classes are not included in this index). MEMBER is the name of the data member in TYPE to index. When MEMBER has type string, it is possible to specify whether the index is case-sensitive or not. The default is case-sensitive.
• meta META
Define the global metadata directive META. Using this option is equivalent to defining the global metadata META in each named Slice file, as well as in any file included by a named Slice file.

Generating a Simple Map

As an example, the following command generates a simple map:
$ slice2freezej dict StringIntMap,string,int
This command directs the compiler to create a map named StringIntMap, with the Slice key type string and the Slice value type int. The compiler produces one Java source file: StringIntMap.java.

Ant Task

In addition to the ant task for executing slice2java, Ice also includes an ant task for executing slice2freezej. The classes for Slice2FreezeJTask are stored in the same JAR file (ant-ice.jar) as Slice2JavaTask. Both tasks also share the same logic for locating a compiler in your execution environ­ment and for managing dependencies between Slice files; refer to Section 10.18.2 for more information.
The Slice2FreezeJTask supports the parameters listed in Table 39.1:
Table 39.1. Ant task parameters
Specifies an alternate name for the dependency file. If you specify a relative filename, it is rel­ative to ant’s current working directory. If not specified, the task uses the name .depend by default. If you do not define this attribute and outputdir is defined, the task creates the .depend file in the designated output direc­tory (see outputdir).
Instructs the Slice compiler to permit symbols that have the reserved prefix Ice. This param­eter is used in the Ice build system and is not normally required by applications.
Specifies the directory in which the Slice com­piler generates Java source files. If not speci­fied, the task uses ant’s current working directory.
Specifies the path name of the Slice compiler. If not specified, the task locates the Slice com­piler in its execution environment as described in Section 10.18.2.
Several Slice compiler options must be defined as nested elements of the task:
• define
Defines a preprocessor macro. The element supports the attributes name and (optionally) value, as shown below:
<define name="FOO">
<define name="BAR" value="5">
These definitions are equivalent to the command-line options -DFOO and
-DBAR=5, respectively.
• dict
Generates a Freeze map. This element is equivalent to the --dict option and supports three attributes: name, key, and value. Refer to page 1556 for more information on this option.
• dictindex
Generates an index for a Freeze map. This element is equivalent to the
--dict-index option and supports three attributes: name, member, and casesensitive. Refer to page 1556 for more information on this option.
• fileset
Specifies the set of Slice files to be compiled. Refer to the ant documentation of its FileSet type for more information.
• includepath
Specifies the include file search path for Slice files. In ant terminology, includepath is a path-like structure. Refer to the ant documentation of its Path type for more information.
• index
Generates an index for a Freeze evictor. This element is equivalent to the
--index option and supports four attributes: name, type, member, and casesensitive. Refer to page 1556 for more information on this option.
• meta
Defines a global metadata directive in each Slice file as well as in each included Slice file. The element supports the attributes name and value.
To enable the Slice2FreezeJTask in your ant project, define the following taskdef element in your project’s build file:
<taskdef name="slice2freezej" classname="Slice2FreezeJTask"/>
This configuration assumes that ant-ice.jar is already present in ant’s class path. Alternatively, you can specify the JAR explicitly as follows:
<taskdef name="slice2freezej" classpath="/opt/Ice/lib/ant-ice.jar"
    classname="Slice2FreezeJTask"/>
Once activated, you can invoke the task to translate your Slice files. The example shown below is a simplified version of the ant project for the library demo:
<target name="generate" depends="init">
    <mkdir dir="generated"/>
    <slice2java outputdir="generated">
        <fileset dir="." includes="Library.ice"/>
    </slice2java>
    <slice2freezej ice="on" outputdir="generated">
        <fileset dir="/opt/Ice/slice/Ice"
            includes="BuiltinSequences.ice"/>
        <fileset dir="." includes="Library.ice"/>
        <dict name="StringIsbnSeqDict" key="string"
            value="Ice::StringSeq"/>
    </slice2freezej>
</target>
This invocation of the slice2freezej task enables the ice option because the generated Freeze map relies on a type that is defined in an Ice namespace and therefore loads the Slice file BuiltinSequences.ice directly.

The Map Class

The class generated by slice2freezej implements the Freeze.Map interface, as shown below:
package Freeze;

public interface Map<K, V> extends NavigableMap<K, V>
{
    void fastPut(K key, V value);
    void close();
    int closeAllIterators();
    void destroy();

    public interface EntryIterator<T>
        extends java.util.Iterator<T>
    {
        void close();
        void destroy(); // an alias for close
    }
}
The Map interface implements standard Java interfaces and provides nonstandard methods that improve efficiency and support database-oriented features. Map defines the following methods:
• fastPut
Inserts a new key-value pair. This method is more efficient than the standard put method because it avoids the overhead of reading and decoding the previous value associated with the key (if any).
• close
Closes the database associated with this map along with all open iterators. A map must be closed when it is no longer needed, either by closing the map directly or by closing the Freeze Connection object with which this map is associated.
• closeAllIterators
Closes all open iterators and returns the number of iterators that were closed. We discuss iterators in more detail in the next section.
• destroy
Removes the database associated with this map along with any indices.
Map inherits much of its functionality from the Freeze.NavigableMap interface, which derives from the standard Java interface java.util.Sort­edMap and also supports a subset of the java.util.NavigableMap1 inter­face from Java6:
package Freeze;

public interface NavigableMap<K, V>
    extends java.util.SortedMap<K, V>
{
    java.util.Map.Entry<K, V> firstEntry();
    java.util.Map.Entry<K, V> lastEntry();

    java.util.Map.Entry<K, V> ceilingEntry(K key);
    java.util.Map.Entry<K, V> floorEntry(K key);
    java.util.Map.Entry<K, V> higherEntry(K key);
    java.util.Map.Entry<K, V> lowerEntry(K key);

    K ceilingKey(K key);
    K floorKey(K key);
    K higherKey(K key);
    K lowerKey(K key);

    java.util.Set<K> descendingKeySet();
    NavigableMap<K, V> descendingMap();

    NavigableMap<K, V> headMap(K toKey, boolean inclusive);
    NavigableMap<K, V> tailMap(K fromKey, boolean inclusive);
    NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive,
                              K toKey, boolean toInclusive);

    java.util.Map.Entry<K, V> pollFirstEntry();
    java.util.Map.Entry<K, V> pollLastEntry();

    boolean fastRemove(K key);
}
The NavigableMap interface provides a number of useful methods:
• firstEntry
lastEntry
Returns the first and last key-value pair, respectively.
• ceilingEntry
Returns the key-value pair associated with the least key greater than or equal to the given key, or null if there is no such key.
• floorEntry
Returns the key-value pair associated with the greatest key less than or equal to the given key, or null if there is no such key.
• higherEntry
Returns the key-value pair associated with the least key greater than the given key, or null if there is no such key.
• lowerEntry
Returns the key-value pair associated with the greatest key less than the given key, or null if there is no such key.
• ceilingKey
floorKey
higherKey
lowerKey
These methods have the same semantics as those described above, except they return only the key portion of the matching key-value pair or null if there is no such key.
• descendingKeySet
Returns a set representing a reverse-order view of the keys in this map.
• descendingMap
Returns a reverse-order view of the entries in this map.
• headMap
Returns a view of the portion of this map whose keys are less than (or equal to, if inclusive is true) the given key.
• tailMap
Returns a view of the portion of this map whose keys are greater than (or equal to, if inclusive is true) the given key.
• subMap
Returns a view of the portion of this map whose keys are within the given range.
• pollFirstEntry
pollLastEntry
Removes and returns the first and last key-value pair, respectively.
• fastRemove
Removes an existing key-value pair. As for fastPut, this method is a more efficient alternative to the standard remove method that returns true if a key-value pair was removed, or false if no match was found.
Many of these methods raise UnsupportedOperationException if you fail to construct the Freeze map using a custom comparator object. The only exceptions are firstEntry, lastEntry, pollFirstEntry, pollLas­tEntry, and fastRemove. (The same applies to NavigableMap objects created for secondary keys.)
Note that NavigableMap also inherits overloaded methods named headMap, tailMap, and subMap from the SortedMap interface. These methods have the same semantics as the ones defined in NavigableMap but they omit the boolean arguments (refer to the JDK documentation for complete details). Although these methods are declared as returning a SortedMap, the actual type of the returned object is a NavigableMap that you can downcast if necessary.
There are some limitations in the sub maps returned by the headMap, tailMap and subMap methods:
• A new entry in the Freeze map cannot be added via a sub map, therefore calling put raises UnsupportedOperationException
• An existing entry in the Freeze map cannot be removed via a sub map or iter­ator for a secondary key (see page 1565).
Now let us examine the contents of the source file created by the example in the previous section:
public class StringIntMap extends ...
    // implements Freeze.Map<String, Integer>
{
    public StringIntMap(
        Freeze.Connection connection,
        String dbName,
        boolean createDb,
        java.util.Comparator<String> comparator);

    public StringIntMap(
        Freeze.Connection connection,
        String dbName,
        boolean createDb);

    public StringIntMap(
        Freeze.Connection connection,
        String dbName);
}
StringIntMap derives from an internal Freeze base class that implements the interface Freeze.Map<String, Integer>. The generated class defines several overloaded constructors whose arguments are described below:
• connection
The Freeze connection object (see Section 39.5.1).
• dbName
The name of the database in which to store this map’s persistent state. Note that a database can only contain the persistent state of one map type. Any attempt to instantiate maps of different types on the same database results in undefined behavior.
• createDb
A flag indicating whether the map should create the database if it does not already exist. If this argument is not specified, the default value is true.
• comparator
An object used to compare the map’s keys. If this argument is not specified, the default behavior compares the encoded form of the keys.

Iterators

You can iterate over a Freeze map just as you can with any container that imple­ments the java.util.Map interface. For example, the code below displays the key and value of each element:
StringIntMap m = new StringIntMap(...);
java.util.Iterator<java.util.Map.Entry<String, Integer>> i =
    m.entrySet().iterator();
while (i.hasNext()) {
    java.util.Map.Entry<String, Integer> e = i.next();
    System.out.println("Key: " + e.getKey());
    System.out.println("Value: " + e.getValue());
}
Generally speaking, a program should close an iterator when it is no longer neces­sary for the reasons given in Section 39.5.3. (An iterator that is garbage collected without being closed emits a warning message.) However, an explicit close was not necessary in the preceding example because Freeze automatically closes a read-only iterator when it reaches the last element (a read-only iterator is one that is opened outside of any transaction). If instead our program had stopped using the iterator prior to reaching the last element, an explicit close would have been necessary:
StringIntMap m = new StringIntMap(...);
java.util.Iterator<java.util.Map.Entry<String, Integer>> i =
    m.entrySet().iterator();
while (i.hasNext()) {
    java.util.Map.Entry<String, Integer> e = i.next();
    System.out.println("Key: " + e.getKey());
    System.out.println("Value: " + e.getValue());
    if (e.getValue().intValue() == 5)
        break;
}
((Freeze.Map.EntryIterator)i).close();
Closing the iterator requires downcasting it to a Freeze-specific interface named Freeze.Map.EntryIterator. The definition of this interface was shown in the previous section.
Freeze maps also support the enhanced for loop functionality in Java5. Here is a simpler way to write our original program:
StringIntMap m = new StringIntMap(...);
for (java.util.Map.Entry<String, Integer> e : m.entrySet()) {
    System.out.println("Key: " + e.getKey());
    System.out.println("Value: " + e.getValue());
}
As in the first example, Freeze automatically closes the iterator when no more elements are available. Although the enhanced for loop is convenient, it is not appropriate for all situations because the loop hides its iterator and therefore prevents the program from accessing the iterator in order to close it. In this case, you can use the traditional while loop instead of the for loop, or you can invoke closeAllIterators on the map as shown below:
StringIntMap m = new StringIntMap(...);
for (java.util.Map.Entry<String, Integer> e : m.entrySet()) {
    System.out.println("Key: " + e.getKey());
    System.out.println("Value: " + e.getValue());
    if (e.getValue().intValue() == 5)
        break;
}
int num = m.closeAllIterators();
assert(num <= 1); // The iterator may already be closed.
The closeAllIterators method returns an integer representing the number of iterators that were actually closed. This value can be useful for diagnostic purposes, such as to assert that a program is correctly closing its iterators.

Indices

Using the --dict-index option to define an index for a secondary key causes slice2freezej to generate the following additional code in a Freeze map:
• A static nested class named IndexComparators, which allows you to supply a custom comparator object for each index in the map.
• An overloading of the map constructor that accepts an instance of Index­Comparators.
• An overloading of the recreate method that accepts an instance of IndexComparators.
• Searching, counting, and range-searching methods for finding key-value pairs using the secondary key.
We discuss each of these additions in more detail below. In this discussion, MEMBER refers to the optional argument of the --dict-index option, and MEMBER_TYPE refers to the type of that member. As explained earlier, if MEMBER is not specified, slice2freezej creates an index for the value type of the map. The sample code presented in this section assumes we have generated a Freeze map using the following command:
$ slice2freezej dict StringIntMap,string,int \
    --dict-index StringIntMap
By default, index keys are sorted using their binary Ice-encoded representation. This is an efficient sorting scheme but does not necessarily provide a meaningful traversal order for applications. You can choose a different order by providing an instance of the IndexComparators class to the map constructor. This class has a public data member holding a comparator (an instance of java.util.Comparator<MEMBER_TYPE>) for each index in the map. The class also provides an empty constructor as well as a convenience constructor that allows you to instantiate and initialize the object all at once. The name of each data member is MEMBERComparator. If MEMBER is not specified, the Index­Comparators class has a single data member named valueComparator. Note that much of the functionality offered by a map index requires that you provide a custom comparator.
Here is the definition of IndexComparators for StringIntMap:
public class StringIntMap ... {
    public static class IndexComparators {
        public IndexComparators() {}

        public IndexComparators(
            java.util.Comparator<Integer> valueComparator);

        public java.util.Comparator<Integer> valueComparator;
    }

    ...
}
To instantiate a Freeze map using your custom comparators, you must use the overloaded constructor that accepts the IndexComparators object. For our StringIntMap, this constructor has the following definition:
public class StringIntMap ... {
    public StringIntMap(
        Freeze.Connection connection,
        String dbName,
        boolean createDb,
        java.util.Comparator<String> comparator,
        IndexComparators indexComparators);

    ...
}
Now we can instantiate our StringIntMap as follows:
java.util.Comparator<String> myMainKeyComparator = ...;
StringIntMap.IndexComparators indexComparators =
    new StringIntMap.IndexComparators();
indexComparators.valueComparator = ...;
StringIntMap m =
    new StringIntMap(connection, "stringIntMap", true,
                     myMainKeyComparator, indexComparators);
If you later need to change the index configuration of a Freeze map, you can use one of the recreate methods to update the database. Here are the definitions from StringIntMap:
public class StringIntMap ... {
    public static void recreate(
        Freeze.Connection connection,
        String dbName,
        java.util.Comparator<String> comparator);

    public static void recreate(
        Freeze.Connection connection,
        String dbName,
        java.util.Comparator<String> comparator,
        IndexComparators indexComparators);

    ...
}
The first overloading is generated for every map, whereas the second overloading is only generated when the map has at least one index. As its name implies, the recreate method creates a new copy of the database. More specifically, the method removes any existing indices, copies every key-value pair to a temporary database, and finally replaces the old database with the new one. As a side-effect, this process also populates any remaining indices. The first overloading of recreate is useful when you have regenerated the map to remove the last index and wish to clean up the map’s database state.
slice2freezej also generates a number of index-specific methods. The names of these methods incorporate the member name (MEMBER), or use value if MEMBER is not specified. In each method name, the value of MEMBER is used unchanged if it appears at the beginning of the method’s name. Otherwise, if MEMBER is used elsewhere in the method name, its first letter is capitalized. The index methods are described below:
• public Freeze.Map.EntryIterator<Map.Entry<K, V>>
findByMEMBER(MEMBER_TYPE index)

public Freeze.Map.EntryIterator<Map.Entry<K, V>>
findByMEMBER(MEMBER_TYPE index, boolean onlyDups)
Returns an iterator over elements of the Freeze map starting with an element with whose index value matches the given index value. If there is no such element, the returned iterator is empty (hasNext always returns false). When the second parameter is true (or is not provided), the returned iterator provides only “duplicate” elements, that is, elements with the very same index value. Otherwise, the iterator sets a starting position in the map, and then provides elements until the end of the map, sorted according to the index comparator. Any attempt to modify the map via this iterator results in an Unsupported­OperationException.
• public int MEMBERCount(MEMBER_TYPE index)
Returns the number of elements in the Freeze map whose index value matches the given index value.
• public NavigableMap<MEMBER_TYPE, Set<Map.Entry<K, V>>>
headMapForMEMBER(MEMBER_TYPE to, boolean inclusive)

public NavigableMap<MEMBER_TYPE, Set<Map.Entry<K, V>>>
headMapForMEMBER(MEMBER_TYPE to)
Returns a view of the portion of the Freeze map whose keys are less than (or equal to, if inclusive is true) the given key. If inclusive is not speci­fied, the method behaves as if inclusive is false.
• public NavigableMap<MEMBER_TYPE, Set<Map.Entry<K, V>>>
tailMapForMEMBER(MEMBER_TYPE from, boolean inclusive)

public NavigableMap<MEMBER_TYPE, Set<Map.Entry<K, V>>>
tailMapForMEMBER(MEMBER_TYPE from)
Returns a view of the portion of the Freeze map whose keys are greater than (or equal to, if inclusive is true) the given key. If inclusive is not specified, the method behaves as if inclusive is true.
• public NavigableMap<MEMBER_TYPE, Set<Map.Entry<K, V>>>
subMapForMEMBER(MEMBER_TYPE from, boolean fromInclusive,
              MEMBER_TYPE to, boolean toInclusive)

public NavigableMap<MEMBER_TYPE, Set<Map.Entry<K, V>>>
subMapForMEMBER(MEMBER_TYPE from, MEMBER_TYPE to)
Returns a view of the portion of the Freeze map whose keys are within the given range. If fromInclusive and toInclusive are not specified, the method behaves as if fromInclusive is true and toInclusive is false.
• public NavigableMap<MEMBER_TYPE, Set<Map.Entry<K, V>>>
mapForMEMBER()
Returns a view of the entire Freeze map ordered by the index key.
For the methods returning a NavigableMap, the key type is the secondary key type and the value is the set of matching key-value pairs from the Freeze map. (For the sake of readability, we have omitted the java.util prefix from Set and Map.Entry.) In other words, the returned map is a mapping of the secondary key to all of the entries whose value contains the same key. Any attempt to add, remove, or modify an element via a sub map view or an iterator of a sub map view results in an UnsupportedOperationException.
Note that iterators returned by the findByMEMBER methods, as well as those created for sub map views, may need to be closed explicitly, just like iterators obtained for the main Freeze map. (See page 1564 for more information.)
Here are the definitions of the index methods for StringIntMap:
public Freeze.Map.EntryIterator<Map.Entry<String, Integer>>
findByValue(Integer index);

public Freeze.Map.EntryIterator<Map.Entry<String, Integer>>
findByValue(Integer index, boolean onlyDups);

public int valueCount(Integer index);

public NavigableMap<Integer, Set<Map.Entry<String, Integer>>>
headMapForValue(Integer to, boolean inclusive);
public NavigableMap<Integer, Set<Map.Entry<String, Integer>>>
headMapForValue(Integer to);

public NavigableMap<Integer, Set<Map.Entry<String, Integer>>>
tailMapForValue(Integer from, boolean inclusive);
public NavigableMap<Integer, Set<Map.Entry<String, Integer>>>
tailMapForValue(Integer from);

public NavigableMap<Integer, Set<Map.Entry<String, Integer>>>
subMapForValue(Integer from, boolean fromInclusive,
               Integer to, boolean toInclusive);
public NavigableMap<Integer, Set<Map.Entry<String, Integer>>>
subMapForValue(Integer from, Integer to);

public NavigableMap<Integer, Set<Map.Entry<String, Integer>>>
mapForValue();

Sample Program

The program below demonstrates how to use a StringIntMap to store <stringint> pairs in a database. You will notice that there are no explicit read or write operations called by the program; instead, simply using the map has the side effect of accessing the database.
public class Client
{
    public static void
    main(String[] args)
    {
        // Initialize the Communicator.
        //
        Ice.Communicator communicator = Ice.Util.initialize(args);

        // Create a Freeze database connection.
        //
        Freeze.Connection connection =
            Freeze.Util.createConnection(communicator, "db");

        // Instantiate the map.
        //
        StringIntMap map = 
            new StringIntMap(connection, "simple", true);

        // Clear the map.
        //
        map.clear();

        int i;

        // Populate the map.
        //
        for (i = 0; i < 26; i++) {
            final char[] ch = { (char)('a' + i) };
            map.put(new String(ch), i);
        }

        // Iterate over the map and change the values.
        //
        for (java.util.Map.Entry<String, Integer> e :
             map.entrySet()) {
            Integer in = e.getValue();
            e.setValue(in.intValue() + 1);
        }

        // Find and erase the last element.
        //
        boolean b;
        b = map.containsKey("z");
        assert(b);
        b = map.fastRemove("z");
        assert(b);

        // Clean up.
        //
        map.close();
        connection.close();
        communicator.destroy();

        System.exit(0);
    }
}
Prior to instantiating a Freeze map, the application must connect to a Berkeley DB database environment:
Freeze.Connection connection =
    Freeze.Util.createConnection(communicator, "db");
The second argument is the name of a Berkeley DB database environment; by default, this is also the file system directory in which Berkeley DB creates all database and administrative files.
Next, the code instantiates the StringIntMap on the connection. The constructor’s second argument supplies the name of the database file, and the third argument indicates that the database should be created if it does not exist:
StringIntMap map = new StringIntMap(connection, "simple", true);
After instantiating the map, we clear it to make sure it is empty in case the program is run more than once:
map.clear();
We populate the map, using a single-character string as the key. As with java.util.Map, the key and value types must be Java objects but the compiler takes care of autoboxing the integer argument:
for (i = 0; i < 26; i++) {
    final char[] ch = { (char)('a' + i) };
    map.put(new String(ch), i);
}
Iterating over the map is no different from iterating over any other map that imple­ments the java.util.Map interface:
for (java.util.Map.Entry<String, Integer> e :
     map.entrySet()) {
    Integer in = e.getValue();
    e.setValue(in.intValue() + 1);
}
Next, the program verifies that an element exists with key z, and then removes it using fastRemove:
b = map.containsKey("z");
assert(b);
b = map.fastRemove("z");
assert(b);
Finally, the program closes the map and its connection.
map.close();
connection.close();

1
The generated class does not implement java.util.NavigableMap because Freeze maps must remain compatible with Java5.


Table of Contents Previous Next
Logo