    //@include lib_Class.js
    //@include lib_sol.common.StringUtils.js
    //@include lib_sol.common.ObjectUtils.js
    //@include lib_sol.common.SordUtils.js
    //@include lib_sol.common.TranslateTerms.js

    /**
     * Keyword list iterator used by dynamic keyword lists. This class provides an abstract layer that simplifies the usage
     * of localized keyword lists.
     *
     * The list is returned as a table.
     *
     * |Key|Value|Content|
     * |:-----|:------|:------|
     * |I|incoming|I - incoming|
     * |O|outgoing|O - outgoing|
     *
     * # Example implementation
     *
     *     //(at)config config /Administration/Business Solutions/contract/Configuration/localizedKwls.config
     *     sol.define("sol.contract.ix.localizedKwl.Relation", {
     *       extend: "sol.common.ix.LocalizedKwlIterator",
     *       kwlName: "relation",
     *       initialize: function() {
     *         var me = this;
     *         me.$super("sol.common.ix.LocalizedKwlIterator", "initialize", [config[me.kwlName]]);
     *       }
     *     });
     *
     *     function getDataIterator() {
     *       var iterator = sol.create("sol.contract.ix.localizedKwl.Relation", {});
     *       return new DynamicKeywordDataProvider(iterator);
     *     }
     *
     * # Sample configuration:
     *
     *     {
     *       "translate": true,
     *       "tableTitle": "sol.contract.localizedKwl.relation.tableTitle",
     *       "columnHeaders": ["sol.contract.localizedKwl.relation.header.key", "sol.contract.localizedKwl.relation.header.value"],
     *         "entries": [{
     *           "key": "O",
     *           "value": "sol.contract.localizedKwl.relation.entry.outgoing"
     *         }, {
     *           "key": "I",
     *           "value": "sol.contract.localizedKwl.relation.entry.incoming"
     *        }]
     *     }
     *
     * @eloix
     * @requires sol.Logger
     */
    sol.define("sol.common.ix.LocalizedKwlIterator", {

      /**
       * @cfg {Boolean} translate
       * If true, the given strings will be interpreted as locale keys and will be translated by the ELOix
       * Default is true.
       */

      /**
       * @cfg {String} language
       * Language
       */

      /**
       * @cfg {String} tableTitle
       * Title of the selection table, shown in the ELO Java Client
       */

      /**
       * @cfg {Array} columnHeaders
       * Array of strings with the column headers
       */

      /**
       * @cfg {Array} entries
       * Array of objects that contains objects with a key and a value property
       */

      /**
       * @cfg {Boolean} blankLine
       * Inserts a blank line at the beginning of the keyword list
       */

      /**
       * @cfg {String} separatorString
       * String that is displayed between the key and the value
       */

      errorMessage: "",

      initialize: function (config) {
        var me = this;
        me.reInitialize(config);
        me.errorMessage = "";
      },

      reInitialize: function (config) {
        var me = this;
        me.terms = sol.common.TranslateTerms;

        me.log = sol.create("sol.Logger", { scope: me.$className || "sol.common.ix.LocalizedKwlIterator" });
        me.log.enter("initialize", config);

        me.config = config;

        if (!me.config) {
          throw "Configuration is empty";
        }

        if (!me.config.tableTitle) {
          throw "Property 'config.tableTitle' is empty";
        }

        if (!me.config.columnHeaders) {
          throw "Property 'config.columnHeaders' is empty";
        }

        if (!me.config.entries) {
          throw "Keyword list entries are empty";
        }

        // set caching variables
        me.localizationKeys = [];
        me.tableTitles = {};
        me.tableHeaders = {};
        me.tables = {};

        me.translate = (me.config.translate != false) ? true : false;

        if (me.translate) {

          me.localizationKeys = [config.tableTitle];

          me.config.columnHeaders.forEach(function (tableHeaderKey) {
            me.localizationKeys.push(tableHeaderKey);
          });

          me.config.entries.forEach(function (entry) {
            me.localizationKeys.push(entry.value);
          });

          me.separatorString = me.config.separatorString || " - ";

          if (me.foreignFilterKey) {
            me.localizationKeys.push(config.foreignKeyNoValue);
          }

          me.terms.require(me.localizationKeys);
        }

        me.log.exit("initialize");
      },

      useForeignConfig: function (filterKeyValue) {
        var me = this, foreignConfig;
        filterKeyValue = sol.common.ObjectUtils.type(filterKeyValue) === "string" ? String(filterKeyValue) : undefined;
        if (filterKeyValue && filterKeyValue !== "undefined") {
          filterKeyValue = (filterKeyValue.indexOf(me.separatorString) > -1) ? filterKeyValue.slice(0, filterKeyValue.indexOf(me.separatorString)) : filterKeyValue;
          foreignConfig = sol.common.ObjectUtils.getProp(typeof me.config === "object" ? me.config.entries : {}, filterKeyValue + ".foreignConfig", "key");
          if (!foreignConfig) {
            throw "No config defined for value `" + filterKeyValue + "` in field `" + me.foreignFilterKey;
          }
          return foreignConfig || me.config;
        } else {
          me.errorMessage = sol.common.TranslateTerms.translate(me.config.foreignKeyNoValue);
          return me.config;
        }
      },

      /**
       * Opens a connection for the ELO Java Client and non map field capable clients.
       *
       * @param {de.elo.ix.client.IXServerEventsContext} ec Execution context
       * @param {de.elo.ix.client.Sord} sord Working version of the current sord object
       * @param {java.lang.String} fieldName Name of the currently focused field
       */
      open: function (ec, sord, fieldName) {
        var me = this,
            filterValue;
        me.foreignFilterKey && me.reInitialize(me.useForeignConfig(sol.common.SordUtils.getObjKeyValue(sord, me.foreignFilterKey)));
        fieldName = String(fieldName);
        me.log.enter("open", { fieldName: fieldName });
        me.prepareCache();
        filterValue = sol.common.SordUtils.getObjKeyValue(sord, fieldName);
        me.filterTable(filterValue);
        me._keyNames = ["$KEY", "$VALUE", fieldName];
        me.log.exit("open");
      },

      /**
       * Opens a connection for ELOwf forms and map field capable components
       *
       * @param {de.elo.ix.client.IXServerEventsContext} ec Execution context
       * @param {java.util.Map} map Map of all entries passed by the client
       * @param {java.lang.String} focusName name of the currently focused field
       */
      openMap: function (ec, map, focusName) {
        var me = this,
            filterValue = "";
        me.foreignFilterKey && me.reInitialize(me.useForeignConfig(map[me.foreignFilterKey]));
        focusName = String(focusName);
        me.log.enter("openMap", { focusName: focusName });
        me.prepareCache();
        if (map && focusName && map[focusName]) {
          filterValue = String(map[focusName]);
        }
        me.filterTable(filterValue);
        me._keyNames = ["$KEY", "$VALUE", focusName];
        me.log.exit("openMap");
      },

      /**
       * Prepares the cache variables for the given language
       */
      prepareCache: function () {
        var me = this;
        me.language = me.config.language || ixConnect.loginResult.clientInfo.language;

        me.index = 0;

        if (me.tables[me.language]) {
          return;
        }

        me.tableTitles[me.language] = me.translate ? me.terms.getTerm(me.language, me.config.tableTitle) : me.config.tableTitle;

        me.tableHeaders[me.language] = me.config.columnHeaders.map(function (tableHeaderKey) {
          return me.translate ? me.terms.getTerm(me.language, tableHeaderKey) : tableHeaderKey;
        });
        me.tableHeaders[me.language].push(null);

        me.tables[me.language] = [];

        if (me.config.blankLine) {
          me.tables[me.language].push([" ", " ", ""]);
        }

        me.config.entries.forEach(function (entry) {
          var localizedValue = me.translate ? sol.common.TranslateTerms.getTerm(me.language, entry.value) : entry.value;
          me.tables[me.language].push([entry.key, localizedValue, entry.key + me.separatorString + localizedValue]);
        });
      },

      /**
       * Filters the table
       *
       * @param {String} filterValue Filter value
       */
      filterTable: function (filterValue) {
        var me = this,
            i;

        if (sol.common.StringUtils.isBlank(filterValue)) {
          me.filteredTable = me.tables[me.language];
          return;
        }

        filterValue = String(filterValue).trim().toLowerCase();
        if ((filterValue.indexOf(me.separatorString) > -1)) {
          me.filteredTable = me.tables[me.language];
          return;
        }

        me.filteredTable = [];
        // Search in keys
        for (i = 0; i < me.tables[me.language].length; i++) {
          if (String(me.tables[me.language][i][0]).toLowerCase() == filterValue) {
            me.filteredTable.push(me.tables[me.language][i]);
            break;
          }
        }
        if (me.filteredTable.length == 0) {
          // Search in values
          for (i = 0; i < me.tables[me.language].length; i++) {
            if (String(me.tables[me.language][i][1]).toLowerCase().indexOf(filterValue) > -1) {
              me.filteredTable.push(me.tables[me.language][i]);
            }
          }
        }
      },

      /**
       * Returns the next row of the table.
       *
       * @return {java.lang.String[]} table row
       */
      getNextRow: function () {
        var me = this;
        return me.filteredTable[me.index++];
      },

      /**
       * Returns the header of this table that can be displayed by the clients.
       *
       * @return {java.lang.String[]} table header
       */
      getHeader: function () {
        var me = this;
        return me.tableHeaders[me.language];
      },

      /**
       * Returns the keys of this table that can be used in order to map
       * map or group fields with columns.
       *
       * @return {java.lang.String[]} table keys
       */
      getKeyNames: function () {
        var me = this;
        return me._keyNames;
      },

      /**
       * Returns true if table has more rows.
       *
       * @return {Boolean} has more rows
       */
      hasMoreRows: function () {
        var me = this;
        return (this.index < (me.filteredTable.length));
      },

      /**
       * Returns the error message that should be displayed by the client
       * instead of the table data.
       *
       * @return {String} Error message
       */
      getMessage: function () {
        var me = this;
        return me.errorMessage;
      },

      /**
       * Returns a title for this table used by the user interface.
       *
       * @return {String} Title
       */
      getTitle: function () {
        var me = this;
        return me.tableTitles[me.language];
      }
    });