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 }