/// <summary> /// Creates a new AdGroupCriterion configured for a REMOVE operation. /// </summary> /// <param name="node">The node whose criterion should be removed.</param> /// <param name="adGroupId">The ad group ID of the criterion.</param> /// <returns>The AdGroupCriterion to be removed.</returns> internal static AdGroupCriterion CreateCriterionForRemove(ProductPartitionNode node, long adGroupId) { PreconditionUtilities.CheckNotNull(node, ShoppingMessages.NodeCannotBeNull); return new AdGroupCriterion() { adGroupId = adGroupId, criterion = new ProductPartition() { id = node.ProductPartitionId } }; }
/// <summary> /// Compares two product partition nodes. /// </summary> /// <param name="leftNode">The left node.</param> /// <param name="rightNode">The right node.</param> private static void CompareTree(ProductPartitionNode leftNode, ProductPartitionNode rightNode) { ProductDimensionEqualityComparer comparer = new ProductDimensionEqualityComparer(); Assert.True(comparer.Equals(leftNode.Dimension, rightNode.Dimension)); Assert.AreEqual(leftNode.Children.Count(), rightNode.Children.Count()); for (int i = 0; i < leftNode.Children.Count(); i++) { ProductPartitionNode leftChildNode = leftNode.Children.ElementAt(i); bool foundMatch = false; for (int j = 0; j < rightNode.Children.Count(); j++) { ProductPartitionNode rightChildNode = rightNode.Children.ElementAt(j); if (comparer.Equals(leftChildNode.Dimension, rightChildNode.Dimension)) { CompareTree(leftChildNode, rightChildNode); foundMatch = true; break; } } Assert.True(foundMatch, "Matching node not found."); } }
public void TestChildNodeBasicFunctionality() { rootNode = rootNode.AsSubdivision(); Assert.False(rootNode.IsUnit, "Parent should not be a unit."); Assert.True(rootNode.IsSubdivision, "Parent should be a subdivision."); ProductBrand childDimension = ProductDimensions.CreateBrand("google"); ProductPartitionNode childNode = rootNode.AddChild(childDimension); Assert.AreSame(childDimension, childNode.Dimension, "Child node merely wraps the " + "underlying dimension node."); Assert.AreSame(rootNode, childNode.Parent, "child.GetParent should return parentNode."); Assert.That(childNode.ProductPartitionId == 0, "Partition ID is incorrect."); Assert.That(childNode.Children.Count() == 0, "ChildNode should not have any children."); Assert.True(childNode.IsUnit, "New node should be a unit node by default."); Assert.True(childNode.IsBiddableUnit, "New node should be a biddable unit node by default."); Assert.That(rootNode.HasChild(childDimension), "rootNode.HasChild should return true when " + "passed the dimension of the added child"); Assert.False(rootNode.HasChild(ProductDimensions.CreateBrand("xyz")), "rootNode.HasChild " + "should return false when passed a dimension for a nonexistent child"); Assert.False(rootNode.HasChild(null), "rootNode.HasChild should return false when passed " + "a dimension for a nonexistent child"); }
/// <summary> /// Creates a new <see cref="AdGroupCriterion"/> configured for an /// <code>ADD</code> operation. /// </summary> /// <param name="node">The node whose criterion should be added.</param> /// <param name="adGroupId">The ad group ID of the criterion.</param> /// <param name="idGenerator">The temporary ID generator for new nodes.</param> /// <returns>An <see cref="AdGroupCriterion"/> object for <code>ADD</code> /// operation.</returns> internal static AdGroupCriterion CreateCriterionForAdd(ProductPartitionNode node, long adGroupId, TemporaryIdGenerator idGenerator) { PreconditionUtilities.CheckNotNull(node, ShoppingMessages.NodeCannotBeNull); AdGroupCriterion adGroupCriterion; if (node.IsExcludedUnit) { adGroupCriterion = new NegativeAdGroupCriterion(); } else { adGroupCriterion = new BiddableAdGroupCriterion() { biddingStrategyConfiguration = node.GetBiddingConfig() }; } adGroupCriterion.adGroupId = adGroupId; adGroupCriterion.criterion = node.GetCriterion(); adGroupCriterion.criterion.id = node.ProductPartitionId; if (node.Parent != null) { (adGroupCriterion.criterion as ProductPartition).parentCriterionId = node.Parent.ProductPartitionId; } return adGroupCriterion; }
/// <summary> /// Initializes a new instance of the <see cref="ProductPartitionNode"/> /// class. /// </summary> /// <param name="parentNode">The parent node.</param> /// <param name="dimension">The product dimension that this node wraps.</param> /// <param name="productPartitionId">The product partition ID.</param> public ProductPartitionNode(ProductPartitionNode parentNode, ProductDimension dimension, long productPartitionId) : this(parentNode, dimension, productPartitionId, new ProductDimensionEqualityComparer()) { }
/// <summary> /// Creates a SET operation for the specified node. /// </summary> /// <param name="node">The node.</param> /// <returns>An operation pair for the specified operation and node. /// </returns> private OperationPair CreateSetBidOperation(ProductPartitionNode node) { // TODO(Anash): This check is dangerous, since we should only depend on parent-child // relationships, not ID relationships. PreconditionUtilities.CheckArgument(node.ProductPartitionId >= NEW_ROOT_ID, string.Format(ShoppingMessages.NodeForSetCannotHaveNegativeId, node)); AdGroupCriterionOperation setOp = new AdGroupCriterionOperation(); setOp.@operator = Operator.SET; setOp.operand = ProductPartitionNodeAdapter.CreateCriterionForSetBid(node, adGroupId); return new OperationPair(node, setOp); }
/// <summary> /// Returns the <see cref="NodeDifference"/> between the original node and /// the new node. /// </summary> /// <param name="originalNode">The original node.</param> /// <param name="newNode">The new node.</param> /// <param name="dimensionComparator">The dimension comparator.</param> private static NodeDifference Diff(ProductPartitionNode originalNode, ProductPartitionNode newNode, IEqualityComparer<ProductDimension> dimensionComparator) { NodeDifference nodeDifference; if (originalNode == null && newNode == null) { nodeDifference = NodeDifference.NONE; } else if (originalNode == null) { nodeDifference = NodeDifference.NEW_NODE; } else if (newNode == null) { nodeDifference = NodeDifference.REMOVED_NODE; } else if (!dimensionComparator.Equals(originalNode.Dimension, newNode.Dimension)) { throw new InvalidOperationException(string.Format( ShoppingMessages.ProductDimensionMismatch, originalNode.Dimension, newNode.Dimension)); } else if (originalNode.IsUnit != newNode.IsUnit) { nodeDifference = NodeDifference.PARTITION_TYPE_CHANGE; } else if (originalNode.IsExcludedUnit != newNode.IsExcludedUnit) { nodeDifference = NodeDifference.EXCLUDED_UNIT_CHANGE; } else if (!originalNode.IsExcludedUnit && originalNode.IsUnit && newNode.IsUnit) { // Both nodes are non-excluded units - the only possible difference // left is the bid. nodeDifference = (originalNode.CpcBid != newNode.CpcBid) ? NodeDifference.BID_CHANGE : NodeDifference.NONE; } else { nodeDifference = NodeDifference.NONE; } return nodeDifference; }
/// <summary> /// Creates ADD operations for the node and ALL of its children. /// </summary> /// <param name="node">The node.</param> /// <param name="idGenerator">The temporary ID generator for new nodes.</param> /// <returns>A list of operation pair for the specified operation and nodes. /// </returns> private List<OperationPair> CreateAddOperations(ProductPartitionNode node, TemporaryIdGenerator idGenerator) { AdGroupCriterionOperation addOp = new AdGroupCriterionOperation(); addOp.@operator = Operator.ADD; // Overwrite the ID set by the user when doing ADD operations. This // minimizes the chances of a malformed tree. node.ProductPartitionId = idGenerator.Next; addOp.operand = ProductPartitionNodeAdapter.CreateCriterionForAdd(node, adGroupId, idGenerator); List<OperationPair> operationsList = new List<OperationPair>(); operationsList.Add(new OperationPair(node, addOp)); // Recursively add all of this node's children to the operations list. foreach (ProductPartitionNode child in node.Children) { operationsList.AddRange(CreateAddOperations(child, idGenerator)); } return operationsList; }
/// <summary> /// Clones this instance. /// </summary> /// <returns>The new node.</returns> internal ProductPartitionNode Clone() { ProductDimension newDimension = null; if (this.Dimension != null) { newDimension = (ProductDimension) SerializationUtilities.CloneObject( this.Dimension); } ProductPartitionNode newNode = new ProductPartitionNode(null, newDimension, this.ProductPartitionId, this.children.Comparer); newNode = CopyProperties(this, newNode); newNode.CloneChildrenFrom(this.Children); return newNode; }
/// <summary> /// Using the criteria in <paramref name="parentIdMap"/>, recursively adds /// all children under the partition ID of <paramref name="parentNode"/> to /// <paramref name="parentNode"/>. /// </summary> /// <param name="parentNode">The parent node.</param> /// <param name="parentIdMap">The multimap from parent partition ID to list /// of child criteria</param> private static void AddChildNodes(ProductPartitionNode parentNode, Dictionary<long, List<AdGroupCriterion>> parentIdMap) { List<AdGroupCriterion> childCriteria = null; if (parentIdMap.ContainsKey(parentNode.ProductPartitionId)) { childCriteria = parentIdMap[parentNode.ProductPartitionId]; } // no children, return. if (childCriteria == null || childCriteria.Count == 0) { return; } // Ensure that the parent is a subdivision. parentNode.AsSubdivision(); foreach (AdGroupCriterion childCriterion in childCriteria) { ProductPartition partition = (ProductPartition) childCriterion.criterion; ProductPartitionNode childNode = parentNode.AddChild(partition.caseValue); childNode.ProductPartitionId = partition.id; if (childCriterion is BiddableAdGroupCriterion) { childNode = childNode.AsBiddableUnit(); Money cpcBidAmount = GetBid((BiddableAdGroupCriterion) childCriterion); if (cpcBidAmount != null) { childNode.CpcBid = cpcBidAmount.microAmount; } } else { childNode = childNode.AsExcludedUnit(); } AddChildNodes(childNode, parentIdMap); } }
/// <summary> /// Initializes a new instance of the <see cref="ProductPartitionNode"/> /// class. /// </summary> /// <param name="parentNode">The parent node.</param> /// <param name="dimension">The product dimension that this node wraps.</param> /// <param name="productPartitionId">The product partition ID.</param> /// <param name="comparer">The comparer for comparing instances of this /// product dimension.</param> public ProductPartitionNode(ProductPartitionNode parentNode, ProductDimension dimension, long productPartitionId, IEqualityComparer<ProductDimension> comparer) { this.parentNode = parentNode; this.dimension = dimension; this.children = new Dictionary<ProductDimension, ProductPartitionNode>(comparer); this.productPartitionId = productPartitionId; this.nodeState = new BiddableUnitState(); }
/// <summary> /// Adds the childNode as a child under this node. /// </summary> /// <param name="childNode">The child node.</param> /// <returns>The child node.</returns> /// <exception cref="System.ArgumentException"> if the parent node already /// contains the child node's dimension.</exception> public ProductPartitionNode AddChild(ProductPartitionNode childNode) { if (!this.IsSubdivision) { throw new ArgumentException(string.Format(ShoppingMessages.ParentNodeIsNotSubdivision, this.Dimension)); } if (children.ContainsKey(childNode.Dimension)) { throw new ArgumentException(string.Format(ShoppingMessages.ChildNodeExists, childNode.Dimension)); } children.Add(childNode.Dimension, childNode); return childNode; }
public void TestCreateMultiNodeTreeFromScratch() { ProductPartitionTree tree = ProductPartitionTree.CreateAdGroupTree( new List<AdGroupCriterion>()); ProductPartitionNode rootNode = tree.Root.AsSubdivision(); ProductPartitionNode brand1 = rootNode.AddChild(ProductDimensions.CreateBrand("google")).AsSubdivision(); ProductPartitionNode brand1Offer1 = brand1.AddChild(ProductDimensions.CreateOfferId("A")); brand1Offer1.AsBiddableUnit().CpcBid = 1000000L; ProductPartitionNode brand1Offer2 = brand1.AddChild(ProductDimensions.CreateOfferId()).AsExcludedUnit(); ProductPartitionNode brand2 = rootNode.AddChild(ProductDimensions.CreateBrand()).AsExcludedUnit(); ProductPartitionNode[] nodes = new ProductPartitionNode[] { rootNode, brand1, brand1Offer1, brand1Offer2, brand2 }; AdGroupCriterionOperation[] mutateOperations = tree.GetMutateOperations(); for (int i = 0; i < nodes.Length; i++) { List<AdGroupCriterionOperation> nodeOperations = shoppingTestUtils.GetOperationsForNode(nodes[i], mutateOperations); Assert.That(nodeOperations.Count == 1); Assert.That(nodeOperations[0].@operator == Operator.ADD); ProductPartition partition = (ProductPartition) nodeOperations[0].operand.criterion; Assert.That(partition.id == nodes[i].ProductPartitionId); if (nodes[i].Parent == null) { Assert.That(partition.parentCriterionIdSpecified == false); } else { Assert.That(partition.parentCriterionId == nodes[i].Parent.ProductPartitionId); } Assert.That(partition.caseValue == nodes[i].Dimension); } }
/// <summary> /// Removes a child node that has matching dimension with the child node. /// </summary> /// <param name="childDimension">The child node.</param> /// <returns>This node.</returns> public ProductPartitionNode RemoveChild(ProductPartitionNode childNode) { return(RemoveChild(childNode.Dimension)); }
/// <summary> /// Gets the operations for node. /// </summary> /// <param name="partitionNode">The partition node.</param> /// <param name="mutateOperations">The list of all mutate operations.</param> /// <returns>The list of operations that apply to partitionNode.</returns> internal List<AdGroupCriterionOperation> GetOperationsForNode( ProductPartitionNode partitionNode, AdGroupCriterionOperation[] mutateOperations) { ProductDimensionEqualityComparer comparer = new ProductDimensionEqualityComparer(); List<AdGroupCriterionOperation> retval = new List<AdGroupCriterionOperation>(); foreach (AdGroupCriterionOperation operation in mutateOperations) { switch (operation.@operator) { case Operator.SET: case Operator.REMOVE: if (operation.operand.criterion.id == partitionNode.ProductPartitionId) { retval.Add(operation); } break; case Operator.ADD: if (comparer.Equals((operation.operand.criterion as ProductPartition).caseValue, partitionNode.Dimension)) { retval.Add(operation); } break; } } return retval; }
/// <summary> /// Creates a new AdGroupCriterion configured for a SET operation that will /// set the criterion's bid. /// </summary> /// <param name="node">The node whose criterion should be updated.</param> /// <param name="adGroupId">The ad group ID of the criterion.</param> /// <param name="biddingConfig">The bidding strategy configuration of the /// criterion.</param> /// <returns>The AdGroupCriterion for SET operation.</returns> internal static AdGroupCriterion CreateCriterionForSetBid(ProductPartitionNode node, long adGroupId) { PreconditionUtilities.CheckNotNull(node, ShoppingMessages.NodeCannotBeNull); PreconditionUtilities.CheckArgument(node.IsBiddableUnit, string.Format(ShoppingMessages.NodeForBidUpdateIsNotBiddable, node.ProductPartitionId)); return new BiddableAdGroupCriterion() { adGroupId = adGroupId, criterion = node.GetCriterion(), biddingStrategyConfiguration = node.GetBiddingConfig() }; }
/// <summary> /// Adds a NEW child for <code>childDimension</code> under this node. /// </summary> /// <param name="childDimension">The <code>ProductDimension</code> for the /// new child</param> /// <returns>The newly created child node.</returns> public ProductPartitionNode AddChild(ProductDimension childDimension) { // Passing a productPartitionId = 0 is insignificant here. ProductPartitionNode newChild = new ProductPartitionNode(this, childDimension, 0, children.Comparer); return AddChild(newChild); }
public void TestRemoveChildThatDoesNotExistFails() { rootNode = rootNode.AsSubdivision(); Assert.Throws<ArgumentException>(delegate() { rootNode.RemoveChild(ProductDimensions.CreateBrand("google")); }); }
/// <summary> /// Removes a child node that has matching dimension with the child node. /// </summary> /// <param name="childDimension">The child node.</param> /// <returns>This node.</returns> public ProductPartitionNode RemoveChild(ProductPartitionNode childNode) { return RemoveChild(childNode.Dimension); }
public void TestAddChildThatExistsFails() { rootNode = rootNode.AsSubdivision(); rootNode.AddChild(ProductDimensions.CreateBrand("google")); // Add the same child again. The call should fail. Assert.Throws<ArgumentException>(delegate() { rootNode.AddChild(ProductDimensions.CreateBrand("google")); }); // Add the same child again, this time with a different case. // The call should fail. Assert.Throws<ArgumentException>(delegate() { rootNode.AddChild(ProductDimensions.CreateBrand("GOOGLE")); }); }
/// <summary> /// Performs a <em>shallow</em> copy of properties from /// <paramref name="fromNode"/> to <paramref name="toNode"/>. /// </summary> /// <param name="fromNode">The node to copy from.</param> /// <param name="toNode">The node to copy to.</param> /// <returns><paramref name="toNode"/>, with its properties updated.</returns> /// <remarks>Does <em>not</em> change the parent node of /// <paramref name="toNode"/>.</remarks> public static ProductPartitionNode CopyProperties(ProductPartitionNode fromNode, ProductPartitionNode toNode) { switch (fromNode.nodeState.NodeType) { case NodeType.BIDDABLE_UNIT: toNode = toNode.AsBiddableUnit(); toNode.CpcBid = fromNode.CpcBid; break; case NodeType.EXCLUDED_UNIT: toNode = toNode.AsExcludedUnit(); break; case NodeType.SUBDIVISION: toNode = toNode.AsSubdivision(); break; default: throw new InvalidOperationException( "Unrecognized node state: " + fromNode.nodeState.NodeType); } toNode.ProductPartitionId = fromNode.ProductPartitionId; return toNode; }
public void TestSetBidOnSubdivisionFails() { rootNode = rootNode.AsSubdivision(); Assert.Throws<InvalidOperationException>(delegate() { rootNode.CpcBid = 1; }); }
/// <summary> /// Returns a new tree based on a non-empty collection of ad group criteria. /// </summary> /// <param name="adGroupId">The ad group ID.</param> /// <param name="parentIdMap">The multimap from parent product partition ID /// to child criteria.</param> /// <returns>a new product partition tree.</returns> private static ProductPartitionTree CreateAdGroupTree(long adGroupId, Dictionary<long, List<AdGroupCriterion>> parentIdMap) { ProductPartitionNode rootNode = null; if (parentIdMap.Count == 0) { rootNode = new ProductPartitionNode(null, null, NEW_ROOT_ID); } else { PreconditionUtilities.CheckState(parentIdMap.ContainsKey(ROOT_PARENT_ID), string.Format(ShoppingMessages.RootCriteriaNotFoundInCriteriaList, adGroupId)); PreconditionUtilities.CheckState(parentIdMap[ROOT_PARENT_ID].Count == 1, string.Format(ShoppingMessages.MoreThanOneRootFound, adGroupId)); AdGroupCriterion rootCriterion = parentIdMap[ROOT_PARENT_ID][0]; PreconditionUtilities.CheckState(rootCriterion is BiddableAdGroupCriterion, string.Format(ShoppingMessages.RootCriterionIsNotBiddable, adGroupId)); BiddableAdGroupCriterion biddableRootCriterion = (BiddableAdGroupCriterion) rootCriterion; rootNode = new ProductPartitionNode(null, null, rootCriterion.criterion.id, new ProductDimensionEqualityComparer()); // Set the root's bid if a bid exists on the BiddableAdGroupCriterion. Money rootNodeBid = GetBid(biddableRootCriterion); if (rootNodeBid != null) { rootNode.AsBiddableUnit().CpcBid = rootNodeBid.microAmount; } AddChildNodes(rootNode, parentIdMap); } return new ProductPartitionTree(adGroupId, rootNode); }
public void TestSetBidOnUnit() { rootNode = rootNode.AsSubdivision(); ProductBrand childDimension = ProductDimensions.CreateBrand("google"); ProductPartitionNode childNode = rootNode.AddChild(childDimension); Assert.That(childNode.CpcBidSpecified == false, "Bid should be null by default."); childNode.CpcBid = 1L; Assert.AreEqual(1L, childNode.CpcBid, "Bid does not reflect setBid."); Assert.True(childNode.IsBiddableUnit, "Node should be a biddable unit."); childNode = childNode.AsExcludedUnit(); Assert.True(childNode.IsExcludedUnit, "Node should be an excluded unit."); Assert.False(childNode.IsBiddableUnit, "Node should not be a biddable unit."); Assert.False(childNode.CpcBidSpecified, "Excluded unit should have a null bid"); // Set back to biddable. childNode = childNode.AsBiddableUnit(); Assert.True(childNode.IsBiddableUnit, "Node should be a biddable unit."); }
/// <summary> /// Adds to the operations list all operations required to mutate /// <paramref name="originalNode"/> to the state* of /// <paramref name="newNode"/>. /// </summary> /// <param name="originalNode">The original node.</param> /// <param name="newNode">The new node.</param> /// <param name="ops">The operations list to add to.</param> /// <param name="idGenerator">The temporary ID generator for ADD operations.</param> /// <returns>The set of child product dimensions that require further /// processing.</returns> private void AddMutateOperations(ProductPartitionNode originalNode, ProductPartitionNode newNode, List<OperationPair> ops, TemporaryIdGenerator idGenerator) { NodeDifference nodeDifference = Diff(originalNode, newNode, dimensionComparator); bool isProcessChildren; switch (nodeDifference) { case NodeDifference.NEW_NODE: ops.AddRange(CreateAddOperations(newNode, idGenerator)); // No need to further process children. The ADD operations above will include operations // for all children of newNode. isProcessChildren = false; break; case NodeDifference.REMOVED_NODE: ops.Add(CreateRemoveOperation(originalNode)); // No need to further process children. The REMOVE operation above will perform a // cascading delete of all children of newNode. isProcessChildren = false; break; case NodeDifference.PARTITION_TYPE_CHANGE: case NodeDifference.EXCLUDED_UNIT_CHANGE: ops.Add(CreateRemoveOperation(originalNode)); ops.AddRange(CreateAddOperations(newNode, idGenerator)); // No need to further process children. The ADD operations above will include operations // for all children of newNode. isProcessChildren = false; break; case NodeDifference.BID_CHANGE: // Ensure that the new node has the proper ID (this may have been lost if the node // was removed and then re-added). newNode.ProductPartitionId = originalNode.ProductPartitionId; ops.Add(CreateSetBidOperation(newNode)); // Process the children of newNode. The SET operation above will only handle changes // made to newNode, not its children. isProcessChildren = true; break; case NodeDifference.NONE: // Ensure that the new node has the proper ID (this may have been lost if the node // was removed and then re-added). newNode.ProductPartitionId = originalNode.ProductPartitionId; // This node does not have changes, but its children may. isProcessChildren = true; break; default: throw new InvalidOperationException("Unrecognized difference: " + nodeDifference); } if (isProcessChildren) { // Try to match the children in new and original trees to identify the // matching dimensions. foreach (ProductPartitionNode newChild in newNode.Children) { if (originalNode.HasChild(newChild.Dimension)) { // this is probably an edit. AddMutateOperations(originalNode.GetChild(newChild.Dimension), newChild, ops, idGenerator); } else { // this is a new node. AddMutateOperations(null, newChild, ops, idGenerator); } } foreach (ProductPartitionNode originalChild in originalNode.Children) { if (newNode.HasChild(originalChild.Dimension)) { // this is probably an edit. We dealt with it before continue; } else { // this is a removed node. AddMutateOperations(originalChild, null, ops, idGenerator); } } } }
public void TestNavigation() { rootNode = rootNode.AsSubdivision(); ProductBrand brandGoogle = ProductDimensions.CreateBrand("google"); ProductBrand brandOther = ProductDimensions.CreateBrand(null); ProductCanonicalCondition conditionNew = ProductDimensions.CreateCanonicalCondition(ProductCanonicalConditionCondition.NEW); ProductCanonicalCondition conditionUsed = ProductDimensions.CreateCanonicalCondition(ProductCanonicalConditionCondition.USED); ProductCanonicalCondition conditionOther = ProductDimensions.CreateCanonicalCondition(); // Build up the brand = Google node under the root. ProductPartitionNode brandGoogleNode = rootNode.AddChild(brandGoogle).AsSubdivision(); brandGoogleNode.AddChild(conditionNew); brandGoogleNode.AddChild(conditionUsed); brandGoogleNode.AddChild(conditionOther); Assert.True(brandGoogleNode.HasChild(conditionNew), "HasChild should return true for existing child dimension."); Assert.AreSame(brandGoogleNode, brandGoogleNode.GetChild(conditionNew).Parent, "parent->GetChild->getParent should return parent."); Assert.True(brandGoogleNode.HasChild(conditionUsed), "HasChild should return true for existing child dimension."); Assert.AreSame(brandGoogleNode, brandGoogleNode.GetChild(conditionUsed).Parent, "parent->GetChild->getParent should return parent."); Assert.True(brandGoogleNode.HasChild(conditionOther), "HasChild should return true for existing child dimension."); Assert.AreSame(brandGoogleNode, brandGoogleNode.GetChild(conditionOther).Parent, "parent->GetChild->getParent should return parent."); // Build up the brand = null (other) node under the root. ProductPartitionNode brandOtherNode = rootNode.AddChild(brandOther).AsSubdivision(); brandOtherNode.AddChild(conditionNew); Assert.True(brandOtherNode.HasChild(conditionNew), "HasChild should return true for existing child dimension."); Assert.AreSame(brandOtherNode, brandOtherNode.GetChild(conditionNew).Parent, "parent->GetChild->getParent should return parent."); Assert.False(brandOtherNode.HasChild(conditionUsed), "HasChild should return false for nonexistent child dimension."); Assert.False(brandOtherNode.HasChild(conditionOther), "HasChild should return false for nonexistent child dimension."); brandOtherNode.AddChild(conditionOther); Assert.True(brandOtherNode.HasChild(conditionOther), "HasChild should return true for existing child dimension."); Assert.AreSame(brandOtherNode, brandOtherNode.GetChild(conditionOther).Parent, "parent->GetChild->getParent should return parent."); // Remove one of the children of brand = null. brandOtherNode.RemoveChild(conditionOther); Assert.False(brandOtherNode.HasChild(conditionOther), "HasChild should return false for a removed child dimension."); // Remove the rest of the children of brand = null. brandOtherNode.RemoveAllChildren(); Assert.False(brandOtherNode.HasChild(conditionNew), "HasChild should return false for any removed child dimension."); Assert.False(brandOtherNode.HasChild(conditionUsed), "HasChild should return false for any removed child dimension."); }
/// <summary> /// Creates a REMOVE operation for the specified node. /// </summary> /// <param name="node">The node to be removed.</param> /// <returns>An operation pair for the node and the REMOVE operation. /// </returns> private OperationPair CreateRemoveOperation(ProductPartitionNode node) { PreconditionUtilities.CheckArgument(node.ProductPartitionId >= NEW_ROOT_ID, string.Format(ShoppingMessages.NodeForRemoveCannotHaveNegativeId, node.ProductPartitionId)); AdGroupCriterionOperation removeOp = new AdGroupCriterionOperation(); removeOp.@operator = Operator.REMOVE; removeOp.operand = ProductPartitionNodeAdapter.CreateCriterionForRemove(node, adGroupId); return new OperationPair(node, removeOp); }
public void Init() { rootNode = new ProductPartitionNode(null, null, ROOT_NODE_ID); }
/// <summary> /// Constructor that initializes the temp ID generator based on the ID of /// the root node. /// </summary> /// <param name="adGroupId">the ID of the ad group.</param> /// <param name="root">The root node of the tree.</param> private ProductPartitionTree(long adGroupId, ProductPartitionNode root) { PreconditionUtilities.CheckNotNull(root, ShoppingMessages.RootNodeCannotBeNull); this.adGroupId = adGroupId; this.root = root; this.dimensionComparator = new ProductDimensionEqualityComparer(); // If this is an existing node in an ad group, then the tree should be // cloned so that we can keep track of the original state of the tree. if (this.Root.ProductPartitionId > 0) { originalRoot = this.Root.Clone(); } }
/// <summary> /// Adds to the operations list all operations required to mutate /// <paramref name="originalNode"/> to the state* of /// <paramref name="newNode"/>. /// </summary> /// <param name="originalNode">The original node.</param> /// <param name="newNode">The new node.</param> /// <param name="ops">The operations list to add to.</param> /// <param name="idGenerator">The temporary ID generator for ADD operations.</param> /// <returns>The set of child product dimensions that require further /// processing.</returns> private void AddMutateOperations(ProductPartitionNode originalNode, ProductPartitionNode newNode, List <OperationPair> ops, TemporaryIdGenerator idGenerator) { NodeDifference nodeDifference = Diff(originalNode, newNode, dimensionComparator); bool isProcessChildren; switch (nodeDifference) { case NodeDifference.NEW_NODE: ops.AddRange(CreateAddOperations(newNode, idGenerator)); // No need to further process children. The ADD operations above will include operations // for all children of newNode. isProcessChildren = false; break; case NodeDifference.REMOVED_NODE: ops.Add(CreateRemoveOperation(originalNode)); // No need to further process children. The REMOVE operation above will perform a // cascading delete of all children of newNode. isProcessChildren = false; break; case NodeDifference.PARTITION_TYPE_CHANGE: case NodeDifference.EXCLUDED_UNIT_CHANGE: ops.Add(CreateRemoveOperation(originalNode)); ops.AddRange(CreateAddOperations(newNode, idGenerator)); // No need to further process children. The ADD operations above will include operations // for all children of newNode. isProcessChildren = false; break; case NodeDifference.BID_CHANGE: // Ensure that the new node has the proper ID (this may have been lost if the node // was removed and then re-added). newNode.ProductPartitionId = originalNode.ProductPartitionId; ops.Add(CreateSetBidOperation(newNode)); // Process the children of newNode. The SET operation above will only handle changes // made to newNode, not its children. isProcessChildren = true; break; case NodeDifference.NONE: // Ensure that the new node has the proper ID (this may have been lost if the node // was removed and then re-added). newNode.ProductPartitionId = originalNode.ProductPartitionId; // This node does not have changes, but its children may. isProcessChildren = true; break; default: throw new InvalidOperationException("Unrecognized difference: " + nodeDifference); } if (isProcessChildren) { // Try to match the children in new and original trees to identify the // matching dimensions. foreach (ProductPartitionNode newChild in newNode.Children) { if (originalNode.HasChild(newChild.Dimension)) { // this is probably an edit. AddMutateOperations(originalNode.GetChild(newChild.Dimension), newChild, ops, idGenerator); } else { // this is a new node. AddMutateOperations(null, newChild, ops, idGenerator); } } foreach (ProductPartitionNode originalChild in originalNode.Children) { if (newNode.HasChild(originalChild.Dimension)) { // this is probably an edit. We dealt with it before continue; } else { // this is a removed node. AddMutateOperations(originalChild, null, ops, idGenerator); } } } }