/**
  * Using the criteria in {@code parentIdMap}, recursively adds all children under the partition ID
  * of {@code parentNode} to {@code parentNode}.
  *
  * @param parentNode required
  * @param parentIdMap the multimap from parent partition ID to list of child criteria
  */
 private static void addChildNodes(
     ProductPartitionNode parentNode, ListMultimap<Long, AdGroupCriterion> parentIdMap) {
   if (parentIdMap.containsKey(parentNode.getProductPartitionId())) {
     parentNode = parentNode.asSubdivision();
   }
   for (AdGroupCriterion adGroupCriterion : parentIdMap.get(parentNode.getProductPartitionId())) {
     ProductPartition partition = (ProductPartition) adGroupCriterion.getCriterion();
     ProductPartitionNode childNode = parentNode.addChild(partition.getCaseValue());
     childNode = childNode.setProductPartitionId(partition.getId());
     if (ProductPartitionType.SUBDIVISION.equals(partition.getPartitionType())) {
       childNode = childNode.asSubdivision();
     } else {
       if (adGroupCriterion instanceof BiddableAdGroupCriterion) {
         childNode = childNode.asBiddableUnit();
         Money cpcBidAmount = getBid((BiddableAdGroupCriterion) adGroupCriterion);
         if (cpcBidAmount != null) {
           childNode = childNode.setBid(cpcBidAmount.getMicroAmount());
         }
       } else {
         childNode = childNode.asExcludedUnit();
       }
     }
     addChildNodes(childNode, parentIdMap);
   }
 }
  /**
   * Returns a new tree based on a non-empty collection of ad group criteria. All parameters
   * required.
   *
   * @param adGroupId the ID of the ad group
   * @param parentIdMap the multimap from parent product partition ID to child criteria
   * @return a new ProductPartitionTree
   */
  private static ProductPartitionTree createNonEmptyAdGroupTree(
      Long adGroupId, ListMultimap<Long, AdGroupCriterion> parentIdMap) {
    Preconditions.checkNotNull(adGroupId, "Null ad group ID");
    Preconditions.checkArgument(
        !parentIdMap.isEmpty(), "parentIdMap passed for ad group ID %s is empty", adGroupId);
    Preconditions.checkArgument(
        parentIdMap.containsKey(null),
        "No root criterion found in the list of ad group criteria for ad group ID %s",
        adGroupId);

    AdGroupCriterion rootCriterion = Iterables.getOnlyElement(parentIdMap.get(null));

    Preconditions.checkState(
        rootCriterion instanceof BiddableAdGroupCriterion,
        "Root criterion for ad group ID %s is not a BiddableAdGroupCriterion",
        adGroupId);
    BiddableAdGroupCriterion biddableRootCriterion = (BiddableAdGroupCriterion) rootCriterion;

    BiddingStrategyConfiguration biddingStrategyConfig =
        biddableRootCriterion.getBiddingStrategyConfiguration();
    Preconditions.checkState(
        biddingStrategyConfig != null,
        "Null bidding strategy config on the root node of ad group ID %s",
        adGroupId);
    ProductPartitionNode rootNode =
        new ProductPartitionNode(
            null,
            (ProductDimension) null,
            rootCriterion.getCriterion().getId(),
            new ProductDimensionComparator());

    // Set the root's bid if a bid exists on the BiddableAdGroupCriterion.
    Money rootNodeBid = getBid(biddableRootCriterion);
    if (rootNodeBid != null) {
      rootNode = rootNode.asBiddableUnit().setBid(rootNodeBid.getMicroAmount());
    }

    addChildNodes(rootNode, parentIdMap);

    return new ProductPartitionTree(adGroupId, biddingStrategyConfig, rootNode);
  }