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

net.sf.eBus.client.EFeed Maven / Gradle / Ivy

The newest version!
//
// Copyright 2015, 2016, 2018, 2020 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.client;

import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import net.sf.eBus.client.EClient.ClientLocation;
import net.sf.eBus.config.EConfigure;
import net.sf.eBus.messages.EMessage;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ENotificationMessage;
import net.sf.eBus.messages.EReplyMessage;
import net.sf.eBus.messages.ERequestMessage;
import net.sf.eBus.messages.ESystemMessage;
import net.sf.eBus.util.ValidationException;
import net.sf.eBus.util.Validator;
import net.sf.eBus.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Applications interface with eBus through feed instances. An
 * application class implements the eBus interface associated
 * with the feed (for example, {@link EPublisher} for
 * {@link EPublishFeed}), opens a feed instance, and then
 * activates the feed. The feed can be activated, deactivated
 * multiple times. Once the feed is {@link EFeed#close() closed},
 * the feed instance cannot be used again. A new feed instance
 * must be opened again.
 * 

* An application defines a feed's {@link FeedScope scope} when * opening the feed. This scope defines the feed's visibility. * That feed may be visible only within the local JVM, within * both local and remote JVMs, and in remote JVMs only. For * example, if a {@link ESubscribeFeed subscription feed} has * local-only scope, then it will receive notifications from * local {@link EPublishFeed publisher feeds} only. Notifications * from remote publishers will not be forwarded to the local-only * subscriber. The following table shows the interface between * feed scopes: *

* * * * * * * * * * * * * * * * * * * * * * * * * * *
Feed Scope
Local OnlyLocal & RemoteRemote Only
Local OnlyMatchMatchNo match
Local & RemoteMatchMatchMatch
Remote OnlyNo matchMatchNo match
*

* (Notice that a remote feed may only support a local & * remote feed and not other remote only feeds.) *

*

* Feed scope gives the application developer control over how * "far" a message will go. If a notification message is intended * to stay within a JVM, both the publish and subscribe feeds may * be set to a local only scope. If a notification is meant for * remote access only, the the publish feed is set to remote only * scope. These examples also apply to request/reply feeds. *

*

* eBus calls an application instance back from only one thread * at any given time. While that thread may change over time, * only one eBus thread will callback the application instance * at any given time. The eBus API is thread-safe and an * application may call eBus methods from multiple, simultaneous * threads. The benefit of this model is that if an application * instance is accessed only by eBus only and not by any other * non-eBus thread, then the application instance is effectively * single-threaded. Such an application instance does not need to * use {@code synchronized} keyword or locks, simplifying the * application class implementation. *

*

* A feed maintains a {@link EClient weak reference} to the * application instance. If the application is finalized while * still owning open feeds, those feeds will be automatically * closed when eBus detects this finalization. *

*

* Each feed is assigned a identifier that is unique within the * application instance scope and feed lifespan. Once a feed is * closed, its identifier is recycled. Put in another way: an * application instance's active feeds all have unique integer * identifiers. These identifiers are not unique across * different application instances. These identifiers are * not unique across an application instance lifespan if * that instance opens and closes feeds multiple times. It is * likely that newly opened feed will have the same identifier as * a previously closed feed. *

*

* The eBus API is intended to be extended with new feed * subclasses. These extensions would provide more sophisticated * notification and request/reply types. One example is a * notification feed that combines historical and live updates or * just historical, depending on what the subscriber requests. * This allows a subscriber to join the feed at any time, not * missing any previously posted notifications. *

*

Message Key Dictionary

*

* eBus v. 4.5.0 added the ability to directly add messages keys * to the eBus message key dictionary and retrieve keys from said * dictionary. Prior to this version, message keys were * indirectly added to the dictionary when opening feeds. This * feature added to support the new multikey feeds * {@link EMultiPublishFeed}, {@link EMultiSubscribeFeed}, * and {@link EMultiReplyFeed}. Multi-subject feeds may use a * {@link net.sf.eBus.util.regex.Pattern query} to match a * variable number of keys. This is why * {@link #addKey(EMessageKey)}, * {@link #addAllKeys(java.util.Collection)} and * {@link #loadKeys(ObjectInputStream)} methods are provided: * unless the message key dictionary is populated with keys prior * to creating a multi-subject query feed, the query would find * no matching keys. *

*

* Multi-subject feeds act as proxies between the application * client and the individual {@code EPublishFeed}, * {@code ESubscribeFeed}, {@code ERequestFeed} and * {@code EReplyFeed} in the multi-subject feed. The * multi-subject feed opens, advertises/subscribes, and closes * all subordinate feeds in unison. The individual feeds all * reference the same client and client callback methods. If a * multi-subject feed is for 100 subjects, then the client * receives callbacks from all 100 subordinate feed and * not for the single multi-subject feed. *

*

* Note: a multi-subject feed is not * {@code EFeed} subclass. However, multi-subject feed behavior * is the same as an {@code EFeed} and may be treated by the * application as if it were an {@code EFeed}. *

* * @see ESubject * @see ENotifyFeed * @see EPublishFeed * @see ESubscribeFeed * @see ERequestFeed * @see EReplyFeed * @see EMultiPublishFeed * @see EMultiSubscribeFeed * @see EMultiRequestFeed * @see EMultiReplyFeed * * @author Charles W. Rapp */ public abstract class EFeed implements IEFeed { //--------------------------------------------------------------- // Enums. // /** * Feed scope is either restricted to this JVM, to both this * JVM and remote JVMs, and to remote JVMs only. The feed * scope is used to determine if two opposing feeds * ({@link EPublishFeed publish} and * {@link ESubscribeFeed subscibe} or * {@link EReplyFeed reply} and {@link ERequestFeed request}) * match and so may exchange messages. */ public enum FeedScope { /** * This feed is matched only by other feeds within this * JVM. */ LOCAL_ONLY (ClientLocation.LOCAL.mask, new ClientLocation[] {ClientLocation.LOCAL}, "local only"), /** * This feed is matched by both local and remote feeds. */ LOCAL_AND_REMOTE ((ClientLocation.LOCAL.mask | ClientLocation.REMOTE.mask), new ClientLocation[] { ClientLocation.LOCAL, ClientLocation.REMOTE }, "local & remote"), /** * This feed is matched only by feeds on remote JVMs. */ REMOTE_ONLY (ClientLocation.REMOTE.mask, new ClientLocation[] {ClientLocation.REMOTE}, "remote only"); //----------------------------------------------------------- // Member data. // /** * Bit mask marking which locations are supported by * this scope. */ private final int mLocationMask; /** * This feed covers these client locations. */ private final List mLocations; /** * Human-readable text describing this zone. */ private final String mDescription; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a feed scope with the given description. * @param locationMask bit mask specifying which * locations are supported by this scope. This is where * the feed client resides. * @param locations the client locations associated with * this feed scope. This is from where messages may be * posted. * @param text text describing this feed scope. */ private FeedScope(final int locationMask, final ClientLocation[] locations, final String text) { mLocationMask = locationMask; mLocations = ImmutableList.copyOf(locations); mDescription = text; } // end of FeedScope(int, ClientLocation[], String) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // /** * Returns {@code true} if this scope supports the given * client location; {@code false} otherwise. * @param loc check this client location. * @return {@code true} if client location is supported. */ public boolean supports(final ClientLocation loc) { return ((mLocationMask & loc.mask) != 0); } // end of supports(ClientLocation) /** * Returns {@code true} if this scope intersects with the * given scope; {@code false} otherwise. * @param scope check for intersection with this scope. * @return {@code true} if {@code scope} is supported. */ public boolean intersects(final FeedScope scope) { return ((mLocationMask & scope.mLocationMask) != 0); } // end of intersects(FeedScope) /** * Returns the client locations covered by this feed * scope. The returned list is immutable. * @return covered client locations. */ /* package */ List locations() { return (mLocations); } // end of locations() // // end of Get Methods. //------------------------------------------------------- //------------------------------------------------------- // Object Method Overrides. // @Override public String toString() { return (mDescription); } // end of toString() // // end of Object Method Overrides. //------------------------------------------------------- } // end of enum FeedScope //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * The default condition always returns true. This default * is used so that a notification subscription and request * advertisement always have a non-{@code null} condition, * removing the need for a {@code if} condition in message * routing code, which improves JIT performance. */ public static final ECondition NO_CONDITION = msg -> true; /** * Message associated with attempting to use an inactive * message feed. */ public static final String FEED_IS_INACTIVE = "feed is inactive"; /** * Message associated with attempting to use an unadvertised * message feed. */ public static final String FEED_NOT_ADVERTISED = "feed not advertised"; /** * Message associated with using a {@code null EClient} . */ public static final String NULL_CLIENT = "client is null"; /** * {@link ESubscriber#notify(ENotificationMessage, IESubscribeFeed)} * method name. */ protected static final String NOTIFY_METHOD = "notify"; /** * Regular expression pattern matching all message key * subjects. A subject must contain at least one character. */ private static final Pattern ALL_SUBJECTS = Pattern.compile(".+"); //----------------------------------------------------------- // Statics. // /** * Logging subsystem interface. */ private static final Logger sLogger = LoggerFactory.getLogger(EFeed.class); //----------------------------------------------------------- // Locals. // /** * The client owning this feed. {@link EClient} maintains a * weak reference to the application instance. */ protected final EClient mEClient; /** * The feed scope is either limited to this local JVM only, * remote JVMs only, and both local and remote JVMs. */ protected final FeedScope mScope; /** * Returns {@code true} if this feed is active, meaning that * it can still be used by the client. Returns {@code false} * if this feed is inactive and cannot be used by the client * again. Once a feed is made inactive, it cannot become * active again. */ protected final AtomicBoolean mIsActive; /** * Set to {@code true} when this feed is connected to its * subject. Initialized to {@code false}. */ protected boolean mInPlace; /** * Immutable identifier unique within the client. In other * words, two feeds for two different {@code EClient} * instances may have the same {@code mFeedId}. */ protected final int mFeedId; /** * Tracks whether this feed is {@link EFeedState#UP active} * or {@link EFeedState#DOWN inactive}. Feed state is * independent of being in place. Whether a feed is in place * or not, this feed may or may not be active. */ protected EFeedState mFeedState; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates an eBus feed for the given client subject, scope, * and feed type. All callback tasks are posted to * {@code client}'s * {@link EClient#dispatch(java.lang.Runnable) run queue}. * @param client post eBus tasks to this client. * @param feedScope this feed supports either local, local * & remote, or just remote feeds. */ protected EFeed(final EClient client, final FeedScope feedScope) { mEClient = client; mScope = feedScope; mFeedId = client.nextFeedId(); mIsActive = new AtomicBoolean(true); mInPlace = false; // Feed state is unknown because it has no initial state. mFeedState = EFeedState.UNKNOWN; sLogger.debug("Client {}, Feed {}: opening.", client.clientId(), mFeedId); } // end of EFeed(...) /** * Creates an eBus feed based on the given builder * configuration. * @param builder contains feed configuration. */ protected EFeed(final Builder builder) { mEClient = builder.mEClient; mScope = builder.mScope; mFeedId = mEClient.nextFeedId(); mIsActive = new AtomicBoolean(true); mInPlace = false; // Feed state is unknown because it has no initial state. mFeedState = EFeedState.UNKNOWN; sLogger.debug("Client {}, Feed {}: opening.", mEClient.clientId(), mFeedId); } // end of EFeed(Builder) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Declarations. // /** * Closes the feed. Performs all necessary work to clean * up this feed. */ protected abstract void inactivate(); // // end of Abstract Method Declarations. //----------------------------------------------------------- //----------------------------------------------------------- // IEFeed Interface Implementation. // /** * Returns the unique feed identifier. The uniqueness is * limited to within the client and for the feed lifespan * only. When a feed is closed, the feed identifier may be * reused. * @return the feed identifier. */ @Override public final int feedId() { return (mFeedId); } // end of feedId() /** * Returns the feed scope: local only, local & remote, or * remote only. * @return feed scope. */ @Override public final FeedScope scope() { return (mScope); } // end of scope() /** * Returns the eBus client referenced by this feed. * @return eBus client. */ @Override public final EClient eClient() { return (mEClient); } // end of eClient() /** * Returns {@code true} if this feed is still active and * {@code false} if inactive. Clients may only use active * feeds. Once a feed is closed, it is marked inactive and * may not be used by the client again. *

* Once a feed is closed, the unique feed identifier may be * reused by a newly opened feed. *

* @return {@code true} if this feed is active. * @see #close() */ @Override public final boolean isActive() { return (mIsActive.get()); } // end of isActive() /** * Returns {@code true} if this feed is "in place" * (subscribed or advertised) and {@code false} otherwise. * @return {@code true} if the feed is in place. */ @Override public final boolean inPlace() { return (mInPlace); } // end of inPlace() /** * Returns {@code true} if the feed state is * {@link EFeedState#UP}; otherwise, returns {@code false}. * @return {@code true} if the feed state is up. */ @Override public boolean isFeedUp() { return (mFeedState == EFeedState.UP); } // end of isFeedUp() /** * Returns the current feed state. A down state means * that messages may not be sent on or received from this * feed. An up state means that messages may possibly be * sent on or received from this feed. * @return current feed state. */ @Override public final EFeedState feedState() { return (mFeedState); } // end of feedState() /** * Closes this feed, marking it as inactive. If this feed * is activated, then the feed is de-activated first. The * feed unique identifier is returned to the available feed * identifier pool and may be assigned to a newly opened * feed. *

* If this feed is already inactive, then does nothing. *

*/ @Override public void close() { // Is this feed still active? if (mIsActive.getAndSet(false)) { // Yes. Make it inactive. sLogger.debug("Client {}, Feed {}: closing.", mEClient.clientId(), mFeedId); inactivate(); // Remove this feed from its client. mEClient.returnFeedId(mFeedId); mEClient.removeFeed(this); } } // end of close() // // end of IEFeed Interface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // Object Method Overrides. // /** * Returns {@code true} if {@code o} is a * non-{@code null EFeed} instance with equal client and * feed identifiers. Otherwise returns {@code false}. * @param o comparison object. * @return {@code true} if {@code o} is a * non-{@code null EFeed} instance with equal client and * feed identifiers. */ @Override public boolean equals(final Object o) { boolean retcode = (this == o); if (!retcode && o instanceof EFeed) { final EFeed feed = (EFeed) o; retcode = (mEClient.clientId() == (feed.mEClient).clientId() && mFeedId == feed.mFeedId); } return (retcode); } // end of equals(Object) /** * Returns the hash of the client and feed identifiers. * @return hash based on the client and feed identifiers. */ @Override public int hashCode() { return (Objects.hash(mEClient.clientId(), mFeedId)); } // end of hashCode() /** * Returns a containing the feed message key and data member * values. * @return textual representation of this feed. */ @Override public String toString() { return ( String.format( "[%s active=%b, in place=%b, id=%d, state=%s]", mScope, mIsActive.get(), mInPlace, mFeedId, mFeedState)); } // end of toString() // // end of Object Method Overrides. //----------------------------------------------------------- //----------------------------------------------------------- // Get Methods. // /** * Returns the feed client identifier. This identifier is * guaranteed unique for the application instance's lifespan. * The identifier is re-used once eBus detects that the * application instance is finalized. This integer identifier * may be used by two different application instances over * the application run time but only if the two instances * do not exist at the same time. * @return unique client identifier. */ public final int clientId() { return (mEClient.clientId()); } // end of clientId() /** * Returns the feed client JVM location: * {@link EClient.ClientLocation#LOCAL local} to this JVM or * in a {@link EClient.ClientLocation#REMOTE remote} JVM. * @return client location. */ public final ClientLocation location() { return (mEClient.location()); } // end of location() /** * Returns the eBus default dispatcher's name. Used when * {@link #register(EObject, String) registering} a client * to the default dispatcher. * @return default dispatcher name. */ public static String defaultDispatcher() { return (EClient.defaultDispatcher()); } // end of defaultDispatcher() /** * Returns a non-{@code null}, possibly empty, message key * list taken from the current message key dictionary * entries. * @return list message key dictionary entries. */ public static List findKeys() { return (ESubject.findKeys()); } // end of findKeys() /** * Returns a list containing message keys for the given * message class found in the message key dictionary. The * message class should reference either a notification or * request message class since only those classes are stored * in the message key dictionary. If this is not the case, * then an empty list is returned. * @param mc message class. * @return a non-{@code null} and possibly empty message key * list. */ public static List findKeys(final Class mc) { return (findKeys(mc, ALL_SUBJECTS)); } // end of findKeys(Class) /** * Returns a list containing message keys for the given * message class and subject pattern found in the message key * dictionary. The message class should reference either a * notification or request message class since only those * classes are stored in the message key dictionary. If this * is not the case, then an empty list is returned. * @param mc message class. * @param query message subject regular expression query. * @return a non-{@code null} and possibly empty message key * list. */ @SuppressWarnings ("unchecked") public static List findKeys(final Class mc, final Pattern query) { final List retval; Objects.requireNonNull(mc, "mc is null"); Objects.requireNonNull(query, "query is null"); if (ESystemMessage.class.isAssignableFrom(mc) || EReplyMessage.class.isAssignableFrom(mc)) { retval = ImmutableList.of(); } else { // Create the regular expression pattern to find // the message keys. final Pattern keyPattern = Pattern.compile(mc.getName() + EMessageKey.KEY_IFS + query.pattern()); retval = ESubject.findKeys(keyPattern); } return (retval); } // end of findKeys(Class, Pattern) /** * Adds the given subject listener to the listeners list. * Does nothing if the listener is already registered. * @param l add this subject listener. */ public static void addListener(final ISubjectListener l) { ESubject.addListener(l); } // end of addListener(ISubjectListener) /** * Removes subject listener from the listeners list. * @param l remove this subject listener. */ public static void removeListener(final ISubjectListener l) { ESubject.removeListener(l); } // end of removeListener(ISubjectListener) // // end of Get Methods. //----------------------------------------------------------- //----------------------------------------------------------- // Set Methods. // /** * Adds {@code key} to the eBus message key dictionary if * {@code key} is not already defined. {@code key} must * reference either a notification or request message since * only those message keys are stored in the message key * dictionary. * @param key add this message key to the subject * @throws IllegalArgumentException * if {@code key} is {@code null} or does not reference * either a {@link net.sf.eBus.messages.ENotificationMessage} * or {@link net.sf.eBus.messages.ERequestMessage}. */ public static void addKey(final EMessageKey key) { Objects.requireNonNull(key, "key is null"); if (key.isSystem() || key.isReply()) { throw ( new IllegalArgumentException( String.format( "%s is not a notification or request", key.className()))); } ESubject.addSubject(key); } // end of addKey(EMessageKey) /** * Adds the given message key collection to the message key * dictionary if all keys are not null and reference * notification and/or request messages. Put conversely, if * {@code keys} contains a {@code null} or non-notification/ * request key, then none of the keys is placed into the * message key dictionary. *

* The listed keys must reference either a notification or * request message since only those message keys are stored * in the message key dictionary. The list may consist of a * mixture of notification and request message keys. *

* @param keys put these notification and/or request message * keys into the message key dictionary. * @throws IllegalArgumentException * if {@code keys} contains a {@code null} or does not * reference either a * {@link net.sf.eBus.messages.ENotificationMessage} * or {@link net.sf.eBus.messages.ERequestMessage}. */ public static void addAllKeys(final Collection keys) { Objects.requireNonNull(keys, "keys is null"); // Firstly, validate the keys *before* attempting to add // them to the dictionary. keys.forEach( key -> { Objects.requireNonNull(key, "contains null key"); if (key.isSystem() || key.isReply()) { throw ( new IllegalArgumentException( String.format( "%s is not a notification or request", key.className()))); } }); ESubject.addAllSubjects(keys); } // end of addAllKeys(Collection<>) /** * Sets the message exhaust to the given instance. All * notification, request, and reply messages passing through * the local eBus are passed to {@code exhaust} via a * dispatch thread. Caller is responsible for opening and * closing the exhaust's underlying persistent store * appropriately. *

* Note: only one exhaust may be * configured at a time. When this method is called, the * current exhaust is replace with {@code exhaust}. If an * application requires messages to be persisted to multiple * destinations, then that must be done via the single * application exhaust. *

*

* If {@code exhaust} is {@code null} then the default * exhaust is used. This default does nothing with the given * message. *

* @param exhaust message exhaust message. Passing in * {@code null} results in the default exhaust being used. */ public static void setExhaust(final IMessageExhaust exhaust) { ESubject.setExhaust(exhaust); } // end of setExhaust(IMessageExhaust) // // end of Set Methods. //----------------------------------------------------------- /** * Creates a new client dispatcher run queue based on the * given configuration. This method allows new dispatcher * run queues to be created dynamically post-JVM start. * Normally dispatcher configurations are contained in an * eBus configuration file and loaded at JVM start using the * Java command line option * -Dnet.sf.eBus.config.jsonFile=<config file path>. * This technique guarantees that eBus Dispatchers are in * place prior to assigned eBus clients to a Dispatcher. * But if this is not possible, then {@code createDispatcher} * may be used to create a Dispatcher dynamically. But care * must be taken to make sure this is done prior to * {@link #register(EObject, String) registering} an eBus * client to that Dispatcher. *

* See {@link net.sf.eBus.config.EConfigure.Dispatcher} and * {@link net.sf.eBus.config.EConfigure.DispatcherBuilder} * for detailed information on how dispatchers work and how * to build a dispatcher configuration. *

* @param dispatcher dispatcher configuration. * @throws NullPointerException * if {@code dispatcher} is {@code null}. * @throws IllegalStateException * if a dispatcher named * {@link net.sf.eBus.config.EConfigure.Dispatcher#name()} * already exists. * * @see net.sf.eBus.config.EConfigure.Dispatcher * @see net.sf.eBus.config.EConfigure.DispatcherBuilder * @see net.sf.eBus.config.EConfigure#dispatcherBuilder() */ public static void createDispatcher(final EConfigure.Dispatcher dispatcher) { Objects.requireNonNull(dispatcher, "dispatcher is null"); EClient.createDispatcher(dispatcher); } // end of createDispatcher(Dispatcher) /** * Registers the application object {@code client} with * eBus, assigning the client to the dispatcher configured * for the client's class, and using the defined * {@link EObject#startup()} and {@link EObject#shutdown()} * methods. Once registered, {@code client} may be * started using the {@link #startup(EObject)} or * {@link #startup(List)} methods. *

* Note: this method must be called * before {@code client} opens any feeds. Failure to * do so results in a thrown {@link IllegalStateException}. *

* @param client register this application object with * eBus. * @throws NullPointerException * if {@code client} is {@code null}. * @throws IllegalStateException * if {@code client} is already registered with eBus. This * will happen if {@code client} has opened any feeds prior * to making this call. * * @see #register(EObject, String) * @see #register(EObject, String, Runnable, Runnable) * @see #startup(EObject) * @see #shutdown(EObject) */ public static void register(final EObject client) { Objects.requireNonNull(client, NULL_CLIENT); // Use the dispatcher for this client's class and the // EObject startup and shutdown methods. final EClient.DispatcherInfo dispatcher = EClient.findDispatcher(client); register(client, dispatcher.name(), client::startup, client::shutdown); } // end of register(EObject) /** * Registers the application object {@code client} with * eBus, assigning {@code client} to the named dispatcher. * This method allows individual application objects to be * assigned to a dispatcher rather than by class. The purpose * here is to allow objects within a class to be assigned to * different dispatchers based on application need. That is, * certain objects may be assigned to a higher priority * dispatcher and the rest assigned to a lower priority * dispatcher. *

* Once registered, {@code client} may be started by calling * {@link #startup(EObject)} or * {@link #startup(List)} methods which, in turn, * calls the defined {@link EObject#startup()} method. *

*

* Note: this method must be called * before {@code client} opens any feeds. Failure to * do so results in a thrown {@link IllegalStateException}. *

* @param client register this application client with eBus. * @param dispatcherName the dispatcher name. * @throws NullPointerException * if {@code client} is {@code null} or * {@code dispatcherName} is {@code null} * @throws IllegalArgumentException * if {@code dispatcherName} is empty or does not reference a * configured dispatcher. * @throws IllegalStateException * if {@code client} is already registered with eBus. This * will happen if {@code client} has opened any feeds prior * to making this call. */ public static void register(final EObject client, final String dispatcherName) { register(client, dispatcherName, client::startup, client::shutdown); } // end of register(EObject, String) /** * Registers the application object {@code client} with * eBus, assigning {@code client} to the named dispatcher. * This method allows individual application objects to be * assigned to a dispatcher rather than by class. The purpose * here is to allow objects within a class to be assigned to * different dispatchers based on application need. That is, * certain objects may be assigned to a higher priority * dispatcher and the rest assigned to a lower priority * dispatcher. *

* Once registered, {@code client} may be started by calling * {@link #startup(EObject)} or * {@link #startup(List)} methods which, in turn, * calls {@code startCb}. *

*

* Note: this method must be called * before {@code client} opens any feeds. Failure to * do so results in a thrown {@link IllegalStateException}. *

* @param client register this application client with eBus. * @param dispatcherName the dispatcher name. * @param startCb {@code client} start-up method callback. * @param shutdownCb {@code client} shutdown method callback. * @throws NullPointerException * if any of the arguments is {@code null}. * @throws IllegalArgumentException * if {@code dispatcherName} is either an empty string or * does not reference a known dispatcher. * @throws IllegalStateException * if {@code client} is already registered with eBus. This * will happen if {@code client} has opened any feeds prior * to making this call. */ public static void register(final EObject client, final String dispatcherName, final Runnable startCb, final Runnable shutdownCb) { Objects.requireNonNull(client, NULL_CLIENT); Objects.requireNonNull(dispatcherName, "dispatcherName is null"); Objects.requireNonNull(startCb, "startCb is null"); Objects.requireNonNull(shutdownCb, "shutdownCb is null"); if (dispatcherName.isEmpty()) { throw ( new IllegalArgumentException( "dispatcherName is empty")); } final EClient.DispatcherInfo info = EClient.findDispatcher(dispatcherName); if (info == null) { throw ( new IllegalArgumentException( dispatcherName + " is an unknown dispatcher")); } EClient.addClient(client, ClientLocation.LOCAL, info, startCb, shutdownCb); } // end of register(EObject, String) /** * Calls the start-up method * {@link #register(EObject, String, Runnable, Runnable) registered} * with eBus if-and-only-if the application object is not * currently started. If application object is started, then * the start-up method will not be called again. *

* Note: it is possible to start an application object * multiple times if that object is shut down between each * start. *

*

* The start-up method is called from within the context of * the object's run queue. Because the application object is * started on an eBus thread, the start-up method will not be * run concurrently with any other eBus callback. *

* @param client start this eBus client. * @throws NullPointerException * if {@code client} is {@code null}. * @throws IllegalStateException * if {@code client} is not registered with eBus. * * @see #register(EObject) * @see #register(EObject, String) * @see #register(EObject, String, Runnable, Runnable) * @see #startup(List) * @see #startupAll() * @see #shutdown(EObject) * @see #shutdown(List) * @see #shutdownAll() */ public static void startup(final EObject client) { final EClient eClient; Objects.requireNonNull(client, NULL_CLIENT); if ((eClient = EClient.findClient(client)) == null) { throw ( new IllegalStateException( "client not registered with eBus")); } final ImmutableList.Builder clients = ImmutableList.builder(); sLogger.debug( "EFeed: starting up client {}.", client.name()); clients.add(eClient); EClient.startup(clients.build()); } // end of startup(EObject) /** * Call the registered start-up method for each of the * application objects in {@code clients} if-and-only-if * the application object is not currently started. If any * of the applications is currently started, then that * objects start-up method will not be called again. *

* Note: it is possible to start an application object * multiple times if that object is shut down between each * start. *

*

* The start-up method is called from within the context of * the object's run queue. Because the application object is * started on an eBus thread, the start-up method will not be * run concurrently with any other eBus callback. *

* @param clients start these eBus clients. * @throws NullPointerException * if {@code clients} is {@code null} or contains a * {@code null} entry. * @throws IllegalStateException * if {@code clients} contains an entry that is not * registered with eBus. * * @see #register(EObject) * @see #register(EObject, String) * @see #register(EObject, String, Runnable, Runnable) * @see #startup(EObject) * @see #startupAll() * @see #shutdown(EObject) * @see #shutdown(List) * @see #shutdownAll() */ public static void startup(final List clients) { Objects.requireNonNull(clients, "clients is null"); final ImmutableList.Builder nameBuilder = ImmutableList.builder(); final ImmutableList.Builder eClients = ImmutableList.builder(); EClient eClient; // Validate clients. for (EObject client : clients) { Objects.requireNonNull( client, "clients contains a null entry"); eClient = EClient.findClient(client); if (eClient == null) { throw ( new IllegalStateException( "clients contains an unregistered object")); } else { nameBuilder.add(" " + client.name()); eClients.add(eClient); } } sLogger.debug( "EFeed: starting up {}.", nameBuilder.build()); // The clients set checks out. Start the application // objects. EClient.startup(eClients.build()); } // end of startup(List<>) /** * Calls the start-up method for all currently registered * application objects * which are not currently started. This method * is generally called from an application's * {@code static main} method after having created and * registered the application's eBus objects. *

* Note: it is possible to start an application object * multiple times if that object is shut down between each * start. *

*

* The start-up method is called from within the context of * the object's run queue. Because the application object is * started on an eBus thread, the start-up method will not be * run concurrently with any other eBus callback. *

* * @see #register(EObject) * @see #register(EObject, String) * @see #register(EObject, String, Runnable, Runnable) * @see #startup(EObject) * @see #startup(List) * @see #shutdown(EObject) * @see #shutdown(List) * @see #shutdownAll() */ public static void startupAll() { sLogger.debug( "EFeed: starting up all registered clients."); EClient.startup(EClient.getClients()); } // end of startupAll() /** * Calls the shutdown method * {@link #register(EObject, String, Runnable, Runnable) registered} * with eBus if-and-only-if the application object is * currently started. If application object is not started, * then the shutdown method will not be called again. *

* Note: it is possible to shut down an application object * multiple times if that object is started up between each * shutdown. *

*

* The shutdown method is called from within the context of * the object's run queue. Because the application object is * stopped on an eBus thread, the shutdown method will not be * run concurrently with any other eBus callback. *

* @param client stop this eBus client. * @throws NullPointerException * if {@code client} is {@code null}. * @throws IllegalStateException * if {@code client} is not registered with eBus. * * @see #register(EObject) * @see #register(EObject, String) * @see #register(EObject, String, Runnable, Runnable) * @see #startup(EObject) * @see #startup(List) * @see #startupAll() * @see #shutdown(List) * @see #shutdownAll() */ public static void shutdown(final EObject client) { Objects.requireNonNull(client, NULL_CLIENT); final EClient eClient = EClient.findClient(client); if (eClient == null) { throw ( new IllegalStateException( "client not registered with eBus")); } final List clients = new ArrayList<>(); clients.add(eClient); EClient.shutdown(clients); } // end of shutdown(EObject) /** * Call the registered shutdown method for each of the * application objects in {@code clients} if-and-only-if * the application object is currently started. If any * of the applications is not currently started, then that * objects shutdown method will not be called again. *

* Note: it is possible to shut down an application object * multiple times if that object is started up between each * shutdown. *

*

* The shutdown method is called from within the context of * the object's run queue. Because the application object is * stopped on an eBus thread, the shutdown method will not be * run concurrently with any other eBus callback. *

* @param clients stop these eBus clients. * @throws NullPointerException * if {@code clients} is {@code null} or contains a * {@code null} entry. * @throws IllegalStateException * if {@code clients} contains an entry that is not * registered with eBus. * * @see #register(EObject) * @see #register(EObject, String) * @see #register(EObject, String, Runnable, Runnable) * @see #startup(EObject) * @see #startup(List) * @see #startupAll() * @see #shutdown(EObject) * @see #shutdownAll() */ public static void shutdown(final List clients) { Objects.requireNonNull(clients, "clients is null"); final List eClients = new ArrayList<>(); EClient eClient; // Validate clients. for (EObject client : clients) { Objects.requireNonNull( client, "clients contains a null entry"); eClient = EClient.findClient(client); if (eClient == null) { throw ( new IllegalStateException( "clients contains an unregistered object")); } else { eClients.add(eClient); } } // The clients set checks out. Stop the application // objects. EClient.shutdown(eClients); } // end of shutdown(List<>) /** * Calls the shutdown method for all currently registered * application objects * which are currently started. This method * is generally called by an application just prior to * shutting down. *

* Note: it is possible to stop an application object * multiple times if that object is started between each * shut down. *

*

* The shutdown method is called from within the context of * the object's run queue. Because the application object is * stopped on an eBus thread, the start-up method will not be * run concurrently with any other eBus callback. *

* * @see #register(EObject) * @see #register(EObject, String) * @see #register(EObject, String, Runnable, Runnable) * @see #startup(EObject) * @see #startup(List) * @see #startupAll() * @see #shutdown(EObject) * @see #shutdown(List) */ public static void shutdownAll() { EClient.shutdown(EClient.getClients()); } // end of shutdownAll() /** * Writes the entire message key dictionary to the given * object output stream. Only message keys are written to the * object output stream. The associated eBus subjects and * their related feeds are not stored. When the message key * is re-loaded from the object stream at application start, * the eBus subjects are recreated but not the feeds. Feeds * must be re-opened by the application upon start. *

* Caller is responsible for opening {@code oos} prior to * calling this method and closing {@code oos} after this * method returns. *

* @param oos load message keys to this object output stream. * @throws IOException * if an error occurs writing message keys to {@code oos}. * * @see #storeKeys(Class, ObjectOutputStream) * @see #storeKeys(Class, Pattern, ObjectOutputStream) * @see #loadKeys(ObjectInputStream) */ public static void storeKeys(final ObjectOutputStream oos) throws IOException { Objects.requireNonNull(oos, "oos is null"); ESubject.storeKeys(oos); } // end of storeKeys() /** * Write those message keys associated with the given message * class to the object output stream. Only message keys are * written to the object output stream. The associated eBus * subjects and their related feeds are not stored. When the * message key is re-loaded from the object stream at * application start, the eBus subjects are recreated but not * the feeds. Feeds must be re-opened by the application upon * start. *

* Caller is responsible for opening {@code oos} prior to * calling this method and closing {@code oos} after this * method returns. *

* @param mc store message keys with this message class. * @param oos load message keys to this object output stream. * @throws IOException * if an error occurs writing message keys to {@code oos}. * * @see #storeKeys(ObjectOutputStream) * @see #storeKeys(Class, Pattern, ObjectOutputStream) * @see #loadKeys(ObjectInputStream) */ public static void storeKeys(final Class mc, final ObjectOutputStream oos) throws IOException { storeKeys(mc, ALL_SUBJECTS, oos); } // end of storeKeys(Class) /** * Write those message keys associated with the given message * class and a subject matching the regular expression to the * object output stream. Only message keys are written to the * object output stream. The associated eBus subjects and * their related feeds are not stored. When the message key * is re-loaded from the object stream at application start, * the eBus subjects are recreated but not the feeds. Feeds * must be re-opened by the application upon start. *

* Caller is responsible for opening {@code oos} prior to * calling this method and closing {@code oos} after this * method returns. *

* @param mc store message keys with this message class. * @param query store message keys with a subject matching * this regular expression. * @param oos load message keys to this object output stream. * @throws IOException * if an I/O error occurs when storing the message keys. * * @see #storeKeys(ObjectOutputStream) * @see #storeKeys(Class, ObjectOutputStream) * @see #loadKeys(ObjectInputStream) */ public static void storeKeys(final Class mc, final Pattern query, final ObjectOutputStream oos) throws IOException { Objects.requireNonNull(mc, "mc is null"); Objects.requireNonNull(query, "query is null"); Objects.requireNonNull(oos, "oos is null"); if (ENotificationMessage.class.isAssignableFrom(mc) || ERequestMessage.class.isAssignableFrom(mc)) { // Create the regular expression pattern to find // the message keys. final Pattern keyPattern = Pattern.compile(mc.getName() + EMessageKey.KEY_IFS + query.pattern()); ESubject.storeKeys(keyPattern, oos); } } // end of storeKeys(Class, Pattern) /** * Reads the {@link EMessageKey message keys} contained in * the given object input stream and loads them back into the * eBus message key dictionary. eBus subjects are re-created * but not their associated feeds. The application is * responsible for re-opening feeds when the application * starts. *

* Message keys defined prior to calling this method are * not overwritten or replaced by duplicates loaded from * the object input stream. *

*

* Caller is responsible for opening {@code ois} prior to * calling this method and closing {@code ois} after this * method returns. *

* @param ois read in message keys from this object input * stream. * @throws IOException * if an I/O error occurs reading in the */ public static void loadKeys(final ObjectInputStream ois) throws IOException { Objects.requireNonNull(ois, "ois is null"); ESubject.loadKeys(ois); } // end of loadKeys(ObjectInputStream) /** * Returns {@code true} if the application object stored in * {@code EClient} defines a method with the given name and * parameters. Returns {@code false} if the application * object does not define the method or inherits a default * implementation of the method. * @param methodName method name. * @param params method parameters. * @return {@code true} if {@code clazz} overrides the * method. */ protected final boolean isOverridden(final String methodName, final Class... params) { boolean retcode = false; try { final Method method = (mEClient.targetClass()).getMethod( methodName, params); retcode = !method.isDefault(); } catch (NoSuchMethodException | SecurityException jex) { // Ignore and return false. } return (retcode); } // end of isOverridden(Class, String, Class...) /** * Checks if the message scope and feed scope are in * agreement. That is, if the message scope is local-only * but the feed scope is not, then throws an * {@code IllegalArgumentException}. * @param key message key. * @param scope feed scope. * @throws IllegalArgumentException * if {@code key} scope is local-only and {@code scope} is * not {@code FeedScope.LOCAL_ONLY}. */ protected static void checkScopes(final EMessageKey key, final FeedScope scope) { if (key.isLocalOnly() && scope != FeedScope.LOCAL_ONLY) { throw ( new IllegalArgumentException( String.format( "%s is local-only but feed scope is %s", key, scope))); } } // end of checkScopes(EMessageKey, FeedScope) /** * Returns the distance between the given subclass and base * class. If a direct subclass of base class, then the * returned value is one. If not a subclass of base class, * then the returned value is < zero. * @param subclass check if this class is descended from * the base class. * @param bc base class. * @return distance from subclass to base class or < zero * if {@code subclass} is not descended from {@code bc}. */ protected static int subclassDistance(final Class subclass, final Class bc) { Class sc = subclass; int distance = 0; int retval = -1; while ((sc = sc.getSuperclass()) != null && retval < 0) { ++distance; if (bc.equals(sc)) { retval = distance; } } return (retval); } // end of subclassDistance(Class, Class) //--------------------------------------------------------------- // Inner classes. // /** * Base class for eBus client callback tasks created by * feeds. Contains the eBus feed object. */ protected abstract static class AbstractClientTask implements Runnable { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * The callback is from this message feed instance. */ protected final IEFeed mFeed; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a client callback task for this eBus client * and message feed. * @param feed the callback is from this message feed * instance. */ protected AbstractClientTask(final IEFeed feed) { mFeed = feed; } // end of AbstractClientTask(IEFeed) // // end of Constructors. //------------------------------------------------------- } // end of class AbstractClientTask /** * Used to issue a feed status callback. * @param feed status applies to this feed type. */ protected final class StatusTask extends AbstractClientTask { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * The publisher new feed status. */ private final EFeedState mFeedState; /** * Pass the feed state to this method. */ private final FeedStatusCallback mCallback; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new feed status task for the given callback * parameters. * @param feedState {@code true} if the feed is up and * {@code false} if down. * @param feed the feed state applies to this feed. * @param cb post feed state update to this callback. */ public StatusTask(final EFeedState feedState, final EFeed feed, final FeedStatusCallback cb) { super (feed); mFeedState = feedState; mCallback = cb; } // end of StatusTask(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Runnable Interface Implementation. // /** * Issues the feed status callback, logging any * client-thrown exception. */ @Override @SuppressWarnings ("unchecked") public void run() { final Object target = mEClient.target(); sLogger.trace(this.toString()); if (target != null) { try { mCallback.call(mFeedState, (T) mFeed); } catch (Throwable tex) { final String reason = "{} publish status callback exception"; final String className = (target.getClass()).getName(); if (sLogger.isDebugEnabled()) { sLogger.warn(reason, className, tex); } else { sLogger.warn(reason, className); } } } } // end of run() // // end of Runnable Interface Implementation. //------------------------------------------------------- } // end of StatusTask /** * This task forwards a notification message to * {@link ESubscriber#notify(ENotificationMessage, IESubscribeFeed)}. */ protected class NotifyTask extends AbstractClientTask { //----------------------------------------------------------- // Member data. // /** * Forward this message to the subscriber if the message * satisfies the subscription condition. */ private final ENotificationMessage mMessage; /** * Apply {@link #mMessage} to this condition and deliver * message if-and-only-if the message satisfies the * condition. */ private final ECondition mCondition; /** * Forward notification message to this callback. */ private final NotifyCallback mCallback; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a notify callback task for the given * parameters. * @param message the notification message. * @param condition subscription condition. * @param feed the message is from this notification * feed. * @param cb notification message callback. */ public NotifyTask(final ENotificationMessage message, final ECondition condition, final IESubscribeFeed feed, final NotifyCallback cb) { super (feed); mMessage = message; mCondition = condition; mCallback = cb; } // end of NotifyTask(...) /** * Creates a notify callback task for the given * condition, feed, and message callback. * @param condition subscription condition. * @param feed message is from this feed. * @param cb notification message callback. */ protected NotifyTask(final ECondition condition, final IESubscribeFeed feed, final NotifyCallback cb) { super (feed); mMessage = null; mCondition = condition; mCallback = cb; } // end of NotifyTask(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Runnable Interface Implementations. // /** * Issues the * {@link ESubscriber#notify(ENotificationMessage, IESubscribeFeed)} * callback if the message satisfies the * subscription condition. Logs any client-thrown * exception. */ @Override @SuppressWarnings ("unchecked") public void run() { final Object target = mEClient.target(); final ENotificationMessage message = message(); if (target != null && message != null) { sLogger.trace( "NotifyTask[key={}]", message.key()); try { // Is the subscriber interested in this // message? if (mCondition.test(message)) { // Yes. Forward the notification message // to the subscriber. mCallback.call( message, (IESubscribeFeed) mFeed); } } catch (Throwable tex) { final String reason = "NotifyTask[{}, {}] exception"; final String className = (target.getClass()).getName(); final EMessageKey key = message.key(); if (sLogger.isDebugEnabled()) { sLogger.warn( reason, className, key, tex); } else { sLogger.warn(reason, className, key); } } } } // end of run() // // end of Runnable Interface Implementations. //------------------------------------------------------- /** * Returns message to be forwarded to target. * @return forwarded message. */ protected ENotificationMessage message() { return (mMessage); } // end of message() } // end of class NotifyTask /** * Base class for all {@link EFeed} builders. Contains the * configuration fields common to all feed types. * * @param feed type. * @param {@link EObject} sub-type. * @param builder leaf type. */ protected abstract static class Builder> { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Instance feed class returned by {@link #build}. This * value is used to identify the target class when a * build failure occurs, placed in the * {@link ValidationException}. */ protected final Class mTargetClass; /** * Matching feed messages are forwarded to this eBus * client. Required for all feeds. */ protected EObject mTarget; /** * Target instance location. Defaults to * {@link ClientLocation#LOCAL}. */ protected ClientLocation mLocation; /** * Publish or subscribe to messages within this feed * scope. Required for all feeds. */ protected FeedScope mScope; /** * eBus client containing the target object. */ protected EClient mEClient; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new feed builder for the given target class. * @param targetClass target class receiving feed * messages. */ protected Builder(final Class targetClass) { mTargetClass = targetClass; mLocation = ClientLocation.LOCAL; } // end of Builder(Class) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Abstract Method Declarations. // /** * Returns an eBus feed instance build from the * configured properties. Note: the * builder configuration was * {@link #validate(Validator) validated} * prior to calling this method. The message object may * now be constructed. * @return target feed instance. */ protected abstract F buildImpl(); /** * Returns {@code Builder this} pointer as the leaf * builder class instance. * @return leaf {@code Builder} instance. */ protected abstract B self(); // // end of Abstract Method Declarations. //------------------------------------------------------- //------------------------------------------------------- // Set Methods. // /** * Sets eBus object which is the target for inbound * messages. Returns {@code this Builder} so that * configuration method calls may be chained. * @param target eBus target object. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code target} is {@code null}. */ public B target(final T target) { mTarget = Objects.requireNonNull(target, "target is null"); return (self()); } // end of target(T) /** * Sets eBus target object location to given value. This * is a package-level method since only * {@link ERemoteApp} is allowed to access it. * @param location eBus target object location. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code location} is {@code null}. */ /* package */ final B location(final ClientLocation location) { mLocation = Objects.requireNonNull( location, "location is null"); return (self()); } // end of location(ClientLocation) /** * Sets feed scope to either local only, remote only, or * local and remote. * @param scope feed receives only those messages within * this scope. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code scope} is {@code null}. */ public B scope(final FeedScope scope) { mScope = Objects.requireNonNull(scope, "scope is null"); return (self()); } // end of scope(FeedScope) // // end of Set Methods. //------------------------------------------------------- /** * Returns target feed instance constructed from the * configured properties. This must be the final * method in a builder configuration call chain. * @return target eBus feed instance. * @throws ValidationException * if {@code this} builder does not contain a valid * target feed configuration. This exception contains a * list of all configuration problems found. */ public final F build() throws ValidationException { final Validator problems = new Validator(); final F retval; // Check if this builder contains valid settings. validate(problems); // Were any problems found? if (!problems.isEmpty()) { // Yes. throw ( new ValidationException( mTargetClass, problems.errors())); } // Find or create the eBus client for the configured // target *before* building the feed because builder // sub-classes depend on this. mEClient = EClient.findOrCreateClient(mTarget, mLocation); retval = buildImpl(); mEClient.addFeed(retval); // No problems found. Return the new feed instance. return (retval); } // end of build() /** * Checks if target eBus object and feed scope are * configured. Also checks if message key is *

* This method should be overridden by subclass feed * builders and called before doing its own * validation. The first line in the subclass * {@code validate} implementation should be * {@code super.validate(problems);}. *

* @param problems place invalid configuration settings * in this problems list. * @return {@code problems} to allow for method chaining. */ protected Validator validate(final Validator problems) { return (problems.requireNotNull(mTarget, "target") .requireNotNull(mScope, "scope")); } // end of validate(Validator) /** * Returns {@code true} if the target eBus object defines * a method with the given name and parameters. Returns * {@code false} if the target object does not define the * method or inherits a default implementation of the * method. * @param methodName method name. * @param params method parameters. * @return {@code true} if {@code clazz} overrides the * method. */ protected final boolean isOverridden(final String methodName, final Class... params) { boolean retcode = false; try { final Method method = (mTarget.getClass()).getMethod( methodName, params); retcode = !method.isDefault(); } catch (NoSuchMethodException | SecurityException jex) { // Ignore and return false. } return (retcode); } // end of isOverridden(String, Class...) } // end of class BuilderPPPP } // end of class EFeed




© 2015 - 2024 Weber Informatics LLC | Privacy Policy