org.apache.flink.api.java.utils.RequiredParameters Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.flink.api.java.utils;
import org.apache.flink.annotation.PublicEvolving;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Facility to manage required parameters in user defined functions.
*/
@PublicEvolving
public class RequiredParameters {
private static final String HELP_TEXT_PARAM_DELIMITER = "\t";
private static final String HELP_TEXT_LINE_DELIMITER = "\n";
private static final int HELP_TEXT_LENGTH_PER_PARAM = 100;
private HashMap data;
public RequiredParameters() {
this.data = new HashMap<>();
}
/**
* Add a parameter based on its name.
*
* @param name - the name of the parameter
* @return - an {@link Option} object representing the parameter
* @throws RequiredParametersException if an option with the same name is already defined
*/
public Option add(String name) throws RequiredParametersException {
if (!this.data.containsKey(name)) {
Option option = new Option(name);
this.data.put(name, option);
return option;
} else {
throw new RequiredParametersException("Option with key " + name + " already exists.");
}
}
/**
* Add a parameter encapsulated in an {@link Option} object.
*
* @param option - the parameter
* @throws RequiredParametersException if an option with the same name is already defined
*/
public void add(Option option) throws RequiredParametersException {
if (!this.data.containsKey(option.getName())) {
this.data.put(option.getName(), option);
} else {
throw new RequiredParametersException("Option with key " + option.getName() + " already exists.");
}
}
/**
* Check for all required parameters defined:
* - has a value been passed
* - if not, does the parameter have an associated default value
* - does the type of the parameter match the one defined in RequiredParameters
* - does the value provided in the parameterTool adhere to the choices defined in the option.
*
* If any check fails, a RequiredParametersException is thrown
*
* @param parameterTool - parameters supplied by the user.
* @return the updated ParameterTool containing all the required parameters
* @throws RequiredParametersException if any of the specified checks fail
*/
public ParameterTool applyTo(ParameterTool parameterTool) throws RequiredParametersException {
List missingArguments = new LinkedList<>();
HashMap newParameters = new HashMap<>(parameterTool.toMap());
for (Option o : data.values()) {
if (newParameters.containsKey(o.getName())) {
if (Objects.equals(newParameters.get(o.getName()), ParameterTool.NO_VALUE_KEY)) {
// the parameter has been passed, but no value, check if there is a default value
checkAndApplyDefaultValue(o, newParameters);
} else {
// a value has been passed in the parameterTool, now check if it adheres to all constraints
checkAmbiguousValues(o, newParameters);
checkIsCastableToDefinedType(o, newParameters);
checkChoices(o, newParameters);
}
} else {
// check if there is a default name or a value passed for a possibly defined alternative name.
if (hasNoDefaultValueAndNoValuePassedOnAlternativeName(o, newParameters)) {
missingArguments.add(o.getName());
}
}
}
if (!missingArguments.isEmpty()) {
throw new RequiredParametersException(this.missingArgumentsText(missingArguments), missingArguments);
}
return ParameterTool.fromMap(newParameters);
}
// check if the given parameter has a default value and add it to the passed map if that is the case
// else throw an exception
private void checkAndApplyDefaultValue(Option o, Map data) throws RequiredParametersException {
if (hasNoDefaultValueAndNoValuePassedOnAlternativeName(o, data)) {
throw new RequiredParametersException("No default value for undefined parameter " + o.getName());
}
}
// check if the value in the given map which corresponds to the name of the given option
// is castable to the type of the option (if any is defined)
private void checkIsCastableToDefinedType(Option o, Map data) throws RequiredParametersException {
if (o.hasType() && !o.isCastableToDefinedType(data.get(o.getName()))) {
throw new RequiredParametersException("Value for parameter " + o.getName() +
" cannot be cast to type " + o.getType());
}
}
// check if the value in the given map which corresponds to the name of the given option
// adheres to the list of given choices for the param in the options (if any are defined)
private void checkChoices(Option o, Map data) throws RequiredParametersException {
if (o.getChoices().size() > 0 && !o.getChoices().contains(data.get(o.getName()))) {
throw new RequiredParametersException("Value " + data.get(o.getName()) +
" is not in the list of valid choices for key " + o.getName());
}
}
// move value passed on alternative name to standard name or apply default value if any defined
// else return true to indicate parameter is 'really' missing
private boolean hasNoDefaultValueAndNoValuePassedOnAlternativeName(Option o, Map data)
throws RequiredParametersException {
if (o.hasAlt() && data.containsKey(o.getAlt())) {
data.put(o.getName(), data.get(o.getAlt()));
} else {
if (o.hasDefaultValue()) {
data.put(o.getName(), o.getDefaultValue());
if (o.hasAlt()) {
data.put(o.getAlt(), o.getDefaultValue());
}
} else {
return true;
}
}
return false;
}
// given that the map contains a value for the name of the option passed
// check if it also contains a value for the shortName in option (if any is defined)
private void checkAmbiguousValues(Option o, Map data) throws RequiredParametersException{
if (data.containsKey(o.getAlt()) && !Objects.equals(data.get(o.getAlt()), ParameterTool.NO_VALUE_KEY)) {
throw new RequiredParametersException("Value passed for parameter " + o.getName() +
" is ambiguous. Value passed for short and long name.");
}
}
/**
* Build a help text for the defined parameters.
*
* The format of the help text will be:
* Required Parameters:
* \t -:shortName:, --:name: \t :helpText: \t default: :defaultValue: \t choices: :choices: \n
*
* @return a formatted help String.
*/
public String getHelp() {
StringBuilder sb = new StringBuilder(data.size() * HELP_TEXT_LENGTH_PER_PARAM);
sb.append("Required Parameters:");
sb.append(HELP_TEXT_LINE_DELIMITER);
for (Option o : data.values()) {
sb.append(this.helpText(o));
}
sb.append(HELP_TEXT_LINE_DELIMITER);
return sb.toString();
}
/**
* Build a help text for the defined parameters and list the missing arguments at the end of the text.
*
*
The format of the help text will be:
* Required Parameters:
* \t -:shortName:, --:name: \t :helpText: \t default: :defaultValue: \t choices: :choices: \n
*
*
Missing parameters:
* \t param1 param2 ... paramN
*
* @param missingArguments - a list of missing parameters
* @return a formatted help String.
*/
public String getHelp(List missingArguments) {
return this.getHelp() + this.missingArgumentsText(missingArguments);
}
/**
* for the given option create a line for the help text. The line looks like:
* \t -:shortName:, --:name: \t :helpText: \t default: :defaultValue: \t choices: :choices:
*/
private String helpText(Option option) {
StringBuilder sb = new StringBuilder(HELP_TEXT_LENGTH_PER_PARAM);
sb.append(HELP_TEXT_PARAM_DELIMITER);
// if there is a short name, add it.
if (option.hasAlt()) {
sb.append("-");
sb.append(option.getAlt());
sb.append(", ");
}
// add the name
sb.append("--");
sb.append(option.getName());
sb.append(HELP_TEXT_PARAM_DELIMITER);
// if there is a help text, add it
if (option.getHelpText() != null) {
sb.append(option.getHelpText());
sb.append(HELP_TEXT_PARAM_DELIMITER);
}
// if there is a default value, add it.
if (option.hasDefaultValue()) {
sb.append("default: ");
sb.append(option.getDefaultValue());
sb.append(HELP_TEXT_PARAM_DELIMITER);
}
// if there is a list of choices add it.
if (!option.getChoices().isEmpty()) {
sb.append("choices: ");
for (String choice : option.getChoices()) {
sb.append(choice);
sb.append(" ");
}
}
sb.append(HELP_TEXT_LINE_DELIMITER);
return sb.toString();
}
private String missingArgumentsText(List missingArguments) {
StringBuilder sb = new StringBuilder(missingArguments.size() * 10);
sb.append("Missing arguments for:");
sb.append(HELP_TEXT_LINE_DELIMITER);
for (String arg : missingArguments) {
sb.append(arg);
sb.append(" ");
}
return sb.toString();
}
}