Skip to content

Commit bfc4e37

Browse files
committed
fix: harden ExportGeneratorX package derivation
1 parent 06de656 commit bfc4e37

4 files changed

Lines changed: 166 additions & 60 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: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,20 @@
1010
*******************************************************************************/
1111
package com.avaloq.tools.ddk.xtext.test.export;
1212

13-
import org.junit.platform.suite.api.SelectClasses;
14-
import org.junit.platform.suite.api.Suite;
15-
16-
import com.avaloq.tools.ddk.xtext.export.exporting.ExportExportingTest;
17-
import com.avaloq.tools.ddk.xtext.export.formatting.ExportFormattingTest;
18-
import com.avaloq.tools.ddk.xtext.export.scoping.ExportScopingTest;
19-
import com.avaloq.tools.ddk.xtext.export.validation.ExportValidationTest;
13+
import org.junit.platform.suite.api.SelectClasses;
14+
import org.junit.platform.suite.api.Suite;
15+
16+
import com.avaloq.tools.ddk.xtext.export.generator.ExportGeneratorXTest;
17+
import com.avaloq.tools.ddk.xtext.export.exporting.ExportExportingTest;
18+
import com.avaloq.tools.ddk.xtext.export.formatting.ExportFormattingTest;
19+
import com.avaloq.tools.ddk.xtext.export.scoping.ExportScopingTest;
20+
import com.avaloq.tools.ddk.xtext.export.validation.ExportValidationTest;
2021

2122

2223
/**
2324
* Empty class serving only as holder for JUnit4 annotations.
2425
*/
25-
@Suite
26-
@SelectClasses({ExportFormattingTest.class, ExportValidationTest.class, ExportScopingTest.class, ExportExportingTest.class})
27-
public class ExportTestSuite {
28-
}
26+
@Suite
27+
@SelectClasses({ExportFormattingTest.class, ExportValidationTest.class, ExportScopingTest.class, ExportExportingTest.class, ExportGeneratorXTest.class})
28+
public class ExportTestSuite {
29+
}

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

Lines changed: 94 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,25 @@ import com.avaloq.tools.ddk.xtext.expression.generator.EClassComparator
1919
import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtil2
2020
import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtil
2121
import com.avaloq.tools.ddk.xtext.expression.generator.Naming
22-
import com.google.common.collect.ListMultimap
23-
import com.google.inject.Inject
24-
import java.util.Collection
25-
import java.util.List
26-
import java.util.Map
22+
import com.google.common.collect.ListMultimap
23+
import com.google.inject.Inject
24+
import java.util.Collection
25+
import java.util.List
26+
import java.util.Map
2727
import org.eclipse.emf.ecore.EAttribute
2828
import org.eclipse.emf.ecore.EClass
2929
import org.eclipse.emf.ecore.EClassifier
3030
import org.eclipse.emf.ecore.EPackage
3131
import org.eclipse.xtext.Grammar
3232

33-
class ExportGeneratorX {
34-
35-
@Inject
36-
extension Naming
33+
class ExportGeneratorX {
34+
35+
static val URI_PROJECT_SEGMENT_INDEX = 1
36+
static val URI_PACKAGE_START_INDEX = 3
37+
static val DEFAULT_PACKAGE_SEGMENT = "generated"
38+
39+
@Inject
40+
extension Naming
3741

3842
def String getName(ExportModel model) {
3943
val uri = model.eResource().getURI();
@@ -50,51 +54,92 @@ class ExportGeneratorX {
5054
return grammarResource?.contents.head as Grammar
5155
}
5256

53-
def String getExportedNamesProvider(ExportModel model) {
54-
val uri = model.eResource().getURI();
55-
// 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";
57-
}
58-
59-
def String getResourceDescriptionManager(ExportModel model) {
60-
val uri = model.eResource().getURI();
61-
// 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";
63-
}
57+
def String getExportedNamesProvider(ExportModel model) {
58+
// TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)
59+
return model.basePackage + ".naming." + getName(model) + "ExportedNamesProvider";
60+
}
61+
62+
def String getResourceDescriptionManager(ExportModel model) {
63+
// TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)
64+
return model.basePackage + ".resource." + getName(model) + "ResourceDescriptionManager";
65+
}
6466

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

69-
def String getResourceDescriptionStrategy(ExportModel model) {
70-
val uri = model.eResource().getURI();
71-
// 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-
}
74-
75-
def String getResourceDescriptionConstants(ExportModel model) {
76-
val uri = model.eResource().getURI();
77-
// 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";
79-
}
80-
81-
def String getFingerprintComputer(ExportModel model) {
82-
val uri = model.eResource().getURI();
83-
// 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";
85-
}
86-
87-
def String getFragmentProvider(ExportModel model) {
88-
val uri = model.eResource().getURI();
89-
// 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";
91-
}
92-
93-
def String getExportFeatureExtension(ExportModel model) {
94-
val uri = model.eResource().getURI();
95-
// 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";
97-
}
71+
def String getResourceDescriptionStrategy(ExportModel model) {
72+
// TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)
73+
return model.basePackage + ".resource." + getName(model) + "ResourceDescriptionStrategy";
74+
}
75+
76+
def String getResourceDescriptionConstants(ExportModel model) {
77+
// TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)
78+
return model.basePackage + ".resource." + getName(model) + "ResourceDescriptionConstants";
79+
}
80+
81+
def String getFingerprintComputer(ExportModel model) {
82+
// TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)
83+
return model.basePackage + ".resource." + getName(model) + "FingerprintComputer";
84+
}
85+
86+
def String getFragmentProvider(ExportModel model) {
87+
// TODO this is a hack; to support modularization we should probably add name to export models (as with scope models)
88+
return model.basePackage + ".resource." + getName(model) + "FragmentProvider";
89+
}
90+
91+
def String getExportFeatureExtension(ExportModel model) {
92+
// TODO we still need to add a package to the models. Extension models already have a name in contrast to cases above
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
142+
}
98143

99144
/**
100145
* Return the export specification for a type's supertype, if any, or null otherwise.

0 commit comments

Comments
 (0)