> ### Solution for
> Programming Exercise 7.2

> THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to the following exercise from this on-line Java textbook.

> Exercise 7.2: Write an applet with a `JTextArea` where the user can enter some text. The applet should have a button. When the user clicks on the button, the applet should count the number of lines in the user's input, the number of words in the user's input, and the number of characters in the user's input. This information should be displayed on three labels in the applet. Recall that if `textInput` is a `JTextArea`, then you can get the contents of the `JTextArea` by calling the function `textInput.getText()`. This function returns a `String` containing all the text from the `JTextArea`. The number of characters is just the length of this `String`. Lines in the `String` are separated by the new line character, '\n', so the number of lines is just the number of new line characters in the `String`, plus one. Words are a little harder to count. Exercise 3.4 has some advice about finding the words in a `String`. Essentially, you want to count the number of characters that are first characters in words. Don't forget to put your `JTextArea` in a `JScrollPane`. Scrollbars should appear when the user types more text than will fit in the available area. Here is my applet:

> Discussion

> The applet contains five components. There are several ways to lay them out. A `GridLayout` with five rows certainly won't work, because the `JTextArea` should be taller than the other components. One possible layout is to use a `GridLayout` with two rows. The `JTextArea` would occupy the first row. The bottom half would contain a `JPanel` that holds the other four components. (A `GridLayout` with two columns and one row would also work, if you wanted an applet that was wider and not so tall. You could put the `JTextArea` in the left half and the other components in a `JPanel` in the right half.) However, I decided to use a `BorderLayout`. The `JTextArea` occupies the Center position, and the South position is occupied by a `JPanel` that contains the other components. The `JPanel` uses a `GridLayout` with four rows. In a `BorderLayout`, the height of the component in the South position is the preferred height of that component. It is just as tall as it wants to be. The `JTextArea` gets any left-over space. So, it will be just as large as it can be without crowding the other components. Once the choice is made, writing the `init()` method is not hard.

> The `actionPerformed()` method will be called when the user clicks the button. Since there is only one possible source for the `ActionEvent`, I don't even bother checking where the event came from. This method just has to get the text from the `JTextArea`, do the counting, and set the labels. The only interesting part is counting the words. Back in Exercise 3.4, words such as "can't", that contain an apostrophe, were counted as two words. This time around, let's handle this special case. Two letters with an apostrophe between them should be counted as part of the same word. The algorithm for counting words is still

>            wordCt = 0
>            for each character in the string:
>               if the character is the first character of a word:
>                  Add 1 to wordCt

> but testing whether a given character is the first character in a word has gotten a little more complicated. To make the test easier, I use a boolean variable, `startOfWord`. The value of this variable is set to true if the character is the start of a word and to false if not. That is, the algorithm becomes:

>            wordCt = 0
>            for each character in the string:
>               Let startOfWord be true if at start of word, false otherwise
>               if startOfWord is true:
>                  Add 1 to wordCt

> The use of a "flag variable" like `startOfWord` can simplify the calculation of a complicated boolean condition. The value is computed as a series of tests:

>            boolean startOfWord;  // Is character i the start of a word?
>            if ( Character.isLetter(text.charAt(i)) == false )
>               startOfWord = false;  // No.  It's not a letter.
>            else if (i == 0)
>               startOfWord = true;   // Yes.  It's a letter at start of text.
>            else if ( Character.isLetter(text.charAt(i-1)) )
>               startOfWord = false;  // No.  It's a letter preceded by a letter.
>            else if ( text.charAt(i-1) == '\'' && i > 1
>                                 && Character.isLetter(text.charAt(i-2)) )
>               startOfWord = false;  // No.  It's a continuation of a word
>                                     //      after an apostrophe.
>            else
>               startOfWord = true;   // Yes.  It's a letter preceded by
>                                     //       a non-letter.

> The first test checks whether the character in position `i` is a letter. If it is not, then we know that it can't be the start of a word, so `startOfWord` is false. If it is a letter, it might be the start of a word, so we go on to make additional tests. Note that if we get to the other tests at all, we already know that the character in position `i` is a letter. And so on. This style of "cascading tests" is very useful. In each test, we already have all the information from the previous tests. Note that the cascade effect works only with "`else if`". Using "`if`" in place of "`else if`" in the preceding code would not give the right answer. (You should be sure to understand why this is so.)

> The Solution

>         /*
>            In this applet, the user types some text in a JTextArea and presses
>            a button.  The applet computes and displays the number of lines
>            in the text, the number of words in the text, and the number of
>            characters in the text.  A word is defined to be a sequence of
>            letters, except that an apostrophe with a letter on each side
>            of it is considered to be a letter.  (Thus "can't" is one word,
>            not two.)
>         */

>         import java.awt.*;
>         import java.awt.event.*;
>         import javax.swing.*;

>         public class TextCounterApplet extends JApplet
>                                           implements ActionListener {

>            JTextArea textInput;     // For the user's input text.

>            JLabel lineCountLabel;   // For displaying the number of lines.
>            JLabel wordCountLabel;   // For displaying the number of words.
>            JLabel charCountLabel;   // For displaying the number of chars.

>            public void init() {

>               setBackground(Color.darkGray);
>               getContentPane().setBackground(Color.darkGray);

>               /* Create the text input area and make sure it has a
>                  white background. */

>               textInput = new JTextArea();
>               textInput.setBackground(Color.white);

>               /* Create a panel to hold the button and three display
>                  labels.  These will be laid out in a GridLayout with
>                  4 rows and 1 column. */

>               JPanel south = new JPanel();
>               south.setBackground(Color.darkGray);
>               south.setLayout( new GridLayout(4,1,2,2) );

>               /* Create the button, set the applet to listen for
>                  clicks on the button, and add it to the panel. */

>               JButton countButton = new JButton("Process the Text");
>               countButton.addActionListener(this);
>               south.add(countButton);

>               /* Create each of the labels, set their colors, and
>                  add them to the panel. */

>               lineCountLabel = new JLabel("  Number of lines:");
>               lineCountLabel.setBackground(Color.white);
>               lineCountLabel.setForeground(Color.blue);
>               lineCountLabel.setOpaque(true);
>               south.add(lineCountLabel);

>               wordCountLabel = new JLabel("  Number of words:");
>               wordCountLabel.setBackground(Color.white);
>               wordCountLabel.setForeground(Color.blue);
>               wordCountLabel.setOpaque(true);
>               south.add(wordCountLabel);

>               charCountLabel = new JLabel("  Number of chars:");
>               charCountLabel.setBackground(Color.white);
>               charCountLabel.setForeground(Color.blue);
>               charCountLabel.setOpaque(true);
>               south.add(charCountLabel);

>               /* Use a BorderLayout on the applet.  Although a BorderLayout
>                  is the default, I want one with a vertical gap of two
>                  pixels, to let the dark gray background color show through. */

>               getContentPane().setLayout( new BorderLayout(2,2) );

>               /* The text area is put into a JScrollPane to provide
>                  scroll bars for the TextArea, and the scroll pane is put in
>                  the Center position.  The panel that holds the button and
>                  labels is in the South position.  Note that the text area
>                  will be sized to fill the space that is left after the
>                  panel is assigned its preferred height. */

>               JScrollPane scroller = new JScrollPane( textInput );
>               getContentPane().add(scroller, BorderLayout.CENTER);
>               getContentPane().add(south, BorderLayout.SOUTH);

>            } // end init();

>            public Insets getInsets() {
>                  // Leave a 2-pixel border around the edges of the applet.
>               return new Insets(2,2,2,2);
>            }

>            public void actionPerformed(ActionEvent evt) {
>                  // Respond when the user clicks on the button by getting
>                  // the text from the text input area, counting the number
>                  // of chars, words, and lines that it contains, and
>                  // setting the labels to display the data.

>                String text;  // The user's input from the text area.

>                int charCt, wordCt, lineCt;  // Char, word, and line counts.

>                text = textInput.getText();

>                charCt = text.length();  // The number of characters in the
>                                         //    text is just its length.

>                /* Compute the wordCt by counting the number of characters
>                   in the text that lie at the beginning of a word.  The
>                   beginning of a word is a letter such that the preceding
>                   character is not a letter.  This is complicated by two
>                   things:  If the letter is the first character in the
>                   text, then it is the beginning of a word.  If the letter
>                   is preceded by an apostrophe, and the apostrophe is
>                   preceded by a letter, than its not the first character
>                   in a word.
>                */

>                wordCt = 0;
>                for (int i = 0; i < charCt; i++) {
>                   boolean startOfWord;  // Is character i the start of a word?
>                   if ( Character.isLetter(text.charAt(i)) == false )
>                      startOfWord = false;  // No.  It's not a letter.
>                   else if (i == 0)
>                      startOfWord = true;   // Yes.  It's a letter at start of text.
>                   else if ( Character.isLetter(text.charAt(i-1)) )
>                      startOfWord = false;  // No.  It's a letter preceded by a letter.
>                   else if ( text.charAt(i-1) == '\'' && i > 1
>                                        && Character.isLetter(text.charAt(i-2)) )
>                      startOfWord = false;  // No.  It's a continuation of a word
>                                            //      after an apostrophe.
>                   else
>                      startOfWord = true;   // Yes.  It's a letter preceded by
>                                            //       a non-letter.
>                   if (startOfWord)
>                      wordCt++;
>                }

>                /* The number of lines is just one plus the number of times the
>                   end of line character, '\n', occurs in the text. */

>                lineCt = 1;
>                for (int i = 0; i < charCt; i++) {
>                   if (text.charAt(i) == '\n')
>                      lineCt++;
>                }

>                /* Set the labels to display the data. */

>                lineCountLabel.setText("  Number of Lines:  " + lineCt);
>                wordCountLabel.setText("  Number of Words:  " + wordCt);
>                charCountLabel.setText("  Number of Chars:  " + charCt);

>            }  // end actionPerformed()

>         } // end class TextCounterApplet

> [ Exercises | Chapter Index | Main Index ]