• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.tradefed.util;
18 
19 import com.android.tradefed.config.ConfigurationException;
20 import com.android.tradefed.config.Option;
21 import com.android.tradefed.config.OptionSetter;
22 import com.android.tradefed.config.OptionSetter.Handler;
23 import com.android.tradefed.config.OptionSetter.MapHandler;
24 import com.android.tradefed.log.LogUtil.CLog;
25 import com.android.tradefed.result.error.InfraErrorIdentifier;
26 
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.lang.reflect.Field;
30 import java.util.Collection;
31 import java.util.Map;
32 import java.util.Properties;
33 import java.util.regex.Pattern;
34 
35 /**
36  * A utility class that allows classes to load a variables value statically from a res file.
37  *
38  * <p>The resource file should be in a key=value format, where the key is associated with the
39  * variable that needs to be retrieved. A single resource file can contain multiple lines, where
40  * each line is associated with one variable.
41  *
42  * <p>To specify any primitive types, a single key=value pair should be used in a line. e.g.:
43  *
44  * <ol>
45  *   <li>my-integer-key=5
46  *   <li>my-string-key=myStringValue
47  * </ol>
48  *
49  * <p>To specify any collections, multiple values can be used, separated by a comma(,). e.g.:
50  *
51  * <ol>
52  *   <li>my-string-list-key=stringOne,stringTwo,stringThree
53  *   <li>my-int-list-key=1,2,3,4,5
54  * </ol>
55  *
56  * <p>To specify a map, multiple mapKey\=mapValue pair can be used, separated by a comma(,). e.g.:
57  *
58  * <ol>
59  *   <li>my-map-key=mapKey1\=mapVal1,mapKey2\=mapVal2
60  * </ol>
61  */
62 public class TfInternalOptionsFetcher {
63     private static String resourcePath = "/util/TfInternalOptions.properties";
64 
65     /**
66      * Fetches the values for all declared fields of the given {@link Class} from the specified
67      * resource file. If a resource file is not set, a default resource file will be used.
68      *
69      * @param classObj the class {@link Object} whose fields should be populated.
70      */
fetchOption(Class<?> classObj)71     public static void fetchOption(Class<?> classObj) {
72         try (InputStream stream =
73                 TfInternalOptionsFetcher.class.getResourceAsStream(resourcePath)) {
74             // load the properties from the resource file
75             Properties properties = new Properties();
76             properties.load(stream);
77             for (Field field : classObj.getDeclaredFields()) {
78                 String propertyKey = field.getName();
79                 String optionVal = (String) properties.get(propertyKey);
80                 // if property exists, update the field
81                 if (optionVal != null) {
82                     Handler handler = OptionSetter.getHandler(field.getGenericType());
83                     if (handler == null) {
84                         throw new ConfigurationException(
85                                 String.format(
86                                         "Unable to get handler for option '%s'.", propertyKey),
87                                 InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR);
88                     }
89                     boolean isOptionField = false;
90                     if (field.getAnnotation(Option.class) != null) {
91                         isOptionField = true;
92                     }
93                     if (Collection.class.isAssignableFrom(field.getType())) {
94                         // if field is a collection, allow multiple values to be specified.
95                         String[] optionValues = optionVal.split(",");
96                         for (String optionValue : optionValues) {
97                             // translate the value
98                             Object translatedValue = handler.translate(optionValue);
99                             OptionSetter.setFieldValue(
100                                     propertyKey,
101                                     classObj,
102                                     field,
103                                     null,
104                                     translatedValue,
105                                     isOptionField);
106                         }
107                     } else if (Map.class.isAssignableFrom(field.getType())) {
108                         // if field is a map, allow multiple key=value pairs for the option value.
109                         String[] optionValues = optionVal.split(",");
110                         for (String optionValue : optionValues) {
111                             String mapKey;
112                             String mapVal;
113                             // only match = to escape use "\="
114                             Pattern p = Pattern.compile("(?<!\\\\)=");
115                             String[] parts =
116                                     p.split(optionValue, /* allow empty-string values */ -1);
117                             // Note that we replace escaped = (\=) to =.
118                             if (parts.length == 2) {
119                                 mapKey = parts[0].replaceAll("\\\\=", "=");
120                                 mapVal = parts[1].replaceAll("\\\\=", "=");
121                             } else {
122                                 throw new ConfigurationException(
123                                         String.format(
124                                                 "option '%s' has an invalid format for value %s:w",
125                                                 propertyKey, optionValue),
126                                         InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR);
127                             }
128                             // translate the key and value
129                             Object translatedKey = ((MapHandler) handler).translateKey(mapKey);
130                             Object translatedValue = handler.translate(mapVal);
131                             OptionSetter.setFieldValue(
132                                     propertyKey,
133                                     classObj,
134                                     field,
135                                     translatedKey,
136                                     translatedValue,
137                                     isOptionField);
138                         }
139                     } else {
140                         OptionSetter.setFieldValue(
141                                 propertyKey,
142                                 classObj,
143                                 field,
144                                 null,
145                                 handler.translate(optionVal),
146                                 isOptionField);
147                     }
148                 }
149             }
150         } catch (IOException | ConfigurationException e) {
151             CLog.w("Unable to fetch option values for class '%s'.", classObj.getName());
152             CLog.e(e);
153         }
154     }
155 
156     /** Set the path of the resource file where the value will be retrieved from. */
setResourcePath(String path)157     public static void setResourcePath(String path) {
158         resourcePath = path;
159     }
160 }
161