You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AjcSpecXmlReader.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. /* *******************************************************************
  2. * Copyright (c) 1999-2001 Xerox Corporation,
  3. * 2002 Palo Alto Research Center, Incorporated (PARC),
  4. * 2003 Contributors.
  5. * All rights reserved.
  6. * This program and the accompanying materials are made available
  7. * under the terms of the Common Public License v1.0
  8. * which accompanies this distribution and is available at
  9. * http://www.eclipse.org/legal/cpl-v10.html
  10. *
  11. * Contributors:
  12. * Xerox/PARC initial implementation
  13. * Wes Isberg resolver
  14. * ******************************************************************/
  15. package org.aspectj.testing.xml;
  16. import java.io.*;
  17. //import java.util.Vector;
  18. import org.apache.commons.digester.Digester;
  19. import org.aspectj.bridge.AbortException;
  20. import org.aspectj.bridge.IMessage;
  21. import org.aspectj.bridge.ISourceLocation;
  22. import org.aspectj.bridge.MessageUtil;
  23. import org.aspectj.bridge.SourceLocation;
  24. import org.aspectj.testing.harness.bridge.*;
  25. import org.aspectj.testing.util.RunUtils;
  26. import org.aspectj.util.LangUtil;
  27. import org.xml.sax.*;
  28. import org.xml.sax.SAXException;
  29. /**
  30. * Read an ajc test specification in xml form.
  31. * Input files should comply with DOCTYPE
  32. */
  33. public class AjcSpecXmlReader {
  34. /*
  35. * To add new elements or attributes:
  36. * - update the DOCTYPE
  37. * - update setupDigester(..)
  38. * - new sub-elements should be created
  39. * - new attributes should have values set as bean properties
  40. * (possibly by mapping names)
  41. * - new sub-elements should be added to parents
  42. * => the parents need the add method - e.g., add{foo}({foo} toadd)
  43. * - add tests
  44. * - add compile-time checks for mapping APIs in
  45. * setupDigesterComipileTimeCheck
  46. * - when adding an attribute set by bean introspection,
  47. * add to the list returned by expectedProperties()
  48. * - update any client writers referring to the DOCTYPE, as necessary.
  49. * - the parent IXmlWriter should delegate to the child component
  50. * as IXmlWriter (or write the subelement itself)
  51. *
  52. * Debugging
  53. * - use logLevel = 2 for tracing
  54. * - common mistakes
  55. * - dtd has to match input
  56. * - no rule defined (or misdefined) so element ignored
  57. * - property read-only (?)
  58. */
  59. // private static final String EOL = "\n";
  60. /** presumed relative-path to dtd file for any XML files written by writeSuiteToXmlFile */
  61. public static final String DTD_PATH = "../tests/ajcTestSuite.dtd";
  62. /** expected doc type of AjcSpec XML files */
  63. public static final String DOCTYPE = "<!DOCTYPE "
  64. + AjcTest.Suite.Spec.XMLNAME + " SYSTEM \"" + DTD_PATH + "\">";
  65. private static final AjcSpecXmlReader ME
  66. = new AjcSpecXmlReader();
  67. /** @return shared instance */
  68. public static final AjcSpecXmlReader getReader() {
  69. return ME;
  70. }
  71. public static void main(String[] a) throws IOException {
  72. writeDTD(new File("../tests/ajcTestSuite2.dtd"));
  73. }
  74. /**
  75. * Write a DTD to dtdFile.
  76. * @deprecated
  77. * @param dtdFile the File to write to
  78. */
  79. public static void writeDTD(File dtdFile) throws IOException {
  80. LangUtil.throwIaxIfNull(dtdFile, "dtdFile");
  81. PrintWriter out = new PrintWriter(new FileWriter(dtdFile));
  82. try {
  83. out.println("<!-- document type for ajc test suite - see "
  84. + AjcSpecXmlReader.class.getName() + " -->");
  85. //out.println(getDocType());
  86. } finally {
  87. out.close();
  88. }
  89. }
  90. private static final String[] LOG = new String[] {"info", "debug", "trace" };
  91. // XXX logLevel n>0 causes JUnit tests to fail!
  92. private int logLevel = 0; // use 2 for tracing
  93. private AjcSpecXmlReader() {}
  94. /** @param level 0..2, info..trace */
  95. public void setLogLevel(int level) {
  96. if (level < 0) {
  97. level = 0;
  98. }
  99. if (level > 2) {
  100. level = 2;
  101. }
  102. logLevel = level;
  103. }
  104. /**
  105. * Print an IXmlWritable to the output file
  106. * with our leader and DOCTYPE.
  107. * @param output the File to write to - overwritten
  108. * @param tests the List of IXmlWritable to write
  109. * @return null if no warnings detected, warnings otherwise
  110. */
  111. public String writeSuiteToXmlFile(File output, IXmlWritable topNode) throws IOException {
  112. PrintWriter writer = new PrintWriter(new FileOutputStream(output));
  113. XMLWriter printSink = new XMLWriter(writer);
  114. writer.println("");
  115. writer.println(AjcSpecXmlReader.DOCTYPE);
  116. writer.println("");
  117. topNode.writeXml(printSink);
  118. writer.close();
  119. String parent = output.getParent();
  120. if (null == parent) {
  121. parent = ".";
  122. }
  123. String dtdPath = parent + "/" + DTD_PATH;
  124. File dtdFile = new File(dtdPath);
  125. if (!dtdFile.canRead()) {
  126. return "expecting dtd file: " + dtdFile.getPath();
  127. }
  128. return null;
  129. }
  130. /**
  131. * Read the specifications for a suite of AjcTest from an XML file.
  132. * This also sets the suite dir in the specification.
  133. * @param file the File must be readable, comply with DOCTYPE.
  134. * @return AjcTest.Suite.Spec read from file
  135. * @see setLogLevel(int)
  136. */
  137. public AjcTest.Suite.Spec readAjcSuite(File file) throws IOException, AbortException {
  138. // setup loggers for digester and beanutils...
  139. System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog"); // XXX
  140. System.setProperty("org.apache.commons.logging.simplelog.defaultlog", LOG[logLevel]); // trace debug XXX
  141. final Digester digester = makeDigester(file);
  142. SuiteHolder holder = new SuiteHolder();
  143. digester.push(holder);
  144. FileInputStream input = new FileInputStream(file);
  145. try {
  146. digester.parse(input);
  147. } catch (SAXException e) {
  148. MessageUtil.fail("parsing " + file, e);
  149. } finally {
  150. if (null != input) {
  151. input.close();
  152. input = null;
  153. }
  154. }
  155. AjcTest.Suite.Spec result = holder.spec;
  156. if (null != result) {
  157. file = file.getAbsoluteFile();
  158. result.setSourceLocation(new SourceLocation(file, 1));
  159. File suiteDir = file.getParentFile();
  160. if (null == suiteDir) {
  161. // should not be the case if absolute
  162. suiteDir = new File("."); // user.dir?
  163. }
  164. result.setSuiteDirFile(suiteDir);
  165. if (result.runtime.isVerbose()) { // XXX hack fixup
  166. RunUtils.enableVerbose(result);
  167. }
  168. }
  169. return result;
  170. }
  171. private Digester makeDigester(final File suiteFile) {
  172. // implement EntityResolver directly; set is failing
  173. Digester result = new Digester() {
  174. final SuiteResolver resolver = new SuiteResolver(suiteFile);
  175. public InputSource resolveEntity(
  176. String publicId,
  177. String systemId)
  178. throws SAXException {
  179. return resolver.resolveEntity(publicId, systemId);
  180. }
  181. };
  182. setupDigester(result);
  183. return result;
  184. }
  185. /** set up the mapping between the xml and Java. */
  186. private void setupDigester(Digester digester) {
  187. // XXX supply sax parser to ignore white space?
  188. digester.setValidating(true);
  189. // try {
  190. // // this is the correct approach, but the commons parser
  191. // // fails to accept a second, overriding registration - see
  192. // // http://lists.xml.org/archives/xml-dev/200111/msg00959.html
  193. // digester.getXMLReader().setEntityResolver(new SuiteResolver(suiteFile));
  194. // } catch (SAXException e) {
  195. // System.err.println("unable to set entity resolver");
  196. // e.printStackTrace(System.err);
  197. // }
  198. // element names come from the element components
  199. final String suiteX = AjcTest.Suite.Spec.XMLNAME;
  200. final String ajctestX = suiteX + "/" + AjcTest.Spec.XMLNAME;
  201. final String compileX = ajctestX + "/" + CompilerRun.Spec.XMLNAME;
  202. final String inccompileX = ajctestX + "/" + IncCompilerRun.Spec.XMLNAME;
  203. final String runX = ajctestX + "/" + JavaRun.Spec.XMLNAME;
  204. final String dirchangesX = "*/" + DirChanges.Spec.XMLNAME;
  205. final String messageX = "*/" + SoftMessage.XMLNAME;
  206. final String messageSrcLocX = messageX + "/" +SoftSourceLocation.XMLNAME;
  207. // ---- each sub-element needs to be created
  208. // handle messages the same at any level
  209. digester.addObjectCreate(suiteX, AjcTest.Suite.Spec.class.getName());
  210. digester.addObjectCreate(ajctestX, AjcTest.Spec.class.getName());
  211. digester.addObjectCreate(compileX, CompilerRun.Spec.class.getName());
  212. //digester.addObjectCreate(compileX + "/file", AbstractRunSpec.WrapFile.class.getName());
  213. digester.addObjectCreate(inccompileX, IncCompilerRun.Spec.class.getName());
  214. digester.addObjectCreate(runX, JavaRun.Spec.class.getName());
  215. digester.addObjectCreate(messageX, SoftMessage.class.getName());
  216. digester.addObjectCreate(messageSrcLocX, SoftSourceLocation.class.getName());
  217. digester.addObjectCreate(dirchangesX, DirChanges.Spec.class.getName());
  218. // ---- set bean properties for sub-elements created automatically
  219. // -- some remapped - warnings
  220. // - if property exists, map will not be used
  221. digester.addSetProperties(suiteX); // ok to have suite messages and global suite options, etc.
  222. digester.addSetProperties(ajctestX,
  223. new String[] { "title", "dir", "pr"},
  224. new String[] { "description", "testDirOffset", "bugId"});
  225. digester.addSetProperties(compileX,
  226. new String[] { "files", "argfiles"},
  227. new String[] { "paths", "argfiles"});
  228. digester.addSetProperties(compileX + "/file");
  229. digester.addSetProperties(inccompileX, "classes", "paths");
  230. digester.addSetProperties(runX,
  231. new String[] { "class", "vm", "skipTester", "fork", "vmargs", "aspectpath"},
  232. new String[] { "className", "javaVersion", "skipTester", "fork", "vmArgs", "aspectpath"});
  233. digester.addSetProperties(dirchangesX);
  234. digester.addSetProperties(messageX);
  235. digester.addSetProperties(messageSrcLocX, "line", "lineAsString");
  236. digester.addSetProperties(messageX, "kind", "kindAsString");
  237. digester.addSetProperties(messageX, "line", "lineAsString");
  238. //digester.addSetProperties(messageX, "details", "details");
  239. // only file subelement of compile uses text as path... XXX vestigial
  240. digester.addCallMethod(compileX + "/file", "setFile", 0);
  241. // ---- when subelements are created, add to parent
  242. // add ajctest to suite, runs to ajctest, files to compile, messages to any parent...
  243. // the method name (e.g., "addSuite") is in the parent (SuiteHolder)
  244. // the class (e.g., AjcTest.Suite.Spec) refers to the type of the object created
  245. digester.addSetNext(suiteX, "addSuite", AjcTest.Suite.Spec.class.getName());
  246. digester.addSetNext(ajctestX, "addChild", AjcTest.Spec.class.getName());
  247. digester.addSetNext(compileX, "addChild", CompilerRun.Spec.class.getName());
  248. digester.addSetNext(inccompileX, "addChild", IncCompilerRun.Spec.class.getName());
  249. digester.addSetNext(runX, "addChild", JavaRun.Spec.class.getName());
  250. //digester.addSetNext(compileX + "/file", "addWrapFile", AbstractRunSpec.WrapFile.class.getName());
  251. digester.addSetNext(messageX, "addMessage", IMessage.class.getName());
  252. // setSourceLocation is for the inline variant
  253. // addSourceLocation is for the extra
  254. digester.addSetNext(messageSrcLocX, "addSourceLocation", ISourceLocation.class.getName());
  255. digester.addSetNext(dirchangesX, "addDirChanges", DirChanges.Spec.class.getName());
  256. // can set parent, but prefer to have "knows-about" flow down only...
  257. }
  258. // ------------------------------------------------------------ testing code
  259. /**
  260. * Get expected bean properties for introspection tests.
  261. * This should return an expected property for every attribute in DOCTYPE,
  262. * using any mapped-to names from setupDigester.
  263. */
  264. static BProps[] expectedProperties() {
  265. return new BProps[]
  266. {
  267. new BProps(AjcTest.Suite.Spec.class,
  268. new String[] { "suiteDir"}), // verbose removed
  269. new BProps(AjcTest.Spec.class,
  270. new String[] { "description", "testDirOffset", "bugId"}),
  271. // mapped from { "title", "dir", "pr"}
  272. new BProps(CompilerRun.Spec.class,
  273. new String[] { "files", "options",
  274. "staging", "badInput", "reuseCompiler", "includeClassesDir",
  275. "argfiles", "aspectpath", "classpath", "extdirs",
  276. "sourceroots", "xlintfile", "outjar"}),
  277. new BProps(IncCompilerRun.Spec.class,
  278. new String[] { "tag" }),
  279. new BProps(JavaRun.Spec.class,
  280. new String[] { "className", "skipTester", "options",
  281. "javaVersion", "errStreamIsError", "outStreamIsError",
  282. "fork", "vmArgs", "aspectpath"}),
  283. // mapped from { "class", ...}
  284. new BProps(DirChanges.Spec.class,
  285. new String[] { "added", "removed", "updated", "unchanged", "dirToken", "defaultSuffix"}),
  286. // new BProps(AbstractRunSpec.WrapFile.class,
  287. // new String[] { "path"}),
  288. new BProps(SoftMessage.class,
  289. new String[] { "kindAsString", "lineAsString", "text", "details", "file"})
  290. // mapped from { "kind", "line", ...}
  291. };
  292. }
  293. /**
  294. * This is only to do compile-time checking for the APIs impliedly
  295. * used in setupDigester(..).
  296. * The property setter checks are redundant with tests based on
  297. * expectedProperties().
  298. */
  299. private static void setupDigesterCompileTimeCheck() {
  300. if (true) { throw new Error("never invoked"); }
  301. AjcTest.Suite.Spec suite = new AjcTest.Suite.Spec();
  302. AjcTest.Spec test = new AjcTest.Spec();
  303. Sandbox sandbox = null;
  304. Validator validator = null;
  305. // AjcTest ajctest = new AjcTest(test, sandbox, validator);
  306. // ajctest.addRunSpec((AbstractRunSpec) null);
  307. //// test.makeIncCompilerRun((IncCompilerRun.Spec) null);
  308. //// test.makeJavaRun((JavaRun.Spec) null);
  309. // ajctest.setDescription((String) null);
  310. // ajctest.setTestBaseDirOffset((String) null);
  311. // ajctest.setBugId((String) null);
  312. // ajctest.setTestSourceLocation((ISourceLocation) null);
  313. CompilerRun.Spec crunSpec = new CompilerRun.Spec();
  314. crunSpec.addMessage((IMessage) null);
  315. // XXX crunSpec.addSourceLocation((ISourceLocation) null);
  316. // crunSpec.addWrapFile((AbstractRunSpec.WrapFile) null);
  317. crunSpec.setOptions((String) null);
  318. crunSpec.setPaths((String) null);
  319. crunSpec.setIncludeClassesDir(false);
  320. crunSpec.setReuseCompiler(false);
  321. crunSpec.setXlintfile((String) null);
  322. crunSpec.setOutjar((String)null);
  323. IncCompilerRun.Spec icrunSpec = new IncCompilerRun.Spec();
  324. icrunSpec.addMessage((IMessage) null);
  325. icrunSpec.setTag((String) null);
  326. icrunSpec.setFresh(false);
  327. JavaRun.Spec jrunspec = new JavaRun.Spec();
  328. jrunspec.addMessage((IMessage) null);
  329. jrunspec.setClassName((String) null);
  330. jrunspec.addMessage((IMessage) null);
  331. // input s.b. interpretable by Boolean.valueOf(String)
  332. jrunspec.setSkipTester(true);
  333. jrunspec.setErrStreamIsError("false");
  334. jrunspec.setOutStreamIsError("false");
  335. jrunspec.setAspectpath("");
  336. jrunspec.setClasspath("");
  337. jrunspec.setFork(false);
  338. jrunspec.setLTW("false");
  339. jrunspec.setException("Error");
  340. DirChanges.Spec dcspec = new DirChanges.Spec();
  341. dcspec.setAdded((String) null);
  342. dcspec.setRemoved((String) null);
  343. dcspec.setUpdated((String) null);
  344. dcspec.setDefaultSuffix((String) null);
  345. dcspec.setDirToken((String) null);
  346. SoftMessage m = new SoftMessage();
  347. m.setSourceLocation((ISourceLocation) null);
  348. m.setText((String) null);
  349. m.setKindAsString((String) null);
  350. m.setDetails((String) null);
  351. SoftSourceLocation sl = new SoftSourceLocation();
  352. sl.setFile((String) null);
  353. sl.setLine((String) null);
  354. sl.setColumn((String) null);
  355. sl.setEndLine((String) null);
  356. // add attribute setters to validate?
  357. }
  358. /** top element on Digester stack holds the test suite */
  359. public static class SuiteHolder {
  360. AjcTest.Suite.Spec spec;
  361. public void addSuite(AjcTest.Suite.Spec spec) {
  362. this.spec = spec;
  363. }
  364. }
  365. /** hold class/properties association for testing */
  366. static class BProps {
  367. final Class cl;
  368. final String[] props;
  369. BProps(Class cl, String[] props) {
  370. this.cl = cl;
  371. this.props = props;
  372. }
  373. }
  374. /**
  375. * Find file NAME=="ajcTestSuite.dtd" from some reasonably-local
  376. * relative directories.
  377. * XXX bug: commons parser doesn't accept second registration,
  378. * so we override Digester's implementation instead.
  379. * XXX cannot JUnit test SuiteResolver since they run from
  380. * local directory with valid reference
  381. * XXX does not fix JDK 1.4 parser message "unable to resolve without base URI"
  382. * XXX should be able to just set BaseURI instead...
  383. */
  384. static class SuiteResolver implements EntityResolver {
  385. public static final String NAME = "ajcTestSuite.dtd";
  386. private static boolean isDir(File dir) {
  387. return ((null != dir) && dir.canRead() && dir.isDirectory());
  388. }
  389. private final File suiteFile;
  390. public SuiteResolver(File suiteFile) {
  391. this.suiteFile = suiteFile;
  392. }
  393. private String getPath(String id) {
  394. // first, try id relative to suite file
  395. final File suiteFileDir = suiteFile.getParentFile();
  396. if (isDir(suiteFileDir)) {
  397. String path = suiteFileDir.getPath()
  398. + "/" + id;
  399. File result = new File(path);
  400. if (result.canRead() && result.isFile()) {
  401. return result.getPath();
  402. }
  403. }
  404. // then try misc paths relative to suite file or current dir
  405. final File[] baseDirs = new File[]
  406. { suiteFileDir, new File(".")
  407. };
  408. final String[] locations = new String[]
  409. { ".", "..", "../tests", "../../tests",
  410. "../../../tests", "tests", "modules/tests"
  411. };
  412. File baseDir;
  413. for (int j = 0; j < baseDirs.length; j++) {
  414. baseDir = baseDirs[j];
  415. if (!isDir(baseDir)) {
  416. continue;
  417. }
  418. for (int i = 0; i < locations.length; i++) {
  419. File dir = new File(baseDir, locations[i]);
  420. if (isDir(dir)) {
  421. File temp = new File(dir, NAME);
  422. if (temp.isFile() && temp.canRead()) {
  423. return temp.getPath();
  424. }
  425. }
  426. }
  427. }
  428. return null;
  429. }
  430. public InputSource resolveEntity(
  431. String publicId,
  432. String systemId)
  433. throws SAXException {
  434. InputSource result = null;
  435. if ((null != systemId) &&
  436. systemId.endsWith(NAME)) {
  437. String path = getPath(systemId);
  438. if (null != path) {
  439. result = new InputSource(path);
  440. result.setSystemId(path);
  441. result.setPublicId(path);
  442. }
  443. }
  444. return result;
  445. }
  446. }
  447. }
  448. //private String getDocTypePath() {
  449. // String result = null;
  450. // if (null != suiteFile) {
  451. // FileReader fr = null;
  452. // try {
  453. // fr = new FileReader(suiteFile);
  454. // BufferedReader reader = new BufferedReader(fr);
  455. // String line;
  456. // while (null != (line = reader.readLine())) {
  457. // String upper = line.trim();
  458. // line = upper.toLowerCase();
  459. // if (line.startsWith("<")) {
  460. // if (line.startsWith("<!doctype ")) {
  461. // int start = line.indexOf("\"");
  462. // int end = line.lastIndexOf(NAME + "\"");
  463. // if ((0 < start) && (start < end)) {
  464. // return upper.substring(start+1,
  465. // end + NAME.length());
  466. // }
  467. // } else if (!line.startsWith("<?xml")) {
  468. // break; // something else...
  469. // }
  470. // }
  471. // }
  472. // } catch (IOException e) {
  473. // // ignore
  474. // } finally {
  475. // if (null != fr) {
  476. // try {
  477. // fr.close();
  478. // } catch (IOException e1) { // ignore
  479. // }
  480. // }
  481. // }
  482. // }
  483. // return null;
  484. //}