001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2016-2026 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.core;
020
021import org.jgrapes.core.annotation.Handler;
022
023/**
024 * Instances of this interface can be used as a communication 
025 * bus for sending events between components. The instances work
026 * as identifiers of channels. Their only functionality is defined
027 * by the {@link Eligible} interface, which allows a channel
028 * (used as attribute of an {@link Event}) to be matched against 
029 * a criterion specified in a {@link Handler}.
030 * 
031 * The need to use the {@link Eligible} interface for comparison
032 * arises from the fact that we cannot use objects as values in
033 * annotations. It must therefore be possible to match channels
034 * (objects) against criteria that can be expressed as constant 
035 * values.
036 * 
037 * Some values have been defined to represent special criteria.
038 * 
039 * * If the value `Channel.class` is specified as criterion in
040 *   a handler, all channel instances match. It is the "catch-all"
041 *   criterion.
042 * 
043 * * If the value `{@link Default}.class` is specified as criterion
044 *   in a handler, the channels from an {@link Event} are
045 *   matched agains the criterion from the component's channel
046 *   (returned by the {@link Manager#channel() channel()} method).  
047 * 
048 * The predefined {@link #BROADCAST} channel is a channel instance
049 * that implements the {@link Eligible} interface in such a way that
050 * all criteria match. Events fired on the {@link #BROADCAST} channel
051 * will therefore be accepted by all handlers (as its name suggests).
052 * 
053 * For ordinary usage, the implementing classes {@link ClassChannel}
054 * and {@link NamedChannel} should be sufficient. If another type of
055 * `Channel` is needed, its implementation must make sure that 
056 * {@link Eligible#isEligibleFor(Object)} returns
057 * `true` if called with `Channel.class` as parameter, else channels 
058 * of the new type will not be delivered to "catch-all" handlers.
059 * 
060 * Objects of type <code>Channel</code> must be immutable.
061 * 
062 * @see Channel#BROADCAST
063 */
064@SuppressWarnings("PMD.ImplicitFunctionalInterface")
065public interface Channel extends Eligible {
066
067    /**
068     * A special channel object that can be passed as argument to 
069     * the constructor of {@link Component#Component(Channel)}. 
070     * Doing this sets the component's channel to the component 
071     * (which is not available as argument when calling the 
072     * constructor).
073     * 
074     * @see Component#Component(Channel)
075     */
076    Channel SELF = new ClassChannel();
077
078    /**
079     * This interface's class can be used to specify the component's 
080     * channel (see {@link Component#channel()}) as criterion in 
081     * handler annotations.
082     * 
083     * Using the component's channel for comparison is the default 
084     * if no channels are specified in the annotation, so specifying 
085     * only this class in the handler annotation is equivalent
086     * to specifying no channel at all. This special channel type is required
087     * if you want to specify a handler that handles events fired on the 
088     * component's channel or on additional channels.
089     */
090    @SuppressWarnings("PMD.ImplicitFunctionalInterface")
091    interface Default extends Channel {
092    }
093
094    /**
095     * By default, a channel is eligible for a broadcast and for
096     * its own default criterion (see {@link #defaultCriterion()}.
097     *
098     * @param criterion the criterion
099     * @return true, if is eligible for
100     */
101    @Override
102    default boolean isEligibleFor(Object criterion) {
103        return criterion.equals(BROADCAST.defaultCriterion())
104            || criterion.equals(defaultCriterion());
105    }
106
107    /**
108     * A special channel instance that can be used to send events to
109     * all components.
110     */
111    Channel BROADCAST = new ClassChannel() {
112
113        /**
114         * Returns <code>Channel.class</code>, the value that must
115         * by definition be matched by any channel.
116         * 
117         * @return <code>Channel.class</code>
118         */
119        @Override
120        public Object defaultCriterion() {
121            return Channel.class;
122        }
123
124        /**
125         * Always returns {@code true} because the broadcast channel
126         * is matched by every channel.
127         * 
128         * @return {@code true}
129         */
130        @Override
131        public boolean isEligibleFor(Object criterion) {
132            return true;
133        }
134
135        /*
136         * (non-Javadoc)
137         * 
138         * @see org.jgrapes.core.ClassChannel#toString()
139         */
140        @Override
141        public String toString() {
142            return "BROADCAST";
143        }
144    };
145
146    /**
147     * Returns a textual representation of a channel's criterion.
148     * 
149     * @param criterion the criterion
150     * @return the representation
151     */
152    static String criterionToString(Object criterion) {
153        StringBuilder builder = new StringBuilder();
154        if (criterion instanceof Class) {
155            if (criterion == Channel.class) {
156                builder.append("BROADCAST");
157            } else {
158                builder.append(Components.className((Class<?>) criterion));
159            }
160        } else {
161            builder.append(criterion);
162        }
163        return builder.toString();
164    }
165
166    /**
167     * Returns a textual representation of a channel.
168     * 
169     * @param channel the channel
170     * @return the representation
171     */
172    @SuppressWarnings("PMD.CompareObjectsWithEquals")
173    static String toString(Channel channel) {
174        if (channel == null) {
175            return "null";
176        }
177        StringBuilder builder = new StringBuilder();
178        if (channel instanceof ClassChannel
179            || channel instanceof NamedChannel) {
180            builder.append(criterionToString(channel.defaultCriterion()));
181        } else if (channel == channel.defaultCriterion()) {
182            builder.append(Components.objectName(channel));
183        } else {
184            builder.append(channel.toString());
185        }
186        return builder.toString();
187    }
188
189    /**
190     * Returns a textual representation of an array of channels.
191     * 
192     * @param channels the channels
193     * @return the representation
194     */
195    @SuppressWarnings({ "PMD.UseVarargs" })
196    static String toString(Channel[] channels) {
197        StringBuilder builder = new StringBuilder();
198        builder.append('[');
199        boolean first = true;
200        for (Channel c : channels) {
201            if (!first) {
202                builder.append(", ");
203            }
204            builder.append(Channel.toString(c));
205            first = false;
206        }
207        builder.append(']');
208        return builder.toString();
209    }
210
211}