
net.sf.eBus.messages.EMessageObject Maven / Gradle / Ivy
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later
// version.
//
// This library is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with this library; if not, write to the
//
// Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330,
// Boston, MA
// 02111-1307 USA
//
// The Initial Developer of the Original Code is Charles W. Rapp.
// Portions created by Charles W. Rapp are
// Copyright 2013, 2016. Charles W. Rapp
// All Rights Reserved.
//
package net.sf.eBus.messages;
import java.io.Serializable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.sf.eBus.messages.type.DataType;
/**
* This abstract class defines the two eBus message classes:
* {@link net.sf.eBus.messages.EMessage} and
* {@link net.sf.eBus.messages.EField}. Message instances are
* used to publish notifications, requests, and replies. Field
* instances are used to implement user-defined fields within
* both messages and other fields.
*
* All {@code EMessageObject} sub-classes must have a
* {@link EFieldInfo} class attribute which specifies the
* {@code public final} class fields. This attribute is used to
* define field order for serialization and construction. For
* example, the user-defined field {@code UserInfo}:
*
*
* @EFieldInfo(fields = {"name", "age"})
* public class UserInfo
* extends EField
* {
* public UserInfo(String name, int age) {
* this.name = name;
* this.age = age;
* }
*
* public final String name;
* public final int age;
* }
*
*
* The {@code UserInfo} constructor arguments must match the
* {@code @EFieldInfo.fields} order. Also,
* {@code @EFieldInfo.fields} must appear as {@code public final}
* class data members. If either requirement fails, then the
* field will be declared invalid.
*
* If another user-defined field is created which extends
* {@code UserInfo} as follows:
*
*
* @EFieldInfo(fields = {"department"})
* public class Employee
* extends UserInfo
* {
* public Employee(String name, int age, String dept) {
* super (name, age);
* this.department = dept;
* }
*
* public final String department;
* }
*
*
* The {@code Employee} constructor arguments must first be the
* super class fields and then the sub-class fields.
*
* The field-ordering rules also apply to {@link EMessage}
* classes. Because {@code EMessage} has two fields:
* {@code public final String subject} and
* {@code public final long timestamp}, all user-defined messages
* must include these fields as the first two constructor
* arguments. {@link EReplyMessage} has two more fields:
* {@code public final EReplyMessage.ReplyStatus replyStatus} and
* {@code public final String replyReason}. User-defined reply
* message constructor must have as its first four arguments:
* subject, timestamp, replyStatus, and replyReason.
*
* {@code EMessageObject} constructor validates the above rules
* the first time a concrete sub-class is constructed. If the
* validation fails, then a {@link InvalidMessageException} is
* thrown. This failure is cached so that the validation is done
* only once.
*
* @author Charles Rapp
*/
public abstract class EMessageObject
implements Serializable
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* A message object may have at most 31 fields. This is
* because there are 31 usable bits in a 4-byte, signed
* integer.
*/
public static final int MAX_FIELDS = 31;
/**
* Serialization version identifier.
*/
private static final long serialVersionUID = 0x040400L;
//-----------------------------------------------------------
// Statics.
//
/**
* Maps the {@code EMessageObject} class to its validation
* flag: {@code null} means the class has not been validated,
* {@code true} means the class is valid, and {@code false}
* means invalid.
*/
private static final ConcurrentMap, ValidationInfo> mValidMap =
new ConcurrentHashMap<>();
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new message object instance. The point behind
* this constructor is to validate a user-defined message
* or field as soon as it is instantiated.
* @throws InvalidMessageException
* if the user-defined message or field is invalid.
*/
protected EMessageObject()
throws InvalidMessageException
{
final Class extends EMessageObject> mc =
this.getClass();
ValidationInfo info = mValidMap.get(mc);
// Has this message been validated before?
if (info == null)
{
// No, validate it now and store away the results.
info = validate(mc);
mValidMap.put(mc, info);
}
if (info.isValid() == false)
{
throw (
new InvalidMessageException(
mc, info.reason(), info.error()));
}
} // end of EMessageObject()
//
// end of Constructors.
//-----------------------------------------------------------
/**
* Returns the validation information for the specified
* {@code EMessageObject} sub-class.
* @param mc validate this message class.
* @return the validation result.
*/
private static ValidationInfo
validate(final Class extends EMessageObject> mc)
{
boolean flag = true;
String reason = null;
Throwable t = null;
try
{
// Called for effect only. This will create a
// MessageType for this class. If mc is not a valid
// message, then an InvalidMessageException is
// thrown with the appropriate reason.
DataType.findType(mc);
}
catch (InvalidMessageException msgex)
{
flag = false;
reason = msgex.getMessage();
t = msgex;
}
return (new ValidationInfo(flag, reason, t));
} // end of validate(Class)
//---------------------------------------------------------------
// Inner classes.
//
/**
* This immutable class tracks the EMessageObject validity.
*/
private static final class ValidationInfo
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* {@code true} if the message is valid and
* {@code false} otherwise.
*/
private final boolean mFlag;
/**
* Text explaining why {@code _flag} is {@code false}.
* Set to {@code null} when {@code _flag} is
* {@code true}.
*/
private final String mReason;
/**
* Thrown error which caused the exception.
*/
private final Throwable mError;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Sets the message object validation to the given flag
* and reason.
* @param flag {@code true} if the message object is
* valid and {@code false} otherwise.
* @param reason text explaining the invalidity.
* @param t error behind a {@code false flag}.
*/
private ValidationInfo(final boolean flag,
final String reason,
final Throwable t)
{
mFlag = flag;
mReason = reason;
mError = t;
} // end of ValidationInfo(boolean, String, Throwable)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns {@code true} if the message object is valid
* and {@code false} otherwise.
* @return {@code true} if the message object is valid.
*/
private boolean isValid()
{
return (mFlag);
} // end of isValid()
/**
* Returns text explaining why the message is invalid.
* May return {@code null}.
* @return text explaining why the message is invalid.
*/
private String reason()
{
return (mReason);
} // end of reason()
/**
* Returns the error behind an invalid message.
* @return {@code Throwable} error.
*/
private Throwable error()
{
return (mError);
} // end of error()
//
// end of Get Methods.
//-------------------------------------------------------
} // end of class ValidationInfo
} // end of class EMessageObject