Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
174fcd2
Implemented draft of RMMR algorithm.
RamSaw Mar 28, 2018
6f61018
Made several changes: corrected wrong behaviour because of missing br…
RamSaw Apr 11, 2018
0fd31dd
Corrected test: it ran all algorithms, but assertions for RMMR algo a…
RamSaw Apr 11, 2018
bc85cad
Corrected marks from pull request.
RamSaw Apr 15, 2018
df7b1e0
Added tests for MovieRentalStore example.
RamSaw Apr 24, 2018
08536b2
Added javadocs and made code clean up.
RamSaw Apr 24, 2018
538ff85
Changed to run parallel. Parallel executing is not comfortable for de…
RamSaw Apr 24, 2018
59b19a2
Made RMMR constructor public. IDEA says it can be package-private but…
RamSaw Apr 24, 2018
1fce0bc
Made RmmrEntitySearcher more consistent and turned to qualified names.
RamSaw May 21, 2018
79ebe6b
Moved 0.01 hardcoded constant to static final constant.
RamSaw May 21, 2018
22ac2b6
Removed new expressions, added Builders, Utils, Factory cases, change…
RamSaw May 29, 2018
e227e2b
Added contextual similarity calculation. Very raw version. TODO: opti…
RamSaw Jul 6, 2018
2d0ddd0
Fixed null pointer exception by change get to getOrDefault. Also adde…
RamSaw Jul 6, 2018
e9f71fd
Changed to multisets and all maps from entity moved to entity class t…
RamSaw Jul 6, 2018
00333f5
Deleted redundant documents sustaining. Now it consists of two refere…
RamSaw Jul 9, 2018
2c4d416
Added support of configuration of algorithm.
RamSaw Jul 9, 2018
c1379f4
Added stemming.
RamSaw Jul 9, 2018
2400fb5
Reverted ArchitectureReloaded.iml file.
RamSaw Jul 9, 2018
66c2b2c
fix conflicts commit
RamSaw Jul 9, 2018
826ee53
Added jar for stemming
RamSaw Jul 9, 2018
83168b2
Removed qualified name error fix because tests were not passing.
RamSaw Jul 9, 2018
9eef351
Merge branch 'RMMR_implementation' of https://github.com/ml-in-progra…
RamSaw Jul 9, 2018
42a7471
Erased bug with qualified names and added excluding short names like …
RamSaw Jul 9, 2018
08740ec
Added tests for RMMR and added support for setting up or not field re…
RamSaw Jul 9, 2018
ebe7152
Speeded up algorithm by adding coordinates that are only not null. Th…
RamSaw Jul 10, 2018
b9e8966
Fixed tests. Failing test were uncommented by mistake.
RamSaw Jul 10, 2018
28153b7
Algorithm was optimized more, now bag finder class deleted and its lo…
RamSaw Jul 10, 2018
d2f1015
Found mistake by dividing on zero. Fixed and as a consequence move me…
RamSaw Jul 10, 2018
c0503ef
RMMR was set up to pass more test cases. On real project output is no…
RamSaw Jul 10, 2018
9f884cd
Ignored tests in RmmrDistancesTest and RmmrEntitySearcherTest. Reason…
RamSaw Jul 10, 2018
5cd9df9
Fixed tests failure. Ignored tests in RmmrDistancesTest and RmmrEntit…
RamSaw Jul 10, 2018
82ade8e
Added support for method references: ClassA::methodInClassA.
RamSaw Jul 10, 2018
bc238ca
Added support for class references. For example call of static method…
RamSaw Jul 10, 2018
a342283
Added failure explanations for all tests which fail.
RamSaw Jul 10, 2018
f15f724
Corrected weights for conceptual and contextual similarity.
RamSaw Jul 10, 2018
9bd97d4
Corrected accuracy formula - now output is a little bit better.
RamSaw Jul 11, 2018
fd94e63
Made clean up of code, code prepared for review. Deleted old test data.
RamSaw Jul 11, 2018
3d86756
Corrected accuracy formula to fit other algorithms. Before that accur…
RamSaw Jul 11, 2018
afa2096
Fixes #50
RamSaw Jul 11, 2018
9fc5780
Fixed test failure.
RamSaw Jul 11, 2018
ac4cf63
Reverted bug fix, because it introduced new bug.
RamSaw Jul 13, 2018
bd370e9
Merge remote-tracking branch 'origin/master' into RMMR_implementation
RamSaw Jul 16, 2018
c183397
Merge remote-tracking branch 'origin/master' into RMMR_implementation
RamSaw Jul 16, 2018
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
6 changes: 0 additions & 6 deletions .idea/copyright/ArchitectureReloaded.xml

This file was deleted.

7 changes: 0 additions & 7 deletions .idea/copyright/profiles_settings.xml

This file was deleted.

1 change: 1 addition & 0 deletions ArchitectureReloaded.iml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/out" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
Expand Down
14 changes: 4 additions & 10 deletions openapi/openapi.iml
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module relativePaths="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/classes" />
<output-test url="file://$MODULE_DIR$/build/testclasses" />
<module external.linked.project.id="openapi" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

</module>
196 changes: 196 additions & 0 deletions src/main/java/org/ml_methods_group/algorithm/RMMR.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package org.ml_methods_group.algorithm;

import org.apache.log4j.Logger;
import org.ml_methods_group.algorithm.entity.ClassEntity;
import org.ml_methods_group.algorithm.entity.EntitySearchResult;
import org.ml_methods_group.algorithm.entity.MethodEntity;
import org.ml_methods_group.config.Logging;
import org.ml_methods_group.utils.AlgorithmsUtil;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
* Implementation of RMMR (Recommendation of Move Method Refactoring) algorithm.
* Based on @see <a href="https://drive.google.com/file/d/17yAlVXRaLuhIcXB4PEzNiZj5p1oi4HlL/view">this article</a>.
*/
public class RMMR extends Algorithm {
/**
* Internal name of the algorithm in the program.
*/
public static final String NAME = "RMMR";
private static final Logger LOGGER = Logging.getLogger(RMMR.class);

/**
* Map: class -> set of method in this class.
*/
private final Map<ClassEntity, Set<MethodEntity>> methodsByClass = new HashMap<>();
/**
* Methods to check for refactoring.
*/
private final List<MethodEntity> units = new ArrayList<>();
/**
* Classes to which method will be considered for moving.
*/
private final List<ClassEntity> classEntities = new ArrayList<>();
private final AtomicInteger progressCount = new AtomicInteger();
/**
* Context which stores all found classes, methods and its metrics (by storing Entity).
*/
private ExecutionContext context;

public RMMR() {
super(NAME, true);
}

@Override
protected List<Refactoring> calculateRefactorings(ExecutionContext context, boolean enableFieldRefactorings) {
if (enableFieldRefactorings) {
// TODO: write to LOGGER or throw Exception? Change UI: disable field checkbox if only RMMR is chosen.
LOGGER.error("Field refactorings are not supported",
new UnsupportedOperationException("Field refactorings are not supported"));
}
this.context = context;
init();

/*
List<Refactoring> accum = new LinkedList<>();
units.forEach(methodEntity -> findRefactoring(methodEntity, accum));
return accum;
*/
return runParallel(units, context, ArrayList::new, this::findRefactoring, AlgorithmsUtil::combineLists);
}

/**
* Initializes units, methodsByClass, classEntities. Data is gathered from context.getEntities().
*/
private void init() {
final EntitySearchResult entities = context.getEntities();
LOGGER.info("Init RMMR");
units.clear();
classEntities.clear();
methodsByClass.clear();

classEntities.addAll(entities.getClasses());
units.addAll(entities.getMethods());
progressCount.set(0);

entities.getMethods().forEach(methodEntity -> {
List<ClassEntity> methodClassEntity = entities.getClasses().stream()
.filter(classEntity -> methodEntity.getClassName().equals(classEntity.getName()))
.collect(Collectors.toList());
if (methodClassEntity.size() != 1) {
LOGGER.error("Found more than 1 class that has this method");
}
methodsByClass.computeIfAbsent(methodClassEntity.get(0), anyKey -> new HashSet<>()).add(methodEntity);
});
}

/**
* Methods decides whether to move method or not, based on calculating distances between given method and classes.
*
* @param entity method to check for move method refactoring.
* @param accumulator list of refactorings, if method must be moved, refactoring for it will be added to this accumulator.
* @return changed or unchanged accumulator.
*/
private List<Refactoring> findRefactoring(MethodEntity entity, List<Refactoring> accumulator) {
reportProgress((double) progressCount.incrementAndGet() / units.size(), context);
context.checkCanceled();
if (!entity.isMovable() || classEntities.size() < 2) {
return accumulator;
}
double minDistance = Double.POSITIVE_INFINITY;
double difference = Double.POSITIVE_INFINITY;
double distanceWithSourceClass = 1;
ClassEntity targetClass = null;
ClassEntity sourceClass = null;
for (final ClassEntity classEntity : classEntities) {
final double distance = getDistance(entity, classEntity);
if (classEntity.getName().equals(entity.getClassName())) {
sourceClass = classEntity;
distanceWithSourceClass = distance;
}
if (distance < minDistance) {
difference = minDistance - distance;
minDistance = distance;
targetClass = classEntity;
} else if (distance - minDistance < difference) {
difference = distance - minDistance;
}
}

if (targetClass == null) {
LOGGER.warn("targetClass is null for " + entity.getName());
return accumulator;
}
final String targetClassName = targetClass.getName();
double differenceWithSourceClass = distanceWithSourceClass - minDistance;
int numberOfMethodsInSourceClass = methodsByClass.get(sourceClass).size();
int numberOfMethodsInTargetClass = methodsByClass.get(targetClass).size();
// considers amount of entities.
double sourceClassCoefficient = 1 - 1.0 / (2 * numberOfMethodsInSourceClass * numberOfMethodsInSourceClass);
double targetClassCoefficient = 1 - 1.0 / (4 * numberOfMethodsInTargetClass * numberOfMethodsInTargetClass);
double differenceWithSourceClassCoefficient = (1 - minDistance) * differenceWithSourceClass;
double accuracy = (0.7 * differenceWithSourceClassCoefficient + 0.3 * difference) *
sourceClassCoefficient * targetClassCoefficient;
// accuracy = 1;
if (accuracy >= 0.01 && !targetClassName.equals(entity.getClassName())) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

accuracy >= 0.01
is it just a comparison with epsilon?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to final static constant.

accumulator.add(new Refactoring(entity.getName(), targetClassName, accuracy, entity.isField()));
}
return accumulator;
}

/**
* Measures distance (a number in [0; 1]) between method and a class.
* It is an average of distances between method and class methods.
* If there is no methods in a given class then distance is 1.
* @param methodEntity method to calculate distance.
* @param classEntity class to calculate distance.
* @return distance between the method and the class.
*/
private double getDistance(MethodEntity methodEntity, ClassEntity classEntity) {
int number = 0;
double sumOfDistances = 0;

if (methodsByClass.containsKey(classEntity)) {
for (MethodEntity methodEntityInClass : methodsByClass.get(classEntity)) {
if (!methodEntity.equals(methodEntityInClass)) {
sumOfDistances += getDistance(methodEntity, methodEntityInClass);
number++;
}
}
}

return number == 0 ? 1 : sumOfDistances / number;
}

/**
* Measures distance (a number in [0; 1]) between two methods.
* It is sizeOfIntersection(A1, A2) / sizeOfUnion(A1, A2), where Ai is a conceptual set of method.
* If A1 and A2 are empty then distance is 1.
* @param methodEntity1 method to calculate distance.
* @param methodEntity2 method to calculate distance.
* @return distance between two given methods.
*/
private double getDistance(MethodEntity methodEntity1, MethodEntity methodEntity2) {
// TODO: Maybe add to methodEntity2 source class where it is located?
Set<String> method1Classes = methodEntity1.getRelevantProperties().getClasses();
Set<String> method2Classes = methodEntity2.getRelevantProperties().getClasses();
int sizeOfIntersection = intersection(method1Classes, method2Classes).size();
int sizeOfUnion = union(method1Classes, method2Classes).size();
return (sizeOfUnion == 0) ? 1 : 1 - (double) sizeOfIntersection / sizeOfUnion;
}

private <T> Set<T> intersection(Set<T> set1, Set<T> set2) {
Set<T> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
return intersection;
}

private <T> Set<T> union(Set<T> set1, Set<T> set2) {
Set<T> union = new HashSet<>(set1);
union.addAll(set2);
return union;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private int getWeightedSize(Map<?, Integer> m) {
return m.values().stream().mapToInt(Integer::valueOf).sum();
}

int sizeOfIntersection(RelevantProperties properties) {
public int sizeOfIntersection(RelevantProperties properties) {
int result = 0;

final BinaryOperator<Integer> bop = Math::min;
Expand Down
Loading