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 */
020package org.sonar.api.rules;
021
022import org.sonar.api.utils.SonarException;
023
024import com.google.common.annotations.VisibleForTesting;
025import com.google.common.collect.Lists;
026import org.apache.commons.lang.StringUtils;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029import org.sonar.api.PropertyType;
030import org.sonar.api.ServerComponent;
031import org.sonar.api.utils.AnnotationUtils;
032import org.sonar.check.Check;
033
034import java.lang.reflect.Field;
035import java.util.Collection;
036import java.util.List;
037
038/**
039 * @since 2.3
040 */
041public 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    Field[] fields = clazz.getDeclaredFields();
076    if (fields != null) {
077      for (Field field : fields) {
078        addRuleProperty(rule, field);
079      }
080    }
081
082    return rule;
083  }
084
085  private Rule toRule(String repositoryKey, Class clazz, Check checkAnnotation) {
086    String ruleKey = StringUtils.defaultIfEmpty(checkAnnotation.key(), clazz.getCanonicalName());
087    String ruleName = StringUtils.defaultIfEmpty(checkAnnotation.title(), ruleKey);
088    Rule rule = Rule.create(repositoryKey, ruleKey, ruleName);
089    rule.setDescription(checkAnnotation.description());
090    rule.setSeverity(RulePriority.fromCheckPriority(checkAnnotation.priority()));
091
092    Field[] fields = clazz.getDeclaredFields();
093    if (fields != null) {
094      for (Field field : fields) {
095        addCheckProperty(rule, field);
096      }
097    }
098    return rule;
099  }
100
101  private void addRuleProperty(Rule rule, Field field) {
102    org.sonar.check.RuleProperty propertyAnnotation = field.getAnnotation(org.sonar.check.RuleProperty.class);
103    if (propertyAnnotation != null) {
104      String fieldKey = StringUtils.defaultIfEmpty(propertyAnnotation.key(), field.getName());
105      RuleParam param = rule.createParameter(fieldKey);
106      param.setDescription(propertyAnnotation.description());
107      param.setDefaultValue(propertyAnnotation.defaultValue());
108      if (!StringUtils.isBlank(propertyAnnotation.type())) {
109        try {
110          param.setType(PropertyType.valueOf(propertyAnnotation.type().trim()).name());
111        } catch (IllegalArgumentException e) {
112          throw new SonarException("Invalid property type [" + propertyAnnotation.type() + "]", e);
113        }
114      } else {
115        param.setType(guessType(field.getType()).name());
116      }
117    }
118  }
119
120  private void addCheckProperty(Rule rule, Field field) {
121    org.sonar.check.CheckProperty propertyAnnotation = field.getAnnotation(org.sonar.check.CheckProperty.class);
122    if (propertyAnnotation != null) {
123      String fieldKey = StringUtils.defaultIfEmpty(propertyAnnotation.key(), field.getName());
124      RuleParam param = rule.createParameter(fieldKey);
125      param.setDescription(propertyAnnotation.description());
126    }
127  }
128
129  @VisibleForTesting
130  static PropertyType guessType(Class<?> type) {
131    if ((type == Integer.class) || (type == int.class)) {
132      return PropertyType.INTEGER;
133    } else if ((type == Float.class) || (type == float.class)) {
134      return PropertyType.FLOAT;
135    } else if ((type == Boolean.class) || (type == boolean.class)) {
136      return PropertyType.BOOLEAN;
137    }
138    return PropertyType.STRING;
139  }
140}