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