Commit 233a41e6 authored by Johannes Salentin's avatar Johannes Salentin

Merge branch 'release/1.0'

parents f0e6a95a 592aff0b
Pipeline #163002 passed with stages
in 2 minutes and 3 seconds
This diff is collapsed.
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<model xmlns="http://www.opengroup.org/xsd/archimate/3.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengroup.org/xsd/archimate/3.0/ http://www.opengroup.org/xsd/archimate/3.0/archimate3_Diagram.xsd" identifier="id-825fc864-cf1b-44ee-9aae-e18b93b010f5">
<name xml:lang="de">Test</name>
<elements>
<element identifier="id-c75917ca-5c20-4fc1-be7d-8756eef684b0" xsi:type="BusinessService">
<name xml:lang="de">S1</name>
</element>
<element identifier="id-435bc94c-32fc-48fb-8677-e2ff74eb32c8" xsi:type="BusinessService">
<name xml:lang="de">S2</name>
</element>
<element identifier="id-ab882019-9db1-4442-800f-c106b05d143c" xsi:type="BusinessService">
<name xml:lang="de">S3</name>
</element>
</elements>
<relationships>
<relationship identifier="id-db86487d-18d5-44ad-93ab-6033c0483e44" source="id-c75917ca-5c20-4fc1-be7d-8756eef684b0" target="id-435bc94c-32fc-48fb-8677-e2ff74eb32c8" xsi:type="Serving" />
<relationship identifier="id-85ef5ebb-0cf3-4a6f-ae84-96db26374e54" source="id-435bc94c-32fc-48fb-8677-e2ff74eb32c8" target="id-ab882019-9db1-4442-800f-c106b05d143c" xsi:type="Serving" />
<relationship identifier="id-3331e143-5efe-47af-b2a1-90334bcb736e" source="id-ab882019-9db1-4442-800f-c106b05d143c" target="id-c75917ca-5c20-4fc1-be7d-8756eef684b0" xsi:type="Serving" />
</relationships>
<views>
<diagrams>
<view identifier="id-605923ee-be74-40a4-9526-199a71bffa5c" xsi:type="Diagram">
<name xml:lang="de">Default View</name>
<node identifier="id-47063029-f542-4d37-b937-0a4bc8aa66aa" elementRef="id-c75917ca-5c20-4fc1-be7d-8756eef684b0" xsi:type="Element" x="120" y="72" w="505" h="181">
<style>
<fillColor r="255" g="255" b="181" a="100" />
<lineColor r="92" g="92" b="92" />
<font name="Segoe UI" size="9">
<color r="0" g="0" b="0" />
</font>
</style>
</node>
<node identifier="id-63ae4155-ef68-4f1b-9d0e-5ecf07b22f26" elementRef="id-435bc94c-32fc-48fb-8677-e2ff74eb32c8" xsi:type="Element" x="720" y="132" w="241" h="121">
<style>
<fillColor r="255" g="255" b="181" a="100" />
<lineColor r="92" g="92" b="92" />
<font name="Segoe UI" size="9">
<color r="0" g="0" b="0" />
</font>
</style>
</node>
<node identifier="id-546e1ea3-d887-4aca-b2d0-a83fa3243196" elementRef="id-ab882019-9db1-4442-800f-c106b05d143c" xsi:type="Element" x="180" y="372" w="385" h="121">
<style>
<fillColor r="255" g="255" b="181" a="100" />
<lineColor r="92" g="92" b="92" />
<font name="Segoe UI" size="9">
<color r="0" g="0" b="0" />
</font>
</style>
</node>
<connection identifier="id-d08a21eb-2c62-4f01-8a4c-9b060b0fa6b2" relationshipRef="id-db86487d-18d5-44ad-93ab-6033c0483e44" xsi:type="Relationship" source="id-47063029-f542-4d37-b937-0a4bc8aa66aa" target="id-63ae4155-ef68-4f1b-9d0e-5ecf07b22f26">
<style>
<lineColor r="0" g="0" b="0" />
<font name="Segoe UI" size="9">
<color r="0" g="0" b="0" />
</font>
</style>
</connection>
<connection identifier="id-8f89b5a3-712a-4f5b-841e-beee2f87b868" relationshipRef="id-85ef5ebb-0cf3-4a6f-ae84-96db26374e54" xsi:type="Relationship" source="id-63ae4155-ef68-4f1b-9d0e-5ecf07b22f26" target="id-546e1ea3-d887-4aca-b2d0-a83fa3243196">
<style>
<lineColor r="0" g="0" b="0" />
<font name="Segoe UI" size="9">
<color r="0" g="0" b="0" />
</font>
</style>
</connection>
<connection identifier="id-ee353188-d6cd-4122-aec9-738467d1d1fd" relationshipRef="id-3331e143-5efe-47af-b2a1-90334bcb736e" xsi:type="Relationship" source="id-546e1ea3-d887-4aca-b2d0-a83fa3243196" target="id-47063029-f542-4d37-b937-0a4bc8aa66aa">
<style>
<lineColor r="0" g="0" b="0" />
<font name="Segoe UI" size="9">
<color r="0" g="0" b="0" />
</font>
</style>
</connection>
</view>
</diagrams>
</views>
</model>
......@@ -6,7 +6,7 @@
<groupId>de.example.main</groupId>
<artifactId>EASmellsDetection</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.0</version>
<name>EA Smell Detection</name>
<properties>
......@@ -36,7 +36,7 @@
<directory>${basedir}</directory>
<includes>
<include>CentralModel.xml</include>
<include>Test.xml</include>
<include>SmellExample.xml</include>
<include>archimate3_Diagram.xsd</include>
<include>archimate3_View.xsd</include>
<include>archimate3_Model.xsd</include>
......
......@@ -5,22 +5,33 @@ import de.example.smells.*;
import java.util.ArrayList;
import java.util.List;
/**
* main class that manages the different detectors
*/
public class EASmellDetector {
public static void main(String[] args) {
// parsing of input xml to this ModelAdapter
ModelAdapter model = new ModelAdapter(args[0], args.length == 2 ? args[1] : null);
Detector.setModel(model);
// register detectors
List<Detector> detectors = new ArrayList<>();
detectors.add(new AmbiguousViewpoint());
detectors.add(new ChattyService());
detectors.add(new CyclicDependency());
detectors.add(new DataService());
detectors.add(new DeadComponent());
detectors.add(new DenseStructure());
detectors.add(new Documentation());
detectors.add(new Duplication());
detectors.add(new HubLikeModularization());
detectors.add(new LazyComponent());
detectors.add(new MessageChain());
detectors.add(new SharedPersistency());
detectors.add(new StrictLayersViolation());
detectors.add(new WeakenedModularity());
// detect each smell
System.out.print("\n");
long startTotalTime = System.nanoTime();
for (Detector detector : detectors) {
......@@ -33,6 +44,8 @@ public class EASmellDetector {
System.out.println("Finished detection of " + detector.getSmellName() + " in " + time + " (" + memory + ")\n");
}
// print total result
System.out.println("The following " + Detector.getSmells().size() + " smells were detected:");
printSmells(Detector.getSmells());
String totalTime = calculateTimeConsumption(startTotalTime);
......
......@@ -5,11 +5,12 @@ import org.xml.sax.SAXException;
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
* adapt the more complex generated model and extend the functionality
*/
public class ModelAdapter {
private static ModelType model;
......@@ -33,7 +34,8 @@ public class ModelAdapter {
}
public ElementType getElementByIdentifier(String id) {
List<ElementType> elements = model.getElements().getElement().stream().filter(e -> e.getIdentifier().equals(id)).collect(Collectors.toList());
List<ElementType> elements = model.getElements().getElement().stream().filter(e -> e.getIdentifier().equals(id))
.collect(Collectors.toList());
if (elements.isEmpty()) {
return null;
} else {
......@@ -142,22 +144,107 @@ public class ModelAdapter {
}
public List<ElementType> getElementsInLayer(String layer) {
List<OrganizationType> l = model.getOrganizations().get(0).getItem().stream().filter(e -> e.getLabelGroup().get(0).getValue().toLowerCase().contains(layer.toLowerCase())).collect(Collectors.toList());
if (l.isEmpty()) {
return new ArrayList<>();
// fallback if organizations is not specified in xml: use the alternative
if (model.getOrganizations().isEmpty()) {
return getElementsInLayerAlternative(layer);
} else {
l = l.get(0).getItem();
List<ElementType> res = new ArrayList<>();
for (OrganizationType element : l) {
ElementType tmp = (ElementType) element.getIdentifierRef();
res.add(tmp);
List<OrganizationType> l = model.getOrganizations().get(0).getItem().stream().filter(e ->
e.getLabelGroup().get(0).getValue().toLowerCase().contains(layer.toLowerCase())).collect(Collectors.toList());
if (l.isEmpty()) {
return new ArrayList<>();
} else if (l.get(0).getItem().isEmpty()) {
return new ArrayList<>();
} else {
l = l.get(0).getItem();
List<ElementType> res = new ArrayList<>();
for (OrganizationType element : l) {
ElementType tmp = (ElementType) element.getIdentifierRef();
res.add(tmp);
}
return res;
}
}
}
public List<ElementType> getElementsOfType(String[] types) {
return getElements().stream().filter(e -> {
for (String type : types) {
if (e.getClass().getSimpleName().equals(type)) {
return true;
}
}
return res;
return false;
}).collect(Collectors.toList());
}
// alternative for getElementsInLayer when organizations is not specified in xml
public List<ElementType> getElementsInLayerAlternative(String layer) {
String[] types;
switch (layer) {
case "Business":
types = new String[]{"BusinessActor", "BusinessRole", "BusinessCollaboration", "BusinessInterface",
"BusinessProcess", "BusinessFunction", "BusinessInteraction", "BusinessEvent", "BusinessService",
"BusinessObject", "Contract", "Representation", "Product"};
break;
case "Application":
types = new String[]{"ApplicationComponent", "ApplicationCollaboration", "ApplicationInterface",
"ApplicationFunction", "ApplicationInteraction", "ApplicationProcess", "ApplicationEvent",
"ApplicationService", "DataObject"};
break;
case "Technology":
types = new String[]{"Node", "Device", "SystemSoftware", "TechnologyCollaboration", "TechnologyInterface", "Path",
"CommunicationNetwork", "TechnologyFunction", "TechnologyProcess", "TechnologyInteraction", "TechnologyEvent",
"TechnologyService", "Artifact", "Equipment", "Facility", "DistributionNetwork", "Material"};
break;
default:
types = new String[]{};
}
return getElementsOfType(types);
}
public List<Diagram> getViews() {
return model.getViews().getDiagrams().getView();
}
// a cluster is in one layer and is related with structural relations to each other
public Set<ElementType> getCluster(ElementType element) {
Set<ElementType> elementAsSet = new HashSet<>();
elementAsSet.add(element);
List<ElementType> businessElements = getElementsInLayer("Business");
if (businessElements.contains(element)) {
return getCluster(elementAsSet, businessElements);
} else {
List<ElementType> applicationElements = getElementsInLayer("Application");
if (applicationElements.contains(element)) {
return getCluster(elementAsSet, applicationElements);
} else {
List<ElementType> technologyElements = getElementsInLayer("Technology");
if (technologyElements.contains(element)) {
return getCluster(elementAsSet, technologyElements);
}
return elementAsSet;
}
}
}
private Set<ElementType> getCluster(Set<ElementType> elements, List<ElementType> elementsInSameLayer) {
Set<ElementType> res = new HashSet<>(elements);
String[] structuralRelationsOut = {"Aggregation", "Realization", "Composition"};
String[] structuralRelationsIn = {"Assignment"};
for (ElementType element : elements) {
Set<ElementType> children = new HashSet<>(getReferencedElementsOf(element, structuralRelationsOut));
children.addAll(getElementsWithReferenceTo(element, structuralRelationsIn));
children = children.stream().filter(elementsInSameLayer::contains).collect(Collectors.toSet());
if (!children.isEmpty()) {
res.addAll(getCluster(children, elementsInSameLayer));
}
}
return res;
}
public boolean isNotStructural(RelationshipType relationship) {
String type = relationship.getClass().getSimpleName();
return !type.contains("Realization") && !type.contains("Assignment") && !type.contains("Aggregation") && !type.contains("Composition");
}
}
package de.example.smells;
import de.example.model.Diagram;
import java.util.List;
public class AmbiguousViewpoint extends Detector {
public AmbiguousViewpoint() {
super("Ambiguous Viewpoint");
}
public List<EASmell> detect() {
for (Diagram view : model.getViews()) {
if (view.getViewpoint() == null) {
addToSmells(new EASmell(getSmellName(), null, " at the View \"" + view.getNameGroup().get(0).getValue() + "\" (" + view.getIdentifier() + ")"));
}
}
return result;
}
}
package de.example.smells;
import de.example.model.ElementType;
import de.example.model.RelationshipType;
import java.util.List;
import java.util.stream.Collectors;
import static de.example.smells.Constants.MAX_CHATTY_SERVICE_RELATIONS;
public class ChattyService extends Detector {
public ChattyService() {
super("Chatty Service");
}
public List<EASmell> detect() {
List<ElementType> serviceElements = model.getElements().stream().filter(e ->
e.getClass().getSimpleName().contains("Service")).collect(Collectors.toList());
for (ElementType serviceElement : serviceElements) {
int relationCount = 0;
for (RelationshipType relationship : model.getRelationships()) {
if (model.isNotStructural(relationship)) {
ElementType source = (ElementType) relationship.getSource();
ElementType target = (ElementType) relationship.getTarget();
if (serviceElements.contains(source) && serviceElements.contains(target) && (serviceElement.equals(source) || serviceElement.equals(target))) {
relationCount++;
}
}
}
if (relationCount > MAX_CHATTY_SERVICE_RELATIONS) {
addToSmells(new EASmell(getSmellName(), serviceElement, " with " + relationCount + " related services"));
}
}
return result;
}
}
......@@ -2,15 +2,27 @@ package de.example.smells;
class Constants {
static final double MAX_AVG_DEGREE = 2;
// Chatty Service
static final int MAX_CHATTY_SERVICE_RELATIONS = 4;
// Dense Structure
static final double MAX_AVG_DEGREE = 1.75;
// Documentation
static final int MAX_DOCUMENTATION_LENGTH = 256;
static final int DUPLICATED_WORDS = 2;
// Duplication
static final double DUPLICATED_WORDS_RATIO = 0.75;
// Hub-like Modularization
static final int LARGE_FAN_IN = 7;
static final int LARGE_FAN_OUT = 7;
static final int LARGE_FAN_IN = 10;
static final int LARGE_FAN_OUT = 10;
// Message Chain
static final int MAX_SERVICE_CHAIN_LENGTH = 4;
// Weakened Modularity
static final int MIN_INTERNAL_RELATIONS = 3;
static final double MODULARITY_RATIO = 1;
}
......@@ -13,8 +13,7 @@ public class CyclicDependency extends Detector {
}
public List<EASmell> detect() {
List<ElementType> elements = model.getElements();
for (ElementType element : elements) {
for (ElementType element : model.getElements()) {
detectCyclicDependency(element);
}
return result;
......@@ -22,18 +21,23 @@ public class CyclicDependency extends Detector {
private void detectCyclicDependency(ElementType element) {
int currentSize = 0;
Set<ElementType> referencedElements = new HashSet<>(model.getReferencedElementsOf(element));
while (referencedElements.size() > currentSize) {
currentSize = referencedElements.size();
Set<ElementType> additionalElements = new HashSet<>();
for (ElementType e : referencedElements) {
additionalElements.addAll(model.getReferencedElementsOf(e));
Set<ElementType> reachableElements = new HashSet<>(model.getReferencedElementsOf(element));
// while new elements were reached
while (reachableElements.size() > currentSize) {
if (reachableElements.contains(element)) {
addToSmells(new EASmell(getSmellName(), element));
break;
}
referencedElements.addAll(additionalElements);
currentSize = reachableElements.size();
reachableElements.addAll(getAdditionalElements(reachableElements));
}
if (referencedElements.contains(element)) {
EASmell cd = new EASmell(getSmellName(), element);
addToSmells(cd);
}
private Set<ElementType> getAdditionalElements(Set<ElementType> reachableElements) {
Set<ElementType> additionalElements = new HashSet<>();
for (ElementType e : reachableElements) {
additionalElements.addAll(model.getReferencedElementsOf(e));
}
return additionalElements;
}
}
package de.example.smells;
import de.example.model.ElementType;
import java.util.List;
import java.util.stream.Collectors;
public class DataService extends Detector {
public DataService() {
super("Data Service");
}
public List<EASmell> detect() {
List<ElementType> serviceElements = model.getElements().stream().filter(e ->
e.getClass().getSimpleName().contains("Service")).collect(Collectors.toList());
List<ElementType> dataElements = model.getElementsOfType(new String[]{"BusinessObject", "DataObject", "SystemSoftware"});
for (ElementType serviceElement : serviceElements) {
List<ElementType> referencedElements = model.getReferencedElementsOf(serviceElement);
if (!referencedElements.isEmpty()) {
boolean onlyDataReferences = true;
for (ElementType referencedElement : referencedElements) {
if (!dataElements.contains(referencedElement)) {
onlyDataReferences = false;
break;
}
}
if (onlyDataReferences) {
addToSmells(new EASmell(getSmellName(), serviceElement));
}
}
}
return result;
}
}
package de.example.smells;
import de.example.model.ElementType;
import de.example.model.RelationshipType;
import java.util.List;
import java.util.Set;
public class DeadComponent extends Detector {
......@@ -11,12 +13,27 @@ public class DeadComponent extends Detector {
}
public List<EASmell> detect() {
List<ElementType> elements = model.getElements();
for (ElementType element : elements) {
if (model.getReferencedElementsOf(element).isEmpty() && model.getElementsWithReferenceTo(element).isEmpty()) {
addToSmells(new EASmell(getSmellName(), element));
}
for (ElementType element : model.getElements()) {
detectDeadComponent(element);
}
return result;
}
private void detectDeadComponent(ElementType element) {
Set<ElementType> cluster = model.getCluster(element);
boolean used = false;
for (RelationshipType relationship : model.getRelationships()) {
ElementType target = (ElementType) relationship.getTarget();
ElementType source = (ElementType) relationship.getSource();
// relationship to element outside the cluster -> so it is used
if ((cluster.contains(source) && !cluster.contains(target)) || (cluster.contains(target) && !cluster.contains(source))) {
used = true;
break;
}
}
if (!used) {
addToSmells(new EASmell(getSmellName(), element));
}
}
}
package de.example.smells;
import de.example.model.ElementType;
import de.example.model.RelationshipType;
import java.util.List;
import java.util.Set;
import static de.example.smells.Constants.MAX_AVG_DEGREE;
......@@ -11,12 +15,31 @@ public class DenseStructure extends Detector {
}
public List<EASmell> detect() {
overallDenseStructure();
for (ElementType element : model.getElements()) {
Set<ElementType> cluster = model.getCluster(element);
double e = 0;
for (RelationshipType relationship : model.getRelationships()) {
ElementType target = (ElementType) relationship.getTarget();
ElementType source = (ElementType) relationship.getSource();
if (cluster.contains(source) && cluster.contains(target)) {
e++;
}
}
double avgDegree = e / cluster.size();
if (avgDegree > MAX_AVG_DEGREE) {
addToSmells(new EASmell("Dense Structure", element, " with average degree of " + avgDegree));
}
}
return result;
}
private void overallDenseStructure() {
double v = model.getElements().size();
double e = model.getRelationships().size();
double avgDegree = e / (v/* * (v - 1)*/);
if (avgDegree > MAX_AVG_DEGREE) {
addToSmells(new EASmell("Dense Structure", null, " with average degree of " + avgDegree));
addToSmells(new EASmell("Overall Dense Structure", null, " with average degree of " + avgDegree));
}
return result;
}
}
......@@ -5,14 +5,20 @@ import de.example.main.ModelAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* A Detector is responsible for the detection of one particular smell
*/
public abstract class Detector {
static ModelAdapter model;
// detected smells of all different detectors
static List<EASmell> smells;
// detected smells of this detector [can be returned by detect()]
List<EASmell> result;
private String smellName;
Detector() {
smells = new ArrayList<>();
result = new ArrayList<>();
}
Detector(String name) {
......@@ -33,11 +39,21 @@ public abstract class Detector {
return smellName;
}