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 java.util.Map;
022import java.util.Optional;
023import java.util.concurrent.ConcurrentHashMap;
024
025/**
026 * Represents a subchannel. Subchannels delegate the invocations of a
027 * {@link Channel}'s methods to their respective main channel. Events fired on
028 * a subchannel are therefore handled by the framework as if they were fired on
029 * the main channel. Firing events on a subchannel instance instead of on the
030 * main channel is a means to associate several events with a common context.
031 */
032public interface Subchannel extends Channel, Associator {
033
034    /**
035     * Returns the main channel.
036     * 
037     * @return the mainChannel
038     */
039    Channel mainChannel();
040
041    /**
042     * Returns the main channel's match value.
043     * 
044     * @see Channel#defaultCriterion()
045     */
046    @Override
047    default Object defaultCriterion() {
048        return mainChannel().defaultCriterion();
049    }
050
051    /**
052     * Delegates to main channel.
053     * 
054     * @see Channel#isEligibleFor(Object)
055     */
056    @Override
057    default boolean isEligibleFor(Object value) {
058        return mainChannel().isEligibleFor(value);
059    }
060
061    /**
062     * Returns a string representation of the channel.
063     *
064     * @param subchannel the subchannel
065     * @return the string
066     */
067    static String toString(Subchannel subchannel) {
068        StringBuilder builder = new StringBuilder();
069        builder.append(Channel.toString(subchannel.mainChannel()))
070            .append('{')
071            .append(Components.objectName(subchannel))
072            .append('}');
073        return builder.toString();
074    }
075
076    /**
077     * Creates a new subchannel of the given component's channel.
078     *
079     * @param component the component used to get the main channel
080     * @return the subchannel
081     */
082    static Subchannel create(Component component) {
083        return new DefaultSubchannel(component.channel());
084    }
085
086    /**
087     * A simple implementation of {@link Subchannel}.
088     */
089    class DefaultSubchannel implements Subchannel {
090        private final Channel mainChannel;
091        private Map<Object, Object> contextData;
092
093        /**
094         * Creates a new instance with the given main channel and response
095         * pipeline.  
096         * 
097         * @param mainChannel the main channel
098         */
099        public DefaultSubchannel(Channel mainChannel) {
100            this.mainChannel = mainChannel;
101        }
102
103        /*
104         * (non-Javadoc)
105         * 
106         * @see org.jgrapes.core.Subchannel#getMainChannel()
107         */
108        @Override
109        public Channel mainChannel() {
110            return mainChannel;
111        }
112
113        /**
114         * Establishes a "named" association to an associated object. Note that 
115         * anything that represents an id can be used as value for 
116         * parameter `name`, it does not necessarily have to be a string.
117         * 
118         * @param by the "name"
119         * @param with the object to be associated
120         */
121        @SuppressWarnings("PMD.ShortVariable")
122        public DefaultSubchannel setAssociated(Object by, Object with) {
123            if (contextData == null) {
124                contextData = new ConcurrentHashMap<>();
125            }
126            if (with == null) {
127                contextData.remove(by);
128            } else {
129                contextData.put(by, with);
130            }
131            return this;
132        }
133
134        /**
135         * Retrieves the associated object following the association 
136         * with the given "name". This general version of the method
137         * supports the retrieval of values of arbitrary types
138         * associated by any "name" types. 
139         * 
140         * @param by the "name"
141         * @param type the tape of the value to be retrieved
142         * @param <V> the type of the value to be retrieved
143         * @return the associate, if any
144         */
145        @SuppressWarnings("PMD.ShortVariable")
146        public <V> Optional<V> associated(Object by, Class<V> type) {
147            if (contextData == null) {
148                return Optional.empty();
149            }
150            return Optional.ofNullable(contextData.get(by))
151                .filter(found -> type.isAssignableFrom(found.getClass()))
152                .map(match -> type.cast(match));
153        }
154
155        /*
156         * (non-Javadoc)
157         * 
158         * @see java.lang.Object#toString()
159         */
160        @Override
161        public String toString() {
162            return Subchannel.toString(this);
163        }
164
165    }
166}