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

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. }