Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
741ad34
SymTypeExpression and wrappers return a supplier in case the symbol c…
Chrisslue Sep 4, 2026
d590008
PR feedback
Chrisslue Sep 10, 2026
3982a02
use __internal__Supplier
Chrisslue Sep 10, 2026
8526745
supplierGet now warn instead if throwing
Chrisslue Sep 11, 2026
bd04066
spike through SymTypeOfObject now uses supplier
Chrisslue Sep 11, 2026
91f492d
Revert "spike through SymTypeOfObject now uses supplier"
Chrisslue Sep 11, 2026
437fe93
SymTypeExpression and wrappers return a supplier in case the symbol c…
Chrisslue Sep 4, 2026
8f29bd2
PR feedback
Chrisslue Sep 10, 2026
6aa41fa
use __internal__Supplier
Chrisslue Sep 10, 2026
48fee8d
supplierGet now warn instead if throwing
Chrisslue Sep 11, 2026
20bed4b
rebase
Chrisslue Sep 11, 2026
2e7007e
Revert "spike through SymTypeOfObject now uses supplier"
Chrisslue Sep 11, 2026
b97dc96
Merge remote-tracking branch 'origin/add-supplier' into add-supplier
Chrisslue Sep 11, 2026
09cb397
broken
Chrisslue Sep 18, 2026
58c28a6
Builder can use supplier
Chrisslue Sep 18, 2026
3b7a68d
removed suppliers from SymTypeExpression desers
Chrisslue Sep 21, 2026
c7d3848
Merge upstream/dev into add-supplier
Chrisslue Sep 21, 2026
e02cd6d
extracted duplicate code:
Chrisslue Sep 21, 2026
22c7377
make all the decorator look the same (don't want to pull out a functi…
Chrisslue Sep 22, 2026
0993d94
tests for new decorations
Chrisslue Sep 22, 2026
396e9b5
tests for new decorations
Chrisslue Sep 23, 2026
8ac98c6
moved resolveOrLogError to out of factory
Chrisslue Oct 1, 2026
b81d69b
reduce __internal__supplier to a pure marker, without implementation …
Chrisslue Oct 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import de.monticore.symbols.basicsymbols._symboltable.DiagramSymbol;
import de.monticore.symbols.basicsymbols._symboltable.IBasicSymbolsScope;
import de.monticore.symbols.basicsymbols._symboltable.TypeSymbol;
import de.monticore.symbols.basicsymbols._symboltable.TypeSymbolSurrogate;
import de.monticore.symboltable.ImportStatement;
import de.monticore.types.MCTypeFacade;
import de.monticore.types.check.SymTypeExpression;
Expand Down Expand Up @@ -169,27 +168,35 @@ public List<String> getAllSuperClassesTransitive(ASTCDClass astcdClass) {
protected List<TypeSymbol> getAllSuperClassesTransitive(TypeSymbol cdTypeSymbol) {
List<TypeSymbol> superSymbolList = new ArrayList<>();
if (cdTypeSymbol.isPresentSuperClass()) {
TypeSymbol superSymbol = cdTypeSymbol.getSuperClass().getTypeInfo();
if (superSymbol instanceof TypeSymbolSurrogate) {
if (!((TypeSymbolSurrogate) superSymbol).checkLazyLoadDelegate()) {
return superSymbolList;
}
superSymbol = ((TypeSymbolSurrogate) superSymbol).lazyLoadDelegate();
Optional<TypeSymbol> superSymbol = resolveTypeInfo(cdTypeSymbol.getSuperClass());
if (superSymbol.isEmpty()) {
return superSymbolList;
}

superSymbolList.add(superSymbol);
superSymbolList.addAll(getAllSuperClassesTransitive(superSymbol));
superSymbolList.add(superSymbol.get());
superSymbolList.addAll(getAllSuperClassesTransitive(superSymbol.get()));
}
return superSymbolList;
}

public Optional<TypeSymbol> resolveTypeInfo(SymTypeExpression symType) {
if (!symType.hasTypeInfo()) {
return Optional.empty();
}
TypeSymbol typeInfo = symType.getTypeInfo();
if (typeInfo.getEnclosingScope() == null) {
return Optional.of(typeInfo);
}
return typeInfo.getEnclosingScope().resolveType(typeInfo.getName());
}

public List<String> getAllSuperInterfacesTransitive(TypeSymbol cdTypeSymbol) {
List<String> superSymbolList = new ArrayList<>();
List<CDTypeSymbol> localSuperInterfaces = Lists.newArrayList();
cdTypeSymbol.getSuperTypesList().stream()
.filter(s -> !isTFInterface(s))
.filter(s -> ((TypeSymbolSurrogate)s.getTypeInfo()).checkLazyLoadDelegate())
.map(s -> ((TypeSymbolSurrogate)s.getTypeInfo()).lazyLoadDelegate())
.map(this::resolveTypeInfo)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(t -> {if(t instanceof CDTypeSymbol && ((CDTypeSymbol)t).isIsInterface()) localSuperInterfaces.add((CDTypeSymbol) t);});
for (CDTypeSymbol superInterface : localSuperInterfaces) {
superSymbolList.add(createASTFullName(superInterface));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@
import de.monticore.codegen.mc2cd.MC2CDStereotypes;
import de.monticore.codegen.mc2cd.TransformationHelper;
import de.monticore.generating.templateengine.GlobalExtensionManagement;
import de.monticore.codegen.cd2java.methods.AccessAsSupplierTypes;
import de.monticore.generating.templateengine.HookPoint;
import de.monticore.generating.templateengine.StringHookPoint;
import de.monticore.symboltable.ISymbol;
import de.monticore.types.MCBasicTypesHelper;
import de.monticore.types.MCTypeFacade;
import de.monticore.types.mcbasictypes._ast.ASTMCPrimitiveType;
import de.monticore.types.mcbasictypes._ast.ASTMCType;
import de.monticore.types.mccollectiontypes._ast.ASTMCGenericType;
import de.monticore.types.mccollectiontypes._ast.ASTMCOptionalType;
import de.monticore.types.mccollectiontypes._ast.ASTMCTypeArgument;
import de.monticore.types.mcsimplegenerictypes.MCSimpleGenericTypesMill;
import de.monticore.umlmodifier._ast.ASTModifier;
import de.monticore.umlstereotype._ast.ASTStereoValue;
import de.monticore.umlstereotype._ast.ASTStereotype;
Expand All @@ -26,11 +28,14 @@
import de.se_rwth.commons.StringTransformations;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

import static de.monticore.cd.codegen.CD2JavaTemplates.VALUE;
import static de.monticore.codegen.cd2java._ast.ast_class.ASTConstants.AST_PREFIX;
import static de.monticore.codegen.cd2java.methods.AccessAsSupplierTypes.STD_SUPPLIER_TYPE;
import static de.monticore.codegen.cd2java.methods.AccessAsSupplierTypes.SUPPLIER_TYPE;

public class DecorationHelper extends MCBasicTypesHelper {

Expand Down Expand Up @@ -67,15 +72,15 @@ public boolean isAstNode(ASTCDAttribute attr) {
}

public boolean isSimpleAstNode(ASTCDAttribute attr) {
return !isOptional(attr.getMCType()) && !isListType(attr.printType()) && isAstNode(attr);
return !isOptional(attr.getMCType()) && !isList(attr.getMCType()) && isAstNode(attr);
}

public boolean isOptionalAstNode(ASTCDAttribute attr) {
return isOptional(attr.getMCType()) && isAstNode(attr);
}

public boolean isListAstNode(ASTCDAttribute attribute) {
return isListType(attribute.printType()) && isAstNode(attribute);
return isList(attribute.getMCType()) && isAstNode(attribute);
}

public boolean isListType(String type) {
Expand All @@ -100,17 +105,36 @@ public boolean isOptional(String type) {
if (index != -1) {
type = type.substring(0, index);
}
return "Optional".equals(type) || "java.lang.Optional".equals(type);
return "Optional".equals(Names.getSimpleName(type));
}

public boolean isOptional(ASTMCType type) {
if (type instanceof ASTMCOptionalType) {
return true;
} else if (type instanceof ASTMCGenericType) {
String simpleType = ((ASTMCGenericType) type).printWithoutTypeArguments();
return "Optional".equals(Names.getSimpleName(simpleType));
/**
* Checks whether the type is wrapped by the internal supplier type.
*/
public boolean isSupplier(String type) {
int index = type.indexOf('<');
if (index != -1) {
type = type.substring(0, index);
}
return false;
return SUPPLIER_TYPE.equals(type);
}

// The ASTMCType overloads intentionally delegate to the String checks above instead of using
// instanceof: a single implementation, and no reliance on the concrete AST node.
public boolean isOptional(ASTMCType type) {
return isOptional(type.printType());
}

public boolean isList(ASTMCType type) {
return isListType(type.printType());
}

public boolean isSupplier(ASTMCType type) {
return isSupplier(type.printType());
}

public boolean shouldHaveSupplier(ASTCDAttribute attribute) {
return AccessAsSupplierTypes.shouldHaveSupplier(attribute);
}

public boolean isString(String type) {
Expand Down Expand Up @@ -159,12 +183,33 @@ public String getSimpleNativeType(String nativeAttributeType) {
* important for Optional and List types
*/
public void addAttributeDefaultValues(ASTCDAttribute attribute, GlobalExtensionManagement glex) {
if (isListType(attribute.printType())) {
glex.replaceTemplate(VALUE, attribute, new StringHookPoint("= new java.util.ArrayList<>()"));
// For a wrapped attribute (Supplier<X>) the default is derived from the unwrapped type X.
boolean isSupplier = isSupplier(attribute.getMCType());
ASTMCType type = unwrapSupplier(attribute.getMCType());

boolean isList = isList(type);
String inner;
if (isList) {
inner = "new java.util.ArrayList<>()";
} else if (isOptional(type)) {
inner = "Optional.empty()";
} else if (isSupplier) {
inner = "null";
} else {
return;
}

} else if (isOptional(attribute.getMCType())) {
glex.replaceTemplate(VALUE, attribute, new StringHookPoint("= Optional.empty()"));
// the initialization expression that is placed after the '=' of the field declaration
String defaultValue;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the defaultValue is what comes after the =.
This is more like the defaultInitialization or simmilar?

if (!isSupplier) {
defaultValue = inner;
} else if (isList) {
// A list must expose a stable instance, otherwise we would create a new one every get and entries would be lost.
defaultValue = "com.google.common.base.Suppliers.memoize(() -> " + inner + ")";
} else {
defaultValue = "() -> " + inner;
}
glex.replaceTemplate(VALUE, attribute, new StringHookPoint("= " + defaultValue));
}

/**
Expand All @@ -179,11 +224,38 @@ public String getNativeAttributeName(String attributeName) {
return attributeName.substring(JavaNamesHelper.PREFIX_WHEN_WORD_IS_RESERVED.length());
}

public ASTMCTypeArgument getReferenceTypeFromOptional(ASTMCType type) {
public ASTMCTypeArgument getReferenceTypeOfOptional(ASTMCType type) {
Preconditions.checkArgument(isOptional(type));
return ((ASTMCGenericType) type).getMCTypeArgumentList().getFirst();
}

public ASTMCTypeArgument getReferenceTypeOfSupplier(ASTMCType type) {
Preconditions.checkArgument(isSupplier(type));
return ((ASTMCGenericType) type).getMCTypeArgumentList().getFirst();
}

/**
* Wraps {@code inner} into the internal supplier type {@code __internal__Supplier<inner>}
*
* A copy of this is already in the MCTypeFacade. This can be deleted, and references rerouted to MCTypeFacade after release ...
*/
public ASTMCType createInternalSupplierTypeOf(ASTMCType inner) {
ASTMCTypeArgument arg = MCSimpleGenericTypesMill
.mCCustomTypeArgumentBuilder().setMCType(inner.deepClone()).build();
return MCTypeFacade.getInstance().createBasicGenericTypeOf(SUPPLIER_TYPE, arg);
}

/**
* Wraps {@code inner} into the public {@code java.util.function.Supplier<inner>} type.
*
* A copy of this is already in the MCTypeFacade. This can be deleted, and references rerouted to MCTypeFacade after release ...
*/
public ASTMCType createStdSupplierTypeOf(ASTMCType inner) {
ASTMCTypeArgument arg = MCSimpleGenericTypesMill
.mCCustomTypeArgumentBuilder().setMCType(inner.deepClone()).build();
return MCTypeFacade.getInstance().createBasicGenericTypeOf(STD_SUPPLIER_TYPE, arg);
}

/**
* methods only used in templates
*/
Expand Down Expand Up @@ -219,12 +291,52 @@ public boolean isAttributeOfTypeEnum(ASTCDAttribute attr) {
return ((CDTypeSymbol) type.get()).isIsEnum();
}

/**
* unwraps the internal supplier type (Supplier&lt;X&gt; -&gt; X); returns the type unchanged if it is not wrapped
*/
public ASTMCType unwrapSupplier(ASTMCType type) {
if (isSupplier(type)) {
return getReferenceTypeOfSupplier(type).getMCTypeOpt().get();
}
return type;
}

/**
* The type that generated code exposes for a supplied attribute:
* {@code __internal__Supplier<X>} (or an unwrapped X) becomes {@code java.util.function.Supplier<X>},
* so the internal wrapper never leaks into the public API.
*/
public ASTMCType toPublicSupplierType(ASTMCType type) {
return createStdSupplierTypeOf(unwrapSupplier(type));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me wonder if the setup is too complex.
I am not sure.

}

/**
* Replaces the internal marker type of all wrapped attributes by the public {@code java.util.function.Supplier<X>}.
* Must be called after all decorators that rely on {@link #isSupplier} are done with the attributes, because
* afterwards the attributes are no longer recognized as supplied. The generated fields then store the plain supplier.
*/
public void unmarkSuppliers(Collection<ASTCDAttribute> attributes) {
attributes.stream()
.filter(a -> isSupplier(a.getMCType()))
.forEach(a -> a.setMCType(toPublicSupplierType(a.getMCType())));
}

/**
* Wraps the type of every attribute that {@link #shouldHaveSupplier} selects into the internal supplier type.
* The internal type is only a generation-time marker, see {@link #unmarkSuppliers}.
*/
public void wrapAndMarkSuppliers(Collection<ASTCDAttribute> attributes) {
attributes.stream()
.filter(this::shouldHaveSupplier)
.forEach(a -> a.setMCType(createInternalSupplierTypeOf(a.getMCType())));
}

/**
* methods return correct getters or setters for a special attribut
* needed in templates
*/
public String getPlainGetter(ASTCDAttribute ast) {
String astType = CD4CodeMill.prettyPrint(ast.getMCType(), false);
String astType = CD4CodeMill.prettyPrint(unwrapSupplier(ast.getMCType()), false);
StringBuilder sb = new StringBuilder();
// Do not use CDTypes.isBoolean() because only primitive boolean uses GET_PREFIX_BOOLEAN
if (astType.equals("boolean")) {
Expand All @@ -247,7 +359,7 @@ public String getPlainGetter(ASTCDAttribute ast) {
public String getPlainSetter(ASTCDAttribute ast) {
StringBuilder sb = new StringBuilder(SET_PREFIX).append(
StringTransformations.capitalize(getNativeAttributeName(ast.getName())));
String astType = CD4CodeMill.prettyPrint(ast.getMCType(), false);
String astType = CD4CodeMill.prettyPrint(unwrapSupplier(ast.getMCType()), false);
if (isListType(astType)) {
if (hasDerivedAttributeName(ast) && ast.getName().endsWith(TransformationHelper.LIST_SUFFIX)) {
sb.replace(sb.length() - TransformationHelper.LIST_SUFFIX.length(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ protected String getNaiveAttributeName(ASTCDAttribute astcdAttribute) {
@Override
protected ASTCDMethod createGetMethod(final ASTCDAttribute ast) {
String name = String.format(GET, StringUtils.capitalize(naiveAttributeName));
ASTMCType type = getDecorationHelper().getReferenceTypeFromOptional(ast.getMCType().deepClone()).getMCTypeOpt().get();
ASTMCType type = getDecorationHelper().getReferenceTypeOfOptional(ast.getMCType().deepClone()).getMCTypeOpt().get();
ASTCDMethod method = this.getCDMethodFacade().createMethod(PUBLIC.build(), type, name);
String referencedSymbolType = symbolTableService.getReferencedSymbolTypeName(ast);
String attributeName = this.getDecorationHelper().getNativeAttributeName(ast.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ public ASTCDClass decorate(final ASTCDClass domainClass) throws DecorateExceptio
.collect(Collectors.toList());

builderAttributes.forEach(this::addAttributeDefaultValues);
// the marker type is not needed anymore: the fields store the plain Supplier
getDecorationHelper().unmarkSuppliers(builderAttributes);

return CD4AnalysisMill.cDClassBuilder()
.setModifier(modifier)
Expand All @@ -133,7 +135,11 @@ public ASTCDClass decorate(final ASTCDClass domainClass) throws DecorateExceptio
}

protected void addAttributeDefaultValues(ASTCDAttribute attribute) {
if (getDecorationHelper().isListType(CD4CodeMill.prettyPrint(attribute.getMCType(), false))) {
if (getDecorationHelper().isSupplier(attribute.getMCType())) {
// a supplier-wrapped attribute must have a non-null default supplier, otherwise build() would
// hand the symbol a null field where a Supplier is expected
getDecorationHelper().addAttributeDefaultValues(attribute, glex);
} else if (getDecorationHelper().isListType(CD4CodeMill.prettyPrint(attribute.getMCType(), false))) {
this.replaceTemplate(VALUE, attribute, new StringHookPoint("= new java.util.ArrayList<>()"));

} else if (getDecorationHelper().isOptional(CD4CodeMill.prettyPrint(attribute.getMCType(), false))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.stream.Collectors;

import static de.monticore.cd.codegen.CD2JavaTemplates.EMPTY_BODY;
import static de.monticore.cd.facade.CDModifier.PUBLIC;

/**
* changes return type of builder setters for list attributes
Expand Down Expand Up @@ -46,8 +47,8 @@ protected List<ASTCDMethod> createSetter(ASTCDAttribute attribute) {
String parameterCall = m.getCDParameterList().stream()
.map(ASTCDParameter::getName)
.collect(Collectors.joining(", "));
this.replaceTemplate(EMPTY_BODY, m, new TemplateHookPoint("_ast.builder.MethodDelegate4ASTBuilder",
attribute, methodName, parameterCall));
this.replaceTemplate(EMPTY_BODY, m, new TemplateHookPoint("_ast.builder.MethodDelegate4ASTBuilder",
attribute.deepClone(), methodName, parameterCall));
}
return methods;
}
Expand All @@ -58,7 +59,24 @@ protected ASTCDMethod createSetListMethod(ASTCDAttribute ast) {
ASTCDMethod method = this.getCDMethodFacade().createMethodByDefinition(signature);
ASTMCReturnType returnType = MCBasicTypesMill.mCReturnTypeBuilder().setMCType(builderType).build();
method.setMCReturnType(returnType);
this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("_ast.builder.Set4ASTBuilder", ast));

String templateName = "_ast.builder.Set4ASTBuilder";
if (getDecorationHelper().isSupplier(ast.getMCType())) {
templateName = "_ast.builder.SupplierSet4ASTBuilder";
}
this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint(templateName, ast));
return method;
}

@Override
protected ASTCDMethod createSetListSupplierMethod(ASTCDAttribute ast) {
String name = "set" + capitalizedAttributeNameWithOutS + "ListSupplier";
ASTMCType supplierType = getDecorationHelper().toPublicSupplierType(ast.getMCType());
ASTCDParameter parameter = this.getCDParameterFacade().createParameter(supplierType, ast.getName());
ASTCDMethod method = this.getCDMethodFacade().createMethod(PUBLIC.build(), name, parameter);
ASTMCReturnType returnType = MCBasicTypesMill.mCReturnTypeBuilder().setMCType(builderType).build();
method.setMCReturnType(returnType);
this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("_ast.builder.SupplierSetRaw4ASTBuilder", ast));
return method;
}
}
Loading
Loading