Model Objects
RuntimeException
,
and are usually implemented using
IllegalArgumentException
,
NullPointerException
,
or IllegalStateException
Exception
RuntimeException
(unchecked) is itself a subclass of Exception
(checked).
Example 1
Model Objects are the data-centric classes
used to represent items in a particular domain. Model Object constructors
need to handle both arbitrary user input, and input from underlying database
ResultSet
s.
Model Object constructors should throw checked exceptions:
ResultSet
s
as having arbitrary, unvalidated content.ModelCtorException
(a checked exception) :
package hirondelle.fish.main.resto; import hirondelle.web4j.model.ModelCtorException; import hirondelle.web4j.model.ModelUtil; import hirondelle.web4j.model.Id; import hirondelle.web4j.security.SafeText; import hirondelle.web4j.model.Decimal; import static hirondelle.web4j.model.Decimal.ZERO; import hirondelle.web4j.model.Check; import hirondelle.web4j.model.Validator; import static hirondelle.web4j.util.Consts.FAILS; /** Model Object for a Restaurant. */ public final class Resto { /** Full constructor. @param aId underlying database internal identifier (optional) 1..50 characters @param aName of the restaurant (required), 2..50 characters @param aLocation street address of the restaurant (optional), 2..50 characters @param aPrice of the fish and chips meal (optional) $0.00..$100.00 @param aComment on the restaurant in general (optional) 2..50 characters */ public Resto( Id aId, SafeText aName, SafeText aLocation, Decimal aPrice, SafeText aComment ) throws ModelCtorException { fId = aId; fName = aName; fLocation = aLocation; fPrice = aPrice; fComment = aComment; validateState(); } public Id getId() { return fId; } public SafeText getName() { return fName; } public SafeText getLocation() { return fLocation; } public Decimal getPrice() { return fPrice; } public SafeText getComment() { return fComment; } @Override public String toString(){ return ModelUtil.toStringFor(this); } @Override public boolean equals(Object aThat){ Boolean result = ModelUtil.quickEquals(this, aThat); if (result == null) { Resto that = (Resto) aThat; result = ModelUtil.equalsFor( this.getSignificantFields(), that.getSignificantFields() ); } return result; } @Override public int hashCode(){ if (fHashCode == 0){ fHashCode = ModelUtil.hashCodeFor(getSignificantFields()); } return fHashCode; } // PRIVATE private final Id fId; private final SafeText fName; private final SafeText fLocation; private final Decimal fPrice; private final SafeText fComment; private int fHashCode; private static final Decimal HUNDRED = Decimal.from("100"); private void validateState() throws ModelCtorException { ModelCtorException ex = new ModelCtorException(); if (FAILS == Check.optional(fId, Check.range(1,50))) { ex.add("Id is optional, 1..50 chars."); } if (FAILS == Check.required(fName, Check.range(2,50))) { ex.add("Restaurant Name is required, 2..50 chars."); } if (FAILS == Check.optional(fLocation, Check.range(2,50))) { ex.add("Location is optional, 2..50 chars."); } Validator[] priceChecks = {Check.range(ZERO, HUNDRED), Check.numDecimalsAlways(2)}; if (FAILS == Check.optional(fPrice, priceChecks)) { ex.add("Price is optional, 0.00 to 100.00."); } if (FAILS == Check.optional(fComment, Check.range(2,50))) { ex.add("Comment is optional, 2..50 chars."); } if ( ! ex.isEmpty() ) throw ex; } private Object[] getSignificantFields(){ return new Object[] {fName, fLocation, fPrice, fComment}; } }
Args
is a convenient utility class. It performs common validations
on method arguments. If a validation fails, then it throws an unchecked
exception. It is suitable for checking the internal consistency of program,
but not for checking arbitrary user input.
package hirondelle.web4j.util; import java.util.regex.*; /** Utility methods for common argument validations. <P>Replaces <tt>if</tt> statements at the start of a method with more compact method calls. <P>Example use case. <P>Instead of : <PRE> public void doThis(String aText){ if (!Util.textHasContent(aText)){ throw new IllegalArgumentException(); } //..main body elided } </PRE> <P>One may instead write : <PRE> public void doThis(String aText){ Args.checkForContent(aText); //..main body elided } </PRE> */ public final class Args { /** If <code>aText</code> does not satisfy {@link Util#textHasContent}, then throw an <code>IllegalArgumentException</code>. <P>Most text used in an application is meaningful only if it has visible content. */ public static void checkForContent(String aText){ if( ! Util.textHasContent(aText) ){ throw new IllegalArgumentException("Text has no visible content"); } } /** If {@link Util#isInRange} returns <code>false</code>, then throw an <code>IllegalArgumentException</code>. @param aLow is less than or equal to <code>aHigh</code>. */ public static void checkForRange(int aNumber, int aLow, int aHigh) { if ( ! Util.isInRange(aNumber, aLow, aHigh) ) { throw new IllegalArgumentException(aNumber + " not in range " + aLow + ".." + aHigh); } } /** If <tt>aNumber</tt> is less than <tt>1</tt>, then throw an <tt>IllegalArgumentException</tt>. */ public static void checkForPositive(int aNumber) { if (aNumber < 1) { throw new IllegalArgumentException(aNumber + " is less than 1"); } } /** If {@link Util#matches} returns <tt>false</tt>, then throw an <code>IllegalArgumentException</code>. */ public static void checkForMatch(Pattern aPattern, String aText){ if (! Util.matches(aPattern, aText)){ throw new IllegalArgumentException( "Text " + Util.quote(aText) + " does not match '" +aPattern.pattern()+ "'" ); } } /** If <code>aObject</code> is null, then throw a <code>NullPointerException</code>. <P>Use cases : <pre> doSomething( Football aBall ){ //1. call some method on the argument : //if aBall is null, then exception is automatically thrown, so //there is no need for an explicit check for null. aBall.inflate(); //2. assign to a corresponding field (common in constructors): //if aBall is null, no exception is immediately thrown, so //an explicit check for null may be useful here Args.checkForNull( aBall ); fBall = aBall; //3. pass on to some other method as parameter : //it may or may not be appropriate to have an explicit check //for null here, according the needs of the problem Args.checkForNull( aBall ); //?? fReferee.verify( aBall ); } </pre> */ public static void checkForNull(Object aObject) { if (aObject == null) { throw new NullPointerException(); } } // PRIVATE private Args(){ //empty - prevent construction } }