• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 package com.android.server.appsearch.appsindexer;
17 
18 import android.annotation.NonNull;
19 import android.app.appsearch.AppSearchSchema;
20 import android.app.appsearch.AppSearchSchema.DocumentPropertyConfig;
21 import android.app.appsearch.AppSearchSchema.PropertyConfig;
22 import android.app.appsearch.GenericDocument;
23 import android.app.appsearch.util.LogUtil;
24 import android.content.pm.PackageManager;
25 import android.content.res.AssetManager;
26 import android.util.ArrayMap;
27 import android.util.Log;
28 
29 import com.android.server.appsearch.appsindexer.appsearchtypes.AppFunctionDocument;
30 import com.android.server.appsearch.appsindexer.appsearchtypes.AppFunctionStaticMetadata;
31 
32 import org.xmlpull.v1.XmlPullParser;
33 import org.xmlpull.v1.XmlPullParserException;
34 import org.xmlpull.v1.XmlPullParserFactory;
35 
36 import java.io.IOException;
37 import java.io.InputStreamReader;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Objects;
43 
44 /**
45  * This class parses static metadata about App Functions from an XML file located within an app's
46  * assets.
47  */
48 public class AppFunctionDocumentParserImpl implements AppFunctionDocumentParser {
49     private static final String TAG = "AppSearchMetadataParser";
50     private static final String XML_TAG_APPFUNCTION = "appfunction";
51     private static final String XML_TAG_APPFUNCTIONS_ROOT = "appfunctions";
52     private static final String XML_TAG_ID = "id";
53     private static final String SNAKE_CASE_SEPARATOR = "_";
54 
55     @NonNull private final String mIndexerPackageName;
56     private final int mMaxAppFunctions;
57 
58     /**
59      * @param indexerPackageName the name of the package performing the indexing. This should be the
60      *     same as the package running the apps indexer.
61      * @param config the app indexer config used to enforce various limits during parsing.
62      */
AppFunctionDocumentParserImpl( @onNull String indexerPackageName, AppsIndexerConfig config)63     public AppFunctionDocumentParserImpl(
64             @NonNull String indexerPackageName, AppsIndexerConfig config) {
65         mIndexerPackageName = Objects.requireNonNull(indexerPackageName);
66         mMaxAppFunctions = config.getMaxAppFunctionsPerPackage();
67     }
68 
69     // TODO(b/367410454): Remove this method once enable_apps_indexer_incremental_put flag is
70     //  rolled out
71     @NonNull
72     @Override
parse( @onNull PackageManager packageManager, @NonNull String packageName, @NonNull String assetFilePath)73     public List<AppFunctionStaticMetadata> parse(
74             @NonNull PackageManager packageManager,
75             @NonNull String packageName,
76             @NonNull String assetFilePath) {
77         Objects.requireNonNull(packageManager);
78         Objects.requireNonNull(packageName);
79         Objects.requireNonNull(assetFilePath);
80         try {
81             return parseAppFunctions(
82                     initializeParser(packageManager, packageName, assetFilePath), packageName);
83         } catch (Exception ex) {
84             // The code parses an XML file from another app's assets, using a broad try-catch to
85             // handle potential errors since the XML structure might be unpredictable.
86             Log.e(
87                     TAG,
88                     String.format(
89                             "Failed to parse XML from package '%s', asset file '%s'",
90                             packageName, assetFilePath),
91                     ex);
92         }
93         return Collections.emptyList();
94     }
95 
96     @NonNull
97     @Override
parseIntoMap( @onNull PackageManager packageManager, @NonNull String packageName, @NonNull String assetFilePath)98     public Map<String, AppFunctionStaticMetadata> parseIntoMap(
99             @NonNull PackageManager packageManager,
100             @NonNull String packageName,
101             @NonNull String assetFilePath) {
102         Objects.requireNonNull(packageManager);
103         Objects.requireNonNull(packageName);
104         Objects.requireNonNull(assetFilePath);
105         try {
106             return parseAppFunctionsIntoMap(
107                     initializeParser(packageManager, packageName, assetFilePath), packageName);
108         } catch (Exception ex) {
109             // The code parses an XML file from another app's assets, using a broad try-catch to
110             // handle potential errors since the XML structure might be unpredictable.
111             Log.e(
112                     TAG,
113                     String.format(
114                             "Failed to parse XML from package '%s', asset file '%s'",
115                             packageName, assetFilePath),
116                     ex);
117         }
118         return Collections.emptyMap();
119     }
120 
121     /**
122      * Initializes an {@link XmlPullParser} to parse xml based on the packageName and assetFilePath.
123      */
124     @NonNull
initializeParser( @onNull PackageManager packageManager, @NonNull String packageName, @NonNull String assetFilePath)125     private XmlPullParser initializeParser(
126             @NonNull PackageManager packageManager,
127             @NonNull String packageName,
128             @NonNull String assetFilePath)
129             throws XmlPullParserException, PackageManager.NameNotFoundException, IOException {
130         Objects.requireNonNull(packageManager);
131         Objects.requireNonNull(packageName);
132         Objects.requireNonNull(assetFilePath);
133         XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
134         factory.setNamespaceAware(true);
135         XmlPullParser parser = factory.newPullParser();
136         AssetManager assetManager =
137                 packageManager.getResourcesForApplication(packageName).getAssets();
138         parser.setInput(new InputStreamReader(assetManager.open(assetFilePath)));
139         return parser;
140     }
141 
142     // TODO(b/367410454): Remove this method once enable_apps_indexer_incremental_put flag is
143     //  rolled out
144     /**
145      * Parses a sequence of `appfunction` elements from the XML into a list of {@link
146      * AppFunctionStaticMetadata}.
147      *
148      * @param parser the XmlPullParser positioned at the start of the xml file
149      */
150     @NonNull
parseAppFunctions( @onNull XmlPullParser parser, @NonNull String packageName)151     private List<AppFunctionStaticMetadata> parseAppFunctions(
152             @NonNull XmlPullParser parser, @NonNull String packageName)
153             throws XmlPullParserException, IOException {
154         List<AppFunctionStaticMetadata> appFunctions = new ArrayList<>();
155 
156         int eventType = parser.getEventType();
157 
158         while (eventType != XmlPullParser.END_DOCUMENT) {
159             String tagName = parser.getName();
160             if (eventType == XmlPullParser.START_TAG && XML_TAG_APPFUNCTION.equals(tagName)) {
161                 AppFunctionStaticMetadata appFunction = parseAppFunction(parser, packageName);
162                 appFunctions.add(appFunction);
163                 if (appFunctions.size() >= mMaxAppFunctions) {
164                     Log.d(TAG, "Exceeding the max number of app functions: " + packageName);
165                     return appFunctions;
166                 }
167             }
168             eventType = parser.next();
169         }
170         return appFunctions;
171     }
172 
173     /**
174      * Parses a sequence of `appfunction` elements from the XML into a map of function ids to their
175      * corresponding {@link AppFunctionStaticMetadata}.
176      *
177      * @param parser the XmlPullParser positioned at the start of the xml file
178      */
179     @NonNull
parseAppFunctionsIntoMap( @onNull XmlPullParser parser, @NonNull String packageName)180     private Map<String, AppFunctionStaticMetadata> parseAppFunctionsIntoMap(
181             @NonNull XmlPullParser parser, @NonNull String packageName)
182             throws XmlPullParserException, IOException {
183         Map<String, AppFunctionStaticMetadata> appFunctions = new ArrayMap<>();
184 
185         int eventType = parser.getEventType();
186 
187         while (eventType != XmlPullParser.END_DOCUMENT) {
188             String tagName = parser.getName();
189             if (eventType == XmlPullParser.START_TAG && XML_TAG_APPFUNCTION.equals(tagName)) {
190                 AppFunctionStaticMetadata appFunction = parseAppFunction(parser, packageName);
191                 appFunctions.put(appFunction.getId(), appFunction);
192                 if (appFunctions.size() >= mMaxAppFunctions) {
193                     Log.d(TAG, "Exceeding the max number of app functions: " + packageName);
194                     return appFunctions;
195                 }
196             }
197             eventType = parser.next();
198         }
199         return appFunctions;
200     }
201 
202     /**
203      * Parses a single `appfunction` element from the XML into an {@link AppFunctionStaticMetadata}
204      * object.
205      *
206      * @param parser the XmlPullParser positioned at the start of an `appfunction` element.
207      * @return an AppFunction object populated with the data from the XML.
208      */
209     @NonNull
parseAppFunction( @onNull XmlPullParser parser, @NonNull String packageName)210     private AppFunctionStaticMetadata parseAppFunction(
211             @NonNull XmlPullParser parser, @NonNull String packageName)
212             throws XmlPullParserException, IOException {
213         String functionId = null;
214         String schemaName = null;
215         Long schemaVersion = null;
216         String schemaCategory = null;
217         Boolean enabledByDefault = null;
218         Integer displayNameStringRes = null;
219         Boolean restrictCallersWithExecuteAppFunctions = null;
220         int eventType = parser.getEventType();
221         while (!(eventType == XmlPullParser.END_TAG
222                 && XML_TAG_APPFUNCTION.equals(parser.getName()))) {
223             if (eventType == XmlPullParser.START_TAG
224                     && !XML_TAG_APPFUNCTION.equals(parser.getName())) {
225                 String tagName = parser.getName();
226                 switch (tagName) {
227                     case "function_id":
228                         functionId = parser.nextText().trim();
229                         break;
230                     case "schema_name":
231                         schemaName = parser.nextText().trim();
232                         break;
233                     case "schema_version":
234                         schemaVersion = Long.parseLong(parser.nextText().trim());
235                         break;
236                     case "schema_category":
237                         schemaCategory = parser.nextText().trim();
238                         break;
239                     case "enabled_by_default":
240                         enabledByDefault = Boolean.parseBoolean(parser.nextText().trim());
241                         break;
242                     case "restrict_callers_with_execute_app_functions":
243                         restrictCallersWithExecuteAppFunctions =
244                                 Boolean.parseBoolean(parser.nextText().trim());
245                         break;
246                     case "display_name_string_res":
247                         displayNameStringRes = Integer.parseInt(parser.nextText().trim());
248                         break;
249                 }
250             }
251             eventType = parser.next();
252         }
253 
254         if (functionId == null) {
255             throw new XmlPullParserException("parseAppFunction: Missing functionId in the xml.");
256         }
257         AppFunctionStaticMetadata.Builder builder =
258                 new AppFunctionStaticMetadata.Builder(packageName, functionId, mIndexerPackageName);
259         if (schemaName != null) {
260             builder.setSchemaName(schemaName);
261         }
262         if (schemaVersion != null) {
263             builder.setSchemaVersion(schemaVersion);
264         }
265         if (schemaCategory != null) {
266             builder.setSchemaCategory(schemaCategory);
267         }
268         if (enabledByDefault != null) {
269             builder.setEnabledByDefault(enabledByDefault);
270         }
271         if (restrictCallersWithExecuteAppFunctions != null) {
272             builder.setRestrictCallersWithExecuteAppFunctions(
273                     restrictCallersWithExecuteAppFunctions);
274         }
275         if (displayNameStringRes != null) {
276             builder.setDisplayNameStringRes(displayNameStringRes);
277         }
278         return builder.build();
279     }
280 
281     @NonNull
282     @Override
parseIntoMapForGivenSchemas( @onNull PackageManager packageManager, @NonNull String packageName, @NonNull String assetFilePath, @NonNull Map<String, AppSearchSchema> schemas)283     public Map<String, AppFunctionDocument> parseIntoMapForGivenSchemas(
284             @NonNull PackageManager packageManager,
285             @NonNull String packageName,
286             @NonNull String assetFilePath,
287             @NonNull Map<String, AppSearchSchema> schemas) {
288         Objects.requireNonNull(packageManager);
289         Objects.requireNonNull(packageName);
290         Objects.requireNonNull(assetFilePath);
291         Objects.requireNonNull(schemas);
292 
293         try {
294             return parseAppFunctionsIntoMapForGivenSchemas(
295                     initializeParser(packageManager, packageName, assetFilePath),
296                     packageName,
297                     schemas);
298         } catch (Exception ex) {
299             // The code parses an XML file from another app's assets, using a broad try-catch to
300             // handle potential errors since the XML structure might be unpredictable.
301             Log.e(
302                     TAG,
303                     String.format(
304                             "Failed to parse XML from package '%s', asset file '%s'",
305                             packageName, assetFilePath),
306                     ex);
307         }
308         return Collections.emptyMap();
309     }
310 
311     @NonNull
parseAppFunctionsIntoMapForGivenSchemas( @onNull XmlPullParser parser, @NonNull String packageName, @NonNull Map<String, AppSearchSchema> schemas)312     private Map<String, AppFunctionDocument> parseAppFunctionsIntoMapForGivenSchemas(
313             @NonNull XmlPullParser parser,
314             @NonNull String packageName,
315             @NonNull Map<String, AppSearchSchema> schemas)
316             throws XmlPullParserException, IOException {
317         Objects.requireNonNull(parser);
318         Objects.requireNonNull(packageName);
319         Objects.requireNonNull(schemas);
320 
321         Map<String, AppFunctionDocument> appFnMetadatas = new ArrayMap<>();
322 
323         Map<String, PropertyConfig> qualifiedPropertyNamesToPropertyConfig =
324                 buildQualifiedPropertyNameToPropertyConfigMap(schemas);
325 
326         int eventType = parser.getEventType();
327 
328         while (eventType != XmlPullParser.END_DOCUMENT) {
329             String tagName = parser.getName();
330             // In previous document formats <appfunction> XML tag was used for denoting
331             // AppFunctionStaticMetadata type.
332             String schemaType =
333                     XML_TAG_APPFUNCTION.equals(tagName)
334                             ? AppFunctionStaticMetadata.SCHEMA_TYPE
335                             : tagName;
336             String schemaNameForPackage =
337                     AppFunctionDocument.getSchemaNameForPackage(packageName, schemaType);
338             if (eventType == XmlPullParser.START_TAG && schemas.containsKey(schemaNameForPackage)) {
339                 // Id of the document will be set after parsing the value from xml.
340                 AppFunctionDocument.Builder appFnDocBuilder =
341                         new AppFunctionDocument.Builder(
342                                 packageName, "", mIndexerPackageName, schemaType);
343                 buildGenericDocumentFromXmlElement(
344                         parser,
345                         packageName,
346                         schemaNameForPackage,
347                         qualifiedPropertyNamesToPropertyConfig,
348                         appFnDocBuilder);
349 
350                 AppFunctionDocument appFunctionDocument = appFnDocBuilder.build();
351                 appFnMetadatas.put(appFunctionDocument.getId(), appFunctionDocument);
352                 if (appFnMetadatas.size() >= mMaxAppFunctions) {
353                     if (LogUtil.DEBUG) {
354                         Log.d(TAG, "Exceeding the max number of app functions: " + packageName);
355                     }
356                     return appFnMetadatas;
357                 }
358             }
359             eventType = parser.next();
360         }
361         return appFnMetadatas;
362     }
363 
364     /**
365      * Tries to parse a single XML element and populate the {@link GenericDocument.Builder} object
366      * recursively.
367      *
368      * <p>When this function is called the parser should point to the xml element that marks the
369      * beginning of the {@link GenericDocument}, and would point to the end tag of the corresponding
370      * doc once this function completes.
371      *
372      * @param parser the XmlPullParser positioned at the start of an XML element.
373      * @param packageName the package name of the app that owns the XML element.
374      * @param schemaType the type of the schema that the XML element belongs to.
375      * @param qualifiedPropertyNamesToPropertyConfig the mapping of qualified property names to
376      *     their corresponding {@link PropertyConfig} objects.
377      * @param docBuilder {@link GenericDocument.Builder} object to populate with the data from the
378      *     XML element.
379      * @throws XmlPullParserException if the XML element is malformed.
380      */
buildGenericDocumentFromXmlElement( @onNull XmlPullParser parser, @NonNull String packageName, @NonNull String schemaType, @NonNull Map<String, PropertyConfig> qualifiedPropertyNamesToPropertyConfig, @NonNull GenericDocument.Builder docBuilder)381     private static void buildGenericDocumentFromXmlElement(
382             @NonNull XmlPullParser parser,
383             @NonNull String packageName,
384             @NonNull String schemaType,
385             @NonNull Map<String, PropertyConfig> qualifiedPropertyNamesToPropertyConfig,
386             @NonNull GenericDocument.Builder docBuilder)
387             throws XmlPullParserException, IOException {
388         Objects.requireNonNull(parser);
389         Objects.requireNonNull(packageName);
390         Objects.requireNonNull(qualifiedPropertyNamesToPropertyConfig);
391 
392         Map<String, List<String>> primitivePropertyValues = new ArrayMap<>();
393         Map<String, List<GenericDocument>> nestedDocumentValues = new ArrayMap<>();
394         String startTag = parser.getName();
395         String currentPropertyPath;
396         boolean wasDocIdSet = false;
397 
398         // Skip the current tag that marks the beginning of the current document.
399         parser.next();
400 
401         while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
402             switch (parser.getEventType()) {
403                 case XmlPullParser.START_TAG:
404                     currentPropertyPath =
405                             createQualifiedPropertyName(
406                                     schemaType,
407                                     toLowerCamelCase(parser.getName(), SNAKE_CASE_SEPARATOR));
408                     PropertyConfig propertyConfig =
409                             qualifiedPropertyNamesToPropertyConfig.get(currentPropertyPath);
410                     if (propertyConfig instanceof DocumentPropertyConfig) {
411                         String nestedSchemaType =
412                                 ((DocumentPropertyConfig) propertyConfig).getSchemaType();
413                         GenericDocument.Builder nestedDoc =
414                                 new GenericDocument.Builder(
415                                         AppFunctionStaticMetadata.APP_FUNCTION_NAMESPACE,
416                                         "",
417                                         nestedSchemaType);
418                         buildGenericDocumentFromXmlElement(
419                                 parser,
420                                 packageName,
421                                 nestedSchemaType,
422                                 qualifiedPropertyNamesToPropertyConfig,
423                                 nestedDoc);
424                         nestedDocumentValues
425                                 .computeIfAbsent(currentPropertyPath, k -> new ArrayList<>())
426                                 .add(nestedDoc.build());
427                     } else if (propertyConfig != null) {
428                         primitivePropertyValues
429                                 .computeIfAbsent(currentPropertyPath, k -> new ArrayList<>())
430                                 .add(parser.nextText().trim());
431                     } else if (parser.getName().equals(XML_TAG_ID)) {
432                         String id = parser.nextText().trim();
433                         if (!id.isEmpty()) {
434                             docBuilder.setId(packageName + "/" + id);
435                             wasDocIdSet = true;
436                         }
437                     }
438                     break;
439 
440                 case XmlPullParser.END_TAG:
441                     if (startTag.equals(parser.getName())) {
442                         for (Map.Entry<String, List<String>> entry :
443                                 primitivePropertyValues.entrySet()) {
444                             addPrimitiveProperty(
445                                     docBuilder,
446                                     qualifiedPropertyNamesToPropertyConfig.get(entry.getKey()),
447                                     entry.getValue());
448                         }
449                         for (Map.Entry<String, List<GenericDocument>> entry :
450                                 nestedDocumentValues.entrySet()) {
451                             String propertyName =
452                                     qualifiedPropertyNamesToPropertyConfig
453                                             .get(entry.getKey())
454                                             .getName();
455                             docBuilder.setPropertyDocument(
456                                     propertyName, entry.getValue().toArray(new GenericDocument[0]));
457                         }
458                         if (!wasDocIdSet) {
459                             throw new XmlPullParserException(
460                                     "No id found for document of type: " + schemaType);
461                         }
462                         return;
463                     }
464                     break;
465             }
466             parser.next();
467         }
468 
469         throw new IllegalStateException("Code should never reach here.");
470     }
471 
472     /**
473      * Builds a mapping of qualified property names to their corresponding {@link PropertyConfig}
474      * objects.
475      *
476      * <p>The key is a concatenation of enclosing schema type and property name, separated by a
477      * period to avoid conflicts between properties with the same name in different schemas. For
478      * example, if the "Person" and "Address" schemas both have a property named "name", then the
479      * qualified property names will be "Person#name" and "Address#name" respectively.
480      *
481      * @param schemaMap the mapping of schema types to their corresponding {@link AppSearchSchema}
482      *     objects.
483      * @return a {@link Map} of qualified property names to their corresponding {@link
484      *     PropertyConfig} objects.
485      */
486     @NonNull
buildQualifiedPropertyNameToPropertyConfigMap( @onNull Map<String, AppSearchSchema> schemaMap)487     private static Map<String, PropertyConfig> buildQualifiedPropertyNameToPropertyConfigMap(
488             @NonNull Map<String, AppSearchSchema> schemaMap) {
489         Objects.requireNonNull(schemaMap);
490 
491         Map<String, PropertyConfig> propertyMap = new ArrayMap<>();
492 
493         for (Map.Entry<String, AppSearchSchema> entry : schemaMap.entrySet()) {
494             String schemaType = entry.getKey();
495             AppSearchSchema schema = entry.getValue();
496 
497             List<AppSearchSchema.PropertyConfig> properties = schema.getProperties();
498             for (int i = 0; i < properties.size(); i++) {
499                 AppSearchSchema.PropertyConfig property = properties.get(i);
500                 String propertyPath = createQualifiedPropertyName(schemaType, property.getName());
501                 propertyMap.put(propertyPath, property);
502             }
503         }
504 
505         return propertyMap;
506     }
507 
508     /**
509      * Converts a string of words separated by separator to lowerCamelCase.
510      *
511      * <p>Returns the same string if string doesn't contain the separator.
512      */
toLowerCamelCase(@onNull String str, @NonNull String separator)513     private static String toLowerCamelCase(@NonNull String str, @NonNull String separator) {
514         if (str.isEmpty()) {
515             return "";
516         }
517 
518         // Return the original string if the separator is not present
519         if (!str.contains(separator)) {
520             return str;
521         }
522 
523         StringBuilder builder = new StringBuilder(str.length());
524         boolean capitalizeNext = false;
525 
526         for (int i = 0; i < str.length(); i++) {
527             char currentChar = str.charAt(i);
528             // skip multiple consecutive separators
529             if (str.startsWith(separator, i)) {
530                 capitalizeNext = true;
531                 i += separator.length() - 1;
532             } else {
533                 if (capitalizeNext) {
534                     builder.append(Character.toUpperCase(currentChar));
535                     capitalizeNext = false;
536                 } else {
537                     builder.append(Character.toLowerCase(currentChar));
538                 }
539             }
540         }
541 
542         return builder.toString();
543     }
544 
545     /**
546      * Creates a qualified property name by concatenating the schema type and property name with a #
547      * separator to avoid conflicts between properties with the same name in different schemas.
548      */
549     @NonNull
createQualifiedPropertyName( @onNull String schemaType, @NonNull String propertyName)550     private static String createQualifiedPropertyName(
551             @NonNull String schemaType, @NonNull String propertyName) {
552         return Objects.requireNonNull(schemaType) + "#" + Objects.requireNonNull(propertyName);
553     }
554 
555     /**
556      * Adds primitive property values to the given {@link GenericDocument.Builder} based on the
557      * given {@link PropertyConfig}.
558      *
559      * <p>Ignores unsupported data types.
560      */
addPrimitiveProperty( @onNull GenericDocument.Builder builder, @NonNull PropertyConfig propertyConfig, @NonNull List<String> values)561     private static void addPrimitiveProperty(
562             @NonNull GenericDocument.Builder builder,
563             @NonNull PropertyConfig propertyConfig,
564             @NonNull List<String> values) {
565         Objects.requireNonNull(builder);
566         Objects.requireNonNull(propertyConfig);
567         Objects.requireNonNull(values);
568 
569         switch (propertyConfig.getDataType()) {
570             case PropertyConfig.DATA_TYPE_BOOLEAN:
571                 boolean[] booleanValues = new boolean[values.size()];
572                 for (int i = 0; i < values.size(); i++) {
573                     booleanValues[i] = Boolean.parseBoolean(values.get(i));
574                 }
575                 builder.setPropertyBoolean(propertyConfig.getName(), booleanValues);
576                 break;
577             case PropertyConfig.DATA_TYPE_LONG:
578                 long[] longValues = new long[values.size()];
579                 for (int i = 0; i < values.size(); i++) {
580                     longValues[i] = Long.parseLong(values.get(i));
581                 }
582                 builder.setPropertyLong(propertyConfig.getName(), longValues);
583                 break;
584             case PropertyConfig.DATA_TYPE_STRING:
585                 builder.setPropertyString(propertyConfig.getName(), values.toArray(new String[0]));
586                 break;
587             default:
588                 // fall-through
589         }
590     }
591 }
592