Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement ART-based JavaTypeCache
Browse files Browse the repository at this point in the history
An adaptive radix tree based implementation of `JavaTypeCache`. This uses slightly more memory than the existing Snappy-based implementation (10-15%), but inserts are around 50% faster and reads about 100% faster.
knutwannheden committed Oct 5, 2024
1 parent 0067a10 commit 3138da5
Showing 7 changed files with 1,491 additions and 7 deletions.
1 change: 1 addition & 0 deletions rewrite-benchmarks/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ dependencies {
jmh(project(":rewrite-maven"))
jmh("org.rocksdb:rocksdbjni:latest.release")
jmh("org.openjdk.jmh:jmh-core:latest.release")
jmh("org.openjdk.jol:jol-core:latest.release")
jmh("io.github.fastfilter:fastfilter:latest.release")

// Nebula doesn't like having jmhAnnotationProcessor without jmh so we just add it twice.
Original file line number Diff line number Diff line change
@@ -15,38 +15,53 @@
*/
package org.openrewrite.benchmarks.java;

import org.jspecify.annotations.Nullable;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jol.info.GraphLayout;
import org.openrewrite.InMemoryExecutionContext;
import org.openrewrite.LargeSourceSet;
import org.openrewrite.SourceFile;
import org.openrewrite.internal.InMemoryLargeSourceSet;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.internal.AdaptiveRadixJavaTypeCache;
import org.openrewrite.java.internal.JavaTypeCache;

import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@State(Scope.Benchmark)
public class JavaCompilationUnitState {
JavaParser.Builder<? extends JavaParser, ?> javaParser;
List<SourceFile> sourceFiles;
List<Path> inputs;
JavaTypeCache snappyTypeCache;
AdaptiveRadixJavaTypeCache radixMapTypeCache;
MapJavaTypeCache typeCache;

public static void main(String[] args) throws URISyntaxException {
new JavaCompilationUnitState().setup();
}

@Setup(Level.Trial)
public void setup() throws URISyntaxException {
Path rewriteRoot = Paths.get(ChangeTypeBenchmark.class.getResource("./")
.toURI()).resolve("../../../../../../../../").normalize();

List<Path> inputs = Arrays.asList(
inputs = Arrays.asList(
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/internal/lang/Nullable.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/internal/lang/NullUtils.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/internal/MetricsHelper.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/internal/ListUtils.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/internal/PropertyPlaceholderHelper.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/internal/RecipeIntrospectionUtils.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/Tree.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/ExecutionContext.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/InMemoryExecutionContext.java"),
@@ -61,21 +76,45 @@ public void setup() throws URISyntaxException {
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/Result.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/SourceFile.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/Recipe.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/ScanningRecipe.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/Validated.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/ValidationException.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/TreeObserver.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/config/ResourceLoader.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java"),
rewriteRoot.resolve("rewrite-core/src/main/java/org/openrewrite/config/RecipeIntrospectionException.java")
);

sourceFiles = JavaParser.fromJavaVersion()
.classpath("jsr305", "classgraph", "jackson-annotations", "micrometer-core", "slf4j-api",
"org.openrewrite.jgit")
javaParser = JavaParser.fromJavaVersion()
.classpath("jsr305", "classgraph", "jackson-annotations", "micrometer-core",
"jgit", "jspecify", "lombok", "annotations");
// .logCompilationWarningsAndErrors(true)
.build()
.parse(inputs, null, new InMemoryExecutionContext(Throwable::printStackTrace))

typeCache = new MapJavaTypeCache();
JavaParser parser = javaParser.typeCache(typeCache).build();
sourceFiles = parser
.parse(inputs, null, new InMemoryExecutionContext())
.collect(Collectors.toList());

radixMapTypeCache = new AdaptiveRadixJavaTypeCache();
for (Map.Entry<String, Object> entry : typeCache.map().entrySet()) {
radixMapTypeCache.put(entry.getKey(), entry.getValue());
}

snappyTypeCache = new JavaTypeCache();
for (Map.Entry<String, Object> entry : typeCache.map().entrySet()) {
snappyTypeCache.put(entry.getKey(), entry.getValue());
}
}

void printMemory() {
long retainedSize = GraphLayout.parseInstance(radixMapTypeCache).totalSize();
System.out.printf("Retained AdaptiveRadixTree size: %10d bytes\n", retainedSize);
retainedSize = GraphLayout.parseInstance(snappyTypeCache).totalSize();
System.out.printf("Retained Snappy size: %10d bytes\n", retainedSize);
}

@TearDown(Level.Trial)
@@ -90,4 +129,37 @@ public LargeSourceSet getSourceSet() {
public List<SourceFile> getSourceFiles() {
return sourceFiles;
}

static class MapJavaTypeCache extends JavaTypeCache {

Map<String, Object> typeCache = new HashMap<>();

public <T> @Nullable T get(String signature) {
//noinspection unchecked
return (T) typeCache.get(signature);
}

public void put(String signature, Object o) {
typeCache.put(signature, o);
}

public Map<String, Object> map() {
return typeCache;
}

public void clear() {
typeCache.clear();
}

public int size() {
return typeCache.size();
}

@Override
public MapJavaTypeCache clone() {
MapJavaTypeCache clone = (MapJavaTypeCache) super.clone();
clone.typeCache = new HashMap<>(this.typeCache);
return clone;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2022 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.benchmarks.java;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.profile.GCProfiler;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openrewrite.InMemoryExecutionContext;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.internal.AdaptiveRadixJavaTypeCache;
import org.openrewrite.java.internal.JavaTypeCache;

import java.net.URISyntaxException;
import java.util.concurrent.TimeUnit;

@Fork(1)
@Measurement(iterations = 2)
@Warmup(iterations = 2)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Threads(4)
public class JavaParserBenchmark {

@Benchmark
public void snappy(JavaCompilationUnitState state, Blackhole bh) {
JavaTypeCache typeCache = new JavaTypeCache();
JavaParser parser = state.javaParser.typeCache(typeCache).build();
parser
.parse(state.inputs, null, new InMemoryExecutionContext())
.forEach(bh::consume);
}

@Benchmark
public void adaptiveRadix(JavaCompilationUnitState state, Blackhole bh) {
AdaptiveRadixJavaTypeCache typeCache = new AdaptiveRadixJavaTypeCache();
JavaParser parser = state.javaParser.typeCache(typeCache).build();
parser
.parse(state.inputs, null, new InMemoryExecutionContext())
.forEach(bh::consume);
}

public static void main(String[] args) throws RunnerException, URISyntaxException {
Options opt = new OptionsBuilder()
.include(JavaParserBenchmark.class.getSimpleName())
.addProfiler(GCProfiler.class)
.shouldFailOnError(true)
.build();
new Runner(opt).run();
JavaCompilationUnitState state = new JavaCompilationUnitState();
state.setup();
state.printMemory();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2022 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.benchmarks.java;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.profile.GCProfiler;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openrewrite.java.internal.JavaTypeCache;
import org.openrewrite.java.internal.AdaptiveRadixJavaTypeCache;

import java.net.URISyntaxException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Fork(1)
@Measurement(iterations = 2)
@Warmup(iterations = 2)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Threads(4)
public class JavaTypeCacheBenchmark {

@Benchmark
public void writeSnappy(JavaCompilationUnitState state, Blackhole bh) {
JavaTypeCache typeCache = new JavaTypeCache();
for (Map.Entry<String, Object> entry : state.typeCache.map().entrySet()) {
typeCache.put(entry.getKey(), entry.getValue());
}
}

@Benchmark
public void writeAdaptiveRadix(JavaCompilationUnitState state, Blackhole bh) {
AdaptiveRadixJavaTypeCache typeCache = new AdaptiveRadixJavaTypeCache();
for (Map.Entry<String, Object> entry : state.typeCache.map().entrySet()) {
typeCache.put(entry.getKey(), entry.getValue());
}
}

@Benchmark
public void readSnappy(JavaCompilationUnitState state, Blackhole bh) {
for (Map.Entry<String, Object> entry : state.typeCache.map().entrySet()) {
bh.consume(state.snappyTypeCache.get(entry.getKey()));
}
}

@Benchmark
public void readAdaptiveRadix(JavaCompilationUnitState state, Blackhole bh) {
for (Map.Entry<String, Object> entry : state.typeCache.map().entrySet()) {
bh.consume(state.radixMapTypeCache.get(entry.getKey()));
}
}

public static void main(String[] args) throws RunnerException, URISyntaxException {
Options opt = new OptionsBuilder()
.include(JavaTypeCacheBenchmark.class.getSimpleName())
.addProfiler(GCProfiler.class)
.shouldFailOnError(true)
.build();
new Runner(opt).run();
JavaCompilationUnitState state = new JavaCompilationUnitState();
state.setup();
state.printMemory();
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,386 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.internal;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class AdaptiveRadixTreeTest {

@Test
public void insertAndSearch_SingleKey() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("cat", 1);

assertThat(tree.search("cat")).isEqualTo(1);
assertThat(tree.search("ca")).isNull();
assertThat(tree.search("c")).isNull();
assertThat(tree.search("dog")).isNull();
}

@Test
public void copy() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("cat", 1);
AdaptiveRadixTree<Integer> copy = tree.copy();
assertThat(copy.search("cat")).isEqualTo(1);
assertThat(copy.search("ca")).isNull();
assertThat(copy.search("c")).isNull();
assertThat(copy.search("dog")).isNull();
}

@Test
public void insertAndSearch_MultipleKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("cat", 1);
tree.insert("car", 2);
tree.insert("cart", 3);
tree.insert("dog", 4);

assertThat(tree.search("cat")).isEqualTo(1);
assertThat(tree.search("car")).isEqualTo(2);
assertThat(tree.search("cart")).isEqualTo(3);
assertThat(tree.search("dog")).isEqualTo(4);

assertThat(tree.search("ca")).isNull();
assertThat(tree.search("c")).isNull();
assertThat(tree.search("do")).isNull();
assertThat(tree.search("dogs")).isNull();
}

@Test
public void insertAndSearch_OverlappingKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("test", 1);
tree.insert("testing", 2);
tree.insert("tester", 3);

assertThat(tree.search("test")).isEqualTo(1);
assertThat(tree.search("testing")).isEqualTo(2);
assertThat(tree.search("tester")).isEqualTo(3);

assertThat(tree.search("tes")).isNull();
assertThat(tree.search("testers")).isNull();
}

@Test
public void insertAndSearch_PrefixKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("a", 1);
tree.insert("ab", 2);
tree.insert("abc", 3);
tree.insert("abcd", 4);

assertThat(tree.search("a")).isEqualTo(1);
assertThat(tree.search("ab")).isEqualTo(2);
assertThat(tree.search("abc")).isEqualTo(3);
assertThat(tree.search("abcd")).isEqualTo(4);

assertThat(tree.search("abcde")).isNull();
assertThat(tree.search("abce")).isNull();
}

@Test
public void insertAndSearch_EmptyString() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("", 1);

assertThat(tree.search("")).isEqualTo(1);
assertThat(tree.search(" ")).isNull();
assertThat(tree.search("a")).isNull();
}

@Test
public void insertAndSearch_SpecialCharacters() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("hello-world", 1);
tree.insert("hello_world", 2);
tree.insert("hello world", 3);

assertThat(tree.search("hello-world")).isEqualTo(1);
assertThat(tree.search("hello_world")).isEqualTo(2);
assertThat(tree.search("hello world")).isEqualTo(3);

assertThat(tree.search("hello")).isNull();
assertThat(tree.search("world")).isNull();
}

@Test
public void insertAndSearch_CaseSensitivity() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("Apple", 1);
tree.insert("apple", 2);

assertThat(tree.search("Apple")).isEqualTo(1);
assertThat(tree.search("apple")).isEqualTo(2);

assertThat(tree.search("Appl")).isNull();
assertThat(tree.search("APPLE")).isNull();
}

@Test
public void insertAndSearch_NumericKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("123", 1);
tree.insert("1234", 2);
tree.insert("12345", 3);

assertThat(tree.search("123")).isEqualTo(1);
assertThat(tree.search("1234")).isEqualTo(2);
assertThat(tree.search("12345")).isEqualTo(3);

assertThat(tree.search("12")).isNull();
assertThat(tree.search("123456")).isNull();
}

@Test
public void insertAndSearch_MixedCharacterKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("user1", 1);
tree.insert("user2", 2);
tree.insert("user10", 3);
tree.insert("user20", 4);

assertThat(tree.search("user1")).isEqualTo(1);
assertThat(tree.search("user2")).isEqualTo(2);
assertThat(tree.search("user10")).isEqualTo(3);
assertThat(tree.search("user20")).isEqualTo(4);

assertThat(tree.search("user")).isNull();
assertThat(tree.search("user3")).isNull();
}

@Test
public void insertAndSearch_LongKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
String longKey1 = "a".repeat(1000);
String longKey2 = "a".repeat(999) + "b";

tree.insert(longKey1, 1);
tree.insert(longKey2, 2);

assertThat(tree.search(longKey1)).isEqualTo(1);
assertThat(tree.search(longKey2)).isEqualTo(2);

assertThat(tree.search("a".repeat(500))).isNull();
}

@Test
public void insertAndSearch_NullValue() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("key", null);

assertThat(tree.search("key")).isNull();
assertThat(tree.search("ke")).isNull();
}

@Test
public void insertAndSearch_UnicodeKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("こんにちは", 1); // "Hello" in Japanese
tree.insert("こんばんは", 2); // "Good evening" in Japanese
tree.insert("你好", 3); // "Hello" in Chinese

assertThat(tree.search("こんにちは")).isEqualTo(1);
assertThat(tree.search("こんばんは")).isEqualTo(2);
assertThat(tree.search("你好")).isEqualTo(3);

assertThat(tree.search("こん")).isNull();
assertThat(tree.search("你好嗎")).isNull();
}

@Test
public void insertAndSearch_EmptyTree() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
assertThat(tree.search("anykey")).isNull();
}

@Test
public void insert_DuplicateKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("duplicate", 1);
tree.insert("duplicate", 2);

assertThat(tree.search("duplicate")).isEqualTo(2);
}

@Test
public void Search_NonExistentKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("exist", 1);

assertThat(tree.search("nonexist")).isNull();
assertThat(tree.search("exis")).isNull();
assertThat(tree.search("exists")).isNull();
}

@Test
public void insertAndSearch_SimilarKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("ab", 1);
tree.insert("abc", 2);
tree.insert("abcd", 3);
tree.insert("abcde", 4);
tree.insert("abcdef", 5);

assertThat(tree.search("ab")).isEqualTo(1);
assertThat(tree.search("abc")).isEqualTo(2);
assertThat(tree.search("abcd")).isEqualTo(3);
assertThat(tree.search("abcde")).isEqualTo(4);
assertThat(tree.search("abcdef")).isEqualTo(5);
}

@Test
public void insertAndSearch_CommonPrefixes() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("prefix", 1);
tree.insert("preface", 2);
tree.insert("preform", 3);
tree.insert("preposition", 4);
tree.insert("presentation", 5);

assertThat(tree.search("prefix")).isEqualTo(1);
assertThat(tree.search("preface")).isEqualTo(2);
assertThat(tree.search("preform")).isEqualTo(3);
assertThat(tree.search("preposition")).isEqualTo(4);
assertThat(tree.search("presentation")).isEqualTo(5);

assertThat(tree.search("pre")).isNull();
assertThat(tree.search("president")).isNull();
}

@Test
public void insertAndSearch_SingleCharacterKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("a", 1);
tree.insert("b", 2);
tree.insert("c", 3);

assertThat(tree.search("a")).isEqualTo(1);
assertThat(tree.search("b")).isEqualTo(2);
assertThat(tree.search("c")).isEqualTo(3);

assertThat(tree.search("d")).isNull();
}

@Test
public void insertAndSearch_NullKey() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
assertThatThrownBy(() -> tree.insert(null, 1))
.isInstanceOf(NullPointerException.class);
}

@Test
public void insertAndSearch_SpecialCaseKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("user_name", 1);
tree.insert("user-name", 2);
tree.insert("user.name", 3);
tree.insert("user@name", 4);
tree.insert("user#name", 5);

assertThat(tree.search("user_name")).isEqualTo(1);
assertThat(tree.search("user-name")).isEqualTo(2);
assertThat(tree.search("user.name")).isEqualTo(3);
assertThat(tree.search("user@name")).isEqualTo(4);
assertThat(tree.search("user#name")).isEqualTo(5);

assertThat(tree.search("user%name")).isNull();
}

@Test
public void insertAndSearch_CyrillicKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("привет", 1); // "Hello" in Russian
tree.insert("проект", 2); // "Project" in Russian

assertThat(tree.search("привет")).isEqualTo(1);
assertThat(tree.search("проект")).isEqualTo(2);

assertThat(tree.search("про")).isNull();
assertThat(tree.search("прив")).isNull();
}

@Test
public void insertAndSearch_EmojiKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("😀", 1);
tree.insert("😀😁", 2);
tree.insert("😀😁😂", 3);

assertThat(tree.search("😀")).isEqualTo(1);
assertThat(tree.search("😀😁")).isEqualTo(2);
assertThat(tree.search("😀😁😂")).isEqualTo(3);

assertThat(tree.search("😀😁😂🤣")).isNull();
}

@Test
public void insertAndSearch_MixedLanguageKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("hello世界", 1); // "hello world" mixing English and Chinese
tree.insert("こんにちはworld", 2); // "hello world" mixing Japanese and English

assertThat(tree.search("hello世界")).isEqualTo(1);
assertThat(tree.search("こんにちはworld")).isEqualTo(2);

assertThat(tree.search("hello")).isNull();
assertThat(tree.search("world")).isNull();
}

@Test
public void insertAndSearch_SpacesInKeys() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("key with spaces", 1);
tree.insert("another key with spaces", 2);

assertThat(tree.search("key with spaces")).isEqualTo(1);
assertThat(tree.search("another key with spaces")).isEqualTo(2);

assertThat(tree.search("key with")).isNull();
assertThat(tree.search("another key")).isNull();
}

@Test
public void insertAndSearch_SpecialUnicodeCharacters() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("naïve", 1);
tree.insert("café", 2);
tree.insert("résumé", 3);

assertThat(tree.search("naïve")).isEqualTo(1);
assertThat(tree.search("café")).isEqualTo(2);
assertThat(tree.search("résumé")).isEqualTo(3);

assertThat(tree.search("naive")).isNull();
assertThat(tree.search("cafe")).isNull();
}

@Test
public void insertAndSearch_ControlCharacters() {
AdaptiveRadixTree<Integer> tree = new AdaptiveRadixTree<>();
tree.insert("line1\nline2", 1);
tree.insert("tab\tcharacter", 2);

assertThat(tree.search("line1\nline2")).isEqualTo(1);
assertThat(tree.search("tab\tcharacter")).isEqualTo(2);

assertThat(tree.search("line1")).isNull();
assertThat(tree.search("tab")).isNull();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2021 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.internal;

import org.jspecify.annotations.Nullable;
import org.openrewrite.internal.AdaptiveRadixTree;

public class AdaptiveRadixJavaTypeCache extends JavaTypeCache {

public AdaptiveRadixJavaTypeCache() {

}
AdaptiveRadixTree<Object> typeCache = new AdaptiveRadixTree<>();

public <T> @Nullable T get(String signature) {
//noinspection unchecked
return (T) typeCache.search(signature);
}

public void put(String signature, Object o) {
typeCache.insert(signature, o);
}

public void clear() {
typeCache.clear();
}

@Override
public AdaptiveRadixJavaTypeCache clone() {
AdaptiveRadixJavaTypeCache clone = (AdaptiveRadixJavaTypeCache) super.clone();
clone.typeCache = this.typeCache.copy();
return clone;
}
}

0 comments on commit 3138da5

Please sign in to comment.