Validating form input
In Section 7 you learned how Java exceptions can be used to indicate an error of some kind. You also learned that there are two types of exceptions, checked and unchecked:
- Use checked exceptions for errors from which you or the user can recover
- Use unchecked exceptions for programming errors
Thinking about how to apply this to your application it is helpful to review the sequence of actions when adding a new zookeeper:
- The user interface causes
createZooKeeper()inZooAdministratorto be invoked - The
createZooKeeper()method instantiates aZooKeeperobject
It is the end user who effectively invokes the createZooKeeper() method (by clicking the Save button). It would therefore make sense for createZooKeeper() to throw a checked exception should an invalid entry be made. However, the ZooKeeper constructor is not directly invoked by the end user because it is called by the code within createZooKeeper(), so any invalid entries here could be considered as preventable (by the programmer) and will therefore throw unchecked exceptions.
For unchecked exceptions you could use either (or both) of the Java supplied IllegalArgumentException or InvalidStateException. For checked exceptions you can make use of the ValidationException class you created in Section 7. Because the attributes can be set in both the constructor and the individual setter methods, you need a way of validating their values in all cases.
You will now define some validation methods in the ZooKeeper class, so begin by defining a new protected[1] helper method called validateName() which performs some validation checks for the name argument:
protected void validateName(String name) {
if (name.isEmpty()) {
throw new IllegalArgumentException("Name must be specified");
}
}
Now define similar methods to validate the other attributes:
protected void validateAddress(String address) {
if (address.isEmpty()) {
throw new IllegalArgumentException("Address must be specified");
}
}
protected void validateEmail(Email email) {
if (email.toString().isEmpty()) {
throw new IllegalArgumentException("Email must be specified");
}
}
protected void validateSalary(BigDecimal salary) {
if (salary.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Salary must be specified");
}
}
- The validations performed have been deliberately kept simple. In a production level application, more validation would be needed
The above validation methods can be called from inside each setter method:
void setName(String name) {
validateName(name);
this.name = name;
}
void setAddress(String address) {
validateAddress(address);
this.address = address;
}
void setEmail(Email email) {
validateEmail(email);
this.email = email;
}
void setSalary(BigDecimal salary) {
validateSalary(salary);
this.salary = salary;
}
Rather than the constructor having to invoke each individual validation method you can delegate to a new private helper method that does it for you:
protected void validate(String name, String address,
Email email, BigDecimal salary) {
validateName(name);
validateAddress(address);
validateEmail(email);
validateSalary(salary);
}
The constructor can now invoke validate():
ZooKeeper(Person person, Email email, BigDecimal salary){
validate(person.getName(), person.getAddress(), email, salary);
this.person = new Person(person);
this.email = email;
this.salary = salary;
}
Remember that if an exception is thrown then the statements that follow it are not performed. Instead, the exception is propagated back through the chain of calling methods until one of them catches it in a try...catch block.
If you return to the createZooKeeper() method in ZooAdministrator you find that as it stands it would simply propagate any thrown exception back to the user. However, these are of the unchecked type, and it was decided earlier that this method should throw a checked exception. In Section 7 you developed the ValidationException class as a checked exception, so you can modify createZooKeeper() as follows:
public ZooKeeper createZooKeeper(String name, String address,
String email, String salary) throws ValidationException {
try {
ZooKeeper zooKeeper = new ZooKeeper(new Person(name, address),
new Email(email),
new BigDecimal(salary));
zooKeepers.add(zooKeeper);
return zooKeeper;
} catch (IllegalArgumentException ex) {
throw new ValidationException(ex.getMessage());
}
}
- By enclosing the statement which can give rise to an exception (the
ZooKeeperconstructor in this case) in atry...catchblock you can trap it should it occur. If it does, the code generates suitable aValidationExceptionby retrieving the text of the caught exception and passing it to the constructor ofValidationException - Note that because
ValidationExceptionis a checked exception the method signature needs to specify that it can potentially be thrown. This was not needed for unchecked exception types
There is another error that could occur that has not been handled yet; the BigDecimal constructor could throw the unchecked exception NumberFormatException if the passed String is not in a valid numerical format:
public ZooKeeper createZooKeeper(String name, String address,
String email, String salary) throws ValidationException {
try {
ZooKeeper zooKeeper = new ZooKeeper(new Person(name, address),
new Email(email),
new BigDecimal(salary));
zooKeepers.add(zooKeeper);
return zooKeeper;
} catch (NumberFormatException ex) {
throw new ValidationException(salary + " is not a valid amount.");
} catch (IllegalArgumentException ex) {
throw new ValidationException(ex.getMessage());
}
}
Because the createZooKeeper() method can now potentially throw a checked exception the saveButtonActionPerformed() method within ZooKeeperEditor will no longer compile. You need to catch the exception should it occur and show a message dialog informing the user of the problem:
private void saveButonActionPerformed(java.awt.event.ActionEvent evt) {
ZooAdministrator admin = ZooAdministrator.getInstance();
try {
if (zooKeeper == null) {
// Adding a new zoo keeper
admin.createZooKeeper(nameField.getText(),
addressArea.getText(),
emailField.getText(),
salaryField.getText());
messageLabel.setText("New zoo keeper added");
} else {
// Changing an existing zoo keeper
admin.changeZooKeeper(zooKeeper,
nameField.getText(),
addressArea.getText(),
emailField.getText(),
salaryField.getText());
messageLabel.setText("Zoo keeper details changed");
}
} catch (ValidationException ex) {
JOptionPane.showMessageDialog(null, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
- Because the method can potentially throw a
ValidationExceptionthe statements are enclosed within atry...catchblock. If an exception is thrown, theJOptionPaneclass is used to inform the user of the problem
You should change the changeZooKeeper() method inside ZooAdministrator to also catch and display to the user any exceptions which occur:
public void changeZooKeeper(ZooKeeper zooKeeper, String name,
String address, String email, String salary)
throws ValidationException {
try {
zooKeeper.setPerson(new Person(name, address));
zooKeeper.setEmail(new Email(email));
zooKeeper.setSalary(new BigDecimal(salary));
} catch (NumberFormatException ex) {
throw new ValidationException(salary + " is not a valid amount.");
} catch (IllegalArgumentException ex) {
throw new ValidationException(ex.getMessage());
}
}
Comments