public class

PrefixUtil

extends java.lang.Object

 java.lang.Object

↳androidx.appsearch.localstorage.util.PrefixUtil

Gradle dependencies

compile group: 'androidx.appsearch', name: 'appsearch-local-storage', version: '1.1.0-alpha05'

  • groupId: androidx.appsearch
  • artifactId: appsearch-local-storage
  • version: 1.1.0-alpha05

Artifact androidx.appsearch:appsearch-local-storage:1.1.0-alpha05 it located at Google repository (https://maven.google.com/)

Overview

Provides utility functions for working with package + database prefixes.

Summary

Fields
public static final charDATABASE_DELIMITER

public static final charPACKAGE_DELIMITER

Methods
public static voidaddPrefixToDocument(DocumentProto.Builder documentBuilder, java.lang.String prefix)

Prepends prefix to all types and namespaces mentioned anywhere in documentBuilder.

public static java.lang.StringcreatePackagePrefix(java.lang.String packageName)

Creates prefix string for given package name.

public static java.lang.StringcreatePrefix(java.lang.String packageName, java.lang.String databaseName)

Creates prefix string for given package name and database name.

public static java.lang.StringgetDatabaseName(java.lang.String prefix)

Returns the database name that's contained within the prefix.

public static java.lang.StringgetPackageName(java.lang.String prefix)

Returns the package name that's contained within the prefix.

public static java.lang.StringgetPrefix(java.lang.String prefixedString)

Creates a package and database prefix string from the input string.

public static java.lang.StringremovePrefix(java.lang.String prefixedString)

Creates a string with the package and database prefix removed from the input string.

public static java.lang.StringremovePrefixesFromDocument(DocumentProto.Builder documentBuilder)

Removes any prefixes from types and namespaces mentioned anywhere in documentBuilder.

public static java.lang.StringremovePrefixesFromSchemaType(SchemaTypeConfigProto.Builder typeConfigBuilder)

Removes any prefixes from types mentioned anywhere in typeConfigBuilder.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final char DATABASE_DELIMITER

public static final char PACKAGE_DELIMITER

Methods

public static java.lang.String createPrefix(java.lang.String packageName, java.lang.String databaseName)

Creates prefix string for given package name and database name.

public static java.lang.String createPackagePrefix(java.lang.String packageName)

Creates prefix string for given package name.

public static java.lang.String getPackageName(java.lang.String prefix)

Returns the package name that's contained within the prefix.

Parameters:

prefix: Prefix string that contains the package name inside of it. The package name must be in the front of the string, and separated from the rest of the string by the PrefixUtil.PACKAGE_DELIMITER.

Returns:

Valid package name.

public static java.lang.String getDatabaseName(java.lang.String prefix)

Returns the database name that's contained within the prefix.

Parameters:

prefix: Prefix string that contains the database name inside of it. The database name must be between the PrefixUtil.PACKAGE_DELIMITER and PrefixUtil.DATABASE_DELIMITER

Returns:

Valid database name.

public static java.lang.String removePrefix(java.lang.String prefixedString)

Creates a string with the package and database prefix removed from the input string.

Parameters:

prefixedString: a string containing a package and database prefix.

Returns:

a string with the package and database prefix removed.

public static java.lang.String getPrefix(java.lang.String prefixedString)

Creates a package and database prefix string from the input string.

Parameters:

prefixedString: a string containing a package and database prefix.

Returns:

a string with the package and database prefix

public static void addPrefixToDocument(DocumentProto.Builder documentBuilder, java.lang.String prefix)

Prepends prefix to all types and namespaces mentioned anywhere in documentBuilder.

Parameters:

documentBuilder: The document to mutate
prefix: The prefix to add

public static java.lang.String removePrefixesFromDocument(DocumentProto.Builder documentBuilder)

Removes any prefixes from types and namespaces mentioned anywhere in documentBuilder.

Parameters:

documentBuilder: The document to mutate

Returns:

Prefix name that was removed from the document.

public static java.lang.String removePrefixesFromSchemaType(SchemaTypeConfigProto.Builder typeConfigBuilder)

Removes any prefixes from types mentioned anywhere in typeConfigBuilder.

Parameters:

typeConfigBuilder: The schema type to mutate

Returns:

Prefix name that was removed from the schema type.

Source

/*
 * Copyright 2021 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.util;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.appsearch.app.AppSearchResult;
import androidx.appsearch.exceptions.AppSearchException;

import com.google.android.icing.proto.DocumentProto;
import com.google.android.icing.proto.PropertyConfigProto;
import com.google.android.icing.proto.PropertyProto;
import com.google.android.icing.proto.SchemaTypeConfigProto;

/**
 * Provides utility functions for working with package + database prefixes.
 *
 * @exportToFramework:hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class PrefixUtil {
    private static final String TAG = "AppSearchPrefixUtil";

    @VisibleForTesting
    public static final char DATABASE_DELIMITER = '/';

    @VisibleForTesting
    public static final char PACKAGE_DELIMITER = '$';

    private PrefixUtil() {
    }

    /**
     * Creates prefix string for given package name and database name.
     */
    @NonNull
    public static String createPrefix(@NonNull String packageName, @NonNull String databaseName) {
        return packageName + PACKAGE_DELIMITER + databaseName + DATABASE_DELIMITER;
    }

    /**
     * Creates prefix string for given package name.
     */
    @NonNull
    public static String createPackagePrefix(@NonNull String packageName) {
        return packageName + PACKAGE_DELIMITER;
    }

    /**
     * Returns the package name that's contained within the {@code prefix}.
     *
     * @param prefix Prefix string that contains the package name inside of it. The package name
     *               must be in the front of the string, and separated from the rest of the
     *               string by the {@link #PACKAGE_DELIMITER}.
     * @return Valid package name.
     */
    @NonNull
    public static String getPackageName(@NonNull String prefix) {
        int delimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
        if (delimiterIndex == -1) {
            // This should never happen if we construct our prefixes properly
            Log.e(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
            return "";
        }
        return prefix.substring(0, delimiterIndex);
    }

    /**
     * Returns the database name that's contained within the {@code prefix}.
     *
     * @param prefix Prefix string that contains the database name inside of it. The database name
     *               must be between the {@link #PACKAGE_DELIMITER} and {@link #DATABASE_DELIMITER}
     * @return Valid database name.
     */
    @NonNull
    public static String getDatabaseName(@NonNull String prefix) {
        int packageDelimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
        if (packageDelimiterIndex == -1) {
            // This should never happen if we construct our prefixes properly
            Log.e(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
            return "";
        }
        int databaseDelimiterIndex = prefix.indexOf(DATABASE_DELIMITER, packageDelimiterIndex + 1);
        if (databaseDelimiterIndex == -1) {
            // This should never happen if we construct our prefixes properly
            Log.e(TAG, "Malformed prefix doesn't contain database delimiter: " + prefix);
            return "";
        }
        return prefix.substring(packageDelimiterIndex + 1, databaseDelimiterIndex);
    }

    /**
     * Creates a string with the package and database prefix removed from the input string.
     *
     * @param prefixedString a string containing a package and database prefix.
     * @return a string with the package and database prefix removed.
     * @throws AppSearchException if the prefixed value does not contain a valid database name.
     */
    @NonNull
    public static String removePrefix(@NonNull String prefixedString) throws AppSearchException {
        // The prefix is made up of the package, then the database. So we only need to find the
        // database cutoff.
        int delimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER);
        if (delimiterIndex == -1) {
            throw new AppSearchException(
                    AppSearchResult.RESULT_INTERNAL_ERROR,
                    "The prefixed value \"" + prefixedString + "\" doesn't contain a valid "
                            + "database name");
        }
        // Add 1 to include the char size of the DATABASE_DELIMITER
        return prefixedString.substring(delimiterIndex + 1);
    }

    /**
     * Creates a package and database prefix string from the input string.
     *
     * @param prefixedString a string containing a package and database prefix.
     * @return a string with the package and database prefix
     * @throws AppSearchException if the prefixed value does not contain a valid database name.
     */
    @NonNull
    public static String getPrefix(@NonNull String prefixedString) throws AppSearchException {
        int delimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER);
        if (delimiterIndex == -1) {
            throw new AppSearchException(
                    AppSearchResult.RESULT_INTERNAL_ERROR,
                    "The prefixed value \"" + prefixedString + "\" doesn't contain a valid "
                            + "database name");
        }
        // Add 1 to include the char size of the DATABASE_DELIMITER
        return prefixedString.substring(0, delimiterIndex + 1);
    }

    /**
     * Prepends {@code prefix} to all types and namespaces mentioned anywhere in
     * {@code documentBuilder}.
     *
     * @param documentBuilder The document to mutate
     * @param prefix          The prefix to add
     */
    public static void addPrefixToDocument(
            @NonNull DocumentProto.Builder documentBuilder,
            @NonNull String prefix) {
        // Rewrite the type name to include/remove the prefix.
        String newSchema = prefix + documentBuilder.getSchema();
        documentBuilder.setSchema(newSchema);

        // Rewrite the namespace to include/remove the prefix.
        documentBuilder.setNamespace(prefix + documentBuilder.getNamespace());

        // Recurse into derived documents
        for (int propertyIdx = 0;
                propertyIdx < documentBuilder.getPropertiesCount();
                propertyIdx++) {
            int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
            if (documentCount > 0) {
                PropertyProto.Builder propertyBuilder =
                        documentBuilder.getProperties(propertyIdx).toBuilder();
                for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
                    DocumentProto.Builder derivedDocumentBuilder =
                            propertyBuilder.getDocumentValues(documentIdx).toBuilder();
                    addPrefixToDocument(derivedDocumentBuilder, prefix);
                    propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
                }
                documentBuilder.setProperties(propertyIdx, propertyBuilder);
            }
        }
    }

    /**
     * Removes any prefixes from types and namespaces mentioned anywhere in
     * {@code documentBuilder}.
     *
     * @param documentBuilder The document to mutate
     * @return Prefix name that was removed from the document.
     * @throws AppSearchException if there are unexpected database prefixing errors.
     */
    @NonNull
    public static String removePrefixesFromDocument(@NonNull DocumentProto.Builder documentBuilder)
            throws AppSearchException {
        // Rewrite the type name and namespace to remove the prefix.
        String schemaPrefix = getPrefix(documentBuilder.getSchema());
        String namespacePrefix = getPrefix(documentBuilder.getNamespace());

        if (!schemaPrefix.equals(namespacePrefix)) {
            throw new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR, "Found unexpected"
                    + " multiple prefix names in document: " + schemaPrefix + ", "
                    + namespacePrefix);
        }

        documentBuilder.setSchema(removePrefix(documentBuilder.getSchema()));
        documentBuilder.setNamespace(removePrefix(documentBuilder.getNamespace()));

        // Recurse into derived documents
        for (int propertyIdx = 0;
                propertyIdx < documentBuilder.getPropertiesCount();
                propertyIdx++) {
            int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
            if (documentCount > 0) {
                PropertyProto.Builder propertyBuilder =
                        documentBuilder.getProperties(propertyIdx).toBuilder();
                for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
                    DocumentProto.Builder derivedDocumentBuilder =
                            propertyBuilder.getDocumentValues(documentIdx).toBuilder();
                    String nestedPrefix = removePrefixesFromDocument(derivedDocumentBuilder);
                    if (!nestedPrefix.equals(schemaPrefix)) {
                        throw new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR,
                                "Found unexpected multiple prefix names in document: "
                                        + schemaPrefix + ", " + nestedPrefix);
                    }
                    propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
                }
                documentBuilder.setProperties(propertyIdx, propertyBuilder);
            }
        }

        return schemaPrefix;
    }

    /**
     * Removes any prefixes from types mentioned anywhere in {@code typeConfigBuilder}.
     *
     * @param typeConfigBuilder The schema type to mutate
     * @return Prefix name that was removed from the schema type.
     * @throws AppSearchException if there are unexpected database prefixing errors.
     */
    @NonNull
    public static String removePrefixesFromSchemaType(
            @NonNull SchemaTypeConfigProto.Builder typeConfigBuilder)
            throws AppSearchException {
        String typePrefix = PrefixUtil.getPrefix(typeConfigBuilder.getSchemaType());
        // Rewrite SchemaProto.types.schema_type
        String newSchemaType =
                typeConfigBuilder.getSchemaType().substring(typePrefix.length());
        typeConfigBuilder.setSchemaType(newSchemaType);

        // Rewrite SchemaProto.types.properties.schema_type
        for (int propertyIdx = 0;
                propertyIdx < typeConfigBuilder.getPropertiesCount();
                propertyIdx++) {
            if (!typeConfigBuilder.getProperties(propertyIdx).getSchemaType().isEmpty()) {
                PropertyConfigProto.Builder propertyConfigBuilder =
                        typeConfigBuilder.getProperties(propertyIdx).toBuilder();
                String newPropertySchemaType = propertyConfigBuilder.getSchemaType()
                        .substring(typePrefix.length());
                propertyConfigBuilder.setSchemaType(newPropertySchemaType);
                typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
            }
        }

        // Rewrite SchemaProto.types.parent_types
        for (int parentTypeIdx = 0; parentTypeIdx < typeConfigBuilder.getParentTypesCount();
                parentTypeIdx++) {
            String newParentType = typeConfigBuilder.getParentTypes(parentTypeIdx).substring(
                    typePrefix.length());
            typeConfigBuilder.setParentTypes(parentTypeIdx, newParentType);
        }
        return typePrefix;
    }
}