From: <mcu...@us...> - 2010-09-15 22:04:55
|
Revision: 1457 http://orm.svn.sourceforge.net/orm/?rev=1457&view=rev Author: mcurland Date: 2010-09-15 22:04:48 +0000 (Wed, 15 Sep 2010) Log Message: ----------- More copy/merge targeted changes * Add matching for model notes and copy note links * Fix matching for value ranges with unspecified upper or lower bounds * Add automatic equivalence matching on request for non-duplicate relationships * Add optimization to request a match once per merge instead of continue to ask on failure * Make sure that lead role paths are matched one time only (two equivalent paths with different projections could match twice) * Fix missed calculation matches when a role path had calculations but no conditions Modified Paths: -------------- trunk/ORMModel/Framework/CopyMergeServices.cs trunk/ORMModel/ObjectModel/ORMCore.CopyMergeClosure.cs trunk/ORMModel/ObjectModel/ORMCore.CopyMergeClosure.xml trunk/ORMModel/ObjectModel/ORMCore.ElementEquivalence.cs trunk/ORMModel/ObjectModel/ValueRange.cs Modified: trunk/ORMModel/Framework/CopyMergeServices.cs =================================================================== --- trunk/ORMModel/Framework/CopyMergeServices.cs 2010-09-15 08:17:28 UTC (rev 1456) +++ trunk/ORMModel/Framework/CopyMergeServices.cs 2010-09-15 22:04:48 UTC (rev 1457) @@ -488,7 +488,7 @@ /// Add an element equivalence relationship. /// </summary> /// <param name="nativeElement">The native element, from the <see cref="Store"/> of - /// the instance called the the <see cref="IElementEquivalence"/> interface.</param> + /// the instance called via the <see cref="IElementEquivalence"/> interface.</param> /// <param name="equivalentElement">An equivalent element in the foreign store.</param> /// <remarks>An implementation of this interface should ignore any call with one or /// more values <see langword="null"/></remarks> @@ -496,9 +496,22 @@ /// <summary> /// Get an equivalent mapping previously added with <see cref="AddEquivalentElement"/> /// </summary> - /// <param name="element">The element to find a match for.</param> + /// <param name="nativeElement">The element to find a match for.</param> /// <returns>The equivalent element, if any.</returns> - ModelElement GetEquivalentElement(ModelElement element); + ModelElement GetEquivalentElement(ModelElement nativeElement); + /// <summary> + /// An equivalence test has been attempted for this element + /// but has failed. Track it so another attempt to test + /// equivalence is not made. + /// </summary> + /// <param name="nativeElement">The native element, from the <see cref="Store"/> of + /// the instance called via the <see cref="IElementEquivalence"/> interface.</param> + void AddFailedEquivalentElement(ModelElement nativeElement); + /// <summary> + /// Check if <see cref="AddFailedEquivalentElement"/> has been + /// called for this element. + /// </summary> + bool IsFailedEquivalentElement(ModelElement nativeElement); } #endregion // IElementEquivalence interface #region CopyClosureIntegrationPhase enum @@ -572,12 +585,66 @@ public static T GetEquivalentElement<T>(T element, Store foreignStore, IEquivalentElementTracker elementTracker) where T : ModelElement { T otherElement = elementTracker.GetEquivalentElement(element) as T; - IElementEquivalence testEquivalence; if (otherElement == null && - null != (testEquivalence = element as IElementEquivalence) && - testEquivalence.MapEquivalentElements(foreignStore, elementTracker)) + !elementTracker.IsFailedEquivalentElement(element)) { - otherElement = elementTracker.GetEquivalentElement(element) as T; + IElementEquivalence testEquivalence; + ElementLink link; + DomainRelationshipInfo relationshipInfo; + ReadOnlyCollection<DomainRoleInfo> domainRoles; + DomainRoleInfo firstRole; + DomainRoleInfo secondRole; + ModelElement otherFirstRolePlayer; + ModelElement otherSecondRolePlayer; + if (null != (testEquivalence = element as IElementEquivalence)) + { + if (testEquivalence.MapEquivalentElements(foreignStore, elementTracker)) + { + otherElement = elementTracker.GetEquivalentElement(element) as T; + } + } + else if (null != (link = element as ElementLink) && + !(relationshipInfo = link.GetDomainRelationship()).AllowsDuplicates && + null != (otherFirstRolePlayer = GetEquivalentElement((firstRole = (domainRoles = relationshipInfo.DomainRoles)[0]).GetRolePlayer(link), foreignStore, elementTracker)) && + null != (otherSecondRolePlayer = GetEquivalentElement((secondRole = domainRoles[1]).GetRolePlayer(link), foreignStore, elementTracker))) + { + // See notes on GetElementLinksToElement in ResolveExistingLink + DomainDataDirectory dataDirectory = foreignStore.DomainDataDirectory; + DomainRoleInfo fromOtherRoleInfo; + DomainRoleInfo toOtherRoleInfo; + ModelElement fromOtherElement; + ModelElement toOtherElement; + if (firstRole.IsOne || !secondRole.IsOne) + { + fromOtherRoleInfo = dataDirectory.FindDomainRole(firstRole.Id); + toOtherRoleInfo = dataDirectory.FindDomainRole(secondRole.Id); + fromOtherElement = otherFirstRolePlayer; + toOtherElement = otherSecondRolePlayer; + } + else + { + fromOtherRoleInfo = dataDirectory.FindDomainRole(secondRole.Id); + toOtherRoleInfo = dataDirectory.FindDomainRole(firstRole.Id); + fromOtherElement = otherSecondRolePlayer; + toOtherElement = otherFirstRolePlayer; + } + if (fromOtherRoleInfo != null) // These will be null together, checking one is sufficient + { + foreach (ElementLink otherLink in fromOtherRoleInfo.GetElementLinks(fromOtherElement)) + { + if (toOtherRoleInfo.GetRolePlayer(otherLink) == toOtherElement && + null != (otherElement = otherLink as T)) + { + elementTracker.AddEquivalentElement(element, otherElement); + break; + } + } + } + } + if (otherElement == null) + { + elementTracker.AddFailedEquivalentElement(element); + } } return otherElement; } @@ -597,10 +664,10 @@ } #endregion // Member Variables and Constructor #region IEquivalentElementTracker Implementation - ModelElement IEquivalentElementTracker.GetEquivalentElement(ModelElement element) + ModelElement IEquivalentElementTracker.GetEquivalentElement(ModelElement nativeElement) { ModelElement retVal; - return myElementDictionary.TryGetValue(element, out retVal) ? retVal : null; + return myElementDictionary.TryGetValue(nativeElement, out retVal) ? retVal : null; } void IEquivalentElementTracker.AddEquivalentElement(ModelElement nativeElement, ModelElement equivalentElement) { @@ -609,6 +676,15 @@ myElementDictionary[nativeElement] = equivalentElement; } } + void IEquivalentElementTracker.AddFailedEquivalentElement(ModelElement nativeElement) + { + myElementDictionary[nativeElement] = null; + } + bool IEquivalentElementTracker.IsFailedEquivalentElement(ModelElement nativeElement) + { + ModelElement checkForNull; + return myElementDictionary.TryGetValue(nativeElement, out checkForNull) && checkForNull == null; + } #endregion // IEquivalentElementTracker Implementation } #endregion // StockEquivalentElementTracker class @@ -1237,6 +1313,7 @@ private readonly Dictionary<Guid, MergeIntegrationOrder> myOrderedRoles; private readonly DomainDataDirectory myTargetDataDirectory; private Dictionary<RoleAndElement, ModelElement> myRequiresOrdering; + private Dictionary<Guid, object> myFailedEquivalence; public EquivalenceTracker(Dictionary<Guid, CopiedElement> equivalenceMap, Dictionary<Guid, MergeIntegrationOrder> orderedRoles, DomainDataDirectory targetDataDirectory, Dictionary<RoleAndElement, ModelElement> requiresOrdering) { myEquivalentElementMap = equivalenceMap; @@ -1246,6 +1323,9 @@ } #endregion // Member Variables and Constructor #region EquivalenceTracker specific + /// <summary> + /// Get back the original or on-demand-created ordering dictionary + /// </summary> public Dictionary<RoleAndElement, ModelElement> RequiresOrdering { get @@ -1253,12 +1333,22 @@ return myRequiresOrdering; } } + /// <summary> + /// Test if an identifier is tracked as either a successful + /// or failed equivalence mapping. + /// </summary> + public bool IsKnownIdentifier(Guid elementId) + { + Dictionary<Guid, object> failures; + return myEquivalentElementMap.ContainsKey(elementId) || + (null != (failures = myFailedEquivalence) && failures.ContainsKey(elementId)); + } #endregion // EquivalenceTracker specific #region IEquivalentElementTracker Implementation - ModelElement IEquivalentElementTracker.GetEquivalentElement(ModelElement element) + ModelElement IEquivalentElementTracker.GetEquivalentElement(ModelElement nativeElement) { CopiedElement copiedElement; - return myEquivalentElementMap.TryGetValue(element.Id, out copiedElement) ? copiedElement.Element : null; + return myEquivalentElementMap.TryGetValue(nativeElement.Id, out copiedElement) ? copiedElement.Element : null; } void IEquivalentElementTracker.AddEquivalentElement(ModelElement nativeElement, ModelElement equivalentElement) { @@ -1288,6 +1378,16 @@ } } } + void IEquivalentElementTracker.AddFailedEquivalentElement(ModelElement nativeElement) + { + (myFailedEquivalence ?? (myFailedEquivalence = new Dictionary<Guid, object>()))[nativeElement.Id] = null; + } + bool IEquivalentElementTracker.IsFailedEquivalentElement(ModelElement nativeElement) + { + Dictionary<Guid, object> failedElements; + return null != (failedElements = myFailedEquivalence) && + failedElements.ContainsKey(nativeElement.Id); + } #endregion // IEquivalentElementTracker Implementation } #endregion // EquivalenceTracker class @@ -1388,6 +1488,7 @@ DomainDataDirectory targetDataDirectory = targetStore.DomainDataDirectory; DomainDataDirectory sourceDataDirectory = sourceStore.DomainDataDirectory; EquivalenceTracker tracker = new EquivalenceTracker(resolvedElements, myOrderedRoles, targetDataDirectory, requiresOrdering); + IEquivalentElementTracker trackerAsInterface = tracker; foreach (KeyValuePair<Guid, IClosureElement> pair in copyClosure) { IClosureElement closureElement = pair.Value; @@ -1401,10 +1502,15 @@ break; // Do not attempt to merge duplicate elements case CopyMergeAction.Match: IElementEquivalence equivalence; - if (null != (equivalence = closureElement.Element as IElementEquivalence) && - !resolvedElements.ContainsKey(pair.Key)) + ModelElement element; + if (!tracker.IsKnownIdentifier(pair.Key) && + null != (equivalence = (element = closureElement.Element) as IElementEquivalence)) { - equivalence.MapEquivalentElements(targetStore, tracker); + if (!equivalence.MapEquivalentElements(targetStore, trackerAsInterface) || + null == trackerAsInterface.GetEquivalentElement(element)) + { + trackerAsInterface.AddFailedEquivalentElement(element); + } } break; case CopyMergeAction.Link: Modified: trunk/ORMModel/ObjectModel/ORMCore.CopyMergeClosure.cs =================================================================== --- trunk/ORMModel/ObjectModel/ORMCore.CopyMergeClosure.cs 2010-09-15 08:17:28 UTC (rev 1456) +++ trunk/ORMModel/ObjectModel/ORMCore.CopyMergeClosure.cs 2010-09-15 22:04:48 UTC (rev 1457) @@ -192,6 +192,7 @@ closureManager.AddCopyClosureDirective(new DomainRoleClosureRestriction(GroupingElementRelationship.ElementDomainRoleId), new DomainRoleClosureRestriction(GroupingElementRelationship.GroupingDomainRoleId), CopyClosureDirectiveOptions.None, CopyClosureBehavior.ExternalReferencedPart); closureManager.AddCopyClosureDirective(new DomainRoleClosureRestriction(LeadRolePathSatisfiesCalculatedCondition.LeadRolePathDomainRoleId), new DomainRoleClosureRestriction(LeadRolePathSatisfiesCalculatedCondition.CalculatedConditionDomainRoleId), CopyClosureDirectiveOptions.None, CopyClosureBehavior.InternalReferencedPart); closureManager.AddCopyClosureDirective(new DomainRoleClosureRestriction(LeadRolePathSatisfiesCalculatedCondition.LeadRolePathDomainRoleId), new DomainRoleClosureRestriction(LeadRolePathSatisfiesCalculatedCondition.CalculatedConditionDomainRoleId), CopyClosureDirectiveOptions.None, CopyClosureBehavior.InternalReferencedPart); + closureManager.AddCopyClosureDirective(new DomainRoleClosureRestriction(ModelNoteReferencesModelElement.ElementDomainRoleId), new DomainRoleClosureRestriction(ModelNoteReferencesModelElement.NoteDomainRoleId), CopyClosureDirectiveOptions.None, CopyClosureBehavior.ExternalReferencedPart); closureManager.AddCopyClosureDirective(new DomainRoleClosureRestriction(Objectification.NestingTypeDomainRoleId), new DomainRoleClosureRestriction(Objectification.NestedFactTypeDomainRoleId), CopyClosureDirectiveOptions.None, CopyClosureBehavior.ExternalCompositePart); closureManager.AddCopyClosureDirective(new DomainRoleClosureRestriction(Objectification.NestedFactTypeDomainRoleId), new DomainRoleClosureRestriction(Objectification.NestingTypeDomainRoleId), CopyClosureDirectiveOptions.None, CopyClosureBehavior.ExternalCompositePart); closureManager.AddCopyClosureDirective(new DomainRoleClosureRestriction(ObjectificationImpliesFactType.ImpliedFactTypeDomainRoleId), new DomainRoleClosureRestriction(ObjectificationImpliesFactType.ImpliedByObjectificationDomainRoleId), CopyClosureDirectiveOptions.None, CopyClosureBehavior.ExternalCompositePart); Modified: trunk/ORMModel/ObjectModel/ORMCore.CopyMergeClosure.xml =================================================================== --- trunk/ORMModel/ObjectModel/ORMCore.CopyMergeClosure.xml 2010-09-15 08:17:28 UTC (rev 1456) +++ trunk/ORMModel/ObjectModel/ORMCore.CopyMergeClosure.xml 2010-09-15 22:04:48 UTC (rev 1457) @@ -205,6 +205,10 @@ <cmc:ClosureRole relationship="ValueTypeHasDataType" role="DataType" closureBehavior="ExternalReferencedPart"/> <cmc:ClosureRole relationship="ReferenceModeHasReferenceModeKind" role="Kind" closureBehavior="ExternalReferencedPart"/> <cmc:ClosureRole relationship="ReadingOrderHasRole" role="Role" closureBehavior="InternalReferencedPart" order="From"/> + <!-- UNDONE: COPYMERGE This is temporary for notes (bring in the note with a referencing element). We actually + want a weak relationship, where the ModelNote reference is pulled in only if both role players are pulled in for other + reasons. We need a notion of a MatchOnly element (match, but do not create) and a non-propagating relationship. --> + <cmc:ClosureRole relationship="ModelNoteReferencesModelElement" role="Note" closureBehavior="ExternalReferencedPart"/> <!-- Role path closures --> <cmc:ClosureRole relationship="LeadRolePathSatisfiesCalculatedCondition" role="CalculatedCondition" closureBehavior="InternalReferencedPart"/> Modified: trunk/ORMModel/ObjectModel/ORMCore.ElementEquivalence.cs =================================================================== --- trunk/ORMModel/ObjectModel/ORMCore.ElementEquivalence.cs 2010-09-15 08:17:28 UTC (rev 1456) +++ trunk/ORMModel/ObjectModel/ORMCore.ElementEquivalence.cs 2010-09-15 22:04:48 UTC (rev 1457) @@ -1212,8 +1212,9 @@ /// </summary> protected bool MapEquivalentElements(Store foreignStore, IEquivalentElementTracker elementTracker) { + RolePathOwner pathOwner = PathOwner; RolePathOwner otherPathOwner; - if (null != (otherPathOwner = CopyMergeUtility.GetEquivalentElement(PathOwner, foreignStore, elementTracker))) + if (null != (otherPathOwner = CopyMergeUtility.GetEquivalentElement(pathOwner, foreignStore, elementTracker))) { LinkedElementCollection<PathObjectUnifier> unifiers = null; LinkedElementCollection<CalculatedPathValue> conditions = null; @@ -1225,8 +1226,37 @@ int[] matchingOtherUnifiers = null; // Array indexed into other unifiers (index +1, zero means empty) BitTracker matchedOthers = default(BitTracker); Dictionary<ModelElement, ModelElement> preMatchedElements = null; + LinkedElementCollection<LeadRolePath> allOwnedPaths = pathOwner.OwnedLeadRolePathCollection; + if (allOwnedPaths.Count == 1) + { + allOwnedPaths = null; + } foreach (LeadRolePath otherLeadRolePath in otherPathOwner.OwnedLeadRolePathCollection) { + if (allOwnedPaths != null) + { + bool previouslyMatched = false; + foreach (LeadRolePath ownedPath in allOwnedPaths) + { + if (ownedPath != this && + elementTracker.GetEquivalentElement(ownedPath) == otherLeadRolePath) + { + // Do a sanity check before we continue. It is possible for one owner + // to have two equivalent paths (possibly with alternate projections, etc). + // The merge framework does not track reverse elements, so we need to make + // sure that we don't match with two different things. Without this check, + // two projections can end up mapping the same lead role path. For the more + // common case of a relatively small number of different paths, this also + // avoids repetitions of the matching code below. + previouslyMatched = true; + break; + } + } + if (previouslyMatched) + { + continue; + } + } if (preMatchedElements == null) { preMatchedElements = new Dictionary<ModelElement,ModelElement>(); @@ -1392,6 +1422,11 @@ // All conditions match, the paths are equivalent. // Now try top pair up any remaining unmapped functions. + if (calculations == null) + { + calculations = CalculatedValueCollection; + calculationCount = calculations.Count; + } if (calculationCount != 0) { int otherCalculationCount; @@ -1799,6 +1834,33 @@ } } #endregion // CalculatedPathValue class + #region CalculatedPathValueInput class + partial class CalculatedPathValueInput : IElementEquivalence + { + /// <summary> + /// Implements <see cref="IElementEquivalence.MapEquivalentElements"/> + /// Match the calculation input based on the associated lead role path. + /// IElementEquivalence is implemented on path components that are referenced from + /// outside the path structure or nest 1-1 mapped elements. + /// </summary> + protected bool MapEquivalentElements(Store foreignStore, IEquivalentElementTracker elementTracker) + { + CalculatedPathValue calculation; + LeadRolePath rolePath; + if (null != (calculation = CalculatedValue) && + null != (rolePath = calculation.LeadRolePath) && + CopyMergeUtility.GetEquivalentElement(rolePath, foreignStore, elementTracker) != null) + { + return elementTracker.GetEquivalentElement(this) != null; + } + return false; + } + bool IElementEquivalence.MapEquivalentElements(Store foreignStore, IEquivalentElementTracker elementTracker) + { + return MapEquivalentElements(foreignStore, elementTracker); + } + } + #endregion // CalculatedPathValueInput class #region RecognizedPhrase class partial class RecognizedPhrase : IElementEquivalence { @@ -1870,4 +1932,33 @@ } } #endregion // NameAlias class + #region ModelNote class + partial class ModelNote : IElementEquivalence + { + /// <summary> + /// Implements <see cref="IElementEquivalence.MapEquivalentElements"/> + /// </summary> + protected new bool MapEquivalentElements(Store foreignStore, IEquivalentElementTracker elementTracker) + { + foreach (ORMModel otherModel in foreignStore.ElementDirectory.FindElements<ORMModel>(false)) + { + string matchText = Text; + foreach (ModelNote otherNote in otherModel.NoteCollection) + { + if (otherNote.Text == matchText) + { + elementTracker.AddEquivalentElement(this, otherNote); + return true; + } + } + break; + } + return false; + } + bool IElementEquivalence.MapEquivalentElements(Store foreignStore, IEquivalentElementTracker elementTracker) + { + return MapEquivalentElements(foreignStore, elementTracker); + } + } + #endregion // ModelNote class } Modified: trunk/ORMModel/ObjectModel/ValueRange.cs =================================================================== --- trunk/ORMModel/ObjectModel/ValueRange.cs 2010-09-15 08:17:28 UTC (rev 1456) +++ trunk/ORMModel/ObjectModel/ValueRange.cs 2010-09-15 22:04:48 UTC (rev 1457) @@ -1707,6 +1707,28 @@ for (int i = 0; i < rangeCount; ++i) { ValueRange range = ranges[i]; + string rangeMin = range.MinValue; + bool minParsed; + if (string.IsNullOrEmpty(rangeMin)) + { + rangeMin = null; + minParsed = false; + } + else + { + minParsed = dataType.ParseNormalizeValue(rangeMin, range.InvariantMinValue, out rangeMin); + } + string rangeMax = range.MaxValue; + bool maxParsed; + if (string.IsNullOrEmpty(rangeMax)) + { + rangeMax = null; + maxParsed = false; + } + else + { + maxParsed = dataType.ParseNormalizeValue(rangeMax, range.InvariantMaxValue, out rangeMax); + } for (int j = 0; j < otherRangeCount; ++j) { if (!otherMatches[j]) @@ -1718,20 +1740,43 @@ // integration is complete. We use the current data type to // compare values, and leave it up to rules to sort out the // remaining issues after data type information is complete. - string normalizedValue; - string otherNormalizedValue; - if (dataType.ParseNormalizeValue(range.MinValue, range.InvariantMinValue, out normalizedValue) && - dataType.ParseNormalizeValue(otherRange.MinValue, otherRange.InvariantMinValue, out otherNormalizedValue) && - (canCompare ? (dataType.Compare(normalizedValue, otherNormalizedValue) == 0) : (normalizedValue == otherNormalizedValue)) && - dataType.ParseNormalizeValue(range.MaxValue, range.InvariantMaxValue, out normalizedValue) && - dataType.ParseNormalizeValue(otherRange.MaxValue, otherRange.InvariantMaxValue, out otherNormalizedValue) && - (canCompare ? (dataType.Compare(normalizedValue, otherNormalizedValue) == 0) : (normalizedValue == otherNormalizedValue))) + string otherRangeBound = otherRange.MinValue; + if (string.IsNullOrEmpty(otherRangeBound)) { - // Ignore endpoint inclusion properties for merging, consider these sufficient equivalent ranges to match. - elementTracker.AddEquivalentElement(range, otherRange); - otherMatches[j] = true; - break; + if (rangeMin != null) + { + continue; + } } + else if (minParsed != dataType.ParseNormalizeValue(otherRangeBound, otherRange.InvariantMinValue, out otherRangeBound)) + { + continue; + } + else if (!((canCompare && minParsed) ? (dataType.Compare(rangeMin, otherRangeBound) == 0) : (rangeMin == otherRangeBound))) + { + continue; + } + otherRangeBound = otherRange.MaxValue; + if (string.IsNullOrEmpty(otherRangeBound)) + { + if (rangeMax != null) + { + continue; + } + } + else if (maxParsed != dataType.ParseNormalizeValue(otherRangeBound, otherRange.InvariantMaxValue, out otherRangeBound)) + { + continue; + } + else if (!((canCompare && maxParsed) ? (dataType.Compare(rangeMax, otherRangeBound) == 0) : (rangeMax == otherRangeBound))) + { + continue; + } + + // Ignore endpoint inclusion properties for merging, consider these sufficient equivalent ranges to match. + elementTracker.AddEquivalentElement(range, otherRange); + otherMatches[j] = true; + break; } } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |