Skip to content

Commit 6dbcda7

Browse files
authored
Implement the kotlin AndFilter and OrFilter. (#45)
1 parent 782797e commit 6dbcda7

File tree

6 files changed

+339
-2
lines changed

6 files changed

+339
-2
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2022 The Cross-Media Measurement Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package org.wfanet.virtualpeople.common.fieldfilter
16+
17+
import com.google.protobuf.Descriptors
18+
import com.google.protobuf.MessageOrBuilder
19+
import org.wfanet.virtualpeople.common.FieldFilterProto
20+
21+
/**
22+
* The implementation of [FieldFilter] when op is AND in config.
23+
*
24+
* Always use [FieldFilter.create]. Users should never construct a [AndFilter] directly.
25+
*/
26+
internal class AndFilter(descriptor: Descriptors.Descriptor, config: FieldFilterProto) :
27+
FieldFilter {
28+
29+
private val subFilters: List<FieldFilter>
30+
31+
init {
32+
if (config.op != FieldFilterProto.Op.AND) {
33+
error("Op must be AND. Input FieldFilterProto: $config")
34+
}
35+
if (config.subFiltersCount == 0) {
36+
error("sub_filters must be set when op is AND. Input FieldFilterProto: $config")
37+
}
38+
subFilters = config.subFiltersList.map { FieldFilter.create(descriptor, it) }
39+
}
40+
41+
/** Returns true if all [subFilters] match */
42+
override fun matches(messageOrBuilder: MessageOrBuilder): Boolean {
43+
for (subFilter in subFilters) {
44+
if (!subFilter.matches(messageOrBuilder)) {
45+
return false
46+
}
47+
}
48+
return true
49+
}
50+
}

src/main/kotlin/org/wfanet/virtualpeople/common/fieldfilter/FieldFilter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ sealed interface FieldFilter {
4141
return when (config.op) {
4242
Op.HAS -> HasFilter(descriptor, config)
4343
Op.EQUAL -> EqualFilter(descriptor, config)
44+
Op.AND -> AndFilter(descriptor, config)
45+
Op.OR -> OrFilter(descriptor, config)
4446
Op.ANY_IN -> AnyInFilter.create(descriptor, config)
4547
Op.GT,
4648
Op.LT,
4749
Op.IN,
4850
Op.REGEXP,
49-
Op.OR,
50-
Op.AND,
5151
Op.NOT,
5252
Op.PARTIAL,
5353
Op.TRUE -> TODO("Not yet implemented")
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2022 The Cross-Media Measurement Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package org.wfanet.virtualpeople.common.fieldfilter
16+
17+
import com.google.protobuf.Descriptors
18+
import com.google.protobuf.MessageOrBuilder
19+
import org.wfanet.virtualpeople.common.FieldFilterProto
20+
21+
/**
22+
* The implementation of [FieldFilter] when op is OR in config.
23+
*
24+
* Always use [FieldFilter.create]. Users should never construct a [OrFilter] directly.
25+
*/
26+
internal class OrFilter(descriptor: Descriptors.Descriptor, config: FieldFilterProto) :
27+
FieldFilter {
28+
29+
private val subFilters: List<FieldFilter>
30+
31+
init {
32+
if (config.op != FieldFilterProto.Op.OR) {
33+
error("Op must be OR. Input FieldFilterProto: $config")
34+
}
35+
if (config.subFiltersCount == 0) {
36+
error("sub_filters must be set when op is AND. Input FieldFilterProto: $config")
37+
}
38+
subFilters = config.subFiltersList.map { FieldFilter.create(descriptor, it) }
39+
}
40+
41+
/** Returns true if any of the [subFilters] matches. */
42+
override fun matches(messageOrBuilder: MessageOrBuilder): Boolean {
43+
for (subFilter in subFilters) {
44+
if (subFilter.matches(messageOrBuilder)) {
45+
return true
46+
}
47+
}
48+
return false
49+
}
50+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2022 The Cross-Media Measurement Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package org.wfanet.virtualpeople.common.fieldfilter
16+
17+
import kotlin.test.assertFailsWith
18+
import kotlin.test.assertFalse
19+
import kotlin.test.assertTrue
20+
import org.junit.Test
21+
import org.junit.runner.RunWith
22+
import org.junit.runners.JUnit4
23+
import org.wfanet.virtualpeople.common.FieldFilterProto.Op
24+
import org.wfanet.virtualpeople.common.fieldFilterProto
25+
import org.wfanet.virtualpeople.common.test.*
26+
27+
@RunWith(JUnit4::class)
28+
class AndFilterTest {
29+
30+
@Test
31+
fun `no sub filter should fail`() {
32+
val fieldFilter = fieldFilterProto { op = Op.AND }
33+
assertFailsWith<IllegalStateException> {
34+
FieldFilter.create(TestProto.getDescriptor(), fieldFilter)
35+
}
36+
}
37+
38+
@Test
39+
fun `all subfilters match should pass`() {
40+
val fieldFilter = fieldFilterProto {
41+
op = Op.AND
42+
subFilters.add(
43+
fieldFilterProto {
44+
name = "a.b.int32_value"
45+
op = Op.EQUAL
46+
value = "1"
47+
}
48+
)
49+
subFilters.add(
50+
fieldFilterProto {
51+
name = "a.b.int64_value"
52+
op = Op.EQUAL
53+
value = "1"
54+
}
55+
)
56+
}
57+
val filter = FieldFilter.create(TestProto.getDescriptor(), fieldFilter)
58+
val testProto1 = testProto {
59+
a = testProtoA {
60+
b = testProtoB {
61+
int32Value = 1
62+
int64Value = 1
63+
}
64+
}
65+
}
66+
assertTrue(filter.matches(testProto1))
67+
assertTrue(filter.matches(testProto1.toBuilder()))
68+
}
69+
70+
@Test
71+
fun `any subfilter mismatch should fail`() {
72+
val fieldFilter = fieldFilterProto {
73+
op = Op.AND
74+
subFilters.add(
75+
fieldFilterProto {
76+
name = "a.b.int32_value"
77+
op = Op.EQUAL
78+
value = "1"
79+
}
80+
)
81+
subFilters.add(
82+
fieldFilterProto {
83+
name = "a.b.int64_value"
84+
op = Op.EQUAL
85+
value = "1"
86+
}
87+
)
88+
}
89+
val filter = FieldFilter.create(TestProto.getDescriptor(), fieldFilter)
90+
val testProto1 = testProto {
91+
a = testProtoA {
92+
b = testProtoB {
93+
int32Value = 1
94+
int64Value = 2
95+
}
96+
}
97+
}
98+
assertFalse(filter.matches(testProto1))
99+
assertFalse(filter.matches(testProto1.toBuilder()))
100+
}
101+
}

src/test/kotlin/org/wfanet/virtualpeople/common/fieldfilter/BUILD.bazel

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,33 @@ kt_jvm_test(
4444
"@wfa_common_jvm//imports/kotlin/kotlin/test",
4545
],
4646
)
47+
48+
kt_jvm_test(
49+
name = "AndFilterTest",
50+
srcs = ["AndFilterTest.kt"],
51+
test_class = "org.wfanet.virtualpeople.common.fieldfilter.AndFilterTest",
52+
deps = [
53+
"//src/main/kotlin/org/wfanet/virtualpeople/common/fieldfilter",
54+
"//src/main/proto/wfa/virtual_people/common/field_filter/test:test_kt_jvm_proto",
55+
"@wfa_common_jvm//imports/java/com/google/common/truth",
56+
"@wfa_common_jvm//imports/java/com/google/common/truth/extensions/proto",
57+
"@wfa_common_jvm//imports/java/com/google/protobuf/util",
58+
"@wfa_common_jvm//imports/java/org/junit",
59+
"@wfa_common_jvm//imports/kotlin/kotlin/test",
60+
],
61+
)
62+
63+
kt_jvm_test(
64+
name = "OrFilterTest",
65+
srcs = ["OrFilterTest.kt"],
66+
test_class = "org.wfanet.virtualpeople.common.fieldfilter.OrFilterTest",
67+
deps = [
68+
"//src/main/kotlin/org/wfanet/virtualpeople/common/fieldfilter",
69+
"//src/main/proto/wfa/virtual_people/common/field_filter/test:test_kt_jvm_proto",
70+
"@wfa_common_jvm//imports/java/com/google/common/truth",
71+
"@wfa_common_jvm//imports/java/com/google/common/truth/extensions/proto",
72+
"@wfa_common_jvm//imports/java/com/google/protobuf/util",
73+
"@wfa_common_jvm//imports/java/org/junit",
74+
"@wfa_common_jvm//imports/kotlin/kotlin/test",
75+
],
76+
)
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2022 The Cross-Media Measurement Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package org.wfanet.virtualpeople.common.fieldfilter
16+
17+
import kotlin.test.assertFailsWith
18+
import kotlin.test.assertFalse
19+
import kotlin.test.assertTrue
20+
import org.junit.Test
21+
import org.junit.runner.RunWith
22+
import org.junit.runners.JUnit4
23+
import org.wfanet.virtualpeople.common.FieldFilterProto.Op
24+
import org.wfanet.virtualpeople.common.fieldFilterProto
25+
import org.wfanet.virtualpeople.common.test.*
26+
27+
@RunWith(JUnit4::class)
28+
class OrFilterTest {
29+
30+
@Test
31+
fun `no sub filter should fail`() {
32+
val fieldFilter = fieldFilterProto { op = Op.OR }
33+
assertFailsWith<IllegalStateException> {
34+
FieldFilter.create(TestProto.getDescriptor(), fieldFilter)
35+
}
36+
}
37+
38+
@Test
39+
fun `any subfilter matches should pass`() {
40+
val fieldFilter = fieldFilterProto {
41+
op = Op.OR
42+
subFilters.add(
43+
fieldFilterProto {
44+
name = "a.b.int32_value"
45+
op = Op.EQUAL
46+
value = "1"
47+
}
48+
)
49+
subFilters.add(
50+
fieldFilterProto {
51+
name = "a.b.int64_value"
52+
op = Op.EQUAL
53+
value = "1"
54+
}
55+
)
56+
}
57+
val filter = FieldFilter.create(TestProto.getDescriptor(), fieldFilter)
58+
59+
val testProto1 = testProto {
60+
a = testProtoA {
61+
b = testProtoB {
62+
int32Value = 2
63+
int64Value = 1
64+
}
65+
}
66+
}
67+
assertTrue(filter.matches(testProto1))
68+
assertTrue(filter.matches(testProto1.toBuilder()))
69+
70+
val testProto2 = testProto { a = testProtoA { b = testProtoB { int64Value = 1 } } }
71+
assertTrue(filter.matches(testProto1))
72+
assertTrue(filter.matches(testProto1.toBuilder()))
73+
}
74+
75+
@Test
76+
fun `All subfilters mismatch should fail`() {
77+
val fieldFilter = fieldFilterProto {
78+
op = Op.OR
79+
subFilters.add(
80+
fieldFilterProto {
81+
name = "a.b.int32_value"
82+
op = Op.EQUAL
83+
value = "1"
84+
}
85+
)
86+
subFilters.add(
87+
fieldFilterProto {
88+
name = "a.b.int64_value"
89+
op = Op.EQUAL
90+
value = "1"
91+
}
92+
)
93+
}
94+
val filter = FieldFilter.create(TestProto.getDescriptor(), fieldFilter)
95+
val testProto1 = testProto {
96+
a = testProtoA {
97+
b = testProtoB {
98+
int32Value = 2
99+
int64Value = 2
100+
}
101+
}
102+
}
103+
assertFalse(filter.matches(testProto1))
104+
assertFalse(filter.matches(testProto1.toBuilder()))
105+
}
106+
}

0 commit comments

Comments
 (0)