diff --git a/com.avaloq.tools.ddk.xtext.export.test/resource/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorXTest.export b/com.avaloq.tools.ddk.xtext.export.test/resource/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorXTest.export new file mode 100644 index 0000000000..ee9e0900c5 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export.test/resource/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorXTest.export @@ -0,0 +1,14 @@ +import "http://www.avaloq.com/tools/ddk/xtext/export/Export" + +interface { + InterfaceExpression=unordered; + UserData=name; +} + +export InterfaceExpression as ref +{ + data ex = this.getExpr().toString(); +} +export UserData as name +{ +} 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..bdc74a89c3 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorXTest.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * 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 org.junit.jupiter.api.Test; + +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 final ExportGeneratorX exportGeneratorX = getXtextTestUtil().get(ExportGeneratorX.class); + + @Override + protected ExportTestUtil getXtextTestUtil() { + return ExportTestUtil.getInstance(); + } + + @Test + public void testShallowProjectUriFallsBackToProjectPackage() { + final ExportModel model = (ExportModel) getTestSource().getModel(); + + assertEquals("test.naming.ExportGeneratorXTestExportedNamesProvider", exportGeneratorX.getExportedNamesProvider(model)); + assertEquals("test.resource.ExportGeneratorXTestResourceDescriptionManager", exportGeneratorX.getResourceDescriptionManager(model)); + assertEquals("test.resource.ExportGeneratorXTestResourceDescriptionStrategy", exportGeneratorX.getResourceDescriptionStrategy(model)); + assertEquals("test.resource.ExportGeneratorXTestResourceDescriptionConstants", exportGeneratorX.getResourceDescriptionConstants(model)); + assertEquals("test.resource.ExportGeneratorXTestFingerprintComputer", exportGeneratorX.getFingerprintComputer(model)); + assertEquals("test.resource.ExportGeneratorXTestFragmentProvider", exportGeneratorX.getFragmentProvider(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 1eee326800..4d9dd4c841 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 @@ -10,19 +10,20 @@ *******************************************************************************/ package com.avaloq.tools.ddk.xtext.test.export; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; - -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; -import com.avaloq.tools.ddk.xtext.export.validation.ExportValidationTest; +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; +import com.avaloq.tools.ddk.xtext.export.validation.ExportValidationTest; /** * Empty class serving only as holder for JUnit4 annotations. */ -@Suite -@SelectClasses({ExportFormattingTest.class, ExportValidationTest.class, ExportScopingTest.class, ExportExportingTest.class}) -public class ExportTestSuite { -} +@Suite +@SelectClasses({ExportFormattingTest.class, ExportValidationTest.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 b88fbe9000..ab7ab7924c 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 @@ -19,21 +19,25 @@ import com.avaloq.tools.ddk.xtext.expression.generator.EClassComparator import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtil2 import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtil import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.google.common.collect.ListMultimap -import com.google.inject.Inject -import java.util.Collection -import java.util.List -import java.util.Map +import com.google.common.collect.ListMultimap +import com.google.inject.Inject +import java.util.Collection +import java.util.List +import java.util.Map import org.eclipse.emf.ecore.EAttribute import org.eclipse.emf.ecore.EClass import org.eclipse.emf.ecore.EClassifier import org.eclipse.emf.ecore.EPackage import org.eclipse.xtext.Grammar -class ExportGeneratorX { - - @Inject - extension Naming +class ExportGeneratorX { + + static val URI_PROJECT_SEGMENT_INDEX = 1 + static val URI_PACKAGE_START_INDEX = 3 + static val DEFAULT_PACKAGE_SEGMENT = "generated" + + @Inject + extension Naming def String getName(ExportModel model) { val uri = model.eResource().getURI(); @@ -50,51 +54,92 @@ class ExportGeneratorX { return grammarResource?.contents.head as Grammar } - def String getExportedNamesProvider(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".naming." + getName(model) + "ExportedNamesProvider"; - } - - def String getResourceDescriptionManager(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "ResourceDescriptionManager"; - } + def String getExportedNamesProvider(ExportModel model) { + // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) + return model.basePackage + ".naming." + getName(model) + "ExportedNamesProvider"; + } + + def String getResourceDescriptionManager(ExportModel model) { + // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) + return model.basePackage + ".resource." + getName(model) + "ResourceDescriptionManager"; + } def String getResourceDescriptionManager(Grammar grammar) { return grammar.name.toJavaPackage + ".resource." + grammar.name.toSimpleName + "ResourceDescriptionManager"; } - def String getResourceDescriptionStrategy(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "ResourceDescriptionStrategy"; - } - - def String getResourceDescriptionConstants(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "ResourceDescriptionConstants"; - } - - def String getFingerprintComputer(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "FingerprintComputer"; - } - - def String getFragmentProvider(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "FragmentProvider"; - } - - def String getExportFeatureExtension(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO we still need to add a package to the models. Extension models already have a name in contrast to cases above - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + model.name + "ExportFeatureExtension"; - } + def String getResourceDescriptionStrategy(ExportModel model) { + // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) + return model.basePackage + ".resource." + getName(model) + "ResourceDescriptionStrategy"; + } + + def String getResourceDescriptionConstants(ExportModel model) { + // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) + return model.basePackage + ".resource." + getName(model) + "ResourceDescriptionConstants"; + } + + def String getFingerprintComputer(ExportModel model) { + // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) + return model.basePackage + ".resource." + getName(model) + "FingerprintComputer"; + } + + def String getFragmentProvider(ExportModel model) { + // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) + return model.basePackage + ".resource." + getName(model) + "FragmentProvider"; + } + + def String getExportFeatureExtension(ExportModel model) { + // TODO we still need to add a package to the models. Extension models already have a name in contrast to cases above + return model.basePackage + ".resource." + model.name + "ExportFeatureExtension"; + } + + private def String getBasePackage(ExportModel model) { + val uri = model.eResource.URI + val packageFromUri = uri.packageFromUri + if (packageFromUri !== null) { + return packageFromUri + } + return uri.fallbackPackage + } + + private def String getPackageFromUri(org.eclipse.emf.common.util.URI uri) { + val packageSegments = uri.segmentsList + if (packageSegments.size > URI_PACKAGE_START_INDEX + 1 && "src".equals(packageSegments.get(URI_PROJECT_SEGMENT_INDEX + 1))) { + return String.join(".", packageSegments.subList(URI_PACKAGE_START_INDEX, uri.segmentCount - 1)) + } + return null + } + + private def String getFallbackPackage(org.eclipse.emf.common.util.URI uri) { + val segments = uri.segmentsList + if (segments.size > URI_PROJECT_SEGMENT_INDEX) { + return segments.get(URI_PROJECT_SEGMENT_INDEX).safePackageSegment + } + return DEFAULT_PACKAGE_SEGMENT + } + + private def String getSafePackageSegment(String segment) { + if (segment === null || segment.empty) { + return DEFAULT_PACKAGE_SEGMENT + } + val normalizedSegment = segment.toLowerCase + val builder = new StringBuilder() + for (var i = 0; i < normalizedSegment.length; i++) { + val character = normalizedSegment.charAt(i) + if (builder.length == 0) { + if (Character.isJavaIdentifierStart(character)) { + builder.append(character) + } else if (Character.isJavaIdentifierPart(character)) { + builder.append('_').append(character) + } else { + builder.append('_') + } + } else { + builder.append(if (Character.isJavaIdentifierPart(character)) character else '_') + } + } + return if (builder.length == 0) DEFAULT_PACKAGE_SEGMENT else builder.toString + } /** * Return the export specification for a type's supertype, if any, or null otherwise.