From 36ada484e941a08c06bbc8de57808e880703e2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20L=C3=BCpges?= Date: Wed, 23 Sep 2026 20:24:31 +0200 Subject: [PATCH] Getter/Setter do not fail on existing method --- .../codegen/decorators/GetterDecorator.java | 85 +++++++++++++------ .../codegen/decorators/SetterDecorator.java | 76 +++++++++++------ .../decorators/data/AbstractDecorator.java | 33 ++++++- .../cd/cdgen/GetterDecoratorTest.java | 26 ++++-- 4 files changed, 156 insertions(+), 64 deletions(-) diff --git a/cdlang/src/main/java/de/monticore/cd/codegen/decorators/GetterDecorator.java b/cdlang/src/main/java/de/monticore/cd/codegen/decorators/GetterDecorator.java index 525bfbf29..4f4899055 100644 --- a/cdlang/src/main/java/de/monticore/cd/codegen/decorators/GetterDecorator.java +++ b/cdlang/src/main/java/de/monticore/cd/codegen/decorators/GetterDecorator.java @@ -53,28 +53,41 @@ public void visit(ASTCDAttribute attribute) { // var decType = (ASTCDType) decoratorData.getAsDecorated(originalClazz); if (MCTypeFacade.getInstance().isBooleanType(attribute.getMCType())) { - this.getterData.getOrCreateMethods(attribute).add(decorateMandatory(decType, attribute)); + decorateMandatory(decType, attribute).ifPresent(this.getterData.getOrCreateMethods( + attribute)::add); } else if (MCCollectionSymTypeRelations.isList(attribute.getSymbol().getType())) { - this.getterData.getOrCreateMethods(attribute).add(decorateList(decType, attribute)); + decorateList(decType, attribute).ifPresent(this.getterData.getOrCreateMethods( + attribute)::add); decorateWithAssocFunctions(decType, attribute, true); } else if (MCCollectionSymTypeRelations.isSet(attribute.getSymbol().getType())) { - this.getterData.getOrCreateMethods(attribute).add(decorateSet(decType, attribute)); + decorateSet(decType, attribute).ifPresent(this.getterData.getOrCreateMethods( + attribute)::add); decorateWithAssocFunctions(decType, attribute, false); } else if (MCCollectionSymTypeRelations.isOptional(attribute.getSymbol().getType())) { - this.getterData.getOrCreateMethods(attribute).add(decorateOptional(decType, attribute)); - this.getterData.getOrCreateMethods(attribute).add(decorateOptionalIsPresent(decType, - attribute)); + decorateOptional(decType, attribute).ifPresent(this.getterData.getOrCreateMethods( + attribute)::add); + decorateOptionalIsPresent(decType, attribute).ifPresent(this.getterData.getOrCreateMethods( + attribute)::add); } else { - this.getterData.getOrCreateMethods(attribute).add(decorateMandatory(decType, attribute)); + decorateMandatory(decType, attribute).ifPresent(this.getterData.getOrCreateMethods( + attribute)::add); } } } - protected MethodInformation decorateMandatory(ASTCDType decoratedType, ASTCDAttribute attribute) { + protected void logAlreadyExistingWarning(ASTCDAttribute attribute) { + Log.warn("0xTODO: Unable to decorate setter of `" + attribute.getName() + + "` as such a method already exists.", attribute.get_SourcePositionStart(), attribute + .get_SourcePositionEnd()); + + } + + protected Optional decorateMandatory(ASTCDType decoratedType, + ASTCDAttribute attribute) { String name = (MCTypeFacade.getInstance().isBooleanType(attribute.getMCType()) ? "is" : "get") + StringTransformations.capitalize(attribute.getName()); ASTMCType type = attribute.getMCType().deepClone(); @@ -84,15 +97,19 @@ protected MethodInformation decorateMandatory(ASTCDType decoratedType, ASTCDAttr "methods.Get", attribute))); method.getModifier().setAbstract(attribute.getModifier().isDerived()); - addToClass(decoratedType, method); + if (!addToClass(decoratedType, method)) { + logAlreadyExistingWarning(attribute); + return Optional.empty(); + } this.updateModifier(attribute); - return new MethodInformation(GetterMethodKind.GET_MANDATORY_OR_OPT, method, "methods.Get", - attribute.getName()); + return Optional.of(new MethodInformation(GetterMethodKind.GET_MANDATORY_OR_OPT, method, + "methods.Get", attribute.getName())); } - protected MethodInformation decorateOptional(ASTCDType decoratedType, ASTCDAttribute attribute) { + protected Optional decorateOptional(ASTCDType decoratedType, + ASTCDAttribute attribute) { String name = "get" + StringTransformations.capitalize(attribute.getName()); ASTMCType type = getCDGenService().getFirstTypeArgument(attribute.getMCType()).deepClone(); @@ -107,26 +124,33 @@ protected MethodInformation decorateOptional(ASTCDType decoratedType, ASTCDAttri getMethod.getModifier().setAbstract(attribute.getModifier().isDerived()); CD4C.getInstance().addImport(decoratedType, Log.class.getName()); - addToClass(decoratedType, getMethod); - return new MethodInformation(GetterMethodKind.GET_MANDATORY_OR_OPT, getMethod, - "methods.opt.Get4Opt", attribute.getName()); + if (!addToClass(decoratedType, getMethod)) { + logAlreadyExistingWarning(attribute); + return Optional.empty(); + } + return Optional.of(new MethodInformation(GetterMethodKind.GET_MANDATORY_OR_OPT, getMethod, + "methods.opt.Get4Opt", attribute.getName())); } - protected MethodInformation decorateOptionalIsPresent(ASTCDType decoratedType, + protected Optional decorateOptionalIsPresent(ASTCDType decoratedType, ASTCDAttribute attribute) { ASTCDMethod isPresentMethod = CDMethodFacade.getInstance().createMethod(attribute.getModifier() .deepClone(), MCTypeFacade.getInstance().createBooleanType(), "isPresent" + StringTransformations.capitalize(attribute.getName())); glexOpt.ifPresent(glex -> glex.replaceTemplate(EMPTY_BODY, isPresentMethod, new TemplateHookPoint("methods.opt.IsPresent4Opt", attribute))); - addToClass(decoratedType, isPresentMethod); + if (!addToClass(decoratedType, isPresentMethod)) { + logAlreadyExistingWarning(attribute); + return Optional.empty(); + } this.updateModifier(attribute); - return new MethodInformation(GetterMethodKind.IS_PRESENT, isPresentMethod, - "methods.opt.IsPresent4Opt", attribute.getName()); + return Optional.of(new MethodInformation(GetterMethodKind.IS_PRESENT, isPresentMethod, + "methods.opt.IsPresent4Opt", attribute.getName())); } - protected MethodInformation decorateSet(ASTCDType decoratedType, ASTCDAttribute attribute) { + protected Optional decorateSet(ASTCDType decoratedType, + ASTCDAttribute attribute) { String name = "get" + StringTransformations.capitalize(attribute.getName()); ASTMCType type = getCDGenService().getFirstTypeArgument(attribute.getMCType()).deepClone(); @@ -135,15 +159,19 @@ protected MethodInformation decorateSet(ASTCDType decoratedType, ASTCDAttribute glexOpt.ifPresent(glex -> glex.replaceTemplate(EMPTY_BODY, getListMethod, new TemplateHookPoint( "methods.Get", attribute))); getListMethod.getModifier().setAbstract(attribute.getModifier().isDerived()); - addToClass(decoratedType, getListMethod); + if (!addToClass(decoratedType, getListMethod)) { + logAlreadyExistingWarning(attribute); + return Optional.empty(); + } this.updateModifier(attribute); - return new MethodInformation(GetterMethodKind.GET_COLLECTION, getListMethod, "methods.Get", - attribute.getName()); + return Optional.of(new MethodInformation(GetterMethodKind.GET_COLLECTION, getListMethod, + "methods.Get", attribute.getName())); } - protected MethodInformation decorateList(ASTCDType decoratedType, ASTCDAttribute attribute) { + protected Optional decorateList(ASTCDType decoratedType, + ASTCDAttribute attribute) { String name = "get" + StringTransformations.capitalize(attribute.getName()); ASTMCType type = getCDGenService().getFirstTypeArgument(attribute.getMCType()).deepClone(); @@ -152,11 +180,14 @@ protected MethodInformation decorateList(ASTCDType decoratedType, ASTCDAttribute glexOpt.ifPresent(glex -> glex.replaceTemplate(EMPTY_BODY, getListMethod, new TemplateHookPoint( "methods.Get", attribute))); getListMethod.getModifier().setAbstract(attribute.getModifier().isDerived()); - addToClass(decoratedType, getListMethod); + if (!addToClass(decoratedType, getListMethod)) { + logAlreadyExistingWarning(attribute); + return Optional.empty(); + } this.updateModifier(attribute); - return new MethodInformation(GetterMethodKind.GET_COLLECTION, getListMethod, "methods.Get", - attribute.getName()); + return Optional.of(new MethodInformation(GetterMethodKind.GET_COLLECTION, getListMethod, + "methods.Get", attribute.getName())); } protected void decorateWithAssocFunctions(ASTCDType decoratedType, ASTCDAttribute attribute, diff --git a/cdlang/src/main/java/de/monticore/cd/codegen/decorators/SetterDecorator.java b/cdlang/src/main/java/de/monticore/cd/codegen/decorators/SetterDecorator.java index 451d0a466..4ebdeba88 100644 --- a/cdlang/src/main/java/de/monticore/cd/codegen/decorators/SetterDecorator.java +++ b/cdlang/src/main/java/de/monticore/cd/codegen/decorators/SetterDecorator.java @@ -54,25 +54,27 @@ public void visit(ASTCDAttribute attribute) { switch (info.getMultiplicity()) { case OPTIONAL: - this.setterData.getOrCreateMethods(attribute).add(decorateOptionalAbsent(decClazz, - attribute)); - this.setterData.getOrCreateMethods(attribute).add(decorateOptSet(decClazz, attribute)); + decorateOptionalAbsent(decClazz, attribute).ifPresent(this.setterData.getOrCreateMethods( + attribute)::add); + decorateOptSet(decClazz, attribute).ifPresent(this.setterData.getOrCreateMethods( + attribute)::add); break; case MANDATORY: - this.setterData.getOrCreateMethods(attribute).add(decorateMandatory(decClazz, attribute)); + decorateMandatory(decClazz, attribute).ifPresent(this.setterData.getOrCreateMethods( + attribute)::add); break; case SET: if (info.isOrdered()) { - this.setterData.getOrCreateMethods(attribute).add(decorateAddWithIndex(decClazz, - attribute)); - this.setterData.getOrCreateMethods(attribute).add(decorateRemoveWithIndex(decClazz, - attribute)); + decorateAddWithIndex(decClazz, attribute).ifPresent(this.setterData.getOrCreateMethods( + attribute)::add); + decorateRemoveWithIndex(decClazz, attribute).ifPresent(this.setterData + .getOrCreateMethods(attribute)::add); } else { - this.setterData.getOrCreateMethods(attribute).add(decorateAddUnordered(decClazz, - attribute)); - this.setterData.getOrCreateMethods(attribute).add(decorateRemoveUnordered(decClazz, - attribute)); + decorateAddUnordered(decClazz, attribute).ifPresent(this.setterData.getOrCreateMethods( + attribute)::add); + decorateRemoveUnordered(decClazz, attribute).ifPresent(this.setterData + .getOrCreateMethods(attribute)::add); } } @@ -80,14 +82,15 @@ public void visit(ASTCDAttribute attribute) { } } - protected MethodInformation decorateMandatory(ASTCDType clazz, ASTCDAttribute attribute) { + protected Optional decorateMandatory(ASTCDType clazz, + ASTCDAttribute attribute) { String name = "set" + StringUtils.capitalize(StringTransformations.capitalize(attribute .getName())); return decorate(clazz, attribute, SetterMethodKind.SET_MANDATORY_OR_OPT, "methods.Set", name, CDParameterFacade.getInstance().createParameters(attribute), attribute); } - protected MethodInformation decorateOptSet(ASTCDType clazz, ASTCDAttribute attribute) { + protected Optional decorateOptSet(ASTCDType clazz, ASTCDAttribute attribute) { String name = "set" + StringUtils.capitalize(StringTransformations.capitalize(attribute .getName())); ASTMCType type = getCDGenService().getFirstTypeArgument(attribute.getMCType()).deepClone(); @@ -96,14 +99,16 @@ protected MethodInformation decorateOptSet(ASTCDType clazz, ASTCDAttribute attri attribute, "--unused--"); } - protected MethodInformation decorateOptionalAbsent(ASTCDType clazz, ASTCDAttribute attribute) { + protected Optional decorateOptionalAbsent(ASTCDType clazz, + ASTCDAttribute attribute) { String name = "set" + StringUtils.capitalize(StringTransformations.capitalize(attribute .getName())) + "Absent"; return decorate(clazz, attribute, SetterMethodKind.UNSET_OPTIONAL, "methods.opt.SetAbsent", name, List.of(), attribute); } - protected MethodInformation decorateAddWithIndex(ASTCDType clazz, ASTCDAttribute attribute) { + protected Optional decorateAddWithIndex(ASTCDType clazz, + ASTCDAttribute attribute) { String name = "add" + StringUtils.capitalize(StringTransformations.capitalize(attribute .getName())); ASTMCType type = getCDGenService().getFirstTypeArgument(attribute.getMCType()).deepClone(); @@ -112,40 +117,52 @@ protected MethodInformation decorateAddWithIndex(ASTCDType clazz, ASTCDAttribute .getInstance().createParameter(type, attribute.getName())), attribute); } - protected MethodInformation decorateRemoveWithIndex(ASTCDType clazz, ASTCDAttribute attribute) { + protected Optional decorateRemoveWithIndex(ASTCDType clazz, + ASTCDAttribute attribute) { String name = "remove" + StringUtils.capitalize(StringTransformations.capitalize(attribute .getName())); ASTMCType type = getCDGenService().getFirstTypeArgument(attribute.getMCType()).deepClone(); var m = decorate(clazz, attribute, SetterMethodKind.REM, "methods.list.Rem", name, List.of( CDParameterFacade.getInstance().createParameter(int.class, "index")), attribute); - m.getSetMethod().setMCReturnType(MCBasicTypesMill.mCReturnTypeBuilder().setMCType(type) + if (m.isEmpty()) { + return m; + } + m.get().getSetMethod().setMCReturnType(MCBasicTypesMill.mCReturnTypeBuilder().setMCType(type) .build()); return m; } - protected MethodInformation decorateAddUnordered(ASTCDType clazz, ASTCDAttribute attribute) { + protected Optional decorateAddUnordered(ASTCDType clazz, + ASTCDAttribute attribute) { String name = "add" + StringUtils.capitalize(StringTransformations.capitalize(attribute .getName())); ASTMCType type = getCDGenService().getFirstTypeArgument(attribute.getMCType()).deepClone(); var m = decorate(clazz, attribute, SetterMethodKind.ADD, "methods.list.AddUnordered", name, List .of(CDParameterFacade.getInstance().createParameter(type, attribute.getName())), attribute); - m.getSetMethod().setMCReturnType(MCBasicTypesMill.mCReturnTypeBuilder().setMCType(MCTypeFacade - .getInstance().createBooleanType()).build()); + if (m.isEmpty()) { + return m; + } + m.get().getSetMethod().setMCReturnType(MCBasicTypesMill.mCReturnTypeBuilder().setMCType( + MCTypeFacade.getInstance().createBooleanType()).build()); return m; } - protected MethodInformation decorateRemoveUnordered(ASTCDType clazz, ASTCDAttribute attribute) { + protected Optional decorateRemoveUnordered(ASTCDType clazz, + ASTCDAttribute attribute) { String name = "remove" + StringUtils.capitalize(StringTransformations.capitalize(attribute .getName())); ASTMCType type = getCDGenService().getFirstTypeArgument(attribute.getMCType()).deepClone(); var m = decorate(clazz, attribute, SetterMethodKind.REM, "methods.list.RemUnordered", name, List .of(CDParameterFacade.getInstance().createParameter(type, attribute.getName())), attribute); - m.getSetMethod().setMCReturnType(MCBasicTypesMill.mCReturnTypeBuilder().setMCType(MCTypeFacade - .getInstance().createBooleanType()).build()); + if (m.isEmpty()) { + return m; + } + m.get().getSetMethod().setMCReturnType(MCBasicTypesMill.mCReturnTypeBuilder().setMCType( + MCTypeFacade.getInstance().createBooleanType()).build()); return m; } - protected MethodInformation decorate(ASTCDType decParent, ASTCDAttribute attribute, + protected Optional decorate(ASTCDType decParent, ASTCDAttribute attribute, SetterMethodKind kind, String templateName, String methodName, List params, Object... templateParams) { @@ -154,9 +171,14 @@ protected MethodInformation decorate(ASTCDType decParent, ASTCDAttribute attribu glexOpt.ifPresent(glex -> glex.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint( templateName, templateParams))); - addToClass(decParent, method); + if (!addToClass(decParent, method)) { + Log.warn("0xTODO: Unable to decorate setter of `" + attribute.getName() + + "` as such a method already exists.", attribute.get_SourcePositionStart(), attribute + .get_SourcePositionEnd()); + return Optional.empty(); + } - return new MethodInformation(kind, method, templateName, attribute.getName()); + return Optional.of(new MethodInformation(kind, method, templateName, attribute.getName())); } diff --git a/cdlang/src/main/java/de/monticore/cd/codegen/decorators/data/AbstractDecorator.java b/cdlang/src/main/java/de/monticore/cd/codegen/decorators/data/AbstractDecorator.java index 20ebc2311..ec7a5e0d5 100644 --- a/cdlang/src/main/java/de/monticore/cd/codegen/decorators/data/AbstractDecorator.java +++ b/cdlang/src/main/java/de/monticore/cd/codegen/decorators/data/AbstractDecorator.java @@ -4,6 +4,7 @@ import de.monticore.ast.ASTNode; import de.monticore.cd.codegen.CDGenService; import de.monticore.cd.codegen.decorators.IDecorator; +import de.monticore.cd4codebasis._ast.ASTCDMethod; import de.monticore.cdbasis._ast.*; import de.monticore.generating.templateengine.GlobalExtensionManagement; import java.util.Optional; @@ -37,9 +38,37 @@ else if (decoratedParent instanceof ASTCDCompilationUnit) .getName()); } - protected void addToClass(ASTCDType clazz, ASTCDMember member) { - // TODO: Only add iff not yet present (#4310) + /** + * Adds a member to a class, if it does not already exist + * + * @param clazz the class + * @param member the to-be added member + * @return whether a conflict already exists + */ + protected boolean addToClass(ASTCDType clazz, ASTCDMember member) { + // add iff not yet present (#4310) + if (member instanceof ASTCDMethod method) { + for (ASTCDMember mem : clazz.getCDMemberList()) { + if (mem instanceof ASTCDMethod meth) { + if (method.getName().equals(meth.getName()) && method.getCDParameterList().size() == meth + .getCDParameterList().size()) { + // TODO: Check if params are compatible? (#4310) + return false; + } + } + } + } + else if (member instanceof ASTCDAttribute attribute) { + for (ASTCDMember mem : clazz.getCDMemberList()) { + if (mem instanceof ASTCDAttribute attr) { + if (attr.getName().equals(attribute.getName())) { + return false; + } + } + } + } clazz.addCDMember(member); + return true; } public CDGenService getCDGenService() { return decoratorData.cdGenService; } diff --git a/cdlang/src/test/java/de/monticore/cd/cdgen/GetterDecoratorTest.java b/cdlang/src/test/java/de/monticore/cd/cdgen/GetterDecoratorTest.java index eb76e032c..cbc443c64 100644 --- a/cdlang/src/test/java/de/monticore/cd/cdgen/GetterDecoratorTest.java +++ b/cdlang/src/test/java/de/monticore/cd/cdgen/GetterDecoratorTest.java @@ -7,7 +7,7 @@ import de.monticore.cd4code.CD4CodeMill; import de.monticore.generating.GeneratorSetup; import de.monticore.generating.templateengine.GlobalExtensionManagement; -import de.se_rwth.commons.logging.Log; +import de.monticore.runtime.junit.MCAssertions; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -19,18 +19,28 @@ public class GetterDecoratorTest extends AbstractDecoratorTest { @Test public void testGetter() throws Exception { - var opt = CD4CodeMill.parser().parse_String("classdiagram TestGetter {\n" - + " <> public class TestGetterC { \n" + " boolean myBool;" + " public int myInt;" - + " <> public int pubX;" + " }\n" - + " public association TestGetterC -> (roleB) Other [*];\n" - + " public association TestGetterC -> (orderedRole) Other [*] {ordered};\n" - + " <> public class Other { \n" + "}\n" + "}"); + var opt = CD4CodeMill.parser().parse_String(""" + classdiagram TestGetter { + <> public class TestGetterC { + boolean myBool; public int myInt; + <> public int pubX; + } + public association TestGetterC -> (roleB) Other [*]; + public association TestGetterC -> (orderedRole) Other [*] {ordered}; + <> public class Other { + } + <> public class AlreadyExisting { + String x; + void getX(); // already existing + } + }"""); Assertions.assertTrue(opt.isPresent()); super.doTest(opt.get()); - Assertions.assertTrue(Log.getFindings().isEmpty()); + MCAssertions.assertHasFindingsStartingWith( + "0xTODO: Unable to decorate setter of `x` as such a method already exists."); } @Override