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.io.util;
020
021import java.util.Optional;
022import org.jgrapes.core.Channel;
023import org.jgrapes.core.EventPipeline;
024import org.jgrapes.core.Manager;
025import org.jgrapes.core.Subchannel;
026import org.jgrapes.io.IOSubchannel;
027import org.jgrapes.io.IOSubchannel.DefaultIOSubchannel;
028
029/**
030 * Provides an I/O subchannel that is linked to another I/O subchannel. A
031 * typical use case for this class is a protocol converter.
032 * 
033 * Protocol converters receive events related to an I/O resource from upstream,
034 * and while processing them usually generate new events to other components
035 * downstream (and vice versa). The events received are associated with a
036 * particular resource by the subchannel that is used to relay them. The
037 * association with the resource must be maintained for the newly generated
038 * events as well. It is, however, not possible to use the same subchannel for
039 * receiving from upstream and sending downstream because it wouldn't be
040 * possible to distinguish between e.g. an {@code Input} event from upstream to
041 * the converter and an {@code Input} event (conveying the converted data) from
042 * the converter to the downstream components.
043 * 
044 * Therefore, the converter must provide and manage independent subchannels for
045 * the data streams on its downstream side with a one-to-one relationship to the
046 * upstream subchannels. The {@code LinkedIOSubchannel} class simplifies this
047 * task. It provides a new subchannel with a reference to an existing 
048 * subchannel. This makes it easy to find the upstream subchannel for a 
049 * given downstream ({@code LinkedIOSubchannel}) when processing response
050 * events. For finding the downstream {@code IOSubchannel} for a given upstream
051 * connection, instances associate themselves with the upstream channel using
052 * the converter component as key. This allows a subchannel to have several
053 * associated linked subchannels.
054 */
055public class LinkedIOSubchannel extends DefaultIOSubchannel {
056
057    private final Manager hub;
058    private final IOSubchannel upstreamChannel;
059    private static ThreadLocal<Integer> linkedRemaining = new ThreadLocal<>();
060
061    /**
062     * Creates a new {@code LinkedIOSubchannel} for a given main channel
063     * that links to the give I/O subchannel. Using this constructor 
064     * is similar to invoking
065     * {@link #LinkedIOSubchannel(Manager, Channel, IOSubchannel, EventPipeline, boolean)}
066     * with {@code true} as last parameter.
067     * 
068     * Because the newly created {@link LinkedIOSubchannel} is referenced by
069     * the upstream channel, it will life as long as the upstream channel,
070     * even if no further references exist.
071     *
072     * @param hub the component that manages this channel
073     * @param mainChannel the main channel
074     * @param upstreamChannel the upstream channel
075     * @param responsePipeline the response pipeline
076     */
077    public LinkedIOSubchannel(Manager hub, Channel mainChannel,
078            IOSubchannel upstreamChannel, EventPipeline responsePipeline) {
079        this(hub, mainChannel, upstreamChannel, responsePipeline, true);
080    }
081
082    /**
083     * Creates a new {@code LinkedIOSubchannel} for a given main channel
084     * that links to a given I/O subchannel.
085     * 
086     * Using this constructor with {@code false} as last parameter prevents the
087     * addition of the back link from the upstream channel to the downstream
088     * channel (see {@link #downstreamChannel(Manager, IOSubchannel)}). 
089     * This can save some space if a converter component has some other 
090     * means to maintain that information. Note that in this case a
091     * reference to the created {@link LinkedIOSubchannel} must be
092     * maintained, else it may be garbage collected. 
093     *
094     * @param hub the converter component that manages this channel
095     * @param mainChannel the main channel
096     * @param upstreamChannel the upstream channel
097     * @param responsePipeline the response pipeline
098     * @param linkBack create the link from upstream to downstream
099     */
100    public LinkedIOSubchannel(Manager hub, Channel mainChannel,
101            IOSubchannel upstreamChannel, EventPipeline responsePipeline,
102            boolean linkBack) {
103        super(mainChannel, responsePipeline);
104        this.hub = hub;
105        this.upstreamChannel = upstreamChannel;
106        if (linkBack) {
107            upstreamChannel.setAssociated(
108                new KeyWrapper(hub), this);
109        }
110    }
111
112    /**
113     * Removes the association between the upstream channel and this
114     * channel. Should only be called if this channel is no longer used.
115     * 
116     * @param hub the converter component that manages this channel
117     */
118    public void unlink(Manager hub) {
119        upstreamChannel.setAssociated(new KeyWrapper(hub), null);
120    }
121
122    /**
123     * Returns the component that manages this channel.
124     *
125     * @return the component that manages this channel
126     */
127    public Manager hub() {
128        return hub;
129    }
130
131    /**
132     * Returns the upstream channel.
133     *
134     * @return the upstream channel
135     */
136    public IOSubchannel upstreamChannel() {
137        return upstreamChannel;
138    }
139
140    /**
141     * Delegates the invocation to the upstream channel 
142     * if no associated data is found for this channel.
143     *
144     * @param <V> the value type
145     * @param by the associator
146     * @param type the type
147     * @return the optional
148     */
149    @Override
150    @SuppressWarnings("PMD.ShortVariable")
151    public <V> Optional<V> associated(Object by, Class<V> type) {
152        Optional<V> result = super.associated(by, type);
153        if (!result.isPresent()) {
154            IOSubchannel upstream = upstreamChannel();
155            if (upstream != null) {
156                return upstream.associated(by, type);
157            }
158        }
159        return result;
160    }
161
162    /**
163     * The {@link #toString()} method of {@link LinkedIOSubchannel}s
164     * shows the channel together with the upstream channel that it
165     * is linked to. If there are several levels of upstream links,
166     * this can become a very long sequence.
167     * 
168     * This method creates the representation of the linked upstream
169     * channel (an arrow followed by the representation of the channel),
170     * but only up to one level. If more levels exist, it returns
171     * an arrow followed by an ellipses. This method is used by
172     * {@link LinkedIOSubchannel#toString()}. Other implementations
173     * of {@link IOSubchannel} should use this method in their
174     * {@link Object#toString()} method as well to keep the result
175     * consistent. 
176     *
177     * @param upstream the upstream channel
178     * @return the string
179     */
180    public static String upstreamToString(Channel upstream) {
181        if (upstream == null) {
182            linkedRemaining.set(null);
183            return "";
184        }
185        if (linkedRemaining.get() == null) {
186            linkedRemaining.set(1);
187        }
188        if (linkedRemaining.get() <= 0) {
189            linkedRemaining.set(null);
190            return "↔…";
191        }
192        linkedRemaining.set(linkedRemaining.get() - 1);
193
194        // Build continuation.
195        StringBuilder builder = new StringBuilder();
196        builder.append('↔')
197            .append(Channel.toString(upstream));
198        linkedRemaining.set(null);
199        return builder.toString();
200
201    }
202
203    /*
204     * (non-Javadoc)
205     * 
206     * @see java.lang.Object#toString()
207     */
208    @Override
209    public String toString() {
210        StringBuilder builder = new StringBuilder();
211        builder.append(Subchannel.toString(this));
212        if (upstreamChannel != null) {
213            builder.append(upstreamToString(upstreamChannel));
214        }
215        return builder.toString();
216    }
217
218    /**
219     * Returns the linked downstream channel that has been created for the 
220     * given component and (upstream) subchannel. If more than one linked 
221     * subchannel has been created for a given component and subchannel, 
222     * the linked subchannel created last is returned.
223     *
224     * @param hub the component that manages this channel
225     * @param upstreamChannel the (upstream) channel
226     * @return the linked downstream subchannel created for the
227     * given component and (upstream) subchannel if it exists
228     */
229    public static Optional<? extends LinkedIOSubchannel> downstreamChannel(
230            Manager hub, IOSubchannel upstreamChannel) {
231        return upstreamChannel.associated(
232            new KeyWrapper(hub), LinkedIOSubchannel.class);
233    }
234
235    /**
236     * Like {@link #downstreamChannel(Manager, IOSubchannel)}, but
237     * with the return value of the specified type.
238     *
239     * @param <T> the generic type
240     * @param hub the component that manages this channel
241     * @param upstreamChannel the (upstream) channel
242     * @param clazz the type of the returned value
243     * @return the linked downstream subchannel created for the
244     * given component and (upstream) subchannel if it exists
245     */
246    public static <T extends LinkedIOSubchannel> Optional<T> downstreamChannel(
247            Manager hub, IOSubchannel upstreamChannel, Class<T> clazz) {
248        return upstreamChannel.associated(
249            new KeyWrapper(hub), clazz);
250    }
251
252    /**
253     * Artificial key.
254     */
255    private static class KeyWrapper {
256
257        private final Manager hub;
258
259        /**
260         * @param hub
261         */
262        public KeyWrapper(Manager hub) {
263            super();
264            this.hub = hub;
265        }
266
267        /*
268         * (non-Javadoc)
269         * 
270         * @see java.lang.Object#hashCode()
271         */
272        @Override
273        @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
274        public int hashCode() {
275            @SuppressWarnings("PMD.AvoidFinalLocalVariable")
276            final int prime = 31;
277            int result = 1;
278            result = prime * result + ((hub == null) ? 0
279                : hub.hashCode());
280            return result;
281        }
282
283        /*
284         * (non-Javadoc)
285         * 
286         * @see java.lang.Object#equals(java.lang.Object)
287         */
288        @Override
289        public boolean equals(Object obj) {
290            if (this == obj) {
291                return true;
292            }
293            if (obj == null) {
294                return false;
295            }
296            if (getClass() != obj.getClass()) {
297                return false;
298            }
299            KeyWrapper other = (KeyWrapper) obj;
300            if (hub == null) {
301                if (other.hub != null) {
302                    return false;
303                }
304            } else if (!hub.equals(other.hub)) {
305                return false;
306            }
307            return true;
308        }
309    }
310}