package com.revolsys.swing.field;
import java.awt.Color;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.math.BigDecimal;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.jdesktop.swingx.JXTextField;
import com.revolsys.awt.WebColors;
import com.revolsys.datatype.DataType;
import com.revolsys.datatype.DataTypes;
import com.revolsys.swing.SwingUtil;
import com.revolsys.swing.listener.WeakFocusListener;
import com.revolsys.swing.menu.MenuFactory;
import com.revolsys.swing.parallel.Invoke;
import com.revolsys.util.Exceptions;
import com.revolsys.util.Property;
import com.revolsys.util.number.BigDecimals;
import com.revolsys.util.number.Numbers;
public class NumberTextField extends JXTextField implements Field, DocumentListener, FocusListener {
private static final long serialVersionUID = 1L;
private static int getLength(final DataType dataType, int length, final int scale,
final BigDecimal minimumValue) {
if (length == 0) {
final Class<?> javaClass = dataType.getJavaClass();
if (javaClass == Byte.class) {
length = 3;
} else if (javaClass == Short.class) {
length = 5;
} else if (javaClass == Integer.class) {
length = 10;
} else if (javaClass == Long.class) {
length = 19;
} else if (javaClass == Float.class) {
length = 10;
} else if (javaClass == Double.class) {
length = 20;
} else {
length = 20;
}
}
if (minimumValue == null || new BigDecimal("0").compareTo(minimumValue) > 0) {
length++;
}
if (scale > 0) {
length++;
}
return length;
}
public static Number newMaximumValue(final DataType dataType, final int length, final int scale) {
final Class<?> javaClass = dataType.getJavaClass();
final StringBuilder text = new StringBuilder(length);
for (int i = length - scale + 1; i > 1; i--) {
text.append('9');
}
if (scale > 0) {
text.append(".");
for (int i = 0; i < scale; i++) {
text.append('9');
}
}
if (javaClass == Byte.class) {
try {
if (length == 0) {
return Byte.MAX_VALUE;
} else {
return new BigDecimal(text.toString()).byteValueExact();
}
} catch (final ArithmeticException e) {
return Byte.MAX_VALUE;
}
} else if (javaClass == Short.class) {
try {
if (length == 0) {
return Short.MAX_VALUE;
} else {
return new BigDecimal(text.toString()).shortValueExact();
}
} catch (final ArithmeticException e) {
return Short.MAX_VALUE;
}
} else if (javaClass == Integer.class) {
try {
if (length == 0) {
return Integer.MAX_VALUE;
} else {
return new BigDecimal(text.toString()).intValueExact();
}
} catch (final ArithmeticException e) {
return Integer.MAX_VALUE;
}
} else if (javaClass == Long.class) {
try {
if (length == 0) {
return Long.MAX_VALUE;
} else {
return new BigDecimal(text.toString()).longValueExact();
}
} catch (final ArithmeticException e) {
return Long.MAX_VALUE;
}
} else if (javaClass == Float.class) {
if (length == 0) {
return Float.MAX_VALUE;
} else {
return new BigDecimal(text.toString()).floatValue();
}
} else if (javaClass == Double.class) {
if (length == 0) {
return Double.MAX_VALUE;
} else {
return new BigDecimal(text.toString()).doubleValue();
}
} else {
return (Number)dataType.toObject(text);
}
}
private final DataType dataType;
private final int length;
private BigDecimal maximumValue;
private BigDecimal minimumValue;
private final int scale;
private final FieldSupport fieldSupport;
public NumberTextField(final DataType dataType, final int length) {
this(dataType, length, 0);
}
public NumberTextField(final DataType dataType, final int length, final int scale) {
this(dataType, length, scale, null, newMaximumValue(dataType, length, scale));
}
public NumberTextField(final DataType dataType, final int length, final int scale,
final Number minimumValue, final Number maximumValue) {
this(null, dataType, length, scale, minimumValue, maximumValue);
}
public NumberTextField(final String fieldName, final DataType dataType, final int length,
final int scale) {
this(fieldName, dataType, length, scale, null, newMaximumValue(dataType, length, scale));
}
public NumberTextField(final String fieldName, final DataType dataType, final int length,
final int scale, final Number minimumValue, final Number maximumValue) {
this.fieldSupport = new FieldSupport(this, fieldName, null, true);
this.dataType = dataType;
this.length = length;
this.scale = scale;
setMinimumValue(minimumValue);
setMaximumValue(maximumValue);
setColumns(getLength(dataType, length, scale, this.minimumValue) + 1);
setHorizontalAlignment(RIGHT);
getDocument().addDocumentListener(this);
addFocusListener(new WeakFocusListener(this));
MenuFactory.getPopupMenuFactory(this);
setFont(SwingUtil.FONT);
}
@Override
public void changedUpdate(final DocumentEvent e) {
validateField();
}
@Override
public Field clone() {
try {
return (Field)super.clone();
} catch (final CloneNotSupportedException e) {
return Exceptions.throwUncheckedException(e);
}
}
@Override
public void firePropertyChange(final String propertyName, final Object oldValue,
final Object newValue) {
super.firePropertyChange(propertyName, oldValue, newValue);
}
@Override
public void focusGained(final FocusEvent e) {
this.fieldSupport.discardAllEdits();
}
@Override
public void focusLost(final FocusEvent e) {
updateFieldValue();
this.fieldSupport.discardAllEdits();
}
@Override
public Color getFieldSelectedTextColor() {
return getSelectedTextColor();
}
@Override
public FieldSupport getFieldSupport() {
return this.fieldSupport;
}
public int getLength() {
return this.length;
}
public Number getMaximumValue() {
return this.maximumValue;
}
public Number getMinimumValue() {
return this.minimumValue;
}
public int getScale() {
return this.scale;
}
private Object getTypedValue(final Object value) {
if (Property.isEmpty(value)) {
return null;
} else {
if ("NaN".equalsIgnoreCase(value.toString())) {
return Double.NaN;
} else if ("-Infinity".equalsIgnoreCase(value.toString())) {
return Double.NEGATIVE_INFINITY;
} else if ("Infinity".equalsIgnoreCase(value.toString())) {
return Double.POSITIVE_INFINITY;
}
try {
final BigDecimal bigNumber = new BigDecimal(value.toString());
return this.dataType.toObject(bigNumber);
} catch (final Throwable t) {
return value.toString();
}
}
}
@Override
public void insertUpdate(final DocumentEvent e) {
validateField();
}
@Override
public void removeUpdate(final DocumentEvent e) {
validateField();
}
@Override
public void setFieldSelectedTextColor(Color color) {
if (color == null) {
color = Field.DEFAULT_SELECTED_FOREGROUND;
}
setSelectedTextColor(color);
}
@Override
public boolean setFieldValue(final Object value) {
Invoke.later(() -> {
final Object newValue = getTypedValue(value);
final Object oldValue = getFieldValue();
String newText;
if (newValue == null) {
newText = "";
} else if (newValue instanceof Number) {
newText = Numbers.toString((Number)newValue);
if ("NAN".equalsIgnoreCase(newText)) {
newText = "NaN";
} else if ("Infinity".equalsIgnoreCase(newText)) {
newText = "Infinity";
} else if ("-Infinity".equalsIgnoreCase(newText)) {
newText = "-Infinity";
} else {
final BigDecimal decimal = new BigDecimal(newText);
newText = decimal.toPlainString();
}
} else {
newText = DataTypes.toString(newValue);
}
if (!DataType.equal(newText, getText())) {
setText(newText);
}
if (!DataType.equal(oldValue, newValue)) {
validateField();
this.fieldSupport.setValue(newValue);
}
});
return false;
}
public void setMaximumValue(final Number maximumValue) {
if (maximumValue == null) {
this.maximumValue = null;
} else {
this.maximumValue = new BigDecimal(Numbers.toString(maximumValue));
}
}
public void setMinimumValue(final Number minimumValue) {
if (minimumValue == null) {
this.minimumValue = null;
} else {
this.minimumValue = new BigDecimal(Numbers.toString(minimumValue));
}
}
@Override
public void setToolTipText(final String text) {
final FieldSupport fieldSupport = getFieldSupport();
if (fieldSupport == null || fieldSupport.setOriginalTooltipText(text)) {
super.setToolTipText(text);
}
}
@Override
public String toString() {
return getFieldName() + "=" + getFieldValue();
}
@Override
public void updateFieldValue() {
final String text = getText();
setFieldValue(text);
}
private void validateField() {
final String text = getText();
String message = null;
if (Property.hasValue(text)) {
if ("NaN".equalsIgnoreCase(text)) {
if (this.dataType.equals(DataTypes.DOUBLE)) {
} else if (this.dataType.equals(DataTypes.FLOAT)) {
} else {
message = "'" + text + "' is not a valid " + this.dataType.getValidationName() + ".";
}
} else if ("Infinity".equalsIgnoreCase(text)) {
if (this.dataType.equals(DataTypes.DOUBLE)) {
} else if (this.dataType.equals(DataTypes.FLOAT)) {
} else {
message = "'" + text + "' is not a valid " + this.dataType.getValidationName() + ".";
}
} else if ("-Infinity".equalsIgnoreCase(text)) {
if (this.dataType.equals(DataTypes.DOUBLE)) {
} else if (this.dataType.equals(DataTypes.FLOAT)) {
} else {
message = "'" + text + "' is not a valid " + this.dataType.getValidationName() + ".";
}
} else {
try {
BigDecimal number = new BigDecimal(text.trim());
if (number.scale() < 0) {
number = number.setScale(this.scale);
}
if (number.scale() > this.scale) {
message = "Number of decimal places must be < " + this.scale;
} else if (this.minimumValue != null && this.minimumValue.compareTo(number) > 0) {
message = BigDecimals.toString(number) + " < " + BigDecimals.toString(this.minimumValue)
+ " (minimum)";
} else if (this.maximumValue != null && this.maximumValue.compareTo(number) < 0) {
message = BigDecimals.toString(number) + " > " + BigDecimals.toString(this.maximumValue)
+ " (maximum)";
} else {
// number = number.setScale(scale);
// final String newText = number.toPlainString();
// if (!newText.equals(text)) {
// setText(newText);
// }
message = null;
}
} catch (final Throwable t) {
message = "'" + text + "' is not a valid " + this.dataType.getValidationName() + ".";
}
}
}
final boolean valid = Property.isEmpty(message);
if (valid) {
this.fieldSupport.setFieldValid();
} else {
this.fieldSupport.setFieldInvalid(message, WebColors.Red, WebColors.Pink);
}
}
}