Skip to content

Commit 4ee1c4e

Browse files
committed
fix: harden ExportGeneratorX package derivation
1 parent 89e13c5 commit 4ee1c4e

4 files changed

Lines changed: 121 additions & 15 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import "http://www.avaloq.com/tools/ddk/xtext/export/Export"
2+
3+
interface {
4+
InterfaceExpression=unordered;
5+
UserData=name;
6+
}
7+
8+
export InterfaceExpression as ref
9+
{
10+
data ex = this.getExpr().toString();
11+
}
12+
export UserData as name
13+
{
14+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Avaloq Group AG and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Avaloq Group AG - initial API and implementation
10+
*******************************************************************************/
11+
package com.avaloq.tools.ddk.xtext.export.generator;
12+
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
15+
import org.junit.jupiter.api.Test;
16+
17+
import com.avaloq.tools.ddk.xtext.export.export.ExportModel;
18+
import com.avaloq.tools.ddk.xtext.test.export.util.ExportTestUtil;
19+
import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTest;
20+
21+
22+
/**
23+
* Regression tests for URI-based package derivation in {@link ExportGeneratorX}.
24+
*/
25+
@SuppressWarnings("nls")
26+
public class ExportGeneratorXTest extends AbstractXtextTest {
27+
28+
private final ExportGeneratorX exportGeneratorX = getXtextTestUtil().get(ExportGeneratorX.class);
29+
30+
@Override
31+
protected ExportTestUtil getXtextTestUtil() {
32+
return ExportTestUtil.getInstance();
33+
}
34+
35+
@Test
36+
public void testShallowProjectUriFallsBackToProjectPackage() {
37+
final ExportModel model = (ExportModel) getTestSource().getModel();
38+
39+
assertEquals("test.naming.ExportGeneratorXTestExportedNamesProvider", exportGeneratorX.getExportedNamesProvider(model));
40+
assertEquals("test.resource.ExportGeneratorXTestResourceDescriptionManager", exportGeneratorX.getResourceDescriptionManager(model));
41+
assertEquals("test.resource.ExportGeneratorXTestResourceDescriptionStrategy", exportGeneratorX.getResourceDescriptionStrategy(model));
42+
assertEquals("test.resource.ExportGeneratorXTestResourceDescriptionConstants", exportGeneratorX.getResourceDescriptionConstants(model));
43+
assertEquals("test.resource.ExportGeneratorXTestFingerprintComputer", exportGeneratorX.getFingerprintComputer(model));
44+
assertEquals("test.resource.ExportGeneratorXTestFragmentProvider", exportGeneratorX.getFragmentProvider(model));
45+
}
46+
}

com.avaloq.tools.ddk.xtext.export.test/src/com/avaloq/tools/ddk/xtext/test/export/ExportTestSuite.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.junit.platform.suite.api.SelectClasses;
1414
import org.junit.platform.suite.api.Suite;
1515

16+
import com.avaloq.tools.ddk.xtext.export.generator.ExportGeneratorXTest;
1617
import com.avaloq.tools.ddk.xtext.export.exporting.ExportExportingTest;
1718
import com.avaloq.tools.ddk.xtext.export.formatting.ExportFormattingTest;
1819
import com.avaloq.tools.ddk.xtext.export.scoping.ExportScopingTest;
@@ -24,6 +25,6 @@
2425
* Empty class serving only as holder for JUnit4 annotations.
2526
*/
2627
@Suite
27-
@SelectClasses({ExportFormattingTest.class, ExportValidationTest.class, ExportValidationOkTest.class, ExportScopingTest.class, ExportExportingTest.class})
28+
@SelectClasses({ExportFormattingTest.class, ExportValidationTest.class, ExportValidationOkTest.class, ExportScopingTest.class, ExportExportingTest.class, ExportGeneratorXTest.class})
2829
public class ExportTestSuite {
2930
}

com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.xtend

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ import org.eclipse.xtext.Grammar
3232

3333
class ExportGeneratorX {
3434

35+
static val URI_PROJECT_SEGMENT_INDEX = 1
36+
static val URI_PACKAGE_START_INDEX = 3
37+
static val DEFAULT_PACKAGE_SEGMENT = "generated"
38+
3539
@Inject
3640
extension Naming
3741

@@ -51,49 +55,90 @@ class ExportGeneratorX {
5155
}
5256

5357
def String getExportedNamesProvider(ExportModel model) {
54-
val uri = model.eResource().getURI();
5558
// TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)
56-
return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".naming." + getName(model) + "ExportedNamesProvider";
59+
return model.basePackage + ".naming." + getName(model) + "ExportedNamesProvider";
5760
}
5861

5962
def String getResourceDescriptionManager(ExportModel model) {
60-
val uri = model.eResource().getURI();
6163
// TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)
62-
return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "ResourceDescriptionManager";
64+
return model.basePackage + ".resource." + getName(model) + "ResourceDescriptionManager";
6365
}
6466

6567
def String getResourceDescriptionManager(Grammar grammar) {
6668
return grammar.name.toJavaPackage + ".resource." + grammar.name.toSimpleName + "ResourceDescriptionManager";
6769
}
6870

6971
def String getResourceDescriptionStrategy(ExportModel model) {
70-
val uri = model.eResource().getURI();
7172
// TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)
72-
return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "ResourceDescriptionStrategy";
73+
return model.basePackage + ".resource." + getName(model) + "ResourceDescriptionStrategy";
7374
}
7475

7576
def String getResourceDescriptionConstants(ExportModel model) {
76-
val uri = model.eResource().getURI();
7777
// TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)
78-
return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "ResourceDescriptionConstants";
78+
return model.basePackage + ".resource." + getName(model) + "ResourceDescriptionConstants";
7979
}
8080

8181
def String getFingerprintComputer(ExportModel model) {
82-
val uri = model.eResource().getURI();
8382
// TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)
84-
return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "FingerprintComputer";
83+
return model.basePackage + ".resource." + getName(model) + "FingerprintComputer";
8584
}
8685

8786
def String getFragmentProvider(ExportModel model) {
88-
val uri = model.eResource().getURI();
8987
// TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)
90-
return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "FragmentProvider";
88+
return model.basePackage + ".resource." + getName(model) + "FragmentProvider";
9189
}
9290

9391
def String getExportFeatureExtension(ExportModel model) {
94-
val uri = model.eResource().getURI();
9592
// TODO we still need to add a package to the models. Extension models already have a name in contrast to cases above
96-
return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + model.name + "ExportFeatureExtension";
93+
return model.basePackage + ".resource." + model.name + "ExportFeatureExtension";
94+
}
95+
96+
private def String getBasePackage(ExportModel model) {
97+
val uri = model.eResource.URI
98+
val packageFromUri = uri.packageFromUri
99+
if (packageFromUri !== null) {
100+
return packageFromUri
101+
}
102+
return uri.fallbackPackage
103+
}
104+
105+
private def String getPackageFromUri(org.eclipse.emf.common.util.URI uri) {
106+
val packageSegments = uri.segmentsList
107+
if (packageSegments.size > URI_PACKAGE_START_INDEX + 1 && "src".equals(packageSegments.get(URI_PROJECT_SEGMENT_INDEX + 1))) {
108+
return String.join(".", packageSegments.subList(URI_PACKAGE_START_INDEX, uri.segmentCount - 1))
109+
}
110+
return null
111+
}
112+
113+
private def String getFallbackPackage(org.eclipse.emf.common.util.URI uri) {
114+
val segments = uri.segmentsList
115+
if (segments.size > URI_PROJECT_SEGMENT_INDEX) {
116+
return segments.get(URI_PROJECT_SEGMENT_INDEX).safePackageSegment
117+
}
118+
return DEFAULT_PACKAGE_SEGMENT
119+
}
120+
121+
private def String getSafePackageSegment(String segment) {
122+
if (segment === null || segment.empty) {
123+
return DEFAULT_PACKAGE_SEGMENT
124+
}
125+
val normalizedSegment = segment.toLowerCase
126+
val builder = new StringBuilder()
127+
for (var i = 0; i < normalizedSegment.length; i++) {
128+
val character = normalizedSegment.charAt(i)
129+
if (builder.length == 0) {
130+
if (Character.isJavaIdentifierStart(character)) {
131+
builder.append(character)
132+
} else if (Character.isJavaIdentifierPart(character)) {
133+
builder.append('_').append(character)
134+
} else {
135+
builder.append('_')
136+
}
137+
} else {
138+
builder.append(if (Character.isJavaIdentifierPart(character)) character else '_')
139+
}
140+
}
141+
return if (builder.length == 0) DEFAULT_PACKAGE_SEGMENT else builder.toString
97142
}
98143

99144
/**

0 commit comments

Comments
 (0)