• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.appsearch.external.localstorage.converter;
18 
19 import android.annotation.NonNull;
20 import android.app.appsearch.AppSearchSchema;
21 import android.app.appsearch.GenericDocument;
22 
23 import com.google.android.icing.proto.DocumentProto;
24 import com.google.android.icing.proto.DocumentProtoOrBuilder;
25 import com.google.android.icing.proto.PropertyProto;
26 import com.google.android.icing.proto.SchemaTypeConfigProto;
27 import com.google.protobuf.ByteString;
28 
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.Map;
32 import java.util.Objects;
33 
34 /**
35  * Translates a {@link GenericDocument} into a {@link DocumentProto}.
36  *
37  * @hide
38  */
39 public final class GenericDocumentToProtoConverter {
40     private static final String[] EMPTY_STRING_ARRAY = new String[0];
41     private static final long[] EMPTY_LONG_ARRAY = new long[0];
42     private static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
43     private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
44     private static final byte[][] EMPTY_BYTES_ARRAY = new byte[0][0];
45     private static final GenericDocument[] EMPTY_DOCUMENT_ARRAY = new GenericDocument[0];
46 
GenericDocumentToProtoConverter()47     private GenericDocumentToProtoConverter() {}
48 
49     /** Converts a {@link GenericDocument} into a {@link DocumentProto}. */
50     @NonNull
51     @SuppressWarnings("unchecked")
toDocumentProto(@onNull GenericDocument document)52     public static DocumentProto toDocumentProto(@NonNull GenericDocument document) {
53         Objects.requireNonNull(document);
54         DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder();
55         mProtoBuilder
56                 .setUri(document.getId())
57                 .setSchema(document.getSchemaType())
58                 .setNamespace(document.getNamespace())
59                 .setScore(document.getScore())
60                 .setTtlMs(document.getTtlMillis())
61                 .setCreationTimestampMs(document.getCreationTimestampMillis());
62         ArrayList<String> keys = new ArrayList<>(document.getPropertyNames());
63         Collections.sort(keys);
64         for (int i = 0; i < keys.size(); i++) {
65             String name = keys.get(i);
66             PropertyProto.Builder propertyProto = PropertyProto.newBuilder().setName(name);
67             Object property = document.getProperty(name);
68             if (property instanceof String[]) {
69                 String[] stringValues = (String[]) property;
70                 for (int j = 0; j < stringValues.length; j++) {
71                     propertyProto.addStringValues(stringValues[j]);
72                 }
73             } else if (property instanceof long[]) {
74                 long[] longValues = (long[]) property;
75                 for (int j = 0; j < longValues.length; j++) {
76                     propertyProto.addInt64Values(longValues[j]);
77                 }
78             } else if (property instanceof double[]) {
79                 double[] doubleValues = (double[]) property;
80                 for (int j = 0; j < doubleValues.length; j++) {
81                     propertyProto.addDoubleValues(doubleValues[j]);
82                 }
83             } else if (property instanceof boolean[]) {
84                 boolean[] booleanValues = (boolean[]) property;
85                 for (int j = 0; j < booleanValues.length; j++) {
86                     propertyProto.addBooleanValues(booleanValues[j]);
87                 }
88             } else if (property instanceof byte[][]) {
89                 byte[][] bytesValues = (byte[][]) property;
90                 for (int j = 0; j < bytesValues.length; j++) {
91                     propertyProto.addBytesValues(ByteString.copyFrom(bytesValues[j]));
92                 }
93             } else if (property instanceof GenericDocument[]) {
94                 GenericDocument[] documentValues = (GenericDocument[]) property;
95                 for (int j = 0; j < documentValues.length; j++) {
96                     DocumentProto proto = toDocumentProto(documentValues[j]);
97                     propertyProto.addDocumentValues(proto);
98                 }
99             } else if (property == null) {
100                 throw new IllegalStateException(
101                         String.format("Property \"%s\" doesn't have any value!", name));
102             } else {
103                 throw new IllegalStateException(
104                         String.format(
105                                 "Property \"%s\" has unsupported value type %s",
106                                 name, property.getClass().toString()));
107             }
108             mProtoBuilder.addProperties(propertyProto);
109         }
110         return mProtoBuilder.build();
111     }
112 
113     /**
114      * Converts a {@link DocumentProto} into a {@link GenericDocument}.
115      *
116      * <p>In the case that the {@link DocumentProto} object proto has no values set, the converter
117      * searches for the matching property name in the {@link SchemaTypeConfigProto} object for the
118      * document, and infers the correct default value to set for the empty property based on the
119      * data type of the property defined by the schema type.
120      *
121      * @param proto the document to convert to a {@link GenericDocument} instance. The document
122      *     proto should have its package + database prefix stripped from its fields.
123      * @param prefix the package + database prefix used searching the {@code schemaTypeMap}.
124      * @param schemaTypeMap map of prefixed schema type to {@link SchemaTypeConfigProto}, used for
125      *     looking up the default empty value to set for a document property that has all empty
126      *     values.
127      */
128     @NonNull
toGenericDocument( @onNull DocumentProtoOrBuilder proto, @NonNull String prefix, @NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap)129     public static GenericDocument toGenericDocument(
130             @NonNull DocumentProtoOrBuilder proto,
131             @NonNull String prefix,
132             @NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap) {
133         Objects.requireNonNull(proto);
134         GenericDocument.Builder<?> documentBuilder =
135                 new GenericDocument.Builder<>(
136                                 proto.getNamespace(), proto.getUri(), proto.getSchema())
137                         .setScore(proto.getScore())
138                         .setTtlMillis(proto.getTtlMs())
139                         .setCreationTimestampMillis(proto.getCreationTimestampMs());
140         String prefixedSchemaType = prefix + proto.getSchema();
141 
142         for (int i = 0; i < proto.getPropertiesCount(); i++) {
143             PropertyProto property = proto.getProperties(i);
144             String name = property.getName();
145             if (property.getStringValuesCount() > 0) {
146                 String[] values = new String[property.getStringValuesCount()];
147                 for (int j = 0; j < values.length; j++) {
148                     values[j] = property.getStringValues(j);
149                 }
150                 documentBuilder.setPropertyString(name, values);
151             } else if (property.getInt64ValuesCount() > 0) {
152                 long[] values = new long[property.getInt64ValuesCount()];
153                 for (int j = 0; j < values.length; j++) {
154                     values[j] = property.getInt64Values(j);
155                 }
156                 documentBuilder.setPropertyLong(name, values);
157             } else if (property.getDoubleValuesCount() > 0) {
158                 double[] values = new double[property.getDoubleValuesCount()];
159                 for (int j = 0; j < values.length; j++) {
160                     values[j] = property.getDoubleValues(j);
161                 }
162                 documentBuilder.setPropertyDouble(name, values);
163             } else if (property.getBooleanValuesCount() > 0) {
164                 boolean[] values = new boolean[property.getBooleanValuesCount()];
165                 for (int j = 0; j < values.length; j++) {
166                     values[j] = property.getBooleanValues(j);
167                 }
168                 documentBuilder.setPropertyBoolean(name, values);
169             } else if (property.getBytesValuesCount() > 0) {
170                 byte[][] values = new byte[property.getBytesValuesCount()][];
171                 for (int j = 0; j < values.length; j++) {
172                     values[j] = property.getBytesValues(j).toByteArray();
173                 }
174                 documentBuilder.setPropertyBytes(name, values);
175             } else if (property.getDocumentValuesCount() > 0) {
176                 GenericDocument[] values = new GenericDocument[property.getDocumentValuesCount()];
177                 for (int j = 0; j < values.length; j++) {
178                     values[j] =
179                             toGenericDocument(property.getDocumentValues(j), prefix, schemaTypeMap);
180                 }
181                 documentBuilder.setPropertyDocument(name, values);
182             } else {
183                 // TODO(b/184966497): Optimize by caching PropertyConfigProto
184                 SchemaTypeConfigProto schema =
185                         Objects.requireNonNull(schemaTypeMap.get(prefixedSchemaType));
186                 setEmptyProperty(name, documentBuilder, schema);
187             }
188         }
189         return documentBuilder.build();
190     }
191 
setEmptyProperty( @onNull String propertyName, @NonNull GenericDocument.Builder<?> documentBuilder, @NonNull SchemaTypeConfigProto schema)192     private static void setEmptyProperty(
193             @NonNull String propertyName,
194             @NonNull GenericDocument.Builder<?> documentBuilder,
195             @NonNull SchemaTypeConfigProto schema) {
196         @AppSearchSchema.PropertyConfig.DataType int dataType = 0;
197         for (int i = 0; i < schema.getPropertiesCount(); ++i) {
198             if (propertyName.equals(schema.getProperties(i).getPropertyName())) {
199                 dataType = schema.getProperties(i).getDataType().getNumber();
200                 break;
201             }
202         }
203 
204         switch (dataType) {
205             case AppSearchSchema.PropertyConfig.DATA_TYPE_STRING:
206                 documentBuilder.setPropertyString(propertyName, EMPTY_STRING_ARRAY);
207                 break;
208             case AppSearchSchema.PropertyConfig.DATA_TYPE_LONG:
209                 documentBuilder.setPropertyLong(propertyName, EMPTY_LONG_ARRAY);
210                 break;
211             case AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE:
212                 documentBuilder.setPropertyDouble(propertyName, EMPTY_DOUBLE_ARRAY);
213                 break;
214             case AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN:
215                 documentBuilder.setPropertyBoolean(propertyName, EMPTY_BOOLEAN_ARRAY);
216                 break;
217             case AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES:
218                 documentBuilder.setPropertyBytes(propertyName, EMPTY_BYTES_ARRAY);
219                 break;
220             case AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT:
221                 documentBuilder.setPropertyDocument(propertyName, EMPTY_DOCUMENT_ARRAY);
222                 break;
223             default:
224                 throw new IllegalStateException("Unknown type of value: " + propertyName);
225         }
226     }
227 }
228