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 the coding rules. 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 }