#region License Information
/* HeuristicLab
* Copyright (C) 2002-2014 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeuristicLab is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HeuristicLab. If not, see .
*/
#endregion
using HeuristicLab.Common;
using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace HeuristicLab.Core.Networks {
[Item("MessagePort", "A port of a network node for sending and receiving messages.")]
[StorableClass]
public class MessagePort : ParameterizedPort, IMessagePort {
public override Image ItemImage {
get {
if (PortConnectionValid) return base.ItemImage;
else return HeuristicLab.Common.Resources.VSImageLibrary.Error;
}
}
new public PortParameterCollection Parameters {
get { return base.Parameters; }
}
[Storable]
protected IMessagePort connectedPort;
public IMessagePort ConnectedPort {
get { return connectedPort; }
set {
if (connectedPort != value) {
DeregisterConnectedPortEvents();
connectedPort = value;
RegisterConnectedPortEvents();
OnConnectedPortChanged();
CheckPortConnection();
}
}
}
IPort IConnectablePort.ConnectedPort {
get { return ConnectedPort; }
set {
if (!(value is IMessagePort))
throw new InvalidOperationException("Type mismatch. ConnectedPort is not an IMessagePort.");
ConnectedPort = (IMessagePort)value;
}
}
[Storable]
protected bool portConnectionValid;
public bool PortConnectionValid {
get { return portConnectionValid; }
protected set {
if (value != portConnectionValid) {
portConnectionValid = value;
OnPortConnectionValidChanged();
OnItemImageChanged();
}
}
}
[Storable]
protected IMessage lastMessage;
public IMessage LastMessage {
get { return lastMessage; }
protected set {
if (value != lastMessage) {
lastMessage = value;
OnLastMessageChanged();
}
}
}
[StorableConstructor]
protected MessagePort(bool deserializing) : base(deserializing) { }
protected MessagePort(MessagePort original, Cloner cloner)
: base(original, cloner) {
connectedPort = cloner.Clone(original.connectedPort);
portConnectionValid = original.portConnectionValid;
lastMessage = null;
RegisterConnectedPortEvents();
}
public MessagePort()
: base("MessagePort") {
portConnectionValid = true;
lastMessage = null;
}
public MessagePort(string name)
: base(name) {
portConnectionValid = true;
lastMessage = null;
}
public MessagePort(string name, string description)
: base(name, description) {
portConnectionValid = true;
lastMessage = null;
}
[StorableHook(HookType.AfterDeserialization)]
private void AfterDeserialization() {
RegisterConnectedPortEvents();
}
public override IDeepCloneable Clone(Cloner cloner) {
return new MessagePort(this, cloner);
}
public bool CanConnectToPort(IPort port) {
return (port == null) || (port is IMessagePort);
}
public IMessage PrepareMessage() {
if (!PortConnectionValid) throw new InvalidOperationException("Port connection is not valid");
var message = new Message();
// collect output parameters from local port
message.Values.AddRange(
Parameters
.Where(p => p.Type.HasFlag(PortParameterType.Output))
.Select(p => p.CreateMessageValue())
);
// collect remaining parameters from connected port
if (ConnectedPort != null) {
message.Values.AddRange(
ConnectedPort.Parameters
.Where(p => !message.Values.ContainsKey(p.Name))
.Select(p => p.CreateMessageValue())
);
}
// collect remaining input parameters from local port
message.Values.AddRange(
Parameters
.Where(p => p.Type.HasFlag(PortParameterType.Input) && !message.Values.ContainsKey(p.Name))
.Select(p => p.CreateMessageValue())
);
return message;
}
public void SendMessage(IMessage message) {
SendMessage(message, CancellationToken.None);
}
public void SendMessage(IMessage message, CancellationToken token) {
if (!PortConnectionValid) throw new InvalidOperationException("Port connection is not valid");
if (ConnectedPort != null) ConnectedPort.ReceiveMessage(message, token);
LastMessage = message;
OnMessageSent(message, token);
}
public async Task SendMessageAsync(IMessage message) {
await SendMessageAsync(message, CancellationToken.None);
}
public async Task SendMessageAsync(IMessage message, CancellationToken token) {
await Task.Run(() => { SendMessage(message, token); }, token);
}
public event EventHandler> MessageSent;
protected virtual void OnMessageSent(IMessage message, CancellationToken token) {
var handler = MessageSent;
if (handler != null) handler(this, new EventArgs(message, token));
}
public void ReceiveMessage(IMessage message, CancellationToken token) {
if (!PortConnectionValid) throw new InvalidOperationException("Port connection is not valid");
LastMessage = message;
OnMessageReceived(message, token);
token.ThrowIfCancellationRequested();
}
public event EventHandler> MessageReceived;
protected virtual void OnMessageReceived(IMessage message, CancellationToken token) {
var handler = MessageReceived;
if (handler != null) handler(this, new EventArgs(message, token));
}
public event EventHandler ConnectedPortChanged;
protected virtual void OnConnectedPortChanged() {
var handler = ConnectedPortChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
public event EventHandler PortConnectionValidChanged;
protected virtual void OnPortConnectionValidChanged() {
var handler = PortConnectionValidChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
public event EventHandler LastMessageChanged;
protected virtual void OnLastMessageChanged() {
var handler = LastMessageChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
protected override void OnInterfaceChanged() {
CheckPortConnection();
base.OnInterfaceChanged();
}
#region ConnectedPort Events
protected virtual void RegisterConnectedPortEvents() {
if (connectedPort != null) {
connectedPort.InterfaceChanged += ConnectedPort_InterfaceChanged;
connectedPort.MessageSent += ConnectedPort_MessageSent;
}
}
protected virtual void DeregisterConnectedPortEvents() {
if (connectedPort != null) {
connectedPort.InterfaceChanged -= ConnectedPort_InterfaceChanged;
connectedPort.MessageSent -= ConnectedPort_MessageSent;
}
}
protected virtual void ConnectedPort_InterfaceChanged(object sender, EventArgs e) {
CheckPortConnection();
}
protected virtual void ConnectedPort_MessageSent(object sender, EventArgs e) {
ReceiveMessage(e.Value, e.Value2);
}
#endregion
#region Helpers
protected virtual void CheckPortConnection() {
PortConnectionValid = (ConnectedPort == null) ||
(ParametersCompatibleToTarget(Parameters, ConnectedPort.Parameters) &&
ParametersCompatibleToTarget(ConnectedPort.Parameters, Parameters));
}
protected virtual bool ParametersCompatibleToTarget(IEnumerable source, IEnumerable target) {
// checks if the source parameters are compatible to the input parameters of the target
foreach (var input in target.Where(p => p.Type.HasFlag(PortParameterType.Input))) {
var param = source.Where(p => input.Name == p.Name).FirstOrDefault();
if (param == null) {
if (input.DefaultValue == null) return false;
} else {
if (!param.Type.HasFlag(PortParameterType.Output)) return false;
if (!input.DataType.IsAssignableFrom(param.DataType)) return false;
}
}
return true;
}
#endregion
}
}