Home | History | Annotate | Download | only in utils
      1 /*
      2  * Copyright (C) 2017 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.tools.appbundle.bundletool.utils;
     18 
     19 import java.nio.file.Path;
     20 import java.nio.file.Paths;
     21 import java.util.ArrayList;
     22 import java.util.Arrays;
     23 import java.util.Collections;
     24 import java.util.HashMap;
     25 import java.util.List;
     26 import java.util.Map;
     27 import java.util.Optional;
     28 
     29 /**
     30  * Utility for flag parsing, specific to the Bundle Tool.
     31  *
     32  * <p>The flags follow the below convention:
     33  *
     34  * <p>[bundle-tool] [command1] [command2] .. [command-n] [--flag1] [--flag2=v2].. [--flagn] where:
     35  *
     36  * <ul>
     37  *   <li>commands: cannot start with "-".
     38  *   <li>flags: have to start with "--". If they have "=" anything after the first occurrence is
     39  *       considered a flag value. By default the flag value is an empty string.
     40  * </ul>
     41  */
     42 public class FlagParser {
     43 
     44   private List<String> commands = new ArrayList<>();
     45   private Map<String, String> flags = new HashMap<>();
     46 
     47   /**
     48    * Parses the given arguments populating the structures.
     49    *
     50    * <p>Calling this function removes any previous parsing results.
     51    */
     52   public FlagParser parse(String[] args) throws ParseException {
     53     this.commands.clear();
     54     // Need to wrap it into a proper list implementation to be able to remove elements.
     55     List<String> argsToProcess = new ArrayList<>(Arrays.asList(args));
     56     while (argsToProcess.size() > 0 && !argsToProcess.get(0).startsWith("-")) {
     57       commands.add(argsToProcess.get(0));
     58       argsToProcess.remove(0);
     59     }
     60     this.flags = parseFlags(argsToProcess);
     61     return this;
     62   }
     63 
     64   private Map<String, String> parseFlags(List<String> args) throws ParseException {
     65     Map<String, String> flagMap = new HashMap<>();
     66     for (String arg : args) {
     67       if (!arg.startsWith("--")) {
     68         throw new ParseException(
     69             String.format("Syntax error: flags should start with -- (%s)", arg));
     70       }
     71       String[] segments = arg.substring(2).split("=", 2);
     72       String value = "";
     73       if (segments.length == 2) {
     74         value = segments[1];
     75       }
     76       if (flagMap.putIfAbsent(segments[0], value) != null) {
     77         throw new ParseException(
     78             String.format("Flag %s has been set more than once.", segments[0]));
     79       }
     80     }
     81     return flagMap;
     82   }
     83 
     84   /** Returns true if a given flag has been set. */
     85   public boolean isFlagSet(String flagName) {
     86     return flags.containsKey(flagName);
     87   }
     88 
     89   /** Returns the flag value wrapped in the Optional class. */
     90   public Optional<String> getFlagValue(String flagName) {
     91     return Optional.ofNullable(flags.get(flagName));
     92   }
     93 
     94   /**
     95    * Returns a flag value. If absent throws IllegalStateException.
     96    *
     97    * @param flagName name of the flag to fetch
     98    * @return string, the value of the flag
     99    * @throws IllegalStateException if the flag was not set.
    100    */
    101   public String getRequiredFlagValue(String flagName) {
    102     return getFlagValue(flagName)
    103         .orElseThrow(
    104             () ->
    105                 new IllegalArgumentException(
    106                     String.format("Missing the required --%s flag.", flagName)));
    107   }
    108 
    109   /** Returns the string value of the flag or the default if has not been set. */
    110   public String getFlagValueOrDefault(String flagName, String defaultValue) {
    111     return flags.getOrDefault(flagName, defaultValue);
    112   }
    113 
    114   public Optional<Path> getFlagValueAsPath(String flagName) {
    115     return Optional.ofNullable(flags.get(flagName)).map(Paths::get);
    116   }
    117 
    118   /**
    119    * Returns the value of the flag as list of strings.
    120    *
    121    * <p>It converts the string flag value to the list assuming it's delimited by a comma. The list
    122    * is empty if the flag has not been set.
    123    */
    124   public List<String> getFlagListValue(String flagName) {
    125     if (!isFlagSet(flagName)) {
    126       return Collections.emptyList();
    127     }
    128     return Arrays.asList(flags.get(flagName).split(","));
    129   }
    130 
    131   /**
    132    * Returns the list of commands that were parsed.
    133    *
    134    * @return the immutable list of commands.
    135    */
    136   public List<String> getCommands() {
    137     return Collections.unmodifiableList(commands);
    138   }
    139 
    140   /** Exception encapsulating any flag parsing errors. */
    141   public static class ParseException extends RuntimeException {
    142 
    143     public ParseException(String message) {
    144       super(message);
    145     }
    146   }
    147 }
    148