docs: public void run()
      {
          final Map<String, Object> buffer = new HashMap<>();
          String line;
  
          if (socket == null)
          {
              throw new IllegalStateException("Unable to run: socket is null.");
          }
  
          this.die = false;
          this.dead = false;
  
          try
          {
              // main loop
              while (!this.die && (line = socket.readLine()) != null)
              {
                  // maybe we will find a better way to identify the protocol
                  // identifier but for now
                  // this works quite well.
                  if (line.startsWith("Asterisk Call Manager/") || line.startsWith("Asterisk Call Manager Proxy/")
                          || line.startsWith("Asterisk Manager Proxy/") || line.startsWith("OpenPBX Call Manager/")
                          || line.startsWith("CallWeaver Call Manager/"))
                  {
                      ProtocolIdentifierReceivedEvent protocolIdentifierReceivedEvent;
                      protocolIdentifierReceivedEvent = new ProtocolIdentifierReceivedEvent(source);
                      protocolIdentifierReceivedEvent.setProtocolIdentifier(line);
                      protocolIdentifierReceivedEvent.setDateReceived(DateUtil.getDate());
                      dispatcher.dispatchEvent(protocolIdentifierReceivedEvent);
                      continue;
                  }
  
                  /*
                   * Special handling for "Response: Follows" (CommandResponse) As
                   * we are using "\r\n" as the delimiter for line this also
                   * handles multiline results as long as they only contain "\n".
                   */
                  if ("Follows".equals(buffer.get("response")) && line.endsWith("--END COMMAND--"))
                  {
                      buffer.put(COMMAND_RESULT_RESPONSE_KEY, line);
                      continue;
                  }
  
                  if (line.length() > 0)
                  {
                      // begin of workaround for Astersik bug 13319
                      // see AJ-77
                      // Use this workaround only when line starts from "From "
                      // and "To "
                      int isFromAtStart = line.indexOf("From ");
                      int isToAtStart = line.indexOf("To ");
  
                      int delimiterIndex = isFromAtStart == 0 || isToAtStart == 0
                          ? line.indexOf(" ") : line.indexOf(":");
                      // end of workaround for Astersik bug 13319
  
                      int delimiterLength = 1;
  
                      if (delimiterIndex > 0 && line.length() > delimiterIndex + delimiterLength)
                      {
                          String name = line.substring(0, delimiterIndex).toLowerCase(Locale.ENGLISH).trim();
                          String value = line.substring(delimiterIndex + delimiterLength).trim();
  
                          addToBuffer(buffer, name, value);
                          // TODO tracing
                          // logger.debug("Got name [" + name + "], value: [" +
                          // value + "]");
                      }
                  }
  
                  // an empty line indicates a normal response's or event's end so
                  // we build
                  // the corresponding value object and dispatch it through the
                  // ManagerConnection.
                  if (line.length() == 0)
                  {
                      if (buffer.containsKey("event"))
                      {
                          // TODO tracing
                          // logger.debug("attempting to build event: " +
                          // buffer.get("event"));
                          ManagerEvent event = buildEvent(source, buffer);
                          if (event != null)
                          {
                              dispatcher.dispatchEvent(event);
  
                              // Backwards compatibility for bridge events.
                              // Asterisk 13 uses BridgeCreate,
                              // BridgeEnter, BridgeLeave and BridgeDestroy
                              // events.
                              // So here we track active bridges and simulate
                              // BridgeEvent's for them allowing legacy code to
                              // still work with BridgeEvent's
                              ManagerEvent secondaryEvent = compatibility.handleEvent(event);
                              if (secondaryEvent != null)
                              {
                                  dispatcher.dispatchEvent(secondaryEvent);
                              }
                          }
                          else
                          {
                              logger.debug("buildEvent returned null");
                          }
                      }
                      else if (buffer.containsKey("response"))
                      {
                          ManagerResponse response = buildResponse(buffer);
                          // TODO tracing
                          // logger.debug("attempting to build response");
                          if (response != null)
                          {
                              dispatcher.dispatchResponse(response);
                          }
                      }
                      else
                      {
                          if (!buffer.isEmpty())
                          {
                              logger.debug("Buffer contains neither response nor event");
                          }
                      }
  
                      buffer.clear();
                  }
              }
              this.dead = true;
              logger.debug("Reached end of stream, terminating reader.");
          }
          catch (IOException e)
          {
              this.terminationException = e;
              this.dead = true;
              logger.info("Terminating reader thread: " + e.getMessage());
          }
          finally
          {
              this.dead = true;
              // cleans resources and reconnects if needed
              DisconnectEvent disconnectEvent = new DisconnectEvent(source);
              disconnectEvent.setDateReceived(DateUtil.getDate());
              dispatcher.dispatchEvent(disconnectEvent);
          }
      }