//******************************************************************************************************
// FrameImageParserBase.cs - Gbtc
//
// Copyright © 2010, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php
//
// 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.
//
//******************************************************************************************************
using GSF.Collections;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;
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 cound 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 flag that determines if object pooling is allowed.
///
///
/// Object pooling is allowed by default for 's that implement ,
/// override and return false if you do not want your frame image parser to use object pooling.
///
public virtual bool AllowObjectPooling
{
get
{
return true;
}
}
///
/// 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 = null;
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 object pooling is allowed and class implementation supports life cycle, use object pool instead of creating an object each time one is parsed
if (AllowObjectPooling && (object)type.GetInterface("TVA.ISupportLifecycle") != null)
{
outputType.SupportsLifecycle = true;
outputType.CreateNew = () => ReusableObjectPool.TakeObject(type);
}
else
{
outputType.SupportsLifecycle = false;
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);
// Return object to the pool 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 = ReusableObjectPool>.Default.TakeObject();
outputArgs.Argument = output;
if (output.AllowQueuedPublication)
{
// Queue-up parsed output for publication
m_outputQueue.Enqueue(outputArgs);
}
else
{
// Publish parsed output immediately
try
{
DataParsed(this, outputArgs);
}
finally
{
ReusableObjectPool>.Default.ReturnObject(outputArgs);
}
}
}
}
///
/// handler used to publish queued outputs.
///
/// Event args continaing new output to publish.
protected virtual void PublishParsedOutput(EventArgs outputArgs)
{
try
{
if ((object)DataParsed != null)
DataParsed(this, outputArgs);
}
finally
{
ReusableObjectPool>.Default.ReturnObject(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
}
}