/* Copyright 2005-2006 Tim Fennell
*
* Licensed 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 net.sourceforge.stripes.validation;
import java.util.Set;
import java.util.HashSet;
import java.util.regex.Pattern;
/**
* <p>Encapsulates the validation metadata for a single property of a single class. Structure
* is purposely very similar to the @Validate annotation. This class is used internally
* to store and manipulate validation metadata, the source of which is often validation
* annotations.</p>
*
* <p>However, since this class is not an annotation it has the added benefits of being
* able to contain behaviour, being subclassable, and of being able to be instantiated at
* runtime - i.e. it can contain non-static validation information.</p>
*
* @author Tim Fennell
* @since Stripes 1.5
*/
public class ValidationMetadata {
private String property;
private boolean encrypted;
private boolean required;
private boolean trim;
private Set<String> on;
private boolean onIsPositive;
private boolean ignore;
private Integer minlength, maxlength;
private Double minvalue, maxvalue;
private Pattern mask;
private String expression;
@SuppressWarnings("rawtypes")
private Class<? extends TypeConverter> converter;
private String label;
/**
* Constructs a ValidationMetadata object for the specified property. Further constraints
* can be specified by calling individual methods, e.g.
* {@code new ValidationMetadata("username").minlength(5).maxlength(10);}
*
* @param property the name of the property to be validated. If the property is a nested
* property then the fully path should be included, e.g. {@code user.address.city}
*/
public ValidationMetadata(String property) {
this.property = property;
}
/**
* Essentially a copy constructor that constructs a ValidationMetadata object from
* an @Validate annotation declared on a property.
*
* @param validate
*/
public ValidationMetadata(String property, Validate validate) {
// Copy over all the simple values
this.property = property;
encrypted(validate.encrypted());
required(validate.required());
trim(validate.trim());
ignore(validate.ignore());
if (validate.minlength() != -1) minlength(validate.minlength());
if (validate.maxlength() != -1) maxlength(validate.maxlength());
if (validate.minvalue() != Double.MIN_VALUE) minvalue(validate.minvalue());
if (validate.maxvalue() != Double.MAX_VALUE) maxvalue(validate.maxvalue());
if (!"".equals(validate.mask())) mask(validate.mask());
if (validate.converter() != TypeConverter.class) converter(validate.converter());
if (!"".equals(validate.expression())) expression(validate.expression());
if (validate.on().length > 0) on(validate.on());
if (!"".equals(validate.label())) this.label = validate.label();
}
/** Returns the name of the property this validation metadata represents. */
public String getProperty() {
return this.property;
}
/** Sets the encrypted flag for this field. True = encrypted, false = plain text. */
public ValidationMetadata encrypted(boolean encrypted) {
this.encrypted = encrypted;
return this;
}
/** Returns true if the field in question is encrypted. */
public boolean encrypted() { return encrypted; }
/** Sets the required-ness of this field. True = required, false = not required. */
public ValidationMetadata required(boolean required) {
this.required = required;
return this;
}
/** Returns true if the field in question is required. */
public boolean required() { return this.required; }
/** Sets the trim flag of this field. True = trim, false = don't trim. */
public ValidationMetadata trim(boolean trim) {
this.trim = trim;
return this;
}
/** Returns true if the field should be trimmed before validation or type conversion. */
public boolean trim() { return this.trim; }
/** Returns true if the field is required when processing the specified event. */
public boolean requiredOn(String event) {
return this.required && !this.ignore && (
(this.on == null) ||
(this.onIsPositive && this.on.contains(event)) ||
(!this.onIsPositive && !this.on.contains(event))
);
}
/** Sets whether or not this field should be ignored during binding and validation. */
public ValidationMetadata ignore(boolean ignore) {
this.ignore = ignore;
return this;
}
/** Returns true if this field should be ignored in binding and validation. */
public boolean ignore() { return this.ignore; }
/** Sets the minimum acceptable length for property values. */
public ValidationMetadata minlength(Integer minlength) {
this.minlength = minlength;
return this;
}
/** Returns the minimum acceptable length for values, or null if there is none. */
public Integer minlength() { return this.minlength; }
/** Sets the maximum acceptable length for property values. */
public ValidationMetadata maxlength(Integer maxlength) {
this.maxlength = maxlength;
return this;
}
/** Returns the maximum acceptable length for values, or null if there is none. */
public Integer maxlength() { return this.maxlength; }
/** Sets the minimum acceptable <b>value</b> for numeric property values. */
public ValidationMetadata minvalue(Double minvalue) {
this.minvalue = minvalue;
return this;
}
/** Returns the minimum acceptable value for numeric properties, or null if there is none. */
public Double minvalue() { return this.minvalue; }
/** Sets the maximum acceptable <b>value</b> for numeric property values. */
public ValidationMetadata maxvalue(Double maxvalue) {
this.maxvalue = maxvalue;
return this;
}
/** Returns the maximum acceptable value for numeric properties, or null if there is none. */
public Double maxvalue() { return this.maxvalue; }
/** Sets the mask which the String form of the property must match. */
public ValidationMetadata mask(String mask) {
this.mask = Pattern.compile(mask);
return this;
}
/** Returns the mask Pattern property values must match, or null if there is none. */
public Pattern mask() { return this.mask; }
/** Sets the overridden TypeConveter to use to convert values. */
@SuppressWarnings("rawtypes")
public ValidationMetadata converter(Class<? extends TypeConverter> converter) {
this.converter = converter;
return this;
}
/** Returns the overridden TypeConverter if there is one, or null. */
@SuppressWarnings("rawtypes")
public Class<? extends TypeConverter> converter() { return this.converter; }
/** Sets the expression that should be used to validate values. */
public ValidationMetadata expression(String expression) {
this.expression = expression;
if (!this.expression.startsWith("${")) this.expression = "${" + this.expression + "}";
return this;
}
/** Returns the overridden TypeConverter if there is one, or null. */
public String expression() { return this.expression; }
/** Sets the set of events for which the field in question is required, if it is at all. */
public ValidationMetadata on(String... on) {
if (on.length == 0) {
this.on = null;
}
else {
// Check for empty strings in the "on" element
for (String s : on) {
if (s.length() == 0 || "!".equals(s)) {
throw new IllegalArgumentException(
"@Validate's \"on\" element must not contain empty strings");
}
}
this.on = new HashSet<String>();
this.onIsPositive = !(on[0].charAt(0) == '!');
for (String s : on) {
if (this.onIsPositive) {
this.on.add(s);
}
else {
this.on.add(s.substring(1));
}
}
}
return this;
}
/** Returns the set of events for which the field in question is required. May return null. */
public Set<String> on() { return this.on; }
/** Set the field label. */
public void label(String label) { this.label = label;}
/** Get the field label. */
public String label() { return label; }
/**
* Overidden toString() that only outputs the constraints that are specified by
* the instance of validation metadata (i.e. omits nulls, defaults etc.)
*
* @return a human readable string form of the metadata
*/
@Override
public String toString() {
return "ValidationMetadata{" +
(required ? "required=" + required : "") +
(encrypted ? "encrypted=" + encrypted : "") +
(ignore ? ", ignore=" + ignore : "" ) +
(minlength != null ? ", minlength=" + minlength : "") +
(maxlength != null ? ", maxlength=" + maxlength : "") +
(minvalue != null ? ", minvalue=" + minvalue : "") +
(maxvalue != null ? ", maxvalue=" + maxvalue : "") +
(mask != null ? ", mask=" + mask : "" ) +
(expression != null ? ", expression='" + expression + '\'' : "") +
(converter != null ? ", converter=" + converter.getSimpleName() : "") +
(label != null ? ", label='" + label + '\'' : "") +
'}';
}
}