/*******************************************************************************
* Copyright (c) 2014 Wind River Systems, Inc. and others. All rights reserved.
* This program and the accompanying materials are made available under the terms
* of the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.tcf.core.internal.channelmanager;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.services.IStreams;
import org.eclipse.tcf.te.runtime.interfaces.IDisposable;
import org.eclipse.tcf.te.tcf.core.activator.CoreBundleActivator;
import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager;
import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.IStreamsListener;
import org.eclipse.tcf.te.tcf.core.interfaces.tracing.ITraceIds;
/**
* Channel manager stream listener proxy implementation.
*/
final class StreamListenerProxy implements IStreams.StreamsListener, IChannelManager.IStreamsListenerProxy {
// The channel
private final IChannel channel;
// The stream type the proxy is registered for
private final String streamType;
// The list of proxied stream listeners
/* default */ ListenerList listeners = new ListenerList();
// The list of delayed stream created events
private final List<StreamListenerProxy.StreamCreatedEvent> delayedCreatedEvents = new ArrayList<StreamListenerProxy.StreamCreatedEvent>();
/**
* Immutable stream created event.
*/
private final static class StreamCreatedEvent {
/**
* The stream type.
*/
public final String streamType;
/**
* The stream id.
*/
public final String streamId;
/**
* The context id.
*/
public final String contextId;
// As the class is immutable, we do not need to build the toString
// value again and again. Build it once in the constructor and reuse it later.
private final String toString;
/**
* Constructor.
*
* @param streamType The stream type.
* @param streamId The stream id.
* @param contextId The context id.
*/
public StreamCreatedEvent(String streamType, String streamId, String contextId) {
this.streamType = streamType;
this.streamId = streamId;
this.contextId = contextId;
toString = toString();
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
return obj instanceof StreamListenerProxy.StreamCreatedEvent
&& toString().equals(((StreamListenerProxy.StreamCreatedEvent)obj).toString());
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return toString().hashCode();
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
if (toString != null) return toString;
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
builder.append(": streamType = "); //$NON-NLS-1$
builder.append(streamType);
builder.append("; streamId = "); //$NON-NLS-1$
builder.append(streamId);
builder.append("; contextId = "); //$NON-NLS-1$
builder.append(contextId);
return builder.toString();
}
}
/**
* Constructor
*
* @param The channel. Must not be <code>null</code>.
*/
public StreamListenerProxy(final IChannel channel, final String streamType) {
Assert.isNotNull(channel);
Assert.isNotNull(streamType);
this.channel = channel;
this.channel.addChannelListener(new IChannel.IChannelListener() {
@Override
public void onChannelOpened() {}
@Override
public void onChannelClosed(Throwable error) {
// Channel is closed, remove ourself
channel.removeChannelListener(this);
// Dispose all registered streams listener
Object[] candidates = listeners.getListeners();
listeners.clear();
for (Object listener : candidates) {
if (listener instanceof IDisposable) {
((IDisposable)listener).dispose();
}
}
}
@Override
public void congestionLevel(int level) {
}
});
// Remember the stream type
this.streamType = streamType;
}
/**
* Returns the stream type the proxy is registered for.
*
* @return The stream type.
*/
public String getStreamType() {
return streamType;
}
/**
* Adds the given streams listener to the list of proxied listeners.
*
* @param listener The streams listener. Must not be <code>null</code>.
*/
public void addListener(IStreamsListener listener) {
Assert.isNotNull(listener);
listener.setProxy(this);
listeners.add(listener);
}
/**
* Removes the given streams listener from the list of proxied listeners.
*
* @param listener The streams listener. Must not be <code>null</code>.
*/
public void removeListener(IStreamsListener listener) {
Assert.isNotNull(listener);
listener.setProxy(null);
listeners.remove(listener);
}
/**
* Returns if the proxied listeners list is empty or not.
*
* @return <code>True</code> if the list is empty, <code>false</code> otherwise.
*/
public boolean isEmpty() {
return listeners.isEmpty();
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.IStreamsListenerProxy#processDelayedCreatedEvents()
*/
@Override
public void processDelayedCreatedEvents() {
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_STREAMS_LISTENER_PROXY)) {
CoreBundleActivator.getTraceHandler().trace("StreamListenerProxy: processDelayedCreatedEvents()", //$NON-NLS-1$
0, ITraceIds.TRACE_STREAMS_LISTENER_PROXY,
IStatus.INFO, getClass());
}
synchronized (delayedCreatedEvents) {
// Make a snapshot of all delayed created events
StreamListenerProxy.StreamCreatedEvent[] events = delayedCreatedEvents.toArray(new StreamListenerProxy.StreamCreatedEvent[delayedCreatedEvents.size()]);
// Clear the events now, it will be refilled by calling the created method
delayedCreatedEvents.clear();
// Loop the delayed created events and recall the created method to process them
for (StreamListenerProxy.StreamCreatedEvent event : events) {
created(event.streamType, event.streamId, event.contextId);
}
}
}
/* (non-Javadoc)
* @see org.eclipse.tcf.services.IStreams.StreamsListener#created(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public void created(String stream_type, String stream_id, String context_id) {
Assert.isNotNull(stream_type);
Assert.isNotNull(stream_id);
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_STREAMS_LISTENER_PROXY)) {
CoreBundleActivator.getTraceHandler().trace("StreamListenerProxy: created(" + stream_type + ", " + stream_id + ", " + context_id + ")", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
0, ITraceIds.TRACE_STREAMS_LISTENER_PROXY,
IStatus.INFO, getClass());
}
// If the context_id is null, disconnect from the stream right away. We do not support
// old TCF agents not sending the context id in the created event.
if (context_id == null) {
IStreams service = channel.getRemoteService(IStreams.class);
if (service != null) {
service.disconnect(stream_id, new IStreams.DoneDisconnect() {
@Override
public void doneDisconnect(IToken token, Exception error) {
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_STREAMS_LISTENER_PROXY)) {
CoreBundleActivator.getTraceHandler().trace("StreamListenerProxy: disconnect -> context id must be not null.", //$NON-NLS-1$
0, ITraceIds.TRACE_STREAMS_LISTENER_PROXY, IStatus.INFO, getClass());
}
}
});
}
return;
}
boolean delayed = false;
boolean disconnect = true;
// Loop all listeners
for (Object l : listeners.getListeners()) {
IStreamsListener listener = (IStreamsListener)l;
// If the listener has no context set yet, the listener cannot decide if
// the event is consumed or not. In this case, the event must be delayed.
if (!listener.hasContext()) {
delayed |= true;
continue;
}
// Does the listener consume the event?
boolean consume = listener.isCreatedConsumed(stream_type, stream_id, context_id);
if (consume) listener.created(stream_type, stream_id, context_id);
// If the created event is consumed by one listener, it cannot be disconnected anymore
disconnect &= !consume;
}
if (delayed) {
// Context not set yet --> add to the delayed list
StreamListenerProxy.StreamCreatedEvent event = new StreamCreatedEvent(stream_type, stream_id, context_id);
synchronized (delayedCreatedEvents) {
if (!delayedCreatedEvents.contains(event)) {
delayedCreatedEvents.add(event);
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_STREAMS_LISTENER_PROXY)) {
CoreBundleActivator.getTraceHandler().trace("StreamListenerProxy: delayed -> at least one listener does not have a context set", //$NON-NLS-1$
0, ITraceIds.TRACE_STREAMS_LISTENER_PROXY, IStatus.INFO, getClass());
}
}
}
return;
}
if (disconnect) {
IStreams service = channel.getRemoteService(IStreams.class);
if (service != null) {
service.disconnect(stream_id, new IStreams.DoneDisconnect() {
@Override
public void doneDisconnect(IToken token, Exception error) {
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_STREAMS_LISTENER_PROXY)) {
CoreBundleActivator.getTraceHandler().trace("StreamListenerProxy: disconnect -> not interested in context id", //$NON-NLS-1$
0, ITraceIds.TRACE_STREAMS_LISTENER_PROXY, IStatus.INFO, getClass());
}
}
});
}
}
}
/* (non-Javadoc)
* @see org.eclipse.tcf.services.IStreams.StreamsListener#disposed(java.lang.String, java.lang.String)
*/
@Override
public void disposed(String stream_type, String stream_id) {
Assert.isNotNull(stream_type);
Assert.isNotNull(stream_id);
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_STREAMS_LISTENER_PROXY)) {
CoreBundleActivator.getTraceHandler().trace("StreamListenerProxy: disposed(" + stream_type + ", " + stream_id + ")", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
0, ITraceIds.TRACE_STREAMS_LISTENER_PROXY,
IStatus.INFO, getClass());
}
// If the delayed created events list is not empty, we have
// to check if one of the delayed create events got disposed
synchronized (delayedCreatedEvents) {
Iterator<StreamListenerProxy.StreamCreatedEvent> iterator = delayedCreatedEvents.iterator();
while (iterator.hasNext()) {
StreamListenerProxy.StreamCreatedEvent event = iterator.next();
if (stream_type.equals(event.streamType) && stream_id.equals(event.streamId)) {
// Remove the create event from the list
iterator.remove();
}
}
}
for (Object l : listeners.getListeners()) {
((IStreamsListener)l).disposed(stream_type, stream_id);
}
}
}