import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import org.sonar.api.profiles.ProfileDefinition;
private final boolean isDefault;
private final String loadedTemplateType;
private final List<org.sonar.api.rules.ActiveRule> activeRules;
+ private final QProfileName parentQProfileName;
private DefinedQProfile(Builder builder, MessageDigest messageDigest) {
this.qProfileName = new QProfileName(builder.language, builder.getName());
this.isDefault = builder.declaredDefault || builder.computedDefault;
this.loadedTemplateType = computeLoadedTemplateType(this.qProfileName, messageDigest);
this.activeRules = ImmutableList.copyOf(builder.activeRules);
+ this.parentQProfileName = builder.parentName == null ? null : new QProfileName(builder.language, builder.parentName);
}
private static String computeLoadedTemplateType(QProfileName qProfileName, MessageDigest messageDigest) {
return activeRules;
}
+ @CheckForNull
+ public QProfileName getParentQProfileName() {
+ return parentQProfileName;
+ }
+
static final class Builder {
private String language;
private String name;
private boolean declaredDefault;
private boolean computedDefault;
private final List<org.sonar.api.rules.ActiveRule> activeRules = new ArrayList<>();
+ private String parentName;
public Builder setLanguage(String language) {
this.language = language;
return this;
}
+ public Builder setParentName(@Nullable String parentName) {
+ this.parentName = parentName;
+ return this;
+ }
+
+ @CheckForNull
+ public String getParentName() {
+ return parentName;
+ }
+
DefinedQProfile build(MessageDigest messageDigest) {
return new DefinedQProfile(this, messageDigest);
}
*/
package org.sonar.server.qualityprofile;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import java.security.MessageDigest;
import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Function;
import javax.annotation.Nullable;
import org.apache.commons.codec.digest.DigestUtils;
import org.sonar.api.profiles.ProfileDefinition;
.entrySet()
.stream()
.filter(DefinedQProfileRepositoryImpl::ensureAtMostOneDeclaredDefault)
+ .filter(entry -> ensureParentExists(entry.getKey(), entry.getValue()))
.collect(MoreCollectors.uniqueIndex(Map.Entry::getKey, entry -> toQualityProfiles(entry.getValue()), buildersByLanguage.size()));
}
+ private boolean ensureParentExists(String language, List<DefinedQProfile.Builder> builders) {
+ Set<String> qProfileNames = builders.stream()
+ .map(DefinedQProfile.Builder::getName)
+ .collect(MoreCollectors.toSet(builders.size()));
+ builders
+ .forEach(builder -> {
+ String parentName = builder.getParentName();
+ checkState(parentName == null || qProfileNames.contains(parentName),
+ "Quality profile with name %s references Quality profile with name %s as its parent, but it does not exist for language %s",
+ builder.getName(), builder.getParentName(), language);
+ });
+ return true;
+ }
+
/**
* Creates {@link DefinedQProfile.Builder} for each unique quality profile name for a given language.
* Builders will have the following properties populated:
for (RulesProfile rulesProfile : rulesProfilesByLanguage.getValue()) {
qualityProfileBuildersByName.compute(
rulesProfile.getName(),
- (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, rulesProfile, name));
+ (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, rulesProfile));
}
return ImmutableList.copyOf(qualityProfileBuildersByName.values());
}
return true;
}
- private static DefinedQProfile.Builder updateOrCreateBuilder(String language, @Nullable DefinedQProfile.Builder existingBuilder, RulesProfile rulesProfile, String name) {
+ private static DefinedQProfile.Builder updateOrCreateBuilder(String language, @Nullable DefinedQProfile.Builder existingBuilder, RulesProfile rulesProfile) {
DefinedQProfile.Builder builder = existingBuilder;
if (builder == null) {
builder = new DefinedQProfile.Builder()
.setLanguage(language)
- .setName(name);
+ .setName(rulesProfile.getName())
+ .setParentName(rulesProfile.getParentName());
}
Boolean defaultProfile = rulesProfile.getDefaultProfile();
boolean declaredDefault = defaultProfile != null && defaultProfile;
}
MessageDigest md5Digest = DigestUtils.getMd5Digest();
return builders.stream()
+ .sorted(new SortByParentName(builders))
.map(builder -> builder.build(md5Digest))
.collect(MoreCollectors.toList(builders.size()));
}
+
+ @VisibleForTesting
+ static class SortByParentName implements Comparator<DefinedQProfile.Builder> {
+ private final Map<String, DefinedQProfile.Builder> buildersByName;
+ @VisibleForTesting
+ final Map<String, Integer> depthByBuilder;
+
+ @VisibleForTesting
+ SortByParentName(Collection<DefinedQProfile.Builder> builders) {
+ this.buildersByName = builders.stream()
+ .collect(MoreCollectors.uniqueIndex(DefinedQProfile.Builder::getName, Function.identity(), builders.size()));
+ this.depthByBuilder = buildDepthByBuilder(buildersByName, builders);
+ }
+
+ private static Map<String, Integer> buildDepthByBuilder(Map<String, DefinedQProfile.Builder> buildersByName, Collection<DefinedQProfile.Builder> builders) {
+ Map<String, Integer> depthByBuilder = new HashMap<>();
+ builders.forEach(builder -> depthByBuilder.put(builder.getName(), 0));
+ builders
+ .stream()
+ .filter(builder -> builder.getParentName() != null)
+ .forEach(builder -> increaseDepth(buildersByName, depthByBuilder, builder));
+ return ImmutableMap.copyOf(depthByBuilder);
+ }
+
+ private static void increaseDepth(Map<String, DefinedQProfile.Builder> buildersByName, Map<String, Integer> maps, DefinedQProfile.Builder builder) {
+ DefinedQProfile.Builder parent = buildersByName.get(builder.getParentName());
+ if (parent.getParentName() != null) {
+ increaseDepth(buildersByName, maps, parent);
+ }
+ maps.put(builder.getName(), maps.get(parent.getName()) + 1);
+ }
+
+ @Override
+ public int compare(DefinedQProfile.Builder o1, DefinedQProfile.Builder o2) {
+ return depthByBuilder.getOrDefault(o1.getName(), 0) - depthByBuilder.getOrDefault(o2.getName(), 0);
+ }
+ }
}