diff --git a/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorXTest.java b/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorXTest.java new file mode 100644 index 0000000000..01d90afb2e --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorXTest.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2026 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.export.generator; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.InternalEObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.impl.ResourceImpl; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.XtextFactory; +import org.junit.jupiter.api.Test; + +import com.avaloq.tools.ddk.xtext.export.export.ExportFactory; +import com.avaloq.tools.ddk.xtext.export.export.ExportModel; +import com.avaloq.tools.ddk.xtext.test.export.util.ExportTestUtil; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTest; + + +/** + * Regression tests for URI-based package derivation in {@link ExportGeneratorX}. + */ +@SuppressWarnings("nls") +public class ExportGeneratorXTest extends AbstractXtextTest { + + private static final String DERIVED_PACKAGE_PROVIDER = "com.avaloq.naming.fooExportedNamesProvider"; + private static final String PROJECT_FALLBACK_PROVIDER = "MyProject.naming.fooExportedNamesProvider"; + private static final String DEFAULT_FALLBACK_PROVIDER = "generated.naming.fooExportedNamesProvider"; + private static final String NO_SIBLING_URI = "platform:/resource/TEST/NoSibling.export"; + + private final ExportGeneratorX exportGeneratorX = getXtextTestUtil().get(ExportGeneratorX.class); + + @Override + protected ExportTestUtil getXtextTestUtil() { + return ExportTestUtil.getInstance(); + } + + @Override + protected String getTestSourceFileName() { + return null; // all tests fabricate synthetic resource URIs + } + + @Test + public void testNestedSourceFolderUrisKeepDerivedPackage() { + assertEquals(DERIVED_PACKAGE_PROVIDER, exportedNamesProviderFor("platform:/resource/MyProject/src/com/avaloq/foo.export")); + assertEquals(DERIVED_PACKAGE_PROVIDER, exportedNamesProviderFor("platform:/resource/MyProject/src-gen/com/avaloq/foo.export")); + assertEquals("main.java.com.avaloq.naming.fooExportedNamesProvider", exportedNamesProviderFor("platform:/resource/MyProject/src/main/java/com/avaloq/foo.export")); + } + + @Test + public void testShortUrisFallBackToValidPackage() { + assertEquals(PROJECT_FALLBACK_PROVIDER, exportedNamesProviderFor("platform:/resource/MyProject/foo.export")); + assertEquals(PROJECT_FALLBACK_PROVIDER, exportedNamesProviderFor("platform:/resource/MyProject/src/foo.export")); + assertEquals(DEFAULT_FALLBACK_PROVIDER, exportedNamesProviderFor("platform:/resource/1My-Project/foo.export")); + } + + @Test + public void testSingleSegmentUriFallsBackToDefaultPackage() { + assertEquals(DEFAULT_FALLBACK_PROVIDER, exportedNamesProviderFor("foo.export")); + } + + @Test + public void testGetGrammarReturnsNullWithoutResolvableGrammar() { + final ExportModel detached = ExportFactory.eINSTANCE.createExportModel(); + assertNull(exportGeneratorX.getGrammar(detached), "model without a resource"); + + final ExportModel withoutResourceSet = ExportFactory.eINSTANCE.createExportModel(); + new ResourceImpl(URI.createURI(NO_SIBLING_URI)).getContents().add(withoutResourceSet); + assertNull(exportGeneratorX.getGrammar(withoutResourceSet), "resource without a resource set"); + + final ExportModel withoutSibling = ExportFactory.eINSTANCE.createExportModel(); + final Resource resource = new ResourceSetImpl().createResource(URI.createURI(NO_SIBLING_URI)); + resource.getContents().add(withoutSibling); + assertNull(exportGeneratorX.getGrammar(withoutSibling), "missing sibling grammar must not throw"); + + final Grammar proxy = XtextFactory.eINSTANCE.createGrammar(); + ((InternalEObject) proxy).eSetProxyURI(URI.createURI("platform:/resource/TEST/Unresolved.xtext#/")); + withoutSibling.setTargetGrammar(proxy); + assertNull(exportGeneratorX.getGrammar(withoutSibling), "unresolved targetGrammar proxy"); + } + + private String exportedNamesProviderFor(final String uri) { + final Resource resource = new ResourceImpl(URI.createURI(uri)); + final ExportModel model = ExportFactory.eINSTANCE.createExportModel(); + resource.getContents().add(model); + return exportGeneratorX.getExportedNamesProvider(model); + } +} diff --git a/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/test/export/ExportTestSuite.java b/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/test/export/ExportTestSuite.java index ea8a86330d..a7de215fc2 100644 --- a/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/test/export/ExportTestSuite.java +++ b/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/test/export/ExportTestSuite.java @@ -13,6 +13,7 @@ import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; +import com.avaloq.tools.ddk.xtext.export.generator.ExportGeneratorXTest; import com.avaloq.tools.ddk.xtext.export.exporting.ExportExportingTest; import com.avaloq.tools.ddk.xtext.export.formatting.ExportFormattingTest; import com.avaloq.tools.ddk.xtext.export.scoping.ExportScopingTest; @@ -24,6 +25,6 @@ * Empty class serving only as holder for JUnit 5 suite annotations. */ @Suite -@SelectClasses({ExportFormattingTest.class, ExportValidationTest.class, ExportValidationOkTest.class, ExportScopingTest.class, ExportExportingTest.class}) +@SelectClasses({ExportFormattingTest.class, ExportValidationTest.class, ExportValidationOkTest.class, ExportScopingTest.class, ExportExportingTest.class, ExportGeneratorXTest.class}) public class ExportTestSuite { } diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.xtend index 23325a04ae..82cf5e6f9d 100644 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.xtend +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.xtend @@ -24,6 +24,7 @@ import com.google.inject.Inject import java.util.Collection import java.util.List import java.util.Map +import javax.lang.model.SourceVersion import org.eclipse.emf.ecore.EAttribute import org.eclipse.emf.ecore.EClass import org.eclipse.emf.ecore.EClassifier @@ -42,22 +43,35 @@ class ExportGeneratorX { } def Grammar getGrammar(ExportModel model) { - val uri = model.eResource.URI // Grammar should be set correctly for export extensions, not yet for normal export sources - if(model.targetGrammar !== null) { + if (model.targetGrammar !== null && !model.targetGrammar.eIsProxy) { return model.targetGrammar; } - val grammarResource = model.eResource.resourceSet.getResource(uri.trimSegments(1).appendSegment(uri.trimFileExtension.lastSegment + '.xtext'), true) - return grammarResource?.contents.head as Grammar + val resource = model.eResource + if (resource?.resourceSet === null) { + return null + } + val uri = resource.URI + try { + val grammarResource = resource.resourceSet.getResource(uri.trimSegments(1).appendSegment(uri.trimFileExtension.lastSegment + '.xtext'), true) + return grammarResource?.contents.head as Grammar + } catch (RuntimeException e) { + // no resolvable sibling grammar; all generators tolerate a null grammar + return null + } } def List getPrefix(URI uri) { // TODO we still need to add a package to the models. Extension models already have a name in contrast to cases above - if (uri.segmentsList().size > 3) { - return uri.segmentsList().subList(3, uri.segmentCount() - 1); - } else { - return uri.segmentsList().subList(uri.segmentCount() - 2, uri.segmentCount() - 1); + val segments = uri.segmentsList + if (segments.size > 4) { + return segments.subList(3, segments.size - 1) + } + // shorter shapes have no package segments: fall back to a valid package name + if (segments.size > 2 && SourceVersion.isName(segments.get(1))) { + return #[segments.get(1)] } + return #['generated'] } def String getExportedNamesProvider(ExportModel model) { diff --git a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GeneratorUtil.java b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GeneratorUtil.java index 0a32edf62b..c0e0a73cd7 100644 --- a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GeneratorUtil.java +++ b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GeneratorUtil.java @@ -55,7 +55,9 @@ protected GeneratorUtil() { */ public static Set allInstantiatedTypes(final Grammar grammar) { Set result = Sets.newLinkedHashSet(); - collectInstantiatedTypes(grammar, result); + if (grammar != null) { // no grammar -> no parser-instantiated subtypes + collectInstantiatedTypes(grammar, result); + } return result; }