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

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

There is a newer version: 7.6.0
Show newest version
//
// Copyright 2017, 2023 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.base.Strings;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.messages.ELocalOnly;
import net.sf.eBus.messages.EMessage;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.util.TernarySearchTree;
import net.sf.eBus.util.Validator;
import net.sf.eBus.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Multiple subject feeds act as a proxy between application
 * objects and multiple subordinate simple feeds
 * ({@link EPublishFeed}, {@link ESubscribeFeed},
 * {@link EReplyFeed}, and {@link ERequestFeed}), all for a
 * single message class. The multi-subject feed keeps subordinate
 * feeds in the same state: open, advertised/subscribed, or
 * closed. The subordinate feeds are configured with the same
 * eBus client and callback methods.
 * 

* A multi-subject feed can be created by defining either a * {@code List} subjects list or providing a * {@code Pattern} subject query. A subject list defines a * static, fixed set of subjects. This subject list will not * change except by explicitly adding new subjects (see * {@link #addFeed(String)} and {@link #addFeeds(List)}). *

*

* A subject query defines a dynamic subject set. * Initially this query is matched against existing subjects * for the given message class. When new subjects are detected * and *

    *
  • * the multi-subject feed is open, *
  • *
  • * the new feed's message class is the same as the * multi-subject feed, and *
  • *
  • * the subject matches the subject query, *
  • *
* then the new subject is added to the multi-subject feed and * moved to the same state as existing subjects. *

*

* New subjects may be added to an open multi-subject feed using * the {@link #addFeed(String)}, {@link #addAllFeeds(List)}, and * {@link #addAllFeeds(Pattern)}. These new subordinate feeds are * moved to the same state as existing subjects.Conversely, * subordinate feeds are removed and closed using the * {@link #closeFeed(String)}, {@link #closeAllFeeds(List)}, and * {@link #closeAllFeeds(Pattern)}. *

*

* Note when using a query to add new subjects, this query is * used only for that {@code addAllFeeds} method call and * not used against new subjects created after the * method returns. *

*

* See the subordinate publisher, subscriber, requester, and * replier multi-subject feeds for a detailed example using * each feed type. *

* * @param all feeds are for this message type. * @param the subordinate feed type. * * @see EMultiPublishFeed * @see EMultiSubscribeFeed * @see EMultiRequestFeed * @see EMultiReplyFeed * * @author Charles W. Rapp */ public abstract class EMultiFeed extends EFeed implements ISubjectListener { //--------------------------------------------------------------- // Enums. // /** * A multi-feed either supports subject list or a subject * pattern. */ public enum MultiFeedType { /** * Multi-feed type not yet set. */ NOT_SET, /** * Create a multi-feed for a given subject list. */ LIST, /** * Create a multi-feed for a given subject pattern. */ QUERY } // end of enum MultiFeedType //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * {@link #key()} returns a message key with the subject * {@value}. */ public static final String MULTIFEED_SUBJECT = "__MULTIFEED__"; private static final String NULL_SUBJECTS = "subjects is null"; private static final String SUBJECTS_CONTAINS_NULL = "subjects contains a null value"; private static final String SUBJECTS_CONTAINS_EMPTY_STRING = "subjects contains an empty string"; private static final String NULL_QUERY = "query is null"; //----------------------------------------------------------- // Statics. // /** * Logging subsystem interface. Shared by all subclasses. */ protected static final Logger sLogger = LoggerFactory.getLogger(EMultiFeed.class); //----------------------------------------------------------- // Locals. // /** * All subordinate feeds apply to this message class. This * message class is used when {@link #addFeed(String) adding} * new feeds to this multi-subject feed. */ protected final Class mMsgClass; /** * Query used to select subjects based on a a given pattern. * Will be {@code null} if this multi-subject feed is list * based. */ protected final @Nullable Pattern mQuery; /** * Contains the subordinate feeds currently opened by this * multi-subject feed. Maps the {@link EMessageKey#subject} * to the subordinate feed. */ protected final Map mFeeds; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new multi-publish feed instance based on the * validated builder settings. * @param builder contains validated feed configuration. */ @SuppressWarnings ("unchecked") protected EMultiFeed(final Builder builder) { super (builder); mMsgClass = builder.mMsgClass; mQuery = builder.mQuery; mFeeds = builder.mFeeds; } // end of EMultiFeed(Builder) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Declarations. // /** * Returns a newly minted subordinate feed for the given * message key. This method is used by * {@link #addFeed(String)} to create a subordinate feed. * @param key create feed for this key. * @return a subordinate feed. */ protected abstract F createFeed(final EMessageKey key); /** * Advertises/subscribes the given feed. This method is * used by {@link #addFeed(String)} to adverise/subscribe * the added feed. * @param feed feed to advertised/subscribed. */ protected abstract void putFeedInPlace(final F feed); // // end of Abstract Method Declarations. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Overrides. // /** * Closes all currently open subordinate feeds and clears * the feeds map. */ @Override protected final void inactivate() { mFeeds.values().forEach(EFeed::close); mFeeds.clear(); } // end of inactivate() // // end of Abstract Method Overrides. //----------------------------------------------------------- //----------------------------------------------------------- // ISubjectListener Interface Implementation. // /** * If the new subject's message type and subject matches * this feed's message type and subject query, then add * create a new subordinate feed and put that new feed * into the proper state. * @param subjectType subject is either for a notification * or request message. * @param subjectKey newly added subject's message key. */ @Override public final void subjectUpdate(final SubjectType subjectType, final EMessageKey subjectKey) { final String subject = subjectKey.subject(); // Is this feed still active? // Is this the correct message class? // Does the subject match the multi-subject query? // Is this a unique subject? if (mIsActive.get() && mMsgClass.equals(subjectKey.messageClass()) && mQuery.matches(subject) && !mFeeds.containsKey(subject)) { // Yes. Create the subordinate feed. doAddFeed(subject); } } // end of subjectUpdate(SubjectType, EMessageKey) // // end of ISubjectListener Interface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // Get Methods. // /** * Returns the multi-subject feed message class. All * {@link #addFeed(String) added} feeds are created with this * class. * @return feed message class. */ public final Class messageClass() { return (mMsgClass); } // end of messageClass() /** * Returns {@code true} if all subordinate feeds are up and * {@code false} otherwise. * @return {@code true} if all subordinate feeds are up. */ @Override public final boolean isFeedUp() { final Iterator fit = (mFeeds.values()).iterator(); boolean retcode = true; while (fit.hasNext() && retcode) { retcode = (fit.next()).isFeedUp(); } return (retcode); } // end of isFeedUp() /** * Returns a message key with {@link #messageClass()} as the * class and {@link #MULTIFEED_SUBJECT} as the subject. * @return multi-feed message key. */ @Override public final EMessageKey key() { return (new EMessageKey(mMsgClass, MULTIFEED_SUBJECT)); } // end of key() /** * Returns the specified feed's 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. *

* If {@code subject} references an unknown feed or the feed * is not in place, then {@link EFeedState#UNKNOWN} is * returned. *

* @param subject return the publish state for this message * subject. * @return the specified feed's state or {@code UNKNOWN} if * {@code subject} does not reference a known feed. * @throws NullPointerException * if {@code subject} is {@code null}. * @throws IllegalArgumentException * if {@code subject} is empty. * @throws IllegalStateException * if this multi-subject feed is closed. */ public final EFeedState feedState(final String subject) { if (Strings.isNullOrEmpty(subject)) { throw ( new IllegalArgumentException( "subject is a null or empty string")); } // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-subject // feeds. throw (new IllegalStateException(FEED_IS_INACTIVE)); } final F feed = mFeeds.get(subject); return (feed == null ? EFeedState.UNKNOWN : feed.feedState()); } // end of feedState(String) /** * Returns {@code true} if the given subject is part of this * multi-feed. * @param subject check if this subject is in the feed. * @return {@code true} if subject is part of multi-feed. */ public final boolean isSubject(final String subject) { // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-subject // feeds. throw (new IllegalStateException(FEED_IS_INACTIVE)); } return (mFeeds.containsKey(subject)); } // end of isSubject(String) /** * Returns the current subordinate feed message subjects. * The returned list is a copy of the actual message subjects * list. Modifying the returned list has no impact on eBus * operations. * @return subordinate feed message subjects. * @throws IllegalStateException * if this multi-subject feed is closed. */ public final List subjects() { // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-subject // feeds. throw (new IllegalStateException(FEED_IS_INACTIVE)); } final List retval = new ArrayList<>(mFeeds.size()); mFeeds.values() .forEach( feed -> retval.add((feed.key()).subject())); return (retval); } // end of subjects() /** * Returns the current subordinate feed message keys. The * returned list is a copy of the actual message key list. * Modifying the returned list has no impact on eBus * operations. * @return subordinate feed message keys. * @throws IllegalStateException * if this multi-subject feed is closed. */ public final List keys() { // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-subject // feeds. throw (new IllegalStateException(FEED_IS_INACTIVE)); } final List retval = new ArrayList<>(mFeeds.size()); mFeeds.values().forEach(feed -> retval.add(feed.key())); return (retval); } // end of keys() /** * Returns a mapping between the current subordinate feed * message keys and the subordinate feed state. The returned * map may be freely modified without impact to this * multi-feed data members. * @return state of current subordinate feeds. */ public final Map feedStates() { // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-subject // feeds. throw (new IllegalStateException(FEED_IS_INACTIVE)); } final Map retval = new HashMap<>(mFeeds.size()); mFeeds.values() .forEach( feed -> retval.put(feed.key(), feed.feedState())); return (retval); } // end of feedStates() // // end of Get Methods. //----------------------------------------------------------- /** * Adds a new feed based on the configured message class and * the given subject. If this multi-subject feed is * advertised or subscribed, then the new subordinate feed is * advertised/subscribed. This method does nothing if there * is a subordinate feed for {@code subject} in place. *

* The newly created feed references the same * {@link #mEClient client}, {@link #mMsgClass message class}, * {@link #mScope scope} and * (optional) {@link ECondition condition} as all the other * subordinate feeds in this multi-subject feed. It will also * use the same callbacks as existing subordinate feeds. *

*

* Individual subordinate feeds are removed and closed using * the {@link #closeFeed(String)} method. *

* @param subject add key with this message subject. * @throws NullPointerException * if {@code subject} is {@code null}. * @throws IllegalArgumentException * if {@code subject} is empty. * @throws IllegalStateException * if this multi-subject feed is closed. * * @see #addAllFeeds(List) * @see #addAllFeeds(Pattern) * @see #closeFeed(String) * @see #closeAllFeeds(List) * @see #closeAllFeeds(Pattern) */ public final void addFeed(final String subject) { if (Strings.isNullOrEmpty(subject)) { throw ( new IllegalArgumentException( "subject is either null or an empty string")); } // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-subject // feeds. throw (new IllegalStateException(FEED_IS_INACTIVE)); } // Is this key unique? if (!mFeeds.containsKey(subject)) { // Yes, this is a new feed. doAddFeed(subject); } } // end of addFeed(String) /** * Adds new feeds based on the configured message class and * the subjects list. If this multi-subject feed is in place * (advertised or subscribed), then the new subordinate feeds * are put in place as well. If {@code subjects} contains * feeds that already exist, the existing feed is neither * replaced or modified. *

* The newly created feeds reference the same * {@link #mEClient client}, {@link #mMsgClass message class}, * {@link #mScope scope} and * (optional) {@link ECondition condition} as all the other * subordinate feeds in this multi-subject feed. They will * also use the same callbacks as existing subordinate feeds. *

* @param subjects create new feeds for these subjects. * @throws NullPointerException * if {@code subjects} is {@code null} or contains a * {@code null} element. * @throws IllegalArgumentException * if {@code subjects} contains an empty string. * @throws IllegalStateException * if this multi-subject feed is closed. * * @see #addFeed(String) * @see #addAllFeeds(Pattern) * @see #closeFeed(String) * @see #closeAllFeeds(List) */ public final void addAllFeeds(final List subjects) { Objects.requireNonNull(subjects, NULL_SUBJECTS); // Are all the subject non-null and not empty? if (subjects.contains(null)) { throw ( new NullPointerException( SUBJECTS_CONTAINS_NULL)); } if (subjects.contains("")) { throw ( new IllegalArgumentException( SUBJECTS_CONTAINS_EMPTY_STRING)); } // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-subject // feeds. throw (new IllegalStateException(FEED_IS_INACTIVE)); } addFeeds(subjects); } // end of addAllFeeds(List) /** * Adds new feeds for each of the subjects matching the given * query. If there are no matching subjects, then no feeds * are added. If {@code query} matches an existing feed * subject, then the existing feed is neither modified not * replaced. The new feeds are for this multi-subject feed's * message class. *

* Note: this query is not * persisted nor used to match against new feed subjects * created after this method returns. This is unlike * a query defined when creating a multi-subject feed. This * initial query is persisted and matched against new * subjects. *

* @param query find all subjects matching this query. * @throws NullPointerException * if {@code query} is {@code null}. * @throws IllegalStateException * if this multi-subject feed is closed. * * @see #addFeed(String) * @see #addAllFeeds(List) * @see #closeFeed(String) * @see #closeAllFeeds(List) * @see #closeAllFeeds(Pattern) */ public final void addAllFeeds(final Pattern query) { Objects.requireNonNull(query, NULL_QUERY); // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-subject // feeds. throw (new IllegalStateException(FEED_IS_INACTIVE)); } // Find the message keys matching the given regular // expression query. final Pattern keyPattern = Pattern.compile(mMsgClass.getName() + EMessageKey.KEY_IFS + query.pattern()); final List subjects = new ArrayList<>(); // Extract the subjects from the matching message keys. ESubject.findKeys(keyPattern) .forEach(key -> subjects.add(key.subject())); addFeeds(subjects); } // end of addAllFeeds(Pattern) /** * Removes and closes the feed for the given message subject. * Does nothing if {@code subject} does not reference a * known subordinate feed. * @param subject remove and close the feed for this subject. * @throws NullPointerException * if {@code subject} is {@code null}. * @throws IllegalArgumentException * if {@code subject} is empty. * @throws IllegalStateException * if this multi-subject feed is closed. * * @see #addFeed(String) * @see #addAllFeeds(List) * @see #addAllFeeds(Pattern) * @see #closeAllFeeds(List) * @see #closeAllFeeds(Pattern) */ public final void closeFeed(final String subject) { Objects.requireNonNull(subject, "subject is null"); if (subject.isEmpty()) { throw ( new IllegalArgumentException( "subject is an empty string")); } // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-subject // feeds. throw (new IllegalStateException(FEED_IS_INACTIVE)); } // Is this a known subordinate feed? if (mFeeds.containsKey(subject)) { // Yes. Remove and close the subordinate feed. doCloseFeed(subject); } } // end of closeFeed(String) /** * Removes and closes all individual feeds for the given * subject list. Does nothing if a listed subject does not * reference a known subordinate feed. * @param subjects remove and close all lists subject feeds. * @throws NullPointerException * if {@code subjects} is {@code null} or contains a * {@code null} subject. * @throws IllegalArgumentException * if {@code subjects} contains an empty string. * @throws IllegalStateException * if this multi-subject feed is closed. * * @see #addFeed(String) * @see #addAllFeeds(List) * @see #addAllFeeds(Pattern) * @see #closeFeed(String) * @see #closeAllFeeds(Pattern) */ public final void closeAllFeeds(final List subjects) { Objects.requireNonNull(subjects, NULL_SUBJECTS); // Are all the subject non-null and not empty? if (subjects.contains(null)) { throw ( new NullPointerException( SUBJECTS_CONTAINS_NULL)); } if (subjects.contains("")) { throw ( new IllegalArgumentException( SUBJECTS_CONTAINS_EMPTY_STRING)); } // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-subject // feeds. throw (new IllegalStateException(FEED_IS_INACTIVE)); } closeFeeds(subjects); } // end of closeAllFeeds(List<>) /** * Removes and closes all individual feeds whose subject * matches the given subject query. Does nothing if there are * no such matching subordinate feed subjects. * @param query find all subordinate feeds whose subject * matches this query. * @throws NullPointerException * if {@code query} is {@code null}. * @throws IllegalStateException * if this multi-subject feed is closed. * * @see #addFeed(String) * @see #addAllFeeds(List) * @see #addAllFeeds(Pattern) * @see #closeFeed(String) * @see #closeAllFeeds(List) */ public final void closeAllFeeds(final Pattern query) { Objects.requireNonNull(query, NULL_QUERY); // Is this feed still active? if (!mIsActive.get()) { // No. Can't add new feeds to closed multi-subject // feeds. throw (new IllegalStateException(FEED_IS_INACTIVE)); } // Find the message keys matching the given regular // expression query. final Pattern keyPattern = Pattern.compile(mMsgClass.getName() + EMessageKey.KEY_IFS + query.pattern()); final List subjects = new ArrayList<>(); // Extract the subjects from the matching message keys. ESubject.findKeys(keyPattern) .forEach(key -> subjects.add(key.subject())); closeFeeds(subjects); } // end of closeAllFeeds(Pattern) /** * Creates the new feeds for each of the list subjects. * @param subjects new feed subjects. */ private void addFeeds(final List subjects) { subjects.stream() // Filter out duplicate feed subjects. .filter(s -> (!mFeeds.containsKey(s))) .forEachOrdered(this::doAddFeed); } // end of addFeeds(List<>) /** * Closes existing feeds for for of the list subjects. * @param subjects close feeds referenced by these subjects. */ private void closeFeeds(final List subjects) { subjects.stream() // Ignore unknown feeds. .filter(mFeeds::containsKey) .forEachOrdered(this::doCloseFeed); } // end of closeFeeds(List<>) /** * Performs the actual work of creating a new subordinate * feed for the given subject. Calling method has already * verified that this feed is still active and that the * subject is valid and unique. * @param subject create subordinate feed for this subject. */ private void doAddFeed(final String subject) { final F feed = createFeed(new EMessageKey(mMsgClass, subject)); mFeeds.put(subject, feed); sLogger.debug( "{} multi-subject feed {} ({}): adding feed {}.", mEClient.location(), mEClient.clientId(), mScope, subject); // Is this multikey feed subscribed? if (mInPlace) { // Yes, put this new feed in place. putFeedInPlace(feed); } } // end of doAddFeed(String) /** * Performs the actual work of removing and closing an * existing subordinate feed for the given subject. Calling * method has already verified that this feed is still active * and that the subject is valid and references a known * subordinate feed. * @param subject subordinate feed subject. */ private void doCloseFeed(final String subject) { (mFeeds.remove(subject)).close(); sLogger.debug( "{} multi-subject feed {} ({}): removed feed {}.", mEClient.location(), mEClient.clientId(), mScope, subject); } // end of doCloseFeed(String) //--------------------------------------------------------------- // Inner classes. // /** * Base class for multiple subject feeds. Defines * {@link #createFeed(EMessageKey)} which creates a * subordinate feed for the given message key. * * @param all feeds are for this message type. * @param feed type. * @param {@link EObject} sub-type. * @param target multi-feed type. * @param builder leaf type. */ protected abstract static class Builder, B extends Builder> extends EFeed.Builder { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Message class must be assignable from this class. */ protected final Class mTargetMsgClass; /** * Subordinate feeds must be for this message class. */ protected Class mMsgClass; /** * Subordinate feeds contained in this multi-feed. */ protected final Map mFeeds; /** * Defines whether this is a subject list or * pattern-based multifeed. */ private MultiFeedType mMultiFeedType; /** * Contains initial subjects list. */ private List mSubjects; /** * Message subject query used to look up existing * subjects for a given message class. */ private Pattern mQuery; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new multi-feed builder for the given target * feed class. * @param targetClass target multi-feed class. * @param targetMsgClass target eBus message class. */ protected Builder(final Class targetClass, final Class targetMsgClass) { super (targetClass); mTargetMsgClass = targetMsgClass; mMultiFeedType = MultiFeedType.NOT_SET; mFeeds = new TernarySearchTree<>(); mSubjects = Collections.emptyList(); } // end of Builder(Class, Class) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Abstract Method Declarations. // /** * Returns a subordinate feed for the given message key. *

* Note: this method is called after the * builder validation. *

* @param key subordinate feed's message key. * @return subordinate feed. */ protected abstract F createFeed(final EMessageKey key); // // end of Abstract Method Declarations. //------------------------------------------------------- //------------------------------------------------------- // Abstract Method Overrides. // /** * Checks if message class and query (if this is a * query-based multi-feed) are set. * @param problems report configuration failures to this * validator. * @return {@code problems}. */ @Override protected Validator validate(Validator problems) { final boolean localOnlyNotification = (mMsgClass != null && mMsgClass.isAnnotationPresent( ELocalOnly.class)); super.validate(problems) .requireNotNull(mMsgClass, "messageClass") .requireTrue(mMultiFeedType != MultiFeedType.NOT_SET, "subjects", "neither subjects or query set") .requireTrue(((localOnlyNotification && mScope == FeedScope.LOCAL_ONLY) || !localOnlyNotification), "messageKey, feedScope", String.format( "%s is local-only but feed scope is %s", mMsgClass, mScope)); // Were any problems found? if (problems.isEmpty()) { // No. Create initial subjects. // If this is a query-based multi-feed and the // query is set, then look up the subjects. if (mMultiFeedType == MultiFeedType.QUERY && mQuery != null) { mSubjects = findSubjects(mQuery); } // Create initial subordindate feeds. mSubjects.forEach( s -> mFeeds.put( s, createFeed( new EMessageKey(mMsgClass, s)))); } return (problems); } // end of validate(Validator) // // end of Abstract Method Overrides. //------------------------------------------------------- //------------------------------------------------------- // Set Methods. // /** * Sets feed message class. All messages sent or received * on this multi-feed must be of this class. * @param mc feed message class. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code mc} is {@code null}. * @throws IllegalArgumentException * if {@code mc} is not assignable from target message * class. */ public B messageClass(final Class mc) { mMsgClass = Objects.requireNonNull( mc, "message class is null"); return (self()); } // end of messageClass(final Class mc) /** * Sets initial multi-feed subjects. * @param subjects initial multi-feed subjects. * @return {@code this Builder} instance. * @throws IllegalStateException * if subjects list or subject query is already set. * @throws IllegalArgumentException * if {@code subjects} contains a {@code null} or empty * string value. */ public final B subjects(final List subjects) { if (mMultiFeedType != MultiFeedType.NOT_SET) { throw ( new IllegalStateException( "initial subjects already set, cannot be set again")); } Objects.requireNonNull(subjects, NULL_SUBJECTS); // Are all the subject non-null and non-empty? if (subjects.contains(null)) { throw ( new NullPointerException( SUBJECTS_CONTAINS_NULL)); } if (subjects.contains("")) { throw ( new IllegalArgumentException( SUBJECTS_CONTAINS_EMPTY_STRING)); } // Note subjects list may be empty. mSubjects = ImmutableList.copyOf(subjects); mMultiFeedType = MultiFeedType.LIST; return (self()); } // end of subjects(List<>) /** * Sets query for looking up initial multi-feed subjects. * @param query match existing subjects against this * query. * @return {@code this Builder} instance. * @throws IllegalStateException * if this is multi-feed uses an initial subject list. * @throws NullPointerException * if {@code query} is {@code null}. */ public final B query(final Pattern query) { if (mMultiFeedType != MultiFeedType.NOT_SET) { throw ( new IllegalStateException( "initial subjects already set, cannot be set again")); } mQuery = Objects.requireNonNull(query, NULL_QUERY); mMultiFeedType = MultiFeedType.QUERY; return (self()); } // end of query(Pattern) // // end of Set Methods. //------------------------------------------------------- /** * Returns immutable list containing those existing * subjects matching the given query. Returned list will * not be {@code null} but may be empty. * @param query match subjects against this query. * @return list of matching subjects. */ private List findSubjects(final Pattern query) { final Pattern keyPattern = Pattern.compile(mMsgClass.getName() + EMessageKey.KEY_IFS + query.pattern()); final ImmutableList.Builder builder = ImmutableList.builder(); // Extract the subjects from the matching message keys. ESubject.findKeys(keyPattern) .forEach(key -> builder.add(key.subject())); return (builder.build()); } // end of findSubjects(Pattern) } // end of class Builder } // end of class EMultiFeed




© 2015 - 2025 Weber Informatics LLC | Privacy Policy