/*
* This file is part of ReqTracker.
*
* Copyright (C) 2015 Taleh Didover, Florian Gerdes, Dmitry Gorelenkov,
* Rajab Hassan Kaoneka, Katsiaryna Krauchanka, Tobias Polzer,
* Gayathery Sathya, Lukas Tajak
*
* ReqTracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ReqTracker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with ReqTracker. If not, see <http://www.gnu.org/licenses/>.
*/
package de.fau.osr.gui.Model;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Pattern;
import de.fau.osr.bl.RequirementsTraceabilityMatrix;
import de.fau.osr.bl.RequirementsTraceabilityMatrixByImpact;
import de.fau.osr.gui.Model.DataElements.AnnotatedLine;
import de.fau.osr.gui.Model.DataElements.Commit;
import de.fau.osr.gui.Model.DataElements.CommitFile;
import de.fau.osr.gui.Model.DataElements.Requirement;
import de.fau.osr.gui.util.GenericLock;
import de.fau.osr.gui.Model.DataElements.*;
/**
* @author Gayathery
* This class 'TrackerAdapterWorker' works as an indexer for the application.
* This class collects data in the background and when it is ready it masks the Tracker.
* When there is a change in the underlying data (example: new commit) then it automatically stops masking until it
* refreshes its data repository. Time consuming methods can be masked using this class
*/
public class TrackerAdapterWorker {
private TrackerAdapter trackerAdapter;
Boolean isReadyForTakeOver;
private Pattern reqPatternString;
public Collection<Requirement> requirements;
private Collection<PathDE> totalCommitFiles;
private HashMap<Requirement,Collection<Commit>> workerRepositoryReqCommit;
private HashMap<Commit, Collection<PathDE>> workerRepositoryCommitCommitFile;
private HashMap<Requirement,Collection<PathDE>> workerRepositoryReqCommitFile;
public GenericLock lock;
public static Date globalChangeTime;
private static long CommitCount;
public static Date getAllRequirements, getImpactPercentageForRequirementAndFile,getCommitsFromRequirement,getAllFiles,getRequirementsFromFile,getCommitFilesForRequirement;
public Boolean isThreadingRequired;
public TrackerAdapterWorker(TrackerAdapter trackerAdapter) throws IOException {
this.trackerAdapter = trackerAdapter;
isReadyForTakeOver = false;
Calendar calc = Calendar.getInstance();
globalChangeTime = calc.getTime();
workerRepositoryReqCommit = new HashMap<Requirement, Collection<Commit>>();
workerRepositoryCommitCommitFile = new HashMap<Commit, Collection<PathDE>>();
workerRepositoryReqCommitFile = new HashMap<Requirement, Collection<PathDE>>();
totalCommitFiles = new ArrayList<PathDE>();
isThreadingRequired = false;
lock = new GenericLock();
}
public void setActualTrackerAdapter(TrackerAdapter trackerAdapter){
this.trackerAdapter = trackerAdapter;
}
public void setThreading(Boolean isRequired){
this.isThreadingRequired = isRequired;
}
public Date trackerLastChangeTime(){
return globalChangeTime;
}
class Collector{
public Collection<PathDE> totalCommitFiles;
public HashMap<Requirement,Collection<Commit>> workerRepositoryReqCommit;
public HashMap<Commit,Collection<PathDE>> workerRepositoryCommitCommitFile;
public HashMap<Requirement,Collection<PathDE>> workerRepositoryReqCommitFile;
public Collector(){
totalCommitFiles = new ArrayList<PathDE>();
workerRepositoryReqCommit = new HashMap<Requirement, Collection<Commit>>();
workerRepositoryCommitCommitFile = new HashMap<Commit, Collection<PathDE>>();
workerRepositoryReqCommitFile = new HashMap<Requirement, Collection<PathDE>>();
}
}
public void collectFutures(Future<Collector> future){
try {
Collector collected = future.get();
totalCommitFiles.addAll(collected.totalCommitFiles);
workerRepositoryReqCommit.putAll(collected.workerRepositoryReqCommit);
workerRepositoryCommitCommitFile.putAll(collected.workerRepositoryCommitCommitFile);
workerRepositoryReqCommitFile.putAll(collected.workerRepositoryReqCommitFile);
} catch (InterruptedException e) {
e.printStackTrace();
return;
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* @author Gayathery
* This method collects data in a background thread initiated by the actual TrackerAdapter
* It has its own datastructure and data is filled into this structure in background
* This method is called whenever the listener thread finds a change in underlying data
*/
public Boolean prepareData()
{
class TrackerAdapterWorkerCallable implements Callable<Collector>{
Requirement requirement;
public TrackerAdapterWorkerCallable(Requirement requirement){
this.requirement = requirement;
}
@Override
public Collector call() throws Exception {
Collector collectWorker = new Collector();
Collection<Commit> commits = getCommitsFromRequirement(requirement);
collectWorker.workerRepositoryReqCommitFile.put(requirement, getCommitFilesForRequirement(requirement));
collectWorker.workerRepositoryReqCommit.put(requirement,commits);
for(Commit commit : commits){
Collection<PathDE> commitFiles = getFilesFromCommit(commit);
collectWorker.workerRepositoryCommitCommitFile.put(commit,commitFiles);
collectWorker.totalCommitFiles.addAll(commitFiles);
}
return collectWorker;
}
}
if(isThreadingRequired){
ExecutorService executor = Executors.newCachedThreadPool();
requirements = getAllRequirements();
List<Future<Collector>> futureList = new ArrayList<Future<Collector>>();
for(Requirement requirement : requirements){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
return false;
}
Future<Collector> future = executor.submit(new TrackerAdapterWorkerCallable(requirement));
futureList.add(future);
}
for(Future<Collector> futureElement : futureList){
collectFutures(futureElement);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
return false;
}
}
executor.shutdown();
}
else{
requirements = getAllRequirements();
for(Requirement requirement : requirements){
Collection<Commit> commits = getCommitsFromRequirement(requirement);
workerRepositoryReqCommitFile.put(requirement, getCommitFilesForRequirement(requirement));
workerRepositoryReqCommit.put(requirement,commits);
for(Commit commit : commits){
Collection<PathDE> commitFiles = getFilesFromCommit(commit);
workerRepositoryCommitCommitFile.put(commit,commitFiles);
totalCommitFiles.addAll(commitFiles);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
return false;
}
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
return false;
}
}
}
Calendar calc = Calendar.getInstance();
CommitCount = getAllCommits().size();
lock.lock();
globalChangeTime = calc.getTime();
getAllRequirements = globalChangeTime;
getCommitsFromRequirement = globalChangeTime;
getAllFiles = globalChangeTime;
getRequirementsFromFile = globalChangeTime;
getCommitFilesForRequirement = globalChangeTime;
getImpactPercentageForRequirementAndFile = globalChangeTime;
lock.unlock();
trackerAdapter.lock.lock();
isReadyForTakeOver = true;
trackerAdapter.lock.unlock();
return true;
}
/**
* @author Gayathery
* This method starts a listener thread to check the changes in underlying data
* This method automatically polls the underlying data at a predefined interval
* This method changes the globalChangeTime when it finds a change in the underlying data
* and this in turns invalidates masking of methods
*/
public void listen(){
class TrackerAdapterWorkerListener extends Thread {
public void run() {
while(true){
long tempCount = getAllCommits().size();
if(tempCount == CommitCount){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
continue;
}
Calendar calc = Calendar.getInstance();
lock.lock();
globalChangeTime = calc.getTime();
lock.unlock();
if(!prepareData())
break;
}
}
}
Thread trackerAdapterWorkerListenerThread = new Thread(new TrackerAdapterWorkerListener());
trackerAdapterWorkerListenerThread.setName("TrackerAdapterWorkerListenerThread");
trackerAdapterWorkerListenerThread.setPriority(Thread.MIN_PRIORITY);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
return;
}
trackerAdapterWorkerListenerThread.start();
}
/**
* @return Collection<Requirement>
* This method masks the getRequirements method of TrackerAdapter after successful data preparation
* @author Gayathery
*/
public Collection<Requirement> getAllRequirements() {
lock.lock();
try{
if(getAllRequirements == globalChangeTime){
return requirements;
}
}
catch(Exception ex){
ex.printStackTrace();
}
finally{
lock.unlock();
}
return trackerAdapter.getAllRequirements();
}
/**
* @return Collection<Commit>
* @param requirement
* This method masks the getCommitsFromRequirement method of TrackerAdapter after successful data preparation
* @author Gayathery
*/
public Collection<Commit> getCommitsFromRequirement(Requirement requirement) {
lock.lock();
try{
if(getCommitsFromRequirement == globalChangeTime){
Collection<Commit> colCommit = workerRepositoryReqCommit.get(requirement);
return colCommit;
}
}
catch(Exception ex){
ex.printStackTrace();
}
finally{
lock.unlock();
}
return trackerAdapter.getCommitsFromRequirement(requirement);
}
/**
* @return Collection<CommitFile>
* @author Gayathery
* This method masks the getAllFiles method of TrackerAdapter after successful data preparation
*/
public Collection<PathDE> getAllFiles() {
lock.lock();
try{
if(getAllFiles == globalChangeTime){
Collection<PathDE> commitFiles = new ArrayList<PathDE>();
workerRepositoryCommitCommitFile.forEach((k,v) -> commitFiles.addAll(v));
return commitFiles;
}
}
catch(Exception ex){
ex.printStackTrace();
}
finally{
lock.unlock();
}
return trackerAdapter.getFiles();
}
/**
* @param file
* @return
* @author Gayathery
* Will be masked in future if performance improvement is required
*/
public Collection<Requirement> getRequirementsFromFile(PathDE file) {
return trackerAdapter.getRequirementsByFile(file);
}
/**
* @param file
* @return
* @author Gayathery
*/
public Collection<Commit> getCommitsFromFile(PathDE file) {
return trackerAdapter.getCommitsByFile(file);
}
/**
* This method masks TrackerAdapter::getImpactPercentageForRequirementAndFile
* after successful data preparation.
* @param file path.
* @param requ irement id.
* @return float - the impact value of requirement *requ* on *file*
*/
public float getImpactPercentageForRequirementAndFile(Requirement requ, PathDE file){
// lock.lock();
// try{
// if(getImpactPercentageForRequirementAndFile == globalChangeTime){
// Collection<PathDE> paths = workerRepositoryReqCommitFile.get(commit.instanceRequirement);
// for(PathDE path : paths){
// if(path.FilePath.equals(file.FilePath))
// return path.impact;
//
// }
// return (float)0.0;
// }
// }
// catch(Exception ex){
// ex.printStackTrace();
// }
// finally{
// lock.unlock();
// }
return getImpactPercentageForRequirementAndFile(requ, file);
}
/**
* @param commit
* @return
* @author Gayathery
* Will be masked in future if performance improvement is required
*/
public Collection<PathDE> getFilesFromCommit(Commit commit) {
return trackerAdapter.getFilesByCommit(commit);
}
/**
* @return
* @author Gayathery
* Will be masked in future if performance improvement is required
*/
public Pattern getCurrentRequirementPattern() {
return trackerAdapter.getCurrentRequirementPattern();
}
/**
* @param reqPatternString
* @author Gayathery
* Will be masked in future if performance improvement is required
*/
public void setReqPatternString(Pattern reqPatternString) {
trackerAdapter.setReqPatternString(reqPatternString);
}
/**
* @return
* @author Gayathery
* Will be masked in future if performance improvement is required
*/
public String getCurrentRepositoryPath() {
return trackerAdapter.getCurrentRepositoryPath();
}
/**
* @return
* @author Gayathery
* Will be masked in future if performance improvement is required
*/
public Collection<Commit> getAllCommits() {
return trackerAdapter.getAllCommits();
}
/**
* @param commit
* @return
* @author Gayathery
* Will be masked in future if performance improvement is required
*/
public Collection<Requirement> getRequirementsFromCommit(Commit commit) {
return trackerAdapter.getRequirementsFromCommit(commit);
}
/**
* @param requirement
* @return Collection<CommitFile>
* This method masks the getFilesByRequirement method of TrackerAdapter after successful data preparation
* @author Gayathery
*/
public Collection<PathDE> getCommitFilesForRequirement(Requirement requirement) {
lock.lock();
try{
if(getCommitFilesForRequirement == globalChangeTime){
Collection<PathDE> colCommitFile = workerRepositoryReqCommitFile.get(requirement);
return colCommitFile;
}
}
catch(Exception ex){
ex.printStackTrace();
}
finally{
lock.unlock();
}
return trackerAdapter.getFilesByRequirement(requirement);
}
/**
* @param next
* @return
* @author Gayathery
* Will be masked in future if performance improvement is required
*/
public Collection<AnnotatedLine> getAnnotatedLines(PathDE next) {
return trackerAdapter.getAnnotatedLines(next);
}
/**
* @return
* @author Gayathery
* Will be masked in future if performance improvement is required
*/
public RequirementsTraceabilityMatrix generateRequirementsTraceability() {
return trackerAdapter.generateRequirementsTraceability();
}
/**
* @return
* @author Gayathery
* Will be masked in future if performance improvement is required
*/
public RequirementsTraceabilityMatrixByImpact generateRequirementsTraceabilityByImpact() {
return trackerAdapter.generateRequirementsTraceabilityByImpact();
}
/**
* @param id
* @param title
* @param description
* @return
* @author Gayathery
* Will be masked in future if performance improvement is required
*/
public boolean updateRequirement(String id, String title, String description) {
return trackerAdapter.updateRequirement(id, title, description);
}
}