Recommender.java
package edu.odu.cs.cs350;
import edu.odu.cs.cs350.Interfaces.*;
import java.util.*;
import java.math.BigDecimal;
/**
* Taking as input a list of tokens which represents a string of
* tokens taken from all files that the Input processed. It will
* process these tokens, and produce a list of recommended
* refactorings, ordered from greatest to least opportunity value.
* Additionally, after finding a refactoring recommendation, but
* before adding the recommendation to the list of recommendations,
* it will compute the opportunity value of that recommendation.
* @see RecommenderInterface
* @see TestRecomender
*/
public class Recommender implements RecommenderInterface {
private List<TokenInterface> tokens;
private List<RefactoringInterface> refactorings;
private int minRefactoringSize;
private int maxRefactoringSize;
/**
* Default constructor.
* tokens and refactorings are set to empty lists;
* minRefactoringSize and maxRefactoringSize are set to 0.
*/
Recommender() {
this.setTokens(new ArrayList<TokenInterface>());
this.setRefactorings(new ArrayList<RefactoringInterface>());
this.setMinRefactoringSize(0);
this.setMaxRefactoringSize(0);
}
/**
* Parameter constructor. with a list of tokens provided.
* tokens is set to inputTokens, recommend runs to set
* the Refactorings list,
* @param inputTokens a list of tokens produced from lexical
* analysis of all the files provided as input.
*/
Recommender(List<? extends TokenInterface> inputTokens) {
this.setTokens(inputTokens);
this.refactorings = new ArrayList<RefactoringInterface>();
this.setMinRefactoringSize();
this.setMaxRefactoringSize();
}
Recommender(List<? extends TokenInterface> inputTokens, int min) {
this.setTokens(inputTokens);
this.refactorings = new ArrayList<RefactoringInterface>();
this.setMinRefactoringSize(min);
this.setMaxRefactoringSize();
this.recommend();
}
/**
* Parameter constructor, with tokens and min and max size of refactorings parameters.
* @param inputTokensa list of tokens produced from lexical
* analysis of all the files provided as input.
* @param min the least number of tokens that a refactoring candidate can have.
* @param max the highest number of tokens that a refactoring candidate can have.
*/
Recommender(List<? extends TokenInterface> inputTokens, int min, int max) {
this.setTokens(inputTokens);
this.setMinRefactoringSize(min);
this.setMaxRefactoringSize(max);
this.recommend();
}
/**
* @return the list of tokens from which the recommendations
* are generated.
*/
@Override
public List<? extends TokenInterface> getTokens() {
return this.tokens;
}
/**
* Sets the list of tokens from which recommendations are
* generated to the input list parameter.
* @param input a list of token objects.
*/
@Override
public void setTokens(List<? extends TokenInterface> input) {
if (input == null || input.size() == 0) {
tokens = new ArrayList<TokenInterface>();
return;
}
tokens = new ArrayList<TokenInterface>();
for (TokenInterface t : input) tokens.add(t);
}
/**
* If no refactorings have been created, create them.
* @return a complete list of all refactoring recommendations.
*/
@Override
public List<? extends RefactoringInterface> getRefactorings() {
if (this.refactorings.size() != 0) return this.refactorings;
if (this.tokens == null || this.tokens.size() == 0) return new ArrayList<RefactoringInterface>();
if (this.refactorings == null || this.refactorings.isEmpty()) this.recommend();
return this.refactorings;
}
/**
* Sets recommender's list of refactorings to a pre-existing
* refactoring list.
* @param input a list of Refactoring recommendations.
*/
@Override
public void setRefactorings(List<? extends RefactoringInterface> input) {
if (input.isEmpty() || input == null) {
refactorings = new ArrayList<RefactoringInterface>();
}
else {
refactorings = new ArrayList<RefactoringInterface>();
for (RefactoringInterface r : input) refactorings.add(r);
}
}
/**
* Recommended refactorings should be greater than
* minRefactoringSize but less than maxRefactoringSize,
* inclusive.
* @return the minimum number of tokens that can make up a
* refactoring.
*/
@Override
public int getMinRefactoringSize() {
return this.minRefactoringSize;
}
/**
* Recommended refactorings should be greater than
* minRefactoringSize but less than maxRefactoringSize,
* inclusive.
* @param input the minimum number of tokens that can make up a
* refactoring.
*/
@Override
public void setMinRefactoringSize(int input) {
this.minRefactoringSize = input;
}
/**
* Recommended refactorings should be greater than
* minRefactoringSize but less than maxRefactoringSize,
* inclusive. If no input was provided, sets the min to
* 0 if Recommender's list of tokens is empty, or to 3%
* of the size of the token list.
*/
@Override
public void setMinRefactoringSize() {
if (this.getTokens().isEmpty() || this.getTokens() == null) {
this.minRefactoringSize = 0;
return;
}
else {
BigDecimal size = new BigDecimal(this.getTokens().size());
BigDecimal result = size.multiply(new BigDecimal("0.03"));
this.minRefactoringSize = result.intValue();
return;
}
}
/**
* Recommended refactorings should be greater than
* minRefactoringSize but less than maxRefactoringSize,
* inclusive.
* @return the maximum number of tokens that can make up a
* refactoring.
*/
@Override
public int getMaxRefactoringSize() {
return this.maxRefactoringSize;
}
/**
* Recommended refactorings should be greater than
* minRefactoringSize but less than maxRefactoringSize,
* inclusive.
* @param input the maximum number of tokens that can make up a
* refactoring.
*/
@Override
public void setMaxRefactoringSize(int input) {
this.maxRefactoringSize = input;
}
/**
* Recommended refactorings should be greater than
* minRefactoringSize but less than maxRefactoringSize,
* inclusive. If no input was provided, sets the max to
* 0 if Recommender's list of tokens is empty, or to 75%
* of the size of the token list.
*/
@Override
public void setMaxRefactoringSize() {
if (this.getTokens().isEmpty() || this.getTokens() == null) {
this.maxRefactoringSize = 0;
return;
}
else {
BigDecimal size = new BigDecimal(this.getTokens().size());
BigDecimal result = size.multiply(new BigDecimal("0.70"));
this.maxRefactoringSize = result.intValue();
return;
}
}
/**
* Computes the opportunity value for refactoring for candidate,
* based on its size and number of reoccurrences in the list of
* tokens that was given to Recommender.
* @param candidate a list of tokens that is a candidate for
* refactorization.
* @param amountOfDuplicates the number of times the candidate
* reoccurs in tokens.
* @param
* @return an integer between 0 and 100, inclusive, representing
* the amount of value a refactoring candidate has.
*/
private int computeOpportunityValue(List<TokenInterface> candidate, int amountOfDuplicates, int amountOfSublists) {
/**
* Formula for calculating opportunity value:
* A = candidate.size() / tokens.size()
* B = amountOfDuplicates / sublists.size()
* value = ( A + B ) * 100
*/
/*BigDecimal A = new BigDecimal(candidate.size());
A.divide(new BigDecimal(tokens.size()));
BigDecimal B = new BigDecimal(amountOfDuplicates);
B.divide(new BigDecimal(amountOfSublists));
BigDecimal result = A.add(B);
result.multiply(new BigDecimal(100));*/
//return result.intValue();
return 5;
}
/**
* Using the list of tokens, finds all suggested refactorings of
* those tokens, calculates the opportunity value for each
* candidate, and then produces a list of suggested
* refactorings.
*/
private void recommend() {
this.refactorings = new ArrayList<RefactoringInterface>();
if (this.tokens == null || this.tokens.size() <= 1 || this.getMaxRefactoringSize() <= 3) return;
// sublists stores each of the created candidate list of tokens.
List<List<TokenInterface>> sublists = new ArrayList<List<TokenInterface>>();
// Create lists with varying sizes between minRefactoringSize up to and including maxRefactoringSize.
for (int i = this.getMinRefactoringSize(); i <= this.getMaxRefactoringSize(); i++) {
for (int j = 0; j < this.getTokens().size(); j++) {
try {
List<TokenInterface> l = tokens.subList(j, ( j + i));
// Only add full statements, must end in a ';' or '}'
if (l.get(l.size() - 1).getTokenType() == TokenType.SEMI_COLON || l.get(l.size() - 1).getTokenType() == TokenType.RIGHT_BRACE) {
sublists.add(l);
}
} catch (IndexOutOfBoundsException e) {
continue;
}
}
}
if (sublists == null || sublists.size() == 0) return;
// Determine whether each sublist is a recommended refactoring.
for (var list : sublists) {
if (list.size() <= 2) continue;
int occurrences = countOccurrences(list, sublists);
if (occurrences > 1) {
int opportunityValue = computeOpportunityValue(list, occurrences - 1, sublists.size());
Refactoring r = new Refactoring(list, opportunityValue);
this.refactorings.add(r);
}
}
}
/**
* Counts the number of times candidate reoccurs in sublists.
* @param candidate the list of tokens representing a candidate for refactorization.
* @param sublists the list of all sublists of tokens.
* @return the number of times candidate reoccurs in sublists (counting itself).
*/
private int countOccurrences(List<TokenInterface> candidate, List<List<TokenInterface>> sublists) {
int result = 0;
for (List<TokenInterface> list : sublists) {
boolean equalTo = true;
if (list.size() == candidate.size()) {
for (int i = 0; i < candidate.size(); i++) {
if (candidate.get(i).getTokenType() != list.get(i).getTokenType()) equalTo = false;
}
}
else {
equalTo = false;
}
if (equalTo == true) result++;
}
return result;
}
}