/*
* Copyright 2001-2013 Stephen Colebourne
*
* 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.joda.beans.ui.swing.component;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Locale;
import java.util.regex.Pattern;
/**
* Common text fields.
*/
public final class JValidatedTextFields {
/** Error message key. */
private static final ErrorStatus ERROR_BYTE_INVALID = ErrorStatus.of("Error.Byte.invalid");
/** Error message key. */
private static final ErrorStatus ERROR_SHORT_INVALID = ErrorStatus.of("Error.Short.invalid");
/** Error message key. */
private static final ErrorStatus ERROR_INTEGER_INVALID = ErrorStatus.of("Error.Integer.invalid");
/** Error message key. */
private static final ErrorStatus ERROR_LONG_INVALID = ErrorStatus.of("Error.Long.invalid");
/** Error message key. */
private static final ErrorStatus ERROR_FLOAT_INVALID = ErrorStatus.of("Error.Float.invalid");
/** Error message key. */
private static final ErrorStatus ERROR_FLOAT_NAN = ErrorStatus.of("Error.Float.nan");
/** Error message key. */
private static final ErrorStatus ERROR_DOUBLE_INVALID = ErrorStatus.of("Error.Double.invalid");
/** Error message key. */
private static final ErrorStatus ERROR_DOUBLE_NAN = ErrorStatus.of("Error.Double.nan");
/** Error message key. */
private static final ErrorStatus ERROR_BIG_INTEGER_INVALID = ErrorStatus.of("Error.BigInteger.invalid");
/** Error message key. */
private static final ErrorStatus ERROR_BIG_DECIMAL_INVALID = ErrorStatus.of("Error.BigDecimal.invalid");
/**
* Restricted constructor.
*/
private JValidatedTextFields() {
}
//-------------------------------------------------------------------------
/**
* Creates a {@code JTextField} that validates a {@code String} value.
*
* @param mandatory true if mandatory
* @param maxLength the maximum length of the text, one or more
* @return the text field, not null
*/
public static JValidatedTextField createStringTextField(final boolean mandatory, final int maxLength) {
return new JValidatedTextField(new StringValidator(mandatory, maxLength));
}
//-------------------------------------------------------------------------
/**
* Creates a {@code JTextField} that validates a {@code Byte} value.
*
* @param mandatory true if mandatory
* @return the text field, not null
*/
public static JValidatedTextField createByteTextField(final boolean mandatory) {
return createLongTextField(mandatory, Byte.MIN_VALUE, Byte.MAX_VALUE);
}
/**
* Creates a {@code JTextField} that validates a {@code Byte} value.
*
* @param mandatory true if mandatory
* @param minInclusive the minimum valid value, inclusive
* @param maxInclusive the maximum valid value, inclusive
* @return the text field, not null
*/
public static JValidatedTextField createByteTextField(final boolean mandatory, final byte minInclusive, final byte maxInclusive) {
return new JValidatedTextField(new IntegralValidator(ERROR_BYTE_INVALID, 4, mandatory, minInclusive, maxInclusive));
}
//-------------------------------------------------------------------------
/**
* Creates a {@code JTextField} that validates a {@code Short} value.
*
* @param mandatory true if mandatory
* @return the text field, not null
*/
public static JValidatedTextField createShortTextField(final boolean mandatory) {
return createLongTextField(mandatory, Short.MIN_VALUE, Short.MAX_VALUE);
}
/**
* Creates a {@code JTextField} that validates a {@code Short} value.
*
* @param mandatory true if mandatory
* @param minInclusive the minimum valid value, inclusive
* @param maxInclusive the maximum valid value, inclusive
* @return the text field, not null
*/
public static JValidatedTextField createShortTextField(final boolean mandatory, final short minInclusive, final short maxInclusive) {
return new JValidatedTextField(new IntegralValidator(ERROR_SHORT_INVALID, 6, mandatory, minInclusive, maxInclusive));
}
//-------------------------------------------------------------------------
/**
* Creates a {@code JTextField} that validates an {@code Integer} value.
*
* @param mandatory true to allow the empty string on exit,
* true for {@code Integer}, false for {@code int}
* @return the text field, not null
*/
public static JValidatedTextField createIntegerTextField(final boolean mandatory) {
return createLongTextField(mandatory, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
/**
* Creates a {@code JTextField} that validates an {@code Integer} value.
*
* @param mandatory true if mandatory
* @param minInclusive the minimum valid value, inclusive
* @param maxInclusive the maximum valid value, inclusive
* @return the text field, not null
*/
public static JValidatedTextField createIntegerTextField(final boolean mandatory, final int minInclusive, final int maxInclusive) {
return new JValidatedTextField(new IntegralValidator(ERROR_INTEGER_INVALID, 11, mandatory, minInclusive, maxInclusive));
}
//-------------------------------------------------------------------------
/**
* Creates a {@code JTextField} that validates a {@code Long} value.
*
* @param mandatory true to allow the empty string on exit,
* true for {@code Long}, false for {@code long}
* @return the text field, not null
*/
public static JValidatedTextField createLongTextField(final boolean mandatory) {
return createLongTextField(mandatory, Long.MIN_VALUE, Long.MAX_VALUE);
}
/**
* Creates a {@code JTextField} that validates a {@code Long} value.
*
* @param mandatory true if mandatory
* @param minInclusive the minimum valid value, inclusive
* @param maxInclusive the maximum valid value, inclusive
* @return the text field, not null
*/
public static JValidatedTextField createLongTextField(final boolean mandatory, final long minInclusive, final long maxInclusive) {
return new JValidatedTextField(new IntegralValidator(ERROR_LONG_INVALID, 20, mandatory, minInclusive, maxInclusive));
}
//-------------------------------------------------------------------------
/**
* Creates a {@code JTextField} that validates a {@code Float} value.
*
* @param mandatory true if mandatory
* @return the text field, not null
*/
public static JValidatedTextField createFloatTextField(final boolean mandatory) {
return createDoubleTextField(mandatory, true, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
}
/**
* Creates a {@code JTextField} that validates a {@code Float} value.
*
* @param mandatory true if mandatory
* @param allowNaN true to allow NaN
* @param minInclusive the minimum valid value, inclusive
* @param maxInclusive the maximum valid value, inclusive
* @return the text field, not null
*/
public static JValidatedTextField createFloatTextField(final boolean mandatory, final boolean allowNaN, final float minInclusive, final float maxInclusive) {
return new JValidatedTextField(new FloatingPointValidator(ERROR_FLOAT_INVALID, ERROR_FLOAT_NAN, mandatory, allowNaN, minInclusive, maxInclusive));
}
//-------------------------------------------------------------------------
/**
* Creates a {@code JTextField} that validates a {@code Double} value.
*
* @param mandatory true if mandatory
* @return the text field, not null
*/
public static JValidatedTextField createDoubleTextField(final boolean mandatory) {
return createDoubleTextField(mandatory, true, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
}
/**
* Creates a {@code JTextField} that validates a {@code Double} value.
*
* @param mandatory true if mandatory
* @param allowNaN true to allow NaN
* @param minInclusive the minimum valid value, inclusive
* @param maxInclusive the maximum valid value, inclusive
* @return the text field, not null
*/
public static JValidatedTextField createDoubleTextField(final boolean mandatory, final boolean allowNaN, final double minInclusive, final double maxInclusive) {
return new JValidatedTextField(new FloatingPointValidator(ERROR_DOUBLE_INVALID, ERROR_DOUBLE_NAN, mandatory, allowNaN, minInclusive, maxInclusive));
}
//-------------------------------------------------------------------------
/**
* Creates a {@code JTextField} that validates a {@code BigInteger} value.
*
* @param mandatory true if mandatory
* @return the text field, not null
*/
public static JValidatedTextField createBigIntegerTextField(final boolean mandatory) {
return createBigIntegerTextField(mandatory, null, null);
}
/**
* Creates a {@code JTextField} that validates a {@code BigInteger} value.
*
* @param mandatory true if mandatory
* @param minInclusive the minimum valid value, inclusive, null for no limit
* @param maxInclusive the maximum valid value, inclusive, null for no limit
* @return the text field, not null
*/
public static JValidatedTextField createBigIntegerTextField(final boolean mandatory, final BigInteger minInclusive, final BigInteger maxInclusive) {
return new JValidatedTextField(new IntegralValidator(ERROR_BIG_INTEGER_INVALID, 128, mandatory, minInclusive, maxInclusive));
}
//-------------------------------------------------------------------------
/**
* Creates a {@code JTextField} that validates a {@code BigDecimal} value.
*
* @param mandatory true if mandatory
* @return the text field, not null
*/
public static JValidatedTextField createBigDecimalTextField(final boolean mandatory) {
return createBigDecimalTextField(mandatory, null, null);
}
/**
* Creates a {@code JTextField} that validates a {@code BigDecimal} value.
*
* @param mandatory true if mandatory
* @param minInclusive the minimum valid value, inclusive, null for no limit
* @param maxInclusive the maximum valid value, inclusive, null for no limit
* @return the text field, not null
*/
public static JValidatedTextField createBigDecimalTextField(final boolean mandatory, final BigDecimal minInclusive, final BigDecimal maxInclusive) {
return new JValidatedTextField(new BigDecimalValidator(ERROR_BIG_DECIMAL_INVALID, mandatory, minInclusive, maxInclusive));
}
//-------------------------------------------------------------------------
private static final class StringValidator extends DefaultJTextFieldValidator {
/** Valid characters for double. */
private static final Pattern VALID = Pattern.compile(".*");
private StringValidator(boolean mandatory, int maxLength) {
super(mandatory, maxLength, VALID);
}
}
//-------------------------------------------------------------------------
private static final class IntegralValidator extends DefaultJTextFieldValidator {
/** Valid characters for integral. */
private static final Pattern VALID = Pattern.compile("[+-]?[0-9]*");
/** The error to use when invalid. */
private final ErrorStatus errorWhenInvalid;
/** The minimum value. */
private final BigInteger minInclusive;
/** The maximum value. */
private final BigInteger maxInclusive;
private IntegralValidator(ErrorStatus errorWhenInvalid, int maxSize, boolean mandatory, long minInclusive, long maxInclusive) {
super(mandatory, maxSize, VALID);
this.errorWhenInvalid = errorWhenInvalid;
this.minInclusive = BigInteger.valueOf(minInclusive);
this.maxInclusive = BigInteger.valueOf(maxInclusive);
}
private IntegralValidator(ErrorStatus errorWhenInvalid, int maxSize, boolean mandatory, BigInteger minInclusive, BigInteger maxInclusive) {
super(mandatory, maxSize, VALID);
this.errorWhenInvalid = errorWhenInvalid;
this.minInclusive = minInclusive;
this.maxInclusive = maxInclusive;
}
@Override
protected ErrorStatus checkStatus(String text, boolean onExit) {
if (onExit && isMandatory() && text.isEmpty()) {
return ErrorStatus.MANDATORY;
}
if (text.isEmpty() || text.equals("+") || text.equals("-")) {
return ErrorStatus.VALID;
}
text = (text.startsWith("+") ? text.substring(1) : text);
try {
BigInteger value = new BigInteger(text);
if ((minInclusive != null && value.compareTo(minInclusive) < 0) ||
(maxInclusive != null && value.compareTo(maxInclusive) > 0)) {
return ErrorStatus.range(minInclusive + " - " + maxInclusive);
}
return ErrorStatus.VALID;
} catch (NumberFormatException ex) {
return errorWhenInvalid;
}
}
@Override
protected String onExit(String text, ErrorStatus status) {
if (status.isValid()) {
if (text.isEmpty() || text.equals("+") || text.equals("-")) {
return "";
}
return text.replace("+", "");
}
return text;
}
}
//-------------------------------------------------------------------------
private static final class FloatingPointValidator extends DefaultJTextFieldValidator {
/** Valid characters for double. */
private static final Pattern VALID = Pattern.compile("[0-9eE.+-]*[fd]?");
/** The error to use when invalid. */
private final ErrorStatus errorWhenInvalid;
/** The error to use when invalid. */
private final ErrorStatus errorWhenNan;
/** Whether to allow NaN. */
private final boolean allowNaN;
/** The minimum value. */
private final double minInclusive;
/** The maximum value. */
private final double maxInclusive;
private FloatingPointValidator(ErrorStatus errorWhenInvalid, ErrorStatus errorWhenNan,
boolean mandatory, boolean allowNaN, double minInclusive, double maxInclusive) {
super(mandatory, 128, VALID);
this.errorWhenInvalid = errorWhenInvalid;
this.errorWhenNan = errorWhenNan;
this.minInclusive = minInclusive;
this.maxInclusive = maxInclusive;
this.allowNaN = allowNaN;
}
@Override
protected boolean validateChange(String text) {
if ("NaN".startsWith(text) || "Infinity".startsWith(text) || "-Infinity".startsWith(text)) {
return true;
}
return super.validateChange(text);
}
@Override
protected ErrorStatus checkStatus(String text, boolean onExit) {
if (onExit && isMandatory() && text.isEmpty()) {
return ErrorStatus.MANDATORY;
}
if (allowNaN == false && "NaN".startsWith(text)) {
return errorWhenNan;
}
String upper = text.toUpperCase(Locale.US);
if (text.isEmpty() || text.equals("+") || text.equals("-") || text.equals(".") ||
(upper.endsWith("E") && upper.endsWith("EE") == false) ||
(upper.endsWith("E-") && upper.endsWith("EE-") == false) ||
(upper.endsWith("E+") && upper.endsWith("EE+") == false) ||
"NaN".startsWith(text)) {
return ErrorStatus.VALID;
}
if ("Infinity".startsWith(text)) {
if (maxInclusive < Double.POSITIVE_INFINITY) {
return ErrorStatus.range(minInclusive + " - " + maxInclusive);
}
return ErrorStatus.VALID;
}
if ("-Infinity".startsWith(text)) {
if (minInclusive > Double.NEGATIVE_INFINITY) {
return ErrorStatus.range(minInclusive + " - " + maxInclusive);
}
return ErrorStatus.VALID;
}
try {
double value;
if (errorWhenInvalid == ERROR_DOUBLE_INVALID) {
value = Double.parseDouble(text);
if (value == Double.POSITIVE_INFINITY || value == Double.NEGATIVE_INFINITY) {
return errorWhenInvalid;
}
} else {
value = Float.parseFloat(text);
if (value == Float.POSITIVE_INFINITY || value == Float.NEGATIVE_INFINITY) {
return errorWhenInvalid;
}
}
if (value < minInclusive || value > maxInclusive) {
return ErrorStatus.range(minInclusive + " - " + maxInclusive);
}
return ErrorStatus.VALID;
} catch (NumberFormatException ex) {
return errorWhenInvalid;
}
}
@Override
protected String onExit(String text, ErrorStatus status) {
if (status.isValid()) {
if (text.isEmpty() || text.equals("+") || text.equals("-") || text.equals(".")) {
return "";
}
if ("NaN".startsWith(text)) {
text = "NaN";
}
if ("Infinity".startsWith(text)) {
text = "Infinity";
}
if ("-Infinity".startsWith(text)) {
text = "-Infinity";
}
String upper = text.toUpperCase(Locale.US);
text = (upper.endsWith("E-") ? text.substring(0, text.length() - 2) : text); // ignore incomplete trailing exponent
text = (upper.endsWith("E+") ? text.substring(0, text.length() - 2) : text); // ignore incomplete trailing exponent
text = (upper.endsWith("E") ? text.substring(0, text.length() - 1) : text); // ignore incomplete trailing exponent
text = (upper.endsWith("D") ? text.substring(0, text.length() - 1) : text); // Java doubles can end in 'd'
text = (upper.endsWith("F") ? text.substring(0, text.length() - 1) : text); // Java doubles can end in 'f'
text = (text.endsWith(".0") ? text.substring(0, text.length() - 2) : text);
return text.replace('E', 'e').replace(".0e", "e").replace("+", "");
}
return text;
}
}
//-------------------------------------------------------------------------
private static final class BigDecimalValidator extends DefaultJTextFieldValidator {
/** Valid characters for double. */
private static final Pattern VALID = Pattern.compile("[0-9eE.+-]*[fd]?");
/** The error to use when invalid. */
private final ErrorStatus errorWhenInvalid;
/** The minimum value. */
private final BigDecimal minInclusive;
/** The maximum value. */
private final BigDecimal maxInclusive;
private BigDecimalValidator(ErrorStatus errorWhenInvalid, boolean mandatory, BigDecimal minInclusive, BigDecimal maxInclusive) {
super(mandatory, 128, VALID);
this.errorWhenInvalid = errorWhenInvalid;
this.minInclusive = minInclusive;
this.maxInclusive = maxInclusive;
}
@Override
protected ErrorStatus checkStatus(String text, boolean onExit) {
if (onExit && isMandatory() && text.isEmpty()) {
return ErrorStatus.MANDATORY;
}
String upper = text.toUpperCase(Locale.US);
text = (upper.endsWith("D") ? text.substring(0, text.length() - 1) : text); // Java doubles can end in 'd'
text = (upper.endsWith("F") ? text.substring(0, text.length() - 1) : text); // Java doubles can end in 'f'
upper = text.toUpperCase(Locale.US);
if (text.isEmpty() || text.equals("+") || text.equals("-") || text.equals(".") ||
(upper.endsWith("E") && upper.endsWith("EE") == false) ||
(upper.endsWith("E-") && upper.endsWith("EE-") == false) ||
(upper.endsWith("E+") && upper.endsWith("EE+") == false)) {
return ErrorStatus.VALID;
}
try {
BigDecimal value = new BigDecimal(text);
if ((minInclusive != null && value.compareTo(minInclusive) < 0) ||
(maxInclusive != null && value.compareTo(maxInclusive) > 0)) {
return ErrorStatus.range(minInclusive + " - " + maxInclusive);
}
return ErrorStatus.VALID;
} catch (NumberFormatException ex) {
return errorWhenInvalid;
}
}
@Override
protected String onExit(String text, ErrorStatus status) {
if (status.isValid()) {
if (text.isEmpty() || text.equals("+") || text.equals("-") || text.equals(".")) {
return "";
}
String upper = text.toUpperCase(Locale.US);
text = (upper.endsWith("E-") ? text.substring(0, text.length() - 2) : text); // ignore incomplete trailing exponent
text = (upper.endsWith("E+") ? text.substring(0, text.length() - 2) : text); // ignore incomplete trailing exponent
text = (upper.endsWith("E") ? text.substring(0, text.length() - 1) : text); // ignore incomplete trailing exponent
text = (upper.endsWith("D") ? text.substring(0, text.length() - 1) : text); // Java doubles can end in 'd'
text = (upper.endsWith("F") ? text.substring(0, text.length() - 1) : text); // Java doubles can end in 'f'
text = (text.endsWith(".0") ? text.substring(0, text.length() - 2) : text);
return text.replace('E', 'e').replace(".0e", "e").replace("+", "");
}
return text;
}
}
}