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}