Skip to content

Commit 2885f39

Browse files
Add null checks and improve documentation for DownloadFilter
1 parent 3305e58 commit 2885f39

File tree

2 files changed

+107
-105
lines changed

2 files changed

+107
-105
lines changed

services-custom/s3-transfer-manager/src/main/java/software/amazon/awssdk/transfer/s3/config/DownloadFilter.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
package software.amazon.awssdk.transfer.s3.config;
1717

18+
import java.util.Objects;
1819
import java.util.function.Predicate;
1920
import software.amazon.awssdk.annotations.SdkPublicApi;
2021
import software.amazon.awssdk.services.s3.model.S3Object;
@@ -38,24 +39,37 @@ public interface DownloadFilter extends Predicate<S3Object> {
3839
@Override
3940
boolean test(S3Object s3Object);
4041

41-
/**
42-
* Returns a composed filter that performs AND operation
42+
/*
43+
* Returns a composed filter that performs AND operation
44+
*
45+
* @param other the predicate to AND with this predicate
46+
* @return a composed predicate that performs OR operation
47+
* @throws NullPointerException if other is null
4348
*/
4449
@Override
4550
default DownloadFilter and(Predicate<? super S3Object> other) {
51+
Objects.requireNonNull(other, "Other predicate cannot be null");
4652
return s3Object -> test(s3Object) && other.test(s3Object);
4753
}
4854

49-
/**
55+
/*
5056
* Returns a composed filter that performs OR operation
57+
*
58+
* @param other the predicate to OR with this predicate
59+
* @return a composed predicate that performs OR operation
60+
* @throws NullPointerException if other is null
5161
*/
5262
@Override
5363
default DownloadFilter or(Predicate<? super S3Object> other) {
64+
Objects.requireNonNull(other, "Other predicate cannot be null");
5465
return s3Object -> test(s3Object) || other.test(s3Object);
5566
}
5667

57-
/**
58-
* Returns a filter that negates the result
68+
/*
69+
* Returns a predicate that represents the logical negation of this predicate.
70+
* If this predicate would download an object, the negated predicate will not download it, and vice versa.
71+
*
72+
* @return a predicate that represents the logical negation of this predicate
5973
*/
6074
@Override
6175
default DownloadFilter negate() {

services-custom/s3-transfer-manager/src/test/java/software/amazon/awssdk/transfer/s3/config/DownloadFilterTest.java

Lines changed: 88 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
1919

20+
import java.util.function.Function;
2021
import java.util.stream.Stream;
2122
import org.junit.jupiter.api.DisplayName;
2223
import org.junit.jupiter.params.ParameterizedTest;
@@ -40,159 +41,146 @@ void allObjectsFilter_shouldWork(S3Object s3Object, boolean result) {
4041
assertThat(DownloadFilter.allObjects().test(s3Object)).isEqualTo(result);
4142
}
4243

43-
private static Stream<Arguments> basicOrFilterTestCases() {
44+
private static Stream<Arguments> filterOperationTestCases() {
45+
Function<S3Object, DownloadFilter> folder1OrFolder3Filter = (s3Object) -> {
46+
DownloadFilter folder1 = obj -> obj.key().startsWith("folder1");
47+
DownloadFilter folder3 = obj -> obj.key().startsWith("folder3");
48+
return folder1.or(folder3);
49+
};
50+
51+
Function<S3Object, DownloadFilter> txtAndLargeSizeFilter = (s3Object) -> {
52+
DownloadFilter txtFilter = obj -> obj.key().endsWith(".txt");
53+
DownloadFilter sizeFilter = obj -> obj.size() > 1000L;
54+
return txtFilter.and(sizeFilter);
55+
};
56+
57+
Function<S3Object, DownloadFilter> notFolder1Filter = (s3Object) -> {
58+
DownloadFilter folder1 = obj -> obj.key().startsWith("folder1");
59+
return folder1.negate();
60+
};
61+
62+
Function<S3Object, DownloadFilter> notLargeSizeFilter = (s3Object) -> {
63+
DownloadFilter largeSize = obj -> obj.size() > 1000L;
64+
return largeSize.negate();
65+
};
66+
67+
Function<S3Object, DownloadFilter> complexFilter = (s3Object) -> {
68+
DownloadFilter folder1 = obj -> obj.key().startsWith("folder1");
69+
DownloadFilter folder3 = obj -> obj.key().startsWith("folder3");
70+
DownloadFilter sizeFilter = obj -> obj.size() > 1000L;
71+
return folder1.or(folder3).and(sizeFilter);
72+
};
73+
4474
return Stream.of(
75+
// OR operation tests
4576
Arguments.of(
46-
"File in folder1 - should match",
77+
"OR: folder1/test.txt matches (folder1 OR folder3)",
4778
S3Object.builder().key("folder1/test.txt").size(2000L).build(),
79+
folder1OrFolder3Filter,
4880
true
4981
),
5082
Arguments.of(
51-
"File in folder3 - should match",
83+
"OR: folder3/test.txt matches (folder1 OR folder3)",
5284
S3Object.builder().key("folder3/test.txt").size(2000L).build(),
85+
folder1OrFolder3Filter,
5386
true
5487
),
5588
Arguments.of(
56-
"File in folder2 - should not match",
89+
"OR: folder2/test.txt does not match (folder1 OR folder3)",
5790
S3Object.builder().key("folder2/test.txt").size(2000L).build(),
91+
folder1OrFolder3Filter,
5892
false
59-
)
60-
);
61-
}
62-
63-
@ParameterizedTest
64-
@MethodSource("basicOrFilterTestCases")
65-
@DisplayName("Test OR filter combinations")
66-
void testBasicOrFilter(String testName, S3Object s3Object, boolean result) {
67-
DownloadFilter folder1Filter = s3Obj -> s3Obj.key().startsWith("folder1");
68-
DownloadFilter folder3Filter = s3Obj -> s3Obj.key().startsWith("folder3");
69-
DownloadFilter combinedFilter = folder1Filter.or(folder3Filter);
70-
assertThat(combinedFilter.test(s3Object))
71-
.as(testName)
72-
.isEqualTo(result);
73-
}
93+
),
7494

75-
private static Stream<Arguments> basicAndFilterTestCases() {
76-
return Stream.of(
95+
// AND operation tests
7796
Arguments.of(
78-
"Large text file - should match",
97+
"AND: large .txt file matches (.txt AND size > 1000)",
7998
S3Object.builder().key("folder1/test.txt").size(2000L).build(),
99+
txtAndLargeSizeFilter,
80100
true
81101
),
82102
Arguments.of(
83-
"Small text file - should not match",
103+
"AND: small .txt file does not match (.txt AND size > 1000)",
84104
S3Object.builder().key("folder1/test.txt").size(500L).build(),
105+
txtAndLargeSizeFilter,
85106
false
86107
),
87108
Arguments.of(
88-
"Large non-text file - should not match",
109+
"AND: large .pdf file does not match (.txt AND size > 1000)",
89110
S3Object.builder().key("folder1/test.pdf").size(2000L).build(),
111+
txtAndLargeSizeFilter,
90112
false
91-
)
92-
);
93-
}
94-
95-
@ParameterizedTest
96-
@MethodSource("basicAndFilterTestCases")
97-
@DisplayName("Test AND filter combinations")
98-
void testBasicAndFilter(String testName, S3Object s3Object, boolean result) {
99-
DownloadFilter txtFilter = s3Obj -> s3Obj.key().endsWith(".txt");
100-
DownloadFilter sizeFilter = s3Obj -> s3Obj.size() > 1000L;
101-
DownloadFilter combinedFilter = txtFilter.and(sizeFilter);
102-
assertThat(combinedFilter.test(s3Object))
103-
.as(testName)
104-
.isEqualTo(result);
105-
}
113+
),
106114

107-
private static Stream<Arguments> basicNegateFilterTestCases() {
108-
return Stream.of(
115+
// NEGATE operation tests
109116
Arguments.of(
110-
"File in folder1 - should not match",
111-
"FOLDER",
117+
"NEGATE: folder1 file does not match NOT(folder1)",
112118
S3Object.builder().key("folder1/test.txt").size(1000L).build(),
119+
notFolder1Filter,
113120
false
114121
),
115122
Arguments.of(
116-
"File not in folder1 - should match",
117-
"FOLDER",
123+
"NEGATE: folder2 file matches NOT(folder1)",
118124
S3Object.builder().key("folder2/test.txt").size(1000L).build(),
125+
notFolder1Filter,
119126
true
120127
),
121128
Arguments.of(
122-
"Large file - should not match",
123-
"SIZE",
129+
"NEGATE: large file does not match NOT(size > 1000)",
124130
S3Object.builder().key("test.txt").size(2000L).build(),
131+
notLargeSizeFilter,
125132
false
126133
),
127134
Arguments.of(
128-
"Small file - should match",
129-
"SIZE",
135+
"NEGATE: small file matches NOT(size > 1000)",
130136
S3Object.builder().key("test.txt").size(500L).build(),
137+
notLargeSizeFilter,
131138
true
132-
)
133-
);
134-
}
135-
136-
@ParameterizedTest
137-
@MethodSource("basicNegateFilterTestCases")
138-
@DisplayName("Test NEGATE filter operations")
139-
void testBasicNegateFilter(String testName, String filterType, S3Object s3Object, boolean result) {
140-
DownloadFilter baseFilter;
141-
142-
switch (filterType) {
143-
case "FOLDER":
144-
baseFilter = s3Obj -> s3Obj.key().startsWith("folder1");
145-
break;
146-
case "SIZE":
147-
baseFilter = s3Obj -> s3Obj.size() > 1000L;
148-
break;
149-
default:
150-
throw new IllegalArgumentException("Unknown filter type: " + filterType);
151-
}
152-
153-
DownloadFilter negatedFilter = baseFilter.negate();
154-
assertThat(negatedFilter.test(s3Object))
155-
.as(testName)
156-
.isEqualTo(result);
157-
}
139+
),
158140

159-
private static Stream<Arguments> combinedFilterTestCases() {
160-
return Stream.of(
141+
// Complex chained operations
161142
Arguments.of(
162-
"Large file in folder1 - should match",
143+
"COMPLEX: large file in folder1 matches ((folder1 OR folder3) AND size > 1000)",
163144
S3Object.builder().key("folder1/test.txt").size(2000L).build(),
164-
true,
165-
"folder1 OR folder3 AND size > 1000"
145+
complexFilter,
146+
true
166147
),
167148
Arguments.of(
168-
"Small file in folder1 - should not match",
149+
"COMPLEX: small file in folder1 does not match ((folder1 OR folder3) AND size > 1000)",
169150
S3Object.builder().key("folder1/test.txt").size(500L).build(),
170-
false,
171-
"folder1 OR folder3 AND size > 1000"
151+
complexFilter,
152+
false
172153
),
173154
Arguments.of(
174-
"Large file in folder2 - should not match",
155+
"COMPLEX: large file in folder2 does not match ((folder1 OR folder3) AND size > 1000)",
175156
S3Object.builder().key("folder2/test.txt").size(2000L).build(),
176-
false,
177-
"folder1 OR folder3 AND size > 1000"
157+
complexFilter,
158+
false
159+
),
160+
Arguments.of(
161+
"COMPLEX: large file in folder3 matches ((folder1 OR folder3) AND size > 1000)",
162+
S3Object.builder().key("folder3/test.txt").size(2000L).build(),
163+
complexFilter,
164+
true
178165
)
179166
);
180167
}
181168

182169
@ParameterizedTest
183-
@MethodSource("combinedFilterTestCases")
184-
@DisplayName("Test combined filter operations")
185-
void testCombinedFilters(String testName, S3Object s3Object, boolean result, String description) {
186-
DownloadFilter folder1Filter = s3Obj -> s3Obj.key().startsWith("folder1");
187-
DownloadFilter folder3Filter = s3Obj -> s3Obj.key().startsWith("folder3");
188-
DownloadFilter sizeFilter = s3Obj -> s3Obj.size() > 1000L;
189-
190-
DownloadFilter chainedFilter = folder1Filter
191-
.or(folder3Filter)
192-
.and(sizeFilter);
193-
194-
assertThat(chainedFilter.test(s3Object))
195-
.as("%s: %s", testName, description)
196-
.isEqualTo(result);
170+
@MethodSource("filterOperationTestCases")
171+
@DisplayName("Test DownloadFilter operations (AND, OR, NEGATE)")
172+
void testFilterOperations(String scenario, S3Object s3Object,
173+
Function<S3Object, DownloadFilter> filterFactory,
174+
boolean expectedResult) {
175+
// Given
176+
DownloadFilter filter = filterFactory.apply(s3Object);
177+
178+
// When
179+
boolean actualResult = filter.test(s3Object);
180+
181+
// Then
182+
assertThat(actualResult)
183+
.as(scenario)
184+
.isEqualTo(expectedResult);
197185
}
198186
}

0 commit comments

Comments
 (0)