Tag Library Number Guess Implementation

Chris W. Johnson
Information Technology Services
The University of Texas at Austin
May 31, 2007

This pure Java Server Pages (JSP) Tag Library implementation of the number guess game is designed to be nearly identical in features and appearance to the Qwicap implementation of the game. Because of the vast differences between the tag library and Qwicap approaches to web application development, this example bears no similarity in structure to the Qwicap example, and only a limited conceptual similarity to the pure JSP example.

Contents

—[ Return To: “Comparison of Number Guess Implementations” ]—

User Experience

Game In Progress, Bad Input

JSP implementation of Number Guess game upon receipt of bad input.

Game Complete

JSP implementation of Number Guess game upon completion of game.

Java Server Page "number-guess.jsp"

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html 
    PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ taglib prefix="guess" uri="/WEB-INF/tlguess.tld" %>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 
<head>
    <meta http-equiv="Content-Type" content='text/html; charset=UTF-8'/>
    <meta http-equiv="Content-Style-Type" content="text/css"/>
    <link rel="stylesheet" media="screen" type="text/css" title="Preferred" href="number-guess.css"/>
    <title>JSP Tag Library Number Guess</title>
</head>
<body>
    
    <h1>JSP Tab Library Number Guess</h1>
    
    <div class='content'>
        
        <guess:InputSection>
            <div class='guess'>
                <p>A random number between 0 and 100 (inclusive) has been selected.</p>
                
                <guess:InputError>
                    <div class='bad-field-error-message'>
                        <guess:InputErrorMessage/>
                    </div>
                </guess:InputError>
                
                <form method='post' action='number-guess.jsp'>
                    <label <guess:InputError>class='bad-field'</guess:InputError>>Guess the number: 
                        <input type='text' size='6' name='guess' <guess:InputError>value='<guess:BadInput/>'</guess:InputError>/>
                    </label>
                    <input type='submit' value='Guess'/>
                </form>
            </div>
        </guess:InputSection>
        
        <guess:DoneSection>
            <div class='done'>
                <p>Correct! The number was <guess:TheNumber/>. You guessed it in <guess:GuessCount/> attempts.</p>
                
                <form method='post' action='number-guess.jsp'>
                    <input type='submit' value='Play Again'/>
                    <input type='hidden' name='restart' value='restart'/>
                </form>
            </div>
        </guess:DoneSection>
        
        <guess:HistorySection>
            <div class='history'>
                <table class='history'>
                    <thead>
                        <tr>
                            <th>No.</th> <th>Guess</th> <th>Result</th>
                        </tr>
                    </thead>
                    <tbody>
                        <guess:HistoryRows>
                            <tr>
                                <td class='no'><guess:GuessNo/></td>
                                <td class='guess'><guess:GuessValue/></td>
                                <td class='result'><guess:GuessResult/></td>
                            </tr>
                        </guess:HistoryRows>
                    </tbody>
                </table>
            </div>
        </guess:HistorySection>
    
    </div>
    
</body>
</html>

Style Sheet "number-guess.css"

body {
    color: black;
    background-color: #d3c692;
    margin: 4% 6% 3% 6%;
    font-family: verdana, arial, helvetica, sans-serif;
}

div.content {
    background-color: #ffffee;
    border: 1px solid;
    padding: 1em 2em;
    text-align: center;
}

div.guess input[type="submit"] {
    width: 12ex;
}

table.history {
    margin-left:  auto;
    margin-right: auto;
    border-top: 1px solid gray;
    border-right: 1px solid gray;
    border-spacing: 0;
    border-collapse: collapse;
    empty-cells: show;
}

table.history th {
    padding: 0.2em 1em;
    border-left: 1px solid white;
    border-bottom: 1px solid gray;
    background-color: gray;
    color: white;
}

table.history th:first-child {
    border-left: 1px solid gray;
}

table.history td {
    padding: 0.2em 1em;
    border-left: 1px solid gray;
    border-bottom: 1px solid gray;
    text-align: center;
}

div.bad-field-error-message {
    border:         1px solid;
    background:     #ecc6c6;
    padding:        0.5em 2em;
    margin-top:     1em;
    margin-bottom:  1em;
    margin-left:    auto;
    margin-right:   auto;
}

*.bad-field {
    color: red;
}

File "WEB-INF/tlguess.tld"

<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">
    <description>A tag library implementing a number guess game.</description>
    <tlib-version>1.0</tlib-version>
    <short-name>NumberGuessTagLibrary</short-name>
    <uri>/tagsguess</uri>

    <tag>
        <name>BadInput</name>
        <tag-class>tagsguess.BadInput</tag-class>
        <body-content>empty</body-content>
        <description>Prints the most recent input, if it was bad.</description>
    </tag>
    <tag>
        <name>DoneSection</name>
        <tag-class>tagsguess.DoneSection</tag-class>
        <body-content>JSP</body-content>
        <description>
            If the game is complete, the body of this tag is included, otherwise it is omitted.
        </description>
    </tag>
    <tag>
        <name>GuessCount</name>
        <tag-class>tagsguess.GuessCount</tag-class>
        <body-content>empty</body-content>
        <description>Prints the number of guesses made during this game.</description>
    </tag>
    <tag>
        <name>GuessNo</name>
        <tag-class>tagsguess.GuessNo</tag-class>
        <body-content>empty</body-content>
        <description>
            When used inside a tagsguess.HistoryRows tag, prints the number of the guess in the current 
            row of history.
        </description>
    </tag>
    <tag>
        <name>GuessResult</name>
        <tag-class>tagsguess.GuessResult</tag-class>
        <body-content>empty</body-content>
        <description>
            When used inside a tagsguess.HistoryRows tag, prints the result of the guess in the current 
            row of history.
        </description>
    </tag>
    <tag>
        <name>GuessValue</name>
        <tag-class>tagsguess.GuessValue</tag-class>
        <body-content>empty</body-content>
        <description>
            When used inside a tagsguess.HistoryRows tag, prints the value of the guess in the current 
            row of history.
        </description>
    </tag>
    <tag>
        <name>HistoryRows</name>
        <tag-class>tagsguess.HistoryRows</tag-class>
        <body-content>JSP</body-content>
        <description>Iterates through the guess history, from most recent guess, to least recent.</description>
    </tag>
    <tag>
        <name>HistorySection</name>
        <tag-class>tagsguess.HistorySection</tag-class>
        <body-content>JSP</body-content>
        <description>
            If there is guess history available, the body of this tag is included, otherwise it is omitted.
        </description>
    </tag>
    <tag>
        <name>InputError</name>
        <tag-class>tagsguess.InputError</tag-class>
        <body-content>JSP</body-content>
        <description>
            If the most recent input was bad, the body of this tag is included, otherwise it is omitted.
        </description>
    </tag>
    <tag>
        <name>InputErrorMessage</name>
        <tag-class>tagsguess.InputErrorMessage</tag-class>
        <body-content>empty</body-content>
        <description>
            Retrieves the error message associated with the most recent input.
        </description>
    </tag>
    <tag>
        <name>InputSection</name>
        <tag-class>tagsguess.InputSection</tag-class>
        <body-content>JSP</body-content>
        <description>
            If the game is in progress (and therefore accepting input), the body of this tag is included, 
            otherwise it is omitted.
        </description>
    </tag>
    <tag>
        <name>TheNumber</name>
        <tag-class>tagsguess.TheNumber</tag-class>
        <body-content>empty</body-content>
        <description>
            Prints the number which the user is/was trying to guess.
        </description>
    </tag>

</taglib>

File "WEB-INF/web.xml"

<?xml version="1.0" encoding="ISO-8859-1"?> 

<!DOCTYPE web-app 
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd"> 

<web-app>

    <display-name>Number Guess Application Implemented With Tag Library</display-name>
    <description>
        Sample application demonstrating the implementation of a number guess game by means of a custom tag library.
    </description>
    
    <session-config>
        <session-timeout>10</session-timeout>
    </session-config>
    
    <taglib>
        <taglib-uri>/WEB-INF/lib/tlguess.jar</taglib-uri>
        <taglib-location>/WEB-INF/tlguess.tld</taglib-location>
    </taglib>
    
</web-app>

tagsguess/BadInput.java

package tagsguess;


import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;


public class BadInput extends TagSupport {

    public int doStartTag() throws JspException {
        
        try {
            pageContext.getOut().print(NumberGuessGame.getCurrentInstance(pageContext).getMostRecentInput());
            return SKIP_BODY;
        } catch (IOException e) {
            throw new JspException(e);
        }
    }
}

tagsguess/DoneSection.java

package tagsguess;


import javax.servlet.jsp.tagext.BodyTagSupport;


public class DoneSection extends BodyTagSupport {
    
    public int doStartTag() {
        
        if (NumberGuessGame.getCurrentInstance(pageContext).getIsDone())
            return EVAL_BODY_INCLUDE;
        
        return SKIP_BODY;
    }
}

tagsguess/GuessCount.java

package tagsguess;


import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.JspException;
import java.io.IOException;


public class GuessCount extends TagSupport {
    
    public int doStartTag() throws JspException {
        
        try {
            pageContext.getOut().print(NumberGuessGame.getCurrentInstance(pageContext).getGuessCount());
            return SKIP_BODY;
        } catch (IOException e) {
            throw new JspException(e);
        }
    }
}

tagsguess/GuessNo.java

package tagsguess;


import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.JspException;
import java.io.IOException;


public class GuessNo extends TagSupport {
    
    public int doStartTag() throws JspException {
        
        try {
            final HistoryRows Row = (HistoryRows) findAncestorWithClass(this, HistoryRows.class);
            
            pageContext.getOut().print(Row.getGuessNo());
            return SKIP_BODY;
        } catch (IOException e) {
            throw new JspException(e);
        }
    }
}

tagsguess/GuessResult.java

package tagsguess;


import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.JspException;
import java.io.IOException;


public class GuessResult extends TagSupport {
    
    public int doStartTag() throws JspException {
        
        try {
            final HistoryRows Row = (HistoryRows) findAncestorWithClass(this, HistoryRows.class);
            
            pageContext.getOut().print(Row.getGuessResult());
            return SKIP_BODY;
        } catch (IOException e) {
            throw new JspException(e);
        }
    }
}

tagsguess/GuessValue.java

package tagsguess;


import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.JspException;
import java.io.IOException;


public class GuessValue extends TagSupport {
    
    public int doStartTag() throws JspException {
        
        try {
            final HistoryRows Row = (HistoryRows) findAncestorWithClass(this, HistoryRows.class);
            
            pageContext.getOut().print(Row.getGuess());
            return SKIP_BODY;
        } catch (IOException e) {
            throw new JspException(e);
        }
    }
}

tagsguess/HistoryRows.java

package tagsguess;


import javax.servlet.jsp.tagext.BodyTagSupport;


public class HistoryRows extends BodyTagSupport {
    
    private NumberGuessGame     Game;
    private int                 Index;
    
    public int doStartTag() {
        
        Game = NumberGuessGame.getCurrentInstance(pageContext);
        Index = Game.getGuessCount() - 1;
        
        return EVAL_BODY_INCLUDE;
    }
    
    public int getGuessNo() {
        return Index + 1;
    }
    
    public int getGuess() {
        return Game.getGuess(Index);
    }
    
    public String getGuessResult() {
        final int               Guess = getGuess();
        final int               TheNumber = Game.getTheNumber();
        String                  Result = "Correct!";
        
        if (Guess < TheNumber)
            Result = "Too Low";
        else if (Guess > TheNumber)
            Result = "Too High";
            
        return Result;
    }
    
    public int doAfterBody() {
        
        if (--Index >= 0)
            return EVAL_BODY_AGAIN; //  EVAL_BODY_TAG;
        
        return SKIP_BODY;
    }
    
    public void release() {
        Game = null;
    }
}

tagsguess/HistorySection.java

package tagsguess;


import javax.servlet.jsp.tagext.BodyTagSupport;


public class HistorySection extends BodyTagSupport {
    
    public int doStartTag() {
        
        if (NumberGuessGame.getCurrentInstance(pageContext).getHasHistory())
            return EVAL_BODY_INCLUDE;
        
        return SKIP_BODY;
    }
}

tagsguess/InputError.java

package tagsguess;


import javax.servlet.jsp.tagext.BodyTagSupport;


public class InputError extends BodyTagSupport {
    
    public int doStartTag() {
        
        if (NumberGuessGame.getCurrentInstance(pageContext).getInputWasBad())
            return EVAL_BODY_INCLUDE;
        
        return SKIP_BODY;
    }
}

tagsguess/InputErrorMessage.java

package tagsguess;


import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.JspException;
import java.io.IOException;


public class InputErrorMessage extends TagSupport {
    
    public int doStartTag() throws JspException {
        
        try {
            pageContext.getOut().print(NumberGuessGame.getCurrentInstance(pageContext).getInputErrorMessage());
            return SKIP_BODY;
        } catch (IOException e) {
            throw new JspException(e);
        }
    }
}

tagsguess/InputSection.java

package tagsguess;


import javax.servlet.jsp.tagext.BodyTagSupport;


public class InputSection extends BodyTagSupport {
    
    public int doStartTag() {
        NumberGuessGame         Game = NumberGuessGame.getCurrentInstance(pageContext);
        final String            GuessStr = pageContext.getRequest().getParameter("guess");
        
        if (GuessStr != null)           
            Game.play(GuessStr);
        else if (pageContext.getRequest().getParameter("restart") != null)
            Game = NumberGuessGame.startNewInstance(pageContext);
        
        return Game.getInProgress() ? EVAL_BODY_INCLUDE : SKIP_BODY;
    }
}

tagsguess/NumberGuessGame.java

package tagsguess;


import javax.servlet.jsp.PageContext;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.Random;


public class NumberGuessGame {
    
    private static final String GameAttributeName = "GuessGame";
    
    private final ArrayList     Hist = new ArrayList();
    private final int           TheNumber;
    private boolean             IsDone = false;
    private String              BadInputErrorMsg;
    private String              MostRecentInput;
    
    
    public static NumberGuessGame getCurrentInstance
    (
        final PageContext       PageCtxt
    )
    {
        NumberGuessGame         Game = (NumberGuessGame) PageCtxt.getSession().getAttribute(GameAttributeName);
        
        if (Game == null)
            Game = startNewInstance(PageCtxt);
        
        return Game;
    }
    
    public static NumberGuessGame startNewInstance
    (
        final PageContext       PageCtxt
    ) 
    {
        final HttpSession       Sess = PageCtxt.getSession();
        final NumberGuessGame   Game = new NumberGuessGame();
        
        Sess.setAttribute(GameAttributeName, Game);
        
        return Game;
    }
    
    public NumberGuessGame() {
        TheNumber = new Random().nextInt(101);
    }
    
    public void play
    (
        final String            GuessStr
    ) 
    {
        if (IsDone)
            return;
        
        MostRecentInput = GuessStr;
        BadInputErrorMsg = null;
        
        try {
            final int               Guess = Integer.parseInt(GuessStr);
            
            if (Guess < 0 || Guess > 100) {
                
                BadInputErrorMsg = "The guess must be in the range 0 to 100 (inclusive). 
                    The number \"" + Guess + "\" is not in that range.";
                
            } else {
                
                Hist.add(new Integer(Guess));
                
                if (Guess == TheNumber)
                    IsDone = true;
            }
            
        } catch (NumberFormatException e) {
            BadInputErrorMsg = "The guess \"" + GuessStr + "\" is not a number.";
        }
    }
    
    public int getTheNumber() {
        return TheNumber;
    }
    
    public boolean getInProgress() {
        return !IsDone;
    }
    
    public boolean getHasHistory() {
        return getGuessCount() > 0;
    }
    
    public int getGuessCount() {
        return Hist.size();
    }
    
    public int getGuess
    (
        final int               Index
    )
    {
        return ((Integer) Hist.get(Index)).intValue();
    }
    
    public boolean getIsDone() {
        return IsDone;
    }
    
    public boolean getInputWasBad() {
        return BadInputErrorMsg != null;
    }
    
    public String getInputErrorMessage() {
        return BadInputErrorMsg;
    }
    
    public String getMostRecentInput() {
        return MostRecentInput;
    }
}

tagsguess/TheNumber.java

package tagsguess;


import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.JspException;
import java.io.IOException;


public class TheNumber extends TagSupport {
    
    public int doStartTag() throws JspException {
        
        try {
            pageContext.getOut().print(NumberGuessGame.getCurrentInstance(pageContext).getTheNumber());
            return SKIP_BODY;
        } catch (IOException e) {
            throw new JspException(e);
        }
    }
}
Valid XHTML 1.0! Valid CSS!