001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube 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 * SonarQube 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 */
020 package org.sonar.api.server.rule;
021
022 import com.google.common.base.Strings;
023 import com.google.common.collect.*;
024 import org.apache.commons.io.IOUtils;
025 import org.apache.commons.lang.StringUtils;
026 import org.slf4j.LoggerFactory;
027 import org.sonar.api.ServerExtension;
028 import org.sonar.api.rule.RuleStatus;
029 import org.sonar.api.rule.Severity;
030 import org.sonar.api.server.debt.DebtRemediationFunction;
031
032 import javax.annotation.CheckForNull;
033 import javax.annotation.Nullable;
034 import javax.annotation.concurrent.Immutable;
035
036 import java.io.IOException;
037 import java.net.URL;
038 import java.util.Collection;
039 import java.util.List;
040 import java.util.Map;
041 import java.util.Set;
042
043 /**
044 * Defines some coding rules of the same repository. For example the Java Findbugs plugin provides an implementation of
045 * this extension point in order to define the rules that it supports.
046 * <p/>
047 * This interface replaces the deprecated class org.sonar.api.rules.RuleRepository.
048 * <p/>
049 * <h3>How to use</h3>
050 * <pre>
051 * public class MyJsRulesDefinition implements RulesDefinition {
052 *
053 * {@literal @}Override
054 * public void define(Context context) {
055 * NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
056 *
057 * // define a rule programmatically. Note that rules
058 * // could be loaded from files (JSON, XML, ...)
059 * NewRule x1Rule = repository.createRule("x1")
060 * .setName("No empty line")
061 * .setHtmlDescription("Generate an issue on empty lines")
062 *
063 * // optional tags
064 * .setTags("style", "stupid")
065 *
066 * // optional status. Default value is READY.
067 * .setStatus(RuleStatus.BETA)
068 *
069 * // default severity when the rule is activated on a Quality profile. Default value is MAJOR.
070 * .setSeverity(Severity.MINOR);
071 *
072 * x1Rule
073 * .setDebtSubCharacteristic("INTEGRATION_TESTABILITY")
074 * .setDebtRemediationFunction(x1Rule.debtRemediationFunctions().linearWithOffset("1h", "30min"));
075 *
076 * x1Rule.createParam("acceptWhitespace")
077 * .setDefaultValue("false")
078 * .setType(RuleParamType.BOOLEAN)
079 * .setDescription("Accept whitespaces on the line");
080 *
081 * // don't forget to call done() to finalize the definition
082 * repository.done();
083 * }
084 * }
085 * </pre>
086 * <p/>
087 * If rules are declared in a XML file with the standard SonarQube format (see
088 * {@link org.sonar.api.server.rule.RulesDefinitionXmlLoader}), then it can be loaded by using :
089 * <p/>
090 * <pre>
091 * public class MyJsRulesDefinition implements RulesDefinition {
092 *
093 * private final RulesDefinitionXmlLoader xmlLoader;
094 *
095 * public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader) {
096 * this.xmlLoader = xmlLoader;
097 * }
098 *
099 * {@literal @}Override
100 * public void define(Context context) {
101 * NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
102 * // see javadoc of RulesDefinitionXmlLoader for the format
103 * xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"));
104 * repository.done();
105 * }
106 * }
107 * </pre>
108 * <p/>
109 * In the above example, XML file must contain name and description of each rule. If it's not the case, then the
110 * (deprecated) English bundles can be used :
111 * <p/>
112 * <pre>
113 * public class MyJsRulesDefinition implements RulesDefinition {
114 *
115 * private final RulesDefinitionXmlLoader xmlLoader;
116 * private final RulesDefinitionI18nLoader i18nLoader;
117 *
118 * public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader, RulesDefinitionI18nLoader i18nLoader) {
119 * this.xmlLoader = xmlLoader;
120 * this.i18nLoader = i18nLoader;
121 * }
122 *
123 * {@literal @}Override
124 * public void define(Context context) {
125 * NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
126 * xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"), "UTF-8");
127 * i18nLoader.load(repository);
128 * repository.done();
129 * }
130 * }
131 * </pre>
132 *
133 * @since 4.3
134 */
135 public interface RulesDefinition extends ServerExtension {
136
137 /**
138 * Default sub-characteristics of technical debt model. See http://www.sqale.org
139 */
140 final class SubCharacteristics {
141 /**
142 * Related to characteristic REUSABILITY
143 */
144 public static final String MODULARITY = "MODULARITY";
145
146 /**
147 * Related to characteristic REUSABILITY
148 */
149 public static final String TRANSPORTABILITY = "TRANSPORTABILITY";
150
151 /**
152 * Related to characteristic PORTABILITY
153 */
154 public static final String COMPILER_RELATED_PORTABILITY = "COMPILER_RELATED_PORTABILITY";
155
156 /**
157 * Related to characteristic PORTABILITY
158 */
159 public static final String HARDWARE_RELATED_PORTABILITY = "HARDWARE_RELATED_PORTABILITY";
160
161 /**
162 * Related to characteristic PORTABILITY
163 */
164 public static final String LANGUAGE_RELATED_PORTABILITY = "LANGUAGE_RELATED_PORTABILITY";
165
166 /**
167 * Related to characteristic PORTABILITY
168 */
169 public static final String OS_RELATED_PORTABILITY = "OS_RELATED_PORTABILITY";
170
171 /**
172 * Related to characteristic PORTABILITY
173 */
174 public static final String SOFTWARE_RELATED_PORTABILITY = "SOFTWARE_RELATED_PORTABILITY";
175
176 /**
177 * Related to characteristic PORTABILITY
178 */
179 public static final String TIME_ZONE_RELATED_PORTABILITY = "TIME_ZONE_RELATED_PORTABILITY";
180
181 /**
182 * Related to characteristic MAINTAINABILITY
183 */
184 public static final String READABILITY = "READABILITY";
185
186 /**
187 * Related to characteristic MAINTAINABILITY
188 */
189 public static final String UNDERSTANDABILITY = "UNDERSTANDABILITY";
190
191 /**
192 * Related to characteristic SECURITY
193 */
194 public static final String API_ABUSE = "API_ABUSE";
195
196 /**
197 * Related to characteristic SECURITY
198 */
199 public static final String ERRORS = "ERRORS";
200
201 /**
202 * Related to characteristic SECURITY
203 */
204 public static final String INPUT_VALIDATION_AND_REPRESENTATION = "INPUT_VALIDATION_AND_REPRESENTATION";
205
206 /**
207 * Related to characteristic SECURITY
208 */
209 public static final String SECURITY_FEATURES = "SECURITY_FEATURES";
210
211 /**
212 * Related to characteristic EFFICIENCY
213 */
214 public static final String CPU_EFFICIENCY = "CPU_EFFICIENCY";
215
216 /**
217 * Related to characteristic EFFICIENCY
218 */
219 public static final String MEMORY_EFFICIENCY = "MEMORY_EFFICIENCY";
220
221 /**
222 * Related to characteristic EFFICIENCY
223 */
224 public static final String NETWORK_USE = "NETWORK_USE";
225
226 /**
227 * Related to characteristic CHANGEABILITY
228 */
229 public static final String ARCHITECTURE_CHANGEABILITY = "ARCHITECTURE_CHANGEABILITY";
230
231 /**
232 * Related to characteristic CHANGEABILITY
233 */
234 public static final String DATA_CHANGEABILITY = "DATA_CHANGEABILITY";
235
236 /**
237 * Related to characteristic CHANGEABILITY
238 */
239 public static final String LOGIC_CHANGEABILITY = "LOGIC_CHANGEABILITY";
240
241 /**
242 * Related to characteristic RELIABILITY
243 */
244 public static final String ARCHITECTURE_RELIABILITY = "ARCHITECTURE_RELIABILITY";
245
246 /**
247 * Related to characteristic RELIABILITY
248 */
249 public static final String DATA_RELIABILITY = "DATA_RELIABILITY";
250
251 /**
252 * Related to characteristic RELIABILITY
253 */
254 public static final String EXCEPTION_HANDLING = "EXCEPTION_HANDLING";
255
256 /**
257 * Related to characteristic RELIABILITY
258 */
259 public static final String FAULT_TOLERANCE = "FAULT_TOLERANCE";
260
261 /**
262 * Related to characteristic RELIABILITY
263 */
264 public static final String INSTRUCTION_RELIABILITY = "INSTRUCTION_RELIABILITY";
265
266 /**
267 * Related to characteristic RELIABILITY
268 */
269 public static final String LOGIC_RELIABILITY = "LOGIC_RELIABILITY";
270
271 /**
272 * Related to characteristic RELIABILITY
273 */
274 public static final String RESOURCE_RELIABILITY = "RESOURCE_RELIABILITY";
275
276 /**
277 * Related to characteristic RELIABILITY
278 */
279 public static final String SYNCHRONIZATION_RELIABILITY = "SYNCHRONIZATION_RELIABILITY";
280
281 /**
282 * Related to characteristic RELIABILITY
283 */
284 public static final String UNIT_TESTS = "UNIT_TESTS";
285
286 /**
287 * Related to characteristic TESTABILITY
288 */
289 public static final String INTEGRATION_TESTABILITY = "INTEGRATION_TESTABILITY";
290
291 /**
292 * Related to characteristic TESTABILITY
293 */
294 public static final String UNIT_TESTABILITY = "UNIT_TESTABILITY";
295
296 private SubCharacteristics() {
297 // only constants
298 }
299 }
300
301 /**
302 * Instantiated by core but not by plugins
303 */
304 class Context {
305 private final Map<String, Repository> repositoriesByKey = Maps.newHashMap();
306 private final ListMultimap<String, ExtendedRepository> extendedRepositoriesByKey = ArrayListMultimap.create();
307
308 public NewRepository createRepository(String key, String language) {
309 return new NewRepositoryImpl(this, key, language, false);
310 }
311
312 public NewExtendedRepository extendRepository(String key, String language) {
313 return new NewRepositoryImpl(this, key, language, true);
314 }
315
316 @CheckForNull
317 public Repository repository(String key) {
318 return repositoriesByKey.get(key);
319 }
320
321 public List<Repository> repositories() {
322 return ImmutableList.copyOf(repositoriesByKey.values());
323 }
324
325 public List<ExtendedRepository> extendedRepositories(String repositoryKey) {
326 return ImmutableList.copyOf(extendedRepositoriesByKey.get(repositoryKey));
327 }
328
329 public List<ExtendedRepository> extendedRepositories() {
330 return ImmutableList.copyOf(extendedRepositoriesByKey.values());
331 }
332
333 private void registerRepository(NewRepositoryImpl newRepository) {
334 if (repositoriesByKey.containsKey(newRepository.key)) {
335 throw new IllegalStateException(String.format("The rule repository '%s' is defined several times", newRepository.key));
336 }
337 repositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository));
338 }
339
340 private void registerExtendedRepository(NewRepositoryImpl newRepository) {
341 extendedRepositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository));
342 }
343 }
344
345 interface NewExtendedRepository {
346 NewRule createRule(String ruleKey);
347
348 @CheckForNull
349 NewRule rule(String ruleKey);
350
351 Collection<NewRule> rules();
352
353 String key();
354
355 void done();
356 }
357
358 interface NewRepository extends NewExtendedRepository {
359 NewRepository setName(String s);
360 }
361
362 class NewRepositoryImpl implements NewRepository {
363 private final Context context;
364 private final boolean extended;
365 private final String key;
366 private String language;
367 private String name;
368 private final Map<String, NewRule> newRules = Maps.newHashMap();
369
370 private NewRepositoryImpl(Context context, String key, String language, boolean extended) {
371 this.extended = extended;
372 this.context = context;
373 this.key = this.name = key;
374 this.language = language;
375 }
376
377 @Override
378 public String key() {
379 return key;
380 }
381
382 @Override
383 public NewRepositoryImpl setName(@Nullable String s) {
384 if (StringUtils.isNotEmpty(s)) {
385 this.name = s;
386 }
387 return this;
388 }
389
390 @Override
391 public NewRule createRule(String ruleKey) {
392 if (newRules.containsKey(ruleKey)) {
393 // Should fail in a perfect world, but at the time being the Findbugs plugin
394 // defines several times the rule EC_INCOMPATIBLE_ARRAY_COMPARE
395 // See http://jira.codehaus.org/browse/SONARJAVA-428
396 LoggerFactory.getLogger(getClass()).warn(String.format("The rule '%s' of repository '%s' is declared several times", ruleKey, key));
397 }
398 NewRule newRule = new NewRule(key, ruleKey);
399 newRules.put(ruleKey, newRule);
400 return newRule;
401 }
402
403 @CheckForNull
404 @Override
405 public NewRule rule(String ruleKey) {
406 return newRules.get(ruleKey);
407 }
408
409 @Override
410 public Collection<NewRule> rules() {
411 return newRules.values();
412 }
413
414 @Override
415 public void done() {
416 // note that some validations can be done here, for example for
417 // verifying that at least one rule is declared
418
419 if (extended) {
420 context.registerExtendedRepository(this);
421 } else {
422 context.registerRepository(this);
423 }
424 }
425 }
426
427 interface ExtendedRepository {
428 String key();
429
430 String language();
431
432 @CheckForNull
433 Rule rule(String ruleKey);
434
435 List<Rule> rules();
436 }
437
438 interface Repository extends ExtendedRepository {
439 String name();
440 }
441
442 @Immutable
443 class RepositoryImpl implements Repository {
444 private final String key, language, name;
445 private final Map<String, Rule> rulesByKey;
446
447 private RepositoryImpl(NewRepositoryImpl newRepository) {
448 this.key = newRepository.key;
449 this.language = newRepository.language;
450 this.name = newRepository.name;
451 ImmutableMap.Builder<String, Rule> ruleBuilder = ImmutableMap.builder();
452 for (NewRule newRule : newRepository.newRules.values()) {
453 newRule.validate();
454 ruleBuilder.put(newRule.key, new Rule(this, newRule));
455 }
456 this.rulesByKey = ruleBuilder.build();
457 }
458
459 @Override
460 public String key() {
461 return key;
462 }
463
464 @Override
465 public String language() {
466 return language;
467 }
468
469 @Override
470 public String name() {
471 return name;
472 }
473
474 @Override
475 @CheckForNull
476 public Rule rule(String ruleKey) {
477 return rulesByKey.get(ruleKey);
478 }
479
480 @Override
481 public List<Rule> rules() {
482 return ImmutableList.copyOf(rulesByKey.values());
483 }
484
485 @Override
486 public boolean equals(Object o) {
487 if (this == o) {
488 return true;
489 }
490 if (o == null || getClass() != o.getClass()) {
491 return false;
492 }
493 RepositoryImpl that = (RepositoryImpl) o;
494 return key.equals(that.key);
495 }
496
497 @Override
498 public int hashCode() {
499 return key.hashCode();
500 }
501 }
502
503 /**
504 * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}.
505 */
506 interface DebtRemediationFunctions {
507 DebtRemediationFunction linear(String coefficient);
508
509 DebtRemediationFunction linearWithOffset(String coefficient, String offset);
510
511 DebtRemediationFunction constantPerIssue(String offset);
512 }
513
514 class NewRule {
515 private final String repoKey, key;
516 private String name, htmlDescription, markdownDescription, internalKey, severity = Severity.MAJOR;
517 private boolean template;
518 private RuleStatus status = RuleStatus.defaultStatus();
519 private String debtSubCharacteristic;
520 private DebtRemediationFunction debtRemediationFunction;
521 private String effortToFixDescription;
522 private final Set<String> tags = Sets.newTreeSet();
523 private final Map<String, NewParam> paramsByKey = Maps.newHashMap();
524 private final DebtRemediationFunctions functions;
525
526 private NewRule(String repoKey, String key) {
527 this.repoKey = repoKey;
528 this.key = key;
529 this.functions = new DefaultDebtRemediationFunctions(repoKey, key);
530 }
531
532 public String key() {
533 return this.key;
534 }
535
536 /**
537 * Required rule name
538 */
539 public NewRule setName(@Nullable String s) {
540 this.name = StringUtils.trimToNull(s);
541 return this;
542 }
543
544 public NewRule setTemplate(boolean template) {
545 this.template = template;
546 return this;
547 }
548
549 public NewRule setSeverity(String s) {
550 if (!Severity.ALL.contains(s)) {
551 throw new IllegalArgumentException(String.format("Severity of rule %s is not correct: %s", this, s));
552 }
553 this.severity = s;
554 return this;
555 }
556
557 public NewRule setHtmlDescription(@Nullable String s) {
558 if (markdownDescription != null) {
559 throw new IllegalStateException(String.format("Rule '%s' already has a Markdown description", this));
560 }
561 this.htmlDescription = StringUtils.trimToNull(s);
562 return this;
563 }
564
565 /**
566 * Load description from a file available in classpath. Example : <code>setHtmlDescription(getClass().getResource("/myrepo/Rule1234.html")</code>
567 */
568 public NewRule setHtmlDescription(@Nullable URL classpathUrl) {
569 if (classpathUrl != null) {
570 try {
571 setHtmlDescription(IOUtils.toString(classpathUrl));
572 } catch (IOException e) {
573 throw new IllegalStateException("Fail to read: " + classpathUrl, e);
574 }
575 } else {
576 this.htmlDescription = null;
577 }
578 return this;
579 }
580
581 public NewRule setMarkdownDescription(@Nullable String s) {
582 if (htmlDescription != null) {
583 throw new IllegalStateException(String.format("Rule '%s' already has an HTML description", this));
584 }
585 this.markdownDescription = StringUtils.trimToNull(s);
586 return this;
587 }
588
589 /**
590 * Load description from a file available in classpath. Example : <code>setMarkdownDescription(getClass().getResource("/myrepo/Rule1234.md")</code>
591 */
592 public NewRule setMarkdownDescription(@Nullable URL classpathUrl) {
593 if (classpathUrl != null) {
594 try {
595 setMarkdownDescription(IOUtils.toString(classpathUrl));
596 } catch (IOException e) {
597 throw new IllegalStateException("Fail to read: " + classpathUrl, e);
598 }
599 } else {
600 this.markdownDescription = null;
601 }
602 return this;
603 }
604
605 /**
606 * Default value is {@link org.sonar.api.rule.RuleStatus#READY}. The value
607 * {@link org.sonar.api.rule.RuleStatus#REMOVED} is not accepted and raises an
608 * {@link java.lang.IllegalArgumentException}.
609 */
610 public NewRule setStatus(RuleStatus status) {
611 if (status.equals(RuleStatus.REMOVED)) {
612 throw new IllegalArgumentException(String.format("Status 'REMOVED' is not accepted on rule '%s'", this));
613 }
614 this.status = status;
615 return this;
616 }
617
618 /**
619 * SQALE sub-characteristic. See http://www.sqale.org
620 *
621 * @see org.sonar.api.server.rule.RulesDefinition.SubCharacteristics for constant values
622 */
623 public NewRule setDebtSubCharacteristic(@Nullable String s) {
624 this.debtSubCharacteristic = s;
625 return this;
626 }
627
628 /**
629 * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}
630 */
631 public DebtRemediationFunctions debtRemediationFunctions() {
632 return functions;
633 }
634
635 /**
636 * @see #debtRemediationFunctions()
637 */
638 public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn) {
639 this.debtRemediationFunction = fn;
640 return this;
641 }
642
643 /**
644 * For rules that use "Linear"/"Linear with offset" remediation functions, the meaning
645 * of the function parameter (= "effort to fix") must be set. This description
646 * explains what 1 point of "effort to fix" represents for the rule.
647 * <p/>
648 * Example : : for the "Insufficient condition coverage", this description for the
649 * remediation function coefficient/offset would be something like
650 * "Effort to test one uncovered condition".
651 */
652 public NewRule setEffortToFixDescription(@Nullable String s) {
653 this.effortToFixDescription = s;
654 return this;
655 }
656
657 public NewParam createParam(String paramKey) {
658 if (paramsByKey.containsKey(paramKey)) {
659 throw new IllegalArgumentException(String.format("The parameter '%s' is declared several times on the rule %s", paramKey, this));
660 }
661 NewParam param = new NewParam(paramKey);
662 paramsByKey.put(paramKey, param);
663 return param;
664 }
665
666 @CheckForNull
667 public NewParam param(String paramKey) {
668 return paramsByKey.get(paramKey);
669 }
670
671 public Collection<NewParam> params() {
672 return paramsByKey.values();
673 }
674
675 /**
676 * @see RuleTagFormat
677 */
678 public NewRule addTags(String... list) {
679 for (String tag : list) {
680 RuleTagFormat.validate(tag);
681 tags.add(tag);
682 }
683 return this;
684 }
685
686 /**
687 * @see RuleTagFormat
688 */
689 public NewRule setTags(String... list) {
690 tags.clear();
691 addTags(list);
692 return this;
693 }
694
695 /**
696 * Optional key that can be used by the rule engine. Not displayed
697 * in webapp. For example the Java Checkstyle plugin feeds this field
698 * with the internal path ("Checker/TreeWalker/AnnotationUseStyle").
699 */
700 public NewRule setInternalKey(@Nullable String s) {
701 this.internalKey = s;
702 return this;
703 }
704
705 private void validate() {
706 if (Strings.isNullOrEmpty(name)) {
707 throw new IllegalStateException(String.format("Name of rule %s is empty", this));
708 }
709 if (Strings.isNullOrEmpty(htmlDescription) && Strings.isNullOrEmpty(markdownDescription)) {
710 throw new IllegalStateException(String.format("One of HTML description or Markdown description must be defined for rule %s", this));
711 }
712 if ((Strings.isNullOrEmpty(debtSubCharacteristic) && debtRemediationFunction != null) || (!Strings.isNullOrEmpty(debtSubCharacteristic) && debtRemediationFunction == null)) {
713 throw new IllegalStateException(String.format("Both debt sub-characteristic and debt remediation function should be defined on rule '%s'", this));
714 }
715 }
716
717 @Override
718 public String toString() {
719 return String.format("[repository=%s, key=%s]", repoKey, key);
720 }
721 }
722
723 @Immutable
724 class Rule {
725 private final Repository repository;
726 private final String repoKey, key, name, htmlDescription, markdownDescription, internalKey, severity;
727 private final boolean template;
728 private final String debtSubCharacteristic;
729 private final DebtRemediationFunction debtRemediationFunction;
730 private final String effortToFixDescription;
731 private final Set<String> tags;
732 private final Map<String, Param> params;
733 private final RuleStatus status;
734
735 private Rule(Repository repository, NewRule newRule) {
736 this.repository = repository;
737 this.repoKey = newRule.repoKey;
738 this.key = newRule.key;
739 this.name = newRule.name;
740 this.htmlDescription = newRule.htmlDescription;
741 this.markdownDescription = newRule.markdownDescription;
742 this.internalKey = newRule.internalKey;
743 this.severity = newRule.severity;
744 this.template = newRule.template;
745 this.status = newRule.status;
746 this.debtSubCharacteristic = newRule.debtSubCharacteristic;
747 this.debtRemediationFunction = newRule.debtRemediationFunction;
748 this.effortToFixDescription = newRule.effortToFixDescription;
749 this.tags = ImmutableSortedSet.copyOf(newRule.tags);
750 ImmutableMap.Builder<String, Param> paramsBuilder = ImmutableMap.builder();
751 for (NewParam newParam : newRule.paramsByKey.values()) {
752 paramsBuilder.put(newParam.key, new Param(newParam));
753 }
754 this.params = paramsBuilder.build();
755 }
756
757 public Repository repository() {
758 return repository;
759 }
760
761 public String key() {
762 return key;
763 }
764
765 public String name() {
766 return name;
767 }
768
769 public String severity() {
770 return severity;
771 }
772
773 @CheckForNull
774 public String htmlDescription() {
775 return htmlDescription;
776 }
777
778 @CheckForNull
779 public String markdownDescription() {
780 return markdownDescription;
781 }
782
783 public boolean template() {
784 return template;
785 }
786
787 public RuleStatus status() {
788 return status;
789 }
790
791 @CheckForNull
792 public String debtSubCharacteristic() {
793 return debtSubCharacteristic;
794 }
795
796 @CheckForNull
797 public DebtRemediationFunction debtRemediationFunction() {
798 return debtRemediationFunction;
799 }
800
801 @CheckForNull
802 public String effortToFixDescription() {
803 return effortToFixDescription;
804 }
805
806 @CheckForNull
807 public Param param(String key) {
808 return params.get(key);
809 }
810
811 public List<Param> params() {
812 return ImmutableList.copyOf(params.values());
813 }
814
815 public Set<String> tags() {
816 return tags;
817 }
818
819 /**
820 * @see RulesDefinition.NewRule#setInternalKey(String)
821 */
822 @CheckForNull
823 public String internalKey() {
824 return internalKey;
825 }
826
827 @Override
828 public boolean equals(Object o) {
829 if (this == o) {
830 return true;
831 }
832 if (o == null || getClass() != o.getClass()) {
833 return false;
834 }
835 Rule other = (Rule) o;
836 return key.equals(other.key) && repoKey.equals(other.repoKey);
837 }
838
839 @Override
840 public int hashCode() {
841 int result = repoKey.hashCode();
842 result = 31 * result + key.hashCode();
843 return result;
844 }
845
846 @Override
847 public String toString() {
848 return String.format("[repository=%s, key=%s]", repoKey, key);
849 }
850 }
851
852 class NewParam {
853 private final String key;
854 private String name, description, defaultValue;
855 private RuleParamType type = RuleParamType.STRING;
856
857 private NewParam(String key) {
858 this.key = this.name = key;
859 }
860
861 public String key() {
862 return key;
863 }
864
865 public NewParam setName(@Nullable String s) {
866 // name must never be null.
867 this.name = StringUtils.defaultIfBlank(s, key);
868 return this;
869 }
870
871 public NewParam setType(RuleParamType t) {
872 this.type = t;
873 return this;
874 }
875
876 /**
877 * Plain-text description. Can be null.
878 */
879 public NewParam setDescription(@Nullable String s) {
880 this.description = StringUtils.defaultIfBlank(s, null);
881 return this;
882 }
883
884 public NewParam setDefaultValue(@Nullable String s) {
885 this.defaultValue = s;
886 return this;
887 }
888 }
889
890 @Immutable
891 class Param {
892 private final String key, name, description, defaultValue;
893 private final RuleParamType type;
894
895 private Param(NewParam newParam) {
896 this.key = newParam.key;
897 this.name = newParam.name;
898 this.description = newParam.description;
899 this.defaultValue = newParam.defaultValue;
900 this.type = newParam.type;
901 }
902
903 public String key() {
904 return key;
905 }
906
907 public String name() {
908 return name;
909 }
910
911 @Nullable
912 public String description() {
913 return description;
914 }
915
916 @Nullable
917 public String defaultValue() {
918 return defaultValue;
919 }
920
921 public RuleParamType type() {
922 return type;
923 }
924
925 @Override
926 public boolean equals(Object o) {
927 if (this == o) {
928 return true;
929 }
930 if (o == null || getClass() != o.getClass()) {
931 return false;
932 }
933 Param that = (Param) o;
934 return key.equals(that.key);
935 }
936
937 @Override
938 public int hashCode() {
939 return key.hashCode();
940 }
941 }
942
943 /**
944 * This method is executed when server is started.
945 */
946 void define(Context context);
947
948 }