//****************************************************************************************************** // FrameImageParserBase.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 03/28/2007 - Pinal C. Patel // Original version of source code generated. // 11/20/2008 - J. Ritchie Carroll // Adapted for more generalized use via the following related base classes: // BinaryImageParserBase => FrameImageParserBase => MultiSourceFrameImageParserBase. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 11/23/2011 - J. Ritchie Carroll // Modified to support buffer optimized ISupportBinaryImage. // 11/06/2012 - J. Ritchie Carroll // Modified to support queued publication processing. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.ComponentModel; using System.Reflection; using System.Text; using GSF.Collections; namespace GSF.Parsing { /// /// This class defines a basic implementation of parsing functionality suitable for automating the parsing of /// a binary data stream represented as frames with common headers and returning the parsed data via an event. /// /// /// /// This parser is designed as a write-only stream such that data can come from any source. /// /// /// This class is more specific than the in that it can automate the parsing of /// a particular protocol that is formatted as a series of frames that have a common method of identification. /// Automation of type creation occurs by loading implementations of common types that implement the /// interface. The common method of identification is handled by /// creating a class derived from the which primarily includes a /// TypeID property, but also should include any state information needed to parse a particular frame if /// necessary. Derived classes simply override the function in order to parse /// the from a provided binary image. /// /// /// Type of identifier used to distinguish output types. /// Type of the interface or class used to represent outputs. public abstract class FrameImageParserBase : BinaryImageParserBase, IFrameImageParser where TOutputType : ISupportFrameImage { #region [ Members ] // Nested Types // Container for Type information. private class TypeInfo { public Type RuntimeType; public TTypeIdentifier TypeID; public Func CreateNew; public bool SupportsLifecycle; } // Events /// /// Occurs when a data image is deserialized successfully to one of the output types that the data /// image represents. /// /// /// is the object that was deserialized from the binary image. /// public event EventHandler> DataParsed; /// /// Occurs when matching an output type for deserializing the data image could not be found. /// /// /// is the ID of the output type that could not be found. /// public event EventHandler> OutputTypeNotFound; /// /// Occurs when more than one type has been defined that can deserialize the specified output type. /// /// /// is the that defines a type ID that has already been defined.
/// is the ID of the output type that was defined more than once. ///
public event EventHandler> DuplicateTypeHandlerEncountered; // Fields private Dictionary m_outputTypes; private readonly AsyncQueue> m_outputQueue; private bool m_disposed; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// protected FrameImageParserBase() { m_outputTypes = new Dictionary(); m_outputQueue = new AsyncQueue> { ProcessItemFunction = PublishParsedOutput }; m_outputQueue.ProcessException += m_parsedOutputQueue_ProcessException; } #endregion #region [ Properties ] /// /// Gets the total number of parsed outputs that are currently queued for publication, if any. /// public virtual int QueuedOutputs { get { if ((object)m_outputQueue != null) return m_outputQueue.Count; return 0; } } /// /// Gets or sets a boolean value that indicates whether the frame image parser is currently enabled. /// public override bool Enabled { get { return base.Enabled; } set { base.Enabled = value; m_outputQueue.Enabled = value; } } /// /// Gets current status of . /// [Browsable(false)] public override string Status { get { StringBuilder status = new StringBuilder(); status.Append(base.Status); status.AppendFormat("Total defined output types: {0}", m_outputTypes.Count); status.AppendLine(); status.AppendFormat(" Parsed outputs to publish: {0}", QueuedOutputs); status.AppendLine(); return status.ToString(); } } #endregion #region [ Methods ] /// /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { if ((object)m_outputTypes != null) { foreach (KeyValuePair item in m_outputTypes) { item.Value.CreateNew = null; } m_outputTypes.Clear(); } m_outputTypes = null; } } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } } /// /// Start the data parser. /// /// /// This overload loads public types from assemblies in the application binaries directory that implement the parser's output type. /// public override void Start() { Start(typeof(TOutputType).LoadImplementations()); } /// /// Starts the data parser given the specified type implementations. /// /// Output type implementations to establish for the parser. public virtual void Start(IEnumerable implementations) { // Call base class start method base.Start(); ConstructorInfo typeCtor; List outputTypes = new List(); // Temporarily hold output types until their IDs are determined. foreach (Type type in implementations) { // See if a parameterless constructor is available for this type typeCtor = type.GetConstructor(Type.EmptyTypes); // Since user can call this overload with any list of types, we double check the type criteria. // If output type is a class, see if current type derives from it, else if output type is an // interface, see if current type implements it. if ((object)typeCtor != null && !type.IsAbstract && ( (typeof(TOutputType).IsClass && type.IsSubclassOf(typeof(TOutputType))) || (typeof(TOutputType).IsInterface && (object)type.GetInterface(typeof(TOutputType).Name) != null)) ) { // The type meets the following criteria: // - has a default public constructor // - is not abstract and can be instantiated. // - type is related to class or interface specified for the output TypeInfo outputType = new TypeInfo { RuntimeType = type }; // If class implementation supports life cycle, automatically dispose of objects when we are done with them outputType.SupportsLifecycle = (object)type.GetInterface("GSF.ISupportLifecycle") != null; outputType.CreateNew = FastObjectFactory.GetCreateObjectFunction(type); // We'll hold all of the matching types in this list temporarily until their IDs are determined. outputTypes.Add(outputType); } } foreach (TypeInfo outputType in outputTypes) { // Now, we'll go though all of the output types we've found and instantiate an instance of each in order to get // the identifier for each of the type. This will help lookup of the type to be used when parsing the data. TOutputType instance = outputType.CreateNew(); outputType.TypeID = instance.TypeID; if (!m_outputTypes.ContainsKey(outputType.TypeID)) m_outputTypes.Add(outputType.TypeID, outputType); else OnDuplicateTypeHandlerEncountered(outputType.RuntimeType, outputType.TypeID); // Dispose of the object if it supports lifecycle management if (outputType.SupportsLifecycle) ((IDisposable)instance).Dispose(); } } /// /// Output type specific frame parsing algorithm. /// /// Buffer containing data to parse. /// Offset index into buffer that represents where to start parsing. /// Maximum length of valid data from offset. /// The length of the data that was parsed. protected override int ParseFrame(byte[] buffer, int offset, int length) { ICommonHeader commonHeader; TypeInfo outputType; TOutputType instance; int parsedLength; // Extract the common header from the buffer image which includes the output type ID. // For any protocol data that is represented as frames of data in a stream, there will // be some set of common identification properties in the frame image, usually at the // top, that is common for all frame types. commonHeader = ParseCommonHeader(buffer, offset, length); // See if there was enough buffer to parse common header, if not exit and wait for more data if ((object)commonHeader == null) return 0; // Lookup TypeID to see if it is a known type if (m_outputTypes.TryGetValue(commonHeader.TypeID, out outputType)) { instance = outputType.CreateNew(); instance.CommonHeader = commonHeader; parsedLength = instance.ParseBinaryImage(buffer, offset, length); // Expose parsed type to consumer if (parsedLength > 0) OnDataParsed(instance); else if (outputType.SupportsLifecycle) ((IDisposable)instance).Dispose(); } else { // Report unrecognized output type OnOutputTypeNotFound(commonHeader.TypeID); // We encountered an unrecognized data type that cannot be parsed if (ProtocolUsesSyncBytes) { // Protocol uses synchronization bytes so we scan for them in the current buffer. This effectively // scans through buffer to next frame... int syncBytesPosition = buffer.IndexOfSequence(ProtocolSyncBytes, offset + 1, length - 1); if (syncBytesPosition > -1) return syncBytesPosition - offset; } // Without synchronization bytes we have no choice but to move onto the next buffer of data :( parsedLength = length; OnDataDiscarded(buffer.BlockCopy(offset, length)); } return parsedLength; } /// /// Parses a common header instance that implements for the output type represented /// in the binary image. /// /// Buffer containing data to parse. /// Offset index into buffer that represents where to start parsing. /// Maximum length of valid data from offset. /// The which includes a type ID for the to be parsed. /// /// /// Derived classes need to provide a common header instance (i.e., class that implements ) /// for the output types; this will primarily include an ID of the that the data image represents. This parsing is /// only for common header information, actual parsing will be handled by output type via its /// method. This header image should also be used to add needed complex state information about the output type being parsed if needed. /// /// /// If there is not enough buffer available to parse common header (as determined by ), return null. Also, if /// the protocol allows frame length to be determined at the time common header is being parsed and there is not enough buffer to parse /// the entire frame, it will be optimal to prevent further parsing by returning null. /// /// protected abstract ICommonHeader ParseCommonHeader(byte[] buffer, int offset, int length); /// /// Raises the event. /// /// The object that was deserialized from binary image. protected virtual void OnDataParsed(TOutputType output) { if ((object)DataParsed != null) { // Get a reusable event args object to publish output EventArgs outputArgs = FastObjectFactory>.CreateObjectFunction(); outputArgs.Argument = output; if (output.AllowQueuedPublication) { // Queue-up parsed output for publication m_outputQueue.Enqueue(outputArgs); } else { // Publish parsed output immediately DataParsed(this, outputArgs); } } } /// /// handler used to publish queued outputs. /// /// Event args containing new output to publish. protected virtual void PublishParsedOutput(EventArgs outputArgs) { if ((object)DataParsed != null) DataParsed(this, outputArgs); } /// /// Raises the event. /// /// The ID of the output type that was not found. protected virtual void OnOutputTypeNotFound(TTypeIdentifier id) { if ((object)OutputTypeNotFound != null) OutputTypeNotFound(this, new EventArgs(id)); } /// /// Raises the event. /// /// The that defines a type ID that has already been defined. /// The ID of the output type that was defined more than once. protected virtual void OnDuplicateTypeHandlerEncountered(Type duplicateType, TTypeIdentifier id) { if ((object)DuplicateTypeHandlerEncountered != null) DuplicateTypeHandlerEncountered(this, new EventArgs(duplicateType, id)); } // Expose exceptions encountered via async queue processing to parsing exception event private void m_parsedOutputQueue_ProcessException(object sender, EventArgs e) { OnParsingException(e.Argument); } #endregion } }