Skip to content

Commit 5e4b971

Browse files
committed
cpu and memory optimizations
1 parent fcd6061 commit 5e4b971

File tree

74 files changed

+684
-448
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+684
-448
lines changed
Lines changed: 33 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package com.iab.gpp.encoder.base64;
22

3-
import java.util.Map;
43
import java.util.regex.Pattern;
5-
import java.util.stream.Collectors;
6-
import java.util.stream.Stream;
4+
5+
import com.iab.gpp.encoder.bitstring.BitString;
6+
import com.iab.gpp.encoder.bitstring.BitStringBuilder;
77
import com.iab.gpp.encoder.datatype.encoder.FixedIntegerEncoder;
88
import com.iab.gpp.encoder.error.DecodingException;
99
import com.iab.gpp.encoder.error.EncodingException;
@@ -12,68 +12,55 @@ public abstract class AbstractBase64UrlEncoder {
1212

1313
abstract protected String pad(String bitString);
1414

15+
private static final int BASE64_BITS = 6;
1516
/**
1617
* Base 64 URL character set. Different from standard Base64 char set in that '+' and '/' are
1718
* replaced with '-' and '_'.
1819
*/
19-
private static String DICT = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
20-
// prettier-ignore
21-
private static Map<Character, Integer> REVERSE_DICT = Stream
22-
.of(new Object[][] {{'A', 0}, {'B', 1}, {'C', 2}, {'D', 3}, {'E', 4}, {'F', 5}, {'G', 6}, {'H', 7}, {'I', 8},
23-
{'J', 9}, {'K', 10}, {'L', 11}, {'M', 12}, {'N', 13}, {'O', 14}, {'P', 15}, {'Q', 16}, {'R', 17}, {'S', 18},
24-
{'T', 19}, {'U', 20}, {'V', 21}, {'W', 22}, {'X', 23}, {'Y', 24}, {'Z', 25}, {'a', 26}, {'b', 27}, {'c', 28},
25-
{'d', 29}, {'e', 30}, {'f', 31}, {'g', 32}, {'h', 33}, {'i', 34}, {'j', 35}, {'k', 36}, {'l', 37}, {'m', 38},
26-
{'n', 39}, {'o', 40}, {'p', 41}, {'q', 42}, {'r', 43}, {'s', 44}, {'t', 45}, {'u', 46}, {'v', 47}, {'w', 48},
27-
{'x', 49}, {'y', 50}, {'z', 51}, {'0', 52}, {'1', 53}, {'2', 54}, {'3', 55}, {'4', 56}, {'5', 57}, {'6', 58},
28-
{'7', 59}, {'8', 60}, {'9', 61}, {'-', 62}, {'_', 63}})
29-
.collect(Collectors.toMap(data -> (Character) data[0], data -> (Integer) data[1]));
30-
31-
private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
32-
private static Pattern BASE64URL_VERIFICATION_PATTERN =
33-
Pattern.compile("^[A-Za-z0-9\\-_]*$", Pattern.CASE_INSENSITIVE);
34-
35-
public String encode(String bitString) {
36-
// should only be 0 or 1
37-
if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches()) {
38-
throw new EncodingException("Unencodable Base64Url '" + bitString + "'");
20+
private static final String DICT = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
21+
private static final int REVERSE_DICT_SIZE = 128;
22+
private static final BitString[] REVERSE_DICT = new BitString[REVERSE_DICT_SIZE];
23+
static {
24+
for (int i = 0; i < DICT.length(); i++) {
25+
REVERSE_DICT[DICT.charAt(i)] = BitString.of(FixedIntegerEncoder.encode(i, 6));
3926
}
27+
}
4028

29+
public String encode(String bitString) {
4130
bitString = pad(bitString);
4231

43-
String str = "";
32+
int length = bitString.length();
33+
StringBuilder str = new StringBuilder(length / BASE64_BITS);
4434

4535
int index = 0;
46-
while (index <= bitString.length() - 6) {
47-
String s = bitString.substring(index, index + 6);
48-
36+
while (index <= length - BASE64_BITS) {
4937
try {
50-
int n = FixedIntegerEncoder.decode(s);
51-
Character c = AbstractBase64UrlEncoder.DICT.charAt(n);
52-
str += c;
53-
index += 6;
38+
int n = FixedIntegerEncoder.decode(bitString, index, BASE64_BITS);
39+
str.append(DICT.charAt(n));
40+
index += BASE64_BITS;
5441
} catch (DecodingException e) {
5542
throw new EncodingException("Unencodable Base64Url '" + bitString + "'");
5643
}
5744
}
5845

59-
return str;
46+
return str.toString();
6047
}
6148

62-
public String decode(String str) {
63-
// should contain only characters from the base64url set
64-
if (!BASE64URL_VERIFICATION_PATTERN.matcher(str).matches()) {
65-
throw new DecodingException("Undecodable Base64URL string");
66-
}
67-
68-
String bitString = "";
69-
70-
for (int i = 0; i < str.length(); i++) {
49+
public BitString decode(String str) {
50+
int length = str.length();
51+
BitStringBuilder sb = new BitStringBuilder(length * BASE64_BITS);
52+
for (int i = 0; i < length; i++) {
7153
char c = str.charAt(i);
72-
Integer n = AbstractBase64UrlEncoder.REVERSE_DICT.get(c);
73-
String s = FixedIntegerEncoder.encode(n, 6);
74-
bitString += s;
75-
}
54+
BitString n = null;
55+
if (c < REVERSE_DICT_SIZE) {
56+
n = REVERSE_DICT[c];
57+
}
58+
if (n == null) {
59+
throw new DecodingException("Undecodable Base64URL string");
60+
}
61+
sb.append(n);
7662

77-
return bitString;
63+
}
64+
return sb.build();
7865
}
7966
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package com.iab.gpp.encoder.bitstring;
2+
3+
import java.util.AbstractList;
4+
import java.util.BitSet;
5+
6+
import com.iab.gpp.encoder.error.DecodingException;
7+
8+
public final class BitString extends AbstractList<Boolean> {
9+
public static final char TRUE = '1';
10+
public static final char FALSE = '0';
11+
public static final String TRUE_STRING = new String(new char[] {TRUE});
12+
public static final String FALSE_STRING = new String(new char[] {FALSE});
13+
14+
private final BitSet bitSet;
15+
private final int from;
16+
private final int to;
17+
18+
BitString(BitSet bitSet, int from, int to) {
19+
this.bitSet = bitSet;
20+
this.from = from;
21+
this.to = to;
22+
}
23+
24+
public static final BitString of(String str) {
25+
int length = str.length();
26+
BitStringBuilder builder = new BitStringBuilder(length);
27+
for (int i = 0; i < length; i++) {
28+
char c = str.charAt(i);
29+
if (c == TRUE) {
30+
builder.append(true);
31+
} else if (c == FALSE) {
32+
builder.append(false);
33+
} else {
34+
throw new DecodingException("Invalid bit string");
35+
}
36+
}
37+
return builder.build();
38+
}
39+
40+
public String toString() {
41+
StringBuilder sb = new StringBuilder(length());
42+
for (int i = from; i < to; i++) {
43+
sb.append(bitSet.get(i) ? TRUE : FALSE);
44+
}
45+
return sb.toString();
46+
}
47+
48+
/**
49+
* This is the fast getter without boxing
50+
* @param i index
51+
* @return the value at that index
52+
*/
53+
public boolean getValue(int i) {
54+
return bitSet.get(from + i);
55+
}
56+
57+
@Override
58+
public Boolean get(int i) {
59+
return getValue(i);
60+
}
61+
62+
@Override
63+
public Boolean set(int index, Boolean element) {
64+
Boolean old = get(index);
65+
bitSet.set(from + index, element);
66+
return old;
67+
}
68+
69+
public int length() {
70+
return to - from;
71+
}
72+
73+
@Override
74+
public int size() {
75+
return length();
76+
}
77+
78+
public BitString substring(int i) {
79+
return substring(i, length());
80+
}
81+
82+
public BitString substring(int newFrom, int newTo) {
83+
int length = length();
84+
if (newFrom > newTo || newFrom < 0 || newFrom > length || newTo > length) {
85+
throw new IllegalArgumentException("Invalid substring");
86+
}
87+
int oldFrom = this.from;
88+
return new BitString(bitSet, oldFrom + newFrom, oldFrom + newTo);
89+
}
90+
91+
public int indexOf(String string) {
92+
return indexOf(string, 0);
93+
}
94+
95+
public int indexOf(String string, int startIndex) {
96+
int stringLength = string.length();
97+
for (int i = startIndex, to = this.to; i < to; i++) {
98+
int match = 0;
99+
for (int j = 0; j < stringLength; j++) {
100+
if ((string.charAt(j) == TRUE) == bitSet.get(from + i + j)) {
101+
match++;
102+
}
103+
}
104+
if (match == stringLength) {
105+
return i;
106+
}
107+
}
108+
return -1;
109+
}
110+
111+
public boolean isEmpty() {
112+
return length() == 0;
113+
}
114+
115+
public BitString expandTo(int target) {
116+
int needed = target - length();
117+
if (needed == 0) {
118+
return this;
119+
}
120+
if (needed < 0) {
121+
return substring(0, target);
122+
}
123+
BitStringBuilder sb = new BitStringBuilder(target);
124+
sb.append(this);
125+
for (int i = 0; i < needed; i++) {
126+
sb.append(false);
127+
}
128+
return sb.build();
129+
}
130+
131+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.iab.gpp.encoder.bitstring;
2+
3+
import java.util.BitSet;
4+
5+
public final class BitStringBuilder {
6+
private final BitSet bitSet;
7+
private int length;
8+
9+
public BitStringBuilder(int initialCapacity) {
10+
this.bitSet = new BitSet(initialCapacity);
11+
}
12+
13+
public BitStringBuilder() {
14+
this.bitSet = new BitSet();
15+
}
16+
17+
public BitString build() {
18+
return new BitString(bitSet, 0, length);
19+
}
20+
21+
public void append(boolean value) {
22+
int idx = length++;
23+
if (value) {
24+
bitSet.set(idx);
25+
}
26+
}
27+
28+
public void append(BitString other) {
29+
int length = other.length();
30+
for (int i = 0; i < length; i++) {
31+
append(other.getValue(i));
32+
}
33+
}
34+
}

iabgpp-encoder/src/main/java/com/iab/gpp/encoder/bitstring/BitStringEncoder.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,28 @@ public static BitStringEncoder getInstance() {
2020
}
2121

2222
public String encode(EncodableBitStringFields fields, List<String> fieldNames) {
23-
String bitString = "";
23+
StringBuilder bitString = new StringBuilder();
2424
for (int i = 0; i < fieldNames.size(); i++) {
2525
String fieldName = fieldNames.get(i);
2626
if (fields.containsKey(fieldName)) {
2727
AbstractEncodableBitStringDataType<?> field = fields.get(fieldName);
28-
bitString += field.encode();
28+
bitString.append(field.encode());
2929
} else {
3030
throw new EncodingException("Field not found: '" + fieldName + "'");
3131
}
3232
}
3333

34-
return bitString;
34+
return bitString.toString();
3535
}
3636

37-
public void decode(String bitString, List<String> fieldNames, EncodableBitStringFields fields) {
37+
public void decode(BitString bitString, List<String> fieldNames, EncodableBitStringFields fields) {
3838
int index = 0;
3939
for (int i = 0; i < fieldNames.size(); i++) {
4040
String fieldName = fieldNames.get(i);
4141
if (fields.containsKey(fieldName)) {
4242
AbstractEncodableBitStringDataType<?> field = fields.get(fieldName);
4343
try {
44-
String substring = field.substring(bitString, index);
44+
BitString substring = field.substring(bitString, index);
4545
field.decode(substring);
4646
index += substring.length();
4747
} catch (SubstringException e) {

iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/AbstractEncodableBitStringDataType.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.util.Collection;
44
import java.util.function.Predicate;
55
import java.util.stream.Collectors;
6+
7+
import com.iab.gpp.encoder.bitstring.BitString;
68
import com.iab.gpp.encoder.error.ValidationException;
79

810
public abstract class AbstractEncodableBitStringDataType<T> implements EncodableDataType<T> {
@@ -50,8 +52,8 @@ public boolean getHardFailIfMissing() {
5052

5153
public abstract String encode();
5254

53-
public abstract void decode(String bitString);
55+
public abstract void decode(BitString bitString);
5456

55-
public abstract String substring(String bitString, int fromIndex) throws SubstringException;
57+
public abstract BitString substring(BitString bitString, int fromIndex) throws SubstringException;
5658

5759
}

0 commit comments

Comments
 (0)