private FlowRuleEvent addOrUpdateFlowRuleInternal(FlowEntry rule) {
    // check if this new rule is an update to an existing entry
    StoredFlowEntry stored = flowTable.getFlowEntry(rule);
    if (stored != null) {
      stored.setBytes(rule.bytes());
      stored.setLife(rule.life());
      stored.setPackets(rule.packets());
      if (stored.state() == FlowEntryState.PENDING_ADD) {
        stored.setState(FlowEntryState.ADDED);
        return new FlowRuleEvent(Type.RULE_ADDED, rule);
      }
      return new FlowRuleEvent(Type.RULE_UPDATED, rule);
    }

    // TODO: Confirm if this behavior is correct. See SimpleFlowRuleStore
    // TODO: also update backup if the behavior is correct.
    flowTable.add(rule);
    return null;
  }
  /**
   * add or update typed flow entry from flow entry into the internal flow table.
   *
   * @param flowEntries the flow entries
   */
  public synchronized void addOrUpdateFlows(FlowEntry... flowEntries) {
    for (FlowEntry fe : flowEntries) {
      // check if this new rule is an update to an existing entry
      TypedStoredFlowEntry stored = deviceFlowTable.getFlowEntry(fe);

      if (stored != null) {
        // duplicated flow entry is collected!, just skip
        if (fe.bytes() == stored.bytes()
            && fe.packets() == stored.packets()
            && fe.life() == stored.life()) {
          log.debug(
              "addOrUpdateFlows:, FlowId="
                  + Long.toHexString(fe.id().value())
                  + ",is DUPLICATED stats collection, just skip."
                  + " AdaptiveStats collection thread for {}",
              sw.getStringId());

          // FIXME modification of "stored" flow entry outside of store
          stored.setLastSeen();
          continue;
        } else if (fe.life() < stored.life()) {
          // Invalid updates the stats values, i.e., bytes, packets, durations ...
          log.debug(
              "addOrUpdateFlows():"
                  + " Invalid Flow Update! The new life is SMALLER than the previous one, jus skip."
                  + " new flowId="
                  + Long.toHexString(fe.id().value())
                  + ", old flowId="
                  + Long.toHexString(stored.id().value())
                  + ", new bytes="
                  + fe.bytes()
                  + ", old bytes="
                  + stored.bytes()
                  + ", new life="
                  + fe.life()
                  + ", old life="
                  + stored.life()
                  + ", new lastSeen="
                  + fe.lastSeen()
                  + ", old lastSeen="
                  + stored.lastSeen());
          // go next
          // FIXME modification of "stored" flow entry outside of store
          stored.setLastSeen();
          continue;
        }

        // update now
        // FIXME modification of "stored" flow entry outside of store
        stored.setLife(fe.life());
        stored.setPackets(fe.packets());
        stored.setBytes(fe.bytes());
        stored.setLastSeen();
        if (stored.state() == FlowEntry.FlowEntryState.PENDING_ADD) {
          // flow is really RULE_ADDED
          stored.setState(FlowEntry.FlowEntryState.ADDED);
        }
        // flow is RULE_UPDATED, skip adding and just updating flow live table
        // deviceFlowTable.calAndSetFlowLiveType(stored);
        continue;
      }

      // add new flow entry, we suppose IMMEDIATE_FLOW
      TypedStoredFlowEntry newFlowEntry =
          new DefaultTypedFlowEntry(fe, FlowLiveType.IMMEDIATE_FLOW);
      deviceFlowTable.addWithCalAndSetFlowLiveType(newFlowEntry);
    }
  }