From c208af0a685faf1ebfe3127d9ee70f1a3ca5c115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Mon, 7 Sep 2026 14:11:17 +0200 Subject: [PATCH 1/2] refactor(scope): clarify generated method bodies --- .../generator/ScopeNameProviderGenerator.java | 155 +++--- .../generator/ScopeProviderGenerator.java | 477 ++++++++---------- 2 files changed, 267 insertions(+), 365 deletions(-) diff --git a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeNameProviderGenerator.java b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeNameProviderGenerator.java index 4275f3d37..38ac86196 100644 --- a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeNameProviderGenerator.java +++ b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeNameProviderGenerator.java @@ -43,8 +43,6 @@ @SuppressWarnings({"checkstyle:MethodName", "nls", "PMD.UnusedFormalParameter"}) public class ScopeNameProviderGenerator { - // CPD-OFF — migrated Xtend generator code, kept faithful; de-dup is a migration follow-up (#1339) - @Inject private GeneratorUtilX generatorUtilX; @@ -79,58 +77,19 @@ public void configure(final GenModelUtilX genModelUtilX, final ScopeModel model) * Produces the body of the {@code internalGetNameFunctions(EClass)} method. Extracted so the Xbase based * {@code ScopeJvmModelInferrer} can attach it directly as a method body. * - * @param it + * @param model * the scope model, must not be {@code null} * @return the method body, never {@code null} */ - public CharSequence internalGetNameFunctionsBody(final ScopeModel it) { + public CharSequence internalGetNameFunctionsBody(final ScopeModel model) { final StringConcatenation builder = new StringConcatenation(); - if (it.getNaming() != null) { + if (model.getNaming() != null) { final Set packages = new LinkedHashSet<>(); - for (final NamingDefinition naming : it.getNaming().getNamings()) { - packages.add(naming.getType().getEPackage()); + for (final NamingDefinition definition : model.getNaming().getNamings()) { + packages.add(definition.getType().getEPackage()); } - for (final EPackage p : packages) { - builder.append("if ("); - builder.append(genModelUtil.qualifiedPackageInterfaceName(p)); - builder.append(".eINSTANCE == eClass.getEPackage()) {"); - builder.newLineIfNotEmpty(); - builder.append(" "); - builder.append("switch (eClass.getClassifierID()) {"); - builder.newLine(); - builder.newLine(); - for (final NamingDefinition n : it.getNaming().getNamings()) { - if (!Objects.equals(n.getType().getEPackage(), p)) { - continue; - } - builder.append(" "); - builder.append("case "); - builder.append(genModelUtil.classifierIdLiteral(n.getType()), " "); - builder.append(":"); - builder.newLineIfNotEmpty(); - builder.append(" "); - builder.append(" "); - builder.append(generatorUtilX.javaContributorComment(generatorUtilX.location(n)), " "); - builder.newLineIfNotEmpty(); - builder.append(" "); - builder.append(" "); - builder.append("return "); - builder.append(nameFunctions(n.getNaming(), it), " "); - builder.append(";"); - builder.newLineIfNotEmpty(); - } - builder.newLine(); - builder.append(" "); - builder.append("default:"); - builder.newLine(); - builder.append(" "); - builder.append("return !eClass.getESuperTypes().isEmpty() ? getNameFunctions(eClass.getESuperTypes().get(0)) : null;"); - builder.newLine(); - builder.append(" "); - builder.append("}"); - builder.newLine(); - builder.append("}"); - builder.newLine(); + for (final EPackage ePackage : packages) { + appendPackageSwitch(builder, ePackage, model); } } builder.append("return !eClass.getESuperTypes().isEmpty() ? getNameFunctions(eClass.getESuperTypes().get(0)) : null;"); @@ -138,6 +97,46 @@ public CharSequence internalGetNameFunctionsBody(final ScopeModel it) { return builder; } + /** Emits naming cases for one package, retaining definition order. */ + private void appendPackageSwitch(final StringConcatenation builder, final EPackage ePackage, final ScopeModel model) { + builder.append("if ("); + builder.append(genModelUtil.qualifiedPackageInterfaceName(ePackage)); + builder.append(".eINSTANCE == eClass.getEPackage()) {"); + builder.newLineIfNotEmpty(); + builder.append(""" + switch (eClass.getClassifierID()) { + + """); + for (final NamingDefinition definition : model.getNaming().getNamings()) { + if (!Objects.equals(definition.getType().getEPackage(), ePackage)) { + continue; + } + appendNamingCase(builder, definition, model); + } + builder.newLine(); + builder.append(""" + default: + return !eClass.getESuperTypes().isEmpty() ? getNameFunctions(eClass.getESuperTypes().get(0)) : null; + } + } + """); + } + + /** Emits a classifier case, including indentation of multiline comments and name functions. */ + private void appendNamingCase(final StringConcatenation builder, final NamingDefinition definition, final ScopeModel model) { + builder.append(" case "); + builder.append(genModelUtil.classifierIdLiteral(definition.getType()), " "); + builder.append(":"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(generatorUtilX.javaContributorComment(generatorUtilX.location(definition)), " "); + builder.newLineIfNotEmpty(); + builder.append(" return "); + builder.append(nameFunctions(definition.getNaming(), model), " "); + builder.append(";"); + builder.newLineIfNotEmpty(); + } + public CharSequence nameFunctions(final Naming it, final ScopeModel model) { return nameFunctions(it, model, null, null); } @@ -189,8 +188,7 @@ protected String _nameFunction(final IntegerLiteral it, final ScopeModel model, return "com.avaloq.tools.ddk.xtext.scoping.NameFunctions.fromConstant(String.valueOf(" + it.getVal() + "))"; } - protected String _nameFunction(final FeatureCall it, final ScopeModel model, final String contextName, - final EClass contextType) { + protected String _nameFunction(final FeatureCall it, final ScopeModel model, final String contextName, final EClass contextType) { final StringConcatenation builder = new StringConcatenation(); final ScopeTranslationContext currentContext = newContext(it, contextName, contextType); builder.newLineIfNotEmpty(); @@ -200,23 +198,7 @@ protected String _nameFunction(final FeatureCall it, final ScopeModel model, fin builder.append(")"); } else if (compiler.isSimpleNavigation(it, currentContext)) { builder.newLineIfNotEmpty(); - builder.append("object -> {"); - builder.newLine(); - builder.append(" "); - builder.append("final "); - builder.append(genModelUtil.instanceClassName(scopeProviderX.scopeType(it)), " "); - builder.append(" obj = ("); - builder.append(genModelUtil.instanceClassName(scopeProviderX.scopeType(it)), " "); - builder.append(") object;"); - builder.newLineIfNotEmpty(); - builder.append(" "); - builder.append("return toQualifiedName("); - builder.append(compiler.javaExpression(it, currentContext), " "); - builder.append(");"); - builder.newLineIfNotEmpty(); - builder.append(" "); - builder.append("}"); - builder.newLine(); + appendNameFunctionLambda(builder, it, currentContext); } else { builder.append("EXPRESSION_NOT_SUPPORTED(\""); builder.append(ExpressionExtensions.serialize(it)); @@ -225,29 +207,12 @@ protected String _nameFunction(final FeatureCall it, final ScopeModel model, fin return builder.toString(); } - protected String _nameFunction(final OperationCall it, final ScopeModel model, final String contextName, - final EClass contextType) { + protected String _nameFunction(final OperationCall it, final ScopeModel model, final String contextName, final EClass contextType) { final StringConcatenation builder = new StringConcatenation(); final ScopeTranslationContext currentContext = newContext(it, contextName, contextType); builder.newLineIfNotEmpty(); if (compiler.isCompilable(it, currentContext)) { - builder.append("object -> {"); - builder.newLine(); - builder.append(" "); - builder.append("final "); - builder.append(genModelUtil.instanceClassName(scopeProviderX.scopeType(it)), " "); - builder.append(" obj = ("); - builder.append(genModelUtil.instanceClassName(scopeProviderX.scopeType(it)), " "); - builder.append(") object;"); - builder.newLineIfNotEmpty(); - builder.append(" "); - builder.append("return toQualifiedName("); - builder.append(compiler.javaExpression(it, currentContext), " "); - builder.append(");"); - builder.newLineIfNotEmpty(); - builder.append(" "); - builder.append("}"); - builder.newLine(); + appendNameFunctionLambda(builder, it, currentContext); builder.append(" "); } else { builder.append("EXPRESSION_NOT_SUPPORTED(\""); @@ -256,6 +221,24 @@ protected String _nameFunction(final OperationCall it, final ScopeModel model, f } return builder.toString(); } + + /** Emits a typed receiver lambda that converts the expression result to a qualified name. */ + private void appendNameFunctionLambda(final StringConcatenation builder, final Expression expression, final ScopeTranslationContext context) { + builder.append("object -> {"); + builder.newLine(); + builder.append(" final "); + builder.append(genModelUtil.instanceClassName(scopeProviderX.scopeType(expression)), " "); + builder.append(" obj = ("); + builder.append(genModelUtil.instanceClassName(scopeProviderX.scopeType(expression)), " "); + builder.append(") object;"); + builder.newLineIfNotEmpty(); + builder.append(" return toQualifiedName("); + builder.append(compiler.javaExpression(expression, context), " "); + builder.append(");"); + builder.newLineIfNotEmpty(); + builder.append(" }"); + builder.newLine(); + } // CHECKSTYLE:CONSTANTS-ON /** diff --git a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderGenerator.java b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderGenerator.java index 417e324e9..43c8192aa 100644 --- a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderGenerator.java +++ b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderGenerator.java @@ -18,9 +18,11 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.ENamedElement; import org.eclipse.xtend2.lib.StringConcatenation; import org.eclipse.xtext.util.Strings; import org.eclipse.xtext.xbase.lib.Pair; @@ -58,8 +60,6 @@ @SuppressWarnings({"checkstyle:MethodName", "nls", "PMD.UnusedFormalParameter"}) public class ScopeProviderGenerator { - // CPD-OFF — migrated Xtend generator code, kept faithful; de-dup is a migration follow-up (#1339) - @Inject private GeneratorUtilX generatorUtilX; @@ -98,74 +98,36 @@ public void configure(final ScopeNameProviderGenerator nameGenerator, final GenM * Produces the body of the {@code doGetScope(EObject, EReference, String, Resource)} method. Extracted so the * Xbase based {@code ScopeJvmModelInferrer} can attach it directly as a method body. * - * @param it + * @param model * the scope model, must not be {@code null} * @return the method body, never {@code null} */ - public CharSequence doGetScopeByReferenceBody(final ScopeModel it) { - final StringConcatenation builder = new StringConcatenation(); - final List scopes = scopesWithReference(it, true); - if (!scopes.isEmpty()) { - builder.append("if (scopeName == null) {"); - builder.newLine(); - builder.append(" "); - builder.append("return null;"); - builder.newLine(); - builder.append("}"); - builder.newLine(); - builder.newLine(); - builder.append("switch (scopeName) {"); - builder.newLine(); - for (final String name : scopeNames(scopes)) { - builder.append("case \""); - builder.append(name); - builder.append("\":"); - builder.newLineIfNotEmpty(); - for (final ScopeDefinition scope : namedScopes(scopes, name)) { - builder.append(" "); - builder.append("if (reference == "); - builder.append(genModelUtil.literalIdentifier(scope.getReference()), " "); - builder.append(") return "); - builder.append(scopeProviderX.scopeMethodName(scope), " "); - builder.append("(context, reference, originalResource);"); - builder.newLineIfNotEmpty(); - } - builder.append(" "); - builder.append("break;"); - builder.newLine(); - } - builder.append(" "); - builder.append("default: break;"); - builder.newLine(); - builder.append("}"); - builder.newLine(); - } - builder.append("return null;"); - builder.newLine(); - return builder; + public CharSequence doGetScopeByReferenceBody(final ScopeModel model) { + return renderScopeDispatch(scopesWithReference(model, true), "reference", ScopeDefinition::getReference); } /** * Produces the body of the {@code doGetScope(EObject, EClass, String, Resource)} method. * - * @param it + * @param model * the scope model, must not be {@code null} * @return the method body, never {@code null} */ - public CharSequence doGetScopeByTypeBody(final ScopeModel it) { + public CharSequence doGetScopeByTypeBody(final ScopeModel model) { + return renderScopeDispatch(scopesWithReference(model, false), "type", ScopeDefinition::getTargetType); + } + + /** Emits the name switch while retaining the reference/type distinction at each case. */ + private CharSequence renderScopeDispatch(final List scopes, final String argumentName, final Function target) { final StringConcatenation builder = new StringConcatenation(); - final List scopes = scopesWithReference(it, false); if (!scopes.isEmpty()) { - builder.append("if (scopeName == null) {"); - builder.newLine(); - builder.append(" "); - builder.append("return null;"); - builder.newLine(); - builder.append("}"); - builder.newLine(); - builder.newLine(); - builder.append("switch (scopeName) {"); - builder.newLine(); + builder.append(""" + if (scopeName == null) { + return null; + } + + switch (scopeName) { + """); for (final String name : scopeNames(scopes)) { builder.append("case \""); builder.append(name); @@ -173,22 +135,20 @@ public CharSequence doGetScopeByTypeBody(final ScopeModel it) { builder.newLineIfNotEmpty(); for (final ScopeDefinition scope : namedScopes(scopes, name)) { builder.append(" "); - builder.append("if (type == "); - builder.append(genModelUtil.literalIdentifier(scope.getTargetType()), " "); + builder.append("if (" + argumentName + " == "); + builder.append(genModelUtil.literalIdentifier(target.apply(scope)), " "); builder.append(") return "); builder.append(scopeProviderX.scopeMethodName(scope), " "); - builder.append("(context, type, originalResource);"); + builder.append("(context, " + argumentName + ", originalResource);"); builder.newLineIfNotEmpty(); } - builder.append(" "); - builder.append("break;"); + builder.append(" break;"); builder.newLine(); } - builder.append(" "); - builder.append("default: break;"); - builder.newLine(); - builder.append("}"); - builder.newLine(); + builder.append(""" + default: break; + } + """); } builder.append("return null;"); builder.newLine(); @@ -198,76 +158,35 @@ public CharSequence doGetScopeByTypeBody(final ScopeModel it) { /** * Produces the body of the {@code doGlobalCache(EObject, EReference, String, Resource)} method. * - * @param it + * @param model * the scope model, must not be {@code null} * @return the method body, never {@code null} */ - public CharSequence doGlobalCacheByReferenceBody(final ScopeModel it) { - final StringConcatenation builder = new StringConcatenation(); - final List scopes = scopesWithReference(it, true); - final List globalScopes = scopes.stream().filter(s -> !globalRules(s).isEmpty()).toList(); - if (!globalScopes.isEmpty()) { - builder.append("if (scopeName != null && context.eContainer() == null) {"); - builder.newLine(); - builder.append(" "); - builder.append("switch (scopeName) {"); - builder.newLine(); - builder.append(" "); - for (final String name : scopeNames(globalScopes)) { - builder.append("case \""); - builder.append(name, " "); - builder.append("\":"); - builder.newLineIfNotEmpty(); - for (final ScopeDefinition scope : namedScopes(scopes, name)) { - builder.append(" "); - builder.append(" "); - final List globalRules = globalRules(scope); - builder.newLineIfNotEmpty(); - if (!globalRules.isEmpty()) { - builder.append(" "); - builder.append(" "); - builder.append("if (reference == "); - builder.append(genModelUtil.literalIdentifier(scope.getReference()), " "); - builder.append(") return true;"); - builder.newLineIfNotEmpty(); - } - } - builder.append(" "); - builder.append(" "); - builder.append("break;"); - builder.newLine(); - } - builder.append(" "); - builder.append("default: break;"); - builder.newLine(); - builder.append(" "); - builder.append("}"); - builder.newLine(); - builder.append("}"); - builder.newLine(); - } - builder.append("return false;"); - builder.newLine(); - return builder; + public CharSequence doGlobalCacheByReferenceBody(final ScopeModel model) { + return renderGlobalCache(scopesWithReference(model, true), "reference", ScopeDefinition::getReference, "scopeName != null && context.eContainer() == null"); } /** * Produces the body of the {@code doGlobalCache(EObject, EClass, String, Resource)} method. * - * @param it + * @param model * the scope model, must not be {@code null} * @return the method body, never {@code null} */ - public CharSequence doGlobalCacheByTypeBody(final ScopeModel it) { + public CharSequence doGlobalCacheByTypeBody(final ScopeModel model) { + return renderGlobalCache(scopesWithReference(model, false), "type", ScopeDefinition::getTargetType, "context.eContainer() == null"); + } + + /** Emits cache eligibility using the entry condition and target of the selected variant. */ + private CharSequence renderGlobalCache(final List scopes, final String argumentName, final Function target, final String entryCondition) { final StringConcatenation builder = new StringConcatenation(); - final List scopes = scopesWithReference(it, false); final List globalScopes = scopes.stream().filter(s -> !globalRules(s).isEmpty()).toList(); if (!globalScopes.isEmpty()) { - builder.append("if (context.eContainer() == null) {"); - builder.newLine(); - builder.append(" "); - builder.append("switch (scopeName) {"); + builder.append("if (" + entryCondition + ") {"); builder.newLine(); + builder.append(""" + switch (scopeName) { + """); builder.append(" "); for (final String name : scopeNames(globalScopes)) { builder.append("case \""); @@ -275,32 +194,23 @@ public CharSequence doGlobalCacheByTypeBody(final ScopeModel it) { builder.append("\":"); builder.newLineIfNotEmpty(); for (final ScopeDefinition scope : namedScopes(scopes, name)) { - builder.append(" "); - builder.append(" "); final List globalRules = globalRules(scope); - builder.newLineIfNotEmpty(); if (!globalRules.isEmpty()) { - builder.append(" "); - builder.append(" "); - builder.append("if (type == "); - builder.append(genModelUtil.literalIdentifier(scope.getTargetType()), " "); + builder.append(" "); + builder.append("if (" + argumentName + " == "); + builder.append(genModelUtil.literalIdentifier(target.apply(scope)), " "); builder.append(") return true;"); builder.newLineIfNotEmpty(); } } - builder.append(" "); - builder.append(" "); - builder.append("break;"); + builder.append(" break;"); builder.newLine(); } - builder.append(" "); - builder.append("default: break;"); - builder.newLine(); - builder.append(" "); - builder.append("}"); - builder.newLine(); - builder.append("}"); - builder.newLine(); + builder.append(""" + default: break; + } + } + """); } builder.append("return false;"); builder.newLine(); @@ -313,104 +223,110 @@ public CharSequence doGlobalCacheByTypeBody(final ScopeModel it) { * * @param scope * the scope definition the method is generated for, must not be {@code null} - * @param it + * @param model * the scope model, must not be {@code null} * @return the method body, never {@code null} * @throws RuntimeException * if the scope definition declares more than one global rule */ - public CharSequence scopeMethodBody(final ScopeDefinition scope, final ScopeModel it) { + public CharSequence scopeMethodBody(final ScopeDefinition scope, final ScopeModel model) { final StringConcatenation builder = new StringConcatenation(); - final List localRules = scopeProviderX.allScopeRules(scope).stream().filter(r -> !r.getContext().isGlobal()).toList(); - builder.newLineIfNotEmpty(); + final List localRules = scopeProviderX.allScopeRules(scope).stream().filter(rule -> !rule.getContext().isGlobal()).toList(); final List globalRules = globalRules(scope); - builder.newLineIfNotEmpty(); if (globalRules.size() > 1) { throw new RuntimeException("only one global rule allowed"); // NOPMD the raw type is the contract the generated scope providers were built against } - builder.newLineIfNotEmpty(); - for (final ScopeRule r : scopeProviderX.sortedRules(scopeProviderX.filterUniqueRules(localRules))) { - final EClass ruleContextType = r.getContext().getContextType(); - builder.append(generatorUtilX.javaContributorComment(generatorUtilX.location(r))); - builder.newLineIfNotEmpty(); - builder.append("if ("); - if (EClassComparator.isEObjectType(ruleContextType)) { - builder.append("true"); - } else { - builder.append("context instanceof "); - builder.append(genModelUtil.instanceClassName(ruleContextType)); - } - builder.append(") {"); - builder.newLineIfNotEmpty(); - builder.append(" "); - builder.append("final "); - builder.append(genModelUtil.instanceClassName(ruleContextType), " "); - builder.append(" ctx = ("); - builder.append(genModelUtil.instanceClassName(ruleContextType), " "); - builder.append(") context;"); - builder.newLineIfNotEmpty(); - builder.append(" "); - final List rulesForTypeAndContext = localRules.stream().filter(r2 -> scopeProviderX.hasSameContext(r2, r)).toList(); - builder.newLineIfNotEmpty(); - builder.append(" "); - final String typeOrRef = scopeProviderX.contextRef(r) != null ? "ref" : "type"; - builder.append(scopeRuleBlock(rulesForTypeAndContext, it, typeOrRef, ruleContextType, r.getContext().isGlobal()), " "); - builder.newLineIfNotEmpty(); - builder.append("}"); - builder.newLine(); + for (final ScopeRule rule : scopeProviderX.sortedRules(scopeProviderX.filterUniqueRules(localRules))) { + appendLocalScopeRule(builder, rule, localRules, model); } if (!localRules.isEmpty() || !globalRules.isEmpty()) { - builder.newLine(); - builder.append("final EObject eContainer = context.eContainer();"); - builder.newLine(); - builder.append("if (eContainer != null) {"); - builder.newLine(); - builder.append(" "); - builder.append("return internalGetScope("); - if (!localRules.isEmpty()) { - builder.append("eContainer"); - } else { - builder.append("getRootObject(eContainer)"); - } - builder.append(", "); - if (scope.getReference() != null) { - builder.append("ref"); - } else { - builder.append("type"); - } - builder.append(", \""); - builder.append(scopeProviderX.getScopeName(scope), " "); - builder.append("\", originalResource);"); - builder.newLineIfNotEmpty(); - builder.append("}"); - builder.newLine(); - builder.newLine(); + appendContainerFallback(builder, scope, !localRules.isEmpty()); } if (!globalRules.isEmpty()) { - final ScopeRule r = globalRules.get(0); - builder.newLineIfNotEmpty(); - final List rulesForTypeAndContext = List.of(r); - builder.newLineIfNotEmpty(); - builder.append(generatorUtilX.javaContributorComment(generatorUtilX.location(r))); - builder.newLineIfNotEmpty(); - builder.append("if (context.eResource() != null) {"); - builder.newLine(); - builder.append(" "); - builder.append("final Resource ctx = context.eResource();"); - builder.newLine(); - builder.append(" "); - final String typeOrRef = scopeProviderX.contextRef(r) != null ? "ref" : "type"; - builder.append(scopeRuleBlock(rulesForTypeAndContext, it, typeOrRef, r.getContext().getContextType(), r.getContext().isGlobal()), " "); - builder.newLineIfNotEmpty(); - builder.append("}"); - builder.newLine(); - builder.newLine(); + appendGlobalScopeRule(builder, globalRules.get(0), model); } builder.append("return null;"); builder.newLine(); return builder; } + /** Emits the matching local rules for one context type. */ + private void appendLocalScopeRule(final StringConcatenation builder, final ScopeRule rule, final List localRules, final ScopeModel model) { + final EClass ruleContextType = rule.getContext().getContextType(); + builder.append(generatorUtilX.javaContributorComment(generatorUtilX.location(rule))); + builder.newLineIfNotEmpty(); + builder.append("if ("); + if (EClassComparator.isEObjectType(ruleContextType)) { + builder.append("true"); + } else { + builder.append("context instanceof "); + builder.append(genModelUtil.instanceClassName(ruleContextType)); + } + builder.append(") {"); + builder.newLineIfNotEmpty(); + builder.append(" final "); + builder.append(genModelUtil.instanceClassName(ruleContextType), " "); + builder.append(" ctx = ("); + builder.append(genModelUtil.instanceClassName(ruleContextType), " "); + builder.append(") context;"); + builder.newLineIfNotEmpty(); + final List rulesForTypeAndContext = localRules.stream().filter(candidate -> scopeProviderX.hasSameContext(candidate, rule)).toList(); + builder.append(" "); + final String typeOrRef = scopeProviderX.contextRef(rule) != null ? "ref" : "type"; + builder.append(scopeRuleBlock(rulesForTypeAndContext, model, typeOrRef, ruleContextType, rule.getContext().isGlobal()), " "); + builder.newLineIfNotEmpty(); + builder.append("}"); + builder.newLine(); + } + + /** Emits the enclosing-object lookup before attempting a global rule. */ + private void appendContainerFallback(final StringConcatenation builder, final ScopeDefinition scope, final boolean hasLocalRules) { + builder.newLine(); + builder.append(""" + final EObject eContainer = context.eContainer(); + if (eContainer != null) { + """); + builder.append(" return internalGetScope("); + if (hasLocalRules) { + builder.append("eContainer"); + } else { + builder.append("getRootObject(eContainer)"); + } + builder.append(", "); + if (scope.getReference() != null) { + builder.append("ref"); + } else { + builder.append("type"); + } + builder.append(", \""); + builder.append(scopeProviderX.getScopeName(scope), " "); + builder.append("\", originalResource);"); + builder.newLineIfNotEmpty(); + builder.append(""" + } + + """); + } + + /** Emits the global rule in the resource context. */ + private void appendGlobalScopeRule(final StringConcatenation builder, final ScopeRule rule, final ScopeModel model) { + final List rulesForTypeAndContext = List.of(rule); + builder.append(generatorUtilX.javaContributorComment(generatorUtilX.location(rule))); + builder.newLineIfNotEmpty(); + builder.append(""" + if (context.eResource() != null) { + final Resource ctx = context.eResource(); + """); + builder.append(" "); + final String typeOrRef = scopeProviderX.contextRef(rule) != null ? "ref" : "type"; + builder.append(scopeRuleBlock(rulesForTypeAndContext, model, typeOrRef, rule.getContext().getContextType(), rule.getContext().isGlobal()), " "); + builder.newLineIfNotEmpty(); + builder.append(""" + } + + """); + } + /** * Produces the Java source for the guard of the given scope rule, for use in the rule block's {@code if} * condition. The guard is evaluated against the {@code ctx} variable holding the rule's context object. @@ -447,82 +363,85 @@ public String stringExpression(final Expression expr, final EClass contextType) return compiler.javaExpression(expr, translator.newCompilationContext("ctx", contextType, List.of(), expr)); } - public CharSequence scopeRuleBlock(final List it, final ScopeModel model, final String typeOrRef, final EClass contextType, - final Boolean isGlobal) { + public CharSequence scopeRuleBlock(final List rules, final ScopeModel model, final String typeOrRef, final EClass contextType, final Boolean isGlobal) { final StringConcatenation builder = new StringConcatenation(); - builder.append("IScope scope = IScope.NULLSCOPE;"); - builder.newLine(); - builder.append("try {"); - builder.newLine(); - if (it.stream().anyMatch(r -> r.getContext().getGuard() != null)) { + builder.append(""" + IScope scope = IScope.NULLSCOPE; + try { + """); + if (rules.stream().anyMatch(rule -> rule.getContext().getGuard() != null)) { + appendGuardedScopeRules(builder, rules, model, typeOrRef, isGlobal); + } else if (rules.size() == 1) { builder.append(" "); - final List sorted = new ArrayList<>(it); - sorted.sort(Comparator.comparingInt((ScopeRule r) -> r.getContext().getGuard() == null ? it.size() : it.indexOf(r))); - boolean hasElements = false; - for (final ScopeRule r : sorted) { - if (hasElements) { - builder.appendImmediate(" else ", " "); - } else { - hasElements = true; - } - if (r.getContext().getGuard() != null) { - builder.append("if ("); - builder.append(guardExpression(r), " "); - builder.append(") "); - } - builder.append("{"); - builder.newLineIfNotEmpty(); - builder.append(" "); - builder.append(" "); - if (it.size() > 1) { - builder.append(generatorUtilX.javaContributorComment(generatorUtilX.location(r)), " "); - builder.newLineIfNotEmpty(); - builder.append(" "); - builder.append(" "); - } - for (final ScopeExpression e : reversed(r.getExprs())) { - builder.append(scopeExpression(e, model, typeOrRef, scopeProviderX.getScope(r), isGlobal), " "); - } - builder.newLineIfNotEmpty(); - builder.append(" "); - builder.append("}"); - } - if (it.stream().noneMatch(r -> r.getContext().getGuard() == null)) { - builder.append(" else {"); - builder.newLineIfNotEmpty(); - builder.append(" "); - builder.append(" "); - builder.append("throw new UnsupportedOperationException(); // continue matching other definitions"); - builder.newLine(); - builder.append(" "); - builder.append("}"); + for (final ScopeExpression expression : reversed(rules.get(0).getExprs())) { + builder.append(scopeExpression(expression, model, typeOrRef, scopeProviderX.getScope(rules.get(0)), isGlobal), " "); } builder.newLineIfNotEmpty(); - } else if (it.size() == 1) { + } else { builder.append(" "); - for (final ScopeExpression e : reversed(it.get(0).getExprs())) { - builder.append(scopeExpression(e, model, typeOrRef, scopeProviderX.getScope(it.get(0)), isGlobal), " "); + error("scope context not unique for definitions: " + rules.stream().map(generatorUtilX::location).collect(Collectors.joining(", "))); + builder.newLineIfNotEmpty(); + } + appendScopeFailureHandler(builder, rules, contextType, isGlobal); + return builder; + } + + /** Emits guarded rules in declaration order, with the unguarded fallback last. */ + private void appendGuardedScopeRules(final StringConcatenation builder, final List rules, final ScopeModel model, final String typeOrRef, final Boolean isGlobal) { + builder.append(" "); + final List sorted = new ArrayList<>(rules); + sorted.sort(Comparator.comparingInt((ScopeRule rule) -> rule.getContext().getGuard() == null ? rules.size() : rules.indexOf(rule))); + boolean hasElements = false; + for (final ScopeRule rule : sorted) { + if (hasElements) { + // Keep the preceding closing-brace segment separate from trailing whitespace. + builder.appendImmediate(" else ", " "); + } else { + hasElements = true; } + if (rule.getContext().getGuard() != null) { + builder.append("if ("); + builder.append(guardExpression(rule), " "); + builder.append(") "); + } + builder.append("{"); builder.newLineIfNotEmpty(); - } else { - builder.append(" "); - error("scope context not unique for definitions: " + it.stream().map(generatorUtilX::location).collect(Collectors.joining(", "))); + builder.append(" "); + if (rules.size() > 1) { + builder.append(generatorUtilX.javaContributorComment(generatorUtilX.location(rule)), " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + } + for (final ScopeExpression expression : reversed(rule.getExprs())) { + builder.append(scopeExpression(expression, model, typeOrRef, scopeProviderX.getScope(rule), isGlobal), " "); + } + builder.newLineIfNotEmpty(); + builder.append(" }"); + } + if (rules.stream().noneMatch(rule -> rule.getContext().getGuard() == null)) { + builder.append(" else {"); builder.newLineIfNotEmpty(); + builder.append(" throw new UnsupportedOperationException(); // continue matching other definitions"); + builder.newLine(); + builder.append(" }"); } + builder.newLineIfNotEmpty(); + } + + /** Emits the diagnostic handler and final scope return. */ + private void appendScopeFailureHandler(final StringConcatenation builder, final List rules, final EClass contextType, final Boolean isGlobal) { builder.append("} catch (Exception e) {"); builder.newLine(); - builder.append(" "); - builder.append("LOGGER.error(\"Error calculating scope for "); + builder.append(" LOGGER.error(\"Error calculating scope for "); builder.append(isGlobal ? "Resource. Context:" : contextType.getName(), " "); builder.append(" \" + com.avaloq.tools.ddk.xtext.util.EObjectUtil.getLocationString(context) + \" ("); - builder.append(scopeProviderX.locatorString(it.get(0)), " "); + builder.append(scopeProviderX.locatorString(rules.get(0)), " "); builder.append(")\", e);"); builder.newLineIfNotEmpty(); - builder.append("}"); - builder.newLine(); - builder.append("return scope;"); - builder.newLine(); - return builder; + builder.append(""" + } + return scope; + """); } protected CharSequence _scopeExpression(final ScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope, From 22c92b583be69865b0a88ac05163093d68c7aba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Mon, 7 Sep 2026 16:55:05 +0200 Subject: [PATCH 2/2] fix(build): compare baseline archive contents Use Tycho zip comparison so ZIP metadata differences do not reject unchanged bundle contents. Co-authored-by: Astra --- ddk-parent/pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ddk-parent/pom.xml b/ddk-parent/pom.xml index eecec12a2..9c0de76c8 100644 --- a/ddk-parent/pom.xml +++ b/ddk-parent/pom.xml @@ -273,6 +273,8 @@ compare-version-with-baselines + + zip ${baseline.repo.url}