Java Meets Novell's GroupWise API Gateway, Part III
Articles and Tips: article
Senior Consultant
Consulting Services
AL YOUNG
Senior Research Engineer
Developer Information
DAYLE S WOOLSTON
Senior Software Engineer
Advanced Development Group
01 Aug 1997
Focuses on exception handling and validation.
Introduction
In this third installment, we again assume a readership of application developers that may be new to object-oriented analysis and design, as well as being somewhat new to Java. As announced in previous articles in this series1-2, the discussion in the present installment focuses on exception handling and validation.
Handling Exceptions
Readers who have followed this series may have remarked that some code listings in previous installments have included exception-handling behavior. For example, some methods have been specified in this general form:
method_name throws java.io.IOException {}
It was necessary to specify such behavior in previous code listings because some of the methods in those listings invoked methods, in basic Java classes, that require specification of exception-handling behavior. For example, if method A exists in a basic Java class (or in any class, for that matter), and method A throws an exception, any method B that invokes A must specify either the exception-object to be thrown or handle the exception.
Where previous code listings have necessitated a throws statement, we have specified minimum exception-handling behavior in the code listing by declaring only that the invoking method throws an instance of java.io.IOException. This contingency made it possible for us to avoid having to intertwine a discussion of exception handling with the discussion of earlier topics.
In this article, we consider exception handling by first examining methods from our own code listings that invoke methods (from basic Java classes) that require an instance of Throwable. We then talk about why we thought it necessary to create our own subclasses of Throwable, for the purpose of exception handling within our own application classes. Finally, we conclude with examples of how our application classes use our customized subclasses of Throwable.
Let's first take a look at the two methods, from this article series to date, that have thrown exceptions:
Message(aFile, bFile)-- Appearing in the second installment in this series (see reference 2) -- Code Listing 2 in that article.
parse(aFile, bFile)-- Also appearing in the second article in this series -- Code Listing 3 of that article.
For convenience, we repeat the Message (aFile, bFile) and parse(aFile, bFile) in this article's Code Listing 1 and Code Listing 2.
Code Listing 1
1: public Message(File aFile, File bFile) throws java.io.IOException { 2: Message aMessage = new Message(); 3: aMessage.parse(aFile, bFile); 4: }
As noted in the article in which these code listings appeared originally, Code Listing 2 presents the contents of the parse() method invoked by line 3 in Code Listing 1.
Code Listing 2
1: public void parse(File aFile, File bFile) throws java.io.IOException { 2: clear(); 3: file = aFile; 4: InputStream anInputStreamOnFile = new FileInputStream(aFile); 5: GwapiTokenInputStream anInputStream = new GwapiTokenInputStream(anInputStreamOnFile, ContentComponentModel.keywords); 6: parseMe(anInputStream, bFile); 7: }
The Message(aFile, bFile) method, in Code Listing 1, throws an exception because the method invokes parse(aFile, bFile). The parse(aFile, bFile) method throws an exception because in its behavior (line 4 of Code Listing 2) a constructor is invoked (i.e., FileInputStream(aFile)) that throws either a SecurityException or a FileNotFoundException.3
In Code Listing 2, another constructor is invoked:
GwapiTokenInputStream(anInputStreamOnFile, ContentComponentModel.keywords)
Nevertheless, neither the constructor nor any of the methods it invokes throw exceptions. Consequently, its presence in the method requires no exception handling. (To confirm this, see Code Listing 1 in the second article in this series, and look up the methods it invokes (i.e., LineNumberInputStream anInputStream) and PushbackInputStream (inputWithLineNumbers).
So far, then, we've identified the need to handle two kinds of exceptions thrown by basic Java classes:
SecurityException
FileNotFoundException
We now present a more complete version of the Message(aFile, bFile) constructor presented in Code Listing 1. The only change we've made in the revised method (see Code Listing 3), is to indicate that the constructor can throw either an instance of java.io.IOException, or an instance of ParsingException.
Code Listing 3
1: public Message(File aFile, File bFile) throws java.io.IOException, ParsingException { 2: Message aMessage = new Message(); 3: aMessage.parse(aFile, bFile); 4: }
ParsingException is an exception class that we've added to the Throwable hierarchy because we want exception behavior otherwise unavailable in that hierarchy.
Ken Arnold and James Gosling make some important points about when to add exception classes to the Throwable hierarchy:
Adding useful data is one reason to create a new exception type.
Another reason to create a new exception is that the type of the exception is an important part of the exception data, because exceptions are caught according to their type. . . .
In general, new exception types should be created when programmers will want to handle one kind of error and not another. Programmers can then use the exception type to execute the correct code, rather than examining the contents of the exception to determine whether they really care about the exception, or caught an irrelevant exception by accident.4
We've made ParsingException a subclass of Exception, and create it with the definition appearing in Code Listing 4.
Code Listing 4
1: class ParsingException extends java.lang.Exception { 2: ParsingException(String aString) { 3: super(aString); 4: } 5: }
In Code Listing 4, the constructor for our customized exception class merely invokes the constructor in its superclass, passing aString in to that constructor.
We also need to modify the behavior appearing in Code Listing 2, so that the parse(aFile, bFile) method also throws an instance of ParsingException. Code Listing 5 presents the modified version of the method. Again, the only change we've made in the new version is to specify that the method throws either an instance of java.io.IOException or an instance of ParsingException.
Code Listing 5
1: public void parse(File aFile, File bFile) throws java.io.IOException, ParsingException { 2: clear(); 3: file = aFile; 4: InputStream anInputStreamOnFile = new FileInputStream(aFile); 5: GwapiTokenInputStream anInputStream = new GwapiTokenInputStream(anInputStreamOnFile, ContentComponentModel.keywords); 6: parseMe(anInputStream, bFile); 7: }
The reason we've changed the parse(aFile, bFile) method to indicate that it can throw an instance of ParsingException, is due to the fact that the parseMe(anInputStream,bFile) method appearing on line 6 of Code Listing 5 also throws an instance of ParsingException. In the second article in this series, we presented a partial description of the behavior contained in the parseMe(anInputStream, bFile) method belonging to each instance of Message. (See Code Listing 4 in the second article in this series.) Now, a more complete version of the parseMe method appears in Code Listing 6.
Code Listing 6
1: protected void parseMe(GwapiTokenInputStream anInputStream, File bFile) throws java.io.IOException, ParsingException{ 2: boolean finishedParsing = false; 3: while (!finishedParsing) { 4: int aToken = anInputStream.getKeyword(); 5: if(aToken == ContentComponentModel.SUBJECT_TOK) { 6: anInputStream.pushBackKeyword(); 7: subject = new Subject(); 8: subject.parse(anInputStream); 9: anInputStream.getSemicolon(); . . . 10: } else { 11: throw new ParsingException(this.getClass().getName() + ": search for keyword -- '" + ContentComponentModel.getKeywordName(aToken) + '" -- failed at line " + anInputStream.lineNumber()); 11: } 12: }
Line 11 of Code Listing 6 invokes the ParsingException() constructor presented in Code Listing 4. Note that the constructor accepts an instance of String as an argument. All of the behavior specified in the parenthetical statement on line 11 merely constructs the string to be passed in to the constructor. For example, this.getClass().getName() simply asks the receiver to return the name of the class of which the receiver is an instance. The receiver, referred to by this, is an instance of Message. Thus, the following line of code:
this.getClass().getName() + ":search for keyword -- '"
Produces the following string:
Message: search for keyword --
Continuing along line 11 in Code Listing 6, we encounter the following statement:
ContentComponentModel.getKeyword(aToken)
The foregoing code excerpt asks the ContentComponentModel class to return the keyword string matching aToken. In other words, the value belonging to aToken when the else statement fires is the value passed in to the getKeyword() method belonging to the ContentComponentModel class. Thus, if the parseMe() method is looking for SUBJECT_TOK when the else statement is invoked, the string being built at line 11 would look like this:
Message: search for keyword -- subject -- failed at line
Finally, line 11 in Code Listing 6 asks anInputStream to return the number of the line that was being parsed when the else statement fired. Code Listing 7 presents the lineNumber() method, defined in GwapiTokenInputStream.java.
Code Listing 7
1: int lineNumber() { 2: return inputWithLineNumbers.getLineNumber(); 3: }
In understanding Code Listing 7, it should be remembered that inputWithLineNumbers is an instance variable belonging to each instance of GwapiTokenInputStream. The lineNumber() behavior defined for each instance of GwapiTokenInputStream merely asks the instance of LineNumberInputStream assigned to the inputWithLineNumbers variable to return the current line number by executing the stream's getLineNumber() method.
The instance of ParsingException having been created at line 11 of Code Listing 6, the instance is then handed back to the parse(aFile, bFile) method presented in Code Listing 5. The parse(aFile, bFile) method then hands the exception back to the Message(aFile, bFile) constructor presented in Code Listing 3. It is this constructor that a Java application would invoke upon deciding to parse a GroupWise header file.
What such an application would do upon receiving an instance of ParsingException is outside the scope of this installment in our series; nevertheless, it may be remarked that determining what such an application would do with an exception involves such things as whether the processing of GroupWise header files is a batch-mode or interactive process, whether sender notification is required, reporting, etc.
Figure 1 presents a class diagram for ParsingException as we've defined and used ParsingException up to this point in our discussion.
Figure 1: Class diagram for ParsingException.
Suppose, however, that a Java application (i.e., an application outside the scope of this particular article) wants to know when the parsing exception occurred. For example, suppose the organizational context of such a Java application requires that if a GroupWise header file isn't processed, some proof must be provided that an attempt was made to do so.
To fulfill such a requirement, we might add a timestamp variable to ParsingException so that in addition to returning an explanation of the problem, the parsing exception can be used to report and verify the attempt.
Figure 2 illustrates the addition of a timestamp variable to the hierarchy.
Figure 2: ParsingException with timestamp variable.
Code Listing 8 shows the new definition of ParsingException, appearing in ParsingException.java. (Code Listing 8 rewrites Code Listing 4, above.)
Code Listing 8
1: class ParsingException extends java.io.Exception { 2: Date timestamp; 3: ParsingException(String aString) { 4: super(aString); 5: timestamp = new Date(); 6: } 7: }
Line 2 of Code Listing 8 adds the timestamp variable to instances of ParsingException. Lines 3 - 6 redefine the ParsingException(String aString) constructor so that the instance's timestamp variable is assigned an instance of Date automatically as part of instantiation. Note that by having the timestamp variable assigned within the rewritten constructor (as opposed, for example, to creating another constructor that accepts a date object as well as a string), the added functionality requires no rewrite of Code Listing 6. This is a modest example of encapsulation, or information hiding.5
Figure 3 presents, in the form of an indented list, all of the classes involved in the hierarchy we've been discussing in this article series. In italics, next to the class to which each method belongs, is the signature of each method in the hierarchy that throws an instance of ParsingException.
Figure 3: Indented list of all classes in sample hierarchy.
Object ContentComponentModel parse(anInputStream) AssignmentModel parse(anInputStream) AddressModel parse(anInputStream) parseParenthesizedUserList(anInputStream,aDomain,aPostOfice) AllTo AllToCc From To ToBc ToCc AttachFileField parse(anInputStream,bFile) DateModel parse(anInputStream) DateField DateSent EnumerationModel parse(anInputStream) MessagePriority MessageType Security FileModel parse(anInputStream,bFile) CurrentFile MessageFile StringModel parse(anInputStream) FileSize FromText HeaderCharacter MessageId OriginalFile SendOptions StatusRequest Subject ToCcText ToText WpcApi AttachedFile parse(anInputStream,bFile) ConversionAllowed EndField Exception InvalidValueException ParsingException GwapiMessageTest GwapiTokenInputStream getSemicolon() getEquals() getValue() getKeyword() getString(delimiters,skipLeadingWhiteSpace) Message parse(aFile,bFile) parseMe(anInputStream,bFile) TokenArray getTokenValue(aName) TokenNode
Thus far in this article and in this article series, we have discussed only the use of ParsingException involved in the two methods listed next to the Message class in Figure 3. We now take a look at a sampling of the parse() methods in the ContentComponentModel hierarchy, focusing on the StringModel part of that hierarchy. Then we'll look at the use made of ParsingException in the GwapiTokenInputStream and the TokenArray classes.
Code Listing 9 presents the parse(anInputStream) method belonging to instances of StringModel.
Code Listing 9
1: void parse(GwapiTokenInputStream anInputStream) throws java.io.IOException, ParsingException { 2: super.parse(anInputStream); 3: value = anInputStream.getValue(); 4: }
Perhaps the first thing to be remarked from an examination of Code Listing 9 is that even though the message definition says that the method can throw an instance of ParsingException, the method doesn't appear to do anything with ParsingException. The reason that the parse() method in Code Listing 9 throws an instance of ParsingException becomes evident when we look at the definition of the parse() method in AssignmentModel, invoked at line 2 in Code Listing 9. AssignmentModel is the immediate superclass of StringModel. The parse() method belonging to AssignmentModel appears in Code Listing 10.
Code Listing 10
1: void parse(GwapiTokenInputStream anInputStream) throws java.io.IOException, ParsingException { 2: super.parse(anInputStream); 3: int aCharacter = anInputStream.read(); 4: if (aCharacter != '=') 5: throw new ParsingException("Equal sign not found at line " + anInputStream.lineno()); 6: }
For the time being, let's skip line 2 in Code Listing 10 and look at lines 3 - 5. Line 3 assigns a character from anInputStream to the temporary variable aCharacter. Line 4 tests aCharacter to determine whether it is an equal sign. If aCharacter isn't an equal sign, line 5 creates an instance of ParsingException, assigns to that instance an explanation of the problem, and throws an instance of ParsingException.
Because the method defined in Code Listing 10 throws an instance of ParsingException, the method in Code Listing 9 must be defined as throwing an instance of ParsingException, even though the method definition in Code Listing 9 appears to do nothing with ParsingException.
In Code Listing 10, line 2 invokes the parse() method belonging to ContentComponentModel. The definition of ContentComponentModel's parse() method appears in Code Listing 11.
Code Listing 11
1: void parse(GwapiTokenInputStream anInputStream) throws java.io.IOException, ParsingException { 2: int aToken = anInputStream.getKeyword(); 3: if (aToken != getMyToken()) 4: throw new ParsingException(getKeywordName(getMyToken()) + ": expected at line " + anInputStream.lineno()); 5: }
As with other methods in the ContentComponentModel hierarchy, the method in Code Listing 11 instantiates a ParsingException and assigns an explanation of failure. Other parse() methods listed in the ContentComponentModel hierarchy in Figure 3 make similar use of ParsingException; that is, the parse() methods return an explanation of the reason a parsing operation failed.
We now turn our attention to the manner in which GwapiTokenInputStream uses ParsingException. The use, however, is not significantly different from what we've seen in ContentComponentModel. Code Listing 12, which presents GwapiTokenInputStream's getSemicolon() method is representative of the other methods, listed in Figure 3, for GwapiTokenInputStream. Given the similarity of usage between Code Listing 12 and other code listings in this article, no explanation of Code Listing 12 seems necessary.
Code Listing 12
1: void getSemicolon() throws java.io.IOException, ParsingException { 2: if (pushedBack) 3: throw new ParsingException("Semicolon not found at line " + lineno() ); 4: contents = null; 5: skipWhite(); 6: int aCharacter = input.read(); 7: if (aCharacter == ';') { 8: return; 9: } else { 10: input.unread(aCharacter); 11: throw new ParsingException("Semicolon not found at line " + lineno() ); 12: } 13: }
The getTokenValue() method in TokenArray also throws an instance of ParsingException. Code Listing 13 presents the getTokenValue() method, whose purpose is to return the token associated with a particular name. TokenArray's use of ParsingException is similar to the manner in which the exception is used by all the other classes in our hierarchy.
Code Listing 13
1: public int getTokenValue( String name ) throws ParsingException { 2: TokenNode target = (TokenNode)tokenNameHash.get( name.toUpperCase() ); 3: if (target == null) 4: throw new ParsingException("Unrecognized keyword ' " + name + "'" ); 5: else 6: return target.token.intValue(); 7: }
In addition to ParsingException, we've created InvalidValueException as another subclass of Exception. Code Listing 14 presents the definition of InvalidValueException.
Code Listing 14
1: class InvalidValueException extends java.lang.Exception { 2: InvalidValueException(String s) { 3: super(s); 4: } 5: }
The class definition, in Code Listing 14, does no more than specify a constructor that hands construction over to the immediate superclass. Like ParsingException, InvalidValueException adds neither variables nor behavior to the hierarchy.
ParsingException focuses on whether a record in a GroupWise header file is structured appropriately (e.g., whether an anticipated key word is present, whether an equal sign appears where it's supposed to). InvalidValueException focuses on whether or not the value in a record is appropriate (e.g., even if a record's syntax is appropriate, is the value assigned to that record appropriate -- for example, is the date in a date record a date and, if so, is it within an acceptable range).
ParsingException enables our Java classes to provide reasonable confidence that a particular record is the record it's supposed to be. InvalidFieldException allows a Java application to implement organizational or procedural policies focusing on values assigned to records in GroupWise header files.
For example, Code Listing 15 presents the kind pf validity checking that can be performed by any instance of Message. Note, however, that there are various kinds of GroupWise messages and that message contents can vary from one kind of message to another. Code Listing 15 presents a simple view of validity checking in that the variations of message content are not addressed.
Code Listing 15
1: public void isValid() throws InvalidValueException { 2: if (wpcApi != null) 3: wpcApi.isValid(); 4: else 5: throw new InvalidValueException(this.getClass().getName() + ": message must contain a wpcApi field"); 6: if (headerCharacter != null) 7: headerCharacter.isValid(); 8: if (messageType != null) 9: messageType.isValid(); 10: if (fromText != null) 11: fromText.isValid(); 12: if (from != null) 13: from.isValid(); 14: if (to != null) 15: to.isValid(); 16: if (allTo != null) 17: allTo.isValid(); 18: if (toCc != null) 19: toCc.isValid(); 20: if (allToCc != null) 21: allToCc.isValid(); 22: if (toBc != null) 23: toBc.isValid(); 24: if (messageId != null) 25: messageId.isValid(); 26: if (messageFile != null) 27: messageFile.isValid(); 28: if (toText != null) 29: toText.isValid(); 30: if (toCcText != null) 31: toCcText.isValid(); 32: if (subject != null) 33: subject.isValid(); 34: if (dateSent != null) 35: dateSent.isValid(); 36: if (security != null) 37: security.isValid(); 38: if (sendOptions != null) 39: sendOptions.isValid(); 40: if (statusRequest != null) 41: statusRequest.isValid(); 42: if (messagePriority != null) 43: messagePriority.isValid(); 44: if (attachFile != null) 45: attachFile.isValid(); 46: if (end != null) 47: end.isValid(); 48: else 59: throw new InvalidValueException(this.getClass().getName() + ": message must contain an end field"); 50: }
In Code Listing 16, we examine the behavior invoked by line 42 of Code Listing 15. The validation in Code Listing 16 ensures that the value, in a header file, specified as the priority of a message is one of the three priorities anticipated.
Code Listing 16
1: protected boolean isValidValue() { 2: if (((value == NORMAL_TOK) || 3: (value == LOW_TOK) || 4: (value == HIGH_TOK) ) 5: return true; 6: else 7: return false; 8: }
Methods testing the validity of a value can be as simple or as complex as required. Because our hierarchy includes a class representing each record in a GroupWise header file, and because validation is performed by each class, not only is a class's validation insulated from other classes, but maintaining the validation code of the hierarchy as a whole is to some extent insulated from changes in policies and procedures governing use of the Java application classes.
References
Johnson, Ashley; Young, Al; Woolston, Dayle S. "Java Meets Novell's GroupWise API Gateway, Part I," Novell Developer Notes(vol. 4, no. 5), May 1997, p. 3.
Johnson, Ashley; Young, Al; Woolston, Dayle S. " Java Meets Novell's GroupWise API Gateway, Part II," Novell Developer Notes(vol. 4, no. 7), July 1997, p. 40.
Gosling, James; Joy, Bill; Steele, Guy. The Java Language Specification. Reading, Massachusetts: Addison Wesley Longman, Inc., 1996, p. 684.
Arnold, Ken; Gosling, James. The Java Programming Language. Reading, Massachusetts: Addison-Wesley Publishing Company, Inc., 1996, pp. 134-135.
Booch, Grady. Object-Oriented Analysis and Design with Applications, 2nd ed. Menlo Park, California: Addison-Wesley Publishing Company, 1994, p. 49.
* Originally published in Novell AppNotes
Disclaimer
The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.