package hirondelle.web4j.model;
import java.io.Serializable;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import hirondelle.web4j.model.ModelUtil;
import hirondelle.web4j.util.Consts;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.security.SafeText;
/**
Building block class for identifiers.
<P>Identifiers are both common and important. Unfortunately, there is no class in the
JDK specifically for identifiers.
<P>An <tt>Id</tt> class is useful for these reasons :
<ul>
<li>it allows model classes to read at a higher level of abstraction. Identifiers are
labeled as such, and stand out very clearly from other items
<li>it avoids a common <a href="http://www.javapractices.com/Topic192.cjp">problem</a>
in modeling identifiers as numbers.
</ul>
<P>The underlying database column may be modeled as either text or as a number.
If the underlying column is of a numeric type, however, then a Data Access Object
will need to pass <tt>Id</tt> parameters to {@link hirondelle.web4j.database.Db}
using {@link #asInteger} or {@link #asLong}.
<P><em>Design Note :</em><br>
This class is <tt>final</tt>, immutable, {@link Serializable},
and {@link Comparable}, in imitation of the other building block classes
such as {@link String}, {@link Integer}, and so on.
*/
public final class Id implements Serializable, Comparable<Id> {
/**
Construct an identifier using an arbitrary {@link String}.
This class uses a {@link SafeText} object internally.
@param aText is non-null, and contains characters that are allowed
by {@link hirondelle.web4j.security.PermittedCharacters}.
*/
public Id(String aText) {
fId = new SafeText(aText);
validateState();
}
/**
Factory method.
Simply a slightly more compact way of building an object, as opposed to 'new'.
*/
public static Id from(String aText){
return new Id(aText);
}
/**
Return this id as an {@link Integer}, if possible.
<P>See class comment.
<P>If this <tt>Id</tt> is not convertible to an {@link Integer}, then a {@link RuntimeException} is
thrown.
*/
public Integer asInteger(){
return new Integer(fId.getRawString());
}
/**
Return this id as a {@link Long}, if possible.
<P>See class comment.
<P>If this <tt>Id</tt> is not convertible to a {@link Long},
then a {@link RuntimeException} is thrown.
*/
public Long asLong(){
return new Long(fId.getRawString());
}
/**
Return the id, with special characters escaped.
<P>The return value either has content (with no leading or trailing spaces),
or is empty.
See {@link hirondelle.web4j.util.EscapeChars#forHTML(String)} for a list of escaped
characters.
*/
@Override public String toString(){
return fId.toString();
}
/** Return the text passed to the constructor. */
public String getRawString(){
return fId.getRawString();
}
/** Return the text with special XML characters esacped. See {@link SafeText#getXmlSafe()}. */
public String getXmlSafe() {
return fId.getXmlSafe();
}
@Override public boolean equals(Object aThat){
Boolean result = ModelUtil.quickEquals(this, aThat);
if ( result == null ){
Id that = (Id) aThat;
result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
}
return result;
}
@Override public int hashCode(){
return ModelUtil.hashCodeFor(getSignificantFields());
}
public int compareTo(Id aThat) {
final int EQUAL = 0;
if ( this == aThat ) return EQUAL;
int comparison = this.fId.compareTo(aThat.fId);
if ( comparison != EQUAL ) return comparison;
return EQUAL;
}
// PRIVATE
/** @serial */
private SafeText fId;
/**
For evolution of this class, see Sun guidelines :
http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/version.html#6678
*/
private static final long serialVersionUID = 7526472295633676147L;
/**
Always treat de-serialization as a full-blown constructor, by
validating the final state of the de-serialized object.
*/
private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException {
//always perform the default de-serialization first
aInputStream.defaultReadObject();
//make defensive copy of mutable fields (none here)
//ensure that object state has not been corrupted or tampered with maliciously
validateState();
}
/**
This is the default implementation of writeObject.
Customise if necessary.
*/
private void writeObject(ObjectOutputStream aOutputStream) throws IOException {
//perform the default serialization for all non-transient, non-static fields
aOutputStream.defaultWriteObject();
}
private void validateState() {
if( ! Util.textHasContent(fId) ) {
if ( ! Consts.EMPTY_STRING.equals(fId.getRawString()) ) {
throw new IllegalArgumentException(
"Id must have content, or be the empty String. Erroneous Value : " + Util.quote(fId)
);
}
}
}
private Object[] getSignificantFields(){
return new Object[] {fId};
}
}