1 /*
2  * Copyright (C) 2016 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.compatibility.common.tradefed.util;
17 
18 import com.android.tradefed.config.Option;
19 
20 import java.lang.reflect.Field;
21 import java.util.ArrayList;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Set;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27 
28 /**
29  * Helper class for manipulating fields with @option annotations.
30  */
31 public final class OptionHelper {
32 
OptionHelper()33     private OptionHelper() {}
34 
35     /**
36      * Return the {@link List} of {@link Field} entries on the given object
37      * that have the {@link Option} annotation.
38      *
39      * @param object An object with @option-annotated fields.
40      */
getFields(Object object)41     private static List<Field> getFields(Object object) {
42         Field[] classFields = object.getClass().getDeclaredFields();
43         List<Field> optionFields = new ArrayList<Field>();
44 
45         for (Field declaredField : classFields) {
46             // allow access to protected and private fields
47             declaredField.setAccessible(true);
48 
49             // store type and values only in annotated fields
50             if (declaredField.isAnnotationPresent(Option.class)) {
51                 optionFields.add(declaredField);
52             }
53         }
54         return optionFields;
55     }
56 
57     /**
58      * Retrieve a {@link Set} of {@link Option} names present on the given
59      * object.
60      *
61      * @param object An object with @option-annotated fields.
62      */
getOptionNames(Object object)63     static Set<String> getOptionNames(Object object) {
64         Set<String> options = new HashSet<String>();
65         List<Field> optionFields = getFields(object);
66 
67         for (Field declaredField : optionFields) {
68             Option option = declaredField.getAnnotation(Option.class);
69             options.add(option.name());
70         }
71         return options;
72     }
73 
74     /**
75      * Retrieve a {@link Set} of {@link Option} short names present on the given
76      * object.
77      *
78      * @param object An object with @option-annotated fields.
79      */
getOptionShortNames(Object object)80     static Set<String> getOptionShortNames(Object object) {
81         Set<String> shortNames = new HashSet<String>();
82         List<Field> optionFields = getFields(object);
83 
84         for (Field declaredField : optionFields) {
85             Option option = declaredField.getAnnotation(Option.class);
86             if (option.shortName() != Option.NO_SHORT_NAME) {
87                 shortNames.add(String.valueOf(option.shortName()));
88             }
89         }
90         return shortNames;
91     }
92 
93     /**
94      * Retrieve a {@link List} of {@link String} entries of the valid
95      * command-line options for the given {@link Object} from the given
96      * input {@link String}.
97      */
getValidCliArgs(String commandString, Object object)98     public static List<String> getValidCliArgs(String commandString, Object object) {
99         Set<String> optionNames = OptionHelper.getOptionNames(object);
100         Set<String> optionShortNames = OptionHelper.getOptionShortNames(object);
101         List<String> validCliArgs = new ArrayList<String>();
102 
103         // get option/value substrings from the command-line string
104         // N.B. tradefed rewrites some expressions from option="value a b" to "option=value a b"
105         String quoteMatching = "(\"[^\"]+\")";
106         Pattern cliPattern = Pattern.compile(
107             "((-[-\\w]+([ =]"                       // match -option=value or --option=value
108             + "(" + quoteMatching + "|[^-\"]+))?"   // allow -option "..." and -option x y z
109             + "))|"
110             + quoteMatching                         // allow anything in direct quotes
111         );
112         Matcher matcher = cliPattern.matcher(commandString);
113 
114         while (matcher.find()) {
115             String optionInput = matcher.group();
116             // split between the option name and value
117             String[] keyNameTokens = optionInput.split("[ =]", 2);
118             // remove initial hyphens and any starting double quote from option args
119             String keyName = keyNameTokens[0].replaceFirst("^\"?--?", "");
120 
121             // add substrings only when the options are recognized
122             if (optionShortNames.contains(keyName) || optionNames.contains(keyName)) {
123                 // add values separated by spaces or in quotes separately to the return array
124                 Pattern tokenPattern = Pattern.compile("(\".*\")|[^\\s=]+");
125                 Matcher tokenMatcher = tokenPattern.matcher(optionInput);
126                 while (tokenMatcher.find()) {
127                     String token = tokenMatcher.group().replaceAll("\"", "");
128                     validCliArgs.add(token);
129                 }
130             }
131         }
132         return validCliArgs;
133     }
134 }
135