-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
code cleanups, duplicate code removals and some threading security improvements
- Loading branch information
1 parent
37a4040
commit 8abcd61
Showing
48 changed files
with
992 additions
and
773 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
Binary file modified
BIN
-505 Bytes
(100%)
BPMNEngine/.vs/ProjectEvaluation/bpmnengine.metadata.v7.bin
Binary file not shown.
Binary file not shown.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
using BPMNEngine.Drawing; | ||
using BPMNEngine.Interfaces.Elements; | ||
using Microsoft.Maui.Graphics; | ||
using System.Collections; | ||
using System.Text; | ||
|
||
namespace BPMNEngine | ||
{ | ||
public sealed partial class BusinessProcess | ||
{ | ||
private static readonly TimeSpan ANIMATION_DELAY = new(0, 0, 1); | ||
private const float DEFAULT_PADDING = 100; | ||
private const int VARIABLE_NAME_WIDTH = 200; | ||
private const int VARIABLE_VALUE_WIDTH = 300; | ||
private const int VARIABLE_IMAGE_WIDTH = VARIABLE_NAME_WIDTH+VARIABLE_VALUE_WIDTH; | ||
|
||
/// <summary> | ||
/// Called to render a PNG image of the process | ||
/// </summary> | ||
/// <param name="type">The output image format to generate, this being jpeg,png or bmp</param> | ||
/// <returns>A Bitmap containing a rendered image of the process</returns> | ||
public byte[] Diagram(ImageFormat type) | ||
=> Diagram(false)?.AsBytes(type); | ||
|
||
internal byte[] Diagram(bool outputVariables, ProcessState state, ImageFormat type) | ||
=> Diagram(outputVariables, state: state)?.AsBytes(type); | ||
|
||
private IImage Diagram(bool outputVariables, ProcessState state = null) | ||
{ | ||
if (definition==null) | ||
throw new DiagramException("Process definition is null or missing"); | ||
if (!definition.Diagrams.Any()) | ||
throw new DiagramException("Process definition does not contain any Diagrams"); | ||
state??=new ProcessState(this, null, null, null); | ||
WriteLogLine((IElement)null, LogLevel.Information, new StackFrame(1, true), DateTime.Now, string.Format("Rendering Business Process Diagram{0}", new object[] { (outputVariables ? " with variables" : " without variables") })); | ||
double width = 0; | ||
double height = 0; | ||
width = definition.Diagrams.Max(d => d.Size.Width+DEFAULT_PADDING); | ||
height = definition.Diagrams.Sum(d => d.Size.Height+DEFAULT_PADDING); | ||
IImage ret = null; | ||
try | ||
{ | ||
var image = BPMNEngine.Elements.Diagram.ProduceImage((int)Math.Ceiling(width), (int)Math.Ceiling(height)); | ||
var surface = image.Canvas; | ||
surface.FillColor=Colors.White; | ||
surface.FillRectangle(new Rect(0, 0, width, height)); | ||
float padding = DEFAULT_PADDING / 2; | ||
definition.Diagrams.ForEach(d => { | ||
surface.DrawImage(d.Render(state.Path, this.definition), DEFAULT_PADDING / 2, padding, d.Size.Width, d.Size.Height); | ||
padding += d.Size.Height + DEFAULT_PADDING; | ||
}); | ||
ret = image.Image; | ||
if (outputVariables) | ||
ret = BusinessProcess.AppendVariables(ret, state); | ||
} | ||
catch (Exception e) | ||
{ | ||
WriteLogException((IElement)null, new StackFrame(1, true), DateTime.Now, e); | ||
ret=null; | ||
} | ||
return ret; | ||
} | ||
|
||
private static IImage ProduceVariablesImage(ProcessState state) | ||
{ | ||
var image = BPMNEngine.Elements.Diagram.ProduceImage(1, 1); | ||
var canvas = image.Canvas; | ||
SizeF sz = canvas.GetStringSize("Variables", BPMNEngine.Elements.Diagram.DefaultFont, BPMNEngine.Elements.Diagram.FONT_SIZE); | ||
int varHeight = (int)sz.Height + 2; | ||
var keys = state[null]; | ||
varHeight+=keys.Sum(key => (int)canvas.GetStringSize(key, BPMNEngine.Elements.Diagram.DefaultFont, BPMNEngine.Elements.Diagram.FONT_SIZE).Height + 2); | ||
|
||
image = BPMNEngine.Elements.Diagram.ProduceImage(VARIABLE_IMAGE_WIDTH, varHeight); | ||
var surface = image.Canvas; | ||
surface.FillColor = Colors.White; | ||
surface.FillRectangle(0, 0, image.Width, image.Height); | ||
|
||
surface.StrokeColor = Colors.Black; | ||
surface.StrokeDashPattern=null; | ||
surface.StrokeSize=1.0f; | ||
|
||
surface.DrawRectangle(0, 0, image.Width, image.Height); | ||
|
||
surface.DrawLine(new Point(0, (int)sz.Height + 2), new Point(VARIABLE_IMAGE_WIDTH, (int)sz.Height + 2)); | ||
surface.DrawLine(new Point(VARIABLE_NAME_WIDTH, (int)sz.Height + 2), new Point(VARIABLE_NAME_WIDTH, image.Height)); | ||
surface.DrawString("Variables", new Rect(0, 2, image.Width, sz.Height), HorizontalAlignment.Center, VerticalAlignment.Center); | ||
float curY = sz.Height + 2; | ||
keys.ForEach(key => | ||
{ | ||
string label = key; | ||
SizeF szLabel = canvas.GetStringSize(label, BPMNEngine.Elements.Diagram.DefaultFont, BPMNEngine.Elements.Diagram.FONT_SIZE); | ||
while (szLabel.Width > VARIABLE_NAME_WIDTH) | ||
{ | ||
if (label.EndsWith("...")) | ||
label = string.Concat(label.AsSpan(0, label.Length - 4), "..."); | ||
else | ||
label = string.Concat(label.AsSpan(0, label.Length - 1), "..."); | ||
szLabel = canvas.GetStringSize(label, BPMNEngine.Elements.Diagram.DefaultFont, BPMNEngine.Elements.Diagram.FONT_SIZE); | ||
} | ||
StringBuilder val = new(); | ||
if (state[null, key] != null) | ||
{ | ||
if (state[null, key].GetType().IsArray) | ||
{ | ||
((IEnumerable)state[null, key]).Cast<object>().ForEach(o => val.AppendFormat("{0},", o)); | ||
val.Length--; | ||
} | ||
else if (state[null, key] is Hashtable hashtable) | ||
{ | ||
val.Append('{'); | ||
hashtable.Keys.Cast<string>().ForEach(k => val.Append($"{{\"{k}\":\"{hashtable[k]}\"}},")); | ||
val.Length--; | ||
val.Append('}'); | ||
} | ||
else | ||
val.Append(state[null, key].ToString()); | ||
} | ||
var sval = val.ToString(); | ||
Size szValue = canvas.GetStringSize(sval, BPMNEngine.Elements.Diagram.DefaultFont, BPMNEngine.Elements.Diagram.FONT_SIZE); | ||
if (szValue.Width > VARIABLE_VALUE_WIDTH) | ||
{ | ||
if (sval.EndsWith("...")) | ||
sval = string.Concat(sval.AsSpan(0, sval.Length - 4), "..."); | ||
else | ||
sval = string.Concat(sval.AsSpan(0, sval.Length - 1), "..."); | ||
canvas.GetStringSize(sval, BPMNEngine.Elements.Diagram.DefaultFont, BPMNEngine.Elements.Diagram.FONT_SIZE); | ||
} | ||
surface.DrawString(label, 2, curY, HorizontalAlignment.Left); | ||
surface.DrawString(sval, 2+VARIABLE_NAME_WIDTH, curY, HorizontalAlignment.Left); | ||
curY += (float)Math.Max(szLabel.Height, szValue.Height) + 2; | ||
surface.DrawLine(new Point(0, curY), new Point(VARIABLE_IMAGE_WIDTH, curY)); | ||
}); | ||
return image.Image; | ||
} | ||
|
||
private static IImage AppendVariables(IImage diagram, ProcessState state) | ||
{ | ||
var vmap = BusinessProcess.ProduceVariablesImage(state); | ||
var ret = BPMNEngine.Elements.Diagram.ProduceImage( | ||
(int)Math.Ceiling(diagram.Width + DEFAULT_PADDING + vmap.Width), | ||
(int)Math.Ceiling(Math.Max(diagram.Height, vmap.Height + DEFAULT_PADDING)) | ||
); | ||
var surface = ret.Canvas; | ||
surface.FillColor = Colors.White; | ||
surface.FillRectangle(0, 0, ret.Width, ret.Height); | ||
surface.DrawImage(diagram, 0, 0, diagram.Width, diagram.Height); | ||
surface.DrawImage(vmap, diagram.Width + DEFAULT_PADDING, DEFAULT_PADDING, vmap.Width, vmap.Height); | ||
return ret.Image; | ||
} | ||
|
||
internal byte[] Animate(bool outputVariables, ProcessState state) | ||
{ | ||
WriteLogLine((IElement)null, LogLevel.Information, new StackFrame(1, true), DateTime.Now, string.Format("Rendering Business Process Animation{0}", new object[] { (outputVariables ? " with variables" : " without variables") })); | ||
var result = Array.Empty<byte>(); | ||
try | ||
{ | ||
state.Path.StartAnimation(); | ||
IImage bd = Diagram(false, state); | ||
#pragma warning disable IDE0270 // Use coalesce expression | ||
if (bd==null) | ||
throw new DiagramException("Unable to create first diagram frame"); | ||
#pragma warning restore IDE0270 // Use coalesce expression | ||
AnimatedPNG apng = new((outputVariables ? BusinessProcess.AppendVariables(bd, state) : bd)) | ||
{ | ||
DefaultFrameDelay= ANIMATION_DELAY | ||
}; | ||
while (state.Path.HasNext()) | ||
{ | ||
string nxtStep = state.Path.MoveToNextStep(); | ||
if (nxtStep != null) | ||
{ | ||
double padding = DEFAULT_PADDING / 2; | ||
if (definition!=null) | ||
{ | ||
var diagram = definition.Diagrams.FirstOrDefault(d => d.RendersElement(nxtStep)); | ||
if (diagram!=null) | ||
{ | ||
var rect = diagram.GetElementRectangle(nxtStep); | ||
IImage img = diagram.Render(state.Path, definition, nxtStep); | ||
apng.AddFrame(img, (int)Math.Ceiling((DEFAULT_PADDING / 2)+rect.X)+3, (int)Math.Ceiling(padding+rect.Y)+3); | ||
} | ||
} | ||
if (outputVariables) | ||
apng.AddFrame(BusinessProcess.ProduceVariablesImage(state), (int)Math.Ceiling(bd.Width + DEFAULT_PADDING), (int)DEFAULT_PADDING, delay: new TimeSpan(0, 0, 0)); | ||
} | ||
} | ||
state.Path.FinishAnimation(); | ||
result = apng.ToBinary(); | ||
apng.Dispose(); | ||
} | ||
catch (Exception e) | ||
{ | ||
WriteLogException((IElement)null, new StackFrame(1, true), DateTime.Now, e); | ||
result=null; | ||
} | ||
return result; | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using BPMNEngine.Interfaces.Elements; | ||
|
||
namespace BPMNEngine | ||
{ | ||
public sealed partial class BusinessProcess | ||
{ | ||
internal void WriteLogLine(string elementID, LogLevel level, StackFrame sf, DateTime timestamp, string message) | ||
=> WriteLogLine((IElement)(elementID == null ? null : GetElement(elementID)), level, sf, timestamp, message); | ||
|
||
internal void WriteLogLine(IElement element, LogLevel level, StackFrame sf, DateTime timestamp, string message) | ||
=> delegates.Logging.LogLine?.Invoke(element, sf.GetMethod().DeclaringType.Assembly.GetName(), sf.GetFileName(), sf.GetFileLineNumber(), level, timestamp, message); | ||
|
||
internal Exception WriteLogException(string elementID, StackFrame sf, DateTime timestamp, Exception exception) | ||
=> WriteLogException((IElement)(elementID == null ? null : GetElement(elementID)), sf, timestamp, exception); | ||
|
||
internal Exception WriteLogException(IElement element, StackFrame sf, DateTime timestamp, Exception exception) | ||
{ | ||
if (delegates.Logging.LogException != null) | ||
{ | ||
delegates.Logging.LogException.Invoke(element, sf.GetMethod().DeclaringType.Assembly.GetName(), sf.GetFileName(), sf.GetFileLineNumber(), timestamp, exception); | ||
if (exception is InvalidProcessDefinitionException processDefinitionException) | ||
{ | ||
processDefinitionException.ProcessExceptions | ||
.ForEach(e => { delegates.Logging.LogException.Invoke(element, sf.GetMethod().DeclaringType.Assembly.GetName(), sf.GetFileName(), sf.GetFileLineNumber(), timestamp, e); }); | ||
} | ||
} | ||
return exception; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using BPMNEngine.Interfaces.State; | ||
using System.Text.Json; | ||
|
||
namespace BPMNEngine | ||
{ | ||
public sealed partial class BusinessProcess | ||
{ | ||
#region Xml State | ||
private static IInternalState LoadState(XmlDocument doc,int? stepIndex=null) | ||
{ | ||
return ProcessState.LoadState(doc,stepIndex:stepIndex); | ||
} | ||
|
||
/// <summary> | ||
/// A Utility call used to extract the variable values from a Business Process State Document. | ||
/// </summary> | ||
/// <param name="doc">The State XML Document file to extract the values from</param> | ||
/// <returns>The variables extracted from the Process State Document</returns> | ||
public static Dictionary<string, object> ExtractProcessVariablesFromStateDocument(XmlDocument doc) | ||
=> LoadState(doc).Variables; | ||
|
||
/// <summary> | ||
/// A Utility call used to extract the variable values from a Business Process State Document at a given step index. | ||
/// </summary> | ||
/// <param name="doc">The State XML Document file to extract the values from</param> | ||
/// <param name="stepIndex">The step index to extract the values at</param> | ||
/// <returns>The variables extracted from the Process State Document</returns> | ||
public static Dictionary<string, object> ExtractProcessVariablesFromStateDocument(XmlDocument doc, int stepIndex) | ||
=> LoadState(doc,stepIndex:stepIndex).Variables; | ||
|
||
/// <summary> | ||
/// A Utility call used to extract the steps from a Business Process State Document | ||
/// </summary> | ||
/// <param name="doc">The State XML Document file to extract the values from</param> | ||
/// <returns>The steps from the Process State Document</returns> | ||
public static IEnumerable<IStateStep> ExtractProcessSteps(XmlDocument doc) | ||
=> LoadState(doc).Steps; | ||
|
||
/// <summary> | ||
/// A Utility call used to extract the log from a Business Process State Document | ||
/// </summary> | ||
/// <param name="doc">The State XML Document file to extract the values from</param> | ||
/// <returns>The log from the Process State Document</returns> | ||
public static string ExtractProcessLog(XmlDocument doc) | ||
=> LoadState(doc).Log; | ||
|
||
#endregion | ||
|
||
#region Json State | ||
private static IInternalState LoadState(Utf8JsonReader reader, int? stepIndex = null) | ||
{ | ||
return ProcessState.LoadState(reader, stepIndex: stepIndex); | ||
} | ||
|
||
/// <summary> | ||
/// A Utility call used to extract the variable values from a Business Process State Document. | ||
/// </summary> | ||
/// <param name="reader">The State Json Document file to extract the values from</param> | ||
/// <returns>The variables extracted from the Process State Document</returns> | ||
public static Dictionary<string, object> ExtractProcessVariablesFromStateDocument(Utf8JsonReader reader) | ||
=> LoadState(reader).Variables; | ||
|
||
/// <summary> | ||
/// A Utility call used to extract the variable values from a Business Process State Document at a given step index. | ||
/// </summary> | ||
/// <param name="reader">The State Json Document file to extract the values from</param> | ||
/// <param name="stepIndex">The step index to extract the values at</param> | ||
/// <returns>The variables extracted from the Process State Document</returns> | ||
public static Dictionary<string, object> ExtractProcessVariablesFromStateDocument(Utf8JsonReader reader, int stepIndex) | ||
=> LoadState(reader, stepIndex: stepIndex).Variables; | ||
|
||
/// <summary> | ||
/// A Utility call used to extract the steps from a Business Process State Document | ||
/// </summary> | ||
/// <param name="reader">The State Json Document file to extract the values from</param> | ||
/// <returns>The steps from the Process State Document</returns> | ||
public static IEnumerable<IStateStep> ExtractProcessSteps(Utf8JsonReader reader) | ||
=> LoadState(reader).Steps; | ||
|
||
/// <summary> | ||
/// A Utility call used to extract the log from a Business Process State Document | ||
/// </summary> | ||
/// <param name="reader">The State Json Document file to extract the values from</param> | ||
/// <returns>The log from the Process State Document</returns> | ||
public static string ExtractProcessLog(Utf8JsonReader reader) | ||
=> LoadState(reader).Log; | ||
#endregion | ||
} | ||
} |
Oops, something went wrong.