#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 } }