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.

AbstractRunSpec.java 29KB

21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
12 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
12 years ago
12 years ago
21 years ago
12 years ago
21 years ago
12 years ago
21 years ago
21 years ago
12 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  1. /* *******************************************************************
  2. * Copyright (c) 1999-2001 Xerox Corporation,
  3. * 2002 Palo Alto Research Center, Incorporated (PARC).
  4. * All rights reserved.
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Public License v1.0
  7. * which accompanies this distribution and is available at
  8. * http://www.eclipse.org/legal/epl-v10.html
  9. *
  10. * Contributors:
  11. * Xerox/PARC initial implementation
  12. * Wes Isberg 2004 updates
  13. * ******************************************************************/
  14. package org.aspectj.testing.harness.bridge;
  15. import java.io.PrintStream;
  16. import java.util.ArrayList;
  17. import java.util.Arrays;
  18. import java.util.BitSet;
  19. import java.util.Iterator;
  20. import java.util.List;
  21. import java.util.ListIterator;
  22. import org.aspectj.bridge.IMessage;
  23. import org.aspectj.bridge.IMessageHandler;
  24. import org.aspectj.bridge.IMessageHolder;
  25. import org.aspectj.bridge.ISourceLocation;
  26. import org.aspectj.bridge.Message;
  27. import org.aspectj.bridge.MessageHandler;
  28. import org.aspectj.bridge.MessageUtil;
  29. import org.aspectj.testing.run.IRunIterator;
  30. import org.aspectj.testing.util.BridgeUtil;
  31. import org.aspectj.testing.util.options.Option;
  32. import org.aspectj.testing.util.options.Option.InvalidInputException;
  33. import org.aspectj.testing.util.options.Options;
  34. import org.aspectj.testing.util.options.Values;
  35. import org.aspectj.testing.xml.IXmlWritable;
  36. import org.aspectj.testing.xml.SoftMessage;
  37. import org.aspectj.testing.xml.XMLWriter;
  38. import org.aspectj.util.LangUtil;
  39. /**
  40. * Base class for initialization of components expecting messages, options, files/paths, and source locations (resolved files), and
  41. * potentially containing child Spec.
  42. * <p>
  43. * <u>initialization</u>: This defines bean/xml setters for all. This converts String to IMessage using
  44. * {@link MessageUtil#readMessage(String)} and String to ISourceLocation using {@link BridgeUtil#makeSourceLocation(input)}. See
  45. * those APIs for input form and limitations. A Spec also accepts (or rejects) runtime configuration from a parent in {@link
  46. * adoptParentValues(RT, IMessageHandler)}. Since some children Spec may balk but this parent Spec continue, use {@link
  47. * getChildren()} to get the full list of children Spec, but {@link getWorkingChildren()} to get the list of children that are not
  48. * being skipped in accordance with runtime configuration.
  49. * <p>
  50. * <u>subclassing</u>: subclasses wishing other than the default behavior for reading String input should override the corresponding
  51. * (bean) setter. They can also override the add{foo} methods to get notice or modify objects constructed from the input.
  52. * <p>
  53. * <u>bean properties</u>: because this is designed to work by standard Java bean introspection, take care to follow bean rules when
  54. * adding methods. In particular, a property is illegal if the setter takes a different type than the getter returns. That means,
  55. * e.g., that all List and array[] getters should be named "get{property}[List|Array]". Otherwise the XML readers will silently fail
  56. * to set the property (perhaps with trace information that the property had no write method or was read-only).
  57. * <p>
  58. * <u>Coordination with writers</u>: because this reads the contents of values written by IXmlWritable, they should ensure their
  59. * values are readable. When flattening and unflattening lists, the convention is to use the {un}flattenList(..) methods in
  60. * XMLWriter.
  61. *
  62. * @see XMLWriter@unflattenList(String)
  63. * @see XMLWriter@flattenList(List)
  64. */
  65. abstract public class AbstractRunSpec implements IRunSpec {
  66. /** true if we expect to use a staging directory */
  67. boolean isStaging;
  68. /** true if this spec permits bad input (e.g., to test error handling) */
  69. boolean badInput;
  70. protected String description;
  71. /** optional source location of the specification itself */
  72. protected ISourceLocation sourceLocation;
  73. private BitSet skipSet;
  74. private boolean skipAll;
  75. protected String xmlElementName; // nonfinal only for clone()
  76. protected final ArrayList<String> keywords;
  77. protected final IMessageHolder /* IMessage */messages;
  78. protected final ArrayList<String> options;
  79. protected final ArrayList<String> paths;
  80. // XXXXXunused protected final ArrayList /*ISourceLocation*/ sourceLocations; // XXX remove?
  81. protected final ArrayList<IRunSpec> children;
  82. protected final ArrayList<DirChanges.Spec> dirChanges;
  83. protected XMLNames xmlNames;
  84. protected String comment;
  85. /** These options are 1:1 with spec, but set at runtime (not saved) */
  86. public final RT runtime;
  87. /** if true, then any child skip causes this to skip */
  88. protected boolean skipIfAnyChildSkipped; // nonfinal only for cloning
  89. public AbstractRunSpec(String xmlElementName) {
  90. this(xmlElementName, true);
  91. }
  92. public AbstractRunSpec(String xmlElementName, boolean skipIfAnyChildSkipped) {
  93. if (null == xmlElementName) {
  94. xmlElementName = "spec";
  95. }
  96. this.xmlElementName = xmlElementName;
  97. messages = new MessageHandler(true);
  98. options = new ArrayList<String>();
  99. paths = new ArrayList<String>();
  100. // XXXXXunused sourceLocations = new ArrayList();
  101. keywords = new ArrayList<String>();
  102. children = new ArrayList<IRunSpec>();
  103. dirChanges = new ArrayList();
  104. xmlNames = XMLNames.DEFAULT;
  105. runtime = new RT();
  106. this.skipIfAnyChildSkipped = skipIfAnyChildSkipped;
  107. }
  108. /** @param comment ignored if null */
  109. public void setComment(String comment) {
  110. if (!LangUtil.isEmpty(comment)) {
  111. this.comment = comment;
  112. }
  113. }
  114. public void setStaging(boolean staging) {
  115. isStaging = staging;
  116. }
  117. public void setBadInput(boolean badInput) {
  118. this.badInput = badInput;
  119. }
  120. boolean isStaging() {
  121. return isStaging;
  122. }
  123. // ------- description (title, label...)
  124. public void setDescription(String description) {
  125. this.description = description;
  126. }
  127. public String getDescription() {
  128. return description;
  129. }
  130. // ------- source location of the spec
  131. public void setSourceLocation(ISourceLocation sourceLocation) {
  132. this.sourceLocation = sourceLocation;
  133. }
  134. public ISourceLocation getSourceLocation() {
  135. return sourceLocation;
  136. }
  137. // ------- keywords
  138. /** @param keyword added after trimming if not empty */
  139. public void setKeyword(String keyword) {
  140. addKeyword(keyword);
  141. }
  142. /** @return ((null == s) || (0 == s.trim().length())); */
  143. public static boolean isEmptyTrimmed(String s) {
  144. return ((null == s) || (0 == s.length()) || (0 == s.trim().length()));
  145. }
  146. /** Add keyword if non-empty and not duplicate */
  147. public void addKeyword(String keyword) {
  148. if (!isEmptyTrimmed(keyword)) {
  149. keyword = keyword.trim();
  150. if (!keywords.contains(keyword)) {
  151. keywords.add(keyword);
  152. }
  153. }
  154. }
  155. public void setKeywords(String items) {
  156. addKeywords(items);
  157. }
  158. public void addKeywords(String items) {
  159. if (null != items) {
  160. addKeywords(XMLWriter.unflattenList(items));
  161. }
  162. }
  163. public void addKeywords(String[] ra) {
  164. if (null != ra) {
  165. for (int i = 0; i < ra.length; i++) {
  166. addKeyword(ra[i]);
  167. }
  168. }
  169. }
  170. public ArrayList<String> getKeywordsList() {
  171. return makeList(keywords);
  172. }
  173. // ------- options - String args
  174. /** @return ArrayList of String options */
  175. public ArrayList<String> getOptionsList() {
  176. return makeList(options);
  177. }
  178. /** @return String[] of options */
  179. public String[] getOptionsArray() {
  180. return (String[]) options.toArray(new String[0]);
  181. }
  182. public void setOption(String option) {
  183. addOption(option);
  184. }
  185. public void addOption(String option) {
  186. if ((null != option) && (0 < option.length())) {
  187. options.add(option);
  188. }
  189. }
  190. /** add options (from XML/bean) - removes any existing options */
  191. public void setOptions(String items) {
  192. this.options.clear();
  193. addOptions(items);
  194. }
  195. /**
  196. * Set options, removing any existing options.
  197. *
  198. * @param options String[] options to use - may be null or empty
  199. */
  200. public void setOptionsArray(String[] options) {
  201. this.options.clear();
  202. if (!LangUtil.isEmpty(options)) {
  203. this.options.addAll(Arrays.asList(options));
  204. }
  205. }
  206. public void addOptions(String items) {
  207. if (null != items) {
  208. addOptions(XMLWriter.unflattenList(items));
  209. }
  210. }
  211. public void addOptions(String[] ra) {
  212. if (null != ra) {
  213. for (int i = 0; i < ra.length; i++) {
  214. addOption(ra[i]);
  215. }
  216. }
  217. }
  218. // --------------- (String) paths
  219. /** @return ArrayList of String paths */
  220. public ArrayList<String> getPathsList() {
  221. return makeList(paths);
  222. }
  223. /** @return String[] of paths */
  224. public String[] getPathsArray() {
  225. return (String[]) paths.toArray(new String[0]);
  226. }
  227. public void setPath(String path) {
  228. addPath(path);
  229. }
  230. public void setPaths(String paths) {
  231. addPaths(paths);
  232. }
  233. public void addPath(String path) {
  234. if (null != path) {
  235. paths.add(path);
  236. }
  237. }
  238. public void addPaths(String items) {
  239. if (null != items) {
  240. addPaths(XMLWriter.unflattenList(items));
  241. }
  242. }
  243. public void addPaths(String[] ra) {
  244. if (null != ra) {
  245. for (int i = 0; i < ra.length; i++) {
  246. addPath(ra[i]);
  247. }
  248. }
  249. }
  250. // --------------------- dir changes
  251. public void addDirChanges(DirChanges.Spec dirChangesSpec) {
  252. if (null != dirChangesSpec) {
  253. dirChanges.add(dirChangesSpec);
  254. }
  255. }
  256. // --------------------- messages
  257. public void setMessage(String message) {
  258. addMessage(message);
  259. }
  260. public void addMessage(IMessage message) {
  261. if (null != message) {
  262. if (!messages.handleMessage(message)) {
  263. String s = "invalid message: " + message;
  264. throw new IllegalArgumentException(s);
  265. }
  266. }
  267. }
  268. public void addMessage(String message) {
  269. if (null != message) {
  270. addMessage(BridgeUtil.readMessage(message));
  271. }
  272. }
  273. /**
  274. * this can ONLY work if each item has no internal comma
  275. */
  276. public void addMessages(String items) {
  277. if (null != items) {
  278. String[] ra = XMLWriter.unflattenList(items);
  279. for (int i = 0; i < ra.length; i++) {
  280. addMessage(ra[i]);
  281. }
  282. }
  283. }
  284. public void addMessages(List messages) {
  285. if (null != messages) {
  286. for (Iterator iter = messages.iterator(); iter.hasNext();) {
  287. Object o = iter.next();
  288. if (o instanceof IMessage) {
  289. addMessage((IMessage) o);
  290. } else {
  291. String m = "not message: " + o;
  292. addMessage(new Message(m, IMessage.WARNING, null, null));
  293. }
  294. }
  295. }
  296. }
  297. /** @return int number of message of this kind (optionally or greater */
  298. public int numMessages(IMessage.Kind kind, boolean orGreater) {
  299. return messages.numMessages(kind, orGreater);
  300. }
  301. public IMessageHolder getMessages() {
  302. return messages;
  303. }
  304. public void addChild(IRunSpec child) {
  305. // fyi, child is added when complete (depth-first), not when initialized,
  306. // so cannot affect initialization of child here
  307. if (null != child) {
  308. children.add(child);
  309. }
  310. }
  311. /** @return copy of children list */
  312. public ArrayList<IRunSpec> getChildren() {
  313. return makeList(children);
  314. }
  315. /** @return copy of children list without children to skip */
  316. public ArrayList<IRunSpec> getWorkingChildren() {
  317. if (skipAll) {
  318. return new ArrayList<IRunSpec>();
  319. }
  320. if (null == skipSet) {
  321. return getChildren();
  322. }
  323. ArrayList<IRunSpec> result = new ArrayList<IRunSpec>();
  324. int i = 0;
  325. for (Iterator<IRunSpec> iter = children.listIterator(); iter.hasNext(); i++) {
  326. IRunSpec child = iter.next();
  327. if (!skipSet.get(i)) {
  328. result.add(child);
  329. }
  330. }
  331. return result;
  332. }
  333. /**
  334. * Recursively absorb parent values if different. This implementation calls doAdoptParentValues(..) and then calls this for any
  335. * children. This is when skipped children are determined. Children may elect to balk at this point, reducing the number of
  336. * children or causing this spec to skip if skipIfAnyChildrenSkipped. For each test skipped, either this doAdoptParentValues(..)
  337. * or the child's adoptParentValues(..) should add one info message with the reason this is being skipped. The only reason to
  338. * override this would be to NOT invoke the same for children, or to do something similar for children which are not
  339. * AbstractRunSpec.
  340. *
  341. * @param parentRuntime the RT values to adopt - ignored if null
  342. * @param handler the IMessageHandler for info messages when skipping
  343. * @return false if this wants to be skipped, true otherwise
  344. */
  345. public boolean adoptParentValues(RT parentRuntime, IMessageHandler handler) {
  346. boolean skipped = false;
  347. skipAll = false;
  348. skipSet = new BitSet();
  349. if (null != parentRuntime) {
  350. skipped = !doAdoptParentValues(parentRuntime, handler);
  351. if (skipped && skipIfAnyChildSkipped) { // no need to continue checking
  352. skipAll = true;
  353. return false;
  354. }
  355. int i = 0;
  356. for (ListIterator iter = children.listIterator(); iter.hasNext(); i++) {
  357. IRunSpec child = (IRunSpec) iter.next();
  358. if (child instanceof AbstractRunSpec) {
  359. AbstractRunSpec arsChild = (AbstractRunSpec) child;
  360. if (!arsChild.adoptParentValues(runtime, handler)) {
  361. skipSet.set(i);
  362. if (!skipped) {
  363. skipped = true;
  364. if (skipIfAnyChildSkipped) { // no need to continue checking
  365. skipAll = true;
  366. return false;
  367. }
  368. }
  369. }
  370. }
  371. }
  372. }
  373. return true;
  374. }
  375. /**
  376. * Adopt parent values. This implementation makes a local copy. If we interpret (and absorb) any options, they should be removed
  377. * from parentRuntime. This sets verbose if different (override) and directly adopts parentOptions if ours is null and otherwise
  378. * adds any non-null options we don't already have. setting verbose and adding to parent options. Implementors override this to
  379. * affect how parent values are adopted. Implementors should not recurse into children. This method may be called multiple
  380. * times, so implementors should not destroy any spec information. Always add an info message when returning false to skip
  381. *
  382. * @param parentRuntime the RT values to adopt - never null
  383. * @return false if this wants to be skipped, true otherwise
  384. */
  385. protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) {
  386. if (runtime.verbose != parentRuntime.verbose) {
  387. runtime.verbose = parentRuntime.verbose;
  388. }
  389. if (!LangUtil.isEmpty(runtime.parentOptions)) {
  390. runtime.parentOptions.clear();
  391. }
  392. if (!LangUtil.isEmpty(parentRuntime.parentOptions)) {
  393. runtime.parentOptions.addAll(parentRuntime.parentOptions);
  394. }
  395. return true;
  396. }
  397. /**
  398. * Implementations call this when signalling skips to ensure consistency in message formatting
  399. *
  400. * @param handler the IMessageHandler sink - not null
  401. * @param reason the String reason to skip - not null
  402. */
  403. protected void skipMessage(IMessageHandler handler, String reason) {
  404. LangUtil.throwIaxIfNull(handler, "handler");
  405. LangUtil.throwIaxIfNull(handler, "reason");
  406. // XXX for Runs, label does not identify the test
  407. String label = toString();
  408. MessageUtil.info(handler, "skipping \"" + label + "\" because " + reason);
  409. }
  410. // --------------------------- writing xml - would prefer castor..
  411. /**
  412. * Control XML output by renaming or suppressing output for attributes and subelements. Subelements are skipped by setting the
  413. * XMLNames booleans to false. Attributes are skipped by setting their name to null.
  414. *
  415. * @param names XMLNames with new names and/or suppress flags.
  416. */
  417. protected void setXMLNames(XMLNames names) {
  418. if (null != names) {
  419. xmlNames = names;
  420. }
  421. }
  422. // /** @return null if value is null or name="{value}" otherwise */
  423. // private String makeAttr(XMLWriter out, String name, String value) {
  424. // if (null == value) {
  425. // return null;
  426. // }
  427. // return XMLWriter.makeAttribute(name, value);
  428. // }
  429. //
  430. // /** @return null if list is null or empty or name="{flattenedList}" otherwise */
  431. // private String makeAttr(XMLWriter out, String name, List list) {
  432. // if (LangUtil.isEmpty(list)) {
  433. // return null;
  434. // }
  435. // String flat = XMLWriter.flattenList(list);
  436. // return XMLWriter.makeAttribute(name, flat);
  437. // }
  438. //
  439. /** @return true if writeAttributes(..) will produce any output */
  440. protected boolean haveAttributes() {
  441. return ((!LangUtil.isEmpty(xmlNames.descriptionName) && !LangUtil.isEmpty(description))
  442. || (!LangUtil.isEmpty(xmlNames.keywordsName) && !LangUtil.isEmpty(keywords))
  443. || (!LangUtil.isEmpty(xmlNames.optionsName) && !LangUtil.isEmpty(options)) || (!LangUtil
  444. .isEmpty(xmlNames.pathsName) && !LangUtil.isEmpty(paths)));
  445. }
  446. /**
  447. * Write attributes without opening or closing elements/attributes. An attribute is written only if the value is not empty and
  448. * the name in xmlNames is not empty
  449. */
  450. protected void writeAttributes(XMLWriter out) {
  451. if (!LangUtil.isEmpty(xmlNames.descriptionName) && !LangUtil.isEmpty(description)) {
  452. out.printAttribute(xmlNames.descriptionName, description);
  453. }
  454. if (!LangUtil.isEmpty(xmlNames.keywordsName) && !LangUtil.isEmpty(keywords)) {
  455. out.printAttribute(xmlNames.keywordsName, XMLWriter.flattenList(keywords));
  456. }
  457. if (!LangUtil.isEmpty(xmlNames.optionsName) && !LangUtil.isEmpty(options)) {
  458. out.printAttribute(xmlNames.optionsName, XMLWriter.flattenList(options));
  459. }
  460. if (!LangUtil.isEmpty(xmlNames.pathsName) && !LangUtil.isEmpty(paths)) {
  461. out.printAttribute(xmlNames.pathsName, XMLWriter.flattenList(paths));
  462. }
  463. if (!LangUtil.isEmpty(xmlNames.commentName) && !LangUtil.isEmpty(comment)) {
  464. out.printAttribute(xmlNames.commentName, comment);
  465. }
  466. if (isStaging && !LangUtil.isEmpty(xmlNames.stagingName)) {
  467. out.printAttribute(xmlNames.stagingName, "true");
  468. }
  469. if (badInput && !LangUtil.isEmpty(xmlNames.badInputName)) {
  470. out.printAttribute(xmlNames.badInputName, "true");
  471. }
  472. }
  473. /**
  474. * The default implementation writes everything as attributes, then subelements for dirChanges, messages, then subelements for
  475. * children. Subclasses that override may delegate back for any of these. Subclasses may also set XMLNames to name or suppress
  476. * any attribute or subelement.
  477. *
  478. * @see writeMessages(XMLWriter)
  479. * @see writeChildren(XMLWriter)
  480. * @see IXmlWritable#writeXml(XMLWriter)
  481. */
  482. public void writeXml(XMLWriter out) {
  483. out.startElement(xmlElementName, false);
  484. writeAttributes(out);
  485. out.endAttributes();
  486. if (!xmlNames.skipMessages) {
  487. writeMessages(out);
  488. }
  489. if (!xmlNames.skipChildren) {
  490. writeChildren(out);
  491. }
  492. out.endElement(xmlElementName);
  493. }
  494. /**
  495. * Write messages. Assumes attributes are closed, can write child elements of current element.
  496. */
  497. protected void writeMessages(XMLWriter out) {
  498. if (0 < messages.numMessages(null, true)) {
  499. SoftMessage.writeXml(out, messages);
  500. }
  501. }
  502. /**
  503. * Write children. Assumes attributes are closed, can write child elements of current element.
  504. */
  505. protected void writeChildren(XMLWriter out) {
  506. if (0 < children.size()) {
  507. for (Iterator<IRunSpec> iter = children.iterator(); iter.hasNext();) {
  508. IXmlWritable self = (IXmlWritable) iter.next();
  509. self.writeXml(out);
  510. }
  511. }
  512. }
  513. // --------------------------- logging
  514. public void printAll(PrintStream out, String prefix) {
  515. out.println(prefix + toString());
  516. for (Iterator<IRunSpec> iter = children.iterator(); iter.hasNext();) {
  517. AbstractRunSpec child = (AbstractRunSpec) iter.next(); // IRunSpec
  518. child.printAll(out, prefix + " ");
  519. }
  520. }
  521. /**
  522. * default implementation returns the description if not empty or the unqualified class name otherwise. Subclasses should not
  523. * call toString from here unless they reimplement it.
  524. *
  525. * @return name of this thing or type
  526. */
  527. protected String getPrintName() {
  528. if (!LangUtil.isEmpty(description)) {
  529. return description;
  530. } else {
  531. return LangUtil.unqualifiedClassName(this);
  532. }
  533. }
  534. /** @return summary count of spec elements */
  535. public String toString() {
  536. return getPrintName() + "(" + containedSummary() + ")";
  537. }
  538. /** @return String of the form (# [options|paths|locations|messages]).. */
  539. protected String containedSummary() {
  540. StringBuffer result = new StringBuffer();
  541. addListCount("options", options, result);
  542. addListCount("paths", paths, result);
  543. // XXXXXunused addListCount("sourceLocations", sourceLocations, result);
  544. List<IMessage> messagesList = messages.getUnmodifiableListView();
  545. addListCount("messages", messagesList, result);
  546. return result.toString().trim();
  547. }
  548. public String toLongString() {
  549. String mssg = "";
  550. if (0 < messages.numMessages(null, true)) {
  551. mssg = " expected messages (" + MessageUtil.renderCounts(messages) + ")";
  552. }
  553. return getPrintName() + containedToLongString() + mssg.trim();
  554. }
  555. /** @return String of the form (# [options|paths|locations|messages]).. */
  556. protected String containedToLongString() {
  557. StringBuffer result = new StringBuffer();
  558. addListEntries("options", options, result);
  559. addListEntries("paths", paths, result);
  560. // XXXXXunused addListEntries("sourceLocations", sourceLocations, result);
  561. List<IMessage> messagesList = messages.getUnmodifiableListView();
  562. addListEntries("messages", messagesList, result);
  563. return result.toString();
  564. }
  565. protected void initClone(AbstractRunSpec spec) throws CloneNotSupportedException {
  566. /*
  567. * clone associated objects only if not (used as?) read-only.
  568. */
  569. spec.badInput = badInput;
  570. spec.children.clear();
  571. for (Iterator<IRunSpec> iter = children.iterator(); iter.hasNext();) {
  572. // clone these...
  573. IRunSpec child = iter.next();
  574. // require all child classes to support clone?
  575. if (child instanceof AbstractRunSpec) {
  576. spec.addChild((AbstractRunSpec) ((AbstractRunSpec) child).clone());
  577. } else {
  578. throw new Error("unable to clone " + child);
  579. }
  580. }
  581. spec.comment = comment;
  582. spec.description = description;
  583. spec.dirChanges.clear();
  584. spec.dirChanges.addAll(dirChanges);
  585. spec.isStaging = spec.isStaging;
  586. spec.keywords.clear();
  587. spec.keywords.addAll(keywords);
  588. spec.messages.clearMessages();
  589. MessageUtil.handleAll(spec.messages, messages, false);
  590. spec.options.clear();
  591. spec.options.addAll(options);
  592. spec.paths.clear();
  593. spec.paths.addAll(paths);
  594. spec.runtime.copy(runtime);
  595. spec.skipAll = skipAll;
  596. spec.skipIfAnyChildSkipped = skipIfAnyChildSkipped;
  597. if (null != skipSet) {
  598. spec.skipSet = new BitSet();
  599. spec.skipSet.or(skipSet);
  600. }
  601. // spec.sourceLocation = sourceLocation;
  602. // spec.sourceLocations.clear();
  603. // XXXXXunused spec.sourceLocations.addAll(sourceLocations);
  604. spec.xmlElementName = xmlElementName;
  605. spec.xmlNames = ((AbstractRunSpec.XMLNames) xmlNames.clone());
  606. }
  607. private static void addListCount(String name, List<?> list, StringBuffer sink) {
  608. int size = list.size();
  609. if ((null != list) && (0 < size)) {
  610. sink.append(" " + size + " ");
  611. sink.append(name);
  612. }
  613. }
  614. private static void addListEntries(String name, List<?> list, StringBuffer sink) {
  615. if ((null != list) && (0 < list.size())) {
  616. sink.append(" " + list.size() + " ");
  617. sink.append(name);
  618. sink.append(": ");
  619. sink.append(list.toString());
  620. }
  621. }
  622. private <T> ArrayList<T> makeList(List<T> list) {
  623. ArrayList<T> result = new ArrayList<T>();
  624. if (null != list) {
  625. result.addAll(list);
  626. }
  627. return result;
  628. }
  629. /**
  630. * Subclasses use this to rename attributes or omit attributes or subelements. To suppress output of an attribute, pass "" as
  631. * the name of the attribute. To use default entries, pass null for that entry. XXX this really should be replaced with nested
  632. * properties associated logical name with actual name (or placeholders for "unused" and "default").
  633. */
  634. public static class XMLNames {
  635. public static final XMLNames DEFAULT = new XMLNames(null, "description", "sourceLocation", "keywords", "options", "paths",
  636. "comment", "staging", "badInput", false, false, false);
  637. final String descriptionName;
  638. final String sourceLocationName;
  639. final String keywordsName;
  640. final String optionsName;
  641. final String pathsName;
  642. final String commentName;
  643. final String stagingName;
  644. final String badInputName;
  645. final boolean skipDirChanges;
  646. final boolean skipMessages;
  647. final boolean skipChildren;
  648. protected Object clone() {
  649. return new XMLNames(null, descriptionName, sourceLocationName, keywordsName, optionsName, pathsName, commentName,
  650. stagingName, badInputName, skipDirChanges, skipMessages, skipChildren);
  651. }
  652. // not runtime, skipAll, skipIfAnyChildSkipped, skipSet
  653. // sourceLocations
  654. /**
  655. * reset all names/behavior or pass defaultNames as the defaults for any null elements
  656. */
  657. XMLNames(XMLNames defaultNames, String descriptionName, String sourceLocationName, String keywordsName, String optionsName,
  658. String pathsName, String commentName, String stagingName, String badInputName, boolean skipDirChanges,
  659. boolean skipMessages, boolean skipChildren) {
  660. this.skipDirChanges = skipDirChanges;
  661. this.skipMessages = skipMessages;
  662. this.skipChildren = skipChildren;
  663. if (null != defaultNames) {
  664. this.descriptionName = (null != descriptionName ? descriptionName : defaultNames.descriptionName);
  665. this.sourceLocationName = (null != sourceLocationName ? sourceLocationName : defaultNames.sourceLocationName);
  666. this.keywordsName = (null != keywordsName ? keywordsName : defaultNames.keywordsName);
  667. this.optionsName = (null != optionsName ? optionsName : defaultNames.optionsName);
  668. this.pathsName = (null != pathsName ? pathsName : defaultNames.pathsName);
  669. this.commentName = (null != commentName ? commentName : defaultNames.commentName);
  670. this.stagingName = (null != stagingName ? stagingName : defaultNames.stagingName);
  671. this.badInputName = (null != badInputName ? badInputName : defaultNames.badInputName);
  672. } else {
  673. this.descriptionName = descriptionName;
  674. this.sourceLocationName = sourceLocationName;
  675. this.keywordsName = keywordsName;
  676. this.optionsName = optionsName;
  677. this.pathsName = pathsName;
  678. this.commentName = commentName;
  679. this.stagingName = stagingName;
  680. this.badInputName = badInputName;
  681. }
  682. }
  683. }
  684. /** subclasses implement this to create and set up a run */
  685. abstract public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator);
  686. /** segregate runtime-only state in spec */
  687. public static class RT {
  688. /** true if we should emit verbose messages */
  689. private boolean verbose;
  690. /** null unless parent set options for children to consider */
  691. final private ArrayList<String> parentOptions;
  692. public RT() {
  693. parentOptions = new ArrayList<String>();
  694. }
  695. public boolean isVerbose() {
  696. return verbose;
  697. }
  698. /**
  699. * Set parent options - old options destroyed. Will result in duplicates if duplicates added. Null or empty entries are
  700. * ignored
  701. *
  702. * @param options ignored if null or empty
  703. */
  704. public void setOptions(String[] options) {
  705. parentOptions.clear();
  706. if (!LangUtil.isEmpty(options)) {
  707. for (int i = 0; i < options.length; i++) {
  708. if (!LangUtil.isEmpty(options[i])) {
  709. parentOptions.add(options[i]);
  710. }
  711. }
  712. }
  713. }
  714. /**
  715. * Copy values from another RT
  716. *
  717. * @param toCopy the RT to copy from
  718. * @throws IllegalArgumentException if toCopy is null
  719. */
  720. public void copy(RT toCopy) {
  721. LangUtil.throwIaxIfNull(toCopy, "parent");
  722. parentOptions.clear();
  723. parentOptions.addAll(toCopy.parentOptions);
  724. verbose = toCopy.verbose;
  725. }
  726. /**
  727. * Return any parent option accepted by validOptions, optionally removing the parent option.
  728. *
  729. * @param validOptions String[] of options to extract
  730. * @param remove if true, then remove any parent option matched
  731. * @return String[] containing any validOptions[i] in parentOptions
  732. *
  733. */
  734. public Values extractOptions(Options validOptions, boolean remove, StringBuffer errors) {
  735. Values result = Values.EMPTY;
  736. if (null == errors) {
  737. errors = new StringBuffer();
  738. }
  739. if (null == validOptions) {
  740. errors.append("null options");
  741. return result;
  742. }
  743. if (LangUtil.isEmpty(parentOptions)) {
  744. return result;
  745. }
  746. // boolean haveOption = false;
  747. String[] parents = (String[]) parentOptions.toArray(new String[0]);
  748. try {
  749. result = validOptions.acceptInput(parents);
  750. } catch (InvalidInputException e) {
  751. errors.append(e.getFullMessage());
  752. return result;
  753. }
  754. if (remove) {
  755. Option.Value[] values = result.asArray();
  756. for (int i = 0; i < values.length; i++) {
  757. Option.Value value = values[i];
  758. if (null == value) {
  759. continue;
  760. }
  761. final int max = i + value.option.numArguments();
  762. if (max > i) {
  763. if (max >= parents.length) {
  764. errors.append("expecting more args for " + value.option + " at [" + i + "]: " + Arrays.asList(parents));
  765. return result;
  766. }
  767. // XXX verify
  768. for (int j = i; j < max; j++) {
  769. parentOptions.remove(parents[j]);
  770. }
  771. i = max - 1;
  772. }
  773. }
  774. }
  775. return result;
  776. }
  777. /**
  778. * Return any parent option which has one of validOptions as a prefix, optionally absorbing (removing) the parent option.
  779. *
  780. * @param validOptions String[] of options to extract
  781. * @param absorb if true, then remove any parent option matched
  782. * @return String[] containing any validOptions[i] in parentOptions (at most once)
  783. */
  784. public String[] extractOptions(String[] validOptions, boolean absorb) {
  785. if (LangUtil.isEmpty(validOptions) || LangUtil.isEmpty(parentOptions)) {
  786. return new String[0];
  787. }
  788. ArrayList<String> result = new ArrayList<String>();
  789. // boolean haveOption = false;
  790. for (int i = 0; i < validOptions.length; i++) {
  791. String option = validOptions[i];
  792. if (LangUtil.isEmpty(option)) {
  793. continue;
  794. }
  795. for (ListIterator<String> iter = parentOptions.listIterator(); iter.hasNext();) {
  796. String parentOption = iter.next();
  797. if (parentOption.startsWith(option)) {
  798. result.add(parentOption);
  799. if (absorb) {
  800. iter.remove();
  801. }
  802. }
  803. }
  804. }
  805. return (String[]) result.toArray(new String[0]);
  806. }
  807. /** Get ListIterator that permits removals */
  808. ListIterator<String> getListIterator() {
  809. return parentOptions.listIterator();
  810. }
  811. /**
  812. * Enable verbose logging
  813. *
  814. * @param verbose if true, do verbose logging
  815. */
  816. public void setVerbose(boolean verbose) {
  817. if (this.verbose != verbose) {
  818. this.verbose = verbose;
  819. }
  820. }
  821. } // class RT
  822. }