Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -146,12 +146,13 @@ public void endVisit(ASTCDAssociation node) {

public void setupModifiers(ASTModifier assocSideModifier, ASTModifier assocModifier,
CDRoleSymbol roleSymbol) {
roleSymbol.setIsPublic(assocSideModifier.isPublic() || assocModifier.isPublic());
roleSymbol.setIsPrivate(assocSideModifier.isPrivate() || assocModifier.isPrivate());
roleSymbol.setIsProtected(assocSideModifier.isProtected() || assocModifier.isProtected());
roleSymbol.setIsPublic(assocSideModifier.isPublic());
roleSymbol.setIsPrivate(assocSideModifier.isPrivate());
roleSymbol.setIsProtected(assocSideModifier.isProtected());
roleSymbol.setIsStatic(assocSideModifier.isStatic() || assocModifier.isStatic());
roleSymbol.setIsFinal(assocSideModifier.isFinal() || assocModifier.isFinal());
roleSymbol.setIsDerived(assocSideModifier.isDerived() || assocModifier.isDerived());
roleSymbol.setIsReadOnly(assocSideModifier.isReadonly() || assocModifier.isReadonly());
}

public static void addRoleToTheirType(CDRoleSymbol symbol, TypeSymbol otherType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ protected void addEbnfCoCos(CDAssociationCoCoChecker checker) {
checker.addCoCo(new CDAssociationOrderedCardinalityGreaterOne());
checker.addCoCo(new CDAssociationRoleNameLowerCase());
checker.addCoCo(new CDAssociationRoleNameNoConflictWithLocalAttribute());
checker.addCoCo(new CDAssociationModifierCoCo());
checker.addCoCo(new CDAssociationJavaClassTypeCoCo());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* (c) https://github.com/MontiCore/monticore */
package de.monticore.cdassociation.cocos.ebnf;

import de.monticore.cdassociation._ast.ASTCDAssociation;
import de.monticore.cdassociation._cocos.CDAssociationASTCDAssociationCoCo;
import de.monticore.cdassociation._symboltable.ICDAssociationScope;
import de.monticore.cdbasis._symboltable.CDTypeSymbol;
import de.monticore.symbols.basicsymbols._symboltable.TypeSymbol;
import de.monticore.types.mcbasictypes._ast.ASTMCQualifiedType;
import de.se_rwth.commons.logging.Log;

public class CDAssociationJavaClassTypeCoCo implements CDAssociationASTCDAssociationCoCo {

@Override
public void check(ASTCDAssociation node) {
check(node, node.getLeft().getMCQualifiedType(), "left");
check(node, node.getRight().getMCQualifiedType(), "right");
}

protected void check(ASTCDAssociation a, ASTMCQualifiedType qualifiedType, String sideName) {

if (qualifiedType == null || a == null) {
return;
}

ICDAssociationScope scope = a.getEnclosingScope();
String qName = qualifiedType.getMCQualifiedName().getQName();

var optTypeSymbol = scope.resolveType(qName);

if (optTypeSymbol.isPresent()) {
TypeSymbol typeSymbol = optTypeSymbol.get();

if (!(typeSymbol instanceof CDTypeSymbol)) {
String assocName = a.isPresentName() ? a.getName() : "unnamed";

Log.error(String.format(
"0xCDA64: Java class '%s' cannot be used on the %s side of association '%s'.", qName,
sideName, assocName), a.get_SourcePositionStart());

}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* (c) https://github.com/MontiCore/monticore */
package de.monticore.cdassociation.cocos.ebnf;

import de.monticore.cdassociation._ast.ASTCDAssociation;
import de.monticore.cdassociation._cocos.CDAssociationASTCDAssociationCoCo;
import de.monticore.cdassociation._symboltable.ICDAssociationScope;
import de.monticore.cdbasis._symboltable.CDTypeSymbol;
import de.monticore.symbols.basicsymbols._symboltable.TypeSymbol;
import de.monticore.umlmodifier._ast.ASTModifier;
import de.se_rwth.commons.logging.Log;

import java.util.Optional;
import java.util.function.Predicate;

public class CDAssociationModifierCoCo implements CDAssociationASTCDAssociationCoCo {

@Override
public void check(ASTCDAssociation a) {

String assocName = a.isPresentName() ? a.getName() : "unnamed";

// Check for conflicting visibility modifiers
boolean hasMain = hasVisibility(a.getModifier());
boolean hasRoleVisibility = hasVisibility(a.getLeft().getModifier()) || hasVisibility(a
.getRight().getModifier());

if (hasMain && hasRoleVisibility) {
Log.warn(String.format(
"The association '%s' cannot combine a main visibility modifier with individual role visibility modifiers.",
assocName), a.get_SourcePositionStart());
}

//----------------------------------check the rest modifiers-----------------------------------------------------------

//// Ensure that the association does not connect static classes
ICDAssociationScope scope = a.getEnclosingScope();

boolean staticClassLeft = isStaticType(scope.resolveType(a.getLeft().getName()));
boolean staticClassRight = isStaticType(scope.resolveType(a.getRight().getName()));

if (staticClassLeft || staticClassRight) {
Log.error(String.format("0xCDA66: The association '%s' cannot connect static classes.",
assocName), a.get_SourcePositionStart());
}

//// Ensure that neither the association nor its roles use forbidden modifiers (static, abstract, local)
checkForbiddenModifier(a, ASTModifier::isStatic, "0xCDA61", String.format(
"The association '%s' (or its roles) must not use static modifiers.", assocName));
checkForbiddenModifier(a, ASTModifier::isAbstract, "0xCDA67", String.format(
"The association '%s' (or its roles) must not use an abstract modifier.", assocName));
checkForbiddenModifier(a, ASTModifier::isLocal, "0xCDA68", String.format(
"The association '%s' (or its roles) must not use a local modifier.", assocName));

}

/**
* Checks whether the given modifier specifies an explicit visibility
* (i.e., public, protected, or private).
*
* @param modifier the AST modifier to check (can be null)
* @return true if a public, protected, or private modifier is present, false otherwise
*/
private boolean hasVisibility(ASTModifier modifier) {
return modifier != null && (modifier.isPublic() || modifier.isProtected() || modifier
.isPrivate());
}

/**
* Safely checks a boolean flag on an ASTModifier, handling null values.
*
* @param modifier the AST modifier to check (can be null)
* @param check a predicate representing the flag to check
* @return true if the modifier is present and the check evaluates to true, false otherwise
*/
private boolean hasModifier(ASTModifier modifier, Predicate<ASTModifier> check) {
return modifier != null && check.test(modifier);
}

/**
* Checks whether the given type symbol represents a static class.
*
* @param optSymbol an optional type symbol resolved from the scope
* @return true if the symbol is a static class, false otherwise
*/
private boolean isStaticType(Optional<TypeSymbol> optSymbol) {
if (optSymbol.isPresent() && optSymbol.get() instanceof CDTypeSymbol typeSymbol) {
return typeSymbol.isPresentAstNode() && typeSymbol.getAstNode().getModifier().isStatic();
}
return false;
}

/**
* Checks whether a forbidden modifier is present on the association itself or its roles,
* and logs an error if the check evaluates to true.
*
* @param a the association AST node to check
* @param check a predicate representing the modifier flag to check
* @param errorCode the error code to log
* @param errorMessage the error message to log
*/
private void checkForbiddenModifier(ASTCDAssociation a, Predicate<ASTModifier> check,
String errorCode, String errorMessage) {
boolean main = hasModifier(a.getModifier(), check);
boolean left = hasModifier(a.getLeft().getModifier(), check);
boolean right = hasModifier(a.getRight().getModifier(), check);
if (main || left || right) {
Log.error(String.format("%s: %s", errorCode, errorMessage), a.get_SourcePositionStart());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* (c) https://github.com/MontiCore/monticore */
package de.monticore.testcdassociation.cocos;

import de.monticore.cdassociation.cocos.ebnf.CDAssociationJavaClassTypeCoCo;
import de.monticore.cdbasis._ast.ASTCDCompilationUnit;
import de.monticore.testcdassociation.CDAssociationTestBasis;
import de.se_rwth.commons.logging.Log;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

public class CDAssociationLeftAndRightJavaClassInvalidTest extends CDAssociationTestBasis {

@Test
public void testValid() throws IOException {
coCoChecker.addCoCo(new CDAssociationJavaClassTypeCoCo());
final Optional<ASTCDCompilationUnit> optAST = p.parse(getFilePath(
"cdassociation/cocos/CDAssociationLeftAndRightJavaClassValid.cd"));
assertTrue(optAST.isPresent());

final ASTCDCompilationUnit ast = optAST.get();
Log.getFindings().clear();
createSymTab(ast);
completeSymTab(ast);
coCoChecker.checkAll(ast);
assertTrue(Log.getFindings().isEmpty());
}

@Test
public void testInvalid() throws IOException {

coCoChecker.addCoCo(new CDAssociationJavaClassTypeCoCo());
final Optional<ASTCDCompilationUnit> optAST = p.parse(getFilePath(
"cdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalid.cd"));
assertTrue(optAST.isPresent());

final ASTCDCompilationUnit ast = optAST.get();
Log.getFindings().clear();
createSymTab(ast);
completeSymTab(ast);
coCoChecker.checkAll(ast);
assertTrue(Log.getFindings().get(0).getMsg().startsWith("0xCDA64"));
assertEquals(4, Log.getFindings().size());
Log.clearFindings();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* (c) https://github.com/MontiCore/monticore */
package de.monticore.testcdassociation.cocos;

import de.monticore.cdassociation.cocos.ebnf.CDAssociationModifierCoCo;
import de.monticore.cdassociation.cocos.ebnf.CDAssociationNameUnique;
import de.monticore.cdbasis._ast.ASTCDCompilationUnit;
import de.monticore.testcdassociation.CDAssociationTestBasis;
import de.se_rwth.commons.logging.Log;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class CDAssociationModifierCoCoTest extends CDAssociationTestBasis {

@Test
public void testValid() throws IOException {
coCoChecker.addCoCo(new CDAssociationNameUnique());
final Optional<ASTCDCompilationUnit> optAST = p.parse(getFilePath(
"cdassociation/cocos/Valid.cd"));
assertTrue(optAST.isPresent());
final ASTCDCompilationUnit ast = optAST.get();
Log.getFindings().clear();
coCoChecker.checkAll(ast);
assertTrue(Log.getFindings().isEmpty());
}

//visibility-modifiers tests
@Test
public void testModifiersValid() throws IOException {
coCoChecker.addCoCo(new CDAssociationModifierCoCo());
final Optional<ASTCDCompilationUnit> optAST = p.parse(getFilePath(
"cdassociation/cocos/CDAssociationModifierCoCoValid.cd"));
assertTrue(optAST.isPresent());
final ASTCDCompilationUnit ast = optAST.get();
Log.getFindings().clear();
createSymTab(ast);
coCoChecker.checkAll(ast);
assertTrue(Log.getFindings().isEmpty());
}

// static/abstract/local modifier test
@Test
public void testModifierInvalid() throws IOException {
coCoChecker.addCoCo(new CDAssociationModifierCoCo());
final Optional<ASTCDCompilationUnit> optAST = p.parse(getFilePath(
"cdassociation/cocos/CDAssociationModifierCoCoInvalid.cd"));
assertTrue(optAST.isPresent());
final ASTCDCompilationUnit ast = optAST.get();
Log.getFindings().clear();
createSymTab(ast);
coCoChecker.checkAll(ast);
assertEquals(13, Log.getFindings().size());
Log.getFindings().clear();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* (c) https://github.com/MontiCore/monticore */

classdiagram CDAssociationLeftAndRightJavaClassInvalid{

class A{
}

//left side is an external java class
association leftJava java.lang.String -> A;

// right side is an external java class
association rightJava A -> java.lang.Integer;

// both sides are external java classes
association bothJava java.lang.Boolean -> java.lang.Long;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* (c) https://github.com/MontiCore/monticore */

classdiagram CDAssociationLeftAndRightJavaClassInvalid{

public class A{
java.lang.String attributeA1;
java.lang.Integer attributeA2;
}

private abstract class B{
readonly int attributeB1;
final java.lang.String attributeB2;
}

protected static class C {
}

association abstract B -> A;
association [0..2] A -> B;

association A -> C;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* (c) https://github.com/MontiCore/monticore */

classdiagram CDAssociationModifierCoCoInvalid{

package p1 {
public class A {
int foo;
int bar;
int field;
}
class B extends A;
}
package p2 {
class C;
static class D;
}

public association visibilityAssocLeft private p1.A -> p2.C;
private association visibilityAssocRight p1.A -> p2.C public;

static association staticAssoc p1.A -> p2.C;
public association leftRoleStatic static p1.A -> p2.C;
private association rightRoleStatic [0..1] p1.A -> p2.C [*] static;

local association localAssoc p1.A -> p2.C;
public association leftLocalRole local p1.A -> p2.C;
private association rightRoleLocal [0..1] p1.A -> p2.C [*] local;

abstract association abstractAssoc p1.A -> p2.C;
public association leftRoleAbstract abstract p1.A -> p2.C;
private association rightRoleAbstract [0..1] p1.A -> p2.C [*] abstract;

association staticClassLeft p2.D -> p1.A;
association staticClassRight p2.C -> p2.D;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* (c) https://github.com/MontiCore/monticore */

classdiagram CDAssociationModifierCoCoValid{

package p1 {
public class A {
int foo;
String field;
}
}
package p2 {
class B;
}

public association assocVisibility p1.A -> p2.B;
association leftVisibility private p1.A -> p2.B;
association rightVisibility p1.A -> p2.B public;

public association finalRole final p2.B -> p1.A final;
private association derivedRoles derived p2.B -> p1.A derived;
protected association readOnlyRoles readonly p1.A -> p2.B readonly;
}
Loading