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 }