Skip to content

Commit f292268

Browse files
minborgcl4es
andcommittedSep 5, 2023
8315454: Add a way to create an immutable snapshot of a BitSet
Co-authored-by: Claes Redestad <redestad@openjdk.org> Reviewed-by: redestad
1 parent 9def453 commit f292268

File tree

3 files changed

+193
-13
lines changed

3 files changed

+193
-13
lines changed
 

‎src/java.base/share/classes/java/net/URLEncoder.java

+16-13
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
import java.nio.charset.UnsupportedCharsetException ;
3333
import java.util.BitSet;
3434
import java.util.Objects;
35+
import java.util.function.IntPredicate;
3536

37+
import jdk.internal.util.ImmutableBitSetPredicate;
3638
import jdk.internal.util.StaticProperty;
3739

3840
/**
@@ -78,7 +80,7 @@
7880
* @since 1.0
7981
*/
8082
public class URLEncoder {
81-
private static final BitSet DONT_NEED_ENCODING;
83+
private static final IntPredicate DONT_NEED_ENCODING;
8284
private static final int CASE_DIFF = ('a' - 'A');
8385
private static final String DEFAULT_ENCODING_NAME;
8486

@@ -120,17 +122,18 @@ public class URLEncoder {
120122
*
121123
*/
122124

123-
DONT_NEED_ENCODING = new BitSet(128);
124-
125-
DONT_NEED_ENCODING.set('a', 'z' + 1);
126-
DONT_NEED_ENCODING.set('A', 'Z' + 1);
127-
DONT_NEED_ENCODING.set('0', '9' + 1);
128-
DONT_NEED_ENCODING.set(' '); /* encoding a space to a + is done
125+
var bitSet = new BitSet(128);
126+
bitSet.set('a', 'z' + 1);
127+
bitSet.set('A', 'Z' + 1);
128+
bitSet.set('0', '9' + 1);
129+
bitSet.set(' '); /* encoding a space to a + is done
129130
* in the encode() method */
130-
DONT_NEED_ENCODING.set('-');
131-
DONT_NEED_ENCODING.set('_');
132-
DONT_NEED_ENCODING.set('.');
133-
DONT_NEED_ENCODING.set('*');
131+
bitSet.set('-');
132+
bitSet.set('_');
133+
bitSet.set('.');
134+
bitSet.set('*');
135+
136+
DONT_NEED_ENCODING = ImmutableBitSetPredicate.of(bitSet);
134137

135138
DEFAULT_ENCODING_NAME = StaticProperty.fileEncoding();
136139
}
@@ -226,7 +229,7 @@ public static String encode(String s, Charset charset) {
226229
for (int i = 0; i < s.length();) {
227230
int c = s.charAt(i);
228231
//System.out.println("Examining character: " + c);
229-
if (DONT_NEED_ENCODING.get(c)) {
232+
if (DONT_NEED_ENCODING.test(c)) {
230233
if (c == ' ') {
231234
c = '+';
232235
needToChange = true;
@@ -269,7 +272,7 @@ public static String encode(String s, Charset charset) {
269272
}
270273
}
271274
i++;
272-
} while (i < s.length() && !DONT_NEED_ENCODING.get((c = s.charAt(i))));
275+
} while (i < s.length() && !DONT_NEED_ENCODING.test((c = s.charAt(i))));
273276

274277
charArrayWriter.flush();
275278
String str = charArrayWriter.toString();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package jdk.internal.util;
27+
28+
import jdk.internal.ValueBased;
29+
import jdk.internal.vm.annotation.Stable;
30+
31+
import java.util.BitSet;
32+
import java.util.function.IntPredicate;
33+
34+
/**
35+
* Class for working with immutable BitSets.
36+
*/
37+
@ValueBased
38+
public class ImmutableBitSetPredicate implements IntPredicate {
39+
40+
@Stable
41+
private final long[] words;
42+
43+
private ImmutableBitSetPredicate(BitSet original) {
44+
// If this class is made public, we need to do
45+
// a defensive array copy as certain BitSet implementations
46+
// may return a shared array. The spec says the array should be _new_ though but
47+
// the consequences might be unspecified for a malicious BitSet.
48+
this.words = original.toLongArray();
49+
}
50+
51+
@Override
52+
public boolean test(int bitIndex) {
53+
if (bitIndex < 0)
54+
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
55+
56+
int wordIndex = wordIndex(bitIndex);
57+
return (wordIndex < words.length)
58+
&& ((words[wordIndex] & (1L << bitIndex)) != 0);
59+
}
60+
61+
/**
62+
* Given a bit index, return word index containing it.
63+
*/
64+
private static int wordIndex(int bitIndex) {
65+
return bitIndex >> 6;
66+
}
67+
68+
/**
69+
* {@return a new {@link IntPredicate } representing the {@link BitSet#get(int)} method applied
70+
* on an immutable snapshot of the current state of this BitSet}.
71+
* <p>
72+
* If the returned predicate is invoked with a {@code bitIndex} that is negative, the predicate
73+
* will throw an IndexOutOfBoundsException just as the {@link BitSet#get(int)} method would.
74+
* <p>
75+
* Returned predicates are threadsafe and can be used without external synchronisation.
76+
*
77+
* @implNote The method is free to return a {@link ValueBased} implementation.
78+
*
79+
* @since 22
80+
*/
81+
public static IntPredicate of(BitSet original) {
82+
return new ImmutableBitSetPredicate(original);
83+
}
84+
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/**
25+
* @test
26+
* @summary Basic tests of immutable BitSets
27+
* @modules java.base/jdk.internal.util
28+
* @run junit ImmutableBitSet
29+
*/
30+
31+
import jdk.internal.util.ImmutableBitSetPredicate;
32+
import org.junit.jupiter.api.Test;
33+
34+
import java.util.BitSet;
35+
import java.util.Random;
36+
import java.util.function.IntPredicate;
37+
38+
import static org.junit.jupiter.api.Assertions.*;
39+
40+
public class ImmutableBitSet {
41+
42+
@Test
43+
void empty() {
44+
BitSet bs = new BitSet();
45+
IntPredicate ibs = ImmutableBitSetPredicate.of(bs);
46+
test(bs, ibs);
47+
}
48+
49+
@Test
50+
void negativeIndex() {
51+
BitSet bs = new BitSet();
52+
IntPredicate ibs = ImmutableBitSetPredicate.of(bs);
53+
assertThrows(IndexOutOfBoundsException.class, () -> {
54+
ibs.test(-1);
55+
});
56+
}
57+
58+
@Test
59+
void basic() {
60+
BitSet bs = createReference(147);
61+
IntPredicate ibs = ImmutableBitSetPredicate.of(bs);
62+
test(bs, ibs);
63+
}
64+
65+
@Test
66+
void clearedAtTheTail() {
67+
for (int i = Long.BYTES - 1; i < Long.BYTES + 2; i++) {
68+
BitSet bs = createReference(i);
69+
for (int j = bs.length() - 1; j > Long.BYTES - 1; j++) {
70+
bs.clear(j);
71+
}
72+
IntPredicate ibs = ImmutableBitSetPredicate.of(bs);
73+
test(bs, ibs);
74+
}
75+
}
76+
77+
static void test(BitSet expected, IntPredicate actual) {
78+
for (int i = 0; i < expected.length() + 17; i++) {
79+
assertEquals(expected.get(i), actual.test(i), "at index " + i);
80+
}
81+
}
82+
83+
private static BitSet createReference(int length) {
84+
BitSet result = new BitSet();
85+
Random random = new Random(length);
86+
for (int i = 0; i < length; i++) {
87+
result.set(i, random.nextBoolean());
88+
}
89+
return result;
90+
}
91+
92+
}

0 commit comments

Comments
 (0)
Please sign in to comment.