From fcd605b490802bff253fe90c2e477275553bd255 Mon Sep 17 00:00:00 2001 From: samatstarion Date: Sun, 21 Jun 2026 17:43:28 +0200 Subject: [PATCH] [Feature] implement Resource.GetURIFragment as the inverse of GetEObject; fixes #76 Return the EObject.Identifier under which the object is cached during load (the name-based Ecore reference that GetEObject resolves), guaranteeing the GetEObject(GetURIFragment(x))==x round-trip. Guards null and objects not contained in the resource with descriptive exceptions. --- .../Resource/GetUriFragmentTestFixture.cs | 119 ++++++++++++++++++ ECoreNetto/Resource/Resource.cs | 31 ++++- 2 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 ECoreNetto.Tests/Resource/GetUriFragmentTestFixture.cs diff --git a/ECoreNetto.Tests/Resource/GetUriFragmentTestFixture.cs b/ECoreNetto.Tests/Resource/GetUriFragmentTestFixture.cs new file mode 100644 index 0000000..4ece0a6 --- /dev/null +++ b/ECoreNetto.Tests/Resource/GetUriFragmentTestFixture.cs @@ -0,0 +1,119 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2017-2025 Starion Group S.A. +// +// 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. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace ECoreNetto.Tests.Resource +{ + using System; + using System.IO; + using System.Linq; + + using ECoreNetto.Resource; + + using NUnit.Framework; + + /// + /// Suite of tests that verify is the inverse of + /// and is appropriately guarded (see issue #76). + /// + [TestFixture] + public class GetUriFragmentTestFixture + { + private ResourceSet resourceSet = null!; + + private Resource resource = null!; + + private EPackage rootPackage = null!; + + [SetUp] + public void SetUp() + { + var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "recipe.ecore"); + var uri = new Uri(Path.GetFullPath(path)); + + this.resourceSet = new ResourceSet(); + this.resource = this.resourceSet.CreateResource(uri); + this.rootPackage = this.resource.Load(null); + } + + [Test] + public void Verify_that_the_root_package_round_trips() + { + var fragment = this.resource.GetURIFragment(this.rootPackage); + + Assert.That(this.resource.GetEObject(fragment), Is.SameAs(this.rootPackage)); + } + + [Test] + public void Verify_that_a_class_round_trips() + { + var eClass = this.rootPackage.EClassifiers.OfType().First(); + + var fragment = this.resource.GetURIFragment(eClass); + + Assert.Multiple(() => + { + Assert.That(fragment, Is.EqualTo(eClass.Identifier)); + Assert.That(this.resource.GetEObject(fragment), Is.SameAs(eClass)); + }); + } + + [Test] + public void Verify_that_a_structural_feature_round_trips() + { + var feature = this.rootPackage.EClassifiers + .OfType() + .SelectMany(eClass => eClass.EStructuralFeatures) + .First(); + + var fragment = this.resource.GetURIFragment(feature); + + Assert.That(this.resource.GetEObject(fragment), Is.SameAs(feature)); + } + + [Test] + public void Verify_that_an_enum_literal_round_trips() + { + // the recipe model defines the 'Unit' enumeration with a 'PIECE' literal + var literal = this.resource.GetEObject("recipe.ecore#//Unit/PIECE"); + Assert.That(literal, Is.Not.Null); + + var fragment = this.resource.GetURIFragment(literal!); + + Assert.That(this.resource.GetEObject(fragment), Is.SameAs(literal)); + } + + [Test] + public void Verify_that_a_null_object_throws() + { + Assert.That(() => this.resource.GetURIFragment(null!), Throws.ArgumentNullException); + } + + [Test] + public void Verify_that_an_object_not_contained_in_the_resource_throws() + { + var eClass = this.rootPackage.EClassifiers.OfType().First(); + + // a different resource does not contain the object, so a fragment cannot be produced + var otherResource = new Resource(); + + Assert.That(() => otherResource.GetURIFragment(eClass), Throws.TypeOf()); + } + } +} diff --git a/ECoreNetto/Resource/Resource.cs b/ECoreNetto/Resource/Resource.cs index 4084d99..4bbfd74 100644 --- a/ECoreNetto/Resource/Resource.cs +++ b/ECoreNetto/Resource/Resource.cs @@ -201,7 +201,7 @@ public IEnumerable AllContents() } /// - /// Returns the URI fragment that, when passed to getEObject will return the given object. + /// Returns the URI fragment that, when passed to , will return the given object. /// /// /// The object to identify @@ -209,9 +209,36 @@ public IEnumerable AllContents() /// /// the URI fragment for the object. /// + /// + /// The returned fragment is the name-based Ecore reference under which the object is registered in this + /// resource (its ), for example recipe.ecore#//Recipe for a class or + /// EStructuralFeature::recipe.ecore#//Recipe/ingredients for a structural feature. This is exactly the + /// reference string consumed by , so the round-trip + /// GetEObject(GetURIFragment(eObject)) returns the same instance. + /// + /// + /// Thrown when is null. + /// + /// + /// Thrown when is not contained in this resource and therefore cannot be turned + /// into a resolvable URI fragment. + /// public string GetURIFragment(EObject eObject) { - throw new NotImplementedException(); + if (eObject == null) + { + throw new ArgumentNullException(nameof(eObject)); + } + + var fragment = eObject.Identifier; + + if (string.IsNullOrEmpty(fragment) || !this.Cache.TryGetValue(fragment, out var cached) || !ReferenceEquals(cached, eObject)) + { + throw new InvalidOperationException( + $"The provided '{eObject.GetType().Name}' is not contained in this resource and cannot be turned into a URI fragment."); + } + + return fragment; } ///