Home | History | Annotate | Download | only in util
      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 
     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      */
     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      */
     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      */
     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      */
     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         String nonSpacedHypen = "((?<!\\s)-(?!\\s))";
    107         Pattern cliPattern = Pattern.compile(
    108             // match -option=value or --option=value
    109             "((-[-\\w]+([ =]"
    110             // allow -option "...", -option x y z, and -option x:y:z and -option x:y:z1=z2
    111             + "(" + quoteMatching + "|([\\w\\/\\s:=.]|"+ nonSpacedHypen + ")+))?"
    112             + "))|"
    113             // allow anything in direct quotes
    114             + quoteMatching
    115         );
    116         Matcher matcher = cliPattern.matcher(commandString);
    117 
    118         while (matcher.find()) {
    119             String optionInput = cleanNameValueDelimiter(matcher.group());
    120             // split between the option name and value
    121             String[] keyNameTokens = optionInput.split(" ", 2);
    122             // remove initial hyphens and any starting double quote from option args
    123             String keyName = keyNameTokens[0].replaceFirst("^\"?--?", "");
    124 
    125             // add substrings only when the options are recognized
    126             if (optionShortNames.contains(keyName) || optionNames.contains(keyName)) {
    127                 // add values separated by spaces or in quotes separately to the return array
    128                 Pattern tokenPattern = Pattern.compile("(\".*\")|[^\\s]+");
    129                 Matcher tokenMatcher = tokenPattern.matcher(optionInput);
    130                 while (tokenMatcher.find()) {
    131                     String token = tokenMatcher.group().replaceAll("\"", "");
    132                     validCliArgs.add(token);
    133                 }
    134             }
    135         }
    136         return validCliArgs;
    137     }
    138 
    139     /*
    140      * The "=" sign is a valid character within an option value (such as in key-value map pairs).
    141      * However, it may also act as delimiter between the option name and value. For simplicity,
    142      * ensure the name-value delimiter is a space, so that we may treat the "=" as any other
    143      * character in the getValidCliArgs() logic above.
    144      */
    145     private static String cleanNameValueDelimiter(String optionNameValue) {
    146         return optionNameValue.replaceFirst("[ =]", " ");
    147     }
    148 }
    149