一、前言
IronRuby是.NET下的一個Ruby實現,此外還有Ruby.net這一開源項目,二者的主要區別是IronRuby利用了Microsoft最新推出的DLR,而Ruby.net則是完全利用原有的CLR實現的。IronRuby入門可參閱http://msdn.microsoft.com/zh-cn/magazine/dd434651.aspx。關於IronRuby的一些基本操作,本文不會涉及,本文僅僅是IronRuby對Ruby操作的一個具體實例。其中包括對所有Ruby類的類名,方法名以及參數列表的獲取與顯示相關的樹結構。究其原因采用IronRuby來進行操作,主要是因為通過Ruby的反射可以獲取到Ruby方法名列表,但是獲取不到方法的參數列表與參數名稱。此文僅供參考,因為本人也對IronRuby接觸不是很久,基本上是摸索出來的,難免會有錯誤的地方。
二、類圖設計
相關類圖設計如下,其中RubyScriptEngine主要負責通過IronRuby來獲取和構造相關的類名、方法名與參數列表以及之間的相關關系。TreeDrawer主要負責設計類名、方法名與參數列表相對應的樹形結構圖。
三、詳細設計
(1)RubyScriptEngine主要負責通過IronRuby來獲取和構造相關的類名、方法名與參數列表以及之間的相關關系。RubyScriptEngine將Ruby文件進行加載,然後動態獲取文件中包含的類、方法與方法參數列表。
具體代碼如下:
public static class RubyScriptEngine { private static readonly ScriptEngine engine = null; static RubyScriptEngine() { engine = Ruby.CreateEngine(); } public static bool InitRelativeFiles(string directory) { if (!Directory.Exists(directory)) { return false; } string[] files = Directory.GetFiles(directory); for (int index = 0; index < files.Length; index++) { InitRelativeFile(files[index]); } return true; } public static bool InitRelativeFile(string fileName) { if (!File.Exists(fileName)) { return false; } try { FileInfo fileInfo = new FileInfo(fileName); if (string.Equals(fileInfo.Extension, ".rb", StringComparison.CurrentCultureIgnoreCase)) { engine.ExecuteFile(fileName); } } catch { return false; } return true; } public static IList<string> GetClassNames() { return engine.Runtime.Globals.GetVariableNames().ToList(); } public static IList<ClassItem> GetClassesInfos() { IList<string> names = GetClassNames(); IList<ClassItem> items = new List<ClassItem>(); foreach (string name in names) { items.Add(GetClassInfo(name)); } return items; } public static ClassItem GetClassInfo(string className, params object[] parameters) { RubyClass rubyClass = engine.Runtime.Globals.GetVariable(className); dynamic instance = engine.Operations.CreateInstance(rubyClass, parameters); ClassItem classItem = new ClassItem(className); IList<string> memberNames = engine.Operations.GetMemberNames(instance); MethodItem methodItem = null; ParameterItem parameterItem = null; foreach (string memberName in memberNames) { RubyMethodInfo methodInfo = rubyClass.GetMethod(memberName) as RubyMethodInfo; if (methodInfo == null) { continue; } methodItem = new MethodItem(memberName,className); RubyArray parameterArray = methodInfo.GetRubyParameterArray(); SimpleAssignmentExpression[] expressions = methodInfo.Parameters.Optional; for (int index = 0; index < parameterArray.Count; index++) { RubyArray vas = parameterArray[index] as RubyArray; string type = vas[0].ToString(); string name = vas[1].ToString(); parameterItem = new ParameterItem(name); if (type == "rest") { parameterItem.DefaultName = "*" + name; parameterItem.Description = RubyResource.ArrayParamDesc; } else if (type == "opt") { for (int eindex = 0; eindex < expressions.Length; eindex++) { SimpleAssignmentExpression ex = expressions[eindex]; Variable variable = ex.Left as Variable; if (!string.Equals(variable.Name, name)) { continue; } Literal literal = ex.Right as Literal; parameterItem.DefaultName = name; parameterItem.DefaultValue = literal.Value; parameterItem.Description = RubyResource.DefaultParamDesc; } } else if (type == "block") { parameterItem.DefaultName = "&" + name; parameterItem.Description = RubyResource.BlockParamDesc; } else { parameterItem.DefaultName = name; } methodItem.Parameters.Add(parameterItem); } classItem.Methods.Add(methodItem); } return classItem; } }
其中相關方法如下:
publicstaticbool InitRelativeFiles(string directory) 根據目錄加載目錄下的Ruby文件
publicstaticbool InitRelativeFile(string fileName) 根據文件名加載該Ruby文件
publicstatic IList<string> GetClassNames() 獲取所有的類名
publicstatic IList<ClassItem> GetClassesInfos() 獲取所有類的信息
publicstatic ClassItem GetClassInfo(string className, paramsobject[] parameters) 根據類名和類的構造函數參數獲取對應的類信息
類的信息顯示效果如下(左側顯示類的信息,右側編輯器顯示類的基本結構):
(2)TreeDrawer主要用於繪制類的樹結構,根據不同的類結構顯示不同的效果。這裡是用Winform來顯示的,本來打算用Silverlight來實現,但是由於時間關系,將就著這樣算了。當然,Silverlight顯示的效果比Winform強多了,而且,本人Silverlight水平比Winform熟練很多(以前項目中用Silverlight動態繪制相關圖形,因此比較熟悉)....
TreeDrawer的主要方法為以下2個:
public Bitmap CreateImage(ClassItem classItem, Font font) { if (classItem == null || font == null) { return null; } ClassBlock classBlock = CreateCurrentClassBlock(classItem); AddLinesAndBlockTexts(classBlock, font); Bitmap bitmap = new Bitmap(3 * BLOCK_WIDTH + 2 * BLOCK_INNER_WIDTH, this.Height); Graphics graphics = Graphics.FromImage(bitmap); graphics.Clear(Color.White); Pen ellipsePen = new Pen(Color.Blue, 2); foreach (Line line in this.Lines) { graphics.DrawLine(ellipsePen, line.X1, line.Y1, line.X2, line.Y2); if (line.HasArrow) { PointF[] points = CreateArrowPoints(new PointF(line.X1, line.Y1), new PointF(line.X2, line.Y2), ARROW_LENGTH, RELATIVE_VALUE); DrawArrowHead(graphics, points); } } foreach (BlockText content in this.Contents) { graphics.DrawString(content.Content, font, Brushes.Black, content.StartX, content.StartY); } return bitmap; } private ClassBlock CreateCurrentClassBlock(ClassItem classItem) { int originalParamY = 0; int lastParamY = 0; int currentParamY = 0; int currentMethodY = 0; int startMethodX = BLOCK_WIDTH + BLOCK_INNER_WIDTH; int startParamX = 2 * BLOCK_WIDTH + 2 * BLOCK_INNER_WIDTH; List<MethodBlock> methodBlocks = new List<MethodBlock>(); foreach (MethodItem methodItem in classItem.Methods) { int paramsCount = methodItem.Parameters.Count; if (paramsCount > 0) { lastParamY += paramsCount * (BLOCK_HEIGHT + BLOCK_INNER_HEGIHT); } else { lastParamY += (BLOCK_HEIGHT + BLOCK_INNER_HEGIHT); currentParamY += (BLOCK_HEIGHT + BLOCK_INNER_HEGIHT); } currentMethodY = ((lastParamY - BLOCK_INNER_HEGIHT - originalParamY) / 2 + originalParamY - (BLOCK_HEIGHT / 2)); originalParamY = lastParamY; MethodBlock methodBlock = new MethodBlock(new Point(startMethodX, currentMethodY), BLOCK_WIDTH, BLOCK_HEIGHT); methodBlock.Content = methodItem.Name; methodBlocks.Add(methodBlock); foreach (ParameterItem parameterItem in methodItem.Parameters) { ParameterBlock parameterBlock = new ParameterBlock(new Point(startParamX, currentParamY), BLOCK_WIDTH, BLOCK_HEIGHT); parameterBlock.Content = parameterItem.DefaultName; methodBlock.ParameterBlocks.Add(parameterBlock); currentParamY += (BLOCK_HEIGHT + BLOCK_INNER_HEGIHT); } } if (lastParamY > 0) { lastParamY -= BLOCK_INNER_HEGIHT; } else { lastParamY = BLOCK_HEIGHT; } this.Height = lastParamY; Point classStartPoint = new Point(0, 0); if (classItem.Methods.Count > 1) { int y = (methodBlocks.Last().LeftBottomPoint.Y - methodBlocks.First().LeftBottomPoint.Y) / 2; classStartPoint = new Point(0, y); } ClassBlock classBlock = new ClassBlock(classStartPoint, BLOCK_WIDTH, BLOCK_HEIGHT); classBlock.Content = classItem.Name; foreach (MethodBlock methodBlock in methodBlocks) { classBlock.MethodBlocks.Add(methodBlock); } return classBlock; }
public Bitmap CreateImage(ClassItem classItem, Font font) 主要負責繪制Bitmap圖片
private ClassBlock CreateCurrentClassBlock(ClassItem classItem) 主要負責將ClassItem(類信息形式)轉換成ClassBlock(坐標形式),並負責計算相應的坐標
類的樹結構顯示效果如下:
當然,還可以定義其他格式的類,顯示的效果根據類的不同繪制相應的樹結構。其中,TreeDrawer中比較簡單的算法會自動設置合理的坐標,以生成相應的樹結構坐標。本處一切以簡單進行處理,不然的話,參數設置是比較多的。
四、總結
IronRuby是.NET下的一個Ruby實現,對於實現單個類的操作來說,用.NET 4.0中的Dynamic更加方便與美觀,如調用PersonClass類的nonArgsMethod方法即可寫成如下格式:
dynamic globals= engine.Runtime.Globals; dynamic apple = globals.PersonClass.@new(); //構造實例 apple.nonArgsMethod(); //調用方法
本文中采用如下代碼進行調用,主要是為了通用性處理。通過類名稱以及構造函數的參數來動態獲取類的信息。
RubyClass rubyClass = engine.Runtime.Globals.GetVariable(className); dynamic instance = engine.Operations.CreateInstance(rubyClass, parameters); ClassItem classItem = new ClassItem(className); IList<string> memberNames = engine.Operations.GetMemberNames(instance);
源代碼下載地址:IronRuby Ruby類樹結構源碼
http://files.cnblogs.com/jasenkin/IronRuby/Jasen.IronRubyApp.rar
作者:JasenKin
出處:http://www.cnblogs.com/jasenkin/