SearchSpecToProtoConverterUtil.java

/*
 * Copyright 2022 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 androidx.appsearch.localstorage.converter;

import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.collection.ArraySet;

import com.google.android.icing.proto.SchemaTypeConfigProto;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Utilities for working with {@link SearchSpecToProtoConverter} and
 * {@link SearchSuggestionSpecToProtoConverter}.
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class SearchSpecToProtoConverterUtil {
    private SearchSpecToProtoConverterUtil() {}

    /**
     * Add prefix to the given namespace filters that user want to search over and find the
     * intersection set with those prefixed namespace candidates that are stored in AppSearch.
     *
     * @param prefixes              Set of database prefix which the caller want to access.
     * @param namespaceMap          The cached Map of {@code <Prefix, Set<PrefixedNamespace>>}
     *                              stores all prefixed namespace filters which are stored in
     *                              AppSearch.
     * @param inputNamespaceFilters The set contains all desired but un-prefixed namespace filters
     *                              of user. If the inputNamespaceFilters is empty, all existing
     *                              prefixedCandidates will be added to the prefixedTargetFilters.
     */
    static Set<String> generateTargetNamespaceFilters(
            @NonNull Set<String> prefixes,
            @NonNull Map<String, Set<String>> namespaceMap,
            @NonNull List<String> inputNamespaceFilters) {
        // Convert namespace filters to prefixed namespace filters
        Set<String> targetPrefixedNamespaceFilters = new ArraySet<>();
        for (String prefix : prefixes) {
            // Step1: find all prefixed namespace candidates that are stored in AppSearch.
            Set<String> prefixedNamespaceCandidates = namespaceMap.get(prefix);
            if (prefixedNamespaceCandidates == null) {
                // This is should never happen. All prefixes should be verified before reach
                // here.
                continue;
            }
            // Step2: get the intersection of user searching filters and those candidates which are
            // stored in AppSearch.
            addIntersectedFilters(prefix, prefixedNamespaceCandidates,
                    inputNamespaceFilters, targetPrefixedNamespaceFilters);
        }
        return targetPrefixedNamespaceFilters;
    }

    /**
     * Add prefix to the given schema filters that user want to search over and find the
     * intersection set with those prefixed schema candidates that are stored in AppSearch.
     *
     * @param prefixes              Set of database prefix which the caller want to access.
     * @param schemaMap             The cached Map of
     *                              {@code <Prefix, Map<PrefixedSchemaType, schemaProto>>}
     *                              stores all prefixed schema filters which are stored in
     *                              AppSearch.
     * @param inputSchemaFilters    The set contains all desired but un-prefixed namespace filters
     *                              of user. If the inputSchemaFilters is empty, all existing
     *                              prefixedCandidates will be added to the prefixedTargetFilters.
     */
    static Set<String> generateTargetSchemaFilters(
            @NonNull Set<String> prefixes,
            @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap,
            @NonNull List<String> inputSchemaFilters) {
        Set<String> targetPrefixedSchemaFilters = new ArraySet<>();
        // Append prefix to input schema filters and get the intersection of existing schema filter.
        for (String prefix : prefixes) {
            // Step1: find all prefixed schema candidates that are stored in AppSearch.
            Map<String, SchemaTypeConfigProto> prefixedSchemaMap = schemaMap.get(prefix);
            if (prefixedSchemaMap == null) {
                // This is should never happen. All prefixes should be verified before reach
                // here.
                continue;
            }
            Set<String> prefixedSchemaCandidates = prefixedSchemaMap.keySet();
            // Step2: get the intersection of user searching filters and those candidates which are
            // stored in AppSearch.
            addIntersectedFilters(prefix, prefixedSchemaCandidates, inputSchemaFilters,
                    targetPrefixedSchemaFilters);
        }
        return targetPrefixedSchemaFilters;
    }

    /**
     * Find the intersection set of candidates existing in AppSearch and user specified filters.
     *
     * @param prefix                   The package and database's identifier.
     * @param prefixedCandidates       The set contains all prefixed candidates which are existing
     *                                 in a database.
     * @param inputFilters             The set contains all desired but un-prefixed filters of user.
     *                                 If the inputFilters is empty, all prefixedCandidates will be
     *                                 added to the prefixedTargetFilters.
     * @param prefixedTargetFilters    The output set contains all desired prefixed filters which
     *                                 are existing in the database.
     */
    private static void addIntersectedFilters(
            @NonNull String prefix,
            @NonNull Set<String> prefixedCandidates,
            @NonNull List<String> inputFilters,
            @NonNull Set<String> prefixedTargetFilters) {
        if (inputFilters.isEmpty()) {
            // Client didn't specify certain schemas to search over, add all candidates.
            prefixedTargetFilters.addAll(prefixedCandidates);
        } else {
            // Client specified some filters to search over, check and only add those are
            // existing in the database.
            for (int i = 0; i < inputFilters.size(); i++) {
                String prefixedTargetFilter = prefix + inputFilters.get(i);
                if (prefixedCandidates.contains(prefixedTargetFilter)) {
                    prefixedTargetFilters.add(prefixedTargetFilter);
                }
            }
        }
    }
}