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.util.Log; 22 23 import com.google.android.icing.proto.DocumentIndexingConfig; 24 import com.google.android.icing.proto.PropertyConfigProto; 25 import com.google.android.icing.proto.SchemaTypeConfigProto; 26 import com.google.android.icing.proto.SchemaTypeConfigProtoOrBuilder; 27 import com.google.android.icing.proto.StringIndexingConfig; 28 import com.google.android.icing.proto.TermMatchType; 29 30 import java.util.List; 31 import java.util.Objects; 32 33 /** 34 * Translates an {@link AppSearchSchema} into a {@link SchemaTypeConfigProto}. 35 * 36 * @hide 37 */ 38 public final class SchemaToProtoConverter { 39 private static final String TAG = "AppSearchSchemaToProtoC"; 40 SchemaToProtoConverter()41 private SchemaToProtoConverter() {} 42 43 /** 44 * Converts an {@link android.app.appsearch.AppSearchSchema} into a {@link 45 * SchemaTypeConfigProto}. 46 */ 47 @NonNull toSchemaTypeConfigProto( @onNull AppSearchSchema schema, int version)48 public static SchemaTypeConfigProto toSchemaTypeConfigProto( 49 @NonNull AppSearchSchema schema, int version) { 50 Objects.requireNonNull(schema); 51 SchemaTypeConfigProto.Builder protoBuilder = 52 SchemaTypeConfigProto.newBuilder() 53 .setSchemaType(schema.getSchemaType()) 54 .setVersion(version); 55 List<AppSearchSchema.PropertyConfig> properties = schema.getProperties(); 56 for (int i = 0; i < properties.size(); i++) { 57 PropertyConfigProto propertyProto = toPropertyConfigProto(properties.get(i)); 58 protoBuilder.addProperties(propertyProto); 59 } 60 return protoBuilder.build(); 61 } 62 63 @NonNull toPropertyConfigProto( @onNull AppSearchSchema.PropertyConfig property)64 private static PropertyConfigProto toPropertyConfigProto( 65 @NonNull AppSearchSchema.PropertyConfig property) { 66 Objects.requireNonNull(property); 67 PropertyConfigProto.Builder builder = 68 PropertyConfigProto.newBuilder().setPropertyName(property.getName()); 69 70 // Set dataType 71 @AppSearchSchema.PropertyConfig.DataType int dataType = property.getDataType(); 72 PropertyConfigProto.DataType.Code dataTypeProto = 73 PropertyConfigProto.DataType.Code.forNumber(dataType); 74 if (dataTypeProto == null) { 75 throw new IllegalArgumentException("Invalid dataType: " + dataType); 76 } 77 builder.setDataType(dataTypeProto); 78 79 // Set cardinality 80 @AppSearchSchema.PropertyConfig.Cardinality int cardinality = property.getCardinality(); 81 PropertyConfigProto.Cardinality.Code cardinalityProto = 82 PropertyConfigProto.Cardinality.Code.forNumber(cardinality); 83 if (cardinalityProto == null) { 84 throw new IllegalArgumentException("Invalid cardinality: " + dataType); 85 } 86 builder.setCardinality(cardinalityProto); 87 88 if (property instanceof AppSearchSchema.StringPropertyConfig) { 89 AppSearchSchema.StringPropertyConfig stringProperty = 90 (AppSearchSchema.StringPropertyConfig) property; 91 StringIndexingConfig stringIndexingConfig = 92 StringIndexingConfig.newBuilder() 93 .setTermMatchType( 94 convertTermMatchTypeToProto(stringProperty.getIndexingType())) 95 .setTokenizerType( 96 convertTokenizerTypeToProto(stringProperty.getTokenizerType())) 97 .build(); 98 builder.setStringIndexingConfig(stringIndexingConfig); 99 100 } else if (property instanceof AppSearchSchema.DocumentPropertyConfig) { 101 AppSearchSchema.DocumentPropertyConfig documentProperty = 102 (AppSearchSchema.DocumentPropertyConfig) property; 103 builder.setSchemaType(documentProperty.getSchemaType()) 104 .setDocumentIndexingConfig( 105 DocumentIndexingConfig.newBuilder() 106 .setIndexNestedProperties( 107 documentProperty.shouldIndexNestedProperties())); 108 } 109 return builder.build(); 110 } 111 112 /** 113 * Converts a {@link SchemaTypeConfigProto} into an {@link 114 * android.app.appsearch.AppSearchSchema}. 115 */ 116 @NonNull toAppSearchSchema(@onNull SchemaTypeConfigProtoOrBuilder proto)117 public static AppSearchSchema toAppSearchSchema(@NonNull SchemaTypeConfigProtoOrBuilder proto) { 118 Objects.requireNonNull(proto); 119 AppSearchSchema.Builder builder = new AppSearchSchema.Builder(proto.getSchemaType()); 120 List<PropertyConfigProto> properties = proto.getPropertiesList(); 121 for (int i = 0; i < properties.size(); i++) { 122 AppSearchSchema.PropertyConfig propertyConfig = toPropertyConfig(properties.get(i)); 123 builder.addProperty(propertyConfig); 124 } 125 return builder.build(); 126 } 127 128 @NonNull toPropertyConfig( @onNull PropertyConfigProto proto)129 private static AppSearchSchema.PropertyConfig toPropertyConfig( 130 @NonNull PropertyConfigProto proto) { 131 Objects.requireNonNull(proto); 132 switch (proto.getDataType()) { 133 case STRING: 134 return toStringPropertyConfig(proto); 135 case INT64: 136 return new AppSearchSchema.LongPropertyConfig.Builder(proto.getPropertyName()) 137 .setCardinality(proto.getCardinality().getNumber()) 138 .build(); 139 case DOUBLE: 140 return new AppSearchSchema.DoublePropertyConfig.Builder(proto.getPropertyName()) 141 .setCardinality(proto.getCardinality().getNumber()) 142 .build(); 143 case BOOLEAN: 144 return new AppSearchSchema.BooleanPropertyConfig.Builder(proto.getPropertyName()) 145 .setCardinality(proto.getCardinality().getNumber()) 146 .build(); 147 case BYTES: 148 return new AppSearchSchema.BytesPropertyConfig.Builder(proto.getPropertyName()) 149 .setCardinality(proto.getCardinality().getNumber()) 150 .build(); 151 case DOCUMENT: 152 return toDocumentPropertyConfig(proto); 153 default: 154 throw new IllegalArgumentException("Invalid dataType: " + proto.getDataType()); 155 } 156 } 157 158 @NonNull toStringPropertyConfig( @onNull PropertyConfigProto proto)159 private static AppSearchSchema.StringPropertyConfig toStringPropertyConfig( 160 @NonNull PropertyConfigProto proto) { 161 AppSearchSchema.StringPropertyConfig.Builder builder = 162 new AppSearchSchema.StringPropertyConfig.Builder(proto.getPropertyName()) 163 .setCardinality(proto.getCardinality().getNumber()) 164 .setTokenizerType( 165 proto.getStringIndexingConfig().getTokenizerType().getNumber()); 166 167 // Set indexingType 168 TermMatchType.Code termMatchTypeProto = proto.getStringIndexingConfig().getTermMatchType(); 169 builder.setIndexingType(convertTermMatchTypeFromProto(termMatchTypeProto)); 170 171 return builder.build(); 172 } 173 174 @NonNull toDocumentPropertyConfig( @onNull PropertyConfigProto proto)175 private static AppSearchSchema.DocumentPropertyConfig toDocumentPropertyConfig( 176 @NonNull PropertyConfigProto proto) { 177 return new AppSearchSchema.DocumentPropertyConfig.Builder( 178 proto.getPropertyName(), proto.getSchemaType()) 179 .setCardinality(proto.getCardinality().getNumber()) 180 .setShouldIndexNestedProperties( 181 proto.getDocumentIndexingConfig().getIndexNestedProperties()) 182 .build(); 183 } 184 185 @NonNull convertTermMatchTypeToProto( @ppSearchSchema.StringPropertyConfig.IndexingType int indexingType)186 private static TermMatchType.Code convertTermMatchTypeToProto( 187 @AppSearchSchema.StringPropertyConfig.IndexingType int indexingType) { 188 switch (indexingType) { 189 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE: 190 return TermMatchType.Code.UNKNOWN; 191 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS: 192 return TermMatchType.Code.EXACT_ONLY; 193 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES: 194 return TermMatchType.Code.PREFIX; 195 default: 196 throw new IllegalArgumentException("Invalid indexingType: " + indexingType); 197 } 198 } 199 200 @AppSearchSchema.StringPropertyConfig.IndexingType convertTermMatchTypeFromProto(@onNull TermMatchType.Code termMatchType)201 private static int convertTermMatchTypeFromProto(@NonNull TermMatchType.Code termMatchType) { 202 switch (termMatchType) { 203 case UNKNOWN: 204 return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE; 205 case EXACT_ONLY: 206 return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS; 207 case PREFIX: 208 return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES; 209 default: 210 // Avoid crashing in the 'read' path; we should try to interpret the document to the 211 // extent possible. 212 Log.w(TAG, "Invalid indexingType: " + termMatchType.getNumber()); 213 return AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE; 214 } 215 } 216 217 @NonNull convertTokenizerTypeToProto( @ppSearchSchema.StringPropertyConfig.TokenizerType int tokenizerType)218 private static StringIndexingConfig.TokenizerType.Code convertTokenizerTypeToProto( 219 @AppSearchSchema.StringPropertyConfig.TokenizerType int tokenizerType) { 220 StringIndexingConfig.TokenizerType.Code tokenizerTypeProto = 221 StringIndexingConfig.TokenizerType.Code.forNumber(tokenizerType); 222 if (tokenizerTypeProto == null) { 223 throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType); 224 } 225 return tokenizerTypeProto; 226 } 227 } 228