1 /* 2 * Copyright 2015 The gRPC Authors 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 io.grpc.benchmarks.qps; 18 19 import static java.lang.Math.max; 20 import static java.lang.String.CASE_INSENSITIVE_ORDER; 21 22 import com.google.common.base.Strings; 23 import java.util.ArrayList; 24 import java.util.Collection; 25 import java.util.List; 26 import java.util.Map; 27 import java.util.Set; 28 import java.util.TreeMap; 29 import java.util.TreeSet; 30 31 /** 32 * Abstract base class for all {@link Configuration.Builder}s. 33 */ 34 public abstract class AbstractConfigurationBuilder<T extends Configuration> 35 implements Configuration.Builder<T> { 36 37 private static final Param HELP = new Param() { 38 @Override 39 public String getName() { 40 return "help"; 41 } 42 43 @Override 44 public String getType() { 45 return ""; 46 } 47 48 @Override 49 public String getDescription() { 50 return "Print this text."; 51 } 52 53 @Override 54 public boolean isRequired() { 55 return false; 56 } 57 58 @Override 59 public String getDefaultValue() { 60 return null; 61 } 62 63 @Override 64 public void setValue(Configuration config, String value) { 65 throw new UnsupportedOperationException(); 66 } 67 }; 68 69 /** 70 * A single application parameter supported by this builder. 71 */ 72 protected interface Param { 73 /** 74 * The name of the parameter as it would appear on the command-line. 75 */ getName()76 String getName(); 77 78 /** 79 * A string representation of the parameter type. If not applicable, just returns an empty 80 * string. 81 */ getType()82 String getType(); 83 84 /** 85 * A description of this parameter used when printing usage. 86 */ getDescription()87 String getDescription(); 88 89 /** 90 * The default value used when not set explicitly. Ignored if {@link #isRequired()} is {@code 91 * true}. 92 */ getDefaultValue()93 String getDefaultValue(); 94 95 /** 96 * Indicates whether or not this parameter is required and must therefore be set before the 97 * configuration can be successfully built. 98 */ isRequired()99 boolean isRequired(); 100 101 /** 102 * Sets this parameter on the given configuration instance. 103 */ setValue(Configuration config, String value)104 void setValue(Configuration config, String value); 105 } 106 107 @Override build(String[] args)108 public final T build(String[] args) { 109 T config = newConfiguration(); 110 Map<String, Param> paramMap = getParamMap(); 111 Set<String> appliedParams = new TreeSet<String>(CASE_INSENSITIVE_ORDER); 112 113 for (String arg : args) { 114 if (!arg.startsWith("--")) { 115 throw new IllegalArgumentException("All arguments must start with '--': " + arg); 116 } 117 String[] pair = arg.substring(2).split("=", 2); 118 String key = pair[0]; 119 String value = ""; 120 if (pair.length == 2) { 121 value = pair[1]; 122 } 123 124 // If help was requested, just throw now to print out the usage. 125 if (HELP.getName().equalsIgnoreCase(key)) { 126 throw new IllegalArgumentException("Help requested"); 127 } 128 129 Param param = paramMap.get(key); 130 if (param == null) { 131 throw new IllegalArgumentException("Unsupported argument: " + key); 132 } 133 param.setValue(config, value); 134 appliedParams.add(key); 135 } 136 137 // Ensure that all required options have been provided. 138 for (Param param : getParams()) { 139 if (param.isRequired() && !appliedParams.contains(param.getName())) { 140 throw new IllegalArgumentException("Missing required option '--" 141 + param.getName() + "'."); 142 } 143 } 144 145 return build0(config); 146 } 147 148 @Override printUsage()149 public final void printUsage() { 150 System.out.println("Usage: [ARGS...]"); 151 int column1Width = 0; 152 List<Param> params = new ArrayList<>(); 153 params.add(HELP); 154 params.addAll(getParams()); 155 156 for (Param param : params) { 157 column1Width = max(commandLineFlag(param).length(), column1Width); 158 } 159 int column1Start = 2; 160 int column2Start = column1Start + column1Width + 2; 161 for (Param param : params) { 162 StringBuilder sb = new StringBuilder(); 163 sb.append(Strings.repeat(" ", column1Start)); 164 sb.append(commandLineFlag(param)); 165 sb.append(Strings.repeat(" ", column2Start - sb.length())); 166 String message = param.getDescription(); 167 sb.append(wordWrap(message, column2Start, 80)); 168 if (param.isRequired()) { 169 sb.append(Strings.repeat(" ", column2Start)); 170 sb.append("[Required]\n"); 171 } else if (param.getDefaultValue() != null && !param.getDefaultValue().isEmpty()) { 172 sb.append(Strings.repeat(" ", column2Start)); 173 sb.append("[Default=" + param.getDefaultValue() + "]\n"); 174 } 175 System.out.println(sb); 176 } 177 System.out.println(); 178 } 179 180 /** 181 * Creates a new configuration instance which will be used as the target for command-line 182 * arguments. 183 */ newConfiguration()184 protected abstract T newConfiguration(); 185 186 /** 187 * Returns the valid parameters supported by the configuration. 188 */ getParams()189 protected abstract Collection<Param> getParams(); 190 191 /** 192 * Called by {@link #build(String[])} after verifying that all required options have been set. 193 * Performs any final validation and modifications to the configuration. If successful, returns 194 * the fully built configuration. 195 */ build0(T config)196 protected abstract T build0(T config); 197 getParamMap()198 private Map<String, Param> getParamMap() { 199 Map<String, Param> map = new TreeMap<String, Param>(CASE_INSENSITIVE_ORDER); 200 for (Param param : getParams()) { 201 map.put(param.getName(), param); 202 } 203 return map; 204 } 205 commandLineFlag(Param param)206 private static String commandLineFlag(Param param) { 207 String name = param.getName().toLowerCase(); 208 String type = (!param.getType().isEmpty() ? '=' + param.getType() : ""); 209 return "--" + name + type; 210 } 211 wordWrap(String text, int startPos, int maxPos)212 private static String wordWrap(String text, int startPos, int maxPos) { 213 StringBuilder builder = new StringBuilder(); 214 int pos = startPos; 215 String[] parts = text.split("\\n", -1); 216 boolean isBulleted = parts.length > 1; 217 for (String part : parts) { 218 int lineStart = startPos; 219 while (!part.isEmpty()) { 220 if (pos < lineStart) { 221 builder.append(Strings.repeat(" ", lineStart - pos)); 222 pos = lineStart; 223 } 224 int maxLength = maxPos - pos; 225 int length = part.length(); 226 if (length > maxLength) { 227 length = part.lastIndexOf(' ', maxPos - pos) + 1; 228 if (length == 0) { 229 length = part.length(); 230 } 231 } 232 builder.append(part.substring(0, length)); 233 part = part.substring(length); 234 235 // Wrap to the next line. 236 builder.append("\n"); 237 pos = 0; 238 lineStart = isBulleted ? startPos + 2 : startPos; 239 } 240 } 241 return builder.toString(); 242 } 243 } 244