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