Loading apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 com.android.server.appsearch.impl; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; import android.util.SparseArray; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** * Fake in-memory implementation of the Icing key-value store and reverse index. * <p> * Currently, only queries by single exact term are supported. There is no support for persistence, * namespaces, i18n tokenization, or schema. * * @hide */ public class FakeIcing { private final AtomicInteger mNextDocId = new AtomicInteger(); private final Map<String, Integer> mUriToDocIdMap = new ArrayMap<>(); /** Array of Documents (pair of uri and content) where index into the array is the docId. */ private final SparseArray<Pair<String, String>> mDocStore = new SparseArray<>(); /** Map of term to posting-list (the set of DocIds containing that term). */ private final Map<String, Set<Integer>> mIndex = new ArrayMap<>(); /** * Inserts a document into the index. * * @param uri The globally unique identifier of the document. * @param doc The contents of the document. */ public void put(@NonNull String uri, @NonNull String doc) { // Update mDocIdMap Integer docId = mUriToDocIdMap.get(uri); if (docId != null) { // Delete the old doc mDocStore.remove(docId); } // Allocate a new docId docId = mNextDocId.getAndIncrement(); mUriToDocIdMap.put(uri, docId); // Update mDocStore mDocStore.put(docId, Pair.create(uri, doc)); // Update mIndex String[] words = normalizeString(doc).split("\\s+"); for (String word : words) { Set<Integer> postingList = mIndex.get(word); if (postingList == null) { postingList = new ArraySet<>(); mIndex.put(word, postingList); } postingList.add(docId); } } /** * Retrieves a document from the index. * * @param uri The URI of the document to retrieve. * @return The body of the document, or {@code null} if no such document exists. */ @Nullable public String get(@NonNull String uri) { Integer docId = mUriToDocIdMap.get(uri); if (docId == null) { return null; } Pair<String, String> record = mDocStore.get(docId); if (record == null) { return null; } return record.second; } /** * Returns documents containing the given term. * * @param term A single exact term to look up in the index. * @return The URIs of the matching documents, or an empty {@code List} if no documents match. */ @NonNull public List<String> query(@NonNull String term) { String normTerm = normalizeString(term); Set<Integer> docIds = mIndex.get(normTerm); if (docIds == null || docIds.isEmpty()) { return Collections.emptyList(); } List<String> uris = new ArrayList<>(docIds.size()); for (int docId : docIds) { Pair<String, String> record = mDocStore.get(docId); if (record != null) { uris.add(record.first); } } return uris; } /** * Deletes a document by its URI. * * @param uri The URI of the document to be deleted. */ public void delete(@NonNull String uri) { // Update mDocIdMap Integer docId = mUriToDocIdMap.get(uri); if (docId != null) { // Delete the old doc mDocStore.remove(docId); mUriToDocIdMap.remove(uri); } } /** Strips out punctuation and converts to lowercase. */ private static String normalizeString(String input) { return input.replaceAll("\\p{P}", "").toLowerCase(Locale.getDefault()); } } services/tests/servicestests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ android_test { "hamcrest-library", "servicestests-utils", "xml-writer-device-lib", "service-appsearch", "service-jobscheduler", ], Loading services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java 0 → 100644 +93 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 com.android.server.appsearch.impl; import static com.google.common.truth.Truth.assertThat; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class FakeIcingTest { @Test public void query() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); assertThat(icing.query("meow")).containsExactly("uri:cat"); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.query("fred")).isEmpty(); } @Test public void queryNorm() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); assertThat(icing.query("the")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.query("The")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.query("tHe")).containsExactly("uri:cat", "uri:dog"); } @Test public void get() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said meow"); } @Test public void replace() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); assertThat(icing.query("meow")).containsExactly("uri:cat"); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said meow"); // Replace icing.put("uri:cat", "The cat said purr"); icing.put("uri:bird", "The cat said tweet"); assertThat(icing.query("meow")).isEmpty(); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog", "uri:bird"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said purr"); } @Test public void delete() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); assertThat(icing.query("meow")).containsExactly("uri:cat"); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said meow"); // Delete icing.delete("uri:cat"); icing.delete("uri:notreal"); assertThat(icing.query("meow")).isEmpty(); assertThat(icing.query("said")).containsExactly("uri:dog"); assertThat(icing.get("uri:cat")).isNull(); } } Loading
apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 com.android.server.appsearch.impl; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; import android.util.SparseArray; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** * Fake in-memory implementation of the Icing key-value store and reverse index. * <p> * Currently, only queries by single exact term are supported. There is no support for persistence, * namespaces, i18n tokenization, or schema. * * @hide */ public class FakeIcing { private final AtomicInteger mNextDocId = new AtomicInteger(); private final Map<String, Integer> mUriToDocIdMap = new ArrayMap<>(); /** Array of Documents (pair of uri and content) where index into the array is the docId. */ private final SparseArray<Pair<String, String>> mDocStore = new SparseArray<>(); /** Map of term to posting-list (the set of DocIds containing that term). */ private final Map<String, Set<Integer>> mIndex = new ArrayMap<>(); /** * Inserts a document into the index. * * @param uri The globally unique identifier of the document. * @param doc The contents of the document. */ public void put(@NonNull String uri, @NonNull String doc) { // Update mDocIdMap Integer docId = mUriToDocIdMap.get(uri); if (docId != null) { // Delete the old doc mDocStore.remove(docId); } // Allocate a new docId docId = mNextDocId.getAndIncrement(); mUriToDocIdMap.put(uri, docId); // Update mDocStore mDocStore.put(docId, Pair.create(uri, doc)); // Update mIndex String[] words = normalizeString(doc).split("\\s+"); for (String word : words) { Set<Integer> postingList = mIndex.get(word); if (postingList == null) { postingList = new ArraySet<>(); mIndex.put(word, postingList); } postingList.add(docId); } } /** * Retrieves a document from the index. * * @param uri The URI of the document to retrieve. * @return The body of the document, or {@code null} if no such document exists. */ @Nullable public String get(@NonNull String uri) { Integer docId = mUriToDocIdMap.get(uri); if (docId == null) { return null; } Pair<String, String> record = mDocStore.get(docId); if (record == null) { return null; } return record.second; } /** * Returns documents containing the given term. * * @param term A single exact term to look up in the index. * @return The URIs of the matching documents, or an empty {@code List} if no documents match. */ @NonNull public List<String> query(@NonNull String term) { String normTerm = normalizeString(term); Set<Integer> docIds = mIndex.get(normTerm); if (docIds == null || docIds.isEmpty()) { return Collections.emptyList(); } List<String> uris = new ArrayList<>(docIds.size()); for (int docId : docIds) { Pair<String, String> record = mDocStore.get(docId); if (record != null) { uris.add(record.first); } } return uris; } /** * Deletes a document by its URI. * * @param uri The URI of the document to be deleted. */ public void delete(@NonNull String uri) { // Update mDocIdMap Integer docId = mUriToDocIdMap.get(uri); if (docId != null) { // Delete the old doc mDocStore.remove(docId); mUriToDocIdMap.remove(uri); } } /** Strips out punctuation and converts to lowercase. */ private static String normalizeString(String input) { return input.replaceAll("\\p{P}", "").toLowerCase(Locale.getDefault()); } }
services/tests/servicestests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ android_test { "hamcrest-library", "servicestests-utils", "xml-writer-device-lib", "service-appsearch", "service-jobscheduler", ], Loading
services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java 0 → 100644 +93 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 com.android.server.appsearch.impl; import static com.google.common.truth.Truth.assertThat; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class FakeIcingTest { @Test public void query() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); assertThat(icing.query("meow")).containsExactly("uri:cat"); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.query("fred")).isEmpty(); } @Test public void queryNorm() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); assertThat(icing.query("the")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.query("The")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.query("tHe")).containsExactly("uri:cat", "uri:dog"); } @Test public void get() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said meow"); } @Test public void replace() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); assertThat(icing.query("meow")).containsExactly("uri:cat"); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said meow"); // Replace icing.put("uri:cat", "The cat said purr"); icing.put("uri:bird", "The cat said tweet"); assertThat(icing.query("meow")).isEmpty(); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog", "uri:bird"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said purr"); } @Test public void delete() { FakeIcing icing = new FakeIcing(); icing.put("uri:cat", "The cat said meow"); icing.put("uri:dog", "The dog said woof"); assertThat(icing.query("meow")).containsExactly("uri:cat"); assertThat(icing.query("said")).containsExactly("uri:cat", "uri:dog"); assertThat(icing.get("uri:cat")).isEqualTo("The cat said meow"); // Delete icing.delete("uri:cat"); icing.delete("uri:notreal"); assertThat(icing.query("meow")).isEmpty(); assertThat(icing.query("said")).containsExactly("uri:dog"); assertThat(icing.get("uri:cat")).isNull(); } }