From f2922682688a40529df269e1551246ac8da5d7ee Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 5 Sep 2023 06:43:00 +0000 Subject: [PATCH] 8315454: Add a way to create an immutable snapshot of a BitSet Co-authored-by: Claes Redestad Reviewed-by: redestad --- .../share/classes/java/net/URLEncoder.java | 29 +++--- .../util/ImmutableBitSetPredicate.java | 85 +++++++++++++++++ .../jdk/java/util/BitSet/ImmutableBitSet.java | 92 +++++++++++++++++++ 3 files changed, 193 insertions(+), 13 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/util/ImmutableBitSetPredicate.java create mode 100644 test/jdk/java/util/BitSet/ImmutableBitSet.java diff --git a/src/java.base/share/classes/java/net/URLEncoder.java b/src/java.base/share/classes/java/net/URLEncoder.java index 1b5ff1cae26..41b9ae791fb 100644 --- a/src/java.base/share/classes/java/net/URLEncoder.java +++ b/src/java.base/share/classes/java/net/URLEncoder.java @@ -32,7 +32,9 @@ import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException ; import java.util.BitSet; import java.util.Objects; +import java.util.function.IntPredicate; +import jdk.internal.util.ImmutableBitSetPredicate; import jdk.internal.util.StaticProperty; /** @@ -78,7 +80,7 @@ import jdk.internal.util.StaticProperty; * @since 1.0 */ public class URLEncoder { - private static final BitSet DONT_NEED_ENCODING; + private static final IntPredicate DONT_NEED_ENCODING; private static final int CASE_DIFF = ('a' - 'A'); private static final String DEFAULT_ENCODING_NAME; @@ -120,17 +122,18 @@ public class URLEncoder { * */ - DONT_NEED_ENCODING = new BitSet(128); - - DONT_NEED_ENCODING.set('a', 'z' + 1); - DONT_NEED_ENCODING.set('A', 'Z' + 1); - DONT_NEED_ENCODING.set('0', '9' + 1); - DONT_NEED_ENCODING.set(' '); /* encoding a space to a + is done + var bitSet = new BitSet(128); + bitSet.set('a', 'z' + 1); + bitSet.set('A', 'Z' + 1); + bitSet.set('0', '9' + 1); + bitSet.set(' '); /* encoding a space to a + is done * in the encode() method */ - DONT_NEED_ENCODING.set('-'); - DONT_NEED_ENCODING.set('_'); - DONT_NEED_ENCODING.set('.'); - DONT_NEED_ENCODING.set('*'); + bitSet.set('-'); + bitSet.set('_'); + bitSet.set('.'); + bitSet.set('*'); + + DONT_NEED_ENCODING = ImmutableBitSetPredicate.of(bitSet); DEFAULT_ENCODING_NAME = StaticProperty.fileEncoding(); } @@ -226,7 +229,7 @@ public class URLEncoder { for (int i = 0; i < s.length();) { int c = s.charAt(i); //System.out.println("Examining character: " + c); - if (DONT_NEED_ENCODING.get(c)) { + if (DONT_NEED_ENCODING.test(c)) { if (c == ' ') { c = '+'; needToChange = true; @@ -269,7 +272,7 @@ public class URLEncoder { } } i++; - } while (i < s.length() && !DONT_NEED_ENCODING.get((c = s.charAt(i)))); + } while (i < s.length() && !DONT_NEED_ENCODING.test((c = s.charAt(i)))); charArrayWriter.flush(); String str = charArrayWriter.toString(); diff --git a/src/java.base/share/classes/jdk/internal/util/ImmutableBitSetPredicate.java b/src/java.base/share/classes/jdk/internal/util/ImmutableBitSetPredicate.java new file mode 100644 index 00000000000..4baeb8e31e3 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/util/ImmutableBitSetPredicate.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.util; + +import jdk.internal.ValueBased; +import jdk.internal.vm.annotation.Stable; + +import java.util.BitSet; +import java.util.function.IntPredicate; + +/** + * Class for working with immutable BitSets. + */ +@ValueBased +public class ImmutableBitSetPredicate implements IntPredicate { + + @Stable + private final long[] words; + + private ImmutableBitSetPredicate(BitSet original) { + // If this class is made public, we need to do + // a defensive array copy as certain BitSet implementations + // may return a shared array. The spec says the array should be _new_ though but + // the consequences might be unspecified for a malicious BitSet. + this.words = original.toLongArray(); + } + + @Override + public boolean test(int bitIndex) { + if (bitIndex < 0) + throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex); + + int wordIndex = wordIndex(bitIndex); + return (wordIndex < words.length) + && ((words[wordIndex] & (1L << bitIndex)) != 0); + } + + /** + * Given a bit index, return word index containing it. + */ + private static int wordIndex(int bitIndex) { + return bitIndex >> 6; + } + + /** + * {@return a new {@link IntPredicate } representing the {@link BitSet#get(int)} method applied + * on an immutable snapshot of the current state of this BitSet}. + *

+ * If the returned predicate is invoked with a {@code bitIndex} that is negative, the predicate + * will throw an IndexOutOfBoundsException just as the {@link BitSet#get(int)} method would. + *

+ * Returned predicates are threadsafe and can be used without external synchronisation. + * + * @implNote The method is free to return a {@link ValueBased} implementation. + * + * @since 22 + */ + public static IntPredicate of(BitSet original) { + return new ImmutableBitSetPredicate(original); + } + +} diff --git a/test/jdk/java/util/BitSet/ImmutableBitSet.java b/test/jdk/java/util/BitSet/ImmutableBitSet.java new file mode 100644 index 00000000000..7dde261ff5d --- /dev/null +++ b/test/jdk/java/util/BitSet/ImmutableBitSet.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Basic tests of immutable BitSets + * @modules java.base/jdk.internal.util + * @run junit ImmutableBitSet + */ + +import jdk.internal.util.ImmutableBitSetPredicate; +import org.junit.jupiter.api.Test; + +import java.util.BitSet; +import java.util.Random; +import java.util.function.IntPredicate; + +import static org.junit.jupiter.api.Assertions.*; + +public class ImmutableBitSet { + + @Test + void empty() { + BitSet bs = new BitSet(); + IntPredicate ibs = ImmutableBitSetPredicate.of(bs); + test(bs, ibs); + } + + @Test + void negativeIndex() { + BitSet bs = new BitSet(); + IntPredicate ibs = ImmutableBitSetPredicate.of(bs); + assertThrows(IndexOutOfBoundsException.class, () -> { + ibs.test(-1); + }); + } + + @Test + void basic() { + BitSet bs = createReference(147); + IntPredicate ibs = ImmutableBitSetPredicate.of(bs); + test(bs, ibs); + } + + @Test + void clearedAtTheTail() { + for (int i = Long.BYTES - 1; i < Long.BYTES + 2; i++) { + BitSet bs = createReference(i); + for (int j = bs.length() - 1; j > Long.BYTES - 1; j++) { + bs.clear(j); + } + IntPredicate ibs = ImmutableBitSetPredicate.of(bs); + test(bs, ibs); + } + } + + static void test(BitSet expected, IntPredicate actual) { + for (int i = 0; i < expected.length() + 17; i++) { + assertEquals(expected.get(i), actual.test(i), "at index " + i); + } + } + + private static BitSet createReference(int length) { + BitSet result = new BitSet(); + Random random = new Random(length); + for (int i = 0; i < length; i++) { + result.set(i, random.nextBoolean()); + } + return result; + } + +} \ No newline at end of file