Java tutorial
/* * Copyright 2013 - 2014 Felix Mller * * This file is part of CodeQ Invest. * * CodeQ Invest is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * CodeQ Invest 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CodeQ Invest. If not, see <http://www.gnu.org/licenses/>. */ package org.codeqinvest.web.project; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Splitter; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.codeqinvest.investment.profit.WeightedProfitCalculator; import org.codeqinvest.quality.Artefact; import org.codeqinvest.quality.QualityViolation; import org.codeqinvest.quality.analysis.QualityAnalysis; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * The generator produces the necessary json tree for the * investment opportunities that are displayed on the project template * in a tree map. * * @author fmueller */ @Component public class InvestmentOpportunitiesJsonGenerator { private static final ObjectMapper MAPPER = new ObjectMapper(); private static final Splitter PACKAGE_SPLITTER = Splitter.on('.'); private final WeightedProfitCalculator weightedProfitCalculator; @Autowired InvestmentOpportunitiesJsonGenerator(WeightedProfitCalculator weightedProfitCalculator) { this.weightedProfitCalculator = weightedProfitCalculator; } public String generate(QualityAnalysis analysis) throws JsonProcessingException { Set<String> alreadyAddedArtefacts = Sets.newHashSet(); Map<String, PackageNode> nodeLookupTable = Maps.newHashMap(); RootNode rootNode = new RootNode(analysis.getProject().getName()); for (QualityViolation violation : analysis.getViolations()) { addArtefact(violation, rootNode, alreadyAddedArtefacts, nodeLookupTable); } rootNode.filterProfitableChildren(); rootNode.updateChangeProbabilityOfProfitableChildren(); rootNode.updateAutomaticChangeProbabilityAndEstimateOfAllChildren(); return MAPPER.writeValueAsString(rootNode); } private void addArtefact(QualityViolation violation, RootNode root, Set<String> alreadyAddedArtefacts, Map<String, PackageNode> nodeLookupTable) { Artefact artefact = violation.getArtefact(); if (!alreadyAddedArtefacts.contains(artefact.getName())) { alreadyAddedArtefacts.add(artefact.getName()); double weightedProfit = weightedProfitCalculator.calculateWeightedProfit(violation); ArtefactNode artefactNode = new ArtefactNode(artefact, weightedProfit); PackageNode currentPackageNode = null; for (String packageName : getAllPackageNamesReversed(artefact)) { if (nodeLookupTable.containsKey(packageName)) { PackageNode node = nodeLookupTable.get(packageName); if (currentPackageNode != null) { node.addChildren(currentPackageNode); } else { node.addChildren(artefactNode); } return; } else { PackageNode packageNode = new PackageNode(getLastPackageName(packageName), packageName); nodeLookupTable.put(packageName, packageNode); if (currentPackageNode != null) { packageNode.addChildren(currentPackageNode); } else { packageNode.addChildren(artefactNode); } currentPackageNode = packageNode; } } if (currentPackageNode != null) { root.addChildren(currentPackageNode); } else { root.addChildren(artefactNode); } } } private String getLastPackageName(String packageName) { String last = null; for (String name : PACKAGE_SPLITTER.split(packageName)) { last = name; } return last; } private List<String> getAllPackageNamesReversed(Artefact artefact) { List<String> packages = new ArrayList<String>(); StringBuilder packagePath = new StringBuilder(); for (String packageName : PACKAGE_SPLITTER.split(artefact.getName())) { if (packageName.equals(artefact.getShortClassName())) { break; } if (packagePath.length() > 0) { packagePath.append('.'); } packagePath.append(packageName); packages.add(packagePath.toString()); } Collections.reverse(packages); return packages; } @Getter @EqualsAndHashCode @JsonPropertyOrder({ "name", "longName", "changeProbability", "automaticChangeProbability", "manualEstimate", "allChildren", "children" }) private static class PackageNode { private final String name; private final String longName; private final Set<PackageNode> allChildren = new TreeSet<PackageNode>(new ByNameComparator()); private final Set<PackageNode> children = new TreeSet<PackageNode>(new ByNameComparator()); @Setter private int changeProbability; @Setter private int automaticChangeProbability; @Setter private Integer manualEstimate; PackageNode(String name, String longName) { this.name = name; this.longName = longName; } void addChildren(PackageNode node) { allChildren.add(node); } void filterProfitableChildren() { for (PackageNode child : getAllChildren()) { child.filterProfitableChildren(); if (child.aggregateProfit() > 0.0) { children.add(child); } } } double aggregateProfit() { double profit = 0.0; for (PackageNode child : getAllChildren()) { double packageProfit = child.aggregateProfit(); if (packageProfit > 0.0) { profit += packageProfit; } } return profit; } void updateChangeProbabilityOfProfitableChildren() { float sumOfChangeProbabilities = 0.0f; for (PackageNode node : getChildren()) { node.updateChangeProbabilityOfProfitableChildren(); sumOfChangeProbabilities += node.getChangeProbability(); } changeProbability = Math.round(sumOfChangeProbabilities / (float) getChildren().size()); } void updateAutomaticChangeProbabilityAndEstimateOfAllChildren() { float sumOfAutomaticChangeProbabilities = 0.0f; Integer manualEstimateOfOneChildren = null; boolean isFirstEstimate = true; boolean hasEachChildrenSameManualEstimate = true; for (PackageNode node : getAllChildren()) { node.updateAutomaticChangeProbabilityAndEstimateOfAllChildren(); if (manualEstimateOfOneChildren == null) { if (isFirstEstimate) { manualEstimateOfOneChildren = node.getManualEstimate(); } else if (node.getManualEstimate() != null) { hasEachChildrenSameManualEstimate = false; } } else if (!manualEstimateOfOneChildren.equals(node.getManualEstimate()) || node.getManualEstimate() == null) { hasEachChildrenSameManualEstimate = false; } isFirstEstimate = false; sumOfAutomaticChangeProbabilities += node.getAutomaticChangeProbability(); } automaticChangeProbability = Math .round(sumOfAutomaticChangeProbabilities / (float) getAllChildren().size()); if (hasEachChildrenSameManualEstimate && manualEstimateOfOneChildren != null) { manualEstimate = manualEstimateOfOneChildren; } } } @EqualsAndHashCode(callSuper = true) @JsonPropertyOrder({ "name", "allChildren", "children" }) @JsonIgnoreProperties({ "longName", "value", "changeProbability", "automaticChangeProbability", "manualEstimate" }) private static class RootNode extends PackageNode { RootNode(String name) { super(name, ""); } } @Getter @EqualsAndHashCode(callSuper = true) @JsonPropertyOrder({ "name", "longName", "value", "changeProbability", "automaticChangeProbability", "manualEstimate" }) @JsonIgnoreProperties({ "allChildren", "children" }) private static class ArtefactNode extends PackageNode { private final double value; ArtefactNode(Artefact artefact, double value) { super(artefact.getShortClassName(), artefact.getName()); this.value = value; final int automaticChangeProbability = (int) Math.round(artefact.getChangeProbability() * 100); setManualEstimate(artefact.getManualEstimate()); setAutomaticChangeProbability(automaticChangeProbability); if (artefact.hasManualEstimate()) { setChangeProbability(artefact.getManualEstimate()); } else { setChangeProbability(automaticChangeProbability); } } @Override double aggregateProfit() { return value; } @Override void addChildren(PackageNode node) { throw new UnsupportedOperationException(); } @Override void updateChangeProbabilityOfProfitableChildren() { // do nothing } @Override void updateAutomaticChangeProbabilityAndEstimateOfAllChildren() { // do nothing } } private static final class ByNameComparator implements Comparator<PackageNode> { @Override public int compare(PackageNode node, PackageNode otherNode) { return node.getName().compareToIgnoreCase(otherNode.getName()); } } }