1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package test.net.sourceforge.pmd;
5   
6   import static org.junit.Assert.assertEquals;
7   import static org.junit.Assert.assertFalse;
8   import static org.junit.Assert.assertNotNull;
9   import static org.junit.Assert.assertNull;
10  import static org.junit.Assert.assertTrue;
11  import static org.junit.Assert.fail;
12  
13  import java.io.BufferedReader;
14  import java.io.ByteArrayInputStream;
15  import java.io.ByteArrayOutputStream;
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.io.InputStreamReader;
19  import java.util.ArrayList;
20  import java.util.HashSet;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Properties;
24  import java.util.Set;
25  import java.util.StringTokenizer;
26  
27  import javax.xml.parsers.ParserConfigurationException;
28  import javax.xml.parsers.SAXParser;
29  import javax.xml.parsers.SAXParserFactory;
30  
31  import junit.framework.JUnit4TestAdapter;
32  import net.sourceforge.pmd.PMD;
33  import net.sourceforge.pmd.Rule;
34  import net.sourceforge.pmd.RuleReference;
35  import net.sourceforge.pmd.RuleSet;
36  import net.sourceforge.pmd.RuleSetFactory;
37  import net.sourceforge.pmd.RuleSetNotFoundException;
38  import net.sourceforge.pmd.RuleSetWriter;
39  import net.sourceforge.pmd.rules.UnusedLocalVariableRule;
40  import net.sourceforge.pmd.util.ResourceLoader;
41  
42  import org.junit.Before;
43  import org.junit.Test;
44  import org.xml.sax.InputSource;
45  import org.xml.sax.SAXException;
46  import org.xml.sax.SAXParseException;
47  import org.xml.sax.helpers.DefaultHandler;
48  
49  import test.net.sourceforge.pmd.testframework.TestDescriptor;
50  
51  public class RuleSetFactoryTest {
52  
53  	private boolean isJdk14;
54  
55  	@Before
56  	public void setUp() {
57  		try {
58  			Class.forName("java.lang.Appendable");
59  		} catch (Throwable t) {
60  			isJdk14 = true;
61  		}
62  	}
63  
64  	@Test
65  	public void testRuleSetFileName() throws RuleSetNotFoundException {
66  		RuleSet rs = loadRuleSet(EMPTY_RULESET);
67  		assertNull("RuleSet file name not expected", rs.getFileName());
68  
69  		RuleSetFactory rsf = new RuleSetFactory();
70  		rs = rsf.createSingleRuleSet("rulesets/basic.xml");
71  		assertEquals("wrong RuleSet file name", rs.getFileName(), "rulesets/basic.xml");
72  	}
73  
74  	@Test
75  	public void testNoRuleSetFileName() {
76  		RuleSet rs = loadRuleSet(EMPTY_RULESET);
77  		assertNull("RuleSet file name not expected", rs.getFileName());
78  	}
79  
80  	@Test
81  	public void testRefs() throws Throwable {
82  		InputStream in = ResourceLoader.loadResourceAsStream("rulesets/favorites.xml", this.getClass().getClassLoader());
83  		if (in == null) {
84  			throw new RuleSetNotFoundException(
85  					"Can't find resource   Make sure the resource is a valid file or URL or is on the CLASSPATH.  Here's the current classpath: "
86  							+ System.getProperty("java.class.path"));
87  		}
88  		RuleSetFactory rsf = new RuleSetFactory();
89  		RuleSet rs = rsf.createSingleRuleSet("rulesets/favorites.xml");
90  		assertNotNull(rs.getRuleByName("WhileLoopsMustUseBraces"));
91  	}
92  
93  	@Test(expected = RuleSetNotFoundException.class)
94  	public void testRuleSetNotFound() throws RuleSetNotFoundException {
95  		RuleSetFactory rsf = new RuleSetFactory();
96  		rsf.createSingleRuleSet("fooooo");
97  	}
98  
99  	@Test
100 	public void testCreateEmptyRuleSet() {
101 		RuleSet rs = loadRuleSet(EMPTY_RULESET);
102 		assertEquals("test", rs.getName());
103 		assertEquals(0, rs.size());
104 	}
105 
106 	@Test
107 	public void testSingleRule() {
108 		RuleSet rs = loadRuleSet(SINGLE_RULE);
109 		assertEquals(1, rs.size());
110 		Rule r = rs.getRules().iterator().next();
111 		assertEquals("MockRuleName", r.getName());
112 		assertEquals("net.sourceforge.pmd.MockRule", r.getRuleClass());
113 		assertEquals("avoid the mock rule", r.getMessage());
114 	}
115 
116 	@Test
117 	public void testMultipleRules() {
118 		RuleSet rs = loadRuleSet(MULTIPLE_RULES);
119 		assertEquals(2, rs.size());
120 		Set<String> expected = new HashSet<String>();
121 		expected.add("MockRuleName1");
122 		expected.add("MockRuleName2");
123 		for (Iterator<Rule> i = rs.getRules().iterator(); i.hasNext();) {
124 			assertTrue(expected.contains(i.next().getName()));
125 		}
126 	}
127 
128 	@Test
129 	public void testSingleRuleWithPriority() {
130 		assertEquals(3, loadFirstRule(PRIORITY).getPriority());
131 	}
132 
133 	@Test
134 	public void testProps() {
135 		Rule r = loadFirstRule(PROPERTIES);
136 		assertTrue(r.hasProperty("foo"));
137 		assertEquals("bar", r.getStringProperty("foo"));
138 		assertEquals(2, r.getIntProperty("fooint"));
139 		assertTrue(r.hasProperty("fooBoolean"));
140 		assertTrue(r.getBooleanProperty("fooBoolean"));
141 		assertTrue(r.hasProperty("fooDouble"));
142 		assertEquals(1.0, r.getDoubleProperty("fooDouble"), 0.05);
143 		assertTrue(!r.hasProperty("BuggleFish"));
144 		assertTrue(r.getDescription().indexOf("testdesc2") != -1);
145 	}
146 
147 	@Test
148 	public void testXPathPluginnameProperty() {
149 		Rule r = loadFirstRule(XPATH_PLUGINNAME);
150 		assertTrue(r.hasProperty("pluginname"));
151 	}
152 
153 	@Test
154 	public void testXPath() {
155 		Rule r = loadFirstRule(XPATH);
156 		assertTrue(r.hasProperty("xpath"));
157 		assertTrue(r.getStringProperty("xpath").indexOf(" //Block ") != -1);
158 	}
159 
160 	@Test
161 	public void testFacadesOffByDefault() {
162 		Rule r = loadFirstRule(XPATH);
163 		assertFalse(r.usesDFA());
164 	}
165 
166 	@Test
167 	public void testDFAFlag() {
168 		assertTrue(loadFirstRule(DFA).usesDFA());
169 	}
170 
171 	@Test
172 	public void testExternalReferenceOverride() {
173 		Rule r = loadFirstRule(REF_OVERRIDE);
174 		assertEquals("TestNameOverride", r.getName());
175 		assertEquals("Test message override", r.getMessage());
176 		assertEquals("Test description override", r.getDescription());
177 		assertEquals("Test example override", r.getExample());
178 		assertEquals("Test that both example are stored", 2, r.getExamples().size());
179 		assertEquals(3, r.getPriority());
180 		assertTrue(r.hasProperty("test2"));
181 		assertEquals("override2", r.getStringProperty("test2"));
182 		assertTrue(r.hasProperty("test3"));
183 		assertEquals("override3", r.getStringProperty("test3"));
184 		assertTrue(r.hasProperty("test4"));
185 		assertEquals("new property", r.getStringProperty("test4"));
186 	}
187 
188 	@Test
189 	public void testOverrideMessage() {
190 		Rule r = loadFirstRule(REF_OVERRIDE_ORIGINAL_NAME);
191 		assertEquals("TestMessageOverride", r.getMessage());
192 	}
193 
194 	@Test
195 	public void testOverrideMessageOneElem() {
196 		Rule r = loadFirstRule(REF_OVERRIDE_ORIGINAL_NAME_ONE_ELEM);
197 		assertEquals("TestMessageOverride", r.getMessage());
198 	}
199 
200 	@Test(expected = IllegalArgumentException.class)
201 	public void testExternalRef() throws IllegalArgumentException {
202 		loadFirstRule(REF_MISPELLED_XREF);
203 	}
204 
205 	@Test
206 	public void testSetPriority() {
207 		RuleSetFactory rsf = new RuleSetFactory();
208 		rsf.setMinimumPriority(2);
209 		assertEquals(0, rsf.createRuleSet(new ByteArrayInputStream(SINGLE_RULE.getBytes())).size());
210 		rsf.setMinimumPriority(4);
211 		assertEquals(1, rsf.createRuleSet(new ByteArrayInputStream(SINGLE_RULE.getBytes())).size());
212 	}
213 
214 	@Test
215 	public void testIncludeExcludePatterns() {
216 		RuleSet ruleSet = loadRuleSet(INCLUDE_EXCLUDE_RULESET);
217 
218 		assertNotNull("Include patterns", ruleSet.getIncludePatterns());
219 		assertEquals("Include patterns size", 2, ruleSet.getIncludePatterns().size());
220 		assertEquals("Include pattern #1", "include1", ruleSet.getIncludePatterns().get(0));
221 		assertEquals("Include pattern #2", "include2", ruleSet.getIncludePatterns().get(1));
222 
223 		assertNotNull("Exclude patterns", ruleSet.getExcludePatterns());
224 		assertEquals("Exclude patterns size", 3, ruleSet.getExcludePatterns().size());
225 		assertEquals("Exclude pattern #1", "exclude1", ruleSet.getExcludePatterns().get(0));
226 		assertEquals("Exclude pattern #2", "exclude2", ruleSet.getExcludePatterns().get(1));
227 		assertEquals("Exclude pattern #3", "exclude3", ruleSet.getExcludePatterns().get(2));
228 	}
229 
230 	@Test
231 	public void testAllPMDBuiltInRulesNeedSinceAndCheckURL() throws IOException, RuleSetNotFoundException,
232 			ParserConfigurationException, SAXException {
233 		int invalidExternalInfoURL = 0;
234 		int invalidSinceAttributes = 0;
235 		String messages = "";
236 		List<String> ruleSetFileNames = getRuleSetFileNames();
237 		for (String fileName : ruleSetFileNames) {
238 			RuleSet ruleSet = loadRuleSetByFileName(fileName);
239 			for (Rule rule : ruleSet.getRules()) {
240 				// Is since missing ?
241 				if (rule.getSince() == null) {
242 					invalidSinceAttributes++;
243 					messages += "Rule " + fileName + "/" + rule.getName() + " is missing 'since' attribute\n";
244 				}
245 				// Is URL valid ?
246 				if (rule.getExternalInfoUrl() == null || "".equalsIgnoreCase(rule.getExternalInfoUrl())) {
247 					messages += "Rule " + fileName + "/" + rule.getName() + " is missing 'externalInfoURL' attribute\n";
248 				}
249 				else {
250 					  String expectedExternalInfoURL = "http://pmd.sourceforge.net/rules/" + fileName.replaceAll("rulesets/","").replaceAll(".xml","") + ".html#" + rule.getName();
251 					  if ( ! expectedExternalInfoURL.equals(rule.getExternalInfoUrl())) {
252 							messages += "Rule " + fileName + "/" + rule.getName() + " seems to have an invalid 'externalInfoURL' value (" + rule.getExternalInfoUrl() + "), it should be:" + expectedExternalInfoURL + "\n";
253 							invalidExternalInfoURL++;
254 					  }
255 				}
256 			}
257 		}
258 		// We do this at the end to ensure we test ALL the rules before failing the test
259 		if ( invalidExternalInfoURL > 0 || invalidSinceAttributes > 0 ) {
260 			fail("All built-in PMD rules need 'since' attribute ("+ invalidSinceAttributes + " are missing) and a proper ExternalURLInfo (" + invalidExternalInfoURL + " are invalid)" + "\n" + messages);
261 		}
262 	}
263 
264 
265 	// FUTURE Enable this test when we're ready to rename rules
266 	/*
267 	@Test
268 	public void testAllPMDBuiltInRulesShouldEndWithRule() throws IOException, RuleSetNotFoundException,
269 			ParserConfigurationException, SAXException {
270 		boolean allValid = true;
271 		List<String> ruleSetFileNames = getRuleSetFileNames();
272 		for (String fileName : ruleSetFileNames) {
273 			RuleSet ruleSet = loadRuleSetByFileName(fileName);
274 			for (Rule rule : ruleSet.getRules()) {
275 				if (!rule.getRuleClass().endsWith("Rule")) {
276 					allValid = false;
277 					System.err.println("Rule " + fileName + "/" + rule.getName()
278 							+ " does not have 'ruleClass' that ends with 'Rule': " + rule.getRuleClass());
279 				}
280 			}
281 		}
282 		assertTrue("All built-in PMD rule classes should end with 'Rule'.", allValid);
283 	}
284 	*/
285 
286 	@Test
287 	public void testXmlSchema() throws IOException, RuleSetNotFoundException, ParserConfigurationException,
288 			SAXException {
289 		if (isJdk14) {
290 			// ignore failure with jdk 1.4
291 			return;
292 		}
293 
294 		boolean allValid = true;
295 		List<String> ruleSetFileNames = getRuleSetFileNames();
296 		for (String fileName : ruleSetFileNames) {
297 			boolean valid = validateAgainstSchema(fileName);
298 			allValid = allValid && valid;
299 		}
300 		assertTrue("All XML must parse without producing validation messages.", allValid);
301 	}
302 
303 	@Test
304 	public void testDtd() throws IOException, RuleSetNotFoundException, ParserConfigurationException, SAXException {
305 		boolean allValid = true;
306 		List<String> ruleSetFileNames = getRuleSetFileNames();
307 		for (String fileName : ruleSetFileNames) {
308 			boolean valid = validateAgainstDtd(fileName);
309 			allValid = allValid && valid;
310 		}
311 		assertTrue("All XML must parse without producing validation messages.", allValid);
312 	}
313 
314 	@Test
315 	public void testReadWriteRoundTrip() throws IOException, RuleSetNotFoundException, ParserConfigurationException,
316 			SAXException {
317 
318 		List<String> ruleSetFileNames = getRuleSetFileNames();
319 		for (String fileName : ruleSetFileNames) {
320 		    testRuleSet(fileName);
321 		}
322 	}
323 
324     @Test
325     public void testWindowsJdk14Bug() throws IOException, RuleSetNotFoundException, ParserConfigurationException,
326             SAXException {
327 
328         if (TestDescriptor.inRegressionTestMode()) {
329             // skip this test if we're only running regression tests
330             return;
331         }
332         // This fails only on Windows running the weaved pmd version
333         testRuleSet("regress/test/net/sourceforge/pmd/xml/j2ee.xml");
334     }
335 
336 	public void testRuleSet(String fileName) throws IOException, RuleSetNotFoundException, ParserConfigurationException,
337 	        SAXException {
338 
339 		// Load original XML
340 		String xml1 = readFullyToString(ResourceLoader.loadResourceAsStream(fileName));
341 
342 		// Load the original RuleSet
343 		RuleSet ruleSet1 = loadRuleSetByFileName(fileName);
344 
345 		// Write to XML, first time
346 		ByteArrayOutputStream outputStream1 = new ByteArrayOutputStream();
347 		RuleSetWriter writer1 = new RuleSetWriter(outputStream1);
348 		writer1.write(ruleSet1);
349 		writer1.close();
350 		String xml2 = new String(outputStream1.toByteArray());
351 
352 		// Read RuleSet from XML, first time
353 		RuleSetFactory ruleSetFactory = new RuleSetFactory();
354 		RuleSet ruleSet2 = ruleSetFactory.createRuleSet(new ByteArrayInputStream(outputStream1.toByteArray()));
355 
356 		// Do write/read a 2nd time, just to be sure
357 
358 		// Write to XML, second time
359 		ByteArrayOutputStream outputStream2 = new ByteArrayOutputStream();
360 		RuleSetWriter writer2 = new RuleSetWriter(outputStream2);
361 		writer2.write(ruleSet2);
362 		writer2.close();
363 		String xml3 = new String(outputStream2.toByteArray());
364 
365 		// System.out.println("xml1: " + xml1);
366 		// System.out.println("xml2: " + xml2);
367 		// System.out.println("xml3: " + xml3);
368 
369 		// Read RuleSet from XML, second time
370 		RuleSet ruleSet3 = ruleSetFactory.createRuleSet(new ByteArrayInputStream(outputStream2.toByteArray()));
371 
372 		// The 2 written XMLs should all be valid w.r.t Schema/DTD
373 		if (!isJdk14) {
374 			assertTrue("1st roundtrip RuleSet XML is not valid against Schema",
375 					validateAgainstSchema(new ByteArrayInputStream(xml2.getBytes())));
376 			assertTrue("2nd roundtrip RuleSet XML is not valid against Schema",
377 					validateAgainstSchema(new ByteArrayInputStream(xml3.getBytes())));
378 		}
379 		assertTrue("1st roundtrip RuleSet XML is not valid against DTD",
380 				validateAgainstDtd(new ByteArrayInputStream(xml2.getBytes())));
381 		assertTrue("2nd roundtrip RuleSet XML is not valid against DTD",
382 				validateAgainstDtd(new ByteArrayInputStream(xml3.getBytes())));
383 
384 		// All 3 versions of the RuleSet should be the same
385 		assertEqualsRuleSet("Original RuleSet and 1st roundtrip Ruleset not the same", ruleSet1, ruleSet2);
386 		assertEqualsRuleSet("1st roundtrip Ruleset and 2nd roundtrip RuleSet not the same", ruleSet2, ruleSet3);
387 
388 		// It's hard to compare the XML DOMs.  At least the roundtrip ones should textually be the same.
389 		assertEquals("1st roundtrip RuleSet XML and 2nd roundtrip RuleSet XML", xml2, xml3);
390 	}
391 
392 	private void assertEqualsRuleSet(String message, RuleSet ruleSet1, RuleSet ruleSet2) {
393 		assertEquals(message + ", RuleSet name", ruleSet1.getName(), ruleSet2.getName());
394 		assertEquals(message + ", RuleSet description", ruleSet1.getDescription(), ruleSet2.getDescription());
395 		assertEquals(message + ", RuleSet language", ruleSet1.getLanguage(), ruleSet2.getLanguage());
396 		assertEquals(message + ", RuleSet exclude patterns", ruleSet1.getExcludePatterns(), ruleSet2.getExcludePatterns());
397 		assertEquals(message + ", RuleSet include patterns", ruleSet1.getIncludePatterns(), ruleSet2.getIncludePatterns());
398 		assertEquals(message + ", RuleSet rule count", ruleSet1.getRules().size(), ruleSet2.getRules().size());
399 
400 		for (int i = 0; i < ruleSet1.getRules().size(); i++) {
401 			Rule rule1 = ((List<Rule>)ruleSet1.getRules()).get(i);
402 			Rule rule2 = ((List<Rule>)ruleSet2.getRules()).get(i);
403 
404 			assertFalse(message + ", Different RuleReference",
405 					((rule1 instanceof RuleReference) && !(rule2 instanceof RuleReference))
406 							|| (!(rule1 instanceof RuleReference) && (rule2 instanceof RuleReference)));
407 
408 			if (rule1 instanceof RuleReference) {
409 				RuleReference ruleReference1 = (RuleReference)rule1;
410 				RuleReference ruleReference2 = (RuleReference)rule2;
411 				assertEquals(message + ", RuleReference overridden name", ruleReference1.getOverriddenName(),
412 						ruleReference2.getOverriddenName());
413 				assertEquals(message + ", RuleReference overridden description", ruleReference1.getOverriddenDescription(),
414 						ruleReference2.getOverriddenDescription());
415 				assertEquals(message + ", RuleReference overridden message", ruleReference1.getOverriddenMessage(),
416 						ruleReference2.getOverriddenMessage());
417 				assertEquals(message + ", RuleReference overridden external info url",
418 						ruleReference1.getOverriddenExternalInfoUrl(), ruleReference2.getOverriddenExternalInfoUrl());
419 				assertEquals(message + ", RuleReference overridden priority", ruleReference1.getOverriddenPriority(),
420 						ruleReference2.getOverriddenPriority());
421 				assertEquals(message + ", RuleReference overridden examples", ruleReference1.getOverriddenExamples(),
422 						ruleReference2.getOverriddenExamples());
423 				assertEquals(message + ", RuleReference overridden properties", ruleReference1.getOverriddenProperties(),
424 						ruleReference2.getOverriddenProperties());
425 			}
426 
427 			assertEquals(message + ", Rule name", rule1.getName(), rule2.getName());
428 			assertEquals(message + ", Rule class", rule1.getRuleClass(), rule2.getRuleClass());
429 			assertEquals(message + ", Rule description", rule1.getDescription(), rule2.getDescription());
430 			assertEquals(message + ", Rule message", rule1.getMessage(), rule2.getMessage());
431 			assertEquals(message + ", Rule external info url", rule1.getExternalInfoUrl(), rule2.getExternalInfoUrl());
432 			assertEquals(message + ", Rule priority", rule1.getPriority(), rule2.getPriority());
433 			assertEquals(message + ", Rule examples", rule1.getExamples(), rule2.getExamples());
434 			assertEquals(message + ", Rule properties", rule1.getProperties(), rule2.getProperties());
435 		}
436 	}
437 
438 	private boolean validateAgainstSchema(String fileName) throws IOException, RuleSetNotFoundException,
439 			ParserConfigurationException, SAXException {
440 		InputStream inputStream = loadResourceAsStream(fileName);
441 		boolean valid = validateAgainstSchema(inputStream);
442 		if (!valid) {
443 			System.err.println("Validation against XML Schema failed for: " + fileName);
444 		}
445 		return valid;
446 	}
447 
448 	private boolean validateAgainstSchema(InputStream inputStream) throws IOException, RuleSetNotFoundException,
449 			ParserConfigurationException, SAXException {
450 		SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
451 		saxParserFactory.setValidating(true);
452 		saxParserFactory.setNamespaceAware(true);
453 
454 		// Hope we're using Xerces, or this may not work!
455 		// Note: Features are listed here http://xerces.apache.org/xerces2-j/features.html
456 		saxParserFactory.setFeature("http://xml.org/sax/features/validation", true);
457 		saxParserFactory.setFeature("http://apache.org/xml/features/validation/schema", true);
458 		saxParserFactory.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true);
459 
460 		SAXParser saxParser = saxParserFactory.newSAXParser();
461 		ValidateDefaultHandler validateDefaultHandler = new ValidateDefaultHandler("etc/ruleset_xml_schema.xsd");
462 		saxParser.parse(inputStream, validateDefaultHandler);
463 		inputStream.close();
464 		return validateDefaultHandler.isValid();
465 	}
466 
467 	private boolean validateAgainstDtd(String fileName) throws IOException, RuleSetNotFoundException,
468 			ParserConfigurationException, SAXException {
469 		InputStream inputStream = loadResourceAsStream(fileName);
470 		boolean valid = validateAgainstDtd(inputStream);
471 		if (!valid) {
472 			System.err.println("Validation against DTD failed for: " + fileName);
473 		}
474 		return valid;
475 	}
476 
477 	private boolean validateAgainstDtd(InputStream inputStream) throws IOException, RuleSetNotFoundException,
478 			ParserConfigurationException, SAXException {
479 		SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
480 		saxParserFactory.setValidating(true);
481 		saxParserFactory.setNamespaceAware(true);
482 
483 		// Read file into memory
484 		String file = readFullyToString(inputStream);
485 
486 		// Remove XML Schema stuff, replace with DTD
487 		file = file.replaceAll("<\\?xml [ a-zA-Z0-9=\".-]*\\?>", "");
488 		file = file.replaceAll("xmlns=\"http://pmd.sf.net/ruleset/1.0.0\"", "");
489 		file = file.replaceAll("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", "");
490 		file = file.replaceAll(
491 				"xsi:schemaLocation=\"http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd\"", "");
492 		file = file.replaceAll("xsi:noNamespaceSchemaLocation=\"http://pmd.sf.net/ruleset_xml_schema.xsd\"", "");
493 
494 		file = "<?xml version=\"1.0\"?>" + PMD.EOL + "<!DOCTYPE ruleset SYSTEM \"file://"
495 				+ System.getProperty("user.dir") + "/etc/ruleset.dtd\">" + PMD.EOL + file;
496 
497 		inputStream = new ByteArrayInputStream(file.getBytes());
498 
499 		SAXParser saxParser = saxParserFactory.newSAXParser();
500 		ValidateDefaultHandler validateDefaultHandler = new ValidateDefaultHandler("etc/ruleset.dtd");
501 		saxParser.parse(inputStream, validateDefaultHandler);
502 		inputStream.close();
503 		return validateDefaultHandler.isValid();
504 	}
505 
506 	private String readFullyToString(InputStream inputStream) throws IOException {
507 		StringBuffer buf = new StringBuffer(64 * 1024);
508 		BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
509 		String line;
510 		while ((line = reader.readLine()) != null) {
511 			buf.append(line);
512 			buf.append(PMD.EOL);
513 		}
514 		reader.close();
515 		return buf.toString();
516 	}
517 
518 	// Gets all test PMD Ruleset XML files
519 	private List<String> getRuleSetFileNames() throws IOException, RuleSetNotFoundException {
520 		Properties properties = new Properties();
521 		properties.load(ResourceLoader.loadResourceAsStream("rulesets/rulesets.properties"));
522 		String fileNames = properties.getProperty("rulesets.testnames");
523 		StringTokenizer st = new StringTokenizer(fileNames, ",");
524 		List<String> ruleSetFileNames = new ArrayList<String>();
525 		while (st.hasMoreTokens()) {
526 			ruleSetFileNames.add(st.nextToken());
527 		}
528 		return ruleSetFileNames;
529 	}
530 
531 	private class ValidateDefaultHandler extends DefaultHandler {
532 		private final String validateDocument;
533 		private boolean valid = true;
534 
535 		public ValidateDefaultHandler(String validateDocument) {
536 			this.validateDocument = validateDocument;
537 		}
538 
539 		public boolean isValid() {
540 			return valid;
541 		}
542 
543 		public void error(SAXParseException e) throws SAXException {
544 			log("Error", e);
545 		}
546 
547 		public void fatalError(SAXParseException e) throws SAXException {
548 			log("FatalError", e);
549 		}
550 
551 		public void warning(SAXParseException e) throws SAXException {
552 			log("Warning", e);
553 		}
554 
555 		private void log(String prefix, SAXParseException e) {
556 			String message = prefix + " at (" + e.getLineNumber() + ", " + e.getColumnNumber() + "): " + e.getMessage();
557 			System.err.println(message);
558 			valid = false;
559 		}
560 
561 		public InputSource resolveEntity(String publicId, String systemId) throws IOException, SAXException {
562 			if ("http://pmd.sf.net/ruleset_xml_schema.xsd".equals(systemId) || systemId.endsWith("ruleset.dtd")) {
563 				try {
564 					InputStream inputStream = loadResourceAsStream(validateDocument);
565 					return new InputSource(inputStream);
566 				} catch (RuleSetNotFoundException e) {
567 					System.err.println(e.getMessage());
568 					throw new IOException(e.getMessage());
569 				}
570 			} else {
571 				throw new IllegalArgumentException("No clue how to handle: publicId=" + publicId + ", systemId="
572 						+ systemId);
573 			}
574 		}
575 	}
576 
577 	private InputStream loadResourceAsStream(String resource) throws RuleSetNotFoundException {
578 		InputStream inputStream = ResourceLoader.loadResourceAsStream(resource, this.getClass().getClassLoader());
579 		if (inputStream == null) {
580 			throw new RuleSetNotFoundException(
581 					"Can't find resource "
582 							+ resource
583 							+ "  Make sure the resource is a valid file or URL or is on the CLASSPATH.  Here's the current classpath: "
584 							+ System.getProperty("java.class.path"));
585 		}
586 		return inputStream;
587 	}
588 
589 	private static final String REF_OVERRIDE_ORIGINAL_NAME = "<?xml version=\"1.0\"?>" + PMD.EOL
590 			+ "<ruleset name=\"test\">" + PMD.EOL + " <description>testdesc</description>" + PMD.EOL + " <rule "
591 			+ PMD.EOL + "  ref=\"rulesets/unusedcode.xml/UnusedLocalVariable\" message=\"TestMessageOverride\"> "
592 			+ PMD.EOL + " </rule>" + PMD.EOL + "</ruleset>";
593 
594 	private static final String REF_MISPELLED_XREF = "<?xml version=\"1.0\"?>" + PMD.EOL + "<ruleset name=\"test\">"
595 			+ PMD.EOL + " <description>testdesc</description>" + PMD.EOL + " <rule " + PMD.EOL
596 			+ "  ref=\"rulesets/unusedcode.xml/FooUnusedLocalVariable\"> " + PMD.EOL + " </rule>" + PMD.EOL
597 			+ "</ruleset>";
598 
599 	private static final String REF_OVERRIDE_ORIGINAL_NAME_ONE_ELEM = "<?xml version=\"1.0\"?>" + PMD.EOL
600 			+ "<ruleset name=\"test\">" + PMD.EOL + " <description>testdesc</description>" + PMD.EOL
601 			+ " <rule ref=\"rulesets/unusedcode.xml/UnusedLocalVariable\" message=\"TestMessageOverride\"/> " + PMD.EOL
602 			+ "</ruleset>";
603 
604 	private static final String REF_OVERRIDE = "<?xml version=\"1.0\"?>" + PMD.EOL + "<ruleset name=\"test\">"
605 			+ PMD.EOL + " <description>testdesc</description>" + PMD.EOL + " <rule " + PMD.EOL
606 			+ "  ref=\"rulesets/unusedcode.xml/UnusedLocalVariable\" " + PMD.EOL + "  name=\"TestNameOverride\" "
607 			+ PMD.EOL + "  message=\"Test message override\"> " + PMD.EOL
608 			+ "  <description>Test description override</description>" + PMD.EOL
609 			+ "  <example>Test example override</example>" + PMD.EOL + "  <priority>3</priority>" + PMD.EOL
610 			+ "  <properties>" + PMD.EOL + "   <property name=\"test2\" value=\"override2\"/>" + PMD.EOL
611 			+ "   <property name=\"test3\"><value>override3</value></property>" + PMD.EOL
612 			+ "   <property name=\"test4\" value=\"new property\"/>" + PMD.EOL + "  </properties>" + PMD.EOL
613 			+ " </rule>" + PMD.EOL + "</ruleset>";
614 
615 	private static final String EMPTY_RULESET = "<?xml version=\"1.0\"?>" + PMD.EOL + "<ruleset name=\"test\">"
616 			+ PMD.EOL + "<description>testdesc</description>" + PMD.EOL + "</ruleset>";
617 
618 	private static final String SINGLE_RULE = "<?xml version=\"1.0\"?>" + PMD.EOL + "<ruleset name=\"test\">" + PMD.EOL
619 			+ "<description>testdesc</description>" + PMD.EOL + "<rule " + PMD.EOL + "name=\"MockRuleName\" " + PMD.EOL
620 			+ "message=\"avoid the mock rule\" " + PMD.EOL + "class=\"net.sourceforge.pmd.MockRule\">"
621 			+ "<priority>3</priority>" + PMD.EOL + "</rule></ruleset>";
622 
623 	private static final String MULTIPLE_RULES = "<?xml version=\"1.0\"?>" + PMD.EOL + "<ruleset name=\"test\">"
624 			+ PMD.EOL + "<description>testdesc</description>" + PMD.EOL + "<rule name=\"MockRuleName1\" " + PMD.EOL
625 			+ "message=\"avoid the mock rule\" " + PMD.EOL + "class=\"net.sourceforge.pmd.MockRule\">" + PMD.EOL
626 			+ "</rule>" + PMD.EOL + "<rule name=\"MockRuleName2\" " + PMD.EOL + "message=\"avoid the mock rule\" "
627 			+ PMD.EOL + "class=\"net.sourceforge.pmd.MockRule\">" + PMD.EOL + "</rule></ruleset>";
628 
629 	private static final String PROPERTIES = "<?xml version=\"1.0\"?>" + PMD.EOL + "<ruleset name=\"test\">" + PMD.EOL
630 			+ "<description>testdesc</description>" + PMD.EOL + "<rule name=\"MockRuleName\" " + PMD.EOL
631 			+ "message=\"avoid the mock rule\" " + PMD.EOL + "class=\"net.sourceforge.pmd.MockRule\">" + PMD.EOL
632 			+ "<description>testdesc2</description>" + PMD.EOL + "<properties>" + PMD.EOL
633 			+ "<property name=\"fooBoolean\" value=\"true\"/>" + PMD.EOL
634 			+ "<property name=\"fooDouble\" value=\"1.0\" />" + PMD.EOL + "<property name=\"foo\" value=\"bar\"/>"
635 			+ PMD.EOL + "<property name=\"fooint\" value=\"2\"/>" + PMD.EOL + "</properties>" + PMD.EOL
636 			+ "</rule></ruleset>";
637 
638 	private static final String XPATH = "<?xml version=\"1.0\"?>" + PMD.EOL + "<ruleset name=\"test\">" + PMD.EOL
639 			+ "<description>testdesc</description>" + PMD.EOL + "<priority>3</priority>" + PMD.EOL
640 			+ "<rule name=\"MockRuleName\" " + PMD.EOL + "message=\"avoid the mock rule\" " + PMD.EOL
641 			+ "class=\"net.sourceforge.pmd.MockRule\">" + PMD.EOL + "<description>testdesc2</description>" + PMD.EOL
642 			+ "<properties>" + PMD.EOL + "<property name=\"xpath\">" + PMD.EOL + "<value>" + PMD.EOL
643 			+ "<![CDATA[ //Block ]]>" + PMD.EOL + "</value>" + PMD.EOL + "</property>" + PMD.EOL + "</properties>"
644 			+ PMD.EOL + "</rule></ruleset>";
645 
646 	private static final String XPATH_PLUGINNAME = "<?xml version=\"1.0\"?>" + PMD.EOL + "<ruleset name=\"test\">"
647 			+ PMD.EOL + "<description>testdesc</description>" + PMD.EOL + "<priority>3</priority>" + PMD.EOL
648 			+ "<rule name=\"MockRuleName\" " + PMD.EOL + "message=\"avoid the mock rule\" " + PMD.EOL
649 			+ "class=\"net.sourceforge.pmd.MockRule\">" + PMD.EOL + "<description>testdesc2</description>" + PMD.EOL
650 			+ "<properties>" + PMD.EOL + "<property name=\"xpath\" pluginname=\"true\">" + PMD.EOL + "<value>"
651 			+ PMD.EOL + "<![CDATA[ //Block ]]>" + PMD.EOL + "</value>" + PMD.EOL + "</property>" + PMD.EOL
652 			+ "</properties>" + PMD.EOL + "</rule></ruleset>";
653 
654 	private static final String PRIORITY = "<?xml version=\"1.0\"?>" + PMD.EOL + "<ruleset name=\"test\">" + PMD.EOL
655 			+ "<description>testdesc</description>" + PMD.EOL + "<rule " + PMD.EOL + "name=\"MockRuleName\" " + PMD.EOL
656 			+ "message=\"avoid the mock rule\" " + PMD.EOL + "class=\"net.sourceforge.pmd.MockRule\">"
657 			+ "<priority>3</priority>" + PMD.EOL + "</rule></ruleset>";
658 
659 	private static final String DFA = "<?xml version=\"1.0\"?>" + PMD.EOL + "<ruleset name=\"test\">" + PMD.EOL
660 			+ "<description>testdesc</description>" + PMD.EOL + "<rule " + PMD.EOL + "name=\"MockRuleName\" " + PMD.EOL
661 			+ "message=\"avoid the mock rule\" " + PMD.EOL + "dfa=\"true\" " + PMD.EOL
662 			+ "class=\"net.sourceforge.pmd.MockRule\">" + "<priority>3</priority>" + PMD.EOL + "</rule></ruleset>";
663 
664 	private static final String INCLUDE_EXCLUDE_RULESET = "<?xml version=\"1.0\"?>" + PMD.EOL
665 			+ "<ruleset name=\"test\">" + PMD.EOL + "<description>testdesc</description>" + PMD.EOL
666 			+ "<include-pattern>include1</include-pattern>" + PMD.EOL + "<include-pattern>include2</include-pattern>"
667 			+ PMD.EOL + "<exclude-pattern>exclude1</exclude-pattern>" + PMD.EOL
668 			+ "<exclude-pattern>exclude2</exclude-pattern>" + PMD.EOL + "<exclude-pattern>exclude3</exclude-pattern>"
669 			+ PMD.EOL + "</ruleset>";
670 
671 	private Rule loadFirstRule(String ruleSetXml) {
672 		RuleSet rs = loadRuleSet(ruleSetXml);
673 		return rs.getRules().iterator().next();
674 	}
675 
676 	private RuleSet loadRuleSetByFileName(String ruleSetFileName) throws RuleSetNotFoundException {
677 		RuleSetFactory rsf = new RuleSetFactory();
678 		return rsf.createSingleRuleSet(ruleSetFileName);
679 	}
680 
681 	private RuleSet loadRuleSet(String ruleSetXml) {
682 		RuleSetFactory rsf = new RuleSetFactory();
683 		return rsf.createRuleSet(new ByteArrayInputStream(ruleSetXml.getBytes()));
684 	}
685 
686 	@Test
687 	public void testExternalReferences() {
688 		RuleSet rs = loadRuleSet(EXTERNAL_REFERENCE_RULE_SET);
689 		assertEquals(1, rs.size());
690 		assertEquals(UnusedLocalVariableRule.class.getName(), rs.getRuleByName("UnusedLocalVariable").getRuleClass());
691 	}
692 
693 	private static final String EXTERNAL_REFERENCE_RULE_SET = "<?xml version=\"1.0\"?>" + PMD.EOL
694 			+ "<ruleset name=\"test\">" + PMD.EOL + "<description>testdesc</description>" + PMD.EOL
695 			+ "<rule ref=\"rulesets/unusedcode.xml/UnusedLocalVariable\"/>" + PMD.EOL + "</ruleset>";
696 
697 	public static junit.framework.Test suite() {
698 		return new JUnit4TestAdapter(RuleSetFactoryTest.class);
699 	}
700 }