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

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