001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2012 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.api.rules;
021    
022    import com.google.common.annotations.VisibleForTesting;
023    import com.google.common.collect.Lists;
024    import org.apache.commons.lang.StringUtils;
025    import org.slf4j.Logger;
026    import org.slf4j.LoggerFactory;
027    import org.sonar.api.PropertyType;
028    import org.sonar.api.ServerComponent;
029    import org.sonar.api.utils.AnnotationUtils;
030    import org.sonar.api.utils.FieldUtils2;
031    import org.sonar.api.utils.SonarException;
032    import org.sonar.check.Check;
033    
034    import java.lang.reflect.Field;
035    import java.util.Collection;
036    import java.util.List;
037    
038    /**
039     * @since 2.3
040     */
041    public final class AnnotationRuleParser implements ServerComponent {
042    
043      private static final Logger LOG = LoggerFactory.getLogger(AnnotationRuleParser.class);
044    
045      public List<Rule> parse(String repositoryKey, Collection<Class> annotatedClasses) {
046        List<Rule> rules = Lists.newArrayList();
047        for (Class annotatedClass : annotatedClasses) {
048          rules.add(create(repositoryKey, annotatedClass));
049        }
050        return rules;
051      }
052    
053      private Rule create(String repositoryKey, Class annotatedClass) {
054        org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(annotatedClass, org.sonar.check.Rule.class);
055        if (ruleAnnotation != null) {
056          return toRule(repositoryKey, annotatedClass, ruleAnnotation);
057        }
058        Check checkAnnotation = AnnotationUtils.getAnnotation(annotatedClass, Check.class);
059        if (checkAnnotation != null) {
060          return toRule(repositoryKey, annotatedClass, checkAnnotation);
061        }
062        LOG.warn("The class " + annotatedClass.getCanonicalName() + " should be annotated with " + Rule.class);
063        return null;
064      }
065    
066      private Rule toRule(String repositoryKey, Class clazz, org.sonar.check.Rule ruleAnnotation) {
067        String ruleKey = StringUtils.defaultIfEmpty(ruleAnnotation.key(), clazz.getCanonicalName());
068        String ruleName = StringUtils.defaultIfEmpty(ruleAnnotation.name(), null);
069        String description = StringUtils.defaultIfEmpty(ruleAnnotation.description(), null);
070        Rule rule = Rule.create(repositoryKey, ruleKey, ruleName);
071        rule.setDescription(description);
072        rule.setSeverity(RulePriority.fromCheckPriority(ruleAnnotation.priority()));
073        rule.setCardinality(ruleAnnotation.cardinality());
074    
075        List<Field> fields = FieldUtils2.getFields(clazz, true);
076        for (Field field : fields) {
077          addRuleProperty(rule, field);
078        }
079        return rule;
080      }
081    
082      private Rule toRule(String repositoryKey, Class clazz, Check checkAnnotation) {
083        String ruleKey = StringUtils.defaultIfEmpty(checkAnnotation.key(), clazz.getCanonicalName());
084        String ruleName = StringUtils.defaultIfEmpty(checkAnnotation.title(), ruleKey);
085        Rule rule = Rule.create(repositoryKey, ruleKey, ruleName);
086        rule.setDescription(checkAnnotation.description());
087        rule.setSeverity(RulePriority.fromCheckPriority(checkAnnotation.priority()));
088    
089        List<Field> fields = FieldUtils2.getFields(clazz, true);
090        for (Field field : fields) {
091          addCheckProperty(rule, field);
092        }
093        return rule;
094      }
095    
096      private void addRuleProperty(Rule rule, Field field) {
097        org.sonar.check.RuleProperty propertyAnnotation = field.getAnnotation(org.sonar.check.RuleProperty.class);
098        if (propertyAnnotation != null) {
099          String fieldKey = StringUtils.defaultIfEmpty(propertyAnnotation.key(), field.getName());
100          RuleParam param = rule.createParameter(fieldKey);
101          param.setDescription(propertyAnnotation.description());
102          param.setDefaultValue(propertyAnnotation.defaultValue());
103          if (!StringUtils.isBlank(propertyAnnotation.type())) {
104            try {
105              param.setType(PropertyType.valueOf(propertyAnnotation.type().trim()).name());
106            } catch (IllegalArgumentException e) {
107              throw new SonarException("Invalid property type [" + propertyAnnotation.type() + "]", e);
108            }
109          } else {
110            param.setType(guessType(field.getType()).name());
111          }
112        }
113      }
114    
115      private void addCheckProperty(Rule rule, Field field) {
116        org.sonar.check.CheckProperty propertyAnnotation = field.getAnnotation(org.sonar.check.CheckProperty.class);
117        if (propertyAnnotation != null) {
118          String fieldKey = StringUtils.defaultIfEmpty(propertyAnnotation.key(), field.getName());
119          RuleParam param = rule.createParameter(fieldKey);
120          param.setDescription(propertyAnnotation.description());
121        }
122      }
123    
124      @VisibleForTesting
125      static PropertyType guessType(Class<?> type) {
126        if ((type == Integer.class) || (type == int.class)) {
127          return PropertyType.INTEGER;
128        } else if ((type == Float.class) || (type == float.class)) {
129          return PropertyType.FLOAT;
130        } else if ((type == Boolean.class) || (type == boolean.class)) {
131          return PropertyType.BOOLEAN;
132        }
133        return PropertyType.STRING;
134      }
135    }