View Javadoc

    1   package net.sourceforge.pmd.lang.rule;
    2
    3   import java.util.ArrayList;
    4   import java.util.Arrays;
    5   import java.util.Collection;
    6   import java.util.HashMap;
    7   import java.util.List;
    8   import java.util.Map;
    9
    10  import net.sourceforge.pmd.PropertyDescriptor;
    11  import net.sourceforge.pmd.Rule;
    12  import net.sourceforge.pmd.RulePriority;
    13  import net.sourceforge.pmd.RuleSetReference;
    14  import net.sourceforge.pmd.lang.Language;
    15  import net.sourceforge.pmd.lang.LanguageVersion;
    16  import net.sourceforge.pmd.util.StringUtil;
    17
    18  /**
    19   * This class represents a Rule which is a reference to Rule defined in another
    20   * RuleSet. All details of the Rule are delegated to the underlying referenced
    21   * Rule, but those operations which modify overridden aspects of the rule are
    22   * explicitly tracked.  Modification operations which set a value to the
    23   * current underlying value do not override.
    24   */
    25  public class RuleReference extends AbstractDelegateRule {
    26
    27  	private Language language;
    28  	private LanguageVersion minimumLanguageVersion;
    29  	private LanguageVersion maximumLanguageVersion;
    30  	private Boolean deprecated;
    31  	private String name;
    32  	private List<PropertyDescriptor<?>> propertyDescriptors;
    33  	private Map<PropertyDescriptor<?>, Object> propertyValues;
    34  	private String message;
    35  	private String description;
    36  	private List<String> examples;
    37  	private String externalInfoUrl;
    38  	private RulePriority priority;
    39  	private RuleSetReference ruleSetReference;
    40
    41  	private static final List<PropertyDescriptor<?>> EMPTY_DESCRIPTORS = new ArrayList<PropertyDescriptor<?>>(0);
    42
    43  	public Language getOverriddenLanguage() {
    44  		return language;
    45  	}
    46
    47  	public RuleReference() {
    48  	}
    49
    50  	public RuleReference(Rule theRule, RuleSetReference theRuleSetReference) {
    51  		setRule(theRule);
    52  		ruleSetReference = theRuleSetReference;
    53  	}
    54
    55  	@Override
    56  	public void setLanguage(Language language) {
    57  		// Only override if different than current value, or if already overridden.
    58  		if (!isSame(language, super.getLanguage()) || this.language != null) {
    59  			this.language = language;
    60  			super.setLanguage(language);
    61  		}
    62  	}
    63
    64  	public LanguageVersion getOverriddenMinimumLanguageVersion() {
    65  		return minimumLanguageVersion;
    66  	}
    67
    68  	@Override
    69  	public void setMinimumLanguageVersion(LanguageVersion minimumLanguageVersion) {
    70  		// Only override if different than current value, or if already overridden.
    71  		if (!isSame(minimumLanguageVersion, super.getMinimumLanguageVersion()) || this.minimumLanguageVersion != null) {
    72  			this.minimumLanguageVersion = minimumLanguageVersion;
    73  			super.setMinimumLanguageVersion(minimumLanguageVersion);
    74  		}
    75  	}
    76
    77  	public LanguageVersion getOverriddenMaximumLanguageVersion() {
    78  		return maximumLanguageVersion;
    79  	}
    80
    81  	@Override
    82  	public void setMaximumLanguageVersion(LanguageVersion maximumLanguageVersion) {
    83  		// Only override if different than current value, or if already overridden.
    84  		if (!isSame(maximumLanguageVersion, super.getMaximumLanguageVersion()) || this.maximumLanguageVersion != null) {
    85  			this.maximumLanguageVersion = maximumLanguageVersion;
    86  			super.setMaximumLanguageVersion(maximumLanguageVersion);
    87  		}
    88  	}
    89
    90  	public Boolean isOverriddenDeprecated() {
    91  		return deprecated;
    92  	}
    93
    94  	@Override
    95  	public boolean isDeprecated() {
    96  		return deprecated != null && deprecated.booleanValue();
    97  	}
    98
    99  	@Override
    100 	public void setDeprecated(boolean deprecated) {
    101 		// Deprecation does not propagate to the underlying Rule.  It is the
    102 		// Rule reference itself which is being deprecated.
    103 		this.deprecated = deprecated ? deprecated : null;
    104 	}
    105
    106 	public String getOverriddenName() {
    107 		return name;
    108 	}
    109
    110 	@Override
    111 	public void setName(String name) {
    112 		// Only override if different than current value, or if already overridden.
    113 		if (!isSame(name, super.getName()) || this.name != null) {
    114 			this.name = name;
    115 			super.setName(name);
    116 		}
    117 	}
    118
    119 	public String getOverriddenMessage() {
    120 		return message;
    121 	}
    122
    123 	@Override
    124 	public void setMessage(String message) {
    125 		// Only override if different than current value, or if already overridden.
    126 		if (!isSame(message, super.getMessage()) || this.message != null) {
    127 			this.message = message;
    128 			super.setMessage(message);
    129 		}
    130 	}
    131
    132 	public String getOverriddenDescription() {
    133 		return description;
    134 	}
    135
    136 	@Override
    137 	public void setDescription(String description) {
    138 		// Only override if different than current value, or if already overridden.
    139 		if (!isSame(description, super.getDescription()) || this.description != null) {
    140 			this.description = description;
    141 			super.setDescription(description);
    142 		}
    143 	}
    144
    145 	public List<String> getOverriddenExamples() {
    146 		return examples;
    147 	}
    148
    149 	@Override
    150 	public void addExample(String example) {
    151 		// TODO Meaningful override of examples is hard, because they are merely
    152 		// a list of strings.  How does one indicate override of a particular
    153 		// value?  Via index?  Rule.setExample(int, String)?  But the XML format
    154 		// does not provide a means of overriding by index, not unless you took
    155 		// the position in the XML file to indicate corresponding index to
    156 		// override.  But that means you have to override starting from index 0.
    157 		// This would be so much easier if examples had to have names, like
    158 		// properties.
    159
    160 		// Only override if different than current values.
    161 		if (!contains(super.getExamples(), example)) {
    162 			if (examples == null) {
    163 				examples = new ArrayList<String>(1);
    164 			}
    165 			// TODO Fix later. To keep example overrides from being unbounded, we're only going to keep track of the last one.
    166 			examples.clear();
    167 			examples.add(example);
    168 			super.addExample(example);
    169 		}
    170 	}
    171
    172 	public String getOverriddenExternalInfoUrl() {
    173 		return externalInfoUrl;
    174 	}
    175
    176 	@Override
    177 	public void setExternalInfoUrl(String externalInfoUrl) {
    178 		// Only override if different than current value, or if already overridden.
    179 		if (!isSame(externalInfoUrl, super.getExternalInfoUrl()) || this.externalInfoUrl != null) {
    180 			this.externalInfoUrl = externalInfoUrl;
    181 			super.setExternalInfoUrl(externalInfoUrl);
    182 		}
    183 	}
    184
    185 	public RulePriority getOverriddenPriority() {
    186 		return priority;
    187 	}
    188
    189 	@Override
    190 	public void setPriority(RulePriority priority) {
    191 		// Only override if different than current value, or if already overridden.
    192 		if (priority != super.getPriority() || this.priority != null) {
    193 			this.priority = priority;
    194 			super.setPriority(priority);
    195 		}
    196 	}
    197
    198 	public List<PropertyDescriptor<?>> getOverriddenPropertyDescriptors() {
    199
    200 		return propertyDescriptors == null ?
    201 				EMPTY_DESCRIPTORS :
    202 					propertyDescriptors;
    203 	}
    204
    205 	@Override
    206 	public void definePropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) throws IllegalArgumentException {
    207 		// Define on the underlying Rule, where it is impossible to have two
    208 		// property descriptors with the same name.  Therefore, there is no need
    209 		// to check if the property is already overridden at this level.
    210 		super.definePropertyDescriptor(propertyDescriptor);
    211 		if (propertyDescriptors == null) {
    212 			propertyDescriptors = new ArrayList<PropertyDescriptor<?>>();
    213 		}
    214 		propertyDescriptors.add(propertyDescriptor);
    215 	}
    216
    217 	public Map<PropertyDescriptor<?>, Object> getOverriddenPropertiesByPropertyDescriptor() {
    218 		return propertyValues;
    219 	}
    220
    221 	@Override
    222 	public <T> void setProperty(PropertyDescriptor<T> propertyDescriptor, T value) {
    223 		// Only override if different than current value.
    224 		if (!isSame(super.getProperty(propertyDescriptor), value)) {
    225 			if (propertyValues == null) {
    226 				propertyValues = new HashMap<PropertyDescriptor<?>, Object>();
    227 			}
    228 			propertyValues.put(propertyDescriptor, value);
    229 			super.setProperty(propertyDescriptor, value);
    230 		}
    231 	}
    232
    233 	public RuleSetReference getRuleSetReference() {
    234 		return ruleSetReference;
    235 	}
    236
    237 	public void setRuleSetReference(RuleSetReference ruleSetReference) {
    238 		this.ruleSetReference = ruleSetReference;
    239 	}
    240
    241 	private static boolean isSame(String s1, String s2) {
    242 		return StringUtil.isSame(s1, s2, true, false, true);
    243 	}
    244
    245 	@SuppressWarnings("PMD.CompareObjectsWithEquals")
    246 	private static boolean isSame(Object o1, Object o2) {
    247 		if (o1 instanceof Object[] && o2 instanceof Object[]) {
    248 			return isSame((Object[])o1, (Object[])o2);
    249 		}
    250 		return o1 == o2 || (o1 != null && o2 != null && o1.equals(o2));
    251 	}
    252
    253 	@SuppressWarnings("PMD.UnusedNullCheckInEquals") //TODO: fix UnusedNullCheckInEquals rule for Arrays
    254 	private static boolean isSame(Object[] a1, Object[] a2) {
    255 		return a1 == a2 || (a1 != null && a2 != null && Arrays.equals(a1, a2));
    256 	}
    257
    258 	private static boolean contains(Collection<String> collection, String s1) {
    259 		for (String s2 : collection) {
    260 			if (isSame(s1, s2)) {
    261 				return true;
    262 			}
    263 		}
    264 		return false;
    265 	}
    266
    267 	public boolean hasDescriptor(PropertyDescriptor<?> descriptor) {
    268 		return (propertyDescriptors != null && propertyDescriptors.contains(descriptor)) ||
    269 		super.hasDescriptor(descriptor);
    270 	}
    271
    272 	public boolean hasOverriddenProperty(PropertyDescriptor<?> descriptor) {
    273 		return propertyValues != null && propertyValues.containsKey(descriptor);
    274 	}
    275
    276 	public boolean usesDefaultValues() {
    277
    278 		List<PropertyDescriptor<?>> descriptors = getOverriddenPropertyDescriptors();
    279 		if (!descriptors.isEmpty()) {
    280 			return false;
    281 		}
    282
    283 		for (PropertyDescriptor<?> desc : descriptors) {
    284 			if (!isSame(desc.defaultValue(), getProperty(desc))) {
    285 				return false;
    286 			}
    287 		}
    288
    289 		if (!getRule().usesDefaultValues()) {
    290 			return false;
    291 		}
    292
    293 		return true;
    294 	}
    295
    296 	public void useDefaultValueFor(PropertyDescriptor<?> desc) {
    297
    298 		// not sure if we should go all the way through to the real thing?
    299 				getRule().useDefaultValueFor(desc);
    300
    301 				if (propertyValues == null) return;
    302
    303 				propertyValues.remove(desc);
    304
    305 				if (propertyDescriptors != null) {
    306 					propertyDescriptors.remove(desc);
    307 				}
    308 	}
    309 }