docs: private void processCRMPair(MtasTokenIdFactory mtasTokenIdFactory,
        int position, String name, String text, Integer currentOffset,
        List<MtasCRMParserFunctionOutput> functionOutputList,
        MtasCRMAncestors unknownAncestors,
        Map<String, List<MtasParserObject>> currentList,
        Map<String, Map<Integer, Set<String>>> updateList,
        Map<String, Set<Integer>> idPositions, Map<String, Integer[]> idOffsets)
        throws MtasParserException, MtasConfigException {
  
      MtasParserType tmpCurrentType;
      MtasParserObject currentObject;
  
      if ((tmpCurrentType = crmPairTypes.get(name)) != null) {
        // get history
        HashMap<String, MtasParserObject> currentNamePairHistory;
        if (!historyPair.containsKey(name)) {
          currentNamePairHistory = new HashMap<>();
          historyPair.put(name, currentNamePairHistory);
        } else {
          currentNamePairHistory = historyPair.get(name);
        }
        Matcher m = pairPattern.matcher(text);
        if (m.find()) {
          String thisKey = m.group(1) + m.group(2);
          String otherKey = (m.group(1).equals("b") ? "e" : "b") + m.group(2);
          if (currentNamePairHistory.containsKey(otherKey)) {
            currentObject = currentNamePairHistory.remove(otherKey);
            currentObject.setText(currentObject.getText() + "+" + text);
            currentObject.addPosition(position);
            processFunctions(name, text, MAPPING_TYPE_CRM_PAIR,
                functionOutputList);
            currentObject.setRealOffsetEnd(currentOffset + 1);
            currentObject.setOffsetEnd(currentOffset + 1);
            idPositions.put(currentObject.getId(), currentObject.getPositions());
            idOffsets.put(currentObject.getId(), currentObject.getOffset());
            currentObject.updateMappings(idPositions, idOffsets);
            unknownAncestors.unknown = currentObject.getUnknownAncestorNumber();
            computeMappingsFromObject(mtasTokenIdFactory, currentObject,
                currentList, updateList);
          } else {
            currentObject = new MtasParserObject(tmpCurrentType);
            currentObject.setUnknownAncestorNumber(unknownAncestors.unknown);
            currentObject.setRealOffsetStart(currentOffset);
            currentObject.setOffsetStart(currentOffset);
            currentObject.setText(text);
            currentObject.addPosition(position);
            if (!prevalidateObject(currentObject, currentList)) {
              unknownAncestors.unknown++;
            } else {
              currentNamePairHistory.put(thisKey, currentObject);
              processFunctions(name, text, MAPPING_TYPE_CRM_PAIR,
                  functionOutputList);
              currentObject.setRealOffsetEnd(currentOffset + 1);
              currentObject.setOffsetEnd(currentOffset + 1);
              idPositions.put(currentObject.getId(),
                  currentObject.getPositions());
              idOffsets.put(currentObject.getId(), currentObject.getOffset());
              // offset always null, so update later with word (should be
              // possible)
              if ((currentObject.getId() != null)
                  && (!currentList.get(MAPPING_TYPE_WORD).isEmpty())) {
                currentList.get(MAPPING_TYPE_WORD)
                    .get((currentList.get(MAPPING_TYPE_WORD).size() - 1))
                    .addUpdateableIdWithOffset(currentObject.getId());
              }
  
            }
          }
        }
  
      }
  
    }