All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.flink.api.java.utils.RequiredParameters Maven / Gradle / Ivy

There is a newer version: 1.20.0
Show newest version
/*
 * 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.List;
import java.util.LinkedList;
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.
	 * @throws RequiredParametersException if any of the specified checks fail
	 */
	public void applyTo(ParameterTool parameterTool) throws RequiredParametersException {
		List missingArguments = new LinkedList<>();
		for (Option o : data.values()) {
			if (parameterTool.data.containsKey(o.getName())) {
				if (Objects.equals(parameterTool.data.get(o.getName()), ParameterTool.NO_VALUE_KEY)) {
					// the parameter has been passed, but no value, check if there is a default value
					checkAndApplyDefaultValue(o, parameterTool.data);
				} else {
					// a value has been passed in the parameterTool, now check if it adheres to all constraints
					checkAmbiguousValues(o, parameterTool.data);
					checkIsCastableToDefinedType(o, parameterTool.data);
					checkChoices(o, parameterTool.data);
				}
			} else {
				// check if there is a default name or a value passed for a possibly defined alternative name.
				if (hasNoDefaultValueAndNoValuePassedOnAlternativeName(o, parameterTool.data)) {
					missingArguments.add(o.getName());
				}
			}
		}
		if (!missingArguments.isEmpty()) {
			throw new RequiredParametersException(this.missingArgumentsText(missingArguments), missingArguments);
		}
	}

	// 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 which 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();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy