-
Notifications
You must be signed in to change notification settings - Fork 0
Support for model migrations #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
65 commits
Select commit
Hold shift + click to select a range
c71b436
added fallback for factories of non-registered languages
6f3fd20
ReplaceWith can handle annotations (tests missing)
8c4bc8f
more robust serializer
b401b91
implemented DynamicLanguage.{DetachChild()|GetContainmentOf()}
3619acf
made LenientNode features work even if feature identity changes
487bd0a
started on migration infrastructure
50cd74f
more tests for LenientNode
109df38
added more tests for LenientNode and LanguageIdentityComparer
1c8da17
fixed TryGetExtends() for Concept and Annotation
9e480c0
improved IMigration API
47496e2
improved ModelMigrator API
c51ed5b
added LenientNode.TryGet() -- WIP
e8a673b
started with LionWebVersionMigration (WIP)
f837e7d
cleanup
2cb4596
taught ClonerBase to handle IReadableNode as reference target
6a53bb3
make sure DynamicLanguageCloner handles entities only referenced some…
b334a73
ModelMigrator
770d540
moved LanguageEntity helpers from MigrationExtensions to MigrationBas…
158913c
working LionWebVersionMigration
f92f89c
added KeyedIdentityComparer and tests
4fa97e7
added parameter to JsonUtils.ReadNodesFromStreamAsync() to retrieve L…
7da9c21
cleaned up LionWebVersionMigration
851f706
added LionWebVersions 2025.1 and 2025.1_Compatible
81f5729
fixed TryGetExtends()
7ac2f98
fixed DynamicStructuredDataTypeInstance.Get()
5f134b0
added tests to migrate to LionWebVersion 2025.1
d8b4f4c
added tests for M1Extension.ReplaceWith() for annotation instances
561b4ea
implemented IReadableNode.TryGet()
b9eae44
added more tests for DynamicStructuredDataType
4f55797
documentation and cleanup for migration types
e076e5b
Introduced SerializerBuilder
61f621f
replaced direct usage of Deserializer with DeserializerBuilder
f8eddc5
Introduced LanguageDeserializerBuilder
34209e4
adjusted build-Generate to use builders
5a45ba5
some cleanup
77f0eb6
some cleanup
9f712dc
added tests for DynamicLanguageCloner
5f6d4f2
restructured ModelMigrator
62a80a9
started on tests for LenientNodeComparer and MigrationResult.Validate()
ee7b864
simplified MigrationResult.Validate()
6cbfea4
Merge branch 'niko/serializer-builder' into niko/migration-adjustments
ab84fe0
Use (De)SerializerBuilder in migration
793467c
added tests for M2Extensions.FindByKey()
97d94fe
added KeysMigration
59a57b2
Merge branch 'main' into niko/migration-adjustments
39c8f96
cleanup after merge
19741d2
make sure LionWebVersionMigration handles name properties
d40c05b
added ILanguageRegistry.KnownLanguages
85fc4b4
added TryGet{Property,Child,Children,Reference,References} to Migrati…
005c63e
added migration test that mixes different versions of a language
5bfa4fd
restructured ShapesMigrationTests
c6b0ae3
beautification
575365f
introduced MigrationExceptions
feadeac
added tests for ModelMigrator
f15b514
cleanup
de6e53b
test for no (applicable) migrations
a6f32c6
added tests for ILanguageRegistry
ff3c4ee
added ComparerBehaviorConfig.CompareCompatibleClassifier
c532767
added tests for MigrationBase
48ec462
obsoleted identity-based helper methods in MigrationBase
9b939ee
Merge branch 'main' into niko/migration-adjustments
390cec1
update after merge
d2c928e
updated changelog
c9f2c7f
separated IModelMigrator into separate file
47a2d4d
improved changelog
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
299 changes: 299 additions & 0 deletions
299
src/LionWeb.Core/Core/Migration/DynamicLanguageCloner.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,299 @@ | ||
// Copyright 2025 TRUMPF Laser SE and other contributors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License") | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
// SPDX-FileCopyrightText: 2024 TRUMPF Laser SE and other contributors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
namespace LionWeb.Core.Migration; | ||
|
||
using M2; | ||
using M3; | ||
using System.Diagnostics.CodeAnalysis; | ||
using Utilities; | ||
|
||
/// Clones Languages as <see cref="DynamicLanguage">DynamicLanguages</see> based on <paramref name="lionWebVersion"/>. | ||
public class DynamicLanguageCloner(LionWebVersions lionWebVersion) | ||
{ | ||
private readonly Dictionary<IKeyed, DynamicIKeyed?> _dynamicMap = new(new KeyedIdentityComparer()); | ||
|
||
/// Provides a mapping of all cloned elements to their clones. | ||
public IReadOnlyDictionary<IKeyed, DynamicIKeyed> DynamicMap => | ||
_dynamicMap | ||
.Where(p => p.Value != null) | ||
.ToDictionary() | ||
.AsReadOnly()!; | ||
|
||
/// Clones all of <paramref name="languages"/> as <see cref="DynamicLanguage">DynamicLanguages</see> based on <see cref="lionWebVersion"/>. | ||
/// <paramref name="languages"/> MUST be self-contained, i.e. no language might refer to another language outside <paramref name="languages"/>. | ||
public Dictionary<LanguageIdentity, DynamicLanguage> Clone(IEnumerable<Language> languages) | ||
{ | ||
CreateClones(languages); | ||
CloneReferencedElements(); | ||
ResolveReferences(); | ||
|
||
return _dynamicMap | ||
.Values | ||
.OfType<DynamicLanguage>() | ||
.ToDictionary(LanguageIdentity.FromLanguage, l => l); | ||
} | ||
|
||
#region Cloning | ||
|
||
private void CreateClones(IEnumerable<Language> languages) | ||
{ | ||
foreach (var l in languages) | ||
{ | ||
DynamicLanguage dynamicLanguage = CloneLanguage(l); | ||
dynamicLanguage.AddEntities(l.Entities.Select(e => CloneEntity(e, dynamicLanguage))); | ||
} | ||
} | ||
|
||
private DynamicLanguageEntity CloneEntity(LanguageEntity languageEntity, DynamicLanguage dynamicLanguage) | ||
{ | ||
DynamicLanguageEntity entity = languageEntity switch | ||
{ | ||
Annotation a => CloneAnnotation(a, dynamicLanguage), | ||
Concept c => CloneConcept(c, dynamicLanguage), | ||
Interface i => CloneInterface(i, dynamicLanguage), | ||
Enumeration e => CloneEnumeration(e, dynamicLanguage), | ||
PrimitiveType p => ClonePrimitiveType(p, dynamicLanguage), | ||
StructuredDataType s => CloneStructuredDataType(s, dynamicLanguage), | ||
_ => throw new ArgumentOutOfRangeException(languageEntity.ToString()) | ||
}; | ||
_dynamicMap[languageEntity] = entity; | ||
return entity; | ||
} | ||
|
||
private DynamicLanguage CloneLanguage(Language language) | ||
{ | ||
var result = new DynamicLanguage(language.GetId(), lionWebVersion) | ||
{ | ||
Name = language.Name, Key = language.Key, Version = language.Version, | ||
}; | ||
result.SetFactory(new MigrationFactory(result)); | ||
_dynamicMap[language] = result; | ||
return result; | ||
} | ||
|
||
private DynamicAnnotation CloneAnnotation(Annotation a, DynamicLanguage language) | ||
{ | ||
var result = new DynamicAnnotation(a.GetId(), lionWebVersion, language) { Name = a.Name, Key = a.Key }; | ||
result.AddFeatures(a.Features.Select(CloneFeature)); | ||
return result; | ||
} | ||
|
||
private DynamicConcept CloneConcept(Concept c, DynamicLanguage language) | ||
{ | ||
var result = new DynamicConcept(c.GetId(), lionWebVersion, language) | ||
{ | ||
Name = c.Name, Key = c.Key, Abstract = c.Abstract, Partition = c.Partition, | ||
}; | ||
result.AddFeatures(c.Features.Select(CloneFeature)); | ||
return result; | ||
} | ||
|
||
private DynamicInterface CloneInterface(Interface i, DynamicLanguage language) | ||
{ | ||
var result = new DynamicInterface(i.GetId(), lionWebVersion, language) { Name = i.Name, Key = i.Key }; | ||
result.AddFeatures(i.Features.Select(CloneFeature)); | ||
return result; | ||
} | ||
|
||
private DynamicEnumeration CloneEnumeration(Enumeration enm, DynamicLanguage language) | ||
{ | ||
var result = new DynamicEnumeration(enm.GetId(), lionWebVersion, language) { Name = enm.Name, Key = enm.Key }; | ||
result.AddLiterals(enm.Literals.Select(lit => | ||
{ | ||
var r = new DynamicEnumerationLiteral(lit.GetId(), lionWebVersion, result) | ||
{ | ||
Name = lit.Name, Key = lit.Key | ||
}; | ||
_dynamicMap.Add(lit, r); | ||
return r; | ||
})); | ||
return result; | ||
} | ||
|
||
private DynamicLanguageEntity ClonePrimitiveType(PrimitiveType p, DynamicLanguage language) => | ||
new DynamicPrimitiveType(p.Key, lionWebVersion, language) { Name = p.Name, Key = p.Key }; | ||
|
||
private DynamicStructuredDataType CloneStructuredDataType(StructuredDataType sdt, DynamicLanguage language) | ||
{ | ||
var result = new DynamicStructuredDataType(sdt.Key, lionWebVersion, language) | ||
{ | ||
Name = sdt.Name, Key = sdt.Key | ||
}; | ||
result.AddFields(sdt.Fields.Select<Field, DynamicField>(f => | ||
{ | ||
var field = new DynamicField(f.GetId(), lionWebVersion, result) { Name = f.Name, Key = f.Key }; | ||
_dynamicMap.Add(f, field); | ||
_dynamicMap.TryAdd(f.Type, null); | ||
return field; | ||
})); | ||
return result; | ||
} | ||
|
||
private DynamicFeature CloneFeature(Feature f) | ||
{ | ||
var result = (DynamicFeature)(f switch | ||
{ | ||
Property p => new DynamicProperty(p.GetId(), lionWebVersion, null) | ||
{ | ||
Name = p.Name, Key = p.Key, Optional = p.Optional | ||
}, | ||
Containment c => new DynamicContainment(c.GetId(), lionWebVersion, null) | ||
{ | ||
Name = c.Name, Key = c.Key, Optional = c.Optional, Multiple = c.Multiple | ||
}, | ||
Reference r => new DynamicReference(r.GetId(), lionWebVersion, null) | ||
{ | ||
Name = r.Name, Key = r.Key, Optional = r.Optional, Multiple = r.Multiple | ||
}, | ||
_ => throw new ArgumentOutOfRangeException(f.ToString()) | ||
}); | ||
_dynamicMap.Add(f, result); | ||
_dynamicMap.TryAdd(f.GetFeatureType(), null); | ||
return result; | ||
} | ||
|
||
private void CloneReferencedElements() | ||
{ | ||
List<IGrouping<Language, KeyValuePair<IKeyed, DynamicIKeyed?>>> unclonedReferencedElements = CollectUnclonedReferencedElements(); | ||
|
||
while (unclonedReferencedElements.Count != 0) | ||
{ | ||
foreach (var grouping in unclonedReferencedElements) | ||
{ | ||
Language inputLanguage = grouping.Key; | ||
DynamicLanguage language; | ||
if (TryLookup(inputLanguage, out var cloned) && cloned is DynamicLanguage l) | ||
{ | ||
language = l; | ||
} else | ||
{ | ||
language = CloneLanguage(inputLanguage); | ||
} | ||
|
||
foreach ((IKeyed? keyed, _) in grouping) | ||
{ | ||
switch (keyed) | ||
{ | ||
case LanguageEntity entity: | ||
var clonedEntity = CloneEntity(entity, language); | ||
language.AddEntities([clonedEntity]); | ||
break; | ||
|
||
default: | ||
throw new ArgumentException(keyed.ToString()); | ||
} | ||
} | ||
} | ||
|
||
unclonedReferencedElements = CollectUnclonedReferencedElements(); | ||
} | ||
|
||
return; | ||
|
||
List<IGrouping<Language, KeyValuePair<IKeyed, DynamicIKeyed?>>> CollectUnclonedReferencedElements() => | ||
_dynamicMap | ||
.Where(p => p.Value == null) | ||
.GroupBy(p => p.Key.GetLanguage(), new LanguageIdentityComparer()) | ||
.ToList(); | ||
} | ||
|
||
#endregion | ||
|
||
#region References | ||
|
||
private void ResolveReferences() | ||
{ | ||
foreach (var pair in _dynamicMap) | ||
{ | ||
switch (pair) | ||
{ | ||
case (Language i, DynamicLanguage r): | ||
r.AddDependsOn(i.DependsOn.Select(Lookup)); | ||
break; | ||
case (Annotation i, DynamicAnnotation r): | ||
r.Annotates = Lookup(i.Annotates); | ||
if (i.TryGetExtends(out var exA)) | ||
r.Extends = Lookup(exA); | ||
r.AddImplements(i.Implements.Select(Lookup)); | ||
break; | ||
case (Concept i, DynamicConcept r): | ||
if (i.TryGetExtends(out var exC)) | ||
r.Extends = Lookup(exC); | ||
r.AddImplements(i.Implements.Select(Lookup)); | ||
break; | ||
case (Interface i, DynamicInterface r): | ||
r.AddExtends(i.Extends.Select(Lookup)); | ||
break; | ||
case (Property i, DynamicProperty r): | ||
r.Type = Lookup(i.Type); | ||
break; | ||
case (Link i, DynamicLink r): | ||
r.Type = Lookup(i.Type); | ||
break; | ||
case (Field i, DynamicField r): | ||
r.Type = Lookup(i.Type); | ||
break; | ||
case (Datatype, DynamicDatatype): | ||
case (EnumerationLiteral, DynamicEnumerationLiteral): | ||
break; | ||
default: | ||
throw new ArgumentOutOfRangeException(pair.ToString()); | ||
} | ||
} | ||
} | ||
|
||
private T Lookup<T>(T keyed) where T : IKeyed? | ||
{ | ||
if (keyed == null) | ||
throw new UnknownLookupException(typeof(T).FullName!); | ||
|
||
#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. | ||
return TryLookup(keyed, out var result) ? result : throw new UnknownLookupException(keyed); | ||
#pragma warning restore CS8631 | ||
} | ||
|
||
private bool TryLookup<T>(T keyed, [NotNullWhen(true)] out T? result) where T : IKeyed | ||
{ | ||
var keyedLanguage = keyed.GetLanguage(); | ||
if (keyedLanguage.Key == lionWebVersion.BuiltIns.Key) | ||
{ | ||
result = lionWebVersion.BuiltIns.FindByKey<T>(keyed.Key); | ||
return true; | ||
} | ||
|
||
if (keyedLanguage.Key == lionWebVersion.LionCore.Key) | ||
{ | ||
result = lionWebVersion.LionCore.FindByKey<T>(keyed.Key); | ||
return true; | ||
} | ||
|
||
if (_dynamicMap.TryGetValue(keyed, out var value)) | ||
{ | ||
if (value is T r) | ||
{ | ||
result = r; | ||
return true; | ||
} | ||
} | ||
|
||
result = default; | ||
return false; | ||
} | ||
|
||
#endregion | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// Copyright 2025 TRUMPF Laser SE and other contributors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License") | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
// SPDX-FileCopyrightText: 2024 TRUMPF Laser SE and other contributors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
namespace LionWeb.Core.Migration; | ||
|
||
using M3; | ||
using System.Diagnostics.CodeAnalysis; | ||
|
||
/// Provides access to all languages known during the current <see cref="IModelMigrator">migration round</see>. | ||
public interface ILanguageRegistry | ||
{ | ||
/// Version of LionWeb standard used for migration. | ||
LionWebVersions LionWebVersion { get; } | ||
|
||
/// Enumerates all languages known in the current migration round. | ||
IEnumerable<DynamicLanguage> KnownLanguages { get; } | ||
|
||
/// Find language for <paramref name="languageIdentity"/>, if known. | ||
/// <returns><c>true</c> if a language for <paramref name="languageIdentity"/> could be found; <c>false</c> otherwise.</returns> | ||
bool TryGetLanguage(LanguageIdentity languageIdentity, [NotNullWhen(true)] out DynamicLanguage? language); | ||
|
||
/// Adds a new language to the current migration round. | ||
/// Uses <see cref="LanguageIdentity.FromLanguage"/> if <paramref name="languageIdentity"/> is <c>null</c>. | ||
/// <returns><c>true</c> if the language has been added to the list of languages; | ||
/// <c>false</c> if a language with <paramref name="languageIdentity"/> is already present.</returns> | ||
bool RegisterLanguage(DynamicLanguage language, LanguageIdentity? languageIdentity = null); | ||
|
||
/// Finds the equivalent of <paramref name="keyed"/> within the current round's languages. | ||
/// <exception cref="UnknownLookupException">If no equivalent can be found for <paramref name="keyed"/>.</exception> | ||
/// <exception cref="AmbiguousLanguageKeyMapping">If not exactly one language could be mapped to the same key.</exception> | ||
T Lookup<T>(T keyed) where T : IKeyed; | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.