/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.rule;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sourceforge.pmd.PropertyDescriptor;
import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.RulePriority;
import net.sourceforge.pmd.RuleSetReference;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.util.StringUtil;
/**
* This class represents a Rule which is a reference to Rule defined in another
* RuleSet. All details of the Rule are delegated to the underlying referenced
* Rule, but those operations which modify overridden aspects of the rule are
* explicitly tracked. Modification operations which set a value to the current
* underlying value do not override.
*/
public class RuleReference extends AbstractDelegateRule {
private Language language;
private LanguageVersion minimumLanguageVersion;
private LanguageVersion maximumLanguageVersion;
private Boolean deprecated;
private String name;
private List<PropertyDescriptor<?>> propertyDescriptors;
private Map<PropertyDescriptor<?>, Object> propertyValues;
private String message;
private String description;
private List<String> examples;
private String externalInfoUrl;
private RulePriority priority;
private RuleSetReference ruleSetReference;
private static final List<PropertyDescriptor<?>> EMPTY_DESCRIPTORS = new ArrayList<>(0);
public RuleReference() {
}
public RuleReference(Rule theRule, RuleSetReference theRuleSetReference) {
setRule(theRule);
ruleSetReference = theRuleSetReference;
}
public Language getOverriddenLanguage() {
return language;
}
@Override
public void setLanguage(Language language) {
// Only override if different than current value, or if already
// overridden.
if (!isSame(language, super.getLanguage()) || this.language != null) {
this.language = language;
super.setLanguage(language);
}
}
public LanguageVersion getOverriddenMinimumLanguageVersion() {
return minimumLanguageVersion;
}
@Override
public void setMinimumLanguageVersion(LanguageVersion minimumLanguageVersion) {
// Only override if different than current value, or if already
// overridden.
if (!isSame(minimumLanguageVersion, super.getMinimumLanguageVersion()) || this.minimumLanguageVersion != null) {
this.minimumLanguageVersion = minimumLanguageVersion;
super.setMinimumLanguageVersion(minimumLanguageVersion);
}
}
public LanguageVersion getOverriddenMaximumLanguageVersion() {
return maximumLanguageVersion;
}
@Override
public void setMaximumLanguageVersion(LanguageVersion maximumLanguageVersion) {
// Only override if different than current value, or if already
// overridden.
if (!isSame(maximumLanguageVersion, super.getMaximumLanguageVersion()) || this.maximumLanguageVersion != null) {
this.maximumLanguageVersion = maximumLanguageVersion;
super.setMaximumLanguageVersion(maximumLanguageVersion);
}
}
public Boolean isOverriddenDeprecated() {
return deprecated;
}
@Override
public boolean isDeprecated() {
return deprecated != null && deprecated.booleanValue();
}
@Override
public void setDeprecated(boolean deprecated) {
// Deprecation does not propagate to the underlying Rule. It is the
// Rule reference itself which is being deprecated.
this.deprecated = deprecated ? deprecated : null;
}
public String getOverriddenName() {
return name;
}
public String getOriginalName() {
return super.getName();
}
@Override
public void setName(String name) {
// Only override if different than current value, or if already
// overridden.
if (!isSame(name, super.getName()) || this.name != null) {
this.name = name;
}
}
@Override
public String getName() {
if (this.name != null) {
return this.name;
}
return super.getName();
}
public String getOverriddenMessage() {
return message;
}
@Override
public void setMessage(String message) {
// Only override if different than current value, or if already
// overridden.
if (!isSame(message, super.getMessage()) || this.message != null) {
this.message = message;
super.setMessage(message);
}
}
public String getOverriddenDescription() {
return description;
}
@Override
public void setDescription(String description) {
// Only override if different than current value, or if already
// overridden.
if (!isSame(description, super.getDescription()) || this.description != null) {
this.description = description;
super.setDescription(description);
}
}
public List<String> getOverriddenExamples() {
return examples;
}
@Override
public void addExample(String example) {
// TODO Meaningful override of examples is hard, because they are merely
// a list of strings. How does one indicate override of a particular
// value? Via index? Rule.setExample(int, String)? But the XML format
// does not provide a means of overriding by index, not unless you took
// the position in the XML file to indicate corresponding index to
// override. But that means you have to override starting from index 0.
// This would be so much easier if examples had to have names, like
// properties.
// Only override if different than current values.
if (!contains(super.getExamples(), example)) {
if (examples == null) {
examples = new ArrayList<>(1);
}
// TODO Fix later. To keep example overrides from being unbounded,
// we're only going to keep track of the last one.
examples.clear();
examples.add(example);
super.addExample(example);
}
}
public String getOverriddenExternalInfoUrl() {
return externalInfoUrl;
}
@Override
public void setExternalInfoUrl(String externalInfoUrl) {
// Only override if different than current value, or if already
// overridden.
if (!isSame(externalInfoUrl, super.getExternalInfoUrl()) || this.externalInfoUrl != null) {
this.externalInfoUrl = externalInfoUrl;
super.setExternalInfoUrl(externalInfoUrl);
}
}
public RulePriority getOverriddenPriority() {
return priority;
}
@Override
public void setPriority(RulePriority priority) {
// Only override if different than current value, or if already
// overridden.
if (priority != super.getPriority() || this.priority != null) {
this.priority = priority;
super.setPriority(priority);
}
}
public List<PropertyDescriptor<?>> getOverriddenPropertyDescriptors() {
return propertyDescriptors == null ? EMPTY_DESCRIPTORS : propertyDescriptors;
}
@Override
public void definePropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) throws IllegalArgumentException {
// Define on the underlying Rule, where it is impossible to have two
// property descriptors with the same name. Therefore, there is no need
// to check if the property is already overridden at this level.
super.definePropertyDescriptor(propertyDescriptor);
if (propertyDescriptors == null) {
propertyDescriptors = new ArrayList<>();
}
propertyDescriptors.add(propertyDescriptor);
}
public Map<PropertyDescriptor<?>, Object> getOverriddenPropertiesByPropertyDescriptor() {
return propertyValues;
}
@Override
public <T> void setProperty(PropertyDescriptor<T> propertyDescriptor, T value) {
// Only override if different than current value.
if (!isSame(super.getProperty(propertyDescriptor), value)) {
if (propertyValues == null) {
propertyValues = new HashMap<>();
}
propertyValues.put(propertyDescriptor, value);
super.setProperty(propertyDescriptor, value);
}
}
public RuleSetReference getRuleSetReference() {
return ruleSetReference;
}
public void setRuleSetReference(RuleSetReference ruleSetReference) {
this.ruleSetReference = ruleSetReference;
}
private static boolean isSame(String s1, String s2) {
return StringUtil.isSame(s1, s2, true, false, true);
}
@SuppressWarnings("PMD.CompareObjectsWithEquals")
private static boolean isSame(Object o1, Object o2) {
if (o1 instanceof Object[] && o2 instanceof Object[]) {
return isSame((Object[]) o1, (Object[]) o2);
}
return o1 == o2 || o1 != null && o2 != null && o1.equals(o2);
}
@SuppressWarnings("PMD.UnusedNullCheckInEquals")
// TODO: fix UnusedNullCheckInEquals rule for Arrays
private static boolean isSame(Object[] a1, Object[] a2) {
return a1 == a2 || a1 != null && a2 != null && Arrays.equals(a1, a2);
}
private static boolean contains(Collection<String> collection, String s1) {
for (String s2 : collection) {
if (isSame(s1, s2)) {
return true;
}
}
return false;
}
@Override
public boolean hasDescriptor(PropertyDescriptor<?> descriptor) {
return propertyDescriptors != null && propertyDescriptors.contains(descriptor)
|| super.hasDescriptor(descriptor);
}
public boolean hasOverriddenProperty(PropertyDescriptor<?> descriptor) {
return propertyValues != null && propertyValues.containsKey(descriptor);
}
@Override
public boolean usesDefaultValues() {
List<PropertyDescriptor<?>> descriptors = getOverriddenPropertyDescriptors();
if (!descriptors.isEmpty()) {
return false;
}
for (PropertyDescriptor<?> desc : descriptors) {
if (!isSame(desc.defaultValue(), getProperty(desc))) {
return false;
}
}
if (!getRule().usesDefaultValues()) {
return false;
}
return true;
}
@Override
public void useDefaultValueFor(PropertyDescriptor<?> desc) {
// not sure if we should go all the way through to the real thing?
getRule().useDefaultValueFor(desc);
if (propertyValues == null) {
return;
}
propertyValues.remove(desc);
if (propertyDescriptors != null) {
propertyDescriptors.remove(desc);
}
}
}