/*
* Copyright (C) 2011 Google Inc.
* 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 com.google.api.explorer.client.editors;
import com.google.api.explorer.client.base.Schema;
import com.google.api.explorer.client.base.Schema.Type;
import com.google.api.explorer.client.base.UrlEncoder;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import java.math.BigInteger;
import java.util.List;
/**
* Factory for {@link Editor}s that will produce the appropriate editor based on
* the properties of the parameter being edited. The factory will also add
* appropriate {@link Validator}s.
*
* @author jasonhall@google.com (Jason Hall)
*/
public class EditorFactory {
private static final List<String> TRUE_FALSE =
ImmutableList.of(Boolean.TRUE.toString(), Boolean.FALSE.toString());
@VisibleForTesting
static UrlEncoder urlEncoder = UrlEncoder.DEFAULT;
/**
* Identifies the relevant {@link Editor} implementation for the given
* {@link Schema}.
*/
public static Editor forParameter(Schema parameter) {
return forParameter(parameter, false);
}
/**
* Identifies the revelant {@link Editor} implementation for the given
* parameter, ignoring whether the parameter is repeated.
*
* @param ignoreRepeated is necessary because a {@link RepeatedEditor} wraps
* an inner {@link Editor} implementation to display repeatedly. When
* ignoreRepeated is true, this method is used to identify the
* correct inner editor to use.
*/
static Editor forParameter(Schema parameter, boolean ignoreRepeated) {
if (!ignoreRepeated && parameter.isRepeated()) {
return new RepeatedEditor(forParameter(parameter, true));
}
// In most cases we'll just want to use a BasicEditor.
Editor editor = new BasicEditor();
Type type = parameter.getType();
// If the parameter expects a boolean value, use an EnumEditor with values
// "true" and "false".
if (type == Type.BOOLEAN) {
editor = new EnumEditor(TRUE_FALSE, null);
}
// Get possible enum values and descriptions. If enum values are specified,
// then use an EnumEditor.
List<String> enumValues = parameter.getEnumValues();
List<String> enumDescriptions = parameter.getEnumDescriptions();
if (enumValues != null && !enumValues.isEmpty()) {
editor = new EnumEditor(enumValues, enumDescriptions);
}
// If the parameter expects an integer value, add an IntegerValidator.
String minimum = parameter.getMinimum();
String maximum = parameter.getMaximum();
final String pattern = parameter.getPattern();
maybeAddValidatorTo(editor, new IntegerValidator(), type == Type.INTEGER);
maybeAddValidatorTo(
editor, new MinimumMaximumValidator(minimum, maximum), minimum != null || maximum != null);
maybeAddValidatorTo(editor, new DecimalValidator(), type == Type.NUMBER);
maybeAddValidatorTo(editor, new RequiredValidator(), parameter.isRequired());
maybeAddValidatorTo(editor, new PatternValidator(pattern), pattern != null);
editor.addValidator(new UrlEncodingValidator());
return editor;
}
/**
* Conditionally adds the given validator to the given editor, if the
* condition is true.
*/
private static void maybeAddValidatorTo(Editor editor, Validator validator, boolean condition) {
if (condition) {
editor.addValidator(validator);
}
}
@VisibleForTesting
static final class MinimumMaximumValidator implements Validator {
private final BigInteger minimum;
private final BigInteger maximum;
MinimumMaximumValidator(String minimum, String maximum) {
this.minimum = minimum == null ? null : new BigInteger(minimum);
this.maximum = maximum == null ? null : new BigInteger(maximum);
}
/**
* Returns true if all values are parseable integers within the bounds of
* the minimum and maximum.
*/
@Override
public ValidationResult isValid(List<String> values) {
for (String value : values) {
if (value.isEmpty()) {
continue;
}
BigInteger val;
try {
val = new BigInteger(value);
} catch (NumberFormatException nfe) {
return SimpleValidationResult.createError("Must be an integer.");
}
if (minimum != null && val.compareTo(minimum) == -1) {
return SimpleValidationResult.createError("Must be >= " + minimum);
}
if (maximum != null && val.compareTo(maximum) == 1) {
return SimpleValidationResult.createError("Must be <= " + maximum);
}
}
return SimpleValidationResult.STATUS_VALID;
}
}
@VisibleForTesting
static class IntegerValidator extends PatternValidator {
private static final String INTEGER_PATTERN = "-?[0-9]+";
public IntegerValidator() {
super(INTEGER_PATTERN);
}
}
@VisibleForTesting
static final class DecimalValidator extends PatternValidator {
private static final String DECIMAL_PATTERN = "-?[0-9]+(\\.[0-9]+)?";
public DecimalValidator() {
super(DECIMAL_PATTERN);
}
}
@VisibleForTesting
static class PatternValidator implements Validator {
@VisibleForTesting
final String pattern;
private PatternValidator(String pattern) {
this.pattern = pattern;
}
/** Returns true if all values are either empty or match the pattern. */
@Override
public ValidationResult isValid(List<String> values) {
for (String value : values) {
if (!value.isEmpty() && !value.matches(pattern)) {
return SimpleValidationResult.createError("Must match pattern " + pattern);
}
}
return SimpleValidationResult.STATUS_VALID;
}
}
@VisibleForTesting
static final class RequiredValidator implements Validator {
/**
* Returns true if at least one string is given, and none of the values are
* empty.
*/
@Override
public ValidationResult isValid(List<String> values) {
if (values.isEmpty()) {
return SimpleValidationResult.createError("Required parameter.");
}
for (String value : values) {
if (value.isEmpty()) {
return SimpleValidationResult.createError("Required parameter.");
}
}
return SimpleValidationResult.STATUS_VALID;
}
}
@VisibleForTesting
static final class UrlEncodingValidator implements Validator {
/**
* Returns invalid if url encoding made a change to the string
*/
@Override
public ValidationResult isValid(List<String> values) {
if (!values.isEmpty()) {
for (String value : values) {
if (value != null) {
boolean neededEncoding = !value.equals(urlEncoder.encodePathSegment(value));
if (neededEncoding) {
return SimpleValidationResult.createInfo("This parameter was URL encoded.");
}
}
}
}
return SimpleValidationResult.STATUS_VALID;
}
}
}