Skip to content

Commit

Permalink
#408 added initial implementation for BaselineManager
Browse files Browse the repository at this point in the history
  • Loading branch information
DirkMahler committed Jul 10, 2024
1 parent d39c791 commit 5fdc4b5
Show file tree
Hide file tree
Showing 13 changed files with 728 additions and 88 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.buschmais.jqassistant.core.analysis.api.baseline;

import java.util.SortedMap;
import java.util.TreeMap;

import lombok.Getter;
import lombok.ToString;

/**
* Represents a baseline for analyze results.
* <p>
* Note that the data structures rely on {@link SortedMap}s to preserve the order of entries. This is required for creating diffs between baseline files which are generated from these structures.
*/
@Getter
@ToString
public class Baseline {

/**
* The baseline per {@link com.buschmais.jqassistant.core.rule.api.model.Concept} id.
*/
private SortedMap<String, RuleBaseline> concepts = new TreeMap<>();

/**
* The baseline per {@link com.buschmais.jqassistant.core.rule.api.model.Constraint} id.
*/
private SortedMap<String, RuleBaseline> constraints = new TreeMap<>();

/**
* Represent a baseline for a specific {@link com.buschmais.jqassistant.core.rule.api.model.Concept} or {@link com.buschmais.jqassistant.core.rule.api.model.Constraint}.
*/
@Getter
@ToString
public static class RuleBaseline {

/**
* Holds the row key as key and the columns (as human-readable labels) as values.
*/
private final SortedMap<String, SortedMap<String, String>> rows = new TreeMap<>();

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.buschmais.jqassistant.core.analysis.api.baseline;

import java.util.Map;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;

import com.buschmais.jqassistant.core.report.api.model.Column;
import com.buschmais.jqassistant.core.report.api.model.Row;
import com.buschmais.jqassistant.core.rule.api.model.Concept;
import com.buschmais.jqassistant.core.rule.api.model.Constraint;
import com.buschmais.jqassistant.core.rule.api.model.ExecutableRule;

import lombok.RequiredArgsConstructor;

/**
* Verifies result rows gathered for rule during an analyze task against a baseline
* <p>
* {@link Row}s of a constraint are considered as "new" if
* <ul>
* <li>No baseline exists.</li>
* <li>A baseline exists but does not yet contain the {@link Row}.</li>
* </ul>
* <p>
* Only {@link Row}s that have been validated using {@link #isNew(ExecutableRule, Row)} ({@link ExecutableRule}, Row)} are copied to the new baseline.
* <p>
*/
@RequiredArgsConstructor
public class BaselineManager {

private final com.buschmais.jqassistant.core.analysis.api.configuration.Baseline configuration;

private final Optional<Baseline> optionalOldBaseline;

private final Baseline newBaseline = new Baseline();

public boolean isNew(ExecutableRule<?> executableRule, Row row) {
String ruleId = executableRule.getId();
String rowKey = row.getKey();
Map<String, Column<?>> columns = row.getColumns();
return optionalOldBaseline.map(oldBaseline -> {
SortedMap<String, Baseline.RuleBaseline> ruleBaseline = getRows(oldBaseline, executableRule);
Baseline.RuleBaseline oldRuleBaseline = ruleBaseline.get(ruleId);
if (oldRuleBaseline != null && oldRuleBaseline.getRows()
.containsKey(rowKey)) {
add(ruleId, rowKey, columns);
return false;
}
return true;
})
.orElseGet(() -> {
add(ruleId, rowKey, columns);
return true;
});
}

private static SortedMap<String, Baseline.RuleBaseline> getRows(Baseline baseline, ExecutableRule<?> executableRule) {
if (executableRule instanceof Concept) {
return baseline.getConcepts();
} else if (executableRule instanceof Constraint) {
return baseline.getConstraints();
}
throw new IllegalArgumentException("Unsupported executable rule: " + executableRule);
}

private void add(String constraintId, String rowKey, Map<String, Column<?>> columns) {
Baseline.RuleBaseline newRuleBaseline = newBaseline.getConstraints()
.computeIfAbsent(constraintId, key -> new Baseline.RuleBaseline());
TreeMap<String, String> row = new TreeMap<>();
columns.entrySet()
.stream()
.forEach(entry -> row.put(entry.getKey(), entry.getValue()
.getLabel()));
newRuleBaseline.getRows()
.put(rowKey, row);
}

public Baseline getNewBaseline() {
return newBaseline;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.buschmais.jqassistant.core.analysis.api.baseline;

import java.io.*;
import java.util.*;

import com.buschmais.jqassistant.core.shared.xml.JAXBHelper;

import lombok.RequiredArgsConstructor;
import org.jqassistant.schema.baseline.v2.ColumnType;
import org.jqassistant.schema.baseline.v2.JqassistantBaseline;
import org.jqassistant.schema.baseline.v2.RowType;
import org.jqassistant.schema.baseline.v2.RuleType;

import static java.util.Optional.empty;
import static java.util.Optional.of;

@RequiredArgsConstructor
public class BaselineRepository {

private static final JAXBHelper<JqassistantBaseline> JAXB_HELPER = new JAXBHelper<>(JqassistantBaseline.class);

private final com.buschmais.jqassistant.core.analysis.api.configuration.Baseline configuration;

private final File ruleDirectory;

public Optional<Baseline> read() {
File baselineFile = getFile();
if (baselineFile.exists()) {
return of(read(baselineFile));
}
return empty();
}

public void write(Baseline baseline) {
write(baseline, getFile());
}

private File getFile() {
return configuration.file()
.map(File::new)
.orElse(new File(ruleDirectory, "jqassistant-baseline.xml"));
}

private static Baseline read(File baselineFile) {
try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(baselineFile))) {
return toBaseline(JAXB_HELPER.unmarshal(inputStream));
} catch (IOException e) {
throw new IllegalStateException("Unable to read baseline file " + baselineFile, e);
}
}

private static Baseline toBaseline(JqassistantBaseline jqassistantBaseline) {
Baseline baseline = new Baseline();
toBaseline(jqassistantBaseline.getConstraint(), baseline.getConstraints());
toBaseline(jqassistantBaseline.getConcept(), baseline.getConcepts());
return baseline;
}

private static void toBaseline(List<RuleType> ruleTypes, SortedMap<String, Baseline.RuleBaseline> ruleBaselines) {
for (RuleType ruleType : ruleTypes) {
Baseline.RuleBaseline ruleBaseline = new Baseline.RuleBaseline();
for (RowType rowType : ruleType.getRow()) {
SortedMap<String, String> columns = new TreeMap<>();
for (ColumnType columnType : rowType.getColumn()) {
columns.put(columnType.getName(), columnType.getValue());
}
ruleBaseline.getRows()
.put(rowType.getKey(), columns);
}
ruleBaselines.put(ruleType.getId(), ruleBaseline);
}
}

private static void write(Baseline baseline, File baselinefile) {
JqassistantBaseline jqassistantBaseline = fromBaseline(baseline);
try {
JAXB_HELPER.marshal(jqassistantBaseline, new FileOutputStream(baselinefile));
} catch (IOException e) {
throw new IllegalStateException("Unable to write baseline file " + baselinefile, e);
}
}

private static JqassistantBaseline fromBaseline(Baseline baseline) {
JqassistantBaseline jqassistantBaseline = new JqassistantBaseline();
fromBaseline(baseline.getConstraints(), jqassistantBaseline.getConstraint());
fromBaseline(baseline.getConcepts(), jqassistantBaseline.getConcept());
return jqassistantBaseline;
}

private static void fromBaseline(SortedMap<String, Baseline.RuleBaseline> ruleBaselines, List<RuleType> ruleTypes) {
for (Map.Entry<String, Baseline.RuleBaseline> ruleBaselineEntry : ruleBaselines.entrySet()) {
String ruleId = ruleBaselineEntry.getKey();
Baseline.RuleBaseline ruleBaseline = ruleBaselineEntry.getValue();
RuleType ruleType = new RuleType();
ruleType.setId(ruleId);
for (Map.Entry<String, SortedMap<String, String>> rowEntry : ruleBaseline.getRows()
.entrySet()) {
String rowKey = rowEntry.getKey();
SortedMap<String, String> columns = rowEntry.getValue();
RowType rowType = new RowType();
rowType.setKey(rowKey);
for (Map.Entry<String, String> columnEntry : columns.entrySet()) {
ColumnType columnType = new ColumnType();
columnType.setName(columnEntry.getKey());
columnType.setValue(columnEntry.getValue());
rowType.getColumn()
.add(columnType);
}
ruleType.getRow()
.add(rowType);
ruleTypes.add(ruleType);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ public interface Analyze {
@Description("The rule configuration.")
Rule rule();

/**
* The {@link Baseline} configuration.
*
* @return The {@link Baseline} configuration.
*/
@Description("The baseline configuration.")
Baseline baseline();

/**
* The {@link Report} configuration.
*
Expand All @@ -41,6 +49,7 @@ public interface Analyze {
Optional<List<String>> constraints();

String EXCLUDE_CONSTRAINTS = "exclude-constraints";

@Description("The constraints to be excluded.")
Optional<List<String>> excludeConstraints();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.buschmais.jqassistant.core.analysis.api.configuration;

import java.util.List;
import java.util.Optional;

import com.buschmais.jqassistant.core.shared.annotation.Description;

import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;

@ConfigMapping(prefix = "jqassistant.analyze.baseline")
public interface Baseline {

@WithDefault("false")
boolean enabled();

String FILE = "file";

@Description("The name of the file for reading and writing the baseline.")
Optional<String> file();

String INCLUDE_CONCEPTS = "include-concepts";

@Description("The concepts to be included.")
Optional<List<String>> includeConcepts();

String INCLUDE_CONSTRAINTS = "include-constraints";

@Description("The constraints to be included.")
@WithDefault("*")
List<String> includeConstraints();

}
Loading

0 comments on commit 5fdc4b5

Please sign in to comment.