All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.github.bhowell2.apilib.ApiSingleParam Maven / Gradle / Ivy
package io.github.bhowell2.apilib;
import io.github.bhowell2.apilib.checks.Check;
import io.github.bhowell2.apilib.errors.ApiParamError;
import io.github.bhowell2.apilib.formatters.Formatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Used to check a single parameter. The value of the parameter is retrieved from the map
* with {@link #keyName} and then passed to any provided {@link Formatter}s and then {@link Check}s.
* If any formatter or check fails an error message may be returned describing the reason for
* failure - if no failure message is returned, a default message will be returned (these can
* be overridden, see {@link ApiLibSettings}). If the parameter can be set to null, this should
* be set with {@link #canBeNull} - in this case, the null value will be passed to the formatters
* (if they exist) and they can return a non-null value if desired.
*
* @author Blake Howell
*/
public class ApiSingleParam extends ApiParamBase, ApiSingleParam.Result> {
/**
* Creates a builder for {@link ApiSingleParam}. A key name is required for a
* single parameter so that the parameter can be retrieved from the Map input
* into {@link #check(Map)}.
*
* canBeNull defaults to false
* displayName defaults to null
* invalidErrorMessage defaults to null
*
* @param keyName name used to retrieve the parameter from a Map. cannot be null.
* @param the parameter's type
* @return a builder for the parameter
*/
public static Builder builder(String keyName) {
return new Builder<>(keyName);
}
/**
* Creates a builder for {@link ApiSingleParam}. A key name is required for a
* single parameter so that the parameter can be retrieved from the Map input
* into {@link #check(Map)}.
*
* canBeNull defaults to false
* displayName defaults to null
* invalidErrorMessage defaults to null
*
* @param keyName name used to retrieve the parameter from a Map. cannot be null.
* @param paramType allows for providing the param type as a parameter rather than
* parameterizing the method call or casting.
* @param the parameter's type (will be the Class provided as parameter)
* @return a builder for the parameter
*/
public static Builder builder(String keyName, Class paramType) {
return builder(keyName);
}
/**
* Creates a builder for {@link ApiSingleParam} that uses all of the fields
* from the supplied ApiSingleParam to copy. A key name is required for a
* single parameter so that the parameter can be retrieved from the Map input
* into {@link #check(Map)}.
*
* The copied fields include: keyName, displayName, canBeNull,
* invalidErrorMessage, formatters, and checks.
*
* @param copyFrom the single parameter to copy
* @param the parameter's type
* @return a builder for the parameter
*/
public static Builder builder(ApiSingleParam copyFrom) {
return new Builder<>(copyFrom.keyName, copyFrom);
}
/**
* Creates a builder for {@link ApiSingleParam} that uses all of the fields
* from the supplied ApiSingleParam to copy, but uses a different key name.
* A key name is required for a single parameter so that the parameter can
* be retrieved from the Map input into {@link #check(Map)}.
*
* The copied fields include: displayName, canBeNull, invalidErrorMessage,
* formatters, and checks.
*
* @param keyName name used to retrieve the parameter from a Map. cannot be null.
* @param copyFrom the single parameter to copy
* @param the parameter's type
* @return a builder for the parameter
*/
public static Builder builder(String keyName, ApiSingleParam copyFrom) {
return new Builder<>(keyName, copyFrom);
}
final Formatter super Object, ? super Object>[] formatters;
final Check [] checks;
public static class Builder extends ApiParamBase.Builder<
ApiSingleParam ,
Builder
> {
private List> checks;
private List> formatters;
public Builder(String keyName) {
super(keyName);
Objects.requireNonNull(keyName, "keyName for ApiSingleParam cannot be null.");
this.checks = new ArrayList<>(1);
this.formatters = new ArrayList<>();
}
public Builder(String keyName, ApiSingleParam copyFrom) {
this(keyName);
// checks can never be null or empty
this.checks = new ArrayList<>(Arrays.asList(copyFrom.checks));
this.formatters = arrayIsNotNullOrEmpty(copyFrom.formatters)
? new ArrayList<>(Arrays.asList(copyFrom.formatters))
: new ArrayList<>();
}
@SafeVarargs
@SuppressWarnings("varargs")
public final Builder addChecks(Check ... checks) {
checkVarArgsNotNullAndValuesNotNull(checks);
this.checks.addAll(Arrays.asList(checks));
return this;
}
/**
* Formatters are applied in the order they are added, so if a later formatter
* depends on the parameter being of a different type then it must come after
* the formatter that cast the parameter to a different type.
*/
@SuppressWarnings("varargs")
public Builder addFormatters(Formatter, ?>... formatters) {
checkVarArgsNotNullAndValuesNotNull(formatters);
if (this.formatters == null) {
this.formatters = new ArrayList<>(formatters.length);
}
this.formatters.addAll(Arrays.asList(formatters));
return this;
}
public ApiSingleParam build() {
// check key name
if (keyName == null) {
throw new IllegalArgumentException("Cannot create ApiSingleParam with null keyName.");
}
// ensure some checks are provided
if (checks == null || checks.size() == 0) {
throw new RuntimeException("No checks were provided for parameter (key name) '" + keyName + "'."
+ " If this is intentional, provide a check that always passes or "
+ "always fails. If the check should always pass Check.alwaysPass() "
+ "has been provided for this case and Check.alwaysFail() has been "
+ "provided if the parameter check should always fail.");
}
return new ApiSingleParam<>(this);
}
}
@SuppressWarnings("unchecked")
private ApiSingleParam(Builder builder) {
super(builder);
this.checks = builder.checks.toArray(new Check[0]);
this.formatters = listIsNotNullOrEmpty(builder.formatters)
? builder.formatters.toArray(new Formatter[0])
: null;
}
private Result returnInvalidErrorMessage(String errMsg) {
// if invalid error message is set it overrides all other error messages
if (this.invalidErrorMessage != null) {
return Result.failure(ApiParamError.invalid(this, this.invalidErrorMessage));
} else {
return errMsg == null
?
Result.failure(ApiParamError.invalid(this))
:
Result.failure(ApiParamError.invalid(this, errMsg));
}
}
@Override
@SuppressWarnings("unchecked")
public Result check(Map params) {
try {
/*
* Due to type erasure this is really just (Object) here. Will not see any casting issues until param is
* passed to formatters or checks that cast to a specific class.
* */
Param param = (Param) params.get(this.keyName);
if (param == null) {
if (params.containsKey(this.keyName)) {
// was SET to null, not just null because it was not set at all
if (canBeNull) {
return Result.success(this.keyName);
} else {
return returnInvalidErrorMessage(ApiLibSettings.DEFAULT_CANNOT_BE_NULL_MESSAGE);
}
} else {
// param is null, but was not SET to null. therefore it is missing.
return Result.failure(ApiParamError.missing(this));
}
}
/*
* It has been decided to avoid re-inserting the formatted value until all checks pass.
* This allows the developer to more easily trace where possible errors occurred.
* Formatters could make irreversible changes to an object and therefore the developer
* would not be able to deduce the original value from the formatted value.
* */
boolean formatted = false;
if (this.formatters != null && this.formatters.length > 0) {
formatted = true;
Object paramToFormat = param;
for (Formatter super Object, ? super Object> formatter : this.formatters) {
Formatter.Result formatResult = formatter.format(paramToFormat);
if (formatResult.failed()) {
return formatResult.hasFailureMessage()
?
Result.failure(ApiParamError.format(this, formatResult.getFailureMessage()))
:
Result.failure(ApiParamError.format(this));
}
paramToFormat = formatResult.getFormattedValue();
}
param = (Param) paramToFormat;
}
/*
* Possible that the formatter set the param to null or that the formatter did
* not do anything with it and it already was null. In either case there are no
* possible checks to run on a null value, so return here.
* */
if (param == null) {
if (canBeNull) {
return Result.success(this.keyName);
} else {
return returnInvalidErrorMessage(ApiLibSettings.DEFAULT_CANNOT_BE_NULL_MESSAGE);
}
}
for (Check check : checks) {
Check.Result checkResult = check.check(param);
if (checkResult.failed()) {
return returnInvalidErrorMessage(checkResult.failureMessage);
}
}
// parameter was formatted in some way and successfully passed all checks. put back in map for user to access later
if (formatted) {
params.put(this.keyName, param);
}
return Result.success(this.keyName);
} catch (ClassCastException e) {
return Result.failure(ApiParamError.cast(this, e));
} catch (Exception e) {
return Result.failure(ApiParamError.exceptional(this, e));
}
}
/**
* Copies all settings (checks, formatters, and canBeNull) from this ApiSingleParam
* and overwrites the keyName and displayName.
* @param keyName the new builder's key name
* @param displayName the new builder's display name
* @return the builder for a new ApiSingleParam with THIS parameter's checks.
*/
public Builder toBuilder(String keyName, String displayName) {
return ApiSingleParam.builder(keyName, this)
.setDisplayName(displayName);
}
/**
* Result for {@link ApiSingleParam#check(Map)}.
*/
public static class Result extends ApiParamBase.Result {
public Result(String keyName) {
super(keyName);
}
public Result(ApiParamError error) {
super(error);
}
public static Result success(String keyName) {
return new Result(keyName);
}
public static Result failure(ApiParamError error) {
return new Result(error);
}
}
}