    //@include lib_Class.js

    /**
     * Base class that represents map data
     *
     * @author MW, ELO Digital Office GmbH
     * @version 1.02.001
     *
     * @eloix
     * @eloas
     * @elojc
     *
     * @requires sol.common.ObjectUtils
     *
     */
    sol.define("sol.common.Map", {

      /**
       * @cfg {String} mapId
       * Map ID
       */

      /**
       * @cfg {String} objId
       * Object ID
       */

      /**
       * @cfg {Boolean} asAdmin
       * If `true`, the copy process will run in administrator context
       */
      asAdmin: undefined,

      initialize: function (config) {
        var me = this;
        me.logger = sol.create("sol.Logger", { scope: me.$className });
        if (!me.mapId) {
          throw "Map ID is empty";
        }
        if (!me.objId) {
          throw "Object ID is empty";
        }
        if (!me.mapDomain) {
          throw "Map domain is empty";
        }
        me.data = {};
        me.newEntries = {};
      },

      /**
       * Sets a map value of an ELO object.
       * @param {String} key
       * @param {String} value
       */
      setValue: function (key, value) {
        var me = this;

        if (!key) {
          throw "Key is empty";
        }

        key = String(key);
        value = (typeof value === "undefined" || value === null) ? "" : String(value);
        me.data[key] = value;

        key = new java.lang.String(key);
        me.newEntries[key] = new KeyValue(key, value);
      },

      /**
       * Set values
       * @param {Object} map Map
       */
      setValues: function (map) {
        var me = this,
            key, value;

        for (key in map) {
          if (map.hasOwnProperty(key)) {
            value = map[key];
            me.setValue(key, value);
          }
        }
      },

      /**
       * Writes the new map entries of an ELO object to the ix map database.
       */
      write: function () {
        var me = this,
            i = 0,
            mapValues = [], entry, key, ixConn;

        ixConn = (me.asAdmin === true) ? ixConnectAdmin : ixConnect;

        me.logger.enter("write", arguments);

        for (key in me.newEntries) {
          if (me.newEntries.hasOwnProperty(key)) {
            entry = me.newEntries[key];
            i++;
            me.logger.debug("Write entry " + i + ": " + entry.key + "=" + entry.value);
            mapValues.push(entry);
          }
        }

        if (mapValues.length > 0) {
          ixConn.ix().checkinMap(me.mapDomain, me.mapId, me.objId, mapValues, LockC.NO);
        }

        me.newEntries = {};

        me.logger.exit("write");
      },

      /**
       * Reads all map entries of an ELO object from the ix map database.
       * @param {String[]} keynames Keynames keys to be read (optional)
       * @return {Object}
       */
      read: function (keynames) {
        var me = this,
            result = {},
            ixConn = (me.asAdmin === true) ? ixConnectAdmin : ixConnect,
            items, keys, i, entry;
        me.logger.enter("read", arguments);

        me.data = {};
        if (keynames) {
          if (sol.common.ObjectUtils.isString(keynames)) {
            keys = [keynames];
            keynames = keys;
          } else if (!sol.common.ObjectUtils.isArray(keynames)) {
            keynames = null;
          }
        } else {
          keynames = null;
        }
        items = ixConn.ix().checkoutMap(me.mapDomain, me.mapId, keynames, LockC.NO).items;
        for (i = 0; i < items.length; i++) {
          entry = items[i];
          me.logger.debug("Read entry " + i + ": " + entry.key + "=" + entry.value);
          me.data[entry.key] = entry.value;
          result[entry.key] = entry.value;
        }
        me.logger.exit("read");

        return result;
      },

      /**
       * Gets a value from the map by a given key.
       * @param {String} key
       * @return {String}
       */
      getValue: function (key) {
        var me = this;
        if (me.data[key]) {
          return String(this.data[key]);
        }
        return "";
      },

      /**
       * Returns a number value
       * @param {type} key
       * @returns {String}
       */
      getNumValue: function (key) {
        var me = this,
            value;

        value = me.getValue(key).replace(",", ".");
        if ((value == "") || isNaN(value)) {
          return "";
        }
        return parseFloat(value);
      },

      /**
       * Sets a number value
       * @param {String} key
       * @param {String} value
       */
      setNumValue: function (key, value) {
        var me = this;

        value = String(value).replace(".", ",");

        me.setValue(key, value);
      },

      /**
       * Checks wether a key and value exists
       * @param {String} key Key
       * @return {Boolean}
       */
      keyAndValueExist: function (key) {
        var me = this;
        return !!me.data[key];
      },

      /**
       * Iterates over a table.
       * @param {String} endOfTableIndicatorColumnName Key name of a column to check wether the line exists
       * @param {Function} func Callback function for the iteration
       * @param {Object} ctx Execution context
       */
      forEachRow: function (endOfTableIndicatorColumnName, func, ctx) {
        var me = this,
            i = 1;
        if (!endOfTableIndicatorColumnName) {
          throw "The end of table indicator column name is empty.";
        }
        if (!func) {
          throw "The function parameter is emtpy.";
        }

        while (me.keyAndValueExist(endOfTableIndicatorColumnName + i)) {
          func.call(ctx, i++);
        }
      },

      /**
       * Returns the keyword list key
       * @param {String} key String
       * @param {String} separator Separator
       * @return {String}
       */
      getKwlKey: function (key, separator) {
        var me = this,
            str, separatorPos;
        str = me.getValue(key);
        if (!str) {
          return "";
        }
        separator = separator || "-";
        separatorPos = str.indexOf(separator);
        if (separatorPos < 0) {
          return str;
        }
        return str.substring(0, separatorPos).trim();
      }
    });

    /**
     * Represents the map data of an ELO object. This class utilizes reading and writing data to the map database.
     *
     *     var map = sol.create('sol.common.SordMap', {
     *       objId: '123'
     *     });
     *
     *     // read all map data from ix database
     *     map.read();
     *
     *     // read a property
     *     var zugferdImported = map.getValue('ZUGFERD_IMPORTED');
     *
     *     // change a property
     *     map.setValue('ZUGFERD_IMPORTED', 'true');
     *
     *     // write map data to ix database
     *     map.write();
     *
     * @author MW, ELO Digital Office GmbH
     * @version 1.1
     *
     * @eloix
     * @eloas
     * @elojc
     *
     * @requires sol.common.
     *
     */
    sol.define("sol.common.SordMap", {
      extend: "sol.common.Map",

      /**
       * @cfg {String} objId
       * ID of the ELO object
       */

      initialize: function (config) {
        var me = this;
        if (!config.objId) {
          throw "Object ID is empty";
        }
        me.mapId = config.objId;
        me.objId = config.objId;
        if (config.asAdmin) {
          me.asAdmin = config.asAdmin;
        }
        me.mapDomain = MapDomainC.DOMAIN_SORD;
        me.$super("sol.common.Map", "initialize", [config]);
      }
    });

    /**
     * Represents the map data of an active workflow.
     *
     *     var map = sol.create("sol.common.WfMap", {
     *       flowId: "111",
     *       objId: "222"
     *     });
     *
     *     // read all workflow map data from the database
     *     map.read();
     *
     *     // read a property
     *     var zugferdImported = map.getValue('ZUGFERD_IMPORTED');
     *
     *     // change a property
     *     map.setValue('ZUGFERD_IMPORTED', 'true');
     *
     *     // write map data to database
     *     map.write();
     *
     * @author MW, ELO Digital Office GmbH
     * @version 1.1
     *
     * @eloix
     * @eloas
     * @elojc
     *
     * @requires sol.common.ObjectUtils
     *
     */
    sol.define("sol.common.WfMap", {
      extend: "sol.common.Map",

      /**
       * @cfg {String} flowId
       * ID of the ELO object
       */

      /**
       * @cfg {String} objId
       */

      initialize: function (config) {
        var me = this;
        if (!config.flowId) {
          throw "Flow ID is empty";
        }
        if (!config.objId) {
          throw "Object ID is empty";
        }
        me.mapId = config.flowId;
        me.objId = config.objId;
        me.mapDomain = MapDomainC.DOMAIN_WORKFLOW_ACTIVE;
        me.$super("sol.common.Map", "initialize", [config]);
      }
    });

    /**
     * Represents map data as table
     *
     *     var sordMap = sol.create("sol.common.SordMap", { objId: objId });
     *     var sordMapTable = sol.create("sol.common.MapTable", { map: sordMap, columnNames: ["INVOICE_TAX_NET_AMOUNT", "INVOICE_TAX_AMOUNT", "INVOICE_TAX_RATE"] });
     *     if (sordMapTable.hasNextRow()) {
     *       sordMapTable.nextRow();
     *       var value = sordMapTable.getNumValue("INVOICE_TAX_RATE");
     *       if (value == 0) {
     *         sordMapTable.removeRow();
     *       }
     *     }
     *     sordMapTable.write();
     *
     * @author MW, ELO Digital Office GmbH
     * @version 1.1
     *
     * @eloix
     * @eloas
     * @elojc
     */
    sol.define("sol.common.MapTable", {

      /**
       * @cfg {sol.common.Map} map (required)
       * Map object
       */

      /**
       * @cfg {Array} columnNames (required)
       * Column names
       */

      /**
       * @cfg {String} endOfTableIndicatorColumnName (optional)
       * End of table indicator column name
       */

      /**
       * @cfg {String} initEmpty (optional)
       * Provides an empty table
       */

      /**
       * @cfg {String} [read=true] (optional)
       * Read the map values
       */

      /**
       * @cfg {String} [reset=true] (optional)
       * Reset the table iterator
       */

      initialize: function (config) {
        var me = this,
            i, j, lastLine, columnName, row;

        me.logger = sol.create("sol.Logger", { scope: me.$className });

        i = 0;
        lastLine = false;

        config = config || {};
        config.read = (config.read == false) ? false : true;
        config.initEmpty = (config.initEmpty == true) ? true : false;
        config.reset = (config.reset == false) ? false : true;

        if (!config.map) {
          throw "Map is empty";
        }

        if (!config.columnNames || (config.columnNames.length == 0)) {
          throw "Column names are empty";
        }

        me.map = config.map;

        me.columnNames = config.columnNames;
        me.data = [];

        if (config.read || !config.initEmpty) {
          me.map.read();
        }

        if (!config.initEmpty) {
          do {
            i++;
            row = {};
            for (j = 0; j < me.columnNames.length; j++) {
              columnName = me.columnNames[j];
              row[columnName] = me.map.getValue(columnName + i);
            }
            if (me.endOfTableIndicatorColumnName) {
              lastLine = !row[me.endOfTableIndicatorColumnName];
            } else {
              for (j = 0; j < me.columnNames.length; j++) {
                lastLine = true;
                columnName = me.columnNames[j];
                if (row[columnName]) {
                  lastLine = false;
                  break;
                }
              }
            }
            if (!lastLine) {
              me.data.push(row);
            }
          } while (!lastLine);
        }

        me.writeRowsCount = i;
        me.index = me.data.length - 1;

        if (config.reset) {
          me.reset();
        }
      },

      /**
       * Resets the iterator
       */
      reset: function () {
        var me = this;
        me.index = -1;
        me.illegalState = true;
      },

      /**
       * Removes the first row and resets the table
       */
      shift: function () {
        var me = this;
        me.data.shift();
        me.reset();
      },

      /**
       * True if the table has a next row
       * @return {Boolean}
       */
      hasNextRow: function () {
        var me = this;
        return ((me.data.length - 1) > me.index);
      },

      /**
       * Moves the row pointer to the next row
       * @return {Object}
       */
      nextRow: function () {
        var me = this;
        me.illegalState = false;
        me.index++;
        return me.data[me.index];
      },

      /**
       * Returns a value
       * @param {String} columnName Column name
       * @return {String}
       */
      getValue: function (columnName) {
        var me = this;
        me.checkState();
        if (me.columnNames.indexOf(columnName) < 0) {
          throw "Key '" + columnName + "' hasn't been predefined as column name.";
        }
        return me.data[me.index][columnName] || "";
      },

      /**
       * Returns the index number
       * @return {Number}
       */
      getDisplayIndex: function () {
        var me = this,
            index;
        me.checkState();
        index = me.index + 1;
        return index;
      },

      /**
       * Returns a number value
       * @param {String} columnName Column name
       * @returns {String}
       */
      getNumValue: function (columnName) {
        var me = this,
            value;

        value = me.getValue(columnName).replace(",", ".");
        if (isNaN(value)) {
          return "";
        }
        return parseFloat(value);
      },

      /**
       * Checks the current row
       */
      checkState: function () {
        var me = this;
        if ((me.index < 0) || me.illegalState) {
          throw "Illegal state: call nextRow()";
        }
      },

      /**
       * Sets a value
       * @param {String} columnName Column name
       * @param {String} value
       */
      setValue: function (columnName, value) {
        var me = this;
        me.checkState();
        if (me.columnNames.indexOf(columnName) < 0) {
          throw "Key '" + columnName + "' hasn't been predefined as column name.";
        }
        me.data[me.index][columnName] = value;
      },

      /**
       * Sets a number value
       * @param {String} columnName Column name
       * @param {String} value
       */
      setNumValue: function (columnName, value) {
        var me = this;

        value = String(value).replace(".", ",");

        me.setValue(columnName, value);
      },

      /**
       * Inserts a row after the current row
       */
      insertRow: function () {
        var me = this;
        me.data.splice(me.index + 1, 0, {});
        me.illegalState = true;
      },

      /**
       * Deletes the current row
       */
      removeRow: function () {
        var me = this;
        me.checkState();
        me.data.splice(me.index, 1);
        me.illegalState = true;
        me.index--;
      },

      /**
       * Appends a row at the end of the table
       */
      appendRow: function () {
        var me = this;
        me.illegalState = false;
        me.index = me.data.length - 1;
        me.insertRow();
        me.nextRow();
      },

      /**
       * Returns the table length
       * @return {Number}
       */
      getLength: function () {
        var me = this,
            length;

        if (me.data) {
          length = me.data.length;
        }

        return length;
      },

      /**
       * Clears the table
       */
      clear: function () {
        var me = this,
            i, j, columnName;

        for (i = 0; i <= me.writeRowsCount; i++) {
          for (j = 0; j < me.columnNames.length; j++) {
            columnName = me.columnNames[j];
            me.map.setValue(columnName + (i + 1), "");
          }
        }
        me.map.write();
        me.data = [];
        me.writeRowsCount = 0;
        me.reset();
      },

      /**
       * Writes the data
       */
      write: function () {
        var me = this,
            i, j, columnName;
        me.logger.enter("write", arguments);

        for (i = 0; i < me.data.length; i++) {
          for (j = 0; j < me.columnNames.length; j++) {
            columnName = me.columnNames[j];
            me.map.setValue(columnName + (i + 1), me.data[i][columnName] || "");
          }
        }
        if (me.initEmpty) {
          me.writeRowsCount = i + 1;
        }
        // Add empty rows
        for (; i <= me.writeRowsCount; i++) {
          for (j = 0; j < me.columnNames.length; j++) {
            columnName = me.columnNames[j];
            me.map.setValue(columnName + (i + 1), "");
          }
        }
        me.map.write();
        me.logger.exit("write");
      }
    });