All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codenarc.rule.AbstractRule Maven / Gradle / Ivy

There is a newer version: 3.5.0-groovy-4.0
Show newest version
/*
 * Copyright 2008 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.codenarc.rule;

import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ImportNode;
import org.codenarc.source.SourceCode;
import org.codenarc.source.SourceCodeCriteria;
import org.codenarc.util.ImportUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Abstract superclass for Rules.
 * 

* Each subclass must define an name property (String) and a priority property * (integer 1..3). * * @author Chris Mair * @author Hamlet D'Arcy */ public abstract class AbstractRule implements Rule { private static final Logger LOG = LoggerFactory.getLogger(AbstractRule.class); /** * Flag indicating whether this rule should be enabled (applied). Defaults to true. * If set to false, this rule will not produce any violations. */ private boolean enabled = true; /** * This rule is only applied to source code (file) pathnames matching this regular expression. */ private String applyToFilesMatching; /** * This rule is NOT applied to source code (file) pathnames matching this regular expression. */ private String doNotApplyToFilesMatching; /** * This rule is only applied to source code (file) names matching this value. The name may optionally * contain a path. If a path is specified, then the source code path must match it. If no path is * specified, then only the source code (file) name is compared (i.e., its path is ignored). * The value may optionally be a comma-separated list of names, in which case one of the names must match. * The name(s) may optionally include wildcard characters ('*' or '?'). */ private String applyToFileNames; /** * This rule is NOT applied to source code (file) names matching this value. The name may optionally * contain a path. If a path is specified, then the source code path must match it. If no path is * specified, then only the source code (file) name is compared (i.e., its path is ignored). * The value may optionally be a comma-separated list of names, in which case any one of the names can match. * The name(s) may optionally include wildcard characters ('*' or '?'). */ private String doNotApplyToFileNames; /** * If not null, this is used as the message for all violations of this rule, overriding any * message generated by the concrete rule subclass. Defaults to null. Note that setting this * to an empty string "hides" the message, if any, generated by the actual rule. */ private String violationMessage; /** * If not null, this is used as the description text for this rule, overriding any * description text found in the i18n resource bundles. Defaults to null. */ private String description; /** * @return the unique name for this rule */ public abstract String getName(); /** * Set the unique name for this rule * @param name - the name for this rule; this should be unique */ public abstract void setName(String name); /** * @return the priority of this rule, between 1 (highest priority) and 3 (lowest priority), inclusive. */ public abstract int getPriority(); /** * Set the priority for this rule * @param priority - the priority of this rule, between 1 (highest priority) and 3 (lowest priority), inclusive. */ public abstract void setPriority(int priority); /** * @return the required compiler phase (as in {@link org.codehaus.groovy.control.Phases}) * of the AST of the {@link SourceCode} * handed to the rule via {@link #applyTo(SourceCode sourceCode)} */ public int getCompilerPhase() { return SourceCode.DEFAULT_COMPILER_PHASE; } /** * Apply this rule to the specified source and return a list of violations (or an empty List) * @param sourceCode - the source to apply this rule to * @param violations - the List of violations to which new violations from this rule are to be added */ public abstract void applyTo(SourceCode sourceCode, List violations); /** * Apply this rule to the specified source and return a list of violations (or an empty List). * This implementation delegates to the abstract applyCode(SourceCode,List), provided by * concrete subclasses. This template method simplifies subclass implementations and also * enables common handling of enablement logic. * @param sourceCode - the source to apply this rule to * @return the List of violations; may be empty */ public List applyTo(SourceCode sourceCode) throws Throwable { try { validateAstCompilerPhase(sourceCode); validate(); List violations = new ArrayList(); if (shouldApplyThisRuleTo(sourceCode)) { applyTo(sourceCode, violations); } overrideViolationMessageIfNecessary(violations); return violations; } catch(Throwable t) { LOG.error("Error from [" + getClass().getName() + "] processing source file [" + sourceCode.getPath() + "]", t); throw t; } } private void validateAstCompilerPhase(SourceCode sourceCode) { if (sourceCode.getAstCompilerPhase() != getCompilerPhase()) { throw new IllegalArgumentException("This rule requires SourceCode with AST compiler phase '" + getCompilerPhase() + "', but was handed one with AST compiler phase '" + sourceCode.getAstCompilerPhase() + "'"); } } /** * Allows rules to check whether preconditions are satisfied and short-circuit execution * (i.e., do nothing) if those preconditions are not satisfied. Return true by default. * This method is provided as a placeholder so subclasses can optionally override. * @return true if all preconditions for this rule are satisfied */ public boolean isReady() { return true; } /** * Allows rules to perform validation. Do nothing by default. * This method is provided as a placeholder so subclasses can optionally override. * Subclasses will typically use assert calls to verify required preconditions. */ public void validate() { } public String toString() { return String.format( "%s[name=%s, priority=%s]", getClassNameNoPackage(), getName(), getPriority() ); } /** * Create and return a new Violation for this rule and the specified values * @param lineNumber - the line number for the violation; may be null * @param sourceLine - the source line for the violation; may be null * @param message - the message for the violation; may be null * @return a new Violation object */ protected Violation createViolation(Integer lineNumber, String sourceLine, String message) { Violation violation = new Violation(); violation.setRule(this); violation.setSourceLine(sourceLine); violation.setLineNumber(lineNumber); violation.setMessage(message); return violation; } /** * Create a new Violation for the AST node. * @param sourceCode - the SourceCode * @param node - the Groovy AST Node * @param message - the message for the violation; defaults to null */ protected Violation createViolation(SourceCode sourceCode, ASTNode node, String message) { String sourceLine = sourceCode.line(node.getLineNumber()-1); return createViolation(node.getLineNumber(), sourceLine, message); } /** * Create and return a new Violation for this rule and the specified import * @param sourceCode - the SourceCode * @param importNode - the ImportNode for the import triggering the violation * @return a new Violation object */ protected Violation createViolationForImport(SourceCode sourceCode, ImportNode importNode, String message) { Map importInfo = ImportUtil.sourceLineAndNumberForImport(sourceCode, importNode); Violation violation = new Violation(); violation.setRule(this); violation.setSourceLine((String) importInfo.get("sourceLine")); violation.setLineNumber((Integer) importInfo.get("lineNumber")); violation.setMessage(message); return violation; } /** * Create and return a new Violation for this rule and the specified import className and alias * @param sourceCode - the SourceCode * @param className - the class name (as specified within the import statement) * @param alias - the alias for the import statement * @param violationMessage - the violation message; may be null * @return a new Violation object */ protected Violation createViolationForImport(SourceCode sourceCode, String className, String alias, String violationMessage) { Map importInfo = ImportUtil.sourceLineAndNumberForImport(sourceCode, className, alias); Violation violation = new Violation(); violation.setRule(this); violation.setSourceLine((String) importInfo.get("sourceLine")); violation.setLineNumber((Integer) importInfo.get("lineNumber")); violation.setMessage(violationMessage); return violation; } private boolean shouldApplyThisRuleTo(SourceCode sourceCode) { if (!enabled) return false; if (!isReady()) return false; SourceCodeCriteria criteria = new SourceCodeCriteria(); criteria.setApplyToFilesMatching(getApplyToFilesMatching()); criteria.setDoNotApplyToFilesMatching(getDoNotApplyToFilesMatching()); criteria.setApplyToFileNames(getApplyToFileNames()); criteria.setDoNotApplyToFileNames(getDoNotApplyToFileNames()); return criteria.matches(sourceCode); } private String getClassNameNoPackage() { String className = getClass().getName(); int indexOfLastPeriod = className.lastIndexOf('.'); return (indexOfLastPeriod == -1) ? className : className.substring(indexOfLastPeriod + 1); } /** * If the violationMessage property of this rule has been set, then use it to set the * message within each violation, overriding the original message(s), if any. */ private void overrideViolationMessageIfNecessary(List violations) { if (violationMessage != null && violations != null) { for (Violation violation : violations) { violation.setMessage(violationMessage); } } } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public String getApplyToFilesMatching() { return applyToFilesMatching; } public void setApplyToFilesMatching(String applyToFilesMatching) { validateRegularExpression(applyToFilesMatching); this.applyToFilesMatching = applyToFilesMatching; } public String getDoNotApplyToFilesMatching() { return doNotApplyToFilesMatching; } public void setDoNotApplyToFilesMatching(String doNotApplyToFilesMatching) { validateRegularExpression(doNotApplyToFilesMatching); this.doNotApplyToFilesMatching = doNotApplyToFilesMatching; } public String getApplyToFileNames() { return applyToFileNames; } public void setApplyToFileNames(String applyToFileNames) { this.applyToFileNames = applyToFileNames; } public String getDoNotApplyToFileNames() { return doNotApplyToFileNames; } public void setDoNotApplyToFileNames(String doNotApplyToFileNames) { this.doNotApplyToFileNames = doNotApplyToFileNames; } public String getViolationMessage() { return violationMessage; } public void setViolationMessage(String violationMessage) { this.violationMessage = violationMessage; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } private void validateRegularExpression(String regex) { Pattern.compile(regex); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy