Rev 2165 | Blame | Compare with Previous | Last modification | View Log | RSS feed
using System;using System.Text;using System.Globalization;using System.Collections;using System.Collections.Specialized;using System.Windows.Forms;using ReqPro40;namespace EA_ReqPro{/// <summary>/// This class is responsible for constructing a facsimile of the reqpro database, from objects given to it/// by the ReqProParser base class. It then enables the user to select particular filter settings applied/// to that data, to control a subsequent import into EA (not performed in this class or it's base class )./// </summary>public class CopyReqProDatabaseToMemory : ReqProParser{// Flag to allow a client to configure this class to allow package structure fragments to be// captured during an import, ie. package structures that are ticked in the filter form but whose// parental structur is not ticked.protected bool allowPackageStructureFragments = false;// items setup by base class calling into provideReqProDatabaseInfo()protected ReqProDB_Artifact RQ_Artifact;protected EA.Element RQ_Element;// Where in an EA database, the copy of the ReqPro database content will be writtenprotected EA.Package ea_rootPackage;// Hierarchy tracking data for the parsing of a ReqPro databaseprotected Stack rq_objs = new Stack(); // Top of stack indicates current parent packageprotected Stack ea_treePos = new Stack(); // Top of stack indicates current tree position reflecting object ordering within the parent packageprotected int lastLevel; // Allows us to determine if we have gone down/up in the hierarchyprivate ReqProObject_Dictionary guid_to_obj_dictionary;private ArrayList trace_log;// Collector for the results of the parsingprotected ReqPro_object rq_root_package;private int writeTracesModulo = 100;/// <summary>/// Constructor logic/// </summary>/// <param name="ea_repository"></param>public CopyReqProDatabaseToMemory(): base(){// figure out where in the EA database we will place the results of parsing// NOTE: This just attempts to find a default value for ea_rootPackage.// Its final value may be overriden later in the parsing process, when the// ReqProDB element is acquired (see in provideReqProDatabaseInfo() method).ea_rootPackage = EA_ProjectBrowser.get_selected_package();}#region Requirement Type methods/// <summary>/// Recursively set the requirement type enum in each requirement object read/// from the ReqPro database. The enum give fast indication of the requirement type./// If we didnt do this, each time the requirement type needs to be evaluated, a string/// compare needs to be done. Here, we do them all up-front so that down the track a simple/// integer access is all that is required./// </summary>/// <param name="rq_obj"></param>private void set_rq_req_types_in_copied_data( ReqPro_object rq_obj ){if (rq_obj.isRequirement){int i = 0;foreach (ReqPro_ReqType req_type in rq_req_types){if (rq_obj.tag.StartsWith(req_type.prefix)){rq_obj.tag_enum = i;}i++;}}foreach( ReqPro_object sub_obj in rq_obj.ReqPro_objects ){// recurseset_rq_req_types_in_copied_data(sub_obj);}}#endregion#region ReqProParser (base class) overrides/// <summary>/// This method is designed to prompt the user to select the ReqPro database file/// before opening and parsing it. Once parsed, the user is offered a chance to setup/// the filter controls./// </summary>/// <param name="ea_repository"></param>/// <returns></returns>public override bool prompt_and_parse(ReqProDB_Artifact.MODE mode, out bool cancelled){cancelled = false;guid_to_obj_dictionary = new ReqProObject_Dictionary();try{pre_parsing();if (true == base.prompt_and_parse(mode, out cancelled)){// update the reqpro root package name from the actual reqpro project that has now been opened// and parsed. This may or may not be used depending upon the higher level function being carried// out. For imports, the name is never used. For exports, it may appear as the root name in// a folder view of the reqpro database package content.rq_root_package.name = ReqProDatabase.name();// make sure all imported requirements have the correct requirement type enumeration// (converted from the string name of the tag)set_rq_req_types_in_copied_data(rq_root_package);// Do one thing for imports, and another for exports.if ( mode == ReqProDB_Artifact.MODE.DOC_MODEL|| mode == ReqProDB_Artifact.MODE.TRACEABILITY ){// bring up the import filter dialog to allow user to specify exactly what gets copiedReqProFilterForm rq_filter = new ReqProFilterForm(mode == ReqProDB_Artifact.MODE.TRACEABILITY);rq_filter.populate(rq_root_package, rq_req_types, rq_req_status_types);// Setup the filter based on the saved filter settings in the ReqProDB element (if any)if (RQ_Element != null){rq_filter.loadFilterSettings(RQ_Element.Notes);}DialogResult dlgRes = rq_filter.ShowDialog();if (dlgRes == DialogResult.OK){allowPackageStructureFragments = rq_filter.allowPackageStructureFragments;// Save filter settings to the ReqProDB element if it is availableif (RQ_Element != null){RQ_Element.Notes = rq_filter.saveFilterSettings();RQ_Element.Update();}return true;}else{cancelled = true;}}else if (mode == ReqProDB_Artifact.MODE.EXPORT){ExportForm exportForm = new ExportForm();exportForm.populate(rq_root_package, EA_TaggedValues.Read(RQ_Element, Constants.TAG_LAST_EXPORT_GUID, ""),rq_req_types, rq_req_status_types);DialogResult dlgRes = exportForm.ShowDialog();if (dlgRes == DialogResult.OK){// communicate user selections to the client class that will perform the export.string folderName;bool createFolder = exportForm.user_create_folder_selection(out folderName);provideExportDecisionsToClient(exportForm.user_selected_object(),createFolder, folderName,exportForm.user_selected_requirement_type(),exportForm.user_selected_export_extent());// NOTE: Do not close the reqpro database here. The client class will do that// after the export completes.return true;}else{cancelled = true;}}}}catch (Exception ex){Main.MessageBoxException(ex, "Exception (parse)");}return false;}/// <summary>/// Function that must be overriden in a client class designed to handle the exporting of/// requirements back to ReqPro. The function gives such a class the details of selections made/// via the export form./// </summary>/// <param name="selectedObject"></param>/// <param name="createFolder"></param>/// <param name="folderName"></param>/// <param name="requirementType"></param>/// <param name="requirementStatus"></param>protected virtual void provideExportDecisionsToClient(ReqPro_object selectedObject,bool createFolder,string folderName,string requirementType,ExportForm.ExportExtent exportExtent){}/// <summary>/// This method will be called by the base class parser when it has obtained a ReqPro/// project object. We capture that object here so we can interrogate the ReqPro database/// ourselves, if we need to. We wont do that for package/requirement reading, but we may/// do it for meta-data such as requirement types, etc./// </summary>/// <param name="reqpro_project"></param>protected override void provideReqProDatabaseInfo(ReqProDB_Artifact rq_artifact,EA.Element rq_element){RQ_Artifact = rq_artifact;RQ_Element = rq_element;// Now we have been given a ReqProDB stereotyped element, we use its parent container package// as our root package. This overrides the default setup during the class constructor.ea_rootPackage = Main.EA_Repository.GetPackageByID(RQ_Element.PackageID);}/// <summary>/// This method will be called by the base class parser whenever a package or requirement object/// is found in the ReqPro database. The method collects important information from the object/// into a structure that begins with the ea_rootPackage object. The structure is highly dynamic/// with each object able to hold a list of other objects. This naturally allows for the ReqPro/// database hierarchy to be accurately reflected. The hierarchy tracking data maintained within/// the method allows us to know what object in the structure to hang off any new object derived/// from info given to us by the base class parser (ie. what object is the parent object at the/// present time during the parsing)./// </summary>/// <param name="level"></param>/// <param name="ea_repository"></param>/// <param name="rq_project"></param>/// <param name="rq_package"></param>/// <param name="rq_requirement"></param>protected override void processObject(int level,ReqPro40.Package rq_package,ReqPro40.Requirement rq_requirement){// If we are still at the same level as the previous package, then pop the previous object// in readiness for pushing the one we are now dealing with.if (level == lastLevel){rq_objs.Pop();ea_treePos.Pop();}// but if we are beneath the previous level, pop all objects that are above us because// we no longer need them in our hierarchy reference data.else if (level < lastLevel){while (lastLevel >= level){rq_objs.Pop();ea_treePos.Pop();lastLevel--;}}// bump the tree position at this level (controls display position in the EA project browser)int treePos = (int)ea_treePos.Pop();treePos++;ea_treePos.Push(treePos);// create the new requirement or packageReqPro_object new_rq_obj = null;if (rq_requirement != null){new_rq_obj = new ReqPro_object();new_rq_obj.isRequirement = true;new_rq_obj.name = rq_requirement.Name;new_rq_obj.text = rq_requirement.Text;new_rq_obj.guid = rq_requirement.GUID;new_rq_obj.tag = rq_requirement.get_Tag(ReqPro40.enumTagFormat.eTagFormat_FullTag);guid_to_obj_dictionary.Add(rq_requirement.GUID, new_rq_obj);bool hasStatus = false;bool hasDifficulty = false;bool hasPriority = false;bool hasSource = false;bool hasSourceVersion = false;bool hasSourceSection = false;bool hasSubsystem = false;bool hasStability = false;bool hasType = false;// Acquire attributes from ReqPro.// Note the use of firstTokenOnly() on those attributes that are freeform text. Those// will most likely be stored in EA tagged values, which have a 256 char size limitation.// The intention is to limit the attribute to a single line, which hopefully should get it under the// size limit. If it does not, the addin will truncate the string and issue a warning when// it writes the tagged value.if (reqTypeHasOneOrMoreAttrs(new_rq_obj,ref hasStatus, ref hasDifficulty, ref hasPriority,ref hasSource, ref hasSourceVersion, ref hasSourceSection,ref hasSubsystem, ref hasStability, ref hasType)){if (hasStatus){new_rq_obj.status = rq_requirement.AttrValues[Constants.RP_ATTR_STATUS, ReqPro40.enumAttrValueLookups.eAttrValueLookup_Label].Text;}if (hasDifficulty){new_rq_obj.difficulty = rq_requirement.AttrValues[Constants.RP_ATTR_DIFFICULTY, ReqPro40.enumAttrValueLookups.eAttrValueLookup_Label].Text;}if (hasPriority){new_rq_obj.priority = rq_requirement.AttrValues[Constants.RP_ATTR_PRIORITY, ReqPro40.enumAttrValueLookups.eAttrValueLookup_Label].Text;}if (hasSource){new_rq_obj.source = firstTokenOnly( rq_requirement.AttrValues[Constants.RP_ATTR_SOURCE, ReqPro40.enumAttrValueLookups.eAttrValueLookup_Label].Text );}if (hasSourceVersion){new_rq_obj.sourceVersion = firstTokenOnly( rq_requirement.AttrValues[Constants.RP_ATTR_SOURCE_VERSION, ReqPro40.enumAttrValueLookups.eAttrValueLookup_Label].Text );}if (hasSourceSection){new_rq_obj.sourceSection = firstTokenOnly( rq_requirement.AttrValues[Constants.RP_ATTR_SOURCE_SECTION, ReqPro40.enumAttrValueLookups.eAttrValueLookup_Label].Text );}if (hasSubsystem){new_rq_obj.subsystem = firstTokenOnly( rq_requirement.AttrValues[Constants.RP_ATTR_SUBSYSTEM_TYPE, ReqPro40.enumAttrValueLookups.eAttrValueLookup_Label].Text );}if (hasStability){new_rq_obj.stability = firstTokenOnly( rq_requirement.AttrValues[Constants.RP_ATTR_STABILITY, ReqPro40.enumAttrValueLookups.eAttrValueLookup_Label].Text );}if (hasType){new_rq_obj.type = firstTokenOnly( rq_requirement.AttrValues[Constants.RP_ATTR_REQ_TYPE, ReqPro40.enumAttrValueLookups.eAttrValueLookup_Label].Text );}}new_rq_obj.version = rq_requirement.VersionNumber;new_rq_obj.versionDateTime = rq_requirement.VersionDateTime;new_rq_obj.iKey = rq_requirement.Key;// requirements can trace to other requirements, so we have to find those in order to re-construct// that traceability later on. Currently, we only process TracesTo relationships from ReqPro. This// seems ok because if a ReqPro user creates a TracesFrom relationship, it shows up as a TracesTo// relationship as well - it just depends on which element (src or dest) you are looking at to see// which type of relationship.int limit_numberOfTracesTo = 0;if (true == rq_requirement.get_HasTracesTo(ref limit_numberOfTracesTo)){// scan through the TracesTo relationshipsReqPro40.Relationships theseRelationships = (ReqPro40.Relationships)rq_requirement.TracesTo;int i_numberOfTracesTo;theseRelationships.MoveFirst();for (i_numberOfTracesTo = 0; i_numberOfTracesTo < limit_numberOfTracesTo; i_numberOfTracesTo++){// Obtain the traced-to requirement from the relationship, and parse itReqPro40.Relationship thisRelationship = theseRelationships.GetCurrentRelationship();ReqPro40.Requirement tracedToRequirement =thisRelationship.get_DestinationRequirement(ReqPro40.enumRequirementsWeights.eReqWeight_Heavy);if (tracedToRequirement != null){// Add the GUID of the traced-to requirement to the relevant list within the// object representing the traced-from requirement (ie. parent requirement).new_rq_obj.ReqPro_traces.Add(tracedToRequirement.GUID);}theseRelationships.MoveNext();}}}else if (rq_package != null){new_rq_obj = new ReqPro_object();new_rq_obj.isPackage = true;// Packages in ReqPro may be prefixed by a number to force ReqPro's alphanumeric sorting// algorithm to order the packages in the way the user wants, as dictated by the actual// numbers used. EA does not have this problem because it uses a tree position number to// control a package/element's position in the project browser. So, strip off any leading// numeric from the ReqPro packages.string trimstring = " 0123456789";char[] trimmer = trimstring.ToCharArray();string filtered_name = rq_package.Name.TrimStart(trimmer);new_rq_obj.name = filtered_name;new_rq_obj.guid = rq_package.GUID;new_rq_obj.iKey = rq_package.Key;guid_to_obj_dictionary.Add(rq_package.GUID, new_rq_obj);}if (new_rq_obj != null){new_rq_obj.level = level;new_rq_obj.treePos = treePos;// attach it to its parent objectReqPro_object parent_rq_obj = (ReqPro_object)rq_objs.Peek();parent_rq_obj.ReqPro_objects.Add( new_rq_obj );new_rq_obj.parent = parent_rq_obj;// keep a count of the number of requirements the object has beneath itif (true == new_rq_obj.isRequirement)parent_rq_obj.numberOfRequirements++;// push the new object onto the stack, ready for any sub-objects that may belong to it.// If, the next time we enter this method, the level is the same, this will get popped off.// If, the next time we enter this method, the level is lower, this and possibly more will// get popped off.rq_objs.Push(new_rq_obj);ea_treePos.Push(0);}// capture what the hierarchy level is for the object just processed.lastLevel = level;}private string firstTokenOnly(string s){if (s != null){char [] delim = {'\r'};string [] tokens = s.Split(delim,2);return tokens[0];}return s;}#endregion#region Trace Relationship Methodsprotected int write_traces(int totalRequirements){trace_log = new ArrayList();Main.WriteSeperator();Main.WriteOutput(string.Format("Writing Trace Information for {0} requirements", totalRequirements), -1);int numberWritten = 0;// adjust modulo for logging purposes so that the number of output messages is restricted// for larger and larger numbers of requirementsif (totalRequirements > 1000)writeTracesModulo = 100;else if (totalRequirements > 500)writeTracesModulo = 50;else if (totalRequirements > 100)writeTracesModulo = 20;else if (totalRequirements > 50)writeTracesModulo = 10;else if (totalRequirements > 20)writeTracesModulo = 5;else // 10 or lesswriteTracesModulo = 1;foreach( ReqPro_object sub_obj in rq_root_package.ReqPro_objects ){if (Main.mustAbort)return numberWritten;numberWritten = write_traces(sub_obj, numberWritten, totalRequirements);}Main.WriteOutput("Traces Completed", -1);if (trace_log.Count > 0){DialogResult dlgRes = MessageBoxEx.Show("Display Log of Trace Connection Changes", "Confirm", MessageBoxButtons.YesNo);if (dlgRes == DialogResult.Yes){foreach (string s in trace_log){Main.WriteOutput(s, -1);}}}Main.WriteSeperator();return numberWritten;}/// <summary>/// This method examines all of the ReqPro_object trace relationships and mirrors them in/// the EA requirement elements that have been formed from each un-filtered ReqPro_objects./// </summary>/// <param name="ea_repository"></param>/// <param name="rq_obj"></param>private int write_traces(ReqPro_object rq_obj, int numberWritten, int totalRequirements){if (Main.mustAbort)return numberWritten;if (rq_obj.isRequirement){// if this object had an EA element made for it during the write_ea_database() process...if (rq_obj.ea_element_ID != -1){// trace outputnumberWritten++;if ((numberWritten % writeTracesModulo) == 0){Main.WriteOutput(string.Format(" {0} of {1}", numberWritten, totalRequirements), -1);}// Get the EA element the object refers toEA.Element ea_req = Main.EA_Repository.GetElementByID(rq_obj.ea_element_ID);if (ea_req != null){if (ea_req.Connectors.Count != 0 || rq_obj.ReqPro_traces.Count != 0)create_or_update_connections(ea_req, rq_obj.ReqPro_traces);}}}// recurse to ensure we examine the entire hiearchyforeach(ReqPro_object sub_obj in rq_obj.ReqPro_objects){if (Main.mustAbort)break;numberWritten = write_traces(sub_obj, numberWritten, totalRequirements);}return numberWritten;}/// <summary>/// This algorithm is intended to create or delete requirement to requirement relationships/// based upon the view of the relationship captured from ReqPro. This function processes/// just one element, and so it should be called repeatedly as the facsimile of the ReqPro/// database captured in memory as a tree structure, is parsed (see write_traces method)./// </summary>/// <param name="src_element"></param>/// <param name="guids"></param>protected void create_or_update_connections(EA.Element src_element, ArrayList guids){bool connectorCollectionNeedsRefreshing = false;string src_tag = EA_TaggedValues.Read(src_element, Constants.TAG_TAG);string src_guid = EA_TaggedValues.Read(src_element, Constants.TAG_GUID, "");ReqPro40.Requirement rp_req = null;// Create a list of booleans that will allow us to track which elements in the guids// list are validated.ArrayList traceValidated = null;if (guids.Count > 0){traceValidated = new ArrayList();for (int i = 0; i < guids.Count; i++){traceValidated.Add(0); // initially, none of the guids are validated.}}int i_traceValidated = 0;int numberOfconnections = src_element.Connectors.Count;if (numberOfconnections > 10){Main.WriteOutput(string.Format(" ...processing requirement with large number ({0}) of traces", numberOfconnections), src_element.ElementID);}// scan the EA elements connectorsshort i_c = 0;foreach(EA.Connector c in src_element.Connectors){int destId = -1;// we dont care about direction of relationship, so test for bothif (c.ClientID == src_element.ElementID)destId = c.SupplierID;else if (c.SupplierID == src_element.ElementID)destId = c.ClientID;// and make sure we filter out self-referential connectorsif (destId != src_element.ElementID){// Get the target element and obtain the tagged values that will effectively mark it as// being a requirementEA.Element ea_tgt_req = Main.EA_Repository.GetElementByID(destId);if (ea_tgt_req != null && ea_tgt_req.Type.Equals("Requirement")){// Get the GUID from the referenced elementstring rp_tgt_req_guid = EA_TaggedValues.Read(ea_tgt_req, Constants.TAG_GUID, "");string rp_tgt_req_tag = EA_TaggedValues.Read(ea_tgt_req, Constants.TAG_TAG, "");if (rp_tgt_req_guid != null && rp_tgt_req_guid.Length > 0 && rp_tgt_req_guid.StartsWith("{")&& rp_tgt_req_tag != null && rp_tgt_req_tag.Length > 0){// looks like an EA element that represents a ReqPro requirement// For this source and destination pair, look for evidence that the relationship// is required by examining the guids list passed in as parameter.i_traceValidated = 0;bool validated = false;foreach (string guid in guids){// Get the target object of the trace relationshipReqPro_object tgt_obj = guid_to_obj_dictionary[guid];if (tgt_obj != null){if (tgt_obj.ea_element_ID != -1){if (destId == tgt_obj.ea_element_ID){validated = true;traceValidated[i_traceValidated] = (int)traceValidated[i_traceValidated] + 1;if ((int)traceValidated[i_traceValidated] > 1){// this is a duplicate trace relationship, so remove itsrc_element.Connectors.DeleteAt(i_c, false);connectorCollectionNeedsRefreshing = true;trace_log.Add("Deleted duplicate connection between " + src_tag + " and " + rp_tgt_req_tag);}break;}}}i_traceValidated++;}if (false == validated){// We did not find evidence that the trace was needed, so remove it// but we have to check that the object at the other end of the relationship does// not have a trace back to us first. In EA, we dont care about directionality since// regardless of direction, all relationships exist in the same collection, but// this is not so in ReqPro, which has TracesTo and TracesFrom relationships (and// other types too).// Also, if the target is not a reqpro requirement then we should probably leave the relationship// in place since the target may be an element an EA user is preparing to be a ReqPro requirement// but they have yet to export it.ReqPro_object tgt_obj = guid_to_obj_dictionary[rp_tgt_req_guid];if (tgt_obj != null){if (false == tgt_obj.ReqPro_traces.Contains(src_guid)){// Does the current requirement we are dealing with have a relationship with another// requirement with the GUID indicated by the tgt_obj ? If is does, then we cannot// delete the relationship in EA. We have to go to the ReqPro database to ascertain this// since the filter dialog may have filtered out the target object and so our local// structure cannot tell us much in this regard.if (rp_req == null)rp_req = ReqProDatabase.get_requirement_by_guid(src_guid);if (rp_req != null && false == ReqProDatabase.any_relationship_exists(rp_req, tgt_obj.guid)){src_element.Connectors.DeleteAt(i_c, false);connectorCollectionNeedsRefreshing = true;trace_log.Add("Deleted connection between " + src_tag + " and " + rp_tgt_req_tag);}}}}}}}else{// Found a self-referential requirement - this is not really allowed, so delete the connectionsrc_element.Connectors.DeleteAt(i_c, false);trace_log.Add("Deleted self-referential connection in " + src_tag);connectorCollectionNeedsRefreshing = true;}i_c++;if ((i_c % 10) == 0){Main.WriteOutput(string.Format(" ...{0} of {1}", (int)i_c, numberOfconnections), src_element.ElementID);}}// Now look for all guids that have not been marked as validated. For these, a new connection// must be establishedi_traceValidated = 0;foreach (string guid in guids){if ((int)traceValidated[i_traceValidated] == 0){// Get the target object of the trace relationshipReqPro_object tgt_obj = guid_to_obj_dictionary[guid];if (tgt_obj != null){if (tgt_obj.ea_element_ID != -1){EA.Element ea_tgt_req = Main.EA_Repository.GetElementByID(tgt_obj.ea_element_ID);if (ea_tgt_req != null && ea_tgt_req.Type.Equals("Requirement")){string rp_tgt_req_tag = EA_TaggedValues.Read(ea_tgt_req, Constants.TAG_TAG, "");// Add the new connection between the src_element and dest_elementEA.Connector c = (EA.Connector)src_element.Connectors.AddNew("", "Dependency");c.SupplierID = ea_tgt_req.ElementID;c.Direction = "Source -> Destination";if (false == c.Update()){Main.WriteOutput("New Connector Error : " + c.GetLastError(), ea_tgt_req.ElementID );Main.WriteOutput("...Failed to create connection between " + src_tag + " and " + rp_tgt_req_tag, ea_tgt_req.ElementID);connectorCollectionNeedsRefreshing = true;}else{trace_log.Add("Created connection between " + src_tag + " and " + rp_tgt_req_tag);}}}}}i_traceValidated++;}if (connectorCollectionNeedsRefreshing)src_element.Connectors.Refresh();}#endregion/// <summary>/// A method to contain common pre-parsing steps./// </summary>private void pre_parsing(){// create an object to represent the root of the database so that we can collect// sub-objects (packages or requirements) underneath it.rq_root_package = new ReqPro_object();rq_root_package.name = "ROOT"; // may be overriden later// initialise the ReqPro database hierarchy tracking datarq_objs.Clear();rq_objs.Push(rq_root_package);ea_treePos.Clear();ea_treePos.Push(0);lastLevel = 0;}}public class ReqProObject_Dictionary : DictionaryBase{public ReqPro_object this[string guid]{get {return (ReqPro_object) this.Dictionary[guid]; }set { this.Dictionary[guid] = value; }}public void Add(string guid, ReqPro_object rp_obj){this.Dictionary.Add(guid, rp_obj);}public bool Contains(string guid){return this.Dictionary.Contains(guid);}public ICollection Keys{get {return this.Dictionary.Keys;}}}}