All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 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 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 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 clazz =
                        (Class)
                            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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy