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 */ parse(String[] args)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 parseFlags(List<String> args)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. */ isFlagSet(String flagName)85 public boolean isFlagSet(String flagName) { 86 return flags.containsKey(flagName); 87 } 88 89 /** Returns the flag value wrapped in the Optional class. */ getFlagValue(String flagName)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 */ getRequiredFlagValue(String flagName)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. */ getFlagValueOrDefault(String flagName, String defaultValue)110 public String getFlagValueOrDefault(String flagName, String defaultValue) { 111 return flags.getOrDefault(flagName, defaultValue); 112 } 113 getFlagValueAsPath(String flagName)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 */ getFlagListValue(String flagName)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 */ getCommands()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 ParseException(String message)143 public ParseException(String message) { 144 super(message); 145 } 146 } 147 } 148