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 @@ ...@@ -6,7 +6,7 @@
<groupId>de.example.main</groupId> <groupId>de.example.main</groupId>
<artifactId>EASmellsDetection</artifactId> <artifactId>EASmellsDetection</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0</version>
<name>EA Smell Detection</name> <name>EA Smell Detection</name>
<properties> <properties>
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
<directory>${basedir}</directory> <directory>${basedir}</directory>
<includes> <includes>
<include>CentralModel.xml</include> <include>CentralModel.xml</include>
<include>Test.xml</include> <include>SmellExample.xml</include>
<include>archimate3_Diagram.xsd</include> <include>archimate3_Diagram.xsd</include>
<include>archimate3_View.xsd</include> <include>archimate3_View.xsd</include>
<include>archimate3_Model.xsd</include> <include>archimate3_Model.xsd</include>
......
...@@ -5,22 +5,33 @@ import de.example.smells.*; ...@@ -5,22 +5,33 @@ import de.example.smells.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/**
* main class that manages the different detectors
*/
public class EASmellDetector { public class EASmellDetector {
public static void main(String[] args) { 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); ModelAdapter model = new ModelAdapter(args[0], args.length == 2 ? args[1] : null);
Detector.setModel(model); Detector.setModel(model);
// register detectors
List<Detector> detectors = new ArrayList<>(); List<Detector> detectors = new ArrayList<>();
detectors.add(new AmbiguousViewpoint());
detectors.add(new ChattyService());
detectors.add(new CyclicDependency()); detectors.add(new CyclicDependency());
detectors.add(new DataService());
detectors.add(new DeadComponent()); detectors.add(new DeadComponent());
detectors.add(new DenseStructure()); detectors.add(new DenseStructure());
detectors.add(new Documentation()); detectors.add(new Documentation());
detectors.add(new Duplication()); detectors.add(new Duplication());
detectors.add(new HubLikeModularization()); detectors.add(new HubLikeModularization());
detectors.add(new LazyComponent());
detectors.add(new MessageChain());
detectors.add(new SharedPersistency()); detectors.add(new SharedPersistency());
detectors.add(new StrictLayersViolation()); detectors.add(new StrictLayersViolation());
detectors.add(new WeakenedModularity()); detectors.add(new WeakenedModularity());
// detect each smell
System.out.print("\n"); System.out.print("\n");
long startTotalTime = System.nanoTime(); long startTotalTime = System.nanoTime();
for (Detector detector : detectors) { for (Detector detector : detectors) {
...@@ -33,6 +44,8 @@ public class EASmellDetector { ...@@ -33,6 +44,8 @@ public class EASmellDetector {
System.out.println("Finished detection of " + detector.getSmellName() + " in " + time + " (" + memory + ")\n"); 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()); printSmells(Detector.getSmells());
String totalTime = calculateTimeConsumption(startTotalTime); String totalTime = calculateTimeConsumption(startTotalTime);
......
...@@ -5,11 +5,12 @@ import org.xml.sax.SAXException; ...@@ -5,11 +5,12 @@ import org.xml.sax.SAXException;
import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/**
* adapt the more complex generated model and extend the functionality
*/
public class ModelAdapter { public class ModelAdapter {
private static ModelType model; private static ModelType model;
...@@ -33,7 +34,8 @@ public class ModelAdapter { ...@@ -33,7 +34,8 @@ public class ModelAdapter {
} }
public ElementType getElementByIdentifier(String id) { 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()) { if (elements.isEmpty()) {
return null; return null;
} else { } else {
...@@ -142,22 +144,107 @@ public class ModelAdapter { ...@@ -142,22 +144,107 @@ public class ModelAdapter {
} }
public List<ElementType> getElementsInLayer(String layer) { 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()); // fallback if organizations is not specified in xml: use the alternative
if (l.isEmpty()) { if (model.getOrganizations().isEmpty()) {
return new ArrayList<>(); return getElementsInLayerAlternative(layer);
} else { } else {
l = l.get(0).getItem(); List<OrganizationType> l = model.getOrganizations().get(0).getItem().stream().filter(e ->
List<ElementType> res = new ArrayList<>(); e.getLabelGroup().get(0).getValue().toLowerCase().contains(layer.toLowerCase())).collect(Collectors.toList());
for (OrganizationType element : l) { if (l.isEmpty()) {
ElementType tmp = (ElementType) element.getIdentifierRef(); return new ArrayList<>();
res.add(tmp); } 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() { public List<Diagram> getViews() {
return model.getViews().getDiagrams().getView(); 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; ...@@ -2,15 +2,27 @@ package de.example.smells;
class Constants { 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 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; // Message Chain
static final int LARGE_FAN_OUT = 10; static final int MAX_SERVICE_CHAIN_LENGTH = 4;
// Weakened Modularity
static final int MIN_INTERNAL_RELATIONS = 3;
static final double MODULARITY_RATIO = 1; static final double MODULARITY_RATIO = 1;
} }
...@@ -13,8 +13,7 @@ public class CyclicDependency extends Detector { ...@@ -13,8 +13,7 @@ public class CyclicDependency extends Detector {
} }
public List<EASmell> detect() { public List<EASmell> detect() {
List<ElementType> elements = model.getElements(); for (ElementType element : model.getElements()) {
for (ElementType element : elements) {
detectCyclicDependency(element); detectCyclicDependency(element);
} }
return result; return result;
...@@ -22,18 +21,23 @@ public class CyclicDependency extends Detector { ...@@ -22,18 +21,23 @@ public class CyclicDependency extends Detector {
private void detectCyclicDependency(ElementType element) { private void detectCyclicDependency(ElementType element) {
int currentSize = 0; int currentSize = 0;
Set<ElementType> referencedElements = new HashSet<>(model.getReferencedElementsOf(element)); Set<ElementType> reachableElements = new HashSet<>(model.getReferencedElementsOf(element));
while (referencedElements.size() > currentSize) { // while new elements were reached
currentSize = referencedElements.size(); while (reachableElements.size() > currentSize) {
Set<ElementType> additionalElements = new HashSet<>(); if (reachableElements.contains(element)) {
for (ElementType e : referencedElements) { addToSmells(new EASmell(getSmellName(), element));
additionalElements.addAll(model.getReferencedElementsOf(e)); 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; package de.example.smells;
import de.example.model.ElementType; import de.example.model.ElementType;
import de.example.model.RelationshipType;
import java.util.List; import java.util.List;
import java.util.Set;
public class DeadComponent extends Detector { public class DeadComponent extends Detector {
...@@ -11,12 +13,27 @@ public class DeadComponent extends Detector { ...@@ -11,12 +13,27 @@ public class DeadComponent extends Detector {
} }
public List<EASmell> detect() { public List<EASmell> detect() {
List<ElementType> elements = model.getElements(); for (ElementType element : model.getElements()) {
for (ElementType element : elements) { detectDeadComponent(element);
if (model.getReferencedElementsOf(element).isEmpty() && model.getElementsWithReferenceTo(element).isEmpty()) {
addToSmells(new EASmell(getSmellName(), element));
}
} }
return result; 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;