IronRuby实现对Ruby类相关信息的树结构显示(C#)

一、前言

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文件进行加载,然后动态获取文件中包含的类、方法与方法参数列表。

具体代码如下:

  1     public static class RubyScriptEngine
  2     {
  3         private static readonly ScriptEngine engine = null;
  4 
  5         static RubyScriptEngine()
  6         {
  7             engine = Ruby.CreateEngine();
  8         }
  9 
 10         public static bool InitRelativeFiles(string directory)
 11         {
 12             if (!Directory.Exists(directory))
 13             {
 14                 return false;
 15             }
 16             
 17             string[] files = Directory.GetFiles(directory);
 18             for (int index = 0; index < files.Length; index++)
 19             {
 20                 InitRelativeFile(files[index]);
 21             }
 22 
 23             return true;
 24         }
 25 
 26         public static bool InitRelativeFile(string fileName) 
 27         {
 28             if (!File.Exists(fileName))
 29             {
 30                 return false;
 31             }
 32 
 33             try
 34             {
 35                 FileInfo fileInfo = new FileInfo(fileName);
 36                 if (string.Equals(fileInfo.Extension, ".rb", StringComparison.CurrentCultureIgnoreCase))
 37                 {
 38                     engine.ExecuteFile(fileName);
 39                 }
 40             }
 41             catch
 42             {
 43                 return false;
 44             }
 45            
 46             return true;
 47         }
 48 
 49         public static IList<string> GetClassNames()
 50         {
 51            return  engine.Runtime.Globals.GetVariableNames().ToList();
 52         }
 53 
 54         public static IList<ClassItem> GetClassesInfos()
 55         {
 56             IList<string> names = GetClassNames();
 57             IList<ClassItem> items = new List<ClassItem>();
 58             foreach (string name in names)
 59             {
 60                 items.Add(GetClassInfo(name));
 61             }
 62 
 63             return items;
 64         }
 65 
 66         public static ClassItem GetClassInfo(string className, params object[] parameters) 
 67         {
 68             RubyClass rubyClass = engine.Runtime.Globals.GetVariable(className);
 69             dynamic instance = engine.Operations.CreateInstance(rubyClass, parameters);
 70             ClassItem classItem = new ClassItem(className);
 71 
 72             IList<string> memberNames = engine.Operations.GetMemberNames(instance);
 73 
 74             MethodItem methodItem = null;
 75             ParameterItem parameterItem = null;  
 76             foreach (string memberName in memberNames)
 77             {
 78                 RubyMethodInfo methodInfo = rubyClass.GetMethod(memberName) as RubyMethodInfo;
 79 
 80                 if (methodInfo == null)
 81                 {
 82                     continue;
 83                 }
 84 
 85                 methodItem = new MethodItem(memberName,className);
 86 
 87                 RubyArray parameterArray = methodInfo.GetRubyParameterArray();
 88                 SimpleAssignmentExpression[] expressions = methodInfo.Parameters.Optional;  
 89 
 90                 for (int index = 0; index < parameterArray.Count; index++)
 91                 {           
 92                     RubyArray vas = parameterArray[index] as RubyArray;
 93                     string type = vas[0].ToString();
 94                     string name = vas[1].ToString();
 95                     parameterItem = new ParameterItem(name);
 96                     if (type == "rest")
 97                     {
 98                         parameterItem.DefaultName = "*" + name;
 99                         parameterItem.Description = RubyResource.ArrayParamDesc;
100                     }
101                     else if (type == "opt")
102                     {
103                         for (int eindex = 0; eindex < expressions.Length; eindex++)
104                         {
105                             SimpleAssignmentExpression ex = expressions[eindex];
106                             Variable variable = ex.Left as Variable;
107                             if (!string.Equals(variable.Name, name))
108                             {
109                                 continue;
110                             }
111 
112                             Literal literal = ex.Right as Literal;           
113                             parameterItem.DefaultName = name;
114                             parameterItem.DefaultValue = literal.Value;
115                             parameterItem.Description = RubyResource.DefaultParamDesc;
116                         }
117                     }
118                     else if (type == "block")
119                     {
120                         parameterItem.DefaultName = "&" + name;
121                         parameterItem.Description = RubyResource.BlockParamDesc;
122                     }
123                     else
124                     {
125                         parameterItem.DefaultName = name;
126                     }
127                     methodItem.Parameters.Add(parameterItem);        
128                 }
129                 classItem.Methods.Add(methodItem);
130             }
131 
132             return classItem;
133         }
134     }

其中相关方法如下:

public static bool InitRelativeFiles(string directory)  根据目录加载目录下的Ruby文件

public static bool InitRelativeFile(string fileName)    根据文件名加载该Ruby文件

public static IList<string> GetClassNames()            获取所有的类名

public static IList<ClassItem> GetClassesInfos()     获取所有类的信息

public static ClassItem GetClassInfo(string className, params object[] parameters)    根据类名和类的构造函数参数获取对应的类信息

 

类的信息显示效果如下(左侧显示类的信息,右侧编辑器显示类的基本结构):

 

(2)TreeDrawer主要用于绘制类的树结构,根据不同的类结构显示不同的效果。这里是用Winform来显示的,本来打算用Silverlight来实现,但是由于时间关系,将就着这样算了。当然,Silverlight显示的效果比Winform强多了,而且,本人Silverlight水平比Winform熟练很多(以前项目中用Silverlight动态绘制相关图形,因此比较熟悉)....

TreeDrawer的主要方法为以下2个:

 1         public Bitmap CreateImage(ClassItem classItem, Font font)
 2         {
 3             if (classItem == null || font == null)
 4             {
 5                 return null;
 6             }
 7 
 8             ClassBlock classBlock = CreateCurrentClassBlock(classItem);
 9             AddLinesAndBlockTexts(classBlock, font);
10 
11             Bitmap bitmap = new Bitmap(3 * BLOCK_WIDTH + 2 * BLOCK_INNER_WIDTH, this.Height);
12             Graphics graphics = Graphics.FromImage(bitmap);
13             graphics.Clear(Color.White);
14             Pen ellipsePen = new Pen(Color.Blue, 2);
15 
16             foreach (Line line in this.Lines)
17             {
18                 graphics.DrawLine(ellipsePen, line.X1, line.Y1, line.X2, line.Y2);
19                 if (line.HasArrow)
20                 {
21                     PointF[] points = CreateArrowPoints(new PointF(line.X1, line.Y1),
22                         new PointF(line.X2, line.Y2), ARROW_LENGTH, RELATIVE_VALUE);
23                     DrawArrowHead(graphics, points);
24                 }
25             }
26 
27             foreach (BlockText content in this.Contents)
28             {
29                 graphics.DrawString(content.Content, font, Brushes.Black, content.StartX, content.StartY);
30             }
31 
32             return bitmap;
33         }
34 
35         private ClassBlock CreateCurrentClassBlock(ClassItem classItem)
36         {
37             int originalParamY = 0;
38             int lastParamY = 0;
39             int currentParamY = 0;
40             int currentMethodY = 0;
41             int startMethodX = BLOCK_WIDTH + BLOCK_INNER_WIDTH;         
42             int startParamX = 2 * BLOCK_WIDTH + 2 * BLOCK_INNER_WIDTH;
43 
44             List<MethodBlock> methodBlocks = new List<MethodBlock>();
45             foreach (MethodItem methodItem in classItem.Methods)
46             {
47                 int paramsCount = methodItem.Parameters.Count;
48                 if (paramsCount > 0)
49                 {
50                     lastParamY += paramsCount * (BLOCK_HEIGHT + BLOCK_INNER_HEGIHT);
51                 }
52                 else
53                 {
54                     lastParamY += (BLOCK_HEIGHT + BLOCK_INNER_HEGIHT);
55                     currentParamY += (BLOCK_HEIGHT + BLOCK_INNER_HEGIHT);
56                 }
57 
58                 currentMethodY = ((lastParamY - BLOCK_INNER_HEGIHT - originalParamY) / 2 + originalParamY - (BLOCK_HEIGHT / 2));
59                 originalParamY = lastParamY;
60 
61                 MethodBlock methodBlock = new MethodBlock(new Point(startMethodX, currentMethodY), BLOCK_WIDTH, BLOCK_HEIGHT);
62                 methodBlock.Content = methodItem.Name;
63                 methodBlocks.Add(methodBlock);
64                 foreach (ParameterItem parameterItem in methodItem.Parameters)
65                 {
66                     ParameterBlock parameterBlock = new ParameterBlock(new Point(startParamX, currentParamY), BLOCK_WIDTH, BLOCK_HEIGHT);
67                     parameterBlock.Content = parameterItem.DefaultName;
68                     methodBlock.ParameterBlocks.Add(parameterBlock);
69                     currentParamY += (BLOCK_HEIGHT + BLOCK_INNER_HEGIHT);
70                 }
71             }
72 
73             if (lastParamY > 0)
74             {
75                 lastParamY -= BLOCK_INNER_HEGIHT;
76             }
77             else
78             {
79                 lastParamY = BLOCK_HEIGHT;
80             }
81 
82             this.Height = lastParamY;
83 
84             Point classStartPoint = new Point(00);
85             if (classItem.Methods.Count > 1)
86             {
87                 int y = (methodBlocks.Last().LeftBottomPoint.Y - methodBlocks.First().LeftBottomPoint.Y) / 2;
88                 classStartPoint = new Point(0, y);
89             }
90 
91             ClassBlock classBlock = new ClassBlock(classStartPoint, BLOCK_WIDTH, BLOCK_HEIGHT);
92             classBlock.Content = classItem.Name;
93             foreach (MethodBlock methodBlock in methodBlocks)
94             {
95                 classBlock.MethodBlocks.Add(methodBlock);
96             }
97 
98             return classBlock;
99         }

 

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类树结构源码

 

posted @ 2011-10-24 23:00  jasen.kin  阅读(2402)  评论(8编辑  收藏  举报