package com.hubspot.blazar.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import com.google.common.base.CharMatcher;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import com.hubspot.blazar.base.GitInfo;
import com.hubspot.blazar.base.MalformedFile;
import com.hubspot.blazar.base.Module;
public class ModuleDiscoveryValidations {
/**
* Currently we do not support branch names with special characters despite the fact that github allows certain special
* characters in branch names (e.g. a single quote). Having special characters in the branch name causes problems in
* services that blazar uses to handle builds, i.e. the build executor and our maven service that detects modules.
*
* @param gitInfo The branch we are checking for validity.
* @return A malformed "file" representing the invalid branch name if it is invalid
*/
public static Optional<MalformedFile> preDiscoveryBranchValidation(GitInfo gitInfo) {
String branch = gitInfo.getBranch();
if (branch.contains("'") ||
branch.contains("`") ||
branch.contains("\"") ||
! CharMatcher.ASCII.matchesAllOf(branch)) {
String message = String.format("Branch %s contained non-ascii or quotation characters not supported by Blazar.%nPlease re-create your branch with a new name.", branch);
return Optional.of(new MalformedFile(gitInfo.getId().get(), "branch-validation", "/", message));
}
return Optional.absent();
}
/**
* Check if we have more than one module with the same name inside a branch
*
* @param modules The modules that were discovered inside the branch.
* @return A malformed "file" representing the duplicate module names if present
*/
public static List<Module> getDuplicateModules(List<Module> modules) {
List<Module> sortedModules = modules.stream().sorted(
Comparator.comparing(module -> module.getName().toLowerCase())).collect(Collectors.toList());
// We want to add all duplicates if there are more that one duplicates per module so we don't use a Set
List<Module> duplicateModules = new ArrayList<>();
int examinedModulePointer = 0;
int nextModulePointer = 1;
while (examinedModulePointer < sortedModules.size() - 1 && nextModulePointer < sortedModules.size()) {
Module examinedModule = sortedModules.get(examinedModulePointer);
Module nextModule = sortedModules.get(nextModulePointer);
// do a case insensitive comparison
if(examinedModule.getName().equalsIgnoreCase(nextModule.getName())) {
duplicateModules.add(nextModule);
//if that's the first duplicate we find then we add the examined in the list too
if (nextModulePointer - examinedModulePointer == 1) {
duplicateModules.add(examinedModule);
}
} else {
examinedModulePointer = nextModulePointer;
}
++nextModulePointer;
}
return ImmutableList.copyOf(duplicateModules);
}
public static MalformedFile getDuplicateModulesMalformedFile(GitInfo branch, List<Module> duplicateModules) {
// group duplicates by name
ListMultimap<String, Module> duplicateModulesPerModuleName = Multimaps.index(duplicateModules, (module) -> module.getName().toLowerCase());
// and turn the map in lines like "duplicateName -> [folder (type:moduleType), folder2 (type: moduleType2, ...]
String duplicatesEntriesAsString = duplicateModulesPerModuleName.asMap().entrySet().stream()
.map(entry -> String.format("%s -> [%s]%n", entry.getKey(), getDuplicateModulesAsString(entry.getValue())))
.collect(Collectors.joining(" ,"));
String message = String.format("The following discovered modules share the same module name: %s. " +
"Module names should be unique inside each repository branch.", duplicatesEntriesAsString);
return new MalformedFile(branch.getId().get(), "duplicate-module-validation", "/", message);
}
private static String getDuplicateModulesAsString(Collection<Module> duplicateModules) {
return duplicateModules.stream().map(duplicateModule -> {
String folder = Strings.isNullOrEmpty(duplicateModule.getFolder())? "/" : duplicateModule.getFolder();
return String.format("%s (type:%s)", folder, duplicateModule.getType());
}).collect(Collectors.joining(" ,"));
}
}