001/* 002 * SonarQube 003 * Copyright (C) 2009-2016 SonarSource SA 004 * mailto:contact AT sonarsource DOT com 005 * 006 * This program 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 * This program 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 License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020package org.sonar.api.server.rule; 021 022import com.google.common.annotations.VisibleForTesting; 023import com.google.common.base.Function; 024import com.google.common.base.Functions; 025import com.google.common.collect.ImmutableMap; 026import java.lang.reflect.Field; 027import java.util.List; 028import javax.annotation.CheckForNull; 029import org.apache.commons.lang.StringUtils; 030import org.sonar.api.rule.RuleStatus; 031import org.sonar.api.utils.AnnotationUtils; 032import org.sonar.api.utils.FieldUtils2; 033import org.sonar.api.utils.log.Logger; 034import org.sonar.api.utils.log.Loggers; 035import org.sonar.check.Cardinality; 036 037/** 038 * Read definitions of rules based on the annotations provided by sonar-check-api. It is used 039 * to feed {@link RulesDefinition}. 040 * 041 * @see org.sonar.api.server.rule.RulesDefinition 042 * @since 4.3 043 */ 044public class RulesDefinitionAnnotationLoader { 045 046 private static final Logger LOG = Loggers.get(RulesDefinitionAnnotationLoader.class); 047 048 private static final Function<Class<?>, RuleParamType> TYPE_FOR_CLASS = Functions.forMap( 049 ImmutableMap.<Class<?>, RuleParamType>builder() 050 .put(Integer.class, RuleParamType.INTEGER) 051 .put(int.class, RuleParamType.INTEGER) 052 .put(Float.class, RuleParamType.FLOAT) 053 .put(float.class, RuleParamType.FLOAT) 054 .put(Boolean.class, RuleParamType.BOOLEAN) 055 .put(boolean.class, RuleParamType.BOOLEAN) 056 .build(), 057 RuleParamType.STRING 058 ); 059 060 public void load(RulesDefinition.NewExtendedRepository repo, Class... annotatedClasses) { 061 for (Class annotatedClass : annotatedClasses) { 062 loadRule(repo, annotatedClass); 063 } 064 } 065 066 @CheckForNull 067 RulesDefinition.NewRule loadRule(RulesDefinition.NewExtendedRepository repo, Class clazz) { 068 org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(clazz, org.sonar.check.Rule.class); 069 if (ruleAnnotation != null) { 070 return loadRule(repo, clazz, ruleAnnotation); 071 } else { 072 LOG.warn("The class " + clazz.getCanonicalName() + " should be annotated with " + org.sonar.check.Rule.class); 073 return null; 074 } 075 } 076 077 private static RulesDefinition.NewRule loadRule(RulesDefinition.NewExtendedRepository repo, Class clazz, org.sonar.check.Rule ruleAnnotation) { 078 String ruleKey = StringUtils.defaultIfEmpty(ruleAnnotation.key(), clazz.getCanonicalName()); 079 String ruleName = StringUtils.defaultIfEmpty(ruleAnnotation.name(), null); 080 String description = StringUtils.defaultIfEmpty(ruleAnnotation.description(), null); 081 082 RulesDefinition.NewRule rule = repo.createRule(ruleKey); 083 rule.setName(ruleName).setHtmlDescription(description); 084 rule.setSeverity(ruleAnnotation.priority().name()); 085 rule.setTemplate(ruleAnnotation.cardinality() == Cardinality.MULTIPLE); 086 rule.setStatus(RuleStatus.valueOf(ruleAnnotation.status())); 087 rule.setTags(ruleAnnotation.tags()); 088 089 List<Field> fields = FieldUtils2.getFields(clazz, true); 090 for (Field field : fields) { 091 loadParameters(rule, field); 092 } 093 094 return rule; 095 } 096 097 private static void loadParameters(RulesDefinition.NewRule rule, Field field) { 098 org.sonar.check.RuleProperty propertyAnnotation = field.getAnnotation(org.sonar.check.RuleProperty.class); 099 if (propertyAnnotation != null) { 100 String fieldKey = StringUtils.defaultIfEmpty(propertyAnnotation.key(), field.getName()); 101 RulesDefinition.NewParam param = rule.createParam(fieldKey) 102 .setDescription(propertyAnnotation.description()) 103 .setDefaultValue(propertyAnnotation.defaultValue()); 104 105 if (!StringUtils.isBlank(propertyAnnotation.type())) { 106 try { 107 param.setType(RuleParamType.parse(propertyAnnotation.type().trim())); 108 } catch (IllegalArgumentException e) { 109 throw new IllegalArgumentException("Invalid property type [" + propertyAnnotation.type() + "]", e); 110 } 111 } else { 112 param.setType(guessType(field.getType())); 113 } 114 } 115 } 116 117 @VisibleForTesting 118 static RuleParamType guessType(Class<?> type) { 119 return TYPE_FOR_CLASS.apply(type); 120 } 121}