net.sf.eBus.messages.EMessageKey Maven / Gradle / Ivy
The newest version!
//
// Copyright 2001 - 2013, 2016, 2017, 2019 Charles W. Rapp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package net.sf.eBus.messages;
import com.google.common.base.Strings;
import java.io.Serializable;
import java.util.Objects;
import net.sf.eBus.messages.EMessage.MessageType;
/**
* Provides an immutable key based on the message class and
* subject. This "type+topic" references a unique eBus message
* subject.
*
* @author Charles Rapp
*/
public class EMessageKey
implements Comparable,
Serializable
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* Use {@value} to separate the message class from message
* subject.
*/
public static final char KEY_IFS = '/';
/**
* The key string format is
* "<message class>/<message subject>".
*/
private static final String KEY_FORMAT = "%s/%s";
/**
* Serialization version identifier.
*/
private static final long serialVersionUID = 0x050200L;
//-----------------------------------------------------------
// Locals.
//
/**
* The message class.
*/
private final Class extends EMessage> mMessageClass;
/**
* The message subject.
*/
private final String mSubject;
/**
* Message key hash code is lazily computed the first time
* it is requested and then cached for later use. The reason
* is that hash key calculation is expensive and not always
* needed.
*/
private int mHashCode;
/**
* The combination of the message class and subject. Used a
* the key into the subject tree. This value is created on
* demand only.
*/
private String mKeyString;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new message key for the given message class and
* subject.
* @param mc message class.
* @param subject message subject
* @throws NullPointerException
* if either {@code mc} or {@code subject} is {@code null}.
* @throws IllegalArgumentException
* if {@code subject} is an empty string.
*/
public EMessageKey(final Class extends EMessage> mc,
final String subject)
{
if (Strings.isNullOrEmpty(subject))
{
throw (
new IllegalArgumentException(
"subject is null or empty"));
}
mMessageClass = Objects.requireNonNull(mc, "mc is null");
mSubject = subject;
mKeyString = null;
mHashCode = 0;
} // end of EMessageKey(Class, String)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Comparable Interface Implemenation.
//
/**
* Returns an integer value that is <, equal to or >
* zero if {@code this EMessageKey} is <, equal to or
* > {@code key}. Comparison is based on the message class
* and then the message subject.
* @param key comparison object.
* @return an integer value that is <, equal to or >
* zero.
*/
@Override
public int compareTo(final EMessageKey key)
{
int retval =
(mMessageClass.getName()).compareTo(
(key.mMessageClass).getName());
if (retval == 0)
{
retval =
mSubject.compareTo(key.mSubject);
}
return (retval);
} // end of compareTo(EMessageKey)
//
// end of Comparable Interface Implemenation.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns {@code true} if {@code obj} is a
* non-{@code null EMessageKey} instance with equivalent
* message class and subject; returns {@code false}
* otherwise.
* @param obj comparison object.
* @return {@code true} if {@code obj} is equivalent to
* {@code this}.
*/
@Override
public boolean equals(final Object obj)
{
boolean retcode = (this == obj);
if (!retcode && obj instanceof EMessageKey)
{
EMessageKey key = (EMessageKey) obj;
retcode =
(mMessageClass.equals(key.mMessageClass) &&
mSubject.equals(key.mSubject));
}
return (retcode);
} // end of equals(Object)
/**
* Returns a hash code based on the message identifier and
* subject.
* @return the message key hash.
*/
@Override
public int hashCode()
{
// Was the hash code previously calculated?
if (mHashCode == 0)
{
// No. Calculate it now.
mHashCode =
((mMessageClass.hashCode() * 37) +
mSubject.hashCode());
}
return (mHashCode);
} // end of hashCode()
/**
* Returns a textual representation of this message key.
* @return a textual representation.
*/
@Override
public String toString()
{
return (mMessageClass.getName() +
KEY_IFS +
mSubject);
} // end of toString()
//
// end of Object Method Overrides.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Get Methods.
//
/**
* Returns the message class name.
* @return the message class name.
*/
public String className()
{
return (mMessageClass.getName());
} // end of className()
/**
* Returns the message class.
* @return the message class.
*/
public Class extends EMessage> messageClass()
{
return (mMessageClass);
} // end of messageClass()
/**
* Returns the subject.
* @return the subject.
*/
public String subject()
{
return (mSubject);
} // end of subject()
/**
* Returns the key in string format:
* "message class name/subject".
* Creates the key string if it has not yet been set.
* @return the key in string format.
*/
public String keyString()
{
if (mKeyString == null)
{
mKeyString =
String.format(KEY_FORMAT,
mMessageClass.getName(),
mSubject);
}
return (mKeyString);
} // end of keyString()
/**
* Returns {@code true} if the message class is a
* {@link ESystemMessage} subclass; otherwise, returns
* {@code false}.
* @return {@code true} if the message key references a
* system message.
*/
public boolean isSystem()
{
return (
ESystemMessage.class.isAssignableFrom(
mMessageClass));
} // end of isSystem()
/**
* Returns {@code true} if the message class is an
* {@link ENotificationMessage} subclass; otherwise, returns
* {@code false}.
* @return {@code true} if the message key references a
* notification message.
*/
public boolean isNotification()
{
return (
ENotificationMessage.class.isAssignableFrom(
mMessageClass));
} // end of isNotification()
/**
* Returns {@code true} if the message class is an
* {@link ERequestMessage} subclass; otherwise, returns
* {@code false}.
* @return {@code true} if the message key references a
* request message.
*/
public boolean isRequest()
{
return (
ERequestMessage.class.isAssignableFrom(
mMessageClass));
} // end of isRequest()
/**
* Returns {@code true} if the message class is an
* {@link EReplyMessage} subclass; otherwise, returns
* {@code false}.
* @return {@code true} if the message key references a
* reply message.
*/
public boolean isReply()
{
return (
EReplyMessage.class.isAssignableFrom(
mMessageClass));
} // end of isReply()
/**
* Returns {@code true} if the message class is local-only
* and {@code false} if the message class may be transmitted.
* @return {@code true} if the message class is local-only.
*/
public boolean isLocalOnly()
{
return (
mMessageClass.isAnnotationPresent(ELocalOnly.class));
} // end of isLocalOnly()
/**
* Returns the key message type.
* @return message type.
*/
public MessageType messageType()
{
final MessageType retval;
if (ENotificationMessage.class.isAssignableFrom(
mMessageClass))
{
retval = MessageType.NOTIFICATION;
}
else if (ERequestMessage.class.isAssignableFrom(
mMessageClass))
{
retval = MessageType.REQUEST;
}
else if (EReplyMessage.class.isAssignableFrom(
mMessageClass))
{
retval = MessageType.REPLY;
}
else
{
retval = MessageType.SYSTEM;
}
return (retval);
} // end of messageType()
//
// end of Get Methods.
//-----------------------------------------------------------
/**
* Returns {@code true} if {@code key}'s message class is
* assignable from {@code this} key's message class and the
* subjects are equal; otherwise returns {@code false}.
* @return if {@code key} is assignable from {@code this}
* message key.
* @param key message key compared to {@code this} message
* key.
*/
public boolean isAssignableFrom(final EMessageKey key)
{
return (mMessageClass.isAssignableFrom(key.messageClass()) &&
mSubject.equals(key.mSubject));
} // end of isAssignableFrom(EMessageKey)
/**
* Returns a message key parsed from the given input. The
* input must be of the form "message class name:subject". If
* the input is {@code null} or an empty string, then
* {@code null} is returned.
* @param s the message key in text form.
* @return the parsed message key instance.
* @throws IllegalArgumentException
* if {@code s} is not properly formatted or contains an
* unknown class name.
* @throws InvalidMessageException
* if {@code s} is properly formatted but contains an unknown
* message class name.
*/
@SuppressWarnings ("unchecked")
public static EMessageKey parseKey(final String s)
throws IllegalArgumentException,
InvalidMessageException
{
EMessageKey retval = null;
// If the input is null or empty, then return null.
if (s != null && !s.isEmpty())
{
final String[] tokens = s.split(":");
// The input must consist of two tokens: message
// class name and subject.
if (tokens.length != 2)
{
throw (
new IllegalArgumentException(
String.format(
"invalid message key \"%s\"", s)));
}
else
{
try
{
final Class extends EMessage> clazz =
(Class extends EMessage>)
Class.forName(tokens[0]);
if (!EMessage.class.isAssignableFrom(clazz))
{
throw (
new InvalidMessageException(
EMessage.class,
String.format(
"%s is not a EMessage subclass",
tokens[0])));
}
retval = new EMessageKey(clazz, tokens[1]);
}
catch (ClassNotFoundException classex)
{
throw (
new IllegalArgumentException(
String.format(
"%s is an unknown class",
tokens[0]),
classex));
}
}
}
return (retval);
} // end of parseKey(String)
} // end of class EMessageKey