/* * Sonar Flex Plugin * Copyright (C) 2010 SonarSource * dev@sonar.codehaus.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 */ package org.sonar.plugins.flex.flexmetrics; import com.google.common.collect.Maps; import org.apache.commons.lang.StringUtils; import org.codehaus.staxmate.in.SMHierarchicCursor; import org.codehaus.staxmate.in.SMInputCursor; import org.sonar.api.batch.SensorContext; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.PersistenceMode; import org.sonar.api.measures.RangeDistributionBuilder; import org.sonar.api.resources.Resource; import org.sonar.api.utils.ParsingUtils; import org.sonar.api.utils.StaxParser; import org.sonar.plugins.flex.FlexFile; import org.sonar.plugins.flex.FlexPackage; import org.sonar.plugins.flex.FlexUtils; import java.io.File; import java.text.ParseException; import java.util.HashMap; import java.util.Map; import javax.xml.stream.XMLStreamException; public class FlexMetricsParser { private final static Number[] FUNCTIONS_DISTRIB_BOTTOM_LIMITS = { 1, 2, 4, 6, 8, 10, 12 }; private final static Number[] CLASSES_DISTRIB_BOTTOM_LIMITS = { 0, 5, 10, 20, 30, 60, 90 }; private SensorContext context; public FlexMetricsParser(SensorContext context) { this.context = context; } public void parse(File xmlFile) { StaxParser parser = new StaxParser(new StaxParser.XmlStreamHandler() { public void stream(SMHierarchicCursor rootCursor) throws XMLStreamException { rootCursor.advance(); // <javancss> SMInputCursor cursor = rootCursor.childElementCursor(); while (cursor.getNext() != null) { String nodeName = cursor.getLocalName(); if (StringUtils.equals("packages", nodeName)) { SMInputCursor packagesCursor = cursor.childElementCursor("package"); processPackages(packagesCursor); } else if (StringUtils.equals("objects", nodeName)) { SMInputCursor objectsCursor = cursor.childElementCursor("object"); processObjects(objectsCursor); } else if (StringUtils.equals("functions", nodeName)) { SMInputCursor functionsCursor = cursor.childElementCursor("function"); processFunctions(functionsCursor); } } } }); try { parser.parse(xmlFile); } catch (XMLStreamException e) { FlexUtils.LOG.error("Error parsing file : " + xmlFile, e); } } private void processPackages(SMInputCursor packagesCursor) throws XMLStreamException { while (packagesCursor.getNext() != null) { Map<String, String> values = processChildren(packagesCursor); String packageName = values.get("name"); Resource flexPackage = new FlexPackage(packageName); context.saveMeasure(flexPackage, CoreMetrics.PACKAGES, 1.0); } } private void processObjects(SMInputCursor objectsCursor) throws XMLStreamException { while (objectsCursor.getNext() != null) { Map<String, String> values = processChildren(objectsCursor); String fileName = values.get("name"); Resource flexFile = new FlexFile(fileName); double lines = parseDouble(values.get("javadocs")) + parseDouble(values.get("single_comment_lines")) + parseDouble(values.get("multi_comment_lines")); context.saveMeasure(flexFile, CoreMetrics.COMMENT_LINES, lines); context.saveMeasure(flexFile, CoreMetrics.CLASSES, 1.0); context.saveMeasure(flexFile, CoreMetrics.FILES, 1.0); context.saveMeasure(flexFile, CoreMetrics.NCLOC, parseDouble(values.get("ncss"))); context.saveMeasure(flexFile, CoreMetrics.FUNCTIONS, parseDouble(values.get("functions"))); } } private void processFunctions(SMInputCursor functionsCursor) throws XMLStreamException { Map<String, Integer> ccnCountPerClass = new HashMap<String, Integer>(); Map<String, RangeDistributionBuilder> ccnDistributionPerClass = new HashMap<String, RangeDistributionBuilder>(); while (functionsCursor.getNext() != null) { Map<String, String> values = processChildren(functionsCursor); String fullFunctionName = values.get("name"); Integer ccnForFunction = Integer.valueOf(values.get("ccn")); String packageAndClassName = getPackageAndClassFromFunction(fullFunctionName); addUpComplexityToClass(ccnCountPerClass, ccnForFunction, packageAndClassName); addUpComplexityToClassDistribution(ccnDistributionPerClass, ccnForFunction, packageAndClassName); } for (Map.Entry<String, Integer> entry : ccnCountPerClass.entrySet()) { String fullname = entry.getKey(); Integer ccnForClass = entry.getValue(); context.saveMeasure(new FlexFile(fullname), CoreMetrics.COMPLEXITY, ccnForClass.doubleValue()); RangeDistributionBuilder ccnDistribution = new RangeDistributionBuilder(CoreMetrics.CLASS_COMPLEXITY_DISTRIBUTION, CLASSES_DISTRIB_BOTTOM_LIMITS); ccnDistribution.add(ccnForClass.doubleValue()); context.saveMeasure(new FlexFile(fullname), ccnDistribution.build().setPersistenceMode(PersistenceMode.MEMORY)); } for (Map.Entry<String, RangeDistributionBuilder> entry : ccnDistributionPerClass.entrySet()) { String fullname = entry.getKey(); RangeDistributionBuilder ccnDistributionForClass = entry.getValue(); context.saveMeasure(new FlexFile(fullname), ccnDistributionForClass.build().setPersistenceMode(PersistenceMode.MEMORY)); } } private static void addUpComplexityToClass(Map<String, Integer> ccnCountperClass, Integer ccnForFunction, String packageAndClassName) { Integer ccnSum = ccnCountperClass.get(packageAndClassName); if (ccnSum == null) { ccnSum = 0; } ccnSum += ccnForFunction; ccnCountperClass.put(packageAndClassName, ccnSum); } private static void addUpComplexityToClassDistribution(Map<String, RangeDistributionBuilder> ccnDistributionperClass, Integer ccnForFunction, String packageAndClassName) { RangeDistributionBuilder ccnDistribution = ccnDistributionperClass.get(packageAndClassName); if (ccnDistribution == null) { ccnDistribution = new RangeDistributionBuilder(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION, FUNCTIONS_DISTRIB_BOTTOM_LIMITS); ccnDistributionperClass.put(packageAndClassName, ccnDistribution); } ccnDistribution.add(ccnForFunction); } static String getPackageAndClassFromFunction(String functionName) { return StringUtils.substringBefore(functionName, "::"); } private static Map<String, String> processChildren(SMInputCursor cursor) throws XMLStreamException { SMInputCursor objectCursor = cursor.childElementCursor(); Map<String, String> values = Maps.newHashMap(); while (objectCursor.getNext() != null) { String nodeName = objectCursor.getLocalName(); values.put(nodeName, objectCursor.collectDescendantText()); } return values; } private static double parseDouble(String value) { if (StringUtils.isBlank(value)) { return 0.0; } try { return ParsingUtils.parseNumber(value); } catch (ParseException e) { return 0.0; } } }