View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.geometry.core.partitioning.bsp;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.List;
22  import java.util.function.Consumer;
23  import java.util.function.Supplier;
24  
25  import org.apache.commons.geometry.core.RegionLocation;
26  import org.apache.commons.geometry.core.partitioning.test.PartitionTestUtils;
27  import org.apache.commons.geometry.core.partitioning.test.TestPoint2D;
28  import org.apache.commons.geometry.core.partitioning.test.TestRegionBSPTree;
29  import org.junit.jupiter.api.Assertions;
30  
31  /** Helper class with a fluent API used to construct assert conditions on tree merge operations.
32   */
33  class MergeChecker {
34  
35      /** Helper interface used when testing tree merge operations.
36       */
37      @FunctionalInterface
38      public interface Operation {
39          TestRegionBSPTree apply(TestRegionBSPTree tree1, TestRegionBSPTree tree2);
40      }
41  
42      /** First tree in the merge operation */
43      private final Supplier<TestRegionBSPTree> tree1Factory;
44  
45      /** Second tree in the merge operation */
46      private final Supplier<TestRegionBSPTree> tree2Factory;
47  
48      /** Merge operation that does not modify either input tree */
49      private final Operation constOperation;
50  
51      /** Merge operation that stores the result in the first input tree
52       * and leaves the second one unmodified.
53       */
54      private final Operation inPlaceOperation;
55  
56      /** The expected node count of the merged tree */
57      private int expectedCount = -1;
58  
59      /** The expected full state of the merged tree */
60      private boolean expectedFull;
61  
62      /** The expected empty state of the merged tree */
63      private boolean expectedEmpty;
64  
65      /** Points expected to lie in the inside of the region */
66      private final List<TestPoint2D> insidePoints = new ArrayList<>();
67  
68      /** Points expected to lie on the outside of the region */
69      private final List<TestPoint2D> outsidePoints = new ArrayList<>();
70  
71      /** Points expected to lie on the  boundary of the region */
72      private final List<TestPoint2D> boundaryPoints = new ArrayList<>();
73  
74      /** Construct a new instance that will verify the output of performing the given merge operation
75       * on the input trees.
76       * @param tree1Factory first tree factory in the merge operation
77       * @param tree2Factory second tree factory in the merge operation
78       * @param constOperation object that performs the merge operation in a form that
79       *      leaves both argument unmodified
80       * @param inPlaceOperation object that performs the merge operation in a form
81       *      that stores the result in the first input tree and leaves the second
82       *      input unchanged.
83       */
84      MergeChecker(
85              final Supplier<TestRegionBSPTree> tree1Factory,
86              final Supplier<TestRegionBSPTree> tree2Factory,
87              final Operation constOperation,
88              final Operation inPlaceOperation) {
89  
90          this.tree1Factory = tree1Factory;
91          this.tree2Factory = tree2Factory;
92          this.constOperation = constOperation;
93          this.inPlaceOperation = inPlaceOperation;
94      }
95  
96      /** Set the expected node count of the merged tree
97       * @param expectedCountVal the expected node count of the merged tree
98       * @return this instance
99       */
100     public MergeChecker count(final int expectedCountVal) {
101         this.expectedCount = expectedCountVal;
102         return this;
103     }
104 
105     /** Set the expected full state of the merged tree.
106      * @param expectedFullVal the expected full state of the merged tree.
107      * @return this instance
108      */
109     public MergeChecker full(final boolean expectedFullVal) {
110         this.expectedFull = expectedFullVal;
111         return this;
112     }
113 
114     /** Set the expected empty state of the merged tree.
115      * @param expectedEmptyVal the expected empty state of the merged tree.
116      * @return this instance
117      */
118     public MergeChecker empty(final boolean expectedEmptyVal) {
119         this.expectedEmpty = expectedEmptyVal;
120         return this;
121     }
122 
123     /** Add points expected to be on the inside of the merged region.
124      * @param points point expected to be on the inside of the merged
125      *      region
126      * @return this instance
127      */
128     public MergeChecker inside(final TestPoint2D... points) {
129         insidePoints.addAll(Arrays.asList(points));
130         return this;
131     }
132 
133     /** Add points expected to be on the outside of the merged region.
134      * @param points point expected to be on the outside of the merged
135      *      region
136      * @return this instance
137      */
138     public MergeChecker outside(final TestPoint2D... points) {
139         outsidePoints.addAll(Arrays.asList(points));
140         return this;
141     }
142 
143     /** Add points expected to be on the boundary of the merged region.
144      * @param points point expected to be on the boundary of the merged
145      *      region
146      * @return this instance
147      */
148     public MergeChecker boundary(final TestPoint2D... points) {
149         boundaryPoints.addAll(Arrays.asList(points));
150         return this;
151     }
152 
153     /** Perform the merge operation and verify the output.
154      */
155     public void check() {
156         check(null);
157     }
158 
159     /** Perform the merge operation and verify the output. The given consumer
160      * is passed the merge result and can be used to perform extra assertions.
161      * @param assertions consumer that will be passed the merge result; may
162      *      be null
163      */
164     public void check(final Consumer<TestRegionBSPTree> assertions) {
165         checkConst(assertions);
166         checkInPlace(assertions);
167     }
168 
169     private void checkConst(final Consumer<TestRegionBSPTree> assertions) {
170         checkInternal(false, constOperation, assertions);
171     }
172 
173     private void checkInPlace(final Consumer<TestRegionBSPTree> assertions) {
174         checkInternal(true, inPlaceOperation, assertions);
175     }
176 
177     private void checkInternal(final boolean inPlace, final Operation operation,
178             final Consumer<? super TestRegionBSPTree> assertions) {
179 
180         final TestRegionBSPTree tree1 = tree1Factory.get();
181         final TestRegionBSPTree tree2 = tree2Factory.get();
182 
183         // store the number of nodes in each tree before the operation
184         final int tree1BeforeCount = tree1.count();
185         final int tree2BeforeCount = tree2.count();
186 
187         // perform the operation
188         final TestRegionBSPTree result = operation.apply(tree1, tree2);
189 
190         // verify the internal consistency of all of the involved trees
191         PartitionTestUtils.assertTreeStructure(tree1);
192         PartitionTestUtils.assertTreeStructure(tree2);
193         PartitionTestUtils.assertTreeStructure(result);
194 
195         // check full/empty status
196         Assertions.assertEquals(expectedFull, result.isFull(), "Unexpected tree 'full' property");
197         Assertions.assertEquals(expectedEmpty, result.isEmpty(), "Unexpected tree 'empty' property");
198 
199         // check the node count
200         if (expectedCount > -1) {
201             Assertions.assertEquals(expectedCount, result.count(), "Unexpected node count");
202         }
203 
204         // check in place or not
205         if (inPlace) {
206             Assertions.assertSame(tree1, result, "Expected merge operation to be in place");
207         } else {
208             Assertions.assertNotSame(tree1, result, "Expected merge operation to return a new instance");
209 
210             // make sure that tree1 wasn't modified
211             Assertions.assertEquals(tree1BeforeCount, tree1.count(), "Tree 1 node count should not have changed");
212         }
213 
214         // make sure that tree2 wasn't modified
215         Assertions.assertEquals(tree2BeforeCount, tree2.count(), "Tree 2 node count should not have changed");
216 
217         // check region point locations
218         PartitionTestUtils.assertPointLocations(result, RegionLocation.INSIDE, insidePoints);
219         PartitionTestUtils.assertPointLocations(result, RegionLocation.OUTSIDE, outsidePoints);
220         PartitionTestUtils.assertPointLocations(result, RegionLocation.BOUNDARY, boundaryPoints);
221 
222         // pass the result to the given function for any additional assertions
223         if (assertions != null) {
224             assertions.accept(result);
225         }
226     }
227 }