blob: fe56932a4e9ab9e293592a7e2706f95ffbd23fb1 [file] [log] [blame]
// Copyright 2006, 2007 The Apache Software Foundation
//
// 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 org.apache.tapestry5.internal.services;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.Field;
import org.apache.tapestry5.FieldValidator;
import org.apache.tapestry5.Validator;
import org.apache.tapestry5.ioc.MessageFormatter;
import org.apache.tapestry5.ioc.Messages;
import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList;
import static org.apache.tapestry5.ioc.internal.util.Defense.cast;
import static org.apache.tapestry5.ioc.internal.util.Defense.notBlank;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.services.TypeCoercer;
import org.apache.tapestry5.runtime.Component;
import org.apache.tapestry5.services.FieldValidatorSource;
import org.apache.tapestry5.services.FormSupport;
import org.apache.tapestry5.services.ValidationMessagesSource;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class FieldValidatorSourceImpl implements FieldValidatorSource
{
private final ValidationMessagesSource messagesSource;
private final Map<String, Validator> validators;
private final TypeCoercer typeCoercer;
private final FormSupport formSupport;
public FieldValidatorSourceImpl(ValidationMessagesSource messagesSource, TypeCoercer typeCoercer,
FormSupport formSupport, Map<String, Validator> validators)
{
this.messagesSource = messagesSource;
this.typeCoercer = typeCoercer;
this.formSupport = formSupport;
this.validators = validators;
}
public FieldValidator createValidator(Field field, String validatorType, String constraintValue)
{
Component component = cast(field, Component.class, "field");
notBlank(validatorType, "validatorType");
ComponentResources componentResources = component.getComponentResources();
String overrideId = componentResources.getId();
Locale locale = componentResources.getLocale();
// So, if you use a TextField on your EditUser page, we want to search the messages
// of the EditUser page (the container), not the TextField (which will always be the same).
Messages overrideMessages = componentResources.getContainerMessages();
return createValidator(field, validatorType, constraintValue, overrideId, overrideMessages, locale);
}
public FieldValidator createValidator(Field field, String validatorType, String constraintValue, String overrideId,
Messages overrideMessages, Locale locale)
{
notBlank(validatorType, "validatorType");
Validator validator = validators.get(validatorType);
if (validator == null) throw new IllegalArgumentException(
ServicesMessages.unknownValidatorType(validatorType, InternalUtils.sortedKeys(validators)));
// I just have this thing about always treating parameters as finals, so
// we introduce a second variable to treat a mutable.
String finalConstraintValue = constraintValue;
// If no constraint was provided, check to see if it is available via a localized message
// key. This is really handy for complex validations such as patterns.
if (finalConstraintValue == null && validator.getConstraintType() != null)
{
String key = overrideId + "-" + validatorType;
if (overrideMessages.contains(key)) finalConstraintValue = overrideMessages.get(key);
else throw new IllegalArgumentException(
ServicesMessages.missingValidatorConstraint(validatorType, validator.getConstraintType()));
}
Object coercedConstraintValue = coerceConstraintValue(finalConstraintValue, validator
.getConstraintType());
MessageFormatter formatter = findMessageFormatter(overrideId, overrideMessages, locale, validatorType,
validator);
return new FieldValidatorImpl(field, coercedConstraintValue, formatter, validator, formSupport);
}
private MessageFormatter findMessageFormatter(String overrideId, Messages overrideMessages, Locale locale,
String validatorType, Validator validator)
{
String overrideKey = overrideId + "-" + validatorType + "-message";
if (overrideMessages.contains(overrideKey)) return overrideMessages.getFormatter(overrideKey);
Messages messages = messagesSource.getValidationMessages(locale);
String key = validator.getMessageKey();
return messages.getFormatter(key);
}
public FieldValidator createValidators(Field field, String specification)
{
List<ValidatorSpecification> specs = parse(specification);
List<FieldValidator> fieldValidators = newList();
for (ValidatorSpecification spec : specs)
{
fieldValidators.add(createValidator(field, spec.getValidatorType(), spec
.getConstraintValue()));
}
if (fieldValidators.size() == 1) return fieldValidators.get(0);
return new CompositeFieldValidator(fieldValidators);
}
@SuppressWarnings("unchecked")
private Object coerceConstraintValue(String constraintValue, Class constraintType)
{
if (constraintType == null) return null;
return typeCoercer.coerce(constraintValue, constraintType);
}
/**
* A code defining what the parser is looking for.
*/
enum State
{
/**
* The start of a validator type.
*/
TYPE_START,
/**
* The end of a validator type.
*/
TYPE_END,
/**
* Equals sign after a validator type, or a comma.
*/
EQUALS_OR_COMMA,
/**
* The start of a constraint value.
*/
VALUE_START,
/**
* The end of the constraint value.
*/
VALUE_END,
/**
* The comma after a constraint value.
*/
COMMA
}
static List<ValidatorSpecification> parse(String specification)
{
List<ValidatorSpecification> result = newList();
char[] input = specification.toCharArray();
int cursor = 0;
int start = -1;
String type = null;
boolean skipWhitespace = true;
State state = State.TYPE_START;
while (cursor < input.length)
{
char ch = input[cursor];
if (skipWhitespace && Character.isWhitespace(ch))
{
cursor++;
continue;
}
skipWhitespace = false;
switch (state)
{
case TYPE_START:
if (Character.isLetter(ch))
{
start = cursor;
state = State.TYPE_END;
break;
}
parseError(cursor, specification);
case TYPE_END:
if (Character.isLetter(ch))
{
break;
}
type = specification.substring(start, cursor);
skipWhitespace = true;
state = State.EQUALS_OR_COMMA;
continue;
case EQUALS_OR_COMMA:
if (ch == '=')
{
skipWhitespace = true;
state = State.VALUE_START;
break;
}
if (ch == ',')
{
result.add(new ValidatorSpecification(type));
type = null;
state = State.COMMA;
continue;
}
parseError(cursor, specification);
case VALUE_START:
start = cursor;
state = State.VALUE_END;
break;
case VALUE_END:
// The value ends when we hit whitespace or a comma
if (Character.isWhitespace(ch) || ch == ',')
{
String value = specification.substring(start, cursor);
result.add(new ValidatorSpecification(type, value));
type = null;
skipWhitespace = true;
state = State.COMMA;
continue;
}
break;
case COMMA:
if (ch == ',')
{
skipWhitespace = true;
state = State.TYPE_START;
break;
}
parseError(cursor, specification);
} // case
cursor++;
} // while
// cursor is now one character past end of string.
// Cleanup whatever state we were in the middle of.
switch (state)
{
case TYPE_END:
type = specification.substring(start);
case EQUALS_OR_COMMA:
result.add(new ValidatorSpecification(type));
break;
// Case when the specification ends with an equals sign.
case VALUE_START:
result.add(new ValidatorSpecification(type, ""));
break;
case VALUE_END:
result.add(new ValidatorSpecification(type, specification.substring(start)));
break;
// For better or worse, ending the string with a comma is valid.
default:
}
return result;
}
private static void parseError(int cursor, String specification)
{
throw new RuntimeException(ServicesMessages.validatorSpecificationParseError(cursor, specification));
}
}