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 /**
297 * Related to characteristic ACCESSIBILITY
298 */
299 public static final String USABILITY_ACCESSIBILITY = "USABILITY_ACCESSIBILITY";
300
301 /**
302 * Related to characteristic ACCESSIBILITY
303 */
304 public static final String USABILITY_COMPLIANCE = "USABILITY_COMPLIANCE";
305
306 /**
307 * Related to characteristic ACCESSIBILITY
308 */
309 public static final String USABILITY_EASE_OF_USE = "USABILITY_EASE_OF_USE";
310
311 /**
312 * Related to characteristic REUSABILITY
313 */
314 public static final String REUSABILITY_COMPLIANCE = "REUSABILITY_COMPLIANCE";
315
316 /**
317 * Related to characteristic PORTABILITY
318 */
319 public static final String PORTABILITY_COMPLIANCE = "PORTABILITY_COMPLIANCE";
320
321 /**
322 * Related to characteristic MAINTAINABILITY
323 */
324 public static final String MAINTAINABILITY_COMPLIANCE = "MAINTAINABILITY_COMPLIANCE";
325
326 /**
327 * Related to characteristic SECURITY
328 */
329 public static final String SECURITY_COMPLIANCE = "SECURITY_COMPLIANCE";
330
331 /**
332 * Related to characteristic EFFICIENCY
333 */
334 public static final String EFFICIENCY_COMPLIANCE = "EFFICIENCY_COMPLIANCE";
335
336 /**
337 * Related to characteristic CHANGEABILITY
338 */
339 public static final String CHANGEABILITY_COMPLIANCE = "CHANGEABILITY_COMPLIANCE";
340
341 /**
342 * Related to characteristic RELIABILITY
343 */
344 public static final String RELIABILITY_COMPLIANCE = "RELIABILITY_COMPLIANCE";
345
346 /**
347 * Related to characteristic TESTABILITY
348 */
349 public static final String TESTABILITY_COMPLIANCE = "TESTABILITY_COMPLIANCE";
350
351 private SubCharacteristics() {
352 // only constants
353 }
354 }
355
356 /**
357 * Instantiated by core but not by plugins
358 */
359 class Context {
360 private final Map<String, Repository> repositoriesByKey = Maps.newHashMap();
361 private final ListMultimap<String, ExtendedRepository> extendedRepositoriesByKey = ArrayListMultimap.create();
362
363 public NewRepository createRepository(String key, String language) {
364 return new NewRepositoryImpl(this, key, language, false);
365 }
366
367 public NewExtendedRepository extendRepository(String key, String language) {
368 return new NewRepositoryImpl(this, key, language, true);
369 }
370
371 @CheckForNull
372 public Repository repository(String key) {
373 return repositoriesByKey.get(key);
374 }
375
376 public List<Repository> repositories() {
377 return ImmutableList.copyOf(repositoriesByKey.values());
378 }
379
380 public List<ExtendedRepository> extendedRepositories(String repositoryKey) {
381 return ImmutableList.copyOf(extendedRepositoriesByKey.get(repositoryKey));
382 }
383
384 public List<ExtendedRepository> extendedRepositories() {
385 return ImmutableList.copyOf(extendedRepositoriesByKey.values());
386 }
387
388 private void registerRepository(NewRepositoryImpl newRepository) {
389 if (repositoriesByKey.containsKey(newRepository.key)) {
390 throw new IllegalStateException(String.format("The rule repository '%s' is defined several times", newRepository.key));
391 }
392 repositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository));
393 }
394
395 private void registerExtendedRepository(NewRepositoryImpl newRepository) {
396 extendedRepositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository));
397 }
398 }
399
400 interface NewExtendedRepository {
401 NewRule createRule(String ruleKey);
402
403 @CheckForNull
404 NewRule rule(String ruleKey);
405
406 Collection<NewRule> rules();
407
408 String key();
409
410 void done();
411 }
412
413 interface NewRepository extends NewExtendedRepository {
414 NewRepository setName(String s);
415 }
416
417 class NewRepositoryImpl implements NewRepository {
418 private final Context context;
419 private final boolean extended;
420 private final String key;
421 private String language;
422 private String name;
423 private final Map<String, NewRule> newRules = Maps.newHashMap();
424
425 private NewRepositoryImpl(Context context, String key, String language, boolean extended) {
426 this.extended = extended;
427 this.context = context;
428 this.key = this.name = key;
429 this.language = language;
430 }
431
432 @Override
433 public String key() {
434 return key;
435 }
436
437 @Override
438 public NewRepositoryImpl setName(@Nullable String s) {
439 if (StringUtils.isNotEmpty(s)) {
440 this.name = s;
441 }
442 return this;
443 }
444
445 @Override
446 public NewRule createRule(String ruleKey) {
447 if (newRules.containsKey(ruleKey)) {
448 // Should fail in a perfect world, but at the time being the Findbugs plugin
449 // defines several times the rule EC_INCOMPATIBLE_ARRAY_COMPARE
450 // See http://jira.codehaus.org/browse/SONARJAVA-428
451 LoggerFactory.getLogger(getClass()).warn(String.format("The rule '%s' of repository '%s' is declared several times", ruleKey, key));
452 }
453 NewRule newRule = new NewRule(key, ruleKey);
454 newRules.put(ruleKey, newRule);
455 return newRule;
456 }
457
458 @CheckForNull
459 @Override
460 public NewRule rule(String ruleKey) {
461 return newRules.get(ruleKey);
462 }
463
464 @Override
465 public Collection<NewRule> rules() {
466 return newRules.values();
467 }
468
469 @Override
470 public void done() {
471 // note that some validations can be done here, for example for
472 // verifying that at least one rule is declared
473
474 if (extended) {
475 context.registerExtendedRepository(this);
476 } else {
477 context.registerRepository(this);
478 }
479 }
480 }
481
482 interface ExtendedRepository {
483 String key();
484
485 String language();
486
487 @CheckForNull
488 Rule rule(String ruleKey);
489
490 List<Rule> rules();
491 }
492
493 interface Repository extends ExtendedRepository {
494 String name();
495 }
496
497 @Immutable
498 class RepositoryImpl implements Repository {
499 private final String key, language, name;
500 private final Map<String, Rule> rulesByKey;
501
502 private RepositoryImpl(NewRepositoryImpl newRepository) {
503 this.key = newRepository.key;
504 this.language = newRepository.language;
505 this.name = newRepository.name;
506 ImmutableMap.Builder<String, Rule> ruleBuilder = ImmutableMap.builder();
507 for (NewRule newRule : newRepository.newRules.values()) {
508 newRule.validate();
509 ruleBuilder.put(newRule.key, new Rule(this, newRule));
510 }
511 this.rulesByKey = ruleBuilder.build();
512 }
513
514 @Override
515 public String key() {
516 return key;
517 }
518
519 @Override
520 public String language() {
521 return language;
522 }
523
524 @Override
525 public String name() {
526 return name;
527 }
528
529 @Override
530 @CheckForNull
531 public Rule rule(String ruleKey) {
532 return rulesByKey.get(ruleKey);
533 }
534
535 @Override
536 public List<Rule> rules() {
537 return ImmutableList.copyOf(rulesByKey.values());
538 }
539
540 @Override
541 public boolean equals(Object o) {
542 if (this == o) {
543 return true;
544 }
545 if (o == null || getClass() != o.getClass()) {
546 return false;
547 }
548 RepositoryImpl that = (RepositoryImpl) o;
549 return key.equals(that.key);
550 }
551
552 @Override
553 public int hashCode() {
554 return key.hashCode();
555 }
556 }
557
558 /**
559 * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}.
560 */
561 interface DebtRemediationFunctions {
562 DebtRemediationFunction linear(String coefficient);
563
564 DebtRemediationFunction linearWithOffset(String coefficient, String offset);
565
566 DebtRemediationFunction constantPerIssue(String offset);
567 }
568
569 class NewRule {
570 private final String repoKey, key;
571 private String name, htmlDescription, markdownDescription, internalKey, severity = Severity.MAJOR;
572 private boolean template;
573 private RuleStatus status = RuleStatus.defaultStatus();
574 private String debtSubCharacteristic;
575 private DebtRemediationFunction debtRemediationFunction;
576 private String effortToFixDescription;
577 private final Set<String> tags = Sets.newTreeSet();
578 private final Map<String, NewParam> paramsByKey = Maps.newHashMap();
579 private final DebtRemediationFunctions functions;
580
581 private NewRule(String repoKey, String key) {
582 this.repoKey = repoKey;
583 this.key = key;
584 this.functions = new DefaultDebtRemediationFunctions(repoKey, key);
585 }
586
587 public String key() {
588 return this.key;
589 }
590
591 /**
592 * Required rule name
593 */
594 public NewRule setName(@Nullable String s) {
595 this.name = StringUtils.trimToNull(s);
596 return this;
597 }
598
599 public NewRule setTemplate(boolean template) {
600 this.template = template;
601 return this;
602 }
603
604 public NewRule setSeverity(String s) {
605 if (!Severity.ALL.contains(s)) {
606 throw new IllegalArgumentException(String.format("Severity of rule %s is not correct: %s", this, s));
607 }
608 this.severity = s;
609 return this;
610 }
611
612 public NewRule setHtmlDescription(@Nullable String s) {
613 if (markdownDescription != null) {
614 throw new IllegalStateException(String.format("Rule '%s' already has a Markdown description", this));
615 }
616 this.htmlDescription = StringUtils.trimToNull(s);
617 return this;
618 }
619
620 /**
621 * Load description from a file available in classpath. Example : <code>setHtmlDescription(getClass().getResource("/myrepo/Rule1234.html")</code>
622 */
623 public NewRule setHtmlDescription(@Nullable URL classpathUrl) {
624 if (classpathUrl != null) {
625 try {
626 setHtmlDescription(IOUtils.toString(classpathUrl));
627 } catch (IOException e) {
628 throw new IllegalStateException("Fail to read: " + classpathUrl, e);
629 }
630 } else {
631 this.htmlDescription = null;
632 }
633 return this;
634 }
635
636 public NewRule setMarkdownDescription(@Nullable String s) {
637 if (htmlDescription != null) {
638 throw new IllegalStateException(String.format("Rule '%s' already has an HTML description", this));
639 }
640 this.markdownDescription = StringUtils.trimToNull(s);
641 return this;
642 }
643
644 /**
645 * Load description from a file available in classpath. Example : <code>setMarkdownDescription(getClass().getResource("/myrepo/Rule1234.md")</code>
646 */
647 public NewRule setMarkdownDescription(@Nullable URL classpathUrl) {
648 if (classpathUrl != null) {
649 try {
650 setMarkdownDescription(IOUtils.toString(classpathUrl));
651 } catch (IOException e) {
652 throw new IllegalStateException("Fail to read: " + classpathUrl, e);
653 }
654 } else {
655 this.markdownDescription = null;
656 }
657 return this;
658 }
659
660 /**
661 * Default value is {@link org.sonar.api.rule.RuleStatus#READY}. The value
662 * {@link org.sonar.api.rule.RuleStatus#REMOVED} is not accepted and raises an
663 * {@link java.lang.IllegalArgumentException}.
664 */
665 public NewRule setStatus(RuleStatus status) {
666 if (status.equals(RuleStatus.REMOVED)) {
667 throw new IllegalArgumentException(String.format("Status 'REMOVED' is not accepted on rule '%s'", this));
668 }
669 this.status = status;
670 return this;
671 }
672
673 /**
674 * SQALE sub-characteristic. See http://www.sqale.org
675 *
676 * @see org.sonar.api.server.rule.RulesDefinition.SubCharacteristics for constant values
677 */
678 public NewRule setDebtSubCharacteristic(@Nullable String s) {
679 this.debtSubCharacteristic = s;
680 return this;
681 }
682
683 /**
684 * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}
685 */
686 public DebtRemediationFunctions debtRemediationFunctions() {
687 return functions;
688 }
689
690 /**
691 * @see #debtRemediationFunctions()
692 */
693 public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn) {
694 this.debtRemediationFunction = fn;
695 return this;
696 }
697
698 /**
699 * For rules that use "Linear"/"Linear with offset" remediation functions, the meaning
700 * of the function parameter (= "effort to fix") must be set. This description
701 * explains what 1 point of "effort to fix" represents for the rule.
702 * <p/>
703 * Example : : for the "Insufficient condition coverage", this description for the
704 * remediation function coefficient/offset would be something like
705 * "Effort to test one uncovered condition".
706 */
707 public NewRule setEffortToFixDescription(@Nullable String s) {
708 this.effortToFixDescription = s;
709 return this;
710 }
711
712 public NewParam createParam(String paramKey) {
713 if (paramsByKey.containsKey(paramKey)) {
714 throw new IllegalArgumentException(String.format("The parameter '%s' is declared several times on the rule %s", paramKey, this));
715 }
716 NewParam param = new NewParam(paramKey);
717 paramsByKey.put(paramKey, param);
718 return param;
719 }
720
721 @CheckForNull
722 public NewParam param(String paramKey) {
723 return paramsByKey.get(paramKey);
724 }
725
726 public Collection<NewParam> params() {
727 return paramsByKey.values();
728 }
729
730 /**
731 * @see RuleTagFormat
732 */
733 public NewRule addTags(String... list) {
734 for (String tag : list) {
735 RuleTagFormat.validate(tag);
736 tags.add(tag);
737 }
738 return this;
739 }
740
741 /**
742 * @see RuleTagFormat
743 */
744 public NewRule setTags(String... list) {
745 tags.clear();
746 addTags(list);
747 return this;
748 }
749
750 /**
751 * Optional key that can be used by the rule engine. Not displayed
752 * in webapp. For example the Java Checkstyle plugin feeds this field
753 * with the internal path ("Checker/TreeWalker/AnnotationUseStyle").
754 */
755 public NewRule setInternalKey(@Nullable String s) {
756 this.internalKey = s;
757 return this;
758 }
759
760 private void validate() {
761 if (Strings.isNullOrEmpty(name)) {
762 throw new IllegalStateException(String.format("Name of rule %s is empty", this));
763 }
764 if (Strings.isNullOrEmpty(htmlDescription) && Strings.isNullOrEmpty(markdownDescription)) {
765 throw new IllegalStateException(String.format("One of HTML description or Markdown description must be defined for rule %s", this));
766 }
767 if ((Strings.isNullOrEmpty(debtSubCharacteristic) && debtRemediationFunction != null) || (!Strings.isNullOrEmpty(debtSubCharacteristic) && debtRemediationFunction == null)) {
768 throw new IllegalStateException(String.format("Both debt sub-characteristic and debt remediation function should be defined on rule '%s'", this));
769 }
770 }
771
772 @Override
773 public String toString() {
774 return String.format("[repository=%s, key=%s]", repoKey, key);
775 }
776 }
777
778 @Immutable
779 class Rule {
780 private final Repository repository;
781 private final String repoKey, key, name, htmlDescription, markdownDescription, internalKey, severity;
782 private final boolean template;
783 private final String debtSubCharacteristic;
784 private final DebtRemediationFunction debtRemediationFunction;
785 private final String effortToFixDescription;
786 private final Set<String> tags;
787 private final Map<String, Param> params;
788 private final RuleStatus status;
789
790 private Rule(Repository repository, NewRule newRule) {
791 this.repository = repository;
792 this.repoKey = newRule.repoKey;
793 this.key = newRule.key;
794 this.name = newRule.name;
795 this.htmlDescription = newRule.htmlDescription;
796 this.markdownDescription = newRule.markdownDescription;
797 this.internalKey = newRule.internalKey;
798 this.severity = newRule.severity;
799 this.template = newRule.template;
800 this.status = newRule.status;
801 this.debtSubCharacteristic = newRule.debtSubCharacteristic;
802 this.debtRemediationFunction = newRule.debtRemediationFunction;
803 this.effortToFixDescription = newRule.effortToFixDescription;
804 this.tags = ImmutableSortedSet.copyOf(newRule.tags);
805 ImmutableMap.Builder<String, Param> paramsBuilder = ImmutableMap.builder();
806 for (NewParam newParam : newRule.paramsByKey.values()) {
807 paramsBuilder.put(newParam.key, new Param(newParam));
808 }
809 this.params = paramsBuilder.build();
810 }
811
812 public Repository repository() {
813 return repository;
814 }
815
816 public String key() {
817 return key;
818 }
819
820 public String name() {
821 return name;
822 }
823
824 public String severity() {
825 return severity;
826 }
827
828 @CheckForNull
829 public String htmlDescription() {
830 return htmlDescription;
831 }
832
833 @CheckForNull
834 public String markdownDescription() {
835 return markdownDescription;
836 }
837
838 public boolean template() {
839 return template;
840 }
841
842 public RuleStatus status() {
843 return status;
844 }
845
846 @CheckForNull
847 public String debtSubCharacteristic() {
848 return debtSubCharacteristic;
849 }
850
851 @CheckForNull
852 public DebtRemediationFunction debtRemediationFunction() {
853 return debtRemediationFunction;
854 }
855
856 @CheckForNull
857 public String effortToFixDescription() {
858 return effortToFixDescription;
859 }
860
861 @CheckForNull
862 public Param param(String key) {
863 return params.get(key);
864 }
865
866 public List<Param> params() {
867 return ImmutableList.copyOf(params.values());
868 }
869
870 public Set<String> tags() {
871 return tags;
872 }
873
874 /**
875 * @see RulesDefinition.NewRule#setInternalKey(String)
876 */
877 @CheckForNull
878 public String internalKey() {
879 return internalKey;
880 }
881
882 @Override
883 public boolean equals(Object o) {
884 if (this == o) {
885 return true;
886 }
887 if (o == null || getClass() != o.getClass()) {
888 return false;
889 }
890 Rule other = (Rule) o;
891 return key.equals(other.key) && repoKey.equals(other.repoKey);
892 }
893
894 @Override
895 public int hashCode() {
896 int result = repoKey.hashCode();
897 result = 31 * result + key.hashCode();
898 return result;
899 }
900
901 @Override
902 public String toString() {
903 return String.format("[repository=%s, key=%s]", repoKey, key);
904 }
905 }
906
907 class NewParam {
908 private final String key;
909 private String name, description, defaultValue;
910 private RuleParamType type = RuleParamType.STRING;
911
912 private NewParam(String key) {
913 this.key = this.name = key;
914 }
915
916 public String key() {
917 return key;
918 }
919
920 public NewParam setName(@Nullable String s) {
921 // name must never be null.
922 this.name = StringUtils.defaultIfBlank(s, key);
923 return this;
924 }
925
926 public NewParam setType(RuleParamType t) {
927 this.type = t;
928 return this;
929 }
930
931 /**
932 * Plain-text description. Can be null.
933 */
934 public NewParam setDescription(@Nullable String s) {
935 this.description = StringUtils.defaultIfBlank(s, null);
936 return this;
937 }
938
939 /**
940 * Empty default value will be converted to null.
941 */
942 public NewParam setDefaultValue(@Nullable String s) {
943 this.defaultValue = Strings.emptyToNull(s);
944 return this;
945 }
946 }
947
948 @Immutable
949 class Param {
950 private final String key, name, description, defaultValue;
951 private final RuleParamType type;
952
953 private Param(NewParam newParam) {
954 this.key = newParam.key;
955 this.name = newParam.name;
956 this.description = newParam.description;
957 this.defaultValue = newParam.defaultValue;
958 this.type = newParam.type;
959 }
960
961 public String key() {
962 return key;
963 }
964
965 public String name() {
966 return name;
967 }
968
969 @Nullable
970 public String description() {
971 return description;
972 }
973
974 @Nullable
975 public String defaultValue() {
976 return defaultValue;
977 }
978
979 public RuleParamType type() {
980 return type;
981 }
982
983 @Override
984 public boolean equals(Object o) {
985 if (this == o) {
986 return true;
987 }
988 if (o == null || getClass() != o.getClass()) {
989 return false;
990 }
991 Param that = (Param) o;
992 return key.equals(that.key);
993 }
994
995 @Override
996 public int hashCode() {
997 return key.hashCode();
998 }
999 }
1000
1001 /**
1002 * This method is executed when server is started.
1003 */
1004 void define(Context context);
1005
1006 }