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.base.Objects;
023import com.google.common.base.Strings;
024import com.google.common.collect.ImmutableList;
025import com.google.common.collect.ImmutableMap;
026import com.google.common.collect.ImmutableSortedSet;
027import com.google.common.collect.Maps;
028import com.google.common.collect.Sets;
029import java.io.IOException;
030import java.net.URL;
031import java.util.Collection;
032import java.util.Collections;
033import java.util.HashMap;
034import java.util.List;
035import java.util.Map;
036import java.util.Set;
037import javax.annotation.CheckForNull;
038import javax.annotation.Nullable;
039import javax.annotation.concurrent.Immutable;
040import org.apache.commons.io.IOUtils;
041import org.apache.commons.lang.StringUtils;
042import org.sonar.api.ExtensionPoint;
043import org.sonar.api.rule.RuleStatus;
044import org.sonar.api.rule.Severity;
045import org.sonar.api.rules.RuleType;
046import org.sonar.api.ce.ComputeEngineSide;
047import org.sonar.api.server.ServerSide;
048import org.sonar.api.server.debt.DebtRemediationFunction;
049import org.sonar.api.utils.log.Loggers;
050
051import static com.google.common.base.Preconditions.checkArgument;
052import static com.google.common.base.Preconditions.checkState;
053import static java.lang.String.format;
054import static org.apache.commons.lang.StringUtils.trimToNull;
055
056/**
057 * Defines some coding rules of the same repository. For example the Java Findbugs plugin provides an implementation of
058 * this extension point in order to define the rules that it supports.
059 * <br>
060 * This interface replaces the deprecated class org.sonar.api.rules.RuleRepository.
061 * <br>
062 * <h3>How to use</h3>
063 * <pre>
064 * public class MyJsRulesDefinition implements RulesDefinition {
065 *
066 *   {@literal @}Override
067 *   public void define(Context context) {
068 *     NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
069 *
070 *     // define a rule programmatically. Note that rules
071 *     // could be loaded from files (JSON, XML, ...)
072 *     NewRule x1Rule = repository.createRule("x1")
073 *      .setName("No empty line")
074 *      .setHtmlDescription("Generate an issue on empty lines")
075 *
076 *      // optional tags
077 *      .setTags("style", "stupid")
078 *
079 *     // optional status. Default value is READY.
080 *     .setStatus(RuleStatus.BETA)
081 *
082 *     // default severity when the rule is activated on a Quality profile. Default value is MAJOR.
083 *     .setSeverity(Severity.MINOR);
084 *
085 *     // optional type for SonarQube Quality Model. Default is RulesDefinition.Type.CODE_SMELL.
086 *     .setType(RulesDefinition.Type.BUG)
087 *
088 *     x1Rule
089 *       .setDebtRemediationFunction(x1Rule.debtRemediationFunctions().linearWithOffset("1h", "30min"));
090 *
091 *     x1Rule.createParam("acceptWhitespace")
092 *       .setDefaultValue("false")
093 *       .setType(RuleParamType.BOOLEAN)
094 *       .setDescription("Accept whitespaces on the line");
095 *
096 *     // don't forget to call done() to finalize the definition
097 *     repository.done();
098 *   }
099 * }
100 * </pre>
101 * <br>
102 * If rules are declared in a XML file with the standard SonarQube format (see
103 * {@link org.sonar.api.server.rule.RulesDefinitionXmlLoader}), then it can be loaded by using :
104 * <br>
105 * <pre>
106 * public class MyJsRulesDefinition implements RulesDefinition {
107 *
108 *   private final RulesDefinitionXmlLoader xmlLoader;
109 *
110 *   public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader) {
111 *     this.xmlLoader = xmlLoader;
112 *   }
113 *
114 *   {@literal @}Override
115 *   public void define(Context context) {
116 *     NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
117 *     // see javadoc of RulesDefinitionXmlLoader for the format
118 *     xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"));
119 *     repository.done();
120 *   }
121 * }
122 * </pre>
123 * <br>
124 * In the above example, XML file must contain name and description of each rule. If it's not the case, then the
125 * (deprecated) English bundles can be used :
126 * <br>
127 * <pre>
128 * public class MyJsRulesDefinition implements RulesDefinition {
129 *
130 *   private final RulesDefinitionXmlLoader xmlLoader;
131 *   private final RulesDefinitionI18nLoader i18nLoader;
132 *
133 *   public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader, RulesDefinitionI18nLoader i18nLoader) {
134 *     this.xmlLoader = xmlLoader;
135 *     this.i18nLoader = i18nLoader;
136 *   }
137 *
138 *   {@literal @}Override
139 *   public void define(Context context) {
140 *     NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
141 *     xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"), "UTF-8");
142 *     i18nLoader.load(repository);
143 *     repository.done();
144 *   }
145 * }
146 * </pre>
147 *
148 * @since 4.3
149 */
150@ServerSide
151@ComputeEngineSide
152@ExtensionPoint
153public interface RulesDefinition {
154
155  /**
156   * Default sub-characteristics of technical debt model. See http://www.sqale.org
157   * @deprecated in 5.5. SQALE Quality Model is replaced by SonarQube Quality Model.
158   *             See https://jira.sonarsource.com/browse/MMF-184
159   */
160  @Deprecated
161  final class SubCharacteristics {
162    /**
163     * Related to characteristic REUSABILITY
164     */
165    public static final String MODULARITY = "MODULARITY";
166
167    /**
168     * Related to characteristic REUSABILITY
169     */
170    public static final String TRANSPORTABILITY = "TRANSPORTABILITY";
171
172    /**
173     * Related to characteristic PORTABILITY
174     */
175    public static final String COMPILER_RELATED_PORTABILITY = "COMPILER_RELATED_PORTABILITY";
176
177    /**
178     * Related to characteristic PORTABILITY
179     */
180    public static final String HARDWARE_RELATED_PORTABILITY = "HARDWARE_RELATED_PORTABILITY";
181
182    /**
183     * Related to characteristic PORTABILITY
184     */
185    public static final String LANGUAGE_RELATED_PORTABILITY = "LANGUAGE_RELATED_PORTABILITY";
186
187    /**
188     * Related to characteristic PORTABILITY
189     */
190    public static final String OS_RELATED_PORTABILITY = "OS_RELATED_PORTABILITY";
191
192    /**
193     * Related to characteristic PORTABILITY
194     */
195    public static final String SOFTWARE_RELATED_PORTABILITY = "SOFTWARE_RELATED_PORTABILITY";
196
197    /**
198     * Related to characteristic PORTABILITY
199     */
200    public static final String TIME_ZONE_RELATED_PORTABILITY = "TIME_ZONE_RELATED_PORTABILITY";
201
202    /**
203     * Related to characteristic MAINTAINABILITY
204     */
205    public static final String READABILITY = "READABILITY";
206
207    /**
208     * Related to characteristic MAINTAINABILITY
209     */
210    public static final String UNDERSTANDABILITY = "UNDERSTANDABILITY";
211
212    /**
213     * Related to characteristic SECURITY
214     */
215    public static final String API_ABUSE = "API_ABUSE";
216
217    /**
218     * Related to characteristic SECURITY
219     */
220    public static final String ERRORS = "ERRORS";
221
222    /**
223     * Related to characteristic SECURITY
224     */
225    public static final String INPUT_VALIDATION_AND_REPRESENTATION = "INPUT_VALIDATION_AND_REPRESENTATION";
226
227    /**
228     * Related to characteristic SECURITY
229     */
230    public static final String SECURITY_FEATURES = "SECURITY_FEATURES";
231
232    /**
233     * Related to characteristic EFFICIENCY
234     */
235    public static final String CPU_EFFICIENCY = "CPU_EFFICIENCY";
236
237    /**
238     * Related to characteristic EFFICIENCY
239     */
240    public static final String MEMORY_EFFICIENCY = "MEMORY_EFFICIENCY";
241
242    /**
243     * Related to characteristic EFFICIENCY
244     */
245    public static final String NETWORK_USE = "NETWORK_USE";
246
247    /**
248     * Related to characteristic CHANGEABILITY
249     */
250    public static final String ARCHITECTURE_CHANGEABILITY = "ARCHITECTURE_CHANGEABILITY";
251
252    /**
253     * Related to characteristic CHANGEABILITY
254     */
255    public static final String DATA_CHANGEABILITY = "DATA_CHANGEABILITY";
256
257    /**
258     * Related to characteristic CHANGEABILITY
259     */
260    public static final String LOGIC_CHANGEABILITY = "LOGIC_CHANGEABILITY";
261
262    /**
263     * Related to characteristic RELIABILITY
264     */
265    public static final String ARCHITECTURE_RELIABILITY = "ARCHITECTURE_RELIABILITY";
266
267    /**
268     * Related to characteristic RELIABILITY
269     */
270    public static final String DATA_RELIABILITY = "DATA_RELIABILITY";
271
272    /**
273     * Related to characteristic RELIABILITY
274     */
275    public static final String EXCEPTION_HANDLING = "EXCEPTION_HANDLING";
276
277    /**
278     * Related to characteristic RELIABILITY
279     */
280    public static final String FAULT_TOLERANCE = "FAULT_TOLERANCE";
281
282    /**
283     * Related to characteristic RELIABILITY
284     */
285    public static final String INSTRUCTION_RELIABILITY = "INSTRUCTION_RELIABILITY";
286
287    /**
288     * Related to characteristic RELIABILITY
289     */
290    public static final String LOGIC_RELIABILITY = "LOGIC_RELIABILITY";
291
292    /**
293     * Related to characteristic RELIABILITY
294     */
295    public static final String RESOURCE_RELIABILITY = "RESOURCE_RELIABILITY";
296
297    /**
298     * Related to characteristic RELIABILITY
299     */
300    public static final String SYNCHRONIZATION_RELIABILITY = "SYNCHRONIZATION_RELIABILITY";
301
302    /**
303     * Related to characteristic RELIABILITY
304     */
305    public static final String UNIT_TESTS = "UNIT_TESTS";
306
307    /**
308     * Related to characteristic TESTABILITY
309     */
310    public static final String INTEGRATION_TESTABILITY = "INTEGRATION_TESTABILITY";
311
312    /**
313     * Related to characteristic TESTABILITY
314     */
315    public static final String UNIT_TESTABILITY = "UNIT_TESTABILITY";
316
317    /**
318     * Related to characteristic ACCESSIBILITY
319     */
320    public static final String USABILITY_ACCESSIBILITY = "USABILITY_ACCESSIBILITY";
321
322    /**
323     * Related to characteristic ACCESSIBILITY
324     */
325    public static final String USABILITY_COMPLIANCE = "USABILITY_COMPLIANCE";
326
327    /**
328     * Related to characteristic ACCESSIBILITY
329     */
330    public static final String USABILITY_EASE_OF_USE = "USABILITY_EASE_OF_USE";
331
332    /**
333     * Related to characteristic REUSABILITY
334     */
335    public static final String REUSABILITY_COMPLIANCE = "REUSABILITY_COMPLIANCE";
336
337    /**
338     * Related to characteristic PORTABILITY
339     */
340    public static final String PORTABILITY_COMPLIANCE = "PORTABILITY_COMPLIANCE";
341
342    /**
343     * Related to characteristic MAINTAINABILITY
344     */
345    public static final String MAINTAINABILITY_COMPLIANCE = "MAINTAINABILITY_COMPLIANCE";
346
347    /**
348     * Related to characteristic SECURITY
349     */
350    public static final String SECURITY_COMPLIANCE = "SECURITY_COMPLIANCE";
351
352    /**
353     * Related to characteristic EFFICIENCY
354     */
355    public static final String EFFICIENCY_COMPLIANCE = "EFFICIENCY_COMPLIANCE";
356
357    /**
358     * Related to characteristic CHANGEABILITY
359     */
360    public static final String CHANGEABILITY_COMPLIANCE = "CHANGEABILITY_COMPLIANCE";
361
362    /**
363     * Related to characteristic RELIABILITY
364     */
365    public static final String RELIABILITY_COMPLIANCE = "RELIABILITY_COMPLIANCE";
366
367    /**
368     * Related to characteristic TESTABILITY
369     */
370    public static final String TESTABILITY_COMPLIANCE = "TESTABILITY_COMPLIANCE";
371
372    private SubCharacteristics() {
373      // only constants
374    }
375  }
376
377  /**
378   * Instantiated by core but not by plugins, except for their tests.
379   */
380  class Context {
381    private final Map<String, Repository> repositoriesByKey = Maps.newHashMap();
382
383    /**
384     * New builder for {@link org.sonar.api.server.rule.RulesDefinition.Repository}.
385     * <br>
386     * A plugin can add rules to a repository that is defined then executed by another plugin. For instance
387     * the FbContrib plugin contributes to the Findbugs plugin rules. In this case no need
388     * to execute {@link org.sonar.api.server.rule.RulesDefinition.NewRepository#setName(String)}
389     */
390    public NewRepository createRepository(String key, String language) {
391      return new NewRepositoryImpl(this, key, language);
392    }
393
394    /**
395     * @deprecated since 5.2. Simply use {@link #createRepository(String, String)}
396     */
397    @Deprecated
398    public NewRepository extendRepository(String key, String language) {
399      return createRepository(key, language);
400    }
401
402    @CheckForNull
403    public Repository repository(String key) {
404      return repositoriesByKey.get(key);
405    }
406
407    public List<Repository> repositories() {
408      return ImmutableList.copyOf(repositoriesByKey.values());
409    }
410
411    /**
412     * @deprecated returns empty list since 5.2. Concept of "extended repository" was misleading and not valuable. Simply declare
413     * repositories and use {@link #repositories()}. See http://jira.sonarsource.com/browse/SONAR-6709
414     */
415    @Deprecated
416    public List<ExtendedRepository> extendedRepositories(String repositoryKey) {
417      return Collections.emptyList();
418    }
419
420    /**
421     * @deprecated returns empty list since 5.2. Concept of "extended repository" was misleading and not valuable. Simply declare
422     * repositories and use {@link #repositories()}. See http://jira.sonarsource.com/browse/SONAR-6709
423     */
424    @Deprecated
425    public List<ExtendedRepository> extendedRepositories() {
426      return Collections.emptyList();
427    }
428
429    private void registerRepository(NewRepositoryImpl newRepository) {
430      Repository existing = repositoriesByKey.get(newRepository.key());
431      if (existing != null) {
432        checkState(existing.language().equals(newRepository.language),
433          "The rule repository '%s' must not be defined for two different languages: %s and %s",
434          newRepository.key, existing.language(), newRepository.language);
435      }
436      repositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository, existing));
437    }
438  }
439
440  interface NewExtendedRepository {
441    /**
442     * Create a rule with specified key. Max length of key is 200 characters. Key must be unique
443     * among the repository
444     * @throws IllegalArgumentException is key is not unique. Note a warning was logged up to version 5.4 (included)
445     */
446    NewRule createRule(String ruleKey);
447
448    @CheckForNull
449    NewRule rule(String ruleKey);
450
451    Collection<NewRule> rules();
452
453    String key();
454
455    void done();
456  }
457
458  interface NewRepository extends NewExtendedRepository {
459    NewRepository setName(String s);
460  }
461
462  class NewRepositoryImpl implements NewRepository {
463    private final Context context;
464    private final String key;
465    private String language;
466    private String name;
467    private final Map<String, NewRule> newRules = Maps.newHashMap();
468
469    private NewRepositoryImpl(Context context, String key, String language) {
470      this.context = context;
471      this.key = this.name = key;
472      this.language = language;
473    }
474
475    @Override
476    public String key() {
477      return key;
478    }
479
480    @Override
481    public NewRepositoryImpl setName(@Nullable String s) {
482      if (StringUtils.isNotEmpty(s)) {
483        this.name = s;
484      }
485      return this;
486    }
487
488    @Override
489    public NewRule createRule(String ruleKey) {
490      checkArgument(!newRules.containsKey(ruleKey), "The rule '%s' of repository '%s' is declared several times", ruleKey, key);
491      NewRule newRule = new NewRule(key, ruleKey);
492      newRules.put(ruleKey, newRule);
493      return newRule;
494    }
495
496    @CheckForNull
497    @Override
498    public NewRule rule(String ruleKey) {
499      return newRules.get(ruleKey);
500    }
501
502    @Override
503    public Collection<NewRule> rules() {
504      return newRules.values();
505    }
506
507    @Override
508    public void done() {
509      // note that some validations can be done here, for example for
510      // verifying that at least one rule is declared
511
512      context.registerRepository(this);
513    }
514
515    @Override
516    public String toString() {
517      return Objects.toStringHelper(this)
518        .add("key", key)
519        .add("language", language)
520        .toString();
521    }
522  }
523
524  interface ExtendedRepository {
525    String key();
526
527    String language();
528
529    @CheckForNull
530    Rule rule(String ruleKey);
531
532    List<Rule> rules();
533  }
534
535  interface Repository extends ExtendedRepository {
536    String name();
537  }
538
539  @Immutable
540  class RepositoryImpl implements Repository {
541    private final String key;
542    private final String language;
543    private final String name;
544    private final Map<String, Rule> rulesByKey;
545
546    private RepositoryImpl(NewRepositoryImpl newRepository, @Nullable Repository mergeInto) {
547      this.key = newRepository.key;
548      this.language = newRepository.language;
549
550      Map<String, Rule> ruleBuilder = new HashMap<>();
551      if (mergeInto != null) {
552        if (!StringUtils.equals(newRepository.language, mergeInto.language()) || !StringUtils.equals(newRepository.key, mergeInto.key())) {
553          throw new IllegalArgumentException(format("Bug - language and key of the repositories to be merged should be the sames: %s and %s", newRepository, mergeInto));
554        }
555        this.name = StringUtils.defaultIfBlank(mergeInto.name(), newRepository.name);
556        for (Rule rule : mergeInto.rules()) {
557          if (!newRepository.key().startsWith("common-") && ruleBuilder.containsKey(rule.key())) {
558            Loggers.get(getClass()).warn("The rule '{}' of repository '{}' is declared several times", rule.key(), mergeInto.key());
559          }
560          ruleBuilder.put(rule.key(), rule);
561        }
562      } else {
563        this.name = newRepository.name;
564      }
565      for (NewRule newRule : newRepository.newRules.values()) {
566        newRule.validate();
567        ruleBuilder.put(newRule.key, new Rule(this, newRule));
568      }
569      this.rulesByKey = ImmutableMap.copyOf(ruleBuilder);
570    }
571
572    @Override
573    public String key() {
574      return key;
575    }
576
577    @Override
578    public String language() {
579      return language;
580    }
581
582    @Override
583    public String name() {
584      return name;
585    }
586
587    @Override
588    @CheckForNull
589    public Rule rule(String ruleKey) {
590      return rulesByKey.get(ruleKey);
591    }
592
593    @Override
594    public List<Rule> rules() {
595      return ImmutableList.copyOf(rulesByKey.values());
596    }
597
598    @Override
599    public boolean equals(Object o) {
600      if (this == o) {
601        return true;
602      }
603      if (o == null || getClass() != o.getClass()) {
604        return false;
605      }
606      RepositoryImpl that = (RepositoryImpl) o;
607      return key.equals(that.key);
608    }
609
610    @Override
611    public int hashCode() {
612      return key.hashCode();
613    }
614
615    @Override
616    public String toString() {
617      return Objects.toStringHelper(this)
618        .add("language", language)
619        .add("key", key)
620        .toString();
621    }
622  }
623
624  /**
625   * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}.
626   */
627  interface DebtRemediationFunctions {
628
629    /**
630     * Shortcut for {@code create(Type.LINEAR, gap multiplier, null)}.
631     * @param gapMultiplier the duration to fix one issue. See {@link DebtRemediationFunction} for details about format.
632     * @see org.sonar.api.server.debt.DebtRemediationFunction.Type#LINEAR
633     */
634    DebtRemediationFunction linear(String gapMultiplier);
635
636    /**
637     * Shortcut for {@code create(Type.LINEAR_OFFSET, gap multiplier, base effort)}.
638     * @param gapMultiplier duration to fix one point of complexity. See {@link DebtRemediationFunction} for details and format.
639     * @param baseEffort duration to make basic analysis. See {@link DebtRemediationFunction} for details and format.
640     * @see org.sonar.api.server.debt.DebtRemediationFunction.Type#LINEAR_OFFSET
641     */
642    DebtRemediationFunction linearWithOffset(String gapMultiplier, String baseEffort);
643
644    /**
645     * Shortcut for {@code create(Type.CONSTANT_ISSUE, null, base effort)}.
646     * @param baseEffort cost per issue. See {@link DebtRemediationFunction} for details and format.
647     * @see org.sonar.api.server.debt.DebtRemediationFunction.Type#CONSTANT_ISSUE
648     */
649    DebtRemediationFunction constantPerIssue(String baseEffort);
650
651    /**
652     * Flexible way to create a {@link DebtRemediationFunction}. An unchecked exception is thrown if
653     * coefficient and/or offset are not valid according to the given @{code type}.
654     * @since 5.3
655     */
656    DebtRemediationFunction create(DebtRemediationFunction.Type type, @Nullable String gapMultiplier, @Nullable String baseEffort);
657  }
658
659  class NewRule {
660    private final String repoKey;
661    private final String key;
662    private RuleType type;
663    private String name;
664    private String htmlDescription;
665    private String markdownDescription;
666    private String internalKey;
667    private String severity = Severity.MAJOR;
668    private boolean template;
669    private RuleStatus status = RuleStatus.defaultStatus();
670    private DebtRemediationFunction debtRemediationFunction;
671    private String gapDescription;
672    private final Set<String> tags = Sets.newTreeSet();
673    private final Map<String, NewParam> paramsByKey = Maps.newHashMap();
674    private final DebtRemediationFunctions functions;
675
676    private NewRule(String repoKey, String key) {
677      this.repoKey = repoKey;
678      this.key = key;
679      this.functions = new DefaultDebtRemediationFunctions(repoKey, key);
680    }
681
682    public String key() {
683      return this.key;
684    }
685
686    /**
687     * Required rule name
688     */
689    public NewRule setName(String s) {
690      this.name = trimToNull(s);
691      return this;
692    }
693
694    public NewRule setTemplate(boolean template) {
695      this.template = template;
696      return this;
697    }
698
699    public NewRule setSeverity(String s) {
700      checkArgument(Severity.ALL.contains(s), "Severity of rule %s is not correct: %s", this, s);
701      this.severity = s;
702      return this;
703    }
704
705    /**
706     * The type as defined by the SonarQube Quality Model.
707     * <br>
708     * When a plugin does not define rule type, then it is deduced from
709     * tags:
710     * <ul>
711     * <li>if the rule has the "bug" tag then type is {@link RuleType#BUG}</li>
712     * <li>if the rule has the "security" tag then type is {@link RuleType#VULNERABILITY}</li>
713     * <li>if the rule has both tags "bug" and "security", then type is {@link RuleType#BUG}</li>
714     * <li>default type is {@link RuleType#CODE_SMELL}</li>
715     * </ul>
716     * Finally the "bug" and "security" tags are considered as reserved. They
717     * are silently removed from the final state of definition.
718     * @since 5.5
719     */
720    public NewRule setType(RuleType t) {
721      this.type = t;
722      return this;
723    }
724
725    /**
726     * The optional description, in HTML format, has no max length. It's exclusive with markdown description
727     * (see {@link #setMarkdownDescription(String)})
728     */
729    public NewRule setHtmlDescription(@Nullable String s) {
730      checkState(markdownDescription == null, "Rule '%s' already has a Markdown description", this);
731      this.htmlDescription = trimToNull(s);
732      return this;
733    }
734
735    /**
736     * Load description from a file available in classpath. Example : <code>setHtmlDescription(getClass().getResource("/myrepo/Rule1234.html")</code>
737     */
738    public NewRule setHtmlDescription(@Nullable URL classpathUrl) {
739      if (classpathUrl != null) {
740        try {
741          setHtmlDescription(IOUtils.toString(classpathUrl));
742        } catch (IOException e) {
743          throw new IllegalStateException("Fail to read: " + classpathUrl, e);
744        }
745      } else {
746        this.htmlDescription = null;
747      }
748      return this;
749    }
750
751    /**
752     * The optional description, in a restricted Markdown format, has no max length. It's exclusive with HTML description
753     * (see {@link #setHtmlDescription(String)})
754     */
755    public NewRule setMarkdownDescription(@Nullable String s) {
756      checkState(htmlDescription == null, "Rule '%s' already has an HTML description", this);
757      this.markdownDescription = trimToNull(s);
758      return this;
759    }
760
761    /**
762     * Load description from a file available in classpath. Example : <code>setMarkdownDescription(getClass().getResource("/myrepo/Rule1234.md")</code>
763     */
764    public NewRule setMarkdownDescription(@Nullable URL classpathUrl) {
765      if (classpathUrl != null) {
766        try {
767          setMarkdownDescription(IOUtils.toString(classpathUrl));
768        } catch (IOException e) {
769          throw new IllegalStateException("Fail to read: " + classpathUrl, e);
770        }
771      } else {
772        this.markdownDescription = null;
773      }
774      return this;
775    }
776
777    /**
778     * Default value is {@link org.sonar.api.rule.RuleStatus#READY}. The value
779     * {@link org.sonar.api.rule.RuleStatus#REMOVED} is not accepted and raises an
780     * {@link java.lang.IllegalArgumentException}.
781     */
782    public NewRule setStatus(RuleStatus status) {
783      checkArgument(RuleStatus.REMOVED != status, "Status 'REMOVED' is not accepted on rule '%s'", this);
784      this.status = status;
785      return this;
786    }
787
788    /**
789     * SQALE sub-characteristic. See http://www.sqale.org
790     *
791     * @see org.sonar.api.server.rule.RulesDefinition.SubCharacteristics for constant values
792     * @deprecated in 5.5. SQALE Quality Model is replaced by SonarQube Quality Model. This method does nothing.
793     *             See https://jira.sonarsource.com/browse/MMF-184
794     * @see #setType(RuleType)
795     */
796    public NewRule setDebtSubCharacteristic(@Nullable String s) {
797      return this;
798    }
799
800    /**
801     * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}
802     */
803    public DebtRemediationFunctions debtRemediationFunctions() {
804      return functions;
805    }
806
807    /**
808     * @see #debtRemediationFunctions()
809     */
810    public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn) {
811      this.debtRemediationFunction = fn;
812      return this;
813    }
814
815    /**
816     * @deprecated since 5.5, replaced by {@link #setGapDescription(String)}
817     */
818    @Deprecated
819    public NewRule setEffortToFixDescription(@Nullable String s) {
820      return setGapDescription(s);
821    }
822
823    /**
824     * For rules that use LINEAR or LINEAR_OFFSET remediation functions, the meaning
825     * of the function parameter (= "gap") must be set. This description
826     * explains what 1 point of "gap" represents for the rule.
827     * <br>
828     * Example: for the "Insufficient condition coverage", this description for the
829     * remediation function gap multiplier/base effort would be something like
830     * "Effort to test one uncovered condition".
831     */
832    public NewRule setGapDescription(@Nullable String s) {
833      this.gapDescription = s;
834      return this;
835    }
836
837    /**
838     * Create a parameter with given unique key. Max length of key is 128 characters.
839     */
840    public NewParam createParam(String paramKey) {
841      checkArgument(!paramsByKey.containsKey(paramKey), "The parameter '%s' is declared several times on the rule %s", paramKey, this);
842      NewParam param = new NewParam(paramKey);
843      paramsByKey.put(paramKey, param);
844      return param;
845    }
846
847    @CheckForNull
848    public NewParam param(String paramKey) {
849      return paramsByKey.get(paramKey);
850    }
851
852    public Collection<NewParam> params() {
853      return paramsByKey.values();
854    }
855
856    /**
857     * @see RuleTagFormat
858     */
859    public NewRule addTags(String... list) {
860      for (String tag : list) {
861        RuleTagFormat.validate(tag);
862        tags.add(tag);
863      }
864      return this;
865    }
866
867    /**
868     * @see RuleTagFormat
869     */
870    public NewRule setTags(String... list) {
871      tags.clear();
872      addTags(list);
873      return this;
874    }
875
876    /**
877     * Optional key that can be used by the rule engine. Not displayed
878     * in webapp. For example the Java Checkstyle plugin feeds this field
879     * with the internal path ("Checker/TreeWalker/AnnotationUseStyle").
880     */
881    public NewRule setInternalKey(@Nullable String s) {
882      this.internalKey = s;
883      return this;
884    }
885
886    private void validate() {
887      if (Strings.isNullOrEmpty(name)) {
888        throw new IllegalStateException(format("Name of rule %s is empty", this));
889      }
890      if (Strings.isNullOrEmpty(htmlDescription) && Strings.isNullOrEmpty(markdownDescription)) {
891        throw new IllegalStateException(format("One of HTML description or Markdown description must be defined for rule %s", this));
892      }
893    }
894
895    @Override
896    public String toString() {
897      return format("[repository=%s, key=%s]", repoKey, key);
898    }
899  }
900
901  @Immutable
902  class Rule {
903    private final Repository repository;
904    private final String repoKey;
905    private final String key;
906    private final String name;
907    private final RuleType type;
908    private final String htmlDescription;
909    private final String markdownDescription;
910    private final String internalKey;
911    private final String severity;
912    private final boolean template;
913    private final DebtRemediationFunction debtRemediationFunction;
914    private final String gapDescription;
915    private final Set<String> tags;
916    private final Map<String, Param> params;
917    private final RuleStatus status;
918
919    private Rule(Repository repository, NewRule newRule) {
920      this.repository = repository;
921      this.repoKey = newRule.repoKey;
922      this.key = newRule.key;
923      this.name = newRule.name;
924      this.htmlDescription = newRule.htmlDescription;
925      this.markdownDescription = newRule.markdownDescription;
926      this.internalKey = newRule.internalKey;
927      this.severity = newRule.severity;
928      this.template = newRule.template;
929      this.status = newRule.status;
930      this.debtRemediationFunction = newRule.debtRemediationFunction;
931      this.gapDescription = newRule.gapDescription;
932      this.type = newRule.type == null ? RuleTagsToTypeConverter.convert(newRule.tags) : newRule.type;
933      this.tags = ImmutableSortedSet.copyOf(Sets.difference(newRule.tags, RuleTagsToTypeConverter.RESERVED_TAGS));
934      ImmutableMap.Builder<String, Param> paramsBuilder = ImmutableMap.builder();
935      for (NewParam newParam : newRule.paramsByKey.values()) {
936        paramsBuilder.put(newParam.key, new Param(newParam));
937      }
938      this.params = paramsBuilder.build();
939    }
940
941    public Repository repository() {
942      return repository;
943    }
944
945    public String key() {
946      return key;
947    }
948
949    public String name() {
950      return name;
951    }
952
953    /**
954     * @since 5.5
955     * @see NewRule#setType(RuleType)
956     */
957    public RuleType type() {
958      return type;
959    }
960
961    public String severity() {
962      return severity;
963    }
964
965    @CheckForNull
966    public String htmlDescription() {
967      return htmlDescription;
968    }
969
970    @CheckForNull
971    public String markdownDescription() {
972      return markdownDescription;
973    }
974
975    public boolean template() {
976      return template;
977    }
978
979    public RuleStatus status() {
980      return status;
981    }
982
983    /**
984     * @deprecated in 5.5. SQALE Quality Model is replaced by SonarQube Quality Model. {@code null} is
985     * always returned. See https://jira.sonarsource.com/browse/MMF-184
986     * @see #type()
987     */
988    @CheckForNull
989    @Deprecated
990    public String debtSubCharacteristic() {
991      return null;
992    }
993
994    @CheckForNull
995    public DebtRemediationFunction debtRemediationFunction() {
996      return debtRemediationFunction;
997    }
998
999    /**
1000     * @deprecated since 5.5, replaced by {@link #gapDescription()}
1001     */
1002    @Deprecated
1003    @CheckForNull
1004    public String effortToFixDescription() {
1005      return gapDescription();
1006    }
1007
1008    @CheckForNull
1009    public String gapDescription() {
1010      return gapDescription;
1011    }
1012
1013    @CheckForNull
1014    public Param param(String key) {
1015      return params.get(key);
1016    }
1017
1018    public List<Param> params() {
1019      return ImmutableList.copyOf(params.values());
1020    }
1021
1022    public Set<String> tags() {
1023      return tags;
1024    }
1025
1026    /**
1027     * @see RulesDefinition.NewRule#setInternalKey(String)
1028     */
1029    @CheckForNull
1030    public String internalKey() {
1031      return internalKey;
1032    }
1033
1034    @Override
1035    public boolean equals(Object o) {
1036      if (this == o) {
1037        return true;
1038      }
1039      if (o == null || getClass() != o.getClass()) {
1040        return false;
1041      }
1042      Rule other = (Rule) o;
1043      return key.equals(other.key) && repoKey.equals(other.repoKey);
1044    }
1045
1046    @Override
1047    public int hashCode() {
1048      int result = repoKey.hashCode();
1049      result = 31 * result + key.hashCode();
1050      return result;
1051    }
1052
1053    @Override
1054    public String toString() {
1055      return format("[repository=%s, key=%s]", repoKey, key);
1056    }
1057  }
1058
1059  class NewParam {
1060    private final String key;
1061    private String name;
1062    private String description;
1063    private String defaultValue;
1064    private RuleParamType type = RuleParamType.STRING;
1065
1066    private NewParam(String key) {
1067      this.key = this.name = key;
1068    }
1069
1070    public String key() {
1071      return key;
1072    }
1073
1074    public NewParam setName(@Nullable String s) {
1075      // name must never be null.
1076      this.name = StringUtils.defaultIfBlank(s, key);
1077      return this;
1078    }
1079
1080    public NewParam setType(RuleParamType t) {
1081      this.type = t;
1082      return this;
1083    }
1084
1085    /**
1086     * Plain-text description. Can be null. Max length is 4000 characters.
1087     */
1088    public NewParam setDescription(@Nullable String s) {
1089      this.description = StringUtils.defaultIfBlank(s, null);
1090      return this;
1091    }
1092
1093    /**
1094     * Empty default value will be converted to null. Max length is 4000 characters.
1095     */
1096    public NewParam setDefaultValue(@Nullable String s) {
1097      this.defaultValue = Strings.emptyToNull(s);
1098      return this;
1099    }
1100  }
1101
1102  @Immutable
1103  class Param {
1104    private final String key;
1105    private final String name;
1106    private final String description;
1107    private final String defaultValue;
1108    private final RuleParamType type;
1109
1110    private Param(NewParam newParam) {
1111      this.key = newParam.key;
1112      this.name = newParam.name;
1113      this.description = newParam.description;
1114      this.defaultValue = newParam.defaultValue;
1115      this.type = newParam.type;
1116    }
1117
1118    public String key() {
1119      return key;
1120    }
1121
1122    public String name() {
1123      return name;
1124    }
1125
1126    @Nullable
1127    public String description() {
1128      return description;
1129    }
1130
1131    @Nullable
1132    public String defaultValue() {
1133      return defaultValue;
1134    }
1135
1136    public RuleParamType type() {
1137      return type;
1138    }
1139
1140    @Override
1141    public boolean equals(Object o) {
1142      if (this == o) {
1143        return true;
1144      }
1145      if (o == null || getClass() != o.getClass()) {
1146        return false;
1147      }
1148      Param that = (Param) o;
1149      return key.equals(that.key);
1150    }
1151
1152    @Override
1153    public int hashCode() {
1154      return key.hashCode();
1155    }
1156  }
1157
1158  /**
1159   * This method is executed when server is started.
1160   */
1161  void define(Context context);
1162
1163}