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