001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd;
005
006 import java.io.InputStream;
007 import java.util.ArrayList;
008 import java.util.List;
009
010 import net.sourceforge.pmd.util.ResourceLoader;
011 import net.sourceforge.pmd.util.StringUtil;
012
013 /**
014 * This class is used to parse a RuleSet reference value. Most commonly used for specifying a
015 * RuleSet to process, or in a Rule 'ref' attribute value in the RuleSet XML. The RuleSet reference
016 * can refer to either an external RuleSet or the current RuleSet when used as a Rule 'ref'
017 * attribute value. An individual Rule in the RuleSet can be indicated.
018 *
019 * For an external RuleSet, referring to the entire RuleSet, the format is <i>ruleSetName</i>,
020 * where the RuleSet name is either a resource file path to a RuleSet that ends with
021 * <code>'.xml'</code>.</li>, or a simple RuleSet name.
022 *
023 * A simple RuleSet name, is one which contains no path separators, and either contains a '-' or is
024 * entirely numeric release number. A simple name of the form <code>[language]-[name]</code> is
025 * short for the full RuleSet name <code>rulesets/[language]/[name].xml</code>. A numeric release
026 * simple name of the form <code>[release]</code> is short for the full PMD Release RuleSet name
027 * <code>rulesets/releases/[release].xml</code>.
028 *
029 * For an external RuleSet, referring to a single Rule, the format is <i>ruleSetName/ruleName</i>,
030 * where the RuleSet name is as described above. A Rule with the <i>ruleName</i> should exist
031 * in this external RuleSet.
032 *
033 * For the current RuleSet, the format is <i>ruleName</i>, where the Rule name is not RuleSet name
034 * (i.e. contains no path separators, '-' or '.xml' in it, and is not all numeric). A Rule with the
035 * <i>ruleName</i> should exist in the current RuleSet.
036 *
037 * <table>
038 * <caption>Examples</caption>
039 * <thead>
040 * <tr>
041 * <th>String</th>
042 * <th>RuleSet file name</th>
043 * <th>Rule</th>
044 * </tr>
045 * </thead>
046 * <tbody>
047 * <tr>
048 * <td>rulesets/java/basic.xml</td>
049 * <td>rulesets/java/basic.xml</td>
050 * <td>all</td>
051 * </tr>
052 * <tr>
053 * <td>java-basic</td>
054 * <td>rulesets/java/basic.xml</td>
055 * <td>all</td>
056 * </tr>
057 * <tr>
058 * <td>50</td>
059 * <td>rulesets/releases/50.xml</td>
060 * <td>all</td>
061 * </tr>
062 * <tr>
063 * <td>rulesets/java/basic.xml/EmptyCatchBlock</td>
064 * <td>rulesets/java/basic.xml</td>
065 * <td>EmptyCatchBlock</td>
066 * </tr>
067 * <tr>
068 * <td>EmptyCatchBlock</td>
069 * <td>null</td>
070 * <td>EmptyCatchBlock</td>
071 * </tr>
072 * </tbody>
073 * </table>
074 */
075 public class RuleSetReferenceId {
076 private final boolean external;
077 private final String ruleSetFileName;
078 private final boolean allRules;
079 private final String ruleName;
080 private final RuleSetReferenceId externalRuleSetReferenceId;
081
082 /**
083 * Construct a RuleSetReferenceId for the given single ID string.
084 * @param id The id string.
085 * @throws IllegalArgumentException If the ID contains a comma character.
086 */
087 public RuleSetReferenceId(final String id) {
088 this(id, null);
089 }
090
091 /**
092 * Construct a RuleSetReferenceId for the given single ID string.
093 * If an external RuleSetReferenceId is given, the ID must refer to a non-external Rule. The
094 * external RuleSetReferenceId will be responsible for producing the InputStream containing
095 * the Rule.
096 *
097 * @param id The id string.
098 * @param externalRuleSetReferenceId A RuleSetReferenceId to associate with this new instance.
099 * @throws IllegalArgumentException If the ID contains a comma character.
100 * @throws IllegalArgumentException If external RuleSetReferenceId is not external.
101 * @throws IllegalArgumentException If the ID is not Rule reference when there is an external RuleSetReferenceId.
102 */
103 public RuleSetReferenceId(final String id, final RuleSetReferenceId externalRuleSetReferenceId) {
104 if (externalRuleSetReferenceId != null && !externalRuleSetReferenceId.isExternal()) {
105 throw new IllegalArgumentException("Cannot pair with non-external <" + externalRuleSetReferenceId + ">.");
106 }
107 if (id != null && id.indexOf(',') >= 0) {
108 throw new IllegalArgumentException("A single RuleSetReferenceId cannot contain ',' (comma) characters: "
109 + id);
110 }
111
112 // Damn this parsing sucks, but my brain is just not working to let me write a simpler scheme.
113 if (StringUtil.isEmpty(id) || isFullRuleSetName(id)) {
114 // A full RuleSet name
115 external = true;
116 ruleSetFileName = id;
117 allRules = true;
118 ruleName = null;
119 } else {
120 // Find last path separator if it exists...
121 final int separatorIndex = Math.max(id.lastIndexOf('/'), id.lastIndexOf('\\'));
122 if (separatorIndex >= 0 && separatorIndex != id.length() - 1) {
123 final String name = id.substring(0, separatorIndex);
124 external = true;
125 if (isFullRuleSetName(name)) {
126 // A full RuleSet name
127 ruleSetFileName = name;
128 } else {
129 // Likely a simple RuleSet name
130 int index = name.indexOf('-');
131 if (index >= 0) {
132 // Standard short name
133 ruleSetFileName = "rulesets/" + name.substring(0, index) + "/" + name.substring(index + 1)
134 + ".xml";
135 } else {
136 // A release RuleSet?
137 if (name.matches("[0-9]+.*")) {
138 ruleSetFileName = "rulesets/releases/" + name + ".xml";
139 } else {
140 // Appears to be a non-standard RuleSet name
141 ruleSetFileName = name;
142 }
143 }
144 }
145
146 // Everything left should be a Rule name
147 allRules = false;
148 ruleName = id.substring(separatorIndex + 1);
149 } else {
150 // Likely a simple RuleSet name
151 int index = id.indexOf('-');
152 if (index >= 0) {
153 // Standard short name
154 external = true;
155 ruleSetFileName = "rulesets/" + id.substring(0, index) + "/" + id.substring(index + 1) + ".xml";
156 allRules = true;
157 ruleName = null;
158 } else {
159 // A release RuleSet?
160 if (id.matches("[0-9]+.*")) {
161 external = true;
162 ruleSetFileName = "rulesets/releases/" + id + ".xml";
163 allRules = true;
164 ruleName = null;
165 } else {
166 // Must be a Rule name
167 external = externalRuleSetReferenceId != null ? true : false;
168 ruleSetFileName = externalRuleSetReferenceId != null ? externalRuleSetReferenceId
169 .getRuleSetFileName() : null;
170 allRules = false;
171 ruleName = id;
172 }
173 }
174 }
175 }
176
177 if (this.external && this.ruleName != null && !this.ruleName.equals(id) && externalRuleSetReferenceId != null) {
178 throw new IllegalArgumentException("Cannot pair external <" + this + "> with external <"
179 + externalRuleSetReferenceId + ">.");
180 }
181 this.externalRuleSetReferenceId = externalRuleSetReferenceId;
182 }
183
184 private static boolean isFullRuleSetName(String name) {
185 return name.endsWith(".xml");
186 }
187
188 /**
189 * Parse a String comma separated list of RuleSet reference IDs into a List of
190 * RuleReferenceId instances.
191 * @param referenceString A comma separated list of RuleSet reference IDs.
192 * @return The corresponding List of RuleSetReferenceId instances.
193 */
194 public static List<RuleSetReferenceId> parse(String referenceString) {
195 List<RuleSetReferenceId> references = new ArrayList<RuleSetReferenceId>();
196 if (referenceString.indexOf(',') == -1) {
197 references.add(new RuleSetReferenceId(referenceString));
198 } else {
199 for (String name : referenceString.split(",")) {
200 references.add(new RuleSetReferenceId(name));
201 }
202 }
203 return references;
204 }
205
206 /**
207 * Is this an external RuleSet reference?
208 * @return <code>true</code> if this is an external reference, <code>false</code> otherwise.
209 */
210 public boolean isExternal() {
211 return external;
212 }
213
214 /**
215 * Is this a reference to all Rules in a RuleSet, or a single Rule?
216 * @return <code>true</code> if this is a reference to all Rules, <code>false</code> otherwise.
217 */
218 public boolean isAllRules() {
219 return allRules;
220 }
221
222 /**
223 * Get the RuleSet file name.
224 * @return The RuleSet file name if this is an external reference, <code>null</code> otherwise.
225 */
226 public String getRuleSetFileName() {
227 return ruleSetFileName;
228 }
229
230 /**
231 * Get the Rule name.
232 * @return The Rule name.
233 * The Rule name.
234 */
235 public String getRuleName() {
236 return ruleName;
237 }
238
239 /**
240 * Try to load the RuleSet resource with the specified ClassLoader. Multiple attempts to get
241 * independent InputStream instances may be made, so subclasses must ensure they support this
242 * behavior. Delegates to an external RuleSetReferenceId if there is one associated with this
243 * instance.
244 *
245 * @param classLoader The ClassLoader to use.
246 * @return An InputStream to that resource.
247 * @throws RuleSetNotFoundException if unable to find a resource.
248 */
249 public InputStream getInputStream(ClassLoader classLoader) throws RuleSetNotFoundException {
250 if (externalRuleSetReferenceId == null) {
251 InputStream in = StringUtil.isEmpty(ruleSetFileName) ? null : ResourceLoader.loadResourceAsStream(
252 ruleSetFileName, classLoader);
253 if (in == null) {
254 throw new RuleSetNotFoundException(
255 "Can't find resource "
256 + ruleSetFileName
257 + ". Make sure the resource is a valid file or URL or is on the CLASSPATH. Here's the current classpath: "
258 + System.getProperty("java.class.path"));
259 }
260 return in;
261 } else {
262 return externalRuleSetReferenceId.getInputStream(classLoader);
263 }
264 }
265
266 /**
267 * Return the String form of this Rule reference.
268 * @return Return the String form of this Rule reference, which is <i>ruleSetFileName</i> for
269 * all Rule external references, <i>ruleSetFileName/ruleName</i>, for a single Rule
270 * external references, or <i>ruleName</i> otherwise.
271 */
272 public String toString() {
273 if (ruleSetFileName != null) {
274 if (allRules) {
275 return ruleSetFileName;
276 } else {
277 return ruleSetFileName + "/" + ruleName;
278 }
279
280 } else {
281 if (allRules) {
282 return "anonymous all Rule";
283 } else {
284 return ruleName;
285 }
286 }
287 }
288 }
|