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