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 com.google.common.annotations.VisibleForTesting;
023import com.google.common.collect.Lists;
024import org.apache.commons.lang.StringUtils;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027import org.sonar.api.PropertyType;
028import org.sonar.api.ServerComponent;
029import org.sonar.api.utils.AnnotationUtils;
030import org.sonar.api.utils.FieldUtils2;
031import org.sonar.api.utils.SonarException;
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    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}