package it.unisa.sesa.repominer.metrics;
import it.unisa.sesa.repominer.db.ChangeDAO;
import it.unisa.sesa.repominer.db.ChangeForCommitDAO;
import it.unisa.sesa.repominer.db.SourceContainerDAO;
import it.unisa.sesa.repominer.db.TypeDAO;
import it.unisa.sesa.repominer.db.entities.Change;
import it.unisa.sesa.repominer.db.entities.ChangeForCommit;
import it.unisa.sesa.repominer.db.entities.Metric;
import it.unisa.sesa.repominer.db.entities.Project;
import it.unisa.sesa.repominer.db.entities.ProjectMetric;
import it.unisa.sesa.repominer.db.entities.SourceContainer;
import it.unisa.sesa.repominer.db.entities.Type;
import it.unisa.sesa.repominer.dbscan.ChangePoint;
import it.unisa.sesa.repominer.dbscan.Cluster;
import it.unisa.sesa.repominer.dbscan.DBSCAN;
import it.unisa.sesa.repominer.metrics.exception.NoChangesException;
import it.unisa.sesa.repominer.preferences.PreferenceConstants;
import it.unisa.sesa.repominer.util.Utils;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This class is responsible to calculate all the history metrics relative to a
* project.
*
* @author RepominerEvo Team
*
*/
public class ProjectMetrics {
private ChangeDAO changeDAO = new ChangeDAO();
private ChangeForCommitDAO changeForCommitDAO = new ChangeForCommitDAO();
private TypeDAO typeDAO = new TypeDAO();
private SourceContainerDAO sourceContainerDAO = new SourceContainerDAO();
/**
* Calculate the NR metric, that represents the system number of revision
*
* @param pProject
* project analyzed
* @return the NR metric value
*/
public ProjectMetric getNumberOfRevision(Project pProject) {
List<Change> changes = this.changeDAO.getChangesOfProject(pProject);
ProjectMetric nr = new ProjectMetric();
nr.setDescription(Metric.NUM_REVISION_DESCRIPTION);
nr.setName(Metric.NUM_REVISION_NAME);
nr.setValue(new Double(changes.size()));
nr.setProjectId(pProject.getId());
nr.setEnd(this.changeDAO.getProjectEndDate(pProject));
nr.setStart(this.changeDAO.getProjectStartDate(pProject));
return nr;
}
/**
* This method break history into burst based periods and then calculates
* the value of ECC Model on this periods
*
* @param pProject
* project analyzed
* @param pEps
* eps parameter for dbscan analysis
* @param pMinPoints
* minPoint parameter for dbscan analysis
* @param pIsStatic
* if true calculates ECCM with Normalized Static Entropy; if
* false calculates ECCM with Adaptive Sizing Entropy
* @return a list of ECC Model values
*/
public List<ProjectMetric> getECCBurstBased(Project pProject, int pEps,
int pMinPoints, boolean pIsStatic) throws NoChangesException {
List<Change> projectChanges = this.changeDAO
.getChangesOfProject(pProject);
if (projectChanges.isEmpty()) {
throw new NoChangesException();
}
Calendar startDate = Utils.dateToCalendar(projectChanges.get(0)
.getCommitDate());
List<ChangePoint> changePoints = new ArrayList<>();
for (Change change : projectChanges) {
int xCoordinate = Utils.daysBetween(startDate,
Utils.dateToCalendar(change.getCommitDate()));
changePoints.add(new ChangePoint(xCoordinate, change));
}
List<ProjectMetric> listECC = new ArrayList<>();
DBSCAN clusterizator = new DBSCAN(pEps, pMinPoints);
List<Cluster> clusters = clusterizator.cluster(changePoints);
for (Cluster cluster : clusters) {
try {
List<ChangePoint> clusterPoints = cluster.getPoints();
Date clusterStartDate = clusterPoints.get(0).getChange()
.getCommitDate();
Date clusterEndDate = clusterPoints
.get(clusterPoints.size() - 1).getChange()
.getCommitDate();
double eccValue = this.calculateECCMValue(pProject,
clusterStartDate, clusterEndDate, pIsStatic);
ProjectMetric currentEccm = new ProjectMetric();
currentEccm.setValue(new Double(eccValue));
currentEccm.setStart(clusterStartDate);
currentEccm.setEnd(clusterEndDate);
if (pIsStatic) {
currentEccm.setDescription(Metric.ECCM_STATIC_DESCRIPTION);
currentEccm.setName(Metric.ECCM_STATIC_NAME);
} else {
currentEccm.setDescription(Metric.ECCM_DESCRIPTION);
currentEccm.setName(Metric.ECCM_NAME);
}
currentEccm.setProjectId(pProject.getId());
listECC.add(currentEccm);
} catch (NoChangesException ex) {
System.err.println(ex.getMessage());
}
}
return listECC;
}
/**
* This method calculate the value of BCC Metric; it calculates this value
* only considering changes occurred in time based period between the start
* and the end date specified in preference panel
*
* @param pProject
* project analyzed
* @param pPeriodStart
* start date for analysis
* @param pPeriodEnd
* end date for analysis
* @return BCC Metric
*/
public ProjectMetric getBCCMMetric(Project pProject, Date pPeriodStart,
Date pPeriodEnd) throws NoChangesException {
double bccValue = this.calculateBCCMMetricValue(pProject, pPeriodStart,
pPeriodEnd);
ProjectMetric bccmMetric = new ProjectMetric();
bccmMetric.setValue(new Double(bccValue));
bccmMetric.setStart(pPeriodStart);
bccmMetric.setEnd(pPeriodEnd);
bccmMetric.setDescription(Metric.BCCM_DESCRIPTION);
bccmMetric.setName(Metric.BCCM_NAME);
bccmMetric.setProjectId(pProject.getId());
return bccmMetric;
}
/**
* This method calculate the BCC value for project passed as parameter
* considering only changes occurred between two Date always passed as
* parameters
*
* @param pProject
* project analyzed
* @param pPeriodStart
* start date for analysis
* @param pPeriodEnd
* end date for analysis
* @return value for BCC Metric of this period
*/
private double calculateBCCMMetricValue(Project pProject,
Date pPeriodStart, Date pPeriodEnd) throws NoChangesException {
List<Type> modifiedClassForProject = this
.getModifiedClassForProject(pProject);
Map<String, Integer> occurrenceTable = new HashMap<>();
// We use getChangesByDateInterval getting all changes occurred in
// selected period
List<Change> changes = this.changeDAO.getChangesByDateInterval(
pProject, pPeriodStart, pPeriodEnd);
if (changes.isEmpty()) {
throw new NoChangesException();
}
int FIcounter = 0;
for (Type modifiedFile : modifiedClassForProject) {
int aux = 0; // counter for occurrence table
for (Change change : changes) {
String changeMsg = change.getMessage();
if (Utils.msgIsBugFixing(changeMsg)
|| Utils.msgIsRefactoring(changeMsg)) {
// skip this change
continue;
}
// this is likely to be a FEATURE INTRODUCTION change at this
// point
List<ChangeForCommit> changesForCommit = this.changeForCommitDAO
.getChangeForCommitOfChange(change);
for (ChangeForCommit changeForCommit : changesForCommit) {
if (changeForCommit.getModifiedFile().equals(
modifiedFile.getSrcFileLocation())) {
aux += 1;
occurrenceTable.put(modifiedFile.getSrcFileLocation(),
aux);
FIcounter += 1;
}
}
}
}
if (occurrenceTable.size() == 0 || FIcounter == 0) {
return .0;
}
double[] probabilty = new double[occurrenceTable.size()];
int index = 0;
// Iterating over occurrenceTable values
for (Integer occurenceValue : occurrenceTable.values()) {
probabilty[index] = occurenceValue / (double) FIcounter;
index++;
}
double bccmMetric = 0;
for (int i = 0; i < probabilty.length; i++) {
if (probabilty[i] != 0) {
bccmMetric += probabilty[i] * Utils.log2(probabilty[i]);
}
}
if (bccmMetric == 0) {
return 0;
}
bccmMetric = bccmMetric * -1;
return bccmMetric;
}
/**
* This method calculate a set of values of BCC Metric; it calculates this
* values broken the history of changes into equal length periods based on
* calendar time from the start of the project. The length of a single
* interval time is specified in preference panel
*
* @param pProject
* project analyzed
* @param periodLength
* length of period for analysis
* @param periodType
* weeks, months or year for period of analysis
* @return some BCC Model values time period based
*/
public List<ProjectMetric> getBCCPeriodBased(Project pProject,
int periodLength, String periodType) {
List<ProjectMetric> listBCC = new ArrayList<>();
int gregorianInterval = 0;
if (periodType.equals(PreferenceConstants.PERIOD_TYPE_WEEK)) {
gregorianInterval = GregorianCalendar.WEEK_OF_YEAR;
} else if (periodType.equals(PreferenceConstants.PERIOD_TYPE_YEAR)) {
gregorianInterval = GregorianCalendar.YEAR;
} else if (periodType.equals(PreferenceConstants.PERIOD_TYPE_MONTH)) {
gregorianInterval = GregorianCalendar.MONTH;
}
Calendar startDate = Utils.dateToCalendar(this.changeDAO
.getProjectStartDate(pProject));
Date endDate = this.changeDAO.getProjectEndDate(pProject);
startDate.roll(GregorianCalendar.DAY_OF_MONTH, -1);
while (startDate.getTime().before(endDate)) {
try {
startDate.add(GregorianCalendar.DAY_OF_MONTH, 1);
Date auxStart = startDate.getTime();
startDate.add(gregorianInterval, periodLength);
Date auxEnd = startDate.getTime();
if (startDate.getTime().after(endDate)) {
auxEnd = endDate;
}
double bccValue = this.calculateBCCMMetricValue(pProject,
auxStart, auxEnd);
ProjectMetric currentBccm = new ProjectMetric();
currentBccm.setValue(new Double(bccValue));
currentBccm.setStart(auxStart);
currentBccm.setEnd(auxEnd);
currentBccm.setDescription(Metric.BCCM_DESCRIPTION);
currentBccm.setName(Metric.BCCM_NAME);
currentBccm.setProjectId(pProject.getId());
listBCC.add(currentBccm);
} catch (NoChangesException ex) {
System.err.println(ex.getMessage());
}
}
return listBCC;
}
/**
* This method get a list of types that have been changed in a project
*
* @param pProject
* project analyzed
* @return all types changed
*/
private List<Type> getModifiedClassForProject(Project pProject) {
List<Type> modifiedClassesForProject = new ArrayList<>();
List<Type> allClassesForProject = new ArrayList<>();
List<SourceContainer> packages = this.sourceContainerDAO
.getPackages(pProject);
for (SourceContainer sourceContainer : packages) {
List<Type> classes = this.typeDAO
.getClassesByPackage(sourceContainer);
allClassesForProject.addAll(classes);
}
// now in modifiedClassForProject we have all the classes of a project:
// we need to delete the ones not changed during history
List<Change> changes = this.changeDAO.getChangesOfProject(pProject);
for (Change change : changes) {
// We take and iterate all changes_for_commit of this package
List<ChangeForCommit> changesForCommit = this.changeForCommitDAO
.getChangeForCommitOfChange(change);
for (ChangeForCommit changeForCommit : changesForCommit) {
for (Type type : allClassesForProject) {
if (type.getSrcFileLocation().equals(
changeForCommit.getModifiedFile())) {
if (!modifiedClassesForProject.contains(type)) {
modifiedClassesForProject.add(type);
}
}
}
}
}
return modifiedClassesForProject;
}
/**
* This method calculate a set of values of ECC Model using time based
* periods method; it calculates this values broken the history of changes
* into equal length periods based on calendar time from the start of the
* project. The length of a single interval time is specified in preference
* panel
*
* @param pProject
* project analyzed
* @param pPeriod
* length of period for analysis
* @param periodType
* weeks, months or years for period of analysis
* @param pIsStatic
* if true calculates ECCM with Normalized Static Entropy; if
* false calculates ECCM with Adaptive Sizing Entropy
* @return some ECC Model values calculated with time based periods method
*/
public List<ProjectMetric> getECCPeriodBased(Project pProject, int pPeriod,
String periodType, Boolean pIsStatic) {
int gregorianInterval = 0;
if (periodType.equals("WEEK")) {
gregorianInterval = GregorianCalendar.WEEK_OF_YEAR;
} else if (periodType.equals("YEAR")) {
gregorianInterval = GregorianCalendar.YEAR;
} else if (periodType.equals("MONTH")) {
gregorianInterval = GregorianCalendar.MONTH;
}
Calendar startDate = Utils.dateToCalendar(this.changeDAO
.getProjectStartDate(pProject));
Date endDate = this.changeDAO.getProjectEndDate(pProject);
List<ProjectMetric> listECC = new ArrayList<>();
startDate.roll(GregorianCalendar.DAY_OF_MONTH, -1);
while (startDate.getTime().before(endDate)) {
try {
startDate.add(GregorianCalendar.DAY_OF_MONTH, 1);
Date auxStart = startDate.getTime();
startDate.add(gregorianInterval, pPeriod);
Date auxEnd = startDate.getTime();
double eccValue = this.calculateECCMValue(pProject, auxStart,
auxEnd, pIsStatic);
ProjectMetric currentEccm = new ProjectMetric();
currentEccm.setValue(new Double(eccValue));
currentEccm.setStart(auxStart);
currentEccm.setEnd(auxEnd);
if (pIsStatic) {
currentEccm.setDescription(Metric.ECCM_STATIC_DESCRIPTION);
currentEccm.setName(Metric.ECCM_STATIC_NAME);
} else {
currentEccm.setDescription(Metric.ECCM_DESCRIPTION);
currentEccm.setName(Metric.ECCM_NAME);
}
currentEccm.setProjectId(pProject.getId());
listECC.add(currentEccm);
} catch (NoChangesException ex) {
System.err.println(ex.getMessage());
}
}
return listECC;
}
/**
* This method calculate the ECC value for project passed as parameter
* considering only changes occurred between two Dates always passed as
* parameters
*
* @param pProject
* project analyzed
* @param pStart
* start date for analysis
* @param pEnd
* end date for analysis
* @param pIsStatic
* if true calculated with Normalized Static Entropy; if false
* calculated with our Adaptive Sizing Entropy
* @return the double value for ECC Metric of this period
*/
private double calculateECCMValue(Project pProject, Date pStart, Date pEnd,
Boolean pIsStatic) throws NoChangesException {
List<Type> modifiedClassForProject = this
.getModifiedClassForProject(pProject);
Map<String, Integer> occurrenceTable = new HashMap<>();
// We use getChangesByDateInterval getting all changes occurred in
// selected period
List<Change> changes = this.changeDAO.getChangesByDateInterval(
pProject, pStart, pEnd);
if (changes.isEmpty()) {
throw new NoChangesException();
}
double FIcounter = 0; // Total of FI changes occurred int this period
for (Type modifiedFile : modifiedClassForProject) {
int aux = 0; // counter for occurrence table
for (Change change : changes) {
String changeMsg = change.getMessage();
if (Utils.msgIsBugFixing(changeMsg)
|| Utils.msgIsRefactoring(changeMsg)) {
// skip this change
continue;
}
// this is likely to be a FEATURE INTRODUCTION change at this
// point
List<ChangeForCommit> changesForCommit = this.changeForCommitDAO
.getChangeForCommitOfChange(change);
for (ChangeForCommit changeForCommit : changesForCommit) {
if (changeForCommit.getModifiedFile().equals(
modifiedFile.getSrcFileLocation())) {
aux += 1;
occurrenceTable.put(modifiedFile.getSrcFileLocation(),
aux);
FIcounter += 1;
}
}
}
}
if (occurrenceTable.size() == 0 || FIcounter == 0) {
return 0.0;
}
double[] probabilty = new double[occurrenceTable.size()];
int index = 0;
// Iterating over occurrenceTable values
for (Integer occurenceValue : occurrenceTable.values()) {
probabilty[index] = occurenceValue / FIcounter;
index++;
}
double ECCMetric = 0;
for (int i = 0; i < probabilty.length; i++) {
ECCMetric += probabilty[i] * Utils.log2(probabilty[i]);
}
if (ECCMetric == 0) {
return 0;
}
if (pIsStatic) {
ECCMetric = ECCMetric
* (1 / Utils.log2(new Double(this.typeDAO
.getSystemNumberOfTypes(pProject))) * -1);
} else {
ECCMetric = ECCMetric * (1 / Utils.log2(probabilty.length) * -1);
}
return ECCMetric;
}
/**
* This method calculate the ECC value for project to analyze considering
* only changes passed as parameters
*
* @param pProject
* project analyzed
* @param changes
* changes for ECC Model analysis
* @param pIsStatic
* if true calculated with Normalized Static Entropy; if false
* calculated with our Adaptive Sizing Entropy
* @return the double value for ECC Metric of this period
*/
private double calculateECCMValue(Project pProject, List<Change> changes,
boolean pIsStatic) throws NoChangesException {
List<Type> modifiedClassForProject = this
.getModifiedClassForProject(pProject);
Map<String, Integer> occurrenceTable = new HashMap<>();
if (changes.isEmpty()) {
throw new NoChangesException();
}
double FIcounter = 0; // Total of FI changes occurred int this period
for (Type modifiedFile : modifiedClassForProject) {
int aux = 0; // counter for occurrence table
for (Change change : changes) {
String changeMsg = change.getMessage();
if (Utils.msgIsBugFixing(changeMsg)
|| Utils.msgIsRefactoring(changeMsg)) {
// skip this change
continue;
}
// this is likely to be a FEATURE INTRODUCTION change at this
// point
List<ChangeForCommit> changesForCommit = this.changeForCommitDAO
.getChangeForCommitOfChange(change);
for (ChangeForCommit changeForCommit : changesForCommit) {
if (changeForCommit.getModifiedFile().equals(
modifiedFile.getSrcFileLocation())) {
aux += 1;
occurrenceTable.put(modifiedFile.getSrcFileLocation(),
aux);
FIcounter += 1;
}
}
}
}
if (occurrenceTable.size() == 0 || FIcounter == 0) {
return 0.0;
}
double[] probabilty = new double[occurrenceTable.size()];
int index = 0;
// Iterating over occurrenceTable values
for (Integer occurenceValue : occurrenceTable.values()) {
probabilty[index] = occurenceValue / FIcounter;
index++;
}
double eccmValue = 0;
for (int i = 0; i < probabilty.length; i++) {
eccmValue += probabilty[i] * Utils.log2(probabilty[i]);
}
if (eccmValue == 0) {
return 0;
}
if (pIsStatic) {
eccmValue = eccmValue
* (1 / Utils.log2(new Double(this.typeDAO
.getSystemNumberOfTypes(pProject))) * -1);
} else {
eccmValue = eccmValue * (1 / Utils.log2(probabilty.length) * -1);
}
return eccmValue;
}
/**
* This method calculate a set of values of ECC Model using modification
* limit period based; it calculates this values broken the history of
* changes into period based on number of modifications specified in
* preferences panel. To prevent a period where little development may have
* occurred from spanning a long time, we impose a limit of 3 months on a
* period even if the modification limit was no reached
*
* @param pProject
* project analyzed
* @param pLimit
* limit of modifications for breaking periods
* @param pIsStatic
* if true calculated with Normalized Static Entropy; if false
* calculated with our Adaptive Sizing Entropy
* @return some ECC Model value calculated with modification limit period
* method
*/
public List<ProjectMetric> getECCModificationBased(Project pProject,
int pLimit, boolean pIsStatic) throws NoChangesException {
List<ProjectMetric> eccmMetrics = new ArrayList<>();
List<Change> projectChanges = this.changeDAO
.getChangesOfProject(pProject);
List<List<Change>> periods = new ArrayList<>();
List<Change> buffer = new ArrayList<>();
Date periodStart = null;
if (!projectChanges.isEmpty()) {
periodStart = projectChanges.get(0).getCommitDate();
} else {
throw new NoChangesException();
}
for (int i = 0; i < projectChanges.size(); i++) {
Change currentChange = projectChanges.get(i);
buffer.add(currentChange);
Change nextChange = null;
if (i + 1 < projectChanges.size()) {
// current change IS NOT last change
nextChange = projectChanges.get(i + 1);
Calendar next3months = Utils.dateToCalendar(periodStart);
next3months.add(Calendar.MONTH, 3);
Date monthsLimit = next3months.getTime();
if (nextChange.getCommitDate().after(monthsLimit)
|| buffer.size() == pLimit) {
periods.add(new ArrayList<Change>(buffer));
buffer.clear();
periodStart = nextChange.getCommitDate();
}
} else {
// current change is last change
periods.add(new ArrayList<Change>(buffer));
buffer.clear();
}
}
for (List<Change> changes : periods) {
double eccmValue = this.calculateECCMValue(pProject, changes,
pIsStatic);
ProjectMetric projectMetric = new ProjectMetric();
if (pIsStatic) {
projectMetric.setDescription(Metric.ECCM_STATIC_DESCRIPTION);
projectMetric.setName(Metric.ECCM_STATIC_NAME);
} else {
projectMetric.setDescription(Metric.ECCM_DESCRIPTION);
projectMetric.setName(Metric.ECCM_NAME);
}
projectMetric.setValue(eccmValue);
projectMetric.setProjectId(pProject.getId());
projectMetric.setStart(changes.get(0).getCommitDate());
projectMetric.setEnd(changes.get(changes.size() - 1)
.getCommitDate());
eccmMetrics.add(projectMetric);
}
return eccmMetrics;
}
}