Skip to content

Commit f4549dd

Browse files
committed
do not pollute heap by string comparing
1 parent f4572e7 commit f4549dd

File tree

6 files changed

+226
-118
lines changed

6 files changed

+226
-118
lines changed

src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import java.util.function.Predicate;
2424

2525
import static java.util.Objects.requireNonNull;
26-
import static org.spacious_team.table_wrapper.api.ReportPageHelper.getCellStringValueIgnoreCasePrefixPredicate;
26+
import static org.spacious_team.table_wrapper.api.StringPrefixPredicate.ignoreCaseStringPrefixPredicateOnObject;
2727

2828
@SuppressWarnings({"unused", "UnusedReturnValue"})
2929
public interface ReportPage {
@@ -157,7 +157,7 @@ default TableCellAddress findByPrefix(String prefix, int startRow, int endRow) {
157157
default TableCellAddress findByPrefix(@Nullable String prefix, int startRow, int endRow, int startColumn, int endColumn) {
158158
return (prefix == null || prefix.isEmpty()) ?
159159
TableCellAddress.NOT_FOUND :
160-
find(startRow, endRow, startColumn, endColumn, getCellStringValueIgnoreCasePrefixPredicate(prefix));
160+
find(startRow, endRow, startColumn, endColumn, ignoreCaseStringPrefixPredicateOnObject(prefix));
161161
}
162162

163163
/**
@@ -208,9 +208,9 @@ default TableCellRange getTableCellRange(@Nullable String firstRowPrefix,
208208
return TableCellRange.EMPTY_RANGE;
209209
}
210210
return getTableCellRange(
211-
getCellStringValueIgnoreCasePrefixPredicate(firstRowPrefix),
211+
ignoreCaseStringPrefixPredicateOnObject(firstRowPrefix),
212212
headersRowCount,
213-
getCellStringValueIgnoreCasePrefixPredicate(lastRowPrefix));
213+
ignoreCaseStringPrefixPredicateOnObject(lastRowPrefix));
214214
}
215215

216216
/**
@@ -250,7 +250,7 @@ default TableCellRange getTableCellRange(@Nullable String firstRowPrefix, int he
250250
return TableCellRange.EMPTY_RANGE;
251251
}
252252
return getTableCellRange(
253-
getCellStringValueIgnoreCasePrefixPredicate(firstRowPrefix),
253+
ignoreCaseStringPrefixPredicateOnObject(firstRowPrefix),
254254
headersRowCount);
255255
}
256256

src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java

Lines changed: 0 additions & 52 deletions
This file was deleted.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Table Wrapper API
3+
* Copyright (C) 2022 Spacious Team <spacious-team@ya.ru>
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as
7+
* published by the Free Software Foundation, either version 3 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
package org.spacious_team.table_wrapper.api;
20+
21+
import lombok.EqualsAndHashCode;
22+
import lombok.NoArgsConstructor;
23+
import lombok.RequiredArgsConstructor;
24+
import lombok.ToString;
25+
import org.checkerframework.checker.nullness.qual.Nullable;
26+
27+
import java.util.function.Predicate;
28+
29+
import static java.lang.Character.isWhitespace;
30+
import static lombok.AccessLevel.PRIVATE;
31+
32+
@NoArgsConstructor(access = PRIVATE)
33+
public final class StringPrefixPredicate {
34+
35+
public static Predicate<@Nullable Object> ignoreCaseStringPrefixPredicateOnObject(CharSequence prefix) {
36+
Predicate<CharSequence> predicate = ignoreCaseStringPrefixPredicate(prefix);
37+
return PredicateOnObjectWrapper.of(predicate);
38+
}
39+
40+
public static Predicate<CharSequence> ignoreCaseStringPrefixPredicate(CharSequence prefix) {
41+
return new IgnoreCaseStringPrefixPredicate(prefix);
42+
}
43+
44+
45+
@ToString
46+
@EqualsAndHashCode
47+
@RequiredArgsConstructor(staticName = "of", access = PRIVATE)
48+
static final class PredicateOnObjectWrapper implements Predicate<@Nullable Object> {
49+
private final Predicate<CharSequence> predicate;
50+
51+
@Override
52+
public boolean test(@Nullable Object o) {
53+
return (o instanceof CharSequence) && predicate.test((CharSequence) o);
54+
}
55+
}
56+
57+
58+
@ToString
59+
@EqualsAndHashCode
60+
static final class IgnoreCaseStringPrefixPredicate implements Predicate<CharSequence> {
61+
private final String prefix;
62+
63+
private IgnoreCaseStringPrefixPredicate(CharSequence prefix) {
64+
this.prefix = prefix.toString().strip();
65+
}
66+
67+
@Override
68+
public boolean test(CharSequence cs) {
69+
int nonWhitespaceIndex = getIndexOfNonWhitespace(cs);
70+
if (nonWhitespaceIndex == -1) {
71+
return false;
72+
}
73+
String string = cs.toString();
74+
return string.regionMatches(true, nonWhitespaceIndex, prefix, 0, prefix.length());
75+
}
76+
77+
private static int getIndexOfNonWhitespace(CharSequence cs) {
78+
for (int i = 0, n = cs.length(); i < n; i++) {
79+
char c = cs.charAt(i);
80+
if (!isWhitespace(c)) {
81+
return i;
82+
}
83+
}
84+
return -1;
85+
}
86+
}
87+
}

src/test/java/org/spacious_team/table_wrapper/api/ReportPageHelperTest.java

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import static org.junit.jupiter.api.Assertions.*;
3535
import static org.mockito.ArgumentMatchers.eq;
3636
import static org.mockito.Mockito.*;
37-
import static org.spacious_team.table_wrapper.api.ReportPageHelper.getCellStringValueIgnoreCasePrefixPredicate;
37+
import static org.spacious_team.table_wrapper.api.StringPrefixPredicate.ignoreCaseStringPrefixPredicateOnObject;
3838
import static org.spacious_team.table_wrapper.api.ReportPageRowHelper.cell;
3939
import static org.spacious_team.table_wrapper.api.ReportPageRowHelper.getRow;
4040
import static org.spacious_team.table_wrapper.api.TableCellAddress.NOT_FOUND;
@@ -50,8 +50,8 @@ class ReportPageTest {
5050
TableCellAddress address2 = TableCellAddress.of(3, 4);
5151
String prefix1 = "A";
5252
String prefix2 = "B";
53-
Predicate<Object> predicate1 = getCellStringValueIgnoreCasePrefixPredicate(prefix1);
54-
Predicate<Object> predicate2 = getCellStringValueIgnoreCasePrefixPredicate(prefix2);
53+
Predicate<Object> predicate1 = ignoreCaseStringPrefixPredicateOnObject(prefix1);
54+
Predicate<Object> predicate2 = ignoreCaseStringPrefixPredicateOnObject(prefix2);
5555
String tableName = "table name";
5656
String headerRow = "header row";
5757
String tableFooterString = "footer";
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Table Wrapper API
3+
* Copyright (C) 2022 Spacious Team <spacious-team@ya.ru>
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as
7+
* published by the Free Software Foundation, either version 3 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
package org.spacious_team.table_wrapper.api;
20+
21+
import nl.jqno.equalsverifier.EqualsVerifier;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.params.ParameterizedTest;
24+
import org.junit.jupiter.params.provider.MethodSource;
25+
import org.spacious_team.table_wrapper.api.StringPrefixPredicate.IgnoreCaseStringPrefixPredicate;
26+
import org.spacious_team.table_wrapper.api.StringPrefixPredicate.PredicateOnObjectWrapper;
27+
28+
import static org.junit.jupiter.api.Assertions.*;
29+
import static org.spacious_team.table_wrapper.api.StringPrefixPredicate.ignoreCaseStringPrefixPredicate;
30+
import static org.spacious_team.table_wrapper.api.StringPrefixPredicate.ignoreCaseStringPrefixPredicateOnObject;
31+
32+
class StringPrefixPredicateTest {
33+
34+
static Object[][] prefixAndMatchingString() {
35+
return new Object[][]{
36+
{"First", "First second"},
37+
{"First", "first second"},
38+
{"first", "First second"},
39+
{"first", "first second"},
40+
{"FIRST", "first second"},
41+
{" FIRST", "first second"},
42+
{" FIRST", " first second"},
43+
{"\tfirst\n", "\n first \tsecond"},
44+
{"first", "\t \nfirst\n\tsecond"},
45+
{"\nfirst\n", "\t \n\rfirst\r\n\tsecond"},
46+
};
47+
}
48+
49+
static Object[][] prefixAndNotMatchingSting() {
50+
return new Object[][]{
51+
{"First", "One two"},
52+
{"first", "fir st two"},
53+
{"first", "zero first"},
54+
{"first", "zero first second"},
55+
};
56+
}
57+
58+
static Object[][] prefixAndObject() {
59+
return new Object[][]{
60+
{"First", 1},
61+
{"First", 1.1},
62+
{"First", new Object()},
63+
};
64+
}
65+
66+
// ignoreCaseStringPrefixPredicateOnObject(CharSequence) tests
67+
68+
@ParameterizedTest
69+
@MethodSource("prefixAndMatchingString")
70+
void ignoreCaseStringPrefixPredicateOnObject_matched(String prefix, String testingString) {
71+
assertTrue(ignoreCaseStringPrefixPredicateOnObject(prefix).test(testingString));
72+
}
73+
74+
@ParameterizedTest
75+
@MethodSource("prefixAndNotMatchingSting")
76+
void ignoreCaseStringPrefixPredicateOnObject_notMatchedString(String prefix, String testingString) {
77+
assertFalse(ignoreCaseStringPrefixPredicateOnObject(prefix).test(testingString));
78+
}
79+
80+
@ParameterizedTest
81+
@MethodSource("prefixAndObject")
82+
void ignoreCaseStringPrefixPredicateOnObject_object(String prefix, Object testingObject) {
83+
assertFalse(ignoreCaseStringPrefixPredicateOnObject(prefix).test(testingObject));
84+
}
85+
86+
@Test
87+
void ignoreCaseStringPrefixPredicateOnObject_null() {
88+
assertFalse(ignoreCaseStringPrefixPredicateOnObject("any").test(null));
89+
}
90+
91+
92+
// ignoreCaseStringPrefixPredicate(CharSequence) tests
93+
94+
@ParameterizedTest
95+
@MethodSource("prefixAndMatchingString")
96+
void ignoreCaseStringPrefixPredicate_matched(String prefix, String testingString) {
97+
assertTrue(ignoreCaseStringPrefixPredicate(prefix).test(testingString));
98+
}
99+
100+
@ParameterizedTest
101+
@MethodSource("prefixAndNotMatchingSting")
102+
void ignoreCaseStringPrefixPredicate_notMatchedString(String prefix, String testingString) {
103+
assertFalse(ignoreCaseStringPrefixPredicate(prefix).test(testingString));
104+
}
105+
106+
@Test
107+
@SuppressWarnings("DataFlowIssue")
108+
void ignoreCaseStringPrefixPredicate_null() {
109+
assertThrows(NullPointerException.class, () -> ignoreCaseStringPrefixPredicate("any").test(null));
110+
}
111+
112+
@Test
113+
void testEqualsAndHashCode() {
114+
EqualsVerifier
115+
.forClass(IgnoreCaseStringPrefixPredicate.class)
116+
.verify();
117+
EqualsVerifier
118+
.forClass(PredicateOnObjectWrapper.class)
119+
.verify();
120+
}
121+
122+
@Test
123+
void testToString() {
124+
assertEquals(
125+
"StringPrefixPredicate.PredicateOnObjectWrapper(predicate=StringPrefixPredicate.IgnoreCaseStringPrefixPredicate(prefix=First))",
126+
ignoreCaseStringPrefixPredicateOnObject("First").toString());
127+
assertEquals(
128+
"StringPrefixPredicate.IgnoreCaseStringPrefixPredicate(prefix=First)",
129+
ignoreCaseStringPrefixPredicate("First").toString());
130+
}
131+
}

0 commit comments

Comments
 (0)