Input dialogs

Input dialogs allows users to input new items, or to change old ones. It's often helpful to implement a class which can handle both cases, since their data validation requirements are almost always identical.


Here, the StockEditor class can either add a new Stock object, or change a Stock which already exists. When a change is performed, all input fields are pre-populated with current values. When an add is performed, all fields are either blank or pre-populated with default values.

Internally, StockEditor uses a standard dialog. It also uses a RegexInputVerifier for all text validations, using regular expressions. Text validation proceeds as follows:

For example, here is the result of inputing an invalid Quantity, which must be an integer:

Response to invalid input

When the OK button is selected, the input is processed only if

If there is an error, the user is informed by a JOptionPane message.
package hirondelle.stocks.portfolio;

import java.util.*;
import java.util.regex.Pattern;
import java.math.BigDecimal;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

import hirondelle.stocks.quotes.Stock;
import hirondelle.stocks.quotes.Exchange;
import hirondelle.stocks.util.Args;
import hirondelle.stocks.util.Util;
import hirondelle.stocks.util.Consts;
import hirondelle.stocks.util.ui.StandardEditor;
import hirondelle.stocks.util.ui.UiUtil;

* Dialog allows user to either add new a {@link Stock} to the 
* {@link CurrentPortfolio}, or to change the parameters of a <tt>Stock</tt>
* which is already in the <tt>CurrentPortfolio</tt>.
final class StockEditor {
  * Constructor.
  * @param aFrame parent to which dialogs of this class are attached.
  StockEditor(JFrame aFrame) {
    fFrame = aFrame;
  * Return the {@link Stock} representing a new 
  * item which the user has input, or <tt>null</tt> if the user cancels the dialog.
  Stock addStock(){
    showDialog("Add New Stock", null);
    return fNewStock;

  * Return the possibly-edited version of <tt>aOldStock</tt>, representing 
  * the desired changes, or <tt>null</tt> if the user cancels the dialog.
  * @param aOldStock {@link Stock} which the end user wishes to change.
  Stock changeStock(Stock aOldStock){
    showDialog("Change Stock", aOldStock);
    return fNewStock;

  * Always returned to the caller, and is null if the user cancels the 
  * dialog.
  private Stock fNewStock;
  private JTextField fNameField;
  private JTextField fTickerField;
  private JComboBox<Exchange> fExchangeField;
  private JTextField fQuantityField;
  private JTextField fAveragePriceField;
  private JFrame fFrame;

  private void showDialog(String aTitle, Stock aInitialValue){
    Editor editor = new Editor(aTitle, fFrame, aInitialValue);

  private final class Editor extends StandardEditor { 
    * @param aInitialValue is possibly-null; if null, then the editor represents 
    * and Add action, otherwise it represents a Change action, and all fields will
    * be pre-populated with values taken from aInitialValue.
    Editor(String aTitle, JFrame aParent, Stock aInitialValue){
      super(aTitle, aParent, StandardEditor.CloseAction.HIDE);
      fInitialValue = aInitialValue;
    @Override protected JComponent getEditorUI () {
      return getEditor(fInitialValue);
    @Override protected void okAction() {
    private Stock fInitialValue;
  private JComponent getEditor(Stock aInitialValue){
    JPanel content = new JPanel();
    content.setLayout(new GridBagLayout());
    addCompanyName(content, aInitialValue);
    addTickerField(content, aInitialValue);
    addExchange(content, aInitialValue);
    addQuantity(content, aInitialValue);
    addAveragePrice(content, aInitialValue);
    return content;

  * Set fNewStock, if the input is valid.
  * If there is at least one error message in the text input fields, then
  * inform the user that the error must be corrected before proceeding.
  * <p>If no error messages, but input still detected as faulty as 
  * per {@link Stock#isValidInput(List, String, String, Exchange, Integer, BigDecimal)}, 
  * then inform user of the problem.
  * @param aEditor is the dialog which will be closed if no errors occur.
  private void initNewStock(StandardEditor aEditor){
    if (hasErrorMessage()) {
      String message = "Please correct the input error before proceeding.";
        fFrame, message, "Invalid Input", JOptionPane.INFORMATION_MESSAGE
    String name = fNameField.getText();
    String ticker = fTickerField.getText();
    Exchange exchange = Exchange.valueFrom(fExchangeField.getSelectedItem().toString());
    Integer numShares = null;
    if (Util.textHasContent(fQuantityField.getText())) {
      numShares = Integer.valueOf(fQuantityField.getText());
    else {
      numShares = Consts.ZERO;
    BigDecimal avgPrice = null;
    if (Util.textHasContent(fAveragePriceField.getText())) {
      avgPrice = new BigDecimal(fAveragePriceField.getText());
    else {
      avgPrice = new BigDecimal( "0.00" );
    java.util.List<String> errorMessages = new ArrayList<>();
    if (Stock.isValidInput( errorMessages, name, ticker, exchange, numShares, avgPrice)) {
      fNewStock = new Stock(name, ticker, exchange, numShares, avgPrice); 
    else {
      Object[] message = errorMessages.toArray();
        fFrame, message, "Invalid Input", JOptionPane.INFORMATION_MESSAGE
  * Return true only if at least one of the text input fields contains 
  * an error message.
  private boolean hasErrorMessage(){
      fNameField.getText().startsWith(RegexInputVerifier.ERROR_MESSAGE_START) ||
      fTickerField.getText().startsWith(RegexInputVerifier.ERROR_MESSAGE_START) ||
      fQuantityField.getText().startsWith(RegexInputVerifier.ERROR_MESSAGE_START) ||

  private void addCompanyName(JPanel aContent, Stock aInitialValue) {
    fNameField = UiUtil.addSimpleEntryField(
      "Company Name:", 
      (aInitialValue == null ? null : aInitialValue.getName()), 
      "No spaces on the ends, and at least one character"
  private void addTickerField(JPanel aContent, Stock aInitialValue) {
    fTickerField = UiUtil.addSimpleEntryField(
      (aInitialValue == null ? null : aInitialValue.getTicker()), 
      "No spaces on the ends, 1-20 characters: " + 
      "letters, periods, underscore and ^"
    String regex = "(\\^)?([A-Za-z._]){1,20}";
    RegexInputVerifier tickerVerifier = 
      new RegexInputVerifier(Pattern.compile(regex), RegexInputVerifier.UseToolTip.FALSE
    fTickerField.setInputVerifier( tickerVerifier );

  private void addExchange(JPanel aContent, Stock aInitialValue) {
    JLabel exchange = new JLabel("Exchange:");
    aContent.add( exchange, UiUtil.getConstraints(2,0) );
    DefaultComboBoxModel<Exchange> exchangesModel = 
      new DefaultComboBoxModel<>(Exchange.VALUES.toArray(new Exchange[0]))
    fExchangeField = new JComboBox<Exchange>(exchangesModel);
    if (aInitialValue != null) {
      fExchangeField.setSelectedItem( aInitialValue.getExchange() );
    GridBagConstraints constraints = UiUtil.getConstraints(2,1);
    constraints.fill = GridBagConstraints.HORIZONTAL;
    aContent.add(fExchangeField, constraints);
  private void addQuantity(JPanel aContent, Stock aInitialValue) {
    fQuantityField = UiUtil.addSimpleEntryField(
      (aInitialValue == null ? null : 
      "Zero or Integer (possible leading minus sign)"
  private void addAveragePrice(JPanel aContent, Stock aInitialValue) {
    fAveragePriceField = UiUtil.addSimpleEntryField(
       "Average Price:", 
      (aInitialValue == null ? null : 
      "Zero or Positive Number (two decimals optional)"

See Also :
Standardized dialogs
Verify input with regular expressions
Verify input with Model Objects