docs: @Override
      public synchronized ChainData addChain(String chainName, FlowType chainType, String[] channelNameList) throws ChannelException, ChainException {
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.entry(tc, "addChain: " + chainName);
          }
          if (null == chainName) {
              throw new InvalidChainNameException("Input chain name is null");
          }
          if (null == channelNameList || 0 == channelNameList.length) {
              throw new InvalidChannelNameException("Invalid channel list");
          }
  
          ChainDataImpl chainData = chainDataMap.get(chainName);
          // Ensure the chain name doesn't already exist in the framework.
          if (null != chainData) {
              InvalidChainNameException e = new InvalidChainNameException("Chain config already exists, " + chainName);
              FFDCFilter.processException(e, getClass().getName() + ".addChain", "1411", this, new Object[] { chainData });
              throw e;
          }
  
          int zRegion = RegionType.NOT_ON_Z;
          boolean okToAdd = true;
          ChannelFactory factory = null;
  
          // Determine if we are running on Z, and if so, what region we are in
          if (currentlyOnZ()) {
              zRegion = currentZRegion();
          }
  
          // Ensure each of the channel configurations already exist in the framework.
          ChannelData[] channelDataArray = new ChannelData[channelNameList.length];
          ChannelData channelData = null;
          int i = 0;
          int lastIndex = channelNameList.length - 1;
          for (i = 0; i <= lastIndex; i++) {
              factory = null;
              channelData = channelDataMap.get(channelNameList[i]);
              if (null == channelData) {
                  InvalidChannelNameException e = new InvalidChannelNameException("Can't add chain config due to unknown channel, " + channelNameList[i]);
                  FFDCFilter.processException(e, getClass().getName() + ".addChain", "1443", this, new Object[] { channelNameList[i] });
                  throw e;
              }
              if (zRegion != RegionType.NOT_ON_Z && chainType.equals(FlowType.INBOUND)) {
                  // On Z and inbound, need to do some special Z things
                  if (i == lastIndex) {
                      if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                          Tr.debug(tc, "On Z, looking at Application Channel, " + channelData.getName());
                      }
                      // Looking at the Inbound Application Channel
                      factory = getChannelFactoryInternal(channelData.getFactoryType(), false);
  
                      if (zRegion == RegionType.SR_REGION) {
                          // in SR, check if we should start this chain
                          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Operating in Servant Region");
                          }
                          if (factory instanceof BoundRegion) {
                              if (!(((BoundRegion) factory).isServantStartable(channelDataMap))) {
                                  // channel does not want to start given the current
                                  // configuration
                                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                      Tr.debug(tc, "Channel says it is not startable in Servant Region");
                                  }
                                  okToAdd = false;
                              }
                          }
                      } else if ((zRegion == BoundRegion.CR_REGION) || (zRegion == BoundRegion.CRA_REGION)) {
                          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Operating in CR or CRA Region, Region constant: " + zRegion);
                          }
                          // In a secure region. check if the channel implements BoundRegion,
                          // and is configured to run in this region
                          if (factory instanceof BoundRegion) {
                              if (zRegion != (((BoundRegion) factory).getRegion(channelDataMap))) {
                                  // channel does not support running in this region
                                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                      Tr.debug(tc, "This Channel says it is not startable in this Region: getRegion returned: " + ((BoundRegion) factory).getRegion(channelDataMap));
                                  }
                                  okToAdd = false;
                              }
                          }
                      }
                  } else {
                      // Not Looking at an application channel in the chain
                      if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                          Tr.debug(tc, "On Z, looking at non-application Channel, " + channelData.getName());
                      }
  
                      if ((zRegion == BoundRegion.CR_REGION) || (zRegion == BoundRegion.CRA_REGION)) {
                          // mark what region this channel is being asked to run in, and
                          // log a warning if this gives a sharable problem
                          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Operating in CR or CRA Region, Region constant: " + zRegion);
                          }
  
                          // Get Channel entry from HashMap
                          Integer channelEntry = ChannelZRegions.get(channelData.getName());
                          if (channelEntry == null) {
                              // new entry
                              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                  Tr.debug(tc, "First time setting region entry for this channel");
                              }
                              ChannelZRegions.put(channelData.getName(), Integer.valueOf(zRegion));
                          } else {
                              // check to see if we are being asked to run in a different region
                              int entryValue = channelEntry.intValue();
  
                              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                  Tr.debug(tc, "Not first time setting region entry for this channel, current region entry is: " + entryValue);
                              }
  
                              if ((entryValue & zRegion) == 0) {
                                  // being asked to be run in a different region, see if this is
                                  // sharable
                                  factory = getChannelFactoryInternal(channelData.getFactoryType(), false);
                                  if (factory instanceof CrossRegionSharable) {
                                      if (!(((CrossRegionSharable) factory).isSharable(channelDataMap))) {
                                          // non-sharable channel is being put into different regions
                                          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                              Tr.debug(tc, "Channel says it is not sharable");
                                          }
                                          Tr.warning(tc, "channel.shared.warning", new Object[] { channelData.getName() });
  
                                      }
                                  }
                                  // update regions that this channel is being used in
                                  ChannelZRegions.put(channelData.getName(), Integer.valueOf(entryValue | zRegion));
                              }
                          }
                      }
                  }
              }
              channelDataArray[i] = channelData;
          } // end-for loop on channels
  
          if (okToAdd) {
              try {
                  // Store values for the host and port in the chain if it is inbound.
                  Map<Object, Object> chainProperties = null;
                  if (FlowType.INBOUND.equals(chainType)) {
                      chainProperties = new HashMap<Object, Object>();
                      Map<Object, Object> tcpProperties = channelDataArray[0].getPropertyBag();
                      chainProperties.put(ChannelFrameworkConstants.HOST_NAME, tcpProperties.get(ChannelFrameworkConstants.HOST_NAME));
                      chainProperties.put(ChannelFrameworkConstants.PORT, tcpProperties.get(ChannelFrameworkConstants.PORT));
                      chainProperties.put(ChannelFrameworkConstants.LISTENING_PORT, tcpProperties.get(ChannelFrameworkConstants.LISTENING_PORT));
                  }
                  chainData = (ChainDataImpl) createChainData(chainName, chainType, channelDataArray, chainProperties);
                  // Add the new chain to the framework configuration.
                  this.chainDataMap.put(chainName, chainData);
                  // Associate any global chain event listeners.
                  for (int j = 0; j < globalChainEventListeners.size(); j++) {
                      chainData.addChainEventListener(globalChainEventListeners.get(j));
                  }
              } catch (IncoherentChainException e) {
                  FFDCFilter.processException(e, getClass().getName() + ".addChain", "1601", this, new Object[] { chainName, chainType, channelDataArray });
                  throw e;
              }
          }
  
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.exit(tc, "addChain");
          }
          return chainData;
      }