001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.plugins.checkstyle;
021    
022    import com.google.common.collect.ArrayListMultimap;
023    import com.google.common.collect.ListMultimap;
024    import org.apache.commons.configuration.BaseConfiguration;
025    import org.apache.commons.configuration.Configuration;
026    import org.apache.commons.lang.StringEscapeUtils;
027    import org.apache.commons.lang.StringUtils;
028    import org.sonar.api.profiles.ProfileExporter;
029    import org.sonar.api.profiles.RulesProfile;
030    import org.sonar.api.resources.Java;
031    import org.sonar.api.rules.ActiveRule;
032    import org.sonar.api.rules.RuleParam;
033    import org.sonar.api.utils.SonarException;
034    
035    import java.io.IOException;
036    import java.io.Writer;
037    import java.util.List;
038    
039    public class CheckstyleProfileExporter extends ProfileExporter {
040    
041      static final String DOCTYPE_DECLARATION = "<!DOCTYPE module PUBLIC \"-//Puppy Crawl//DTD Check Configuration 1.2//EN\" \"http://www.puppycrawl.com/dtds/configuration_1_2.dtd\">";
042      private Configuration conf;
043    
044      public CheckstyleProfileExporter(Configuration conf) {
045        super(CheckstyleConstants.REPOSITORY_KEY, CheckstyleConstants.PLUGIN_NAME);
046        this.conf = conf;
047        setSupportedLanguages(Java.KEY);
048        setMimeType("application/xml");
049      }
050    
051      /**
052       * for unit tests
053       */
054      CheckstyleProfileExporter() {
055        this(new BaseConfiguration());
056      }
057    
058      public void exportProfile(RulesProfile profile, Writer writer) {
059        try {
060          ListMultimap<String, ActiveRule> activeRulesByConfigKey = arrangeByConfigKey(profile.getActiveRulesByRepository(CheckstyleConstants.REPOSITORY_KEY));
061          generateXML(writer, activeRulesByConfigKey);
062    
063        } catch (IOException e) {
064          throw new SonarException("Fail to export the profile " + profile, e);
065        }
066    
067      }
068    
069      private void generateXML(Writer writer, ListMultimap<String, ActiveRule> activeRulesByConfigKey) throws IOException {
070        appendXmlHeader(writer);
071        appendCustomFilters(writer);
072        appendCheckerModules(writer, activeRulesByConfigKey);
073        appendTreeWalker(writer, activeRulesByConfigKey);
074        appendXmlFooter(writer);
075      }
076    
077      private void appendXmlHeader(Writer writer) throws IOException {
078        writer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
079            + DOCTYPE_DECLARATION
080            + "<!-- Generated by Sonar -->"
081            + "<module name=\"Checker\">");
082      }
083    
084      private void appendCustomFilters(Writer writer) throws IOException {
085        String filtersXML = conf.getString(CheckstyleConstants.FILTERS_KEY, CheckstyleConstants.FILTERS_DEFAULT_VALUE);
086        if (StringUtils.isNotBlank(filtersXML)) {
087          writer.append(filtersXML);
088        }
089      }
090    
091      private void appendCheckerModules(Writer writer, ListMultimap<String, ActiveRule> activeRulesByConfigKey) throws IOException {
092        for (String configKey : activeRulesByConfigKey.keySet()) {
093          if (!isInTreeWalker(configKey)) {
094            List<ActiveRule> activeRules = activeRulesByConfigKey.get(configKey);
095            for (ActiveRule activeRule : activeRules) {
096              appendModule(writer, activeRule);
097            }
098          }
099        }
100      }
101    
102      private void appendTreeWalker(Writer writer, ListMultimap<String, ActiveRule> activeRulesByConfigKey) throws IOException {
103        writer.append("<module name=\"TreeWalker\">");
104        writer.append("<module name=\"FileContentsHolder\"/> ");
105        for (String configKey : activeRulesByConfigKey.keySet()) {
106          if (isInTreeWalker(configKey)) {
107            List<ActiveRule> activeRules = activeRulesByConfigKey.get(configKey);
108            for (ActiveRule activeRule : activeRules) {
109              appendModule(writer, activeRule);
110            }
111          }
112        }
113        writer.append("</module>");
114      }
115    
116      private void appendXmlFooter(Writer writer) throws IOException {
117        writer.append("</module>");
118      }
119    
120      static boolean isInTreeWalker(String configKey) {
121        return StringUtils.startsWithIgnoreCase(configKey, "Checker/TreeWalker/");
122      }
123    
124      private static ListMultimap<String, ActiveRule> arrangeByConfigKey(List<ActiveRule> activeRules) {
125        ListMultimap<String, ActiveRule> result = ArrayListMultimap.create();
126        if (activeRules != null) {
127          for (ActiveRule activeRule : activeRules) {
128            result.put(activeRule.getConfigKey(), activeRule);
129          }
130        }
131        return result;
132      }
133    
134      private void appendModule(Writer writer, ActiveRule activeRule) throws IOException {
135        String moduleName = StringUtils.substringAfterLast(activeRule.getConfigKey(), "/");
136        writer.append("<module name=\"");
137        StringEscapeUtils.escapeXml(writer, moduleName);
138        writer.append("\">");
139        if (activeRule.getRule().getParent() != null) {
140          appendModuleProperty(writer, "id", activeRule.getRuleKey());
141        }
142        appendModuleProperty(writer, "severity", CheckstyleSeverityUtils.toSeverity(activeRule.getSeverity()));
143        appendRuleParameters(writer, activeRule);
144        writer.append("</module>");
145      }
146    
147      private void appendRuleParameters(Writer writer, ActiveRule activeRule) throws IOException {
148        for (RuleParam ruleParam : activeRule.getRule().getParams()) {
149          String value = activeRule.getParameter(ruleParam.getKey());
150          if (StringUtils.isNotBlank(value)) {
151            appendModuleProperty(writer, ruleParam.getKey(), value);
152          }
153        }
154      }
155    
156      private void appendModuleProperty(Writer writer, String propertyKey, String propertyValue) throws IOException {
157        if (StringUtils.isNotBlank(propertyValue)) {
158          writer.append("<property name=\"");
159          StringEscapeUtils.escapeXml(writer, propertyKey);
160          writer.append("\" value=\"");
161          StringEscapeUtils.escapeXml(writer, propertyValue);
162          writer.append("\"/>");
163        }
164      }
165    
166    }