Circuit Breaker App (Java w/source code)

The following was a program I wrote back in college.


Press the ‘+’ button to add an appliance, and the ‘-‘ button to subtract the last appliance. Up to 30 appliances can be entered. All appliances showing will be calculated, whether or not they have anything in their fields, so be sure to add and subtract the appropriate number of appliances before displaying the report.

You can enter whatever values you wish for the power, current, and volts of each appliance. When you hit the calculate button, it will go through each appliance and take the current displayed. If the current field is blank, it will calculate the currents based on the power and volts. If all fields have values but the power, volts, and current do not match within 1% (in other words, if P is not close to to V * I), then the power and volts fields are made blank.

Be sure to enter in the total current the circuit breaker can handle, otherwise the program will assume it’s zero. When you hit the calculate button, a blue ‘O’ will show if the breaker will not trip, and a red ‘X’ will show if the breaker will trip.

Be sure to hit the calculate button before you generate the report, because the report will go by what’s in the fields without checking them. In other words, the program assumes you know what you’re doing. If you want to make sure the report is accurate, click the calculate button before hitting the report button.

Once the report is generated, you will notice it is all in a single text field. This is so you can modify whatever you want in the report, and copy and paste the entire report onto the clipboard so you can put it wherever else you like, such as in Notepad or another document program for printing.

The App


Download

If you don’t want to copy the source code below, you can download it from here for $1:

Source Code

“Main” package

Main.java

package Main;

import javax.swing.JFrame;
/**
 * The program starts execution from here.  This creates a JFrame, sets its content pane to a new ElectricPanel, then sets the properties of the JFrame.
 * Throughout this program, absolute positioning is used.  Layout managers are not used.
 * @author Eric Sweeten
 */
public class Main
{
  public static void main(String[] args)
  {
    JFrame window = new JFrame("Circuit Breaker App");
    window.setContentPane(new ElectricPanel());
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.setResizable(false);
    window.pack();
    window.setLocationRelativeTo(null);
    window.setVisible(true);
  }
}

ElectricPanel.java

package Main;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.text.AbstractDocument;

import Appliance.Appliance;
import Appliance.Report;
import DocumentFilter.DocFilter;
@SuppressWarnings("serial")
/**
 * As mentioned in Main.java, absolute positioning is used for this program.  The layout manager is set to null, otherwise it is FlowLayout by default.
 * Also, I keep the values used for numerical calculations in the various JTextFields and then parse them into doubles.  I feel that reduces redundancy that would happen
 * if you create additional sets of double values on top of the JTextFields.
 * @author Eric Sweeten
 */
public class ElectricPanel extends JPanel
{
  /**
   * The default width of the JPanel
   */
  private static final int WIDTH = 340;
  /**
   * The default height of the JPanel
   */
  private static final int HEIGHT = 660;
  /**
   * The button to add an appliance
   */
  private final JButton addButton;
  /**
   * The button to subtract an appliance
   */
  private final JButton subtractButton;
  /**
   * The button to do the calculations
   */
  private final JButton calculateButton;
  /**
   * The button to generate the report
   */
  private final JButton reportButton;
  /**
   * The help button.  Gives instructions to the user how to use the program, and clarifies the technicalities on why the program does the things the way it does (such as erasing the volts and power fields when it disagrees with the current)
   */
  private final JButton helpButton;
  /**
   * The text field that shows the total current of all appliances listed
   */
  private JTextField circuitBreakerNumeratorTextField;
  /**
   * The text field that shows the maximum current the circuit breaker can handle before tripping
   */
  private JTextField circuitBreakerDenominatorTextField;
  /**
   * The title labels for each column, including "Description", "Volts", "Current", and "Power"
   */
  private JLabel[] labels;
  /**
   * The label at the bottom-left that says "Circuit Breaker:"
   */
  private JLabel circuitBreakerLabel;
  /**
   * The label that shows a forward slash between the circuitBreakerNumeratorTextField and circuitBreakerDenominatorTextField
   */
  private JLabel forwardSlashLabel;
  /**
   * This label shows a blue "O" for when the breaker will not trip and a red "X" for when it will
   */
  private JLabel tripLabel;
  /**
   * This is an instance of the ButtonListener class below, which implements an ActionListener.  It is to listen to the buttons being pressed, and all put into one instance of an ActionListener to reduce redundancy.
   */
  private final ButtonListener buttonListener;
  /**
   * The document filter, to be used with the JTextFields to filter out non-numerical characters entered in by the user for the appropriate fields
   */
  private final DocFilter documentFilter;
  /**
   * This ArrayList contains Appliance objects for the various appliances the user enters in.  This is explained in more depth in the Appliance class itself.
   */
  private final ArrayList<Appliance> appliances;
  /**
   * This constructor initializes and arranges the various GUI components, and finally adds a single appliance to the GUI.
   */
  public ElectricPanel()
  {
    setPreferredSize(new Dimension(WIDTH, HEIGHT));  
    setFocusable(true);
    requestFocus();
    setLayout(null); //this line is necessary, because it's FlowLayout by default and I don't want to use any layout managers
    appliances = new ArrayList<Appliance>();
    labels = new JLabel[]{
        new JLabel("Description"),
        new JLabel("Volts"),
        new JLabel("Current"),
        new JLabel("Power")};
    Dimension size;
    for (int i = 0; i < 4; i++)
    {
      size = labels[i].getPreferredSize();  //The height is always 16 but the widths are different.
      labels[i].setBounds(i == 0 ? 10 : 125 + 60 * (i - 1), 10, size.width, size.height);
      add(labels[i]);
    }
    documentFilter = new DocFilter();
    buttonListener = new ButtonListener();
    addButton = getNewAddOrSubtractButton('+', 30);
    subtractButton = getNewAddOrSubtractButton('-', 50);
    Insets insets = new Insets(0, 0, 0, 0); //rather than creating a new instance of Insets three times, I create it once here and set the margins to three different components to this Insets
    helpButton = new JButton("?");
    helpButton.setMargin(insets);
    helpButton.setBounds(305, 10, 24, 18);
    helpButton.addActionListener(buttonListener);
    calculateButton = new JButton("Calc");
    calculateButton.setBounds(214, 635, 45, 20);
    calculateButton.setMargin(insets);
    calculateButton.addActionListener(buttonListener);
    reportButton = new JButton("Report");
    reportButton.setBounds(259, 635, 45, 20);
    reportButton.setMargin(insets);
    reportButton.addActionListener(buttonListener);
    setupCircuitBreakerTextFieldsAndLabels();
    add(helpButton);
    add(addButton);
    add(subtractButton);
    add(calculateButton);
    add(reportButton);
    add(circuitBreakerNumeratorTextField);
    add(circuitBreakerDenominatorTextField);
    add(circuitBreakerLabel);
    add(forwardSlashLabel);
    add(tripLabel);
    addAppliance();
    /*
     * When an appliance is added, the subtractButton is set to visible.  This should not be so for when the first appliance is added, since you cannot have less than one appliance, and so the setVisible property of the subtractButton is set back to false after the initial appliance is added.
     */
    subtractButton.setVisible(false);
  }
  /**
   * Adds an instance of Appliance to appliances and sets the appropriate coordinates for its GUI components
   */
  private void addAppliance()
  {
    subtractButton.setVisible(true);
    appliances.add(new Appliance());
    int index = appliances.size() - 1;
    Dimension size = appliances.get(index).getTextFieldDescription().getPreferredSize();
    appliances.get(index).getTextFieldDescription().setBounds(10, 30 + 20 * index, size.width, size.height);
    size = appliances.get(index).getTextFieldVolts().getPreferredSize();
    appliances.get(index).getTextFieldVolts().setBounds(125, 30 + 20 * index, size.width, size.height);
    size = appliances.get(index).getTextFieldCurrent().getPreferredSize();
    appliances.get(index).getTextFieldCurrent().setBounds(185, 30 + 20 * index, size.width, size.height);
    size = appliances.get(index).getTextFieldPower().getPreferredSize();
    appliances.get(index).getTextFieldPower().setBounds(245, 30 + 20 * index, size.width, size.height);
    add(appliances.get(index).getTextFieldDescription());
    add(appliances.get(index).getTextFieldVolts());
    add(appliances.get(index).getTextFieldCurrent());
    add(appliances.get(index).getTextFieldPower());
    if (index == 29) addButton.setVisible(false);
  }
  /**
   * Makes an instance of ReportPanel and displays it, passing in the report as a single String
   */
  private void displayReport()
  {
    final Report report = new Report(generateReport());
    report.display();
  }
  /**
   * Finds out if the breaker will trip.  The text in the circuitBreakerNumeratorTextField does not need to be surrounded by try and catch because the field is unable to be edited by the user, and is only edited by the program, and is always given a number value.
   */
  private void findOutIfBreakerWillTrip()
  {
    double max = getBreakerValue();
    double total;
    total = Double.parseDouble(circuitBreakerNumeratorTextField.getText());
    if (total > max)
    {
      tripLabel.setForeground(Color.RED);
      tripLabel.setText("X");
    }
    else
    {
      tripLabel.setForeground(Color.BLUE);
      tripLabel.setText("O");
    }
    repaint();
  }
  /**
   * Generates the report and puts it in one single String.  This is called by the displayReport() method above.
   * @return
   */
  private String generateReport()
  {
    String report = "Data\n\n";
    report += "Description\t\tVolts\tPower\tCurrent\n\n";
    for (Appliance a : appliances)
    {
      report += a.getTextFieldDescription().getText() + "\t\t";
      report += a.getTextFieldVolts().getText() + "\t";
      report += a.getTextFieldPower().getText() + "\t";
      report += a.getTextFieldCurrent().getText() + "\n";
    }
    report += "\nS / T = " + circuitBreakerNumeratorTextField.getText() + " / " + getBreakerValue();
    report += "\nS = sum of currents from all appliances";
    report += "\nT = total current circuit breaker can handle";
    report += "\nThe breaker will" + ((getBreakerValue() >= Double.parseDouble(circuitBreakerNumeratorTextField.getText()) ? " not" : "") + " trip.");
    return report;
  }
  /**
   * This gets the value of the breaker entered in by the user.  It is surrounded by try and catch in case the user enters in something that cannot be read as a double.  If the value cannot be read as a double, the value is considered to be zero.
   * @return The double number value of the circuit breaker
   */
  private double getBreakerValue()
  {
    double value;
    try
    {
      value = Double.parseDouble(circuitBreakerDenominatorTextField.getText());
    }
    catch (NumberFormatException exc)
    {
      value = 0.0;
    }
    return value;
  }
  /**
   * The purpose of this method is to reduce redundancy.  It is called twice: once to make the plus sign JButton, and once to make the minus sign JButton
   * @param plusOrMinus '+' if it's for the plus sign and '-' if it's for the minus sign
   * @param yPos The y-position of this JButton, as it's separate for the plus sign and minus sign (the minus sign is 20 pixels below the plus sign)
   */
  private JButton getNewAddOrSubtractButton(final char plusOrMinus, final int yPos)
  {
    JButton button = new JButton(plusOrMinus + "");
    Insets insets = new Insets(0, 0, 0, 0);
    button.setBounds(305, yPos, 24, 18);
    button.setMargin(insets);
    button.addActionListener(buttonListener);
    return button;     
  }
  /**
   * This helper method sets up the text fields that display the values related to the circuit breaker, as well as the label of the forward slash ("/") and the label that shows the "X" or "O" for when the breaker will or will not break, respectively.
   */
  private void setupCircuitBreakerTextFieldsAndLabels()
  {
    forwardSlashLabel = new JLabel("/");
    forwardSlashLabel.setBounds(148, 635, 20, 20);
    circuitBreakerDenominatorTextField = new JTextField();
    circuitBreakerDenominatorTextField.setBounds(155, 635, 40, 20);
    circuitBreakerNumeratorTextField = new JTextField("0");
    circuitBreakerNumeratorTextField.setBounds(105, 635, 40, 20);
    circuitBreakerNumeratorTextField.setEditable(false);
    tripLabel = new JLabel();
    tripLabel.setBounds(200, 635, 20, 20);
    tripLabel.setForeground(Color.BLUE);
    findOutIfBreakerWillTrip();
    /*
     * The purpose of this DocumentFilter is to prevent anything non-number like to be entered into the fields that require numbers.  The values that are allowed to be entered are digits as well as a decimal and a minus sign.
     */
    ((AbstractDocument)circuitBreakerDenominatorTextField.getDocument()).setDocumentFilter(documentFilter);
    circuitBreakerLabel = new JLabel("Circuit Breaker:");
    circuitBreakerLabel.setBounds(10, 635, 100, 20);
  }
  /**
   * Subtracts an instance of Appliance from the ArrayList appliances, first subtracting the GUI components then subtracting it from the ArrayList.  It cannot be subtracted from the ArrayList without taking it off the JPanel first.
   */
  private void subtractAppliance()
  {
    int index = appliances.size() - 1;
    addButton.setVisible(true);
    remove(appliances.get(index).getTextFieldDescription());
    remove(appliances.get(index).getTextFieldVolts());
    remove(appliances.get(index).getTextFieldCurrent());
    remove(appliances.get(index).getTextFieldPower());
    appliances.remove(index);
    if (appliances.size() == 1) subtractButton.setVisible(false);
  }
  /**
   * This is the ActionListener class that listens to all the buttons, including the add, subtract, calculate, and report buttons
   */
  private class ButtonListener implements ActionListener
  {
    @Override
    public void actionPerformed(ActionEvent arg0)
    {
      if (arg0.getSource() == addButton)
      {
        addAppliance();
        repaint();
      }
      else if (arg0.getSource() == subtractButton)
      {
        subtractAppliance();
        repaint();
      }
      else if (arg0.getSource() == calculateButton)
      {
        double totalCurrent = 0;
        double current;
        double powerOverVolts;
        for (Appliance a : appliances)
        {
          try
          {
            if (a.getTextFieldCurrent().getText().isEmpty())
            {
              current = 0;
            }
            else
              current = Double.parseDouble(a.getTextFieldCurrent().getText());
            /*
             * If the current is 0 (or not entered in), then the current is calculated by the power divided by the volts.  If, however, the current is entered in, then the power and volts field are ignored.
             */
            powerOverVolts = Double.parseDouble(a.getTextFieldPower().getText()) / Double.parseDouble(a.getTextFieldVolts().getText());
            if (current == 0)
            { 
              current = powerOverVolts;
            }
            else
            {
              /*
               * If the current read is not zero, and it is not within 1% of the power over volts, then the user entered incorrect information, and to give indication of it, the text field for power and volts are set blank.
               */
              if (current < powerOverVolts * 0.99 || current > powerOverVolts * 1.01)
              {
                a.getTextFieldPower().setText("");
                a.getTextFieldVolts().setText("");
              }
            }
          }
          catch (NumberFormatException exc)
          {
            current = 0;
          }
          a.getTextFieldCurrent().setText(current + "");
          a.getTextFieldCurrent().setCaretPosition(0);
          a.getTextFieldPower().setCaretPosition(0);
          a.getTextFieldVolts().setCaretPosition(0);
          totalCurrent += current;
        }
        circuitBreakerNumeratorTextField.setText(totalCurrent + "");
        circuitBreakerNumeratorTextField.moveCaretPosition(0);
        findOutIfBreakerWillTrip();
      }
      else if (arg0.getSource() == reportButton)
      {
        displayReport();
      }
      else if (arg0.getSource() == helpButton)
      {
        String text
              = "Press the '+' button to add an appliance, and the '-' button to subtract the last appliance.\n";
        text += "Up to 30 appliances can be entered.  All appliances showing will be calculated, whether or\n";
        text += "not they have anything in their fields, so be sure to add and subtract the appropriate number\n";
        text += "of appliances before displaying the report.\n\n";
        text += "You can enter whatever values you wish for the power, current, and volts of each appliance.\n";
        text += "When you hit the calculate button, it will go through each appliance and take the current\n";
        text += "displayed.  If the current field is blank, it will calculate the currents based on the power\n";
        text += "and volts.  If all fields have values but the power, volts, and current do not match within\n";
        text += "1% (in other words, if P is not close to to V * I), then the power and volts fields are made\n";
        text += "blank.\n\nBe sure to enter in the total current the circuit breaker can handle, otherwise the program\n";
        text += "will assume it's zero.  When you hit the calculate button, a blue 'O' will show if the breaker\n";
        text += "will not trip, and a red 'X' will show if the breaker will trip.\n\n";
        text += "Be sure to hit the calculate button before you generate the report, because the report will go\n";
        text += "by what's in the fields without checking them.  In other words, the program assumes you\n";
        text += "know what you're doing.  If you want to make sure the report is accurate, click the calculate\n";
        text += "button before hitting the report button.\n\n";
        text += "Once the report is generated, you will notice it is all in a single text field.  This is so you\n";
        text += "can modify whatever you want in the report, and copy and paste the entire report onto the\n";
        text += "clipboard so you can put it wherever else you like, such as in Notepad or another document\n";
        text += "program for printing.\n\n";
        text += "(This help menu is designed to explain how the program works, in case you are confused\n";
        text += "on why it does the things it does.)\n\n";
        text += "Authors:\n\nEric Sweeten\nRon LaMadrid\nUsaffe Rodriguez";
        JOptionPane.showMessageDialog(null, text, "Help", JOptionPane.INFORMATION_MESSAGE);
      }
    }
  }
}

“DocumentFilter” package

DocFilter.java

package DocumentFilter;

import java.awt.Toolkit;

import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
/**
 * The purpose of this DocumentFilter is to prevent anything non-number like to be entered into the fields that require numbers.  The values that are allowed to be entered are digits as well as a decimal and a minus sign.
 * This class reduces redundancy, as it is used in both ElectricPanel and Appliance.
 * A constructor is unnecessary for this class.
 */
public class DocFilter extends DocumentFilter
{
  boolean isValidInput;
  char character;
  @Override
  public void insertString(final FilterBypass fp, final int offset, final String string, final AttributeSet aset)
  {
    isValidInput = true;
    for (int i = 0; i < string.length(); i++)
    {
      character = string.charAt(i);
      if (!Character.isDigit(character) && !(character == '.') && !(character == '-'))
      {
        isValidInput = false;
        break;
      }
    }
    if (isValidInput)
    {
      try
      {
        super.insertString(fp, offset, string, aset);
      }
      catch (BadLocationException exc)
      {
        exc.printStackTrace();
      }
    }
    else
    {
      Toolkit.getDefaultToolkit().beep();
    }
  }
  @Override
  public void replace(final FilterBypass fp, final int offset, final int length, final String string, final AttributeSet aset)
  {
    isValidInput = true;
    for (int i = 0; i < string.length(); i++)
    {
      character = string.charAt(i);
      if (!(Character.isDigit(character) || character == '.' || character == '-'))
      {
        isValidInput = false;
        break;
      }
    }
    if (isValidInput)
    {
      try
      {
        super.replace(fp, offset, length, string, aset);
      }
      catch (BadLocationException exc)
      {
        exc.printStackTrace();
      }
    }
    else
    {
      Toolkit.getDefaultToolkit().beep();
    }
  }
}

“Appliance” package

Appliance.java

package Appliance;
import javax.swing.JTextField;
import javax.swing.text.AbstractDocument;

import DocumentFilter.DocFilter;
public class Appliance
{
  /**
   * The text field for the description of the appliance
   */
  private final JTextField textField_description;
  /**
   * The text field that contains the amount of volts in the appliance
   */
  private final JTextField textField_volts;
  /**
   * The text field that contains the amount of current in the appliance
   */
  private final JTextField textField_current;
  /**
   * The text field that contains the amount of power in the appliance
   */
  private final JTextField textField_power;
  /**
   * An instance of the DocFilter class below, which extends DocumentFilter.  A single instance is made because the same check is ran through all four text fields for the appliance.  The purpose then is to reduce redundancy.
   */
  private final DocFilter documentFilter;
  /**
   * This is the Appliance constructor.  It initializes the JTextFields, including their sizes, and sets the document filters.
   */
  public Appliance()
  {
    textField_description = new JTextField(10);
    textField_volts = new JTextField(5);
    textField_current = new JTextField(5);
    textField_power = new JTextField(5);
    documentFilter = new DocFilter();
    ((AbstractDocument)textField_volts.getDocument()).setDocumentFilter(documentFilter);
    ((AbstractDocument)textField_current.getDocument()).setDocumentFilter(documentFilter);
    ((AbstractDocument)textField_power.getDocument()).setDocumentFilter(documentFilter);
  }
  /**
   * Returns the JTextField textField_current to read the amount of current in the appliance
   * @return The amount of current in the appliance
   */
  public JTextField getTextFieldCurrent()
  {
    return textField_current;
  }
  /**
   * Returns the JTextField textField_description to read the description of the appliance
   * @return The description of the appliance
   */
  public JTextField getTextFieldDescription()
  {
    return textField_description;
  }
  /**
   * Returns the JTextField textField_power to read the amount of power in the appliance
   * @return The amount of power in the appliance
   */
  public JTextField getTextFieldPower()
  {
    return textField_power;
  }
  /**
   * Returns the JTextField textField_volts to read the amount of volts in the appliance
   * @return The amount of volts in the appliance
   */
  public JTextField getTextFieldVolts()
  {
    return textField_volts;
  }
}

Report.java

package Appliance;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;

/**
 * This report panel is designed to hold a single, large JTextArea.  This is so the user can edit the information in the text area, to add, subtract, or modify anything they wish, and to be able to copy the report onto the clipboard to be pasted anywhere else they wish, such as in a Notepad or Word document in order to be printed.
 * @author Eric Sweeten
 */
@SuppressWarnings("serial")
public class Report extends JPanel
{
  /**
   * The report, in a single large String, passed in the constructor
   */
  final String report;
  /**
   * The frame that contains the panel, which contains the text area and the OK button
   */
  private JFrame reportFrame;
  /**
   * The text area containing the report
   */
  private JTextArea textArea;
  /**
   * The OK button to close the frame and return to the original application.  The frame can also be closed by clicking the "X" button on the top-right.
   */
  private JButton okayButton;
  /**
   * The constructor for the ReportPanel, which initializes the various GUI components and adds them to the frame
   * @param report The report, in a single String, sent into the constructor from the calling class (ElectricPanel)
   */
  public Report(final String report)
  {
    reportFrame = new JFrame("Report");
    textArea = new JTextArea();
    textArea.setText(report);
    okayButton = new JButton("OK");
    okayButton.addActionListener(new ActionListener()
    {
      @Override
      public void actionPerformed(ActionEvent arg0)
      {
        reportFrame.dispose();
      }
    });
    add(textArea);
    add(okayButton);
    reportFrame.setContentPane(this);
    reportFrame.pack();
    reportFrame.setLocationRelativeTo(null);
    this.report = report;
  }
  /**
   * Displays the report panel.  The purpose of this method is to get rid of the warning of the instance of ReportPanel not being used in the calling class (ElectricPanel).  I could have just put this in the constructor and it would have had the same effect.  It is considered good programming practice, though, to use the object in some way after it is created.
   */
  public void display()
  {
    reportFrame.setVisible(true);
  }
}