ParameterTool.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. package com.winhc.max.compute.graph.util;
  2. import javax.annotation.Nullable;
  3. import java.io.*;
  4. import java.util.*;
  5. import java.util.concurrent.ConcurrentHashMap;
  6. /**
  7. * @author: XuJiakai
  8. * 2022/6/7 11:28
  9. */
  10. public class ParameterTool {
  11. protected transient Map<String, String> defaultData;
  12. protected transient Set<String> unrequestedParameters;
  13. protected static final String NO_VALUE_KEY = "__NO_VALUE_KEY";
  14. protected static final String DEFAULT_UNDEFINED = "<undefined>";
  15. // ------------------ Constructors ------------------------
  16. /**
  17. * Returns {@link ParameterTool} for the given arguments. The arguments are keys followed by
  18. * values. Keys have to start with '-' or '--'
  19. *
  20. * <p><strong>Example arguments:</strong> --key1 value1 --key2 value2 -key3 value3
  21. *
  22. * @param args Input array arguments
  23. * @return A {@link ParameterTool}
  24. */
  25. public static ParameterTool fromArgs(String[] args) {
  26. final Map<String, String> map = new HashMap<>(args.length / 2);
  27. int i = 0;
  28. while (i < args.length) {
  29. final String key = getKeyFromArgs(args, i);
  30. if (key.isEmpty()) {
  31. throw new IllegalArgumentException(
  32. "The input " + Arrays.toString(args) + " contains an empty argument");
  33. }
  34. i += 1; // try to find the value
  35. if (i >= args.length) {
  36. map.put(key, NO_VALUE_KEY);
  37. } else if (isNumber(args[i])) {
  38. map.put(key, args[i]);
  39. i += 1;
  40. } else if (args[i].startsWith("--") || args[i].startsWith("-")) {
  41. // the argument cannot be a negative number because we checked earlier
  42. // -> the next argument is a parameter name
  43. map.put(key, NO_VALUE_KEY);
  44. } else {
  45. map.put(key, args[i]);
  46. i += 1;
  47. }
  48. }
  49. return fromMap(map);
  50. }
  51. /**
  52. * Returns {@link ParameterTool} for the given {@link Properties} file.
  53. *
  54. * @param path Path to the properties file
  55. * @return A {@link ParameterTool}
  56. * @throws IOException If the file does not exist
  57. * @see Properties
  58. */
  59. public static ParameterTool fromPropertiesFile(String path) throws IOException {
  60. File propertiesFile = new File(path);
  61. return fromPropertiesFile(propertiesFile);
  62. }
  63. /**
  64. * Returns {@link ParameterTool} for the given {@link Properties} file.
  65. *
  66. * @param file File object to the properties file
  67. * @return A {@link ParameterTool}
  68. * @throws IOException If the file does not exist
  69. * @see Properties
  70. */
  71. public static ParameterTool fromPropertiesFile(File file) throws IOException {
  72. if (!file.exists()) {
  73. throw new FileNotFoundException(
  74. "Properties file " + file.getAbsolutePath() + " does not exist");
  75. }
  76. try (FileInputStream fis = new FileInputStream(file)) {
  77. return fromPropertiesFile(fis);
  78. }
  79. }
  80. /**
  81. * Returns {@link ParameterTool} for the given InputStream from {@link Properties} file.
  82. *
  83. * @param inputStream InputStream from the properties file
  84. * @return A {@link ParameterTool}
  85. * @throws IOException If the file does not exist
  86. * @see Properties
  87. */
  88. public static ParameterTool fromPropertiesFile(InputStream inputStream) throws IOException {
  89. Properties props = new Properties();
  90. props.load(inputStream);
  91. return fromMap((Map) props);
  92. }
  93. /**
  94. * Returns {@link ParameterTool} for the given map.
  95. *
  96. * @param map A map of arguments. Both Key and Value have to be Strings
  97. * @return A {@link ParameterTool}
  98. */
  99. public static ParameterTool fromMap(Map<String, String> map) {
  100. checkNotNull(map, "Unable to initialize from empty map");
  101. return new ParameterTool(map);
  102. }
  103. /**
  104. * Returns {@link ParameterTool} from the system properties. Example on how to pass system
  105. * properties: -Dkey1=value1 -Dkey2=value2
  106. *
  107. * @return A {@link ParameterTool}
  108. */
  109. public static ParameterTool fromSystemProperties() {
  110. return fromMap((Map) System.getProperties());
  111. }
  112. // ------------------ ParameterUtil ------------------------
  113. protected final Map<String, String> data;
  114. private ParameterTool(Map<String, String> data) {
  115. this.data = Collections.unmodifiableMap(new HashMap<>(data));
  116. this.defaultData = new ConcurrentHashMap<>(data.size());
  117. this.unrequestedParameters =
  118. Collections.newSetFromMap(new ConcurrentHashMap<>(data.size()));
  119. unrequestedParameters.addAll(data.keySet());
  120. }
  121. @Override
  122. public boolean equals(Object o) {
  123. if (this == o) {
  124. return true;
  125. }
  126. if (o == null || getClass() != o.getClass()) {
  127. return false;
  128. }
  129. ParameterTool that = (ParameterTool) o;
  130. return Objects.equals(data, that.data)
  131. && Objects.equals(defaultData, that.defaultData)
  132. && Objects.equals(unrequestedParameters, that.unrequestedParameters);
  133. }
  134. @Override
  135. public int hashCode() {
  136. return Objects.hash(data, defaultData, unrequestedParameters);
  137. }
  138. // ------------------ Get data from the util ----------------
  139. /**
  140. * Returns number of parameters in {@link ParameterTool}.
  141. */
  142. public int getNumberOfParameters() {
  143. return data.size();
  144. }
  145. /**
  146. * Returns the String value for the given key. If the key does not exist it will return null.
  147. */
  148. public String get(String key) {
  149. addToDefaults(key, null);
  150. unrequestedParameters.remove(key);
  151. return data.get(key);
  152. }
  153. public String getOrDefault(String key, String defaultValue) {
  154. String s = get(key);
  155. if (s == null) {
  156. return defaultValue;
  157. } else {
  158. return s;
  159. }
  160. }
  161. /**
  162. * Check if value is set.
  163. */
  164. public boolean has(String value) {
  165. addToDefaults(value, null);
  166. unrequestedParameters.remove(value);
  167. return data.containsKey(value);
  168. }
  169. /**
  170. * Returns a {@link Properties} object from this {@link ParameterTool}.
  171. *
  172. * @return A {@link Properties}
  173. */
  174. public Properties getProperties() {
  175. Properties props = new Properties();
  176. props.putAll(this.data);
  177. return props;
  178. }
  179. /**
  180. * Create a properties file with all the known parameters (call after the last get*() call). Set
  181. * the default value, if available.
  182. *
  183. * <p>Use this method to create a properties file skeleton.
  184. *
  185. * @param pathToFile Location of the default properties file.
  186. */
  187. public void createPropertiesFile(String pathToFile) throws IOException {
  188. createPropertiesFile(pathToFile, true);
  189. }
  190. /**
  191. * Create a properties file with all the known parameters (call after the last get*() call). Set
  192. * the default value, if overwrite is true.
  193. *
  194. * @param pathToFile Location of the default properties file.
  195. * @param overwrite Boolean flag indicating whether or not to overwrite the file
  196. * @throws IOException If overwrite is not allowed and the file exists
  197. */
  198. public void createPropertiesFile(String pathToFile, boolean overwrite) throws IOException {
  199. final File file = new File(pathToFile);
  200. if (file.exists()) {
  201. if (overwrite) {
  202. file.delete();
  203. } else {
  204. throw new RuntimeException(
  205. "File " + pathToFile + " exists and overwriting is not allowed");
  206. }
  207. }
  208. final Properties defaultProps = new Properties();
  209. defaultProps.putAll(this.defaultData);
  210. try (final OutputStream out = new FileOutputStream(file)) {
  211. defaultProps.store(
  212. out, "Default file created by Flink's ParameterUtil.createPropertiesFile()");
  213. }
  214. }
  215. @Override
  216. protected Object clone() throws CloneNotSupportedException {
  217. return new ParameterTool(this.data);
  218. }
  219. // ------------------------- Interaction with other ParameterUtils -------------------------
  220. /**
  221. * Merges two {@link ParameterTool}.
  222. *
  223. * @param other Other {@link ParameterTool} object
  224. * @return The Merged {@link ParameterTool}
  225. */
  226. public ParameterTool mergeWith(ParameterTool other) {
  227. final Map<String, String> resultData = new HashMap<>(data.size() + other.data.size());
  228. resultData.putAll(data);
  229. resultData.putAll(other.data);
  230. final ParameterTool ret = new ParameterTool(resultData);
  231. final HashSet<String> requestedParametersLeft = new HashSet<>(data.keySet());
  232. requestedParametersLeft.removeAll(unrequestedParameters);
  233. final HashSet<String> requestedParametersRight = new HashSet<>(other.data.keySet());
  234. requestedParametersRight.removeAll(other.unrequestedParameters);
  235. ret.unrequestedParameters.removeAll(requestedParametersLeft);
  236. ret.unrequestedParameters.removeAll(requestedParametersRight);
  237. return ret;
  238. }
  239. // ------------------------- ExecutionConfig.UserConfig interface -------------------------
  240. public Map<String, String> toMap() {
  241. return data;
  242. }
  243. // ------------------------- Serialization ---------------------------------------------
  244. private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
  245. in.defaultReadObject();
  246. defaultData = new ConcurrentHashMap<>(data.size());
  247. unrequestedParameters = Collections.newSetFromMap(new ConcurrentHashMap<>(data.size()));
  248. }
  249. public static String getKeyFromArgs(String[] args, int index) {
  250. String key;
  251. if (args[index].startsWith("--")) {
  252. key = args[index].substring(2);
  253. } else if (args[index].startsWith("-")) {
  254. key = args[index].substring(1);
  255. } else {
  256. throw new IllegalArgumentException(
  257. String.format(
  258. "Error parsing arguments '%s' on '%s'. Please prefix keys with -- or -.",
  259. Arrays.toString(args), args[index]));
  260. }
  261. if (key.isEmpty()) {
  262. throw new IllegalArgumentException(
  263. "The input " + Arrays.toString(args) + " contains an empty argument");
  264. }
  265. return key;
  266. }
  267. public static boolean isNumber(final String str) {
  268. if (isEmpty(str)) {
  269. return false;
  270. }
  271. final char[] chars = str.toCharArray();
  272. int sz = chars.length;
  273. boolean hasExp = false;
  274. boolean hasDecPoint = false;
  275. boolean allowSigns = false;
  276. boolean foundDigit = false;
  277. // deal with any possible sign up front
  278. final int start = (chars[0] == '-') ? 1 : 0;
  279. if (sz > start + 1 && chars[start] == '0') { // leading 0
  280. if (
  281. (chars[start + 1] == 'x') ||
  282. (chars[start + 1] == 'X')
  283. ) { // leading 0x/0X
  284. int i = start + 2;
  285. if (i == sz) {
  286. return false; // str == "0x"
  287. }
  288. // checking hex (it can't be anything else)
  289. for (; i < chars.length; i++) {
  290. if ((chars[i] < '0' || chars[i] > '9')
  291. && (chars[i] < 'a' || chars[i] > 'f')
  292. && (chars[i] < 'A' || chars[i] > 'F')) {
  293. return false;
  294. }
  295. }
  296. return true;
  297. } else if (Character.isDigit(chars[start + 1])) {
  298. // leading 0, but not hex, must be octal
  299. int i = start + 1;
  300. for (; i < chars.length; i++) {
  301. if (chars[i] < '0' || chars[i] > '7') {
  302. return false;
  303. }
  304. }
  305. return true;
  306. }
  307. }
  308. sz--; // don't want to loop to the last char, check it afterwords
  309. // for type qualifiers
  310. int i = start;
  311. // loop to the next to last char or to the last char if we need another digit to
  312. // make a valid number (e.g. chars[0..5] = "1234E")
  313. while (i < sz || (i < sz + 1 && allowSigns && !foundDigit)) {
  314. if (chars[i] >= '0' && chars[i] <= '9') {
  315. foundDigit = true;
  316. allowSigns = false;
  317. } else if (chars[i] == '.') {
  318. if (hasDecPoint || hasExp) {
  319. // two decimal points or dec in exponent
  320. return false;
  321. }
  322. hasDecPoint = true;
  323. } else if (chars[i] == 'e' || chars[i] == 'E') {
  324. // we've already taken care of hex.
  325. if (hasExp) {
  326. // two E's
  327. return false;
  328. }
  329. if (!foundDigit) {
  330. return false;
  331. }
  332. hasExp = true;
  333. allowSigns = true;
  334. } else if (chars[i] == '+' || chars[i] == '-') {
  335. if (!allowSigns) {
  336. return false;
  337. }
  338. allowSigns = false;
  339. foundDigit = false; // we need a digit after the E
  340. } else {
  341. return false;
  342. }
  343. i++;
  344. }
  345. if (i < chars.length) {
  346. if (chars[i] >= '0' && chars[i] <= '9') {
  347. // no type qualifier, OK
  348. return true;
  349. }
  350. if (chars[i] == 'e' || chars[i] == 'E') {
  351. // can't have an E at the last byte
  352. return false;
  353. }
  354. if (chars[i] == '.') {
  355. if (hasDecPoint || hasExp) {
  356. // two decimal points or dec in exponent
  357. return false;
  358. }
  359. // single trailing decimal point after non-exponent is ok
  360. return foundDigit;
  361. }
  362. if (!allowSigns
  363. && (chars[i] == 'd'
  364. || chars[i] == 'D'
  365. || chars[i] == 'f'
  366. || chars[i] == 'F')) {
  367. return foundDigit;
  368. }
  369. if (chars[i] == 'l'
  370. || chars[i] == 'L') {
  371. // not allowing L with an exponent or decimal point
  372. return foundDigit && !hasExp && !hasDecPoint;
  373. }
  374. // last character is illegal
  375. return false;
  376. }
  377. // allowSigns is true iff the val ends in 'E'
  378. // found digit it to make sure weird stuff like '.' and '1E-' doesn't pass
  379. return !allowSigns && foundDigit;
  380. }
  381. public static boolean isEmpty(final CharSequence cs) {
  382. return cs == null || cs.length() == 0;
  383. }
  384. protected void addToDefaults(String key, String value) {
  385. final String currentValue = defaultData.get(key);
  386. if (currentValue == null) {
  387. if (value == null) {
  388. value = DEFAULT_UNDEFINED;
  389. }
  390. defaultData.put(key, value);
  391. } else {
  392. // there is already an entry for this key. Check if the value is the undefined
  393. if (currentValue.equals(DEFAULT_UNDEFINED) && value != null) {
  394. // update key with better default value
  395. defaultData.put(key, value);
  396. }
  397. }
  398. }
  399. public static <T> T checkNotNull(@Nullable T reference, @Nullable String errorMessage) {
  400. if (reference == null) {
  401. throw new NullPointerException(String.valueOf(errorMessage));
  402. }
  403. return reference;
  404. }
  405. }