package org.zalando.catwatch.backend.service;
import com.google.common.base.Splitter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.zalando.catwatch.backend.model.Project;
import org.zalando.catwatch.backend.repo.ProjectRepository;
import org.zalando.catwatch.backend.service.comparator.ProjectCommitComparator;
import org.zalando.catwatch.backend.service.comparator.ProjectContributorComparator;
import org.zalando.catwatch.backend.service.comparator.ProjectForkComparator;
import org.zalando.catwatch.backend.service.comparator.ProjectScoreComparator;
import org.zalando.catwatch.backend.service.comparator.ProjectStarComparator;
import org.zalando.catwatch.backend.util.Constants;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class ProjectServiceImpl implements ProjectService {
private static final String SORT_ORDER_DESC = "-";
private static final Integer DEFAULT_LIMIT = 5;
private static final Integer DEFAULT_OFFSET = 0;
private final ProjectRepository projectRepository;
private final Environment env;
@Autowired
public ProjectServiceImpl(ProjectRepository projectRepository, Environment env) {
this.projectRepository = projectRepository;
this.env = env;
}
@Override
public Iterable<Project> findProjects(final String organizations, final Optional<Integer> limit,
final Optional<Integer> offset, final Optional<Date> startDate, final Optional<Date> endDate,
final Optional<String> sortBy, final Optional<String> query, final Optional<String> language) {
List<Project> resultList = new ArrayList<>();
for (String organization : getOrganizations(organizations)) {
if (startDate.isPresent() && endDate.isPresent()) {
List<Project> startProjects = projectRepository.findProjects(organization, startDate.get(), query,language);
List<Project> endProjects = projectRepository.findProjects(organization, endDate.get(), query, language);
resultList.addAll(getMergedProjectList(startProjects, endProjects));
} else if (startDate.isPresent() && !endDate.isPresent()) {
List<Project> startProjects = projectRepository.findProjects(organization, startDate.get(), query,language);
List<Project> endProjects = projectRepository.findProjects(organization, query,language);
resultList.addAll(getMergedProjectList(startProjects, endProjects));
} else if (!startDate.isPresent() && endDate.isPresent()) {
List<Project> projects = projectRepository.findProjects(organization, endDate.get(), query, language);
resultList.addAll(projects);
} else {
List<Project> projects = projectRepository.findProjects(organization, query, language);
resultList.addAll(projects);
}
}
resultList = getSortedResultList(resultList, sortBy);
Integer limitVal = limit.orElse(DEFAULT_LIMIT);
Integer offsetVal = offset.orElse(DEFAULT_OFFSET);
return resultList.stream().skip(offsetVal).limit(limitVal).collect(Collectors.toList());
}
private List<Project> getMergedProjectList(final List<Project> projectsStart, final List<Project> projectsEnd) {
Map<Long, Project> startProjectMap = convertProjectsToMap(projectsStart);
Map<Long, Project> endProjectMap = convertProjectsToMap(projectsEnd);
List<Project> resultList = new ArrayList<>();
for (Map.Entry<Long, Project> entry : endProjectMap.entrySet()) {
if (startProjectMap.containsKey(entry.getKey())) {
resultList.add(createMergedProject(startProjectMap.get(entry.getKey()), entry.getValue()));
} else {
resultList.add(entry.getValue());
}
}
return resultList;
}
private List<Project> getSortedResultList(final List<Project> projectList, final Optional<String> sortBy) {
if (isSortOrderAscending(sortBy)) {
Collections.sort(projectList, getProjectSortComparator(sortBy, true));
} else {
Collections.sort(projectList, Collections.reverseOrder(getProjectSortComparator(sortBy, false)));
}
return projectList;
}
private Project createMergedProject(final Project startProject, final Project endProject) {
endProject.setStarsCount(endProject.getStarsCount() - startProject.getStarsCount());
endProject.setCommitsCount(endProject.getCommitsCount() - startProject.getCommitsCount());
endProject.setForksCount(endProject.getForksCount() - startProject.getForksCount());
endProject.setContributorsCount(endProject.getContributorsCount() - startProject.getContributorsCount());
endProject.setScore(endProject.getScore() - startProject.getScore());
return endProject;
}
private Map<Long, Project> convertProjectsToMap(final List<Project> projects) {
Map<Long, Project> projectMap = new HashMap<>();
for (Project project : projects) {
projectMap.put(project.getGitHubProjectId(), project);
}
return projectMap;
}
private Iterable<String> getOrganizations(String organizations) {
if (organizations == null) {
organizations = getOrganizationConfig();
}
return Splitter.on(',').trimResults().omitEmptyStrings().split(organizations);
}
/**
* This method returns the list of organizations string.
*
* @return
*/
private String getOrganizationConfig() {
if (!env.containsProperty(Constants.CONFIG_ORGANIZATION_LIST)) {
// TODO throw new exception
return "";
}
return env.getProperty(Constants.CONFIG_ORGANIZATION_LIST);
}
/**
* This returns sorting order.
*
* @param sortBy
* @return
*/
private boolean isSortOrderAscending(final Optional<String> sortBy) {
if (sortBy.isPresent()) {
String sortColumn = sortBy.get();
return !sortColumn.startsWith(SORT_ORDER_DESC);
} else {
return false;
}
}
/**
* This returns the required sorting comparator class.
*
* @param sortBy
* @param sortOrderAscending
* @return
*/
private Comparator<Project> getProjectSortComparator(final Optional<String> sortBy, final boolean sortOrderAscending) {
String sortColumn;
if (sortBy.isPresent()) {
if (!sortOrderAscending) {
sortColumn = sortBy.get().substring(1);
} else {
sortColumn = sortBy.get();
}
} else {
sortColumn = ProjectSortColumn.SCORE;
}
switch (sortColumn) {
case ProjectSortColumn.STARS_COUNT:
return new ProjectStarComparator();
case ProjectSortColumn.SCORE:
return new ProjectScoreComparator();
case ProjectSortColumn.COMMITS_COUNT:
return new ProjectCommitComparator();
case ProjectSortColumn.FORKS_COUNT:
return new ProjectForkComparator();
case ProjectSortColumn.CONTRIBUTION_COUNT:
return new ProjectContributorComparator();
default:
return new ProjectScoreComparator();
}
}
}