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 * By default, a channel is eligible for a broadcast and for 094 * its own default criterion (see {@link #defaultCriterion()}. 095 * 096 * @param criterion the criterion 097 * @return true, if is eligible for 098 */ 099 @Override 100 default boolean isEligibleFor(Object criterion) { 101 return criterion.equals(BROADCAST.defaultCriterion()) 102 || criterion.equals(defaultCriterion()); 103 } 104 105 /** 106 * A special channel instance that can be used to send events to 107 * all components. 108 */ 109 Channel BROADCAST = new ClassChannel() { 110 111 /** 112 * Returns <code>Channel.class</code>, the value that must 113 * by definition be matched by any channel. 114 * 115 * @return <code>Channel.class</code> 116 */ 117 @Override 118 public Object defaultCriterion() { 119 return Channel.class; 120 } 121 122 /** 123 * Always returns {@code true} because the broadcast channel 124 * is matched by every channel. 125 * 126 * @return {@code true} 127 */ 128 @Override 129 public boolean isEligibleFor(Object criterion) { 130 return true; 131 } 132 133 /* 134 * (non-Javadoc) 135 * 136 * @see org.jgrapes.core.ClassChannel#toString() 137 */ 138 @Override 139 public String toString() { 140 return "BROADCAST"; 141 } 142 }; 143 144 /** 145 * Returns a textual representation of a channel's criterion. 146 * 147 * @param criterion the criterion 148 * @return the representation 149 */ 150 static String criterionToString(Object criterion) { 151 StringBuilder builder = new StringBuilder(); 152 if (criterion instanceof Class) { 153 if (criterion == Channel.class) { 154 builder.append("BROADCAST"); 155 } else { 156 builder.append(Components.className((Class<?>) criterion)); 157 } 158 } else { 159 builder.append(criterion); 160 } 161 return builder.toString(); 162 } 163 164 /** 165 * Returns a textual representation of a channel. 166 * 167 * @param channel the channel 168 * @return the representation 169 */ 170 static String toString(Channel channel) { 171 if (channel == null) { 172 return "null"; 173 } 174 StringBuilder builder = new StringBuilder(); 175 if (channel instanceof ClassChannel 176 || channel instanceof NamedChannel) { 177 builder.append(criterionToString(channel.defaultCriterion())); 178 } else if (channel == channel.defaultCriterion()) { 179 builder.append(Components.objectName(channel)); 180 } else { 181 builder.append(channel.toString()); 182 } 183 return builder.toString(); 184 } 185 186 /** 187 * Returns a textual representation of an array of channels. 188 * 189 * @param channels the channels 190 * @return the representation 191 */ 192 @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.UseVarargs" }) 193 static String toString(Channel[] channels) { 194 StringBuilder builder = new StringBuilder(); 195 builder.append('['); 196 boolean first = true; 197 for (Channel c : channels) { 198 if (!first) { 199 builder.append(", "); 200 } 201 builder.append(Channel.toString(c)); 202 first = false; 203 } 204 builder.append(']'); 205 return builder.toString(); 206 } 207 208}