private List<NormalizationPlan> computeMergeNormalizationPlans(final NormalizeContext ctx) {
    final NormalizerConfiguration configuration = normalizerConfiguration;
    if (ctx.getTableRegions().size() < configuration.getMergeMinRegionCount(ctx)) {
      LOG.debug(
        "Table {} has {} regions, required min number of regions for normalizer to run"
          + " is {}, not computing merge plans.",
        ctx.getTableName(), ctx.getTableRegions().size(), configuration.getMergeMinRegionCount());
      return Collections.emptyList();
    }

    final long avgRegionSizeMb = (long) ctx.getAverageRegionSizeMb();
    if (avgRegionSizeMb < configuration.getMergeMinRegionSizeMb(ctx)) {
      return Collections.emptyList();
    }
    LOG.debug("Computing normalization plan for table {}. average region size: {} MB, number of"
      + " regions: {}.", ctx.getTableName(), avgRegionSizeMb, ctx.getTableRegions().size());

    // this nested loop walks the table's region chain once, looking for contiguous sequences of
    // regions that meet the criteria for merge. The outer loop tracks the starting point of the
    // next sequence, the inner loop looks for the end of that sequence. A single sequence becomes
    // an instance of MergeNormalizationPlan.

    final List<NormalizationPlan> plans = new LinkedList<>();
    final List<NormalizationTarget> rangeMembers = new LinkedList<>();
    long sumRangeMembersSizeMb;
    int current = 0;
    for (int rangeStart = 0; rangeStart < ctx.getTableRegions().size() - 1
      && current < ctx.getTableRegions().size();) {
      // walk the region chain looking for contiguous sequences of regions that can be merged.
      rangeMembers.clear();
      sumRangeMembersSizeMb = 0;
      for (current = rangeStart; current < ctx.getTableRegions().size(); current++) {
        final RegionInfo regionInfo = ctx.getTableRegions().get(current);
        final long regionSizeMb = getRegionSizeMB(regionInfo);
        if (skipForMerge(configuration, ctx, regionInfo)) {
          // this region cannot participate in a range. resume the outer loop.
          rangeStart = Math.max(current, rangeStart + 1);
          break;
        }
        if (
          // when there are no range members, seed the range with whatever we have. this way we're
          // prepared in case the next region is 0-size.
          rangeMembers.isEmpty()
            // when there is only one region and the size is 0, seed the range with whatever we
            // have.
            || (rangeMembers.size() == 1 && sumRangeMembersSizeMb == 0)
            // add an empty region to the current range only if it doesn't exceed max merge request
            // region count
            || (regionSizeMb == 0 && rangeMembers.size() < getMergeRequestMaxNumberOfRegionsCount())
            // add region if current range region size is less than avg region size of table
            // and current range doesn't exceed max merge request region count
            || ((regionSizeMb + sumRangeMembersSizeMb <= avgRegionSizeMb)
              && (rangeMembers.size() < getMergeRequestMaxNumberOfRegionsCount()))
        ) {
          // add the current region to the range when there's capacity remaining.
          rangeMembers.add(new NormalizationTarget(regionInfo, regionSizeMb));
          sumRangeMembersSizeMb += regionSizeMb;
          continue;
        }
        // we have accumulated enough regions to fill a range. resume the outer loop.
        rangeStart = Math.max(current, rangeStart + 1);
        break;
      }
      if (rangeMembers.size() > 1) {
        plans.add(new MergeNormalizationPlan.Builder().setTargets(rangeMembers).build());
      }
    }
    return plans;
  }
Computes the merge plans that should be executed for this table to converge average region
towards target average or target region count.
hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SimpleRegionNormalizer.java