package com.winhc.max.compute.graph.util; import javax.annotation.Nullable; import java.io.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @author: XuJiakai * 2022/6/7 11:28 */ public class ParameterTool { protected transient Map defaultData; protected transient Set unrequestedParameters; protected static final String NO_VALUE_KEY = "__NO_VALUE_KEY"; protected static final String DEFAULT_UNDEFINED = ""; // ------------------ Constructors ------------------------ /** * Returns {@link ParameterTool} for the given arguments. The arguments are keys followed by * values. Keys have to start with '-' or '--' * *

Example arguments: --key1 value1 --key2 value2 -key3 value3 * * @param args Input array arguments * @return A {@link ParameterTool} */ public static ParameterTool fromArgs(String[] args) { final Map map = new HashMap<>(args.length / 2); int i = 0; while (i < args.length) { final String key = getKeyFromArgs(args, i); if (key.isEmpty()) { throw new IllegalArgumentException( "The input " + Arrays.toString(args) + " contains an empty argument"); } i += 1; // try to find the value if (i >= args.length) { map.put(key, NO_VALUE_KEY); } else if (isNumber(args[i])) { map.put(key, args[i]); i += 1; } else if (args[i].startsWith("--") || args[i].startsWith("-")) { // the argument cannot be a negative number because we checked earlier // -> the next argument is a parameter name map.put(key, NO_VALUE_KEY); } else { map.put(key, args[i]); i += 1; } } return fromMap(map); } /** * Returns {@link ParameterTool} for the given {@link Properties} file. * * @param path Path to the properties file * @return A {@link ParameterTool} * @throws IOException If the file does not exist * @see Properties */ public static ParameterTool fromPropertiesFile(String path) throws IOException { File propertiesFile = new File(path); return fromPropertiesFile(propertiesFile); } /** * Returns {@link ParameterTool} for the given {@link Properties} file. * * @param file File object to the properties file * @return A {@link ParameterTool} * @throws IOException If the file does not exist * @see Properties */ public static ParameterTool fromPropertiesFile(File file) throws IOException { if (!file.exists()) { throw new FileNotFoundException( "Properties file " + file.getAbsolutePath() + " does not exist"); } try (FileInputStream fis = new FileInputStream(file)) { return fromPropertiesFile(fis); } } /** * Returns {@link ParameterTool} for the given InputStream from {@link Properties} file. * * @param inputStream InputStream from the properties file * @return A {@link ParameterTool} * @throws IOException If the file does not exist * @see Properties */ public static ParameterTool fromPropertiesFile(InputStream inputStream) throws IOException { Properties props = new Properties(); props.load(inputStream); return fromMap((Map) props); } /** * Returns {@link ParameterTool} for the given map. * * @param map A map of arguments. Both Key and Value have to be Strings * @return A {@link ParameterTool} */ public static ParameterTool fromMap(Map map) { checkNotNull(map, "Unable to initialize from empty map"); return new ParameterTool(map); } /** * Returns {@link ParameterTool} from the system properties. Example on how to pass system * properties: -Dkey1=value1 -Dkey2=value2 * * @return A {@link ParameterTool} */ public static ParameterTool fromSystemProperties() { return fromMap((Map) System.getProperties()); } // ------------------ ParameterUtil ------------------------ protected final Map data; private ParameterTool(Map data) { this.data = Collections.unmodifiableMap(new HashMap<>(data)); this.defaultData = new ConcurrentHashMap<>(data.size()); this.unrequestedParameters = Collections.newSetFromMap(new ConcurrentHashMap<>(data.size())); unrequestedParameters.addAll(data.keySet()); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ParameterTool that = (ParameterTool) o; return Objects.equals(data, that.data) && Objects.equals(defaultData, that.defaultData) && Objects.equals(unrequestedParameters, that.unrequestedParameters); } @Override public int hashCode() { return Objects.hash(data, defaultData, unrequestedParameters); } // ------------------ Get data from the util ---------------- /** * Returns number of parameters in {@link ParameterTool}. */ public int getNumberOfParameters() { return data.size(); } /** * Returns the String value for the given key. If the key does not exist it will return null. */ public String get(String key) { addToDefaults(key, null); unrequestedParameters.remove(key); return data.get(key); } public String getOrDefault(String key, String defaultValue) { String s = get(key); if (s == null) { return defaultValue; } else { return s; } } /** * Check if value is set. */ public boolean has(String value) { addToDefaults(value, null); unrequestedParameters.remove(value); return data.containsKey(value); } /** * Returns a {@link Properties} object from this {@link ParameterTool}. * * @return A {@link Properties} */ public Properties getProperties() { Properties props = new Properties(); props.putAll(this.data); return props; } /** * Create a properties file with all the known parameters (call after the last get*() call). Set * the default value, if available. * *

Use this method to create a properties file skeleton. * * @param pathToFile Location of the default properties file. */ public void createPropertiesFile(String pathToFile) throws IOException { createPropertiesFile(pathToFile, true); } /** * Create a properties file with all the known parameters (call after the last get*() call). Set * the default value, if overwrite is true. * * @param pathToFile Location of the default properties file. * @param overwrite Boolean flag indicating whether or not to overwrite the file * @throws IOException If overwrite is not allowed and the file exists */ public void createPropertiesFile(String pathToFile, boolean overwrite) throws IOException { final File file = new File(pathToFile); if (file.exists()) { if (overwrite) { file.delete(); } else { throw new RuntimeException( "File " + pathToFile + " exists and overwriting is not allowed"); } } final Properties defaultProps = new Properties(); defaultProps.putAll(this.defaultData); try (final OutputStream out = new FileOutputStream(file)) { defaultProps.store( out, "Default file created by Flink's ParameterUtil.createPropertiesFile()"); } } @Override protected Object clone() throws CloneNotSupportedException { return new ParameterTool(this.data); } // ------------------------- Interaction with other ParameterUtils ------------------------- /** * Merges two {@link ParameterTool}. * * @param other Other {@link ParameterTool} object * @return The Merged {@link ParameterTool} */ public ParameterTool mergeWith(ParameterTool other) { final Map resultData = new HashMap<>(data.size() + other.data.size()); resultData.putAll(data); resultData.putAll(other.data); final ParameterTool ret = new ParameterTool(resultData); final HashSet requestedParametersLeft = new HashSet<>(data.keySet()); requestedParametersLeft.removeAll(unrequestedParameters); final HashSet requestedParametersRight = new HashSet<>(other.data.keySet()); requestedParametersRight.removeAll(other.unrequestedParameters); ret.unrequestedParameters.removeAll(requestedParametersLeft); ret.unrequestedParameters.removeAll(requestedParametersRight); return ret; } // ------------------------- ExecutionConfig.UserConfig interface ------------------------- public Map toMap() { return data; } // ------------------------- Serialization --------------------------------------------- private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); defaultData = new ConcurrentHashMap<>(data.size()); unrequestedParameters = Collections.newSetFromMap(new ConcurrentHashMap<>(data.size())); } public static String getKeyFromArgs(String[] args, int index) { String key; if (args[index].startsWith("--")) { key = args[index].substring(2); } else if (args[index].startsWith("-")) { key = args[index].substring(1); } else { throw new IllegalArgumentException( String.format( "Error parsing arguments '%s' on '%s'. Please prefix keys with -- or -.", Arrays.toString(args), args[index])); } if (key.isEmpty()) { throw new IllegalArgumentException( "The input " + Arrays.toString(args) + " contains an empty argument"); } return key; } public static boolean isNumber(final String str) { if (isEmpty(str)) { return false; } final char[] chars = str.toCharArray(); int sz = chars.length; boolean hasExp = false; boolean hasDecPoint = false; boolean allowSigns = false; boolean foundDigit = false; // deal with any possible sign up front final int start = (chars[0] == '-') ? 1 : 0; if (sz > start + 1 && chars[start] == '0') { // leading 0 if ( (chars[start + 1] == 'x') || (chars[start + 1] == 'X') ) { // leading 0x/0X int i = start + 2; if (i == sz) { return false; // str == "0x" } // checking hex (it can't be anything else) for (; i < chars.length; i++) { if ((chars[i] < '0' || chars[i] > '9') && (chars[i] < 'a' || chars[i] > 'f') && (chars[i] < 'A' || chars[i] > 'F')) { return false; } } return true; } else if (Character.isDigit(chars[start + 1])) { // leading 0, but not hex, must be octal int i = start + 1; for (; i < chars.length; i++) { if (chars[i] < '0' || chars[i] > '7') { return false; } } return true; } } sz--; // don't want to loop to the last char, check it afterwords // for type qualifiers int i = start; // loop to the next to last char or to the last char if we need another digit to // make a valid number (e.g. chars[0..5] = "1234E") while (i < sz || (i < sz + 1 && allowSigns && !foundDigit)) { if (chars[i] >= '0' && chars[i] <= '9') { foundDigit = true; allowSigns = false; } else if (chars[i] == '.') { if (hasDecPoint || hasExp) { // two decimal points or dec in exponent return false; } hasDecPoint = true; } else if (chars[i] == 'e' || chars[i] == 'E') { // we've already taken care of hex. if (hasExp) { // two E's return false; } if (!foundDigit) { return false; } hasExp = true; allowSigns = true; } else if (chars[i] == '+' || chars[i] == '-') { if (!allowSigns) { return false; } allowSigns = false; foundDigit = false; // we need a digit after the E } else { return false; } i++; } if (i < chars.length) { if (chars[i] >= '0' && chars[i] <= '9') { // no type qualifier, OK return true; } if (chars[i] == 'e' || chars[i] == 'E') { // can't have an E at the last byte return false; } if (chars[i] == '.') { if (hasDecPoint || hasExp) { // two decimal points or dec in exponent return false; } // single trailing decimal point after non-exponent is ok return foundDigit; } if (!allowSigns && (chars[i] == 'd' || chars[i] == 'D' || chars[i] == 'f' || chars[i] == 'F')) { return foundDigit; } if (chars[i] == 'l' || chars[i] == 'L') { // not allowing L with an exponent or decimal point return foundDigit && !hasExp && !hasDecPoint; } // last character is illegal return false; } // allowSigns is true iff the val ends in 'E' // found digit it to make sure weird stuff like '.' and '1E-' doesn't pass return !allowSigns && foundDigit; } public static boolean isEmpty(final CharSequence cs) { return cs == null || cs.length() == 0; } protected void addToDefaults(String key, String value) { final String currentValue = defaultData.get(key); if (currentValue == null) { if (value == null) { value = DEFAULT_UNDEFINED; } defaultData.put(key, value); } else { // there is already an entry for this key. Check if the value is the undefined if (currentValue.equals(DEFAULT_UNDEFINED) && value != null) { // update key with better default value defaultData.put(key, value); } } } public static T checkNotNull(@Nullable T reference, @Nullable String errorMessage) { if (reference == null) { throw new NullPointerException(String.valueOf(errorMessage)); } return reference; } }