blob: a6224af8ae09ded6b2abeddce1208678f610b99b [file] [log] [blame]
/*
* 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.felix.metatype;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.osgi.service.metatype.AttributeDefinition;
/**
* Provides various validation routines used by the {@link AD#validate(String)}
* method.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
final class ADValidator
{
/**
* Validates a given input string according to the type specified by the given attribute
* definition.
* <p>
* The validation is done in the following way:
* </p>
* <ul>
* <li>If the input is undefined (ie. <code>null</code>), and the attribute is mandatory, the
* validation fails due to a missing value. If the attribute is optional, the input is
* accepted;</li>
* <li>If the input represents a <em>boolean</em> value, it is tested whether it is defined (in
* case of non-zero cardinality) and represents either <tt>"true"</tt> or <tt>"false"</tt>. The
* minimum and maximum parameters are <b>not</b> used in this validation;</li>
* <li>If the input represents a <em>character</em> value, it is tested whether it is defined
* (in case of non-zero cardinality). The character value must be defined within the character
* range specified by the minimum and maximum parameters (if defined);</li>
* <li>If the input represents a <em>numeric</em> value, it is tested whether it is defined (in
* case of non-zero cardinality). The numeric value must be defined within the numeric range
* specified by the minimum and maximum parameters (if defined);</li>
* <li>If the input represents a <em>string</em> or <em>password</em>, it is tested whether it
* is defined (in case of non-zero cardinality). The length of the string value must be in the
* range specified by the minimum and maximum parameters (if defined).</li>
* </ul>
* <p>
* For all types of attributes, if it defines option values, the input should be present as one
* of the defined option values.
* </p>
*
* @param ad
* the attribute definition to use in the validation;
* @param rawInput
* the raw input value to validate.
* @return <code>null</code> if no validation is available, <tt>""</tt> if
* validation was successful, or any other non-empty string in case
* validation fails.
*/
public static String validate(AD ad, String rawInput)
{
// Handle the case in which the given input is not defined...
if (rawInput == null)
{
if (ad.isRequired())
{
return AD.VALIDATE_MISSING;
}
return ""; // accept null value...
}
// Raw input is defined, validate it further
String[] input;
if (ad.getCardinality() == 0)
{
input = new String[] { rawInput.trim() };
}
else
{
input = AD.splitList(rawInput);
}
int type = ad.getType();
switch (type)
{
case AttributeDefinition.BOOLEAN:
return validateBooleanValue(ad, input);
case AttributeDefinition.CHARACTER:
return validateCharacterValue(ad, input);
case AttributeDefinition.BIGDECIMAL:
case AttributeDefinition.BIGINTEGER:
case AttributeDefinition.BYTE:
case AttributeDefinition.DOUBLE:
case AttributeDefinition.FLOAT:
case AttributeDefinition.INTEGER:
case AttributeDefinition.LONG:
case AttributeDefinition.SHORT:
return validateNumericValue(ad, input);
case AttributeDefinition.PASSWORD:
case AttributeDefinition.STRING:
return validateString(ad, input);
default:
return null; // no validation present...
}
}
/**
* Searches for a given search value in a given array of options.
*
* @param searchValue
* the value to search for;
* @param optionValues
* the values to search in.
* @return <code>null</code> if the given search value is not found in the
* given options, the searched value if found, or <tt>""</tt> if no
* search value or options were given.
*/
private static String findOptionValue(String searchValue, String[] optionValues)
{
if ((searchValue == null) || (optionValues == null) || (optionValues.length == 0))
{
// indicates that we've not searched...
return "";
}
for (int i = 0; i < optionValues.length; i++)
{
if (optionValues[i].equals(searchValue))
{
return optionValues[i];
}
}
return null;
}
/**
* Parses a given string value into a numeric type.
*
* @param type
* the type to parse;
* @param value
* the value to parse.
* @return a {@link Number} representation of the given value, or
* <code>null</code> if the input was <code>null</code>, empty, or
* not a numeric type.
* @throws NumberFormatException
* in case the given value cannot be parsed as numeric value.
*/
private static Comparable parseNumber(int type, String value) throws NumberFormatException
{
if ((value != null) && (value.length() > 0))
{
switch (type)
{
case AttributeDefinition.BIGDECIMAL:
return new BigDecimal(value);
case AttributeDefinition.BIGINTEGER:
return new BigInteger(value);
case AttributeDefinition.BYTE:
return Byte.valueOf(value);
case AttributeDefinition.SHORT:
return Short.valueOf(value);
case AttributeDefinition.INTEGER:
return Integer.valueOf(value);
case AttributeDefinition.LONG:
return Long.valueOf(value);
case AttributeDefinition.FLOAT:
return Float.valueOf(value);
case AttributeDefinition.DOUBLE:
return Double.valueOf(value);
default:
return null;
}
}
return null;
}
/**
* Parses a given string value as character, allowing <code>null</code>
* -values and empty values to be given as input.
*
* @param value
* the value to parse as character, can be <code>null</code> or
* an empty value.
* @return the character value if, and only if, the given input was non-
* <code>null</code> and a non-empty string.
*/
private static Character parseOptionalChar(String value)
{
if ((value != null) && (value.length() > 0))
{
return Character.valueOf(value.charAt(0));
}
return null;
}
/**
* Parses a given string value as numeric value, allowing
* <code>null</code>-values and invalid numeric values to be given as
* input.
*
* @param type the type of number, should only be a numeric type;
* @param value
* the value to parse as integer, can be <code>null</code> or a
* non-numeric value.
* @return the integer value if, and only if, the given input was non-
* <code>null</code> and a valid integer representation.
*/
private static Comparable parseOptionalNumber(int type, String value)
{
if (value != null)
{
try
{
return parseNumber(type, value);
}
catch (NumberFormatException e)
{
// Ignore; invalid value...
}
}
return null;
}
/**
* Validates a given input string as boolean value.
*
* @param ad
* the attribute definition to use in the validation;
* @param input
* the array with input values to validate.
* @return <code>null</code> if no validation is available, <tt>""</tt> if
* validation was successful, or any other non-empty string in case
* validation fails.
*/
private static String validateBooleanValue(AD ad, String[] input)
{
for (int i = 0; i < input.length; i++)
{
String value = input[i];
int length = (value == null) ? 0 : value.length();
if ((length == 0) && ad.isRequired())
{
return AD.VALIDATE_MISSING;
}
else if (length > 0 && !"true".equalsIgnoreCase(value) && !"false".equalsIgnoreCase(value))
{
return AD.VALIDATE_INVALID_VALUE;
}
}
String[] optionValues = ad.getOptionValues();
if ((optionValues != null) && (optionValues.length > 0))
{
return null; // no validation possible for this type...
}
return ""; // accept given value...
}
/**
* Validates a given input string as character value.
*
* @param ad
* the attribute definition to use in the validation;
* @param input
* the array with input values to validate.
* @return <code>null</code> if no validation is available, <tt>""</tt> if
* validation was successful, or any other non-empty string in case
* validation fails.
*/
private static String validateCharacterValue(AD ad, String[] input)
{
Character min = parseOptionalChar(ad.getMin());
Character max = parseOptionalChar(ad.getMax());
String[] optionValues = ad.getOptionValues();
for (int i = 0; i < input.length; i++)
{
Character ch = null;
int length = (input[i] == null) ? 0 : input[i].length();
if (length > 1)
{
return AD.VALIDATE_GREATER_THAN_MAXIMUM;
}
else if ((length == 0) && ad.isRequired())
{
return AD.VALIDATE_MISSING;
}
else if (length == 1)
{
ch = Character.valueOf(input[i].charAt(0));
// Check whether the minimum value is adhered for all values...
if ((min != null) && (ch.compareTo(min) < 0))
{
return AD.VALIDATE_LESS_THAN_MINIMUM;
}
// Check whether the maximum value is adhered for all values...
if ((max != null) && (ch.compareTo(max) > 0))
{
return AD.VALIDATE_GREATER_THAN_MAXIMUM;
}
}
if (findOptionValue(input[i], optionValues) == null)
{
return AD.VALIDATE_NOT_A_VALID_OPTION;
}
}
return ""; // accept given value...
}
/**
* Validates a given input string as numeric value.
*
* @param ad
* the attribute definition to use in the validation;
* @param input
* the array with input values to validate.
* @return <code>null</code> if no validation is available, <tt>""</tt> if
* validation was successful, or any other non-empty string in case
* validation fails.
*/
private static String validateNumericValue(AD ad, String[] input)
{
Comparable min = parseOptionalNumber(ad.getType(), ad.getMin());
Comparable max = parseOptionalNumber(ad.getType(), ad.getMax());
String[] optionValues = ad.getOptionValues();
for (int i = 0; i < input.length; i++)
{
Comparable value = null;
try
{
value = parseNumber(ad.getType(), input[i]);
}
catch (NumberFormatException e)
{
return AD.VALIDATE_INVALID_VALUE;
}
if ((value == null) && ad.isRequired())
{
// Possible if the cardinality != 0 and input was something like
// "0,,1"...
return AD.VALIDATE_MISSING;
}
// Check whether the minimum value is adhered for all values...
if ((min != null) && (value != null) && (value.compareTo(min) < 0))
{
return AD.VALIDATE_LESS_THAN_MINIMUM;
}
// Check whether the maximum value is adhered for all values...
if ((max != null) && (value != null) && (value.compareTo(max) > 0))
{
return AD.VALIDATE_GREATER_THAN_MAXIMUM;
}
if (findOptionValue(input[i], optionValues) == null)
{
return AD.VALIDATE_NOT_A_VALID_OPTION;
}
}
return ""; // accept given value...
}
/**
* Validates a given input string as string (or password).
*
* @param ad
* the attribute definition to use in the validation;
* @param input
* the array with input values to validate.
* @return <code>null</code> if no validation is available, <tt>""</tt> if
* validation was successful, or any other non-empty string in case
* validation fails.
*/
private static String validateString(AD ad, String[] input)
{
// The length() method of a string yields an Integer, so the maximum string length is 2^31-1...
Integer min = (Integer) parseOptionalNumber(AttributeDefinition.INTEGER, ad.getMin());
Integer max = (Integer) parseOptionalNumber(AttributeDefinition.INTEGER, ad.getMax());
String[] optionValues = ad.getOptionValues();
for (int i = 0; i < input.length; i++)
{
String value = input[i];
final int length = value == null ? 0 : value.length();
if (ad.isRequired() && value == null)
{
return AD.VALIDATE_MISSING;
}
// Check whether the minimum length is adhered for all values...
if ((min != null) && (length < min.intValue()))
{
return AD.VALIDATE_LESS_THAN_MINIMUM;
}
// Check whether the maximum length is adhered for all values...
if ((max != null) && (length > max.intValue()))
{
return AD.VALIDATE_GREATER_THAN_MAXIMUM;
}
if (findOptionValue(value, optionValues) == null)
{
return AD.VALIDATE_NOT_A_VALID_OPTION;
}
}
return ""; // accept given value...
}
}