Saturday, April 17, 2010
Sunday, April 11, 2010
MDL File importer
This is the third and last post on the Rational rose file importer.
Having this Rational Rose model:
I can now get this Modlr model:
The code is in the demos folder: PluginRoseFileRead
More importantly I tested this on a model I worked on some years ago for the swedish parliament:
It took almost two minutes to import it.
Notice to round magnifier in the diagram overview – we added this to Modlr some weeks ago, looks cool.
Saturday, March 27, 2010
Declarative ViewModels and Taborder
A very good friend of mine, Lars Olofsson, complained about the tab-order between controls in the ViewModels. The project he is working on uses the ViewModels A LOT. And as the work of developing this system goes on, little changes to the ViewModels get applied and the Taborder gets messier and messier.
He suggested that the Taborder should be made visible to the eye so that anyone touching the viewmodel definition could see if it was ok or not. That was a brilliant idea – why have it not always been that way:
Rational Rose Modeler
So I had set out for doing the RationalRose file-format reader for Modlr. As I discussed earlier I was intimidated by the non-xml-file-format, and discouraged by the details in the format.
So I first made some logic that turned it into XML, then I made a plugin to Modlr that could deduce a Model from XML… So there is no escaping this I guess – I need to actually read and interpret the data…
BTW If anyone wonders why I have not done this until now it was because I was skiing – first in Sälen, Sweden – brilliant weather, ok hills, then in BadGastain, Austria – ok weather , brilliant hills!
1: RoseImport ri = new RoseImport(); // This reads the mdl and turns it into XML
2: ri.OpenFile(textBoxFileName.Text, true);
3: _es = EcoSpace;
4: _createdobjects = new Dictionary<string, IEcoObject>();
5:
6: InstansiateObjects(ri.XDocument); // This should interpret the xml content and match it with the model I have
7: foreach (XDocument xd in ri.CatFiles) // RoseImport also find CAT file references and reads those too
8: {
9: InstansiateObjects(xd);
10: }
Since I am lazy, stressed and living in Sweden, the country with the highest taxes in the world, I really need to be efficient about this…
My model was deduced from the XML so I am pretty sure that the names in the XML will be the same in my model.
Using the meta information from eco I can go like this to instantiate the correct objects:
1: IEcoObject existing = null;
2: XElement keyelement = xe.Elements("key") as XElement;
3: if (keyelement != null && keyelement.Value != null)
4: {
5: string Key = keyelement.Value;
6: if (_createdobjects.ContainsKey(keyelement.Value))
7: existing = _createdobjects[keyelement.Value];
8: }
9: if (existing == null)
10: {
11: IClass iclass = EcoServiceHelper.GetTypeSystemService(_es).TypeSystem.
12: GetClassifierByName("ro_" + xe.Name.LocalName) as IClass; // My model prefixed all rose classes with ro_
13: existing = EcoServiceHelper.GetObjectFactoryService(_es).CreateNewObject(iclass);
14: if (keyelement != null && keyelement.Value != null)
15: {
16: _createdobjects.Add(keyelement.Value, existing);
17: }
18:
19: }
Ok , moving on to the attributes:
1: foreach (XElement child in xe.Elements())
2: {
3: if (!child.HasElements)
4: {
5: IElement elem = existing.AsIObject().Properties[child.Name.LocalName];
6: if (elem != null && !(elem is IObjectList))
7: elem.SetValue<string>(child.Value);
8: }
9: else
10: {
11: // load association
12: }
13: }
And the associations:
1: // load association
2: IElement elem = existing.AsIObject().Properties[child.Name.LocalName];
3: if (elem is IObjectList)
4: {
5: foreach (XElement associationobject in child.Elements())
6: {
7: IEcoObject associaionInstansiatedObject=InstansiateObjects(associationobject);
8: if (associaionInstansiatedObject.AsIObject().UmlClass.ConformsTo((elem as IObjectList).UmlClass))
9: (elem as IObjectList).Add(associaionInstansiatedObject);
10: else
11: System.Diagnostics.Debug.WriteLine(string.Format("Tried to add a {0} to a list of {1}, association was {2}",
associaionInstansiatedObject.AsIObject().UmlClass.Name,
(elem as IObjectList).UmlClass.Name, child.Name.LocalName));
12: }
13: }
Note the last line where I check if the loaded data actually will be valid to add in this association… Since my model was deduced from XML it is probably lacking important abstractions (inheritance). And sure enough, running this on a mdl file gave me this output:
1: Tried to add a ro_Class to a list of ro_Class_Category, association was logical_models
2: Tried to add a ro_Class to a list of ro_Class_Category, association was logical_models
3: Tried to add a ro_Class to a list of ro_Class_Category, association was logical_models
4: Tried to add a ro_Class to a list of ro_Class_Category, association was logical_models
5: Tried to add a ro_Class to a list of ro_Class_Category, association was logical_models
6: Tried to add a ro_Association to a list of ro_Class_Category, association was logical_models
7: Tried to add a ro_Association to a list of ro_Class_Category, association was logical_models
8: Tried to add a ro_Association to a list of ro_Class_Category, association was logical_models
9: Tried to add a ro_Association to a list of ro_Class_Category, association was logical_models
10: Tried to add a ro_Association to a list of ro_Class_Category, association was logical_models
11: Tried to add a ro_Association to a list of ro_Class_Category, association was logical_models
12: Tried to add a ro_Association to a list of ro_Class_Category, association was logical_models
13: Tried to add a ro_Association to a list of ro_Class_Category, association was logical_models
14: Tried to add a ro_Association to a list of ro_Class_Category, association was logical_models
15: Tried to add a ro_ClassDiagram to a list of ro_UseCaseDiagram, association was logical_presentations
This leads me to believe that the association logical_models really should point to some abstraction rather than to the ro_Class_Category itself… Keep in mind that I know nothing about what all this mean yet, I am just as ignorant to stuff maintained by IBM as IBM is of me (wow that is a lot).
So I change the model by introducing a superclass called LogicalModel (made sense since the association was called that):
BTW: I moved around the attributes (removing them from the subclasses to the superclass) with the newly implemented copy-paste function of the Modlr-tree (Yippy!).
I found some more missing abstractions that I added. Finally the output list was empty. No more errors.
At this point I need to actually understand what it is I am doing – after all I am supposed to understand the content of the rose file in order to turn the artifacts within into modlr objects (defined by, what we call, extendedmodellayer).
No better way than to use the EcoSpaceDebugger and autoforms. Load my sample mdl again:
There is a ro_ClassDiagram that looks interesting:
…it has a multi link called Items that contain ro_ClassView and ro_AssociationView…
Looking closer in the ro_ClassView – I guessing that this is what ModelLayer calls PlacedClass:
…I wonder how if finds the ro_class… I am guessing it uses the quidu reference…
Using the ecospace debugger I wrote this ocl: ro_Class.allinstances->select(quid='"3BDC6B1A02D7"') and sure enough:
So I will amend the model to keep this knowledge in a derived association.
The Derivation expression is “ro_Class.allinstances->select(quid=self.quidu)->first”
Not only does the derived association help me when looking in autoforms and writing code; it also ensures that things stay optimized. A derived association is subscribed to by the eco-runtime, and the value is cached until it changes. And when it changes it is merely marked as out of date, so it is only updated if someone tries to read it again.
It is easy to imagine that I would write code and look up the ro_Class from the ro_ClassView.quidu whenever I needed it; but the derived association removes the need for repetitive, error prone, performance hurting, hard to find code and brings it up in the light, as a first class member of the ro_ClassView ready to serve whenever I need it – available in the model for everyone to see. How beautiful is that?
So I am not going to bore you with all the details – I will continue to work on this – the main purpose of this post was to explain how I use eco to move away from time consuming hand coding techniques, towards building and using tools to get the job done.
Get out of the middle ages – become model driven.
Sunday, February 21, 2010
Strongly typed thank you!
This weekend I set out for doing a Rational Rose file import plugin for Modlr. I wrote the code that successfully traversed the rather strange file format that they use. Rational Rose is a pre-xml product:
1:
2: (object Petal
3: version 47
4: _written "Rose 8.0.0303.1400"
5: charSet 0)
6:
7: (object Design "Logical View"
8: is_unit TRUE
9: is_loaded TRUE
10: quid "4B7FCC8D02EF"
11: defaults (object defaults
12: rightMargin 0.250000
13: leftMargin 0.250000
14: topMargin 0.250000
15: bottomMargin 0.500000
16: pageOverlap 0.250000
17: clipIconLabels TRUE
18: autoResize TRUE
19: snapToGrid TRUE
20: gridX 0
21: gridY 0
22: defaultFont (object Font
23: size 12
24: face "Arial"
25: bold FALSE
26: italics FALSE
27: underline FALSE
28: strike FALSE
29: color 0
30: default_color TRUE)
31: showMessageNum 3
32: showClassOfObject TRUE
33: notation "Unified")
34: root_usecase_package (object Class_Category "Use Case View"
35: quid "4B7FCC8D02F1"
So when I had all that done I thought that it would an easy and quick task to wrap that information into a model designed in eco so that I could populate it with objects from the file. Once that was done I planned to write the code that transformed from that rose model to the eco modellayer model…
But I immediately got super tired when I realized that I had to create some 30-40 classes with a lot of attributes and relations all by hand in order to hold the rational rose data from the file…
It seemed silly that I would need to do stuff by hand at all. After all we are the tool making species! So I started to think that this is a common scenario – I have a description, or data adhering to a description, in one format, and I want to have that as a model so I can code towards it with strongly typed classes ( the older (and wiser?) I get, I get increasingly more allergic to scripting ).
So if this is a common problem that at least I come across from time to time maybe I should solve that first?
I asked myself what format the data I need to handle usually come in : XML.
So to avoid losing focus of my initial problem all together I used my Rational Rose File decoder logic and made it spit out the same information as XML:
1: <?xml version="1.0" encoding="utf-8"?>
2: <root>
3: <Petal>
4: <name></name>
5: <key></key>
6: <version>47</version>
7: <_written>"Rose 8.0.0303.1400"</_written>
8: </Petal>
9: <Design>
10: <name>Logical View</name>
11: <key></key>
12: <is_unit>TRUE</is_unit>
13: <is_loaded>TRUE</is_loaded>
14: <quid>"4B7FCC8D02EF"</quid>
15: <defaults>
16: <defaults>
17: <name></name>
18: <key></key>
19: <rightMargin>0.250000</rightMargin>
20: <leftMargin>0.250000</leftMargin>
21: <topMargin>0.250000</topMargin>
22: <bottomMargin>0.500000</bottomMargin>
23: <pageOverlap>0.250000</pageOverlap>
24: <clipIconLabels>TRUE</clipIconLabels>
25: <autoResize>TRUE</autoResize>
26: <snapToGrid>TRUE</snapToGrid>
27: <gridX>0</gridX>
28: <gridY>0</gridY>
29: <defaultFont>
30: <Font>
31: <name></name>
The general idea was to write a plugin that given this XML, or a similar XML, or preferably any XML, could derive a model for me, saving me from doing stuff manually.
I know that some of you are thinking “oh boy this guy is writing a plugin in order to write a plugin in order to convert a rose file?”, and yes you are correct. But one plugin will be very generic – taking almost any XML and turning it into a modlr model.
So nothing fancy:
1: XDocument xd = XDocument.Load(file);
2: foreach (XElement xe in xd.Root.Elements())
3: {
4: Analyze(xe);
5: }
And the magic of the plugin:
1: private Eco.ModelLayer.Class Analyze(XElement xe)
2: {
3: Eco.ModelLayer.Class c = EnsureClass(GetNameFromXE(xe));
4: foreach (XAttribute xa in xe.Attributes())
5: {
6: EnsureAttribute(c, xa.Name.ToString());
7: }
8: foreach (XElement subxe in xe.Elements())
9: {
10:
11: if (subxe.FirstNode is XText || subxe.FirstNode==null)
12: {
13: // treat as model attribute
14: EnsureAttribute(c, GetNameFromXE(subxe));
15: }
16: else
17: {
18: // treat as model association
19: /*
20: We treat to types of linking:
21: single link may look like this:
22: obj1
23: attr1
24: link
25: attr1
26:
27: * multilink often looks like this:
28: obj1
29: attr1
30: link
31: obj2
32: attr1
33: */
34: if (subxe.Elements().Count() > 0 &&
(subxe.Elements().First().FirstNode is XText || subxe.Elements().First().FirstNode == null))
35: {
36: Eco.ModelLayer.Class targetclass = Analyze(subxe);
37: AssociationEnd ae = EnsureAssociation(c, targetclass, GetNameFromXE(subxe));
38: if (ae!=null)
39: ae.Multiplicity = "0..1";
40: }
41: else
42: {
43: List<Eco.ModelLayer.Class> classesfoundinrelation = new List<Eco.ModelLayer.Class>();
44: foreach (XElement xeObjectInAssoc in subxe.Elements())
45: {
46: Eco.ModelLayer.Class targetclass = Analyze(xeObjectInAssoc);
47: if (classesfoundinrelation.IndexOf(targetclass) == -1)
48: classesfoundinrelation.Add(targetclass);
49: }
50:
51: if (classesfoundinrelation.Count > 0)
52: {
53: // if classesfoundinrelation.Count>1 the association is most likely
pointing to a super class of the classes found, but we dont have enough info - pick first
54: AssociationEnd ae = EnsureAssociation(c, classesfoundinrelation[0], GetNameFromXE(subxe));
55: if (ae!=null)
56: ae.Multiplicity = "0..*";
57: }
58: }
59: }
60: }
61: return c;
62: }
63:
The logic to actually create a Class, an Attribute or a Relation looks like this:
1: private AssociationEnd EnsureAssociation(Eco.ModelLayer.Class cfrom, Eco.ModelLayer.Class cto, string xName)
2: {
3:
4: if (_esp != null)
5: {
6: var res = (from x in cfrom.AssociationEnd where x.OtherEnd != null && x.OtherEnd.Name == xName select x);
7: if (res.Count() == 0)
8: {
9: AssociationEnd ae1=new AssociationEnd(_esp);
10: AssociationEnd ae2 = new AssociationEnd(_esp);
11: Association a = new Association(_esp);
12: a.AssociationEnd.Add(ae1);
13: a.AssociationEnd.Add(ae2);
14: a.Package_ = _package;
15: ae2.Name = xName;
16: ae2.IsNavigable = true;
17: cfrom.AssociationEnd.Add(ae1);
18: cto.AssociationEnd.Add(ae2);
19: return ae2;
20: }
21: return res.First().OtherEnd;
22: }
23: return null;
24: }
25:
26: private void EnsureAttribute(Eco.ModelLayer.Class c, string xName)
27: {
28: if (_esp != null)
29: {
30: foreach (ModelElement me in c.AllFeatures)
31: {
32: if (me is Eco.ModelLayer.Attribute)
33: {
34: if (xName == (me as Eco.ModelLayer.Attribute).Name)
35: {
36: return;
37: }
38: }
39: }
40: // not found
41:
42: Eco.ModelLayer.Attribute a=new Eco.ModelLayer.Attribute(_esp);
43: a.Name = xName;
44: if (_stringDataType==null)
45: _stringDataType=(from x in _esp.GetEcoService<IExtentService>().AllInstances<Datatype>()
where x.Name.ToLower()=="string" select x).First<Datatype>();
46: a.Type = _stringDataType;
47: c.Feature.Add(a);
48:
49: }
50: }
51:
52: private Eco.ModelLayer.Class EnsureClass(string xName)
53: {
54: if (_esp != null)
55: {
56: string completename = _prefix + xName;
57: var result = (from x in _esp.GetEcoService<IExtentService>().AllInstances<Eco.ModelLayer.Class>()
58: where (x.Name == completename && x.OwningPackage == _package) select x).ToArray<Eco.ModelLayer.Class>();
59: if (result.Count() == 0)
60: {
61: Eco.ModelLayer.Class c = new Eco.ModelLayer.Class(_esp);
62: c.Name = completename;
63: c.Package_=_package;
64: return c;
65: }
66: else
67: return result[0];
68: }
69: return null;
70: }
71: }
I tried the logic on several different xml files that I found in my temp folder and found some special cases. This lead me to the conclusion that I needed at least two different strategies: one where the xml element has a generic name and the real name is given in some attribute. One example of this is the EcoMdl file format:
1: <?xml version="1.0" encoding="utf-8"?>
2: <contents>
3: <object type="Diagram" href="ClassDiagram_Diagram1">
4: <doc><![CDATA[]]></doc>
5: <modlrdiagram>diagramimages/efcd3388-264f-469f-b6e2-5e0a1dc68839.jpg</modlrdiagram>
6: <attribute name="ColorOnNew">0</attribute>
7: <attribute name="Id">efcd3388-264f-469f-b6e2-5e0a1dc68839</attribute>
8: <attribute name="SnapGridSize">15</attribute>
9: <attribute name="PresentationName">Diagram1</attribute>
10: <attribute name="ShowMethodSignatures">False</attribute>
11: <attribute name="ShowAssociationNames">DimDefaults</attribute>
12: <attribute name="ShowVisibility">False</attribute>
13: <attribute name="SquareNewAssociations">False</attribute>
14: <attribute name="SquareNewGeneralizations">False</attribute>
15: <attribute name="ShowMethodReturnTypes">False</attribute>
16: <attribute name="Name">Diagram1</attribute>
17: <link name="PlacedClass">
18: <object type="PlacedClass" href="Class1">
19: <attribute name="RenderedWidth">200,8</attribute>
20: <attribute name="id">0aac4fb8-5dd6-4082-b16b-73c07565fc57</attribute>
21: <attribute name="Color">1692800256</attribute>
22: <attribute name="Size">1</attribute>
23: <attribute name="LastKnownName">Class1</attribute>
24: <attribute name="RenderedHeight">49,25</attribute>
But the more common scenario is the format where the xml elements have specific names. Like the pad format:
1: <?xml version="1.0" encoding="UTF-8"?>
2: <XML_DIZ_INFO>
3: <MASTER_PAD_VERSION_INFO>
4: <MASTER_PAD_VERSION>3.01</MASTER_PAD_VERSION>
5: <MASTER_PAD_EDITOR>PADManager 2.0.46</MASTER_PAD_EDITOR>
6: <MASTER_PAD_INFO>Portable Application Description, or PAD for short, is a data set that is used by shareware authors to disseminate information to anyone interested in their software products. To find out more go to http://www.asp-shareware.org/pad</MASTER_PAD_INFO>
7: </MASTER_PAD_VERSION_INFO>
8: <RoboSoft>
9: <Company_UIN>CMP-009701CA593</Company_UIN>
10: <Company_Description></Company_Description>
11: <Product_UIN>APP-009701FB886</Product_UIN>
12: <Publish_on_CD>FALSE</Publish_on_CD>
13: <Search_String></Search_String>
14: <Press_Release_Search_String></Press_Release_Search_String>
15: <NewsFeed_Search_String></NewsFeed_Search_String>
16: <Search_Engine_Search_String></Search_Engine_Search_String>
17: <Web_Directories_Search_String></Web_Directories_Search_String>
18: <Search_String_Unique></Search_String_Unique>
19: <Comments_For_Reviewer></Comments_For_Reviewer>
20: <Additional_Categories></Additional_Categories>
21: <Backlink></Backlink>
22: </RoboSoft>
23: <Company_Info>
24: <Company_Name>CapableObjects.com</Company_Name>
25: <Address_1>Box 1200</Address_1>
26: <Address_2>Huddinge</Address_2>
27: <City_Town>Stockholm</City_Town>
I added a window that pops up when executing the plugin, it lets you pick the xml file, choose strategy, choose target package and GO:
And it gave me this (I dragged them onto the diagram myself)
Puh, now when I have a good starting point for the rational rose object model I can back to my original task… But that will be some other day…
(the plugin is in the demos folder for the next available eco build and it is called PluginXmlToModel)
