001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2016-2018 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 */
064public interface Channel extends Eligible {
065
066    /**
067     * A special channel object that can be passed as argument to 
068     * the constructor of {@link Component#Component(Channel)}. 
069     * Doing this sets the component's channel to the component 
070     * (which is not available as argument when calling the 
071     * constructor).
072     * 
073     * @see Component#Component(Channel)
074     */
075    Channel SELF = new ClassChannel();
076
077    /**
078     * This interface's class can be used to specify the component's 
079     * channel (see {@link Component#channel()}) as criterion in 
080     * handler annotations.
081     * 
082     * Using the component's channel for comparison is the default 
083     * if no channels are specified in the annotation, so specifying 
084     * only this class in the handler annotation is equivalent
085     * to specifying no channel at all. This special channel type is required
086     * if you want to specify a handler that handles events fired on the 
087     * component's channel or on additional channels.
088     */
089    interface Default extends Channel {
090    }
091
092    /**
093     * A special channel instance that can be used to send events to
094     * all components.
095     */
096    Channel BROADCAST = new ClassChannel() {
097
098        /**
099         * Returns <code>Channel.class</code>, the value that must
100         * by definition be matched by any channel.
101         * 
102         * @return <code>Channel.class</code>
103         */
104        @Override
105        public Object defaultCriterion() {
106            return Channel.class;
107        }
108
109        /**
110         * Always returns {@code true} because the broadcast channel
111         * is matched by every channel.
112         * 
113         * @return {@code true}
114         */
115        @Override
116        public boolean isEligibleFor(Object criterion) {
117            return true;
118        }
119
120        /*
121         * (non-Javadoc)
122         * 
123         * @see org.jgrapes.core.ClassChannel#toString()
124         */
125        @Override
126        public String toString() {
127            return "BROADCAST";
128        }
129    };
130
131    /**
132     * Returns a textual representation of a channel's criterion.
133     * 
134     * @param criterion the criterion
135     * @return the representation
136     */
137    static String criterionToString(Object criterion) {
138        StringBuilder builder = new StringBuilder();
139        if (criterion instanceof Class) {
140            if (criterion == Channel.class) {
141                builder.append("BROADCAST");
142            } else {
143                builder.append(Components.className((Class<?>) criterion));
144            }
145        } else {
146            builder.append(criterion);
147        }
148        return builder.toString();
149    }
150
151    /**
152     * Returns a textual representation of a channel.
153     * 
154     * @param channel the channel
155     * @return the representation
156     */
157    static String toString(Channel channel) {
158        if (channel == null) {
159            return "null";
160        }
161        StringBuilder builder = new StringBuilder();
162        if (channel instanceof ClassChannel
163            || channel instanceof NamedChannel) {
164            builder.append(criterionToString(channel.defaultCriterion()));
165        } else if (channel == channel.defaultCriterion()) {
166            builder.append(Components.objectName(channel));
167        } else {
168            builder.append(channel.toString());
169        }
170        return builder.toString();
171    }
172
173    /**
174     * Returns a textual representation of an array of channels.
175     * 
176     * @param channels the channels
177     * @return the representation
178     */
179    @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.UseVarargs" })
180    static String toString(Channel[] channels) {
181        StringBuilder builder = new StringBuilder();
182        builder.append('[');
183        boolean first = true;
184        for (Channel c : channels) {
185            if (!first) {
186                builder.append(", ");
187            }
188            builder.append(Channel.toString(c));
189            first = false;
190        }
191        builder.append(']');
192        return builder.toString();
193    }
194
195}