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文件进行加载,然后动态获取文件中包含的类、方法与方法参数列表。
具体代码如下:
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个:
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(0, 0);
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 apple = globals.PersonClass.@new(); //构造实例
apple.nonArgsMethod(); //调用方法
本文中采用如下代码进行调用,主要是为了通用性处理。通过类名称以及构造函数的参数来动态获取类的信息。
dynamic instance = engine.Operations.CreateInstance(rubyClass, parameters);
ClassItem classItem = new ClassItem(className);
IList<string> memberNames = engine.Operations.GetMemberNames(instance);
源代码下载地址:IronRuby Ruby类树结构源码