summaryrefslogtreecommitdiffstats
path: root/testing/src
diff options
context:
space:
mode:
authorwisberg <wisberg>2002-12-16 18:14:27 +0000
committerwisberg <wisberg>2002-12-16 18:14:27 +0000
commit1025cde05abe95be6ca65aab3ba126258efc647f (patch)
tree7d40d6fa06fef869e4d38cbe44ce431011e2eff8 /testing/src
parentd842c4f1139629c1f062b74ba818d233b2c31043 (diff)
downloadaspectj-1025cde05abe95be6ca65aab3ba126258efc647f.tar.gz
aspectj-1025cde05abe95be6ca65aab3ba126258efc647f.zip
initial version
Diffstat (limited to 'testing/src')
-rw-r--r--testing/src/.cvsignore1
-rw-r--r--testing/src/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java1864
-rw-r--r--testing/src/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java181
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/gui.properties53
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Back16.gifbin0 -> 183 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Delete24.gifbin0 -> 93 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Forward16.gifbin0 -> 183 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Open16.gifbin0 -> 138 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Open24.gifbin0 -> 230 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Quit24.gifbin0 -> 313 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Reload24.gifbin0 -> 245 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Save16.gifbin0 -> 206 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Save24.gifbin0 -> 80 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/SaveAs16.gifbin0 -> 255 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/SaveAs24.gifbin0 -> 160 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Start16.gifbin0 -> 140 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Start24.gifbin0 -> 165 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Stop16.gifbin0 -> 171 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Stop24.gifbin0 -> 488 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Test16.gifbin0 -> 933 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Testfailed16.gifbin0 -> 872 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Testopen16.gifbin0 -> 935 bytes
-rw-r--r--testing/src/org/aspectj/testing/gui/resources/images/Testpassed16.gifbin0 -> 230 bytes
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java883
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java282
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/AjcTest.java371
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java966
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/DirChanges.java522
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java377
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/Globals.java94
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/IAjcRun.java44
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/IRunSpec.java24
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/IncCompilerRun.java444
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/JavaRun.java298
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/RunSpecIterator.java256
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/Sandbox.java386
-rw-r--r--testing/src/org/aspectj/testing/harness/bridge/Validator.java557
-rw-r--r--testing/src/org/aspectj/testing/run/IRun.java62
-rw-r--r--testing/src/org/aspectj/testing/run/IRunIterator.java62
-rw-r--r--testing/src/org/aspectj/testing/run/IRunListener.java38
-rw-r--r--testing/src/org/aspectj/testing/run/IRunStatus.java175
-rw-r--r--testing/src/org/aspectj/testing/run/IRunValidator.java28
-rw-r--r--testing/src/org/aspectj/testing/run/RunIterator.java136
-rw-r--r--testing/src/org/aspectj/testing/run/RunListener.java116
-rw-r--r--testing/src/org/aspectj/testing/run/RunListeners.java89
-rw-r--r--testing/src/org/aspectj/testing/run/RunStatus.java408
-rw-r--r--testing/src/org/aspectj/testing/run/RunValidator.java206
-rw-r--r--testing/src/org/aspectj/testing/run/Runner.java510
-rw-r--r--testing/src/org/aspectj/testing/run/WrappedRunIterator.java72
-rw-r--r--testing/src/org/aspectj/testing/util/AccumulatingFileFilter.java53
-rw-r--r--testing/src/org/aspectj/testing/util/BridgeUtil.java439
-rw-r--r--testing/src/org/aspectj/testing/util/CollectorFileFilter.java88
-rw-r--r--testing/src/org/aspectj/testing/util/Diffs.java103
-rw-r--r--testing/src/org/aspectj/testing/util/FileUtil.java745
-rw-r--r--testing/src/org/aspectj/testing/util/IntRange.java118
-rw-r--r--testing/src/org/aspectj/testing/util/IntValidator.java21
-rw-r--r--testing/src/org/aspectj/testing/util/IteratorWrapper.java147
-rw-r--r--testing/src/org/aspectj/testing/util/LangUtil.java1192
-rw-r--r--testing/src/org/aspectj/testing/util/LineReader.java204
-rw-r--r--testing/src/org/aspectj/testing/util/Node.java162
-rw-r--r--testing/src/org/aspectj/testing/util/NullPrintStream.java95
-rw-r--r--testing/src/org/aspectj/testing/util/ObjectChecker.java38
-rw-r--r--testing/src/org/aspectj/testing/util/ProxyPrintStream.java104
-rw-r--r--testing/src/org/aspectj/testing/util/RunUtils.java368
-rw-r--r--testing/src/org/aspectj/testing/util/SFileReader.java185
-rw-r--r--testing/src/org/aspectj/testing/util/StandardObjectChecker.java127
-rw-r--r--testing/src/org/aspectj/testing/util/StreamSniffer.java76
-rw-r--r--testing/src/org/aspectj/testing/util/StreamsHandler.java217
-rw-r--r--testing/src/org/aspectj/testing/util/StringAccumulator.java99
-rw-r--r--testing/src/org/aspectj/testing/util/StringVisitor.java28
-rw-r--r--testing/src/org/aspectj/testing/util/TestClassLoader.java154
-rw-r--r--testing/src/org/aspectj/testing/util/TestDiffs.java360
-rw-r--r--testing/src/org/aspectj/testing/util/ValidFileFilter.java123
-rw-r--r--testing/src/org/aspectj/testing/util/WebInstall.java208
-rw-r--r--testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java432
-rw-r--r--testing/src/org/aspectj/testing/xml/IXmlWritable.java25
-rw-r--r--testing/src/org/aspectj/testing/xml/SoftMessage.java287
-rw-r--r--testing/src/org/aspectj/testing/xml/SoftSourceLocation.java89
-rw-r--r--testing/src/org/aspectj/testing/xml/XMLWriter.java353
79 files changed, 16145 insertions, 0 deletions
diff --git a/testing/src/.cvsignore b/testing/src/.cvsignore
new file mode 100644
index 000000000..a3f0b1b77
--- /dev/null
+++ b/testing/src/.cvsignore
@@ -0,0 +1 @@
+*.lst
diff --git a/testing/src/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java b/testing/src/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java
new file mode 100644
index 000000000..ec112d2b5
--- /dev/null
+++ b/testing/src/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java
@@ -0,0 +1,1864 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.internal.tools.ant.taskdefs;
+
+import java.beans.*;
+import java.io.*;
+import java.util.*;
+import java.util.List;
+import java.text.*;
+import org.apache.tools.ant.*;
+import org.apache.tools.ant.taskdefs.*;
+import org.apache.tools.ant.types.*;
+import org.aspectj.util.LangUtil;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.*;
+import javax.swing.table.*;
+import javax.swing.text.*;
+
+public class Ajctest extends Task implements PropertyChangeListener {
+ private static Ajctest CURRENT_AJCTEST;
+
+ // todo shutdown hook assumes one task per VM
+ public Ajctest() {
+ super();
+ CURRENT_AJCTEST = this;
+ }
+
+ private static boolean firstTime = true;
+
+ public final PropertyChangeSupport bean = new PropertyChangeSupport(this);
+
+ {
+ bean.addPropertyChangeListener(this);
+ }
+
+ public void propertyChange(PropertyChangeEvent evt) {
+ String name = evt.getPropertyName();
+ if ("ajdoc.good".equals(name)) ajdocStats.goods++;
+ else if ("ajc.good".equals(name)) ajcStats.goods++;
+ else if ("run.good".equals(name)) runStats.goods++;
+ if ("ajdoc.fail".equals(name)) ajdocStats.fails++;
+ else if ("ajc.fail".equals(name)) ajcStats.fails++;
+ else if ("run.fail".equals(name)) runStats.fails++;
+ }
+
+ private void fire(String prop, Object oldval, Object newval) {
+ bean.firePropertyChange(prop, oldval, newval);
+ }
+
+ private void fire(String prop) {
+ fire(prop, "dummy-old", "dummy-new");
+ }
+
+ private static boolean dumpresults = false;
+ private static Stats ajdocStats = new Stats();
+ private static Stats ajcStats = new Stats();
+ private static Stats runStats = new Stats();
+ private static Stats errorStats = new Stats();
+ private static final String NO_TESTID = "NONE";
+ private File workingdir = new File("ajworkingdir"); //XXX
+
+ //fields
+ private String testId = NO_TESTID;
+ private List args = new Vector();
+ private List testsets = new Vector();
+ private Path classpath;
+ private Path internalclasspath;
+ private File destdir;
+ private File dir;
+ private File errordir;
+ private File errorfile;
+ private List testclasses = new Vector();
+ private boolean nocompile;
+ private Ajdoc ajdoc = null;
+ private boolean noclean;
+ private boolean noverify;
+ private List depends = new Vector();
+ //end-fields
+
+ public Argfile createArgfile() {
+ return createTestset().createArgfile();
+ }
+
+ public void setNoverify(boolean input) {
+ if (input != noverify) noverify = input;
+ }
+
+ public void setOwningTarget(Target target) {
+ super.setOwningTarget(target);
+ if (null != target) {
+ //setTestId(target.getName());
+ }
+ }
+
+ public void setTestId(String str) {
+ if ((null != str) && (0 < str.trim().length())) {
+ testId = str;
+ }
+ }
+
+ public void setArgs(String str) {
+ if (str == null || str.length() < 1) return;
+ StringTokenizer tok = new StringTokenizer(str, ",", false);
+ while (tok.hasMoreTokens()) {
+ String name = tok.nextToken().trim();
+ if (0 < name.length()) {
+ parse(name.startsWith("J") ? createJarg() : createArg(), name);
+ }
+ }
+ }
+
+ private void parse(Argument arg, String name) {
+ int itilde = name.lastIndexOf("~");
+ if (itilde != -1) {
+ name = name.substring(0, itilde) + name.substring(itilde+1);
+ }
+ int ieq = name.lastIndexOf("=");
+ int icolon = name.lastIndexOf(":");
+ int ileft = name.lastIndexOf("[");
+ int iright = name.lastIndexOf("]");
+ boolean always = true;
+ String rest = "";
+ String newName = name;
+ if (ieq != -1) {
+ rest = name.substring(ieq+1);
+ newName = name.substring(0, ieq);
+ always = true;
+ } else if (icolon != -1) {
+ rest = name.substring(icolon+1);
+ newName = name.substring(0, icolon);
+ always = false;
+ } else if (ileft != -1) {
+ newName = name.substring(0, ileft);
+ always = true;
+ }
+ String values = ileft == -1 ? rest :
+ name.substring(ileft+1, iright > ileft ? iright : rest.length()-1);
+ String value = null;
+ if (itilde != -1) {
+ String prop = project.getUserProperty(values);
+ if (prop == null) {
+ prop = project.getProperty(values);
+ }
+ if (prop != null) {
+ value = prop;
+ }
+ }
+ if (value != null) {
+ arg.setValue(value);
+ } else {
+ arg.setValues(values);
+ }
+ arg.setName(newName);
+ arg.setAlways(always);
+ }
+
+ public Argument createJarg() {
+ Argument arg = new Argument(true);
+ args.add(arg);
+ return arg;
+ }
+
+
+ public Argument createArg() {
+ Argument arg = new Argument(false);
+ args.add(arg);
+ return arg;
+ }
+
+ public void setClasspath(Path path) {
+ if (classpath == null) {
+ classpath = path;
+ } else {
+ classpath.append(path);
+ }
+ }
+
+ public Path createClasspath() {
+ if (classpath == null) {
+ classpath = new Path(project);
+ }
+ return classpath.createPath();
+ }
+
+ public void setClasspathRef(Reference r) {
+ createClasspath().setRefid(r);
+ }
+
+ public void setInternalclasspath(Path path) {
+ if (internalclasspath == null) {
+ internalclasspath = path;
+ } else {
+ internalclasspath.append(path);
+ }
+ }
+
+ public Path createInternalclasspath() {
+ if (internalclasspath == null) {
+ internalclasspath = new Path(project);
+ }
+ return internalclasspath.createPath();
+ }
+
+ public void setInternalclasspathRef(Reference r) {
+ createInternalclasspath().setRefid(r);
+ }
+
+ public void setDestdir(String destdir) {
+ this.destdir = project.resolveFile(destdir);
+ }
+
+ public void setDir(File dir) {
+ this.dir = dir;
+ }
+
+ public void setErrordir(File dir) {
+ this.errordir = errordir;
+ }
+
+ public void setErrorfile(File errorfile) {
+ this.errorfile = errorfile;
+ }
+
+ public Run createJava() {
+ Run testclass = new Run(project);
+ testclasses.add(testclass);
+ return testclass;
+ }
+
+ public void setClasses(String str) {
+ for (StringTokenizer t = new StringTokenizer(str, ", ", false);
+ t.hasMoreTokens();) {
+ createJava().setClassname(t.nextToken().trim());
+ }
+ }
+
+ public void setTestclass(String testclass) {
+ createJava().setClassname(testclass);
+ }
+
+ public void setAjdoc(boolean b) {
+ if (b && ajdoc == null) {
+ createAjdoc();
+ } else if (!b) {
+ ajdoc = null;
+ }
+ }
+
+ public void setAjdocargs(String str) {
+ createAjdoc();
+ for (StringTokenizer t = new StringTokenizer(str, ", ", false);
+ t.hasMoreTokens();) {
+ ajdoc.createArg().setValue(t.nextToken().trim());
+ }
+ }
+
+ public void addAjdoc(Ajdoc ajdoc) {
+ this.ajdoc = ajdoc;
+ }
+
+ public Ajdoc createAjdoc() {
+ return ajdoc = new Ajdoc();
+ }
+
+ public static class Argument {
+ private String name;
+ private List values = new Vector();
+ private boolean always = true;
+ final boolean isj;
+ public Argument(boolean isj) {
+ this.isj = isj;
+ }
+ public void setName(String str) {
+ this.name = str.startsWith("-") ? str :
+ ("-" + (str.startsWith("J") ? str.substring(1) : str));
+ }
+ public void setValues(String str) {
+ values = new Vector();
+ StringTokenizer tok = new StringTokenizer(str, ", ", false);
+ while (tok.hasMoreTokens()) {
+ values.add(tok.nextToken().trim());
+ }
+ }
+ public void setValue(String value) {
+ (values = new Vector()).add(value);
+ }
+ public void setAlways(boolean always) {
+ this.always = always;
+ }
+ public String toString() { return name + ":" + values; }
+ }
+
+ public void setNocompile(boolean nocompile) {
+ this.nocompile = nocompile;
+ }
+
+ private static class Stats {
+ int goods = 0;
+ int fails = 0;
+ }
+
+ private static class Arg {
+ final String name;
+ final String value;
+ final boolean isj;
+ Arg(String name, String value, boolean isj) {
+ this.name = name;
+ this.value = value;
+ this.isj = isj;
+ }
+ public String toString() {
+ return name + (!"".equals(value) ? " " + value : "");
+ }
+ }
+
+ public Testset createTestset() {
+ Testset testset = new Testset();
+ testsets.add(testset);
+ return testset;
+ }
+
+ public void setNoclean(boolean noclean) {
+ this.noclean = noclean;
+ }
+
+ public void setDepends(String depends) {
+ for (StringTokenizer t = new StringTokenizer(depends, ", ", false);
+ t.hasMoreTokens();) {
+ this.depends.add(t.nextToken().trim());
+ }
+ }
+ //end-methods
+
+ public static class Argfile {
+ private String name;
+ public void setName(String name) { this.name = name; }
+ }
+
+ public class Ajdoc {
+ private Commandline cmd = new Commandline();
+ public Commandline.Argument createArg() { return cmd.createArgument(); }
+ public Commandline getCommandline() { return cmd; }
+ public String toString() { return cmd + ""; }
+ }
+
+ public class Testset extends FileSet {
+ private List argfileNames = new Vector();
+ public List argfiles;
+ public List files;
+ public List args = new Vector();
+ public String classname;
+ private boolean havecludes = false;
+ private List testclasses = new Vector();
+ private Path classpath;
+ private Path internalclasspath;
+ private Ajdoc ajdoc = null;
+ private boolean fork = false;
+ private boolean noclean;
+ private List depends = new Vector();
+ public String toString() {
+ String str = "";
+ if (files.size() > 0) {
+ str += "files:" + "\n";
+ for (Iterator i = files.iterator(); i.hasNext();) {
+ str += "\t" + i.next() + "\n";
+ }
+ }
+ if (argfiles.size() > 0) {
+ str += "argfiles:" + "\n";
+ for (Iterator i = argfiles.iterator(); i.hasNext();) {
+ str += "\t" + i.next() + "\n";
+ }
+ }
+ if (args.size() > 0) {
+ str += "args:" + "\n";
+ for (Iterator i = args.iterator(); i.hasNext();) {
+ str += "\t" + i.next() + "\n";
+ }
+ }
+ if (testclasses.size() > 0) {
+ str += "classes:" + "\n";
+ for (Iterator i = testclasses.iterator(); i.hasNext();) {
+ str += "\t" + i.next() + "\n";
+ }
+ }
+ return str;
+ }
+ public void setIncludes(String includes) {
+ super.setIncludes(includes);
+ havecludes = true;
+ }
+ public void setExcludes(String excludes) {
+ super.setExcludes(excludes);
+ havecludes = true;
+ }
+ public void setIncludesfile(File includesfile) {
+ super.setIncludesfile(includesfile);
+ havecludes = true;
+ }
+ public void setExcludesfile(File excludesfile) {
+ super.setExcludesfile(excludesfile);
+ havecludes = true;
+ }
+
+ public void setArgfile(String name) {
+ createArgfile().setName(name);
+ }
+
+ public void setArgfiles(String str) {
+ StringTokenizer tok = new StringTokenizer(str, ", ", false);
+ while (tok.hasMoreTokens()) {
+ createArgfile().setName(tok.nextToken().trim());
+ }
+
+ }
+ public Argfile createArgfile() {
+ Argfile argfile = new Argfile();
+ argfileNames.add(argfile);
+ return argfile;
+ }
+ public Run createJava() {
+ // See crashing note
+ //Run testclass = new Run();
+ Run testclass = new Run(project);
+ this.testclasses.add(testclass);
+ return testclass;
+ }
+ public void addJava(Run run) {
+ this.testclasses.add(run);
+ }
+ public void setJava(String str) {
+ StringTokenizer t = new StringTokenizer(str, " ");
+ Run run = createJava();
+ run.setClassname(t.nextToken().trim());
+ while (t.hasMoreTokens()) {
+ run.createArg().setValue(t.nextToken().trim());
+ }
+ }
+ public void setTestclass(String testclass) {
+ createJava().setClassname(testclass);
+ }
+
+ public void setClasses(String str) {
+ for (StringTokenizer t = new StringTokenizer(str, ", ", false);
+ t.hasMoreTokens();) {
+ createJava().setClassname(t.nextToken().trim());
+ }
+ }
+ public void setClasspath(Path path) {
+ if (classpath == null) {
+ classpath = path;
+ } else {
+ classpath.append(path);
+ }
+ }
+
+ public Path createClasspath() {
+ if (classpath == null) {
+ classpath = new Path(project);
+ }
+ return classpath.createPath();
+ }
+
+ public void setClasspathRef(Reference r) {
+ createClasspath().setRefid(r);
+ }
+ public void setInternalclasspath(Path path) {
+ if (internalclasspath == null) {
+ internalclasspath = path;
+ } else {
+ internalclasspath.append(path);
+ }
+ }
+
+ public Path createInternalclasspath() {
+ if (internalclasspath == null) {
+ internalclasspath = new Path(project);
+ }
+ return internalclasspath.createPath();
+ }
+
+ public void setInternalclasspathRef(Reference r) {
+ createInternalclasspath().setRefid(r);
+ }
+
+ public void setAjdoc(boolean b) {
+ if (b && ajdoc == null) {
+ createAjdoc();
+ } else if (!b) {
+ this.ajdoc = null;
+ }
+ }
+ public Ajdoc getAjdoc() { return this.ajdoc; }
+ public void setAjdocargs(String str) {
+ createAjdoc();
+ for (StringTokenizer t = new StringTokenizer(str, ", ", false);
+ t.hasMoreTokens();) {
+ this.ajdoc.createArg().setValue(t.nextToken().trim());
+ }
+ }
+ public void addAjdoc(Ajdoc ajdoc) {
+ this.ajdoc = ajdoc;
+ }
+ public Ajdoc createAjdoc() {
+ return this.ajdoc = new Ajdoc();
+ }
+ public void setFork(boolean fork) {
+ this.fork = fork;
+ }
+ public void setNoclean(boolean noclean) {
+ this.noclean = noclean;
+ }
+ public void setDepends(String depends) {
+ for (StringTokenizer t = new StringTokenizer(depends, ", ", false);
+ t.hasMoreTokens();) {
+ this.depends.add(t.nextToken().trim());
+ }
+ }
+ //end-testset-methods
+
+ public void resolve() throws BuildException {
+ if (dir != null) this.setDir(dir);
+ File src = getDir(project);
+ argfiles = new Vector();
+ files = new Vector();
+ for(Iterator iter = argfileNames.iterator(); iter.hasNext();) {
+ String name = ((Argfile)iter.next()).name;
+ File argfile = new File(src, name);
+ if (check(argfile, name, location)) argfiles.add(argfile);
+ }
+ if (havecludes || argfiles.size() <= 0) {
+ String[] filenames =
+ getDirectoryScanner(project).getIncludedFiles();
+ for (int j = 0; j < filenames.length; j++) {
+ String name = filenames[j];
+ if (name.endsWith(".java")) {
+ File file = new File(src, name);
+ if (check(file, name, location)) files.add(file);
+ }
+ }
+ }
+ for (Iterator i = Ajctest.this.testclasses.iterator();
+ i.hasNext();) {
+ this.testclasses.add((Run)i.next());
+ }
+ if (this.classpath == null) {
+ setClasspath(Ajctest.this.classpath);
+ }
+ if (this.internalclasspath == null) {
+ setInternalclasspath(Ajctest.this.internalclasspath);
+ }
+ if (this.ajdoc == null) {
+ this.ajdoc = Ajctest.this.ajdoc;
+ }
+ if (this.fork) {
+ for (Iterator i = this.testclasses.iterator(); i.hasNext();) {
+ ((Run)i.next()).setFork(fork);
+ }
+ }
+ if (!this.noclean) {
+ this.noclean = Ajctest.this.noclean;
+ }
+ this.depends.addAll(Ajctest.this.depends);
+ }
+ private boolean check(File file, String name, Location loc)
+ throws BuildException {
+ loc = loc != null ? loc : location;
+ if (file == null) {
+ throw new BuildException
+ ("file " + name + " is null!", loc);
+ }
+ if (!file.exists()) {
+ throw new BuildException
+ ("file " + file + " with name " + name +
+ " doesn't exist!", loc);
+ }
+ return true;
+ }
+ public void setArgs(String str) {
+ if (str == null || str.length() < 1) return;
+ StringTokenizer tok = new StringTokenizer(str, ",", false);
+ while (tok.hasMoreTokens()) {
+ String name = tok.nextToken().trim();
+ parse(name.startsWith("J") ? createJarg() : createArg(), name);
+ }
+ }
+
+ public Argument createJarg() {
+ Argument arg = new Argument(true);
+ args.add(arg);
+ return arg;
+ }
+
+ public Argument createArg() {
+ Argument arg = new Argument(false);
+ args.add(arg);
+ return arg;
+ }
+ }
+
+ private void prepare() throws BuildException {
+
+ }
+
+ private void finish() throws BuildException {
+ if (errors.size() > 0) {
+ log("");
+ log("There " + w(errors) + " " + errors.size() + " errors:");
+ for (int i = 0; i < errors.size(); i++) {
+ log(" ", (Failure)errors.get(i), i);
+ }
+ }
+ allErrors.addAll(errors);
+ }
+
+ private void log(String space, Failure failure, int num) {
+ String number = "[" + num + "] ";
+ log(enough(number, 60, '-'));
+ for (int i = number.length()-1; i > 0; i--) space += " ";
+ log(space, failure.testset.files, "files:");
+ log(space, failure.testset.argfiles, "argfiles:");
+ log(space, failure.args, "args:");
+ log(space + "msgs:" + failure.msgs);
+ }
+
+
+ private String enough(String str, int size, char filler) {
+ while (str.length() < size) str += filler;
+ return str;
+ }
+
+
+ private void log(String space, List list, String title) {
+ if (list == null || list.size() < 1) return;
+ log(space + title);
+ for (Iterator i = list.iterator(); i.hasNext();) {
+ log(space + " " + i.next());
+ }
+ }
+
+ private void execute(Testset testset, List args) throws BuildException {
+ if (testset.files.size() > 0) {
+ log("\tfiles:");
+ for (Iterator i = testset.files.iterator();
+ i.hasNext();) {
+ log("\t " + i.next());
+ }
+ }
+ if (testset.argfiles.size() > 0) {
+ log("\targfiles:");
+ for (Iterator i = testset.argfiles.iterator();
+ i.hasNext();) {
+ log("\t " + i.next());
+ }
+ }
+ if (args.size() > 0) {
+ log("\targs:");
+ for (Iterator i = args.iterator();
+ i.hasNext();) {
+ log("\t " + i.next());
+ }
+ }
+ if (testset.testclasses.size() > 0) {
+ log("\tclasses:");
+ for (Iterator i = testset.testclasses.iterator();
+ i.hasNext();) {
+ log("\t " + i.next());
+ }
+ }
+ if (!testset.noclean &&
+ (!isSet("noclean") && !isSet("nocompile"))) {
+ delete(destdir);
+ make(destdir);
+ }
+ delete(workingdir);
+ make(workingdir);
+ for (Iterator i = testset.depends.iterator(); i.hasNext();) {
+ String target = i.next()+"";
+ // todo: capture failures here?
+ project.executeTarget(target);
+ }
+ int exit;
+ if (!isSet("nodoc") && testset.ajdoc != null) {
+ log("\tdoc... " + testset.ajdoc);
+ AjdocWrapper ajdoc = new AjdocWrapper(testset, args);
+ if ((exit = ajdoc.run()) != 0) {
+ post(testset, args, ajdoc.msgs, exit, "ajdoc");
+ } else {
+ fire("ajdoc.good");
+ }
+ fire("ajdoc.done");
+ log("\tdone with ajdoc.");
+ }
+ boolean goodCompile = true;
+ if (!isSet("nocompile") && !nocompile) {
+ log("\tcompile" +
+ (testset.noclean ? "(boostrapped)" : "") + "...");
+ //AjcWrapper ajc = new AjcWrapper(testset, args);
+ JavaCommandWrapper ajc;
+ // XXX dependency on Ant property ajctest.compiler
+ final String compiler = getAntProperty("ajctest.compiler");
+ if ("eclipse".equals(compiler) || "eajc".equals(compiler)) {
+ ajc = new EAjcWrapper(testset, args);
+ } else if ((null == compiler) || "ajc".equals(compiler)) {
+ ajc = new AjcWrapper(testset, args);
+ } else if ("javac".equals(compiler)) {
+ throw new Error("javac not supported");
+ //ajc = new JavacWrapper(testset, args);
+ } else {
+ throw new Error("unknown compiler: " + compiler);
+ }
+
+ System.out.println("using compiler: " + ajc);
+ try {
+ if ((exit = ajc.run()) != 0) {
+ post(testset, args, ajc.msgs, exit, "ajc");
+ goodCompile = false;
+ } else {
+ fire("ajc.good");
+ }
+ fire("ajc.done");
+ } catch (Throwable ___) {
+ post(testset, args, ___+"", -1, "ajc");
+ goodCompile = false;
+ }
+ }
+ if (!goodCompile) {
+ post(testset, new Vector(),
+ "couldn't run classes " + testset.testclasses +
+ "due to failed compile",
+ -1, "run");
+
+ } else if (!isSet("norun")) {
+ for (Iterator i = testset.testclasses.iterator();
+ i.hasNext();) {
+ Run testclass = (Run)i.next();
+ log("\ttest..." + testclass.classname());
+ if (null != destdir) {
+ testclass.setClassesDir(destdir.getAbsolutePath());
+ }
+ if ((exit = testclass.executeJava()) != 0) {
+ post(testset, new Vector(), testclass.msgs, exit, "run");
+ } else {
+ fire("run.good");
+ }
+ fire("run.done");
+ }
+ }
+ log("");
+ }
+
+ public void execute() throws BuildException {
+ gui(this);
+ dumpresults = isSet("dumpresults");
+ prepare();
+ log(testsets.size() + " testset" + s(testsets),
+ Project.MSG_VERBOSE);
+ Map testsetToArgcombo = new HashMap();
+ List argcombos = new Vector();
+ for (Iterator iter = testsets.iterator(); iter.hasNext();) {
+ Testset testset = (Testset)iter.next();
+ testset.resolve();
+ List bothargs = new Vector(args);
+ bothargs.addAll(testset.args);
+ List argcombo = argcombo(bothargs);
+ argcombos.add(new Integer(argcombo.size()));
+ testsetToArgcombo.put(testset, argcombo);
+ }
+ while (!testsetToArgcombo.isEmpty()) {
+ int _ = 1;
+ for (Iterator iter = testsets.iterator(); iter.hasNext(); _++) {
+ Testset testset = (Testset)iter.next();
+ List argcombo = (List)testsetToArgcombo.get(testset);
+ if (argcombo.size() == 0) {
+ testsetToArgcombo.remove(testset);
+ continue;
+ }
+ List args = (List)argcombo.remove(0);
+ final String startStr = "Testset " + _ + " of " + testsets.size();
+ String str = startStr + " / Combo " + _ + " of " + argcombos.size();
+ log("---------- " + str + " ----------");
+ execute(testset, args);
+ }
+ }
+
+// for (Iterator iter = testsets.iterator(); iter.hasNext(); _++) {
+// Testset testset = (Testset)iter.next();
+// testset.resolve();
+// List bothargs = new Vector(args);
+// bothargs.addAll(testset.args);
+// int __ = 1;
+// List argcombo = argcombo(bothargs);
+// log(argcombo.size() + " combination" + s(argcombo),
+// Project.MSG_VERBOSE);
+// final String startStr = "Testset " + _ + " of " + testsets.size();
+// for (Iterator comboiter = argcombo.iterator();
+// comboiter.hasNext(); __++) {
+// List args = (List)comboiter.next();
+// execute(testset, args);
+// log("");
+// }
+// }
+ finish();
+ }
+
+ private void delete(File dir) throws BuildException {
+ Delete delete = (Delete)project.createTask("delete");
+ delete.setDir(dir);
+ delete.execute();
+ }
+
+ private void make(File dir) throws BuildException {
+ Mkdir mkdir = (Mkdir)project.createTask("mkdir");
+ mkdir.setDir(dir);
+ mkdir.execute();
+ }
+
+ private String getAntProperty(String name) {
+ String uprop = project.getUserProperty(name);
+ if (null == uprop) {
+ uprop = project.getProperty(name);
+ }
+ return uprop;
+ }
+
+ private boolean isSet(String name) {
+ String uprop = project.getUserProperty(name);
+ if (uprop == null ||
+ "no".equals(uprop) ||
+ "false".equals(uprop)) return false;
+ String prop = project.getProperty(name);
+ if (prop == null ||
+ "no".equals(prop) ||
+ "false".equals(prop)) return false;
+ return true;
+ }
+
+ /**
+ * Interpose Wrapper class to catch and report exceptions
+ * by setting a positive value for System.exit().
+ * (In some cases it seems that Exceptions are not being reported
+ * as errors in the tests.)
+ * This forces the VM to fork. A forked VM is required for
+ * two reasons:
+ * (1) The wrapper class may have been defined by a different
+ * class loader than the target class, so it would not be able
+ * to load the target class;
+ * <p>
+ * (2) Since the wrapper class is generic, we have to pass in
+ * the name of the target class. I choose to do this using
+ * VM properties rather than hacking up the arguments.
+ * <p>todo: relies on name/value of property "taskdef.jar"
+ * to add jar with wrapper to invoking classpath.
+ * <p>
+ * It is beneficial for another reason:
+ * (3) The wrapper class can be asked to test-load all classes
+ * in a classes dir, by setting a VM property. This class
+ * sets up the property if the value is defined using
+ * <code>setClassesDir(String)</code>
+ * <p>todo: if more tunnelling, generalize and parse.
+ */
+ public class RunWrapper extends Java {
+ public final Class LINK_WRAPPER_CLASS = MainWrapper.class;
+ /** tracked in MainWrapper.PROP_NAME */ // todo: since reflective, avoid direct
+ public final String PROP_NAME = "MainWrapper.classname";
+ /** tracked in MainWrapper.CLASSDIR_NAME */
+ public final String CLASSDIR_NAME = "MainWrapper.classdir";
+ public final String WRAPPER_CLASS
+ = "org.aspectj.internal.tools.ant.taskdefs.MainWrapper";
+ private String classname;
+ protected String classesDir;
+ /** capture classname here, replace with WRAPPER_CLASS */
+ public void setClassname(String classname) {
+ super.setClassname(WRAPPER_CLASS);
+ this.classname = classname;
+ }
+
+ /**
+ * Setup the requirements for the wrapper class:
+ * <li>fork to get classpath and VM properties right</li>
+ * <li>set VM property</li>
+ * <li>add ${ajctest.wrapper.jar} (with wrapper class) to the classpath</li>
+ */
+ private void setup() {
+ setFork(true);
+ Commandline.Argument cname = createJvmarg();
+ cname.setValue("-D"+PROP_NAME+"="+classname);
+ if (!noverify) {
+ cname = createJvmarg();
+ cname.setValue("-Xfuture"); // todo: 1.2 or later..
+ }
+ if (null != classesDir) {
+ cname = createJvmarg();
+ cname.setValue("-D"+CLASSDIR_NAME+"="+classesDir);
+ }
+ // todo dependence on name/setting of ajctest.wrapper.jar
+ String value = project.getProperty("ajctest.wrapper.jar");
+ if (null != value) {
+ Path wrapperPath = new Path(project, value);
+ RunWrapper.this.createClasspath().append(wrapperPath);
+ }
+ }
+
+ /** do setup, then super.execute() */
+ public int executeJava() {
+ setup();
+ int result = super.executeJava();
+ // snarf - also load all classes?
+ return result;
+ }
+
+ /** set directory to scan for classes */
+ public void setClassesDir(String dir) {
+ classesDir = dir;
+ }
+ }
+
+ public class Run extends RunWrapper {
+ //public class Run extends Java
+ private Path bootclasspath;
+ public void setBootbootclasspath(Path path) {
+ if (bootclasspath == null) {
+ bootclasspath = path;
+ } else {
+ bootclasspath.append(path);
+ }
+ }
+ public Path createBootbootclasspath() {
+ if (bootclasspath == null) bootclasspath = new Path(this.project);
+ return bootclasspath.createPath();
+ }
+ public void setBootbootclasspathRef(Reference r) {
+ createBootbootclasspath().setRefid(r);
+ }
+ private Path bootclasspatha;
+ public void setBootbootclasspatha(Path path) {
+ if (bootclasspatha == null) {
+ bootclasspatha = path;
+ } else {
+ bootclasspatha.append(path);
+ }
+ }
+ public Path createBootbootclasspatha() {
+ if (bootclasspatha == null) bootclasspatha = new Path(this.project);
+ return bootclasspatha.createPath();
+ }
+ public void setBootbootclasspathaRef(Reference r) {
+ createBootbootclasspatha().setRefid(r);
+ }
+ private Path bootclasspathp;
+ public void setBootbootclasspathp(Path path) {
+ if (bootclasspathp == null) {
+ bootclasspathp = path;
+ } else {
+ bootclasspathp.append(path);
+ }
+ }
+ public Path createBootbootclasspathp() {
+ if (bootclasspathp == null) bootclasspathp = new Path(this.project);
+ return bootclasspathp.createPath();
+ }
+ public void setBootbootclasspathpRef(Reference r) {
+ createBootbootclasspathp().setRefid(r);
+ }
+ public Run(Project project) {
+ super();
+ //this.project = Ajctest.this.project;
+ this.setTaskName("ajcjava");
+ this.project = project;
+ }
+ public String msgs = "";
+ public int executeJava() {
+ Path cp = Ajctest.this.classpath != null ? Ajctest.this.classpath :
+ new Path(this.project, destdir.getAbsolutePath());
+ cp.append(Path.systemClasspath);
+ this.setClasspath(cp);
+ if (bootclasspath != null) {
+ setFork(true);
+ createJvmarg().setValue("-Xbootclasspath:" + bootclasspath);
+ }
+ if (bootclasspatha != null) {
+ setFork(true);
+ createJvmarg().setValue("-Xbootclasspath/a:" + bootclasspatha);
+ }
+ if (bootclasspathp != null) {
+ setFork(true);
+ createJvmarg().setValue("-Xbootclasspath/p:" + bootclasspathp);
+ }
+ int exit = -1;
+ // todo: add timeout feature todo: this or below?
+ try {
+ exit = super.executeJava();
+ } catch (Throwable t) {
+ StringWriter sw = new StringWriter();
+ PrintWriter out = new PrintWriter(sw);
+ t.printStackTrace(out);
+ msgs = sw.toString();
+ out.close();
+ // todo: return exit code
+ }
+ return exit;
+ }
+ public String _classname;
+ public String classname() { return _classname; }
+ public void setClassname(String classname) {
+ super.setClassname(_classname = classname);
+ }
+ public String toString() { return _classname; }
+ }
+ // class Run
+ // todo: need to run in a wrapper which report non-zero int on exception
+ // todo: unused method? see executeJava above.
+ private int java(String classname, Collection args) throws BuildException {
+ Java java = (Java)project.createTask("java");
+ java.setClassname(classname);
+ for (Iterator i = args.iterator(); i.hasNext();) {
+ Object o = i.next();
+ Commandline.Argument arg = java.createArg();
+ if (o instanceof File) {
+ arg.setFile((File)o);
+ } else if (o instanceof Path) {
+ arg.setPath((Path)o);
+ } else {
+ arg.setValue(o+"");
+ }
+ }
+ return java.executeJava();
+ }
+
+ private static List allErrors = new Vector();
+ private List errors = new Vector();
+
+ private void post(Testset testset, List args,
+ String msgs, int exit, String type) {
+ errors.add(new Failure(testset, args, msgs, exit, type, testId));
+ fire(type + ".fail");
+ }
+
+ private static long startTime;
+ private static long stopTime;
+
+ private static String date(long time) {
+ return DateFormat.getDateTimeInstance
+ (DateFormat.FULL, DateFormat.FULL).
+ format(new Date(time));
+ }
+
+ static {
+ final PrintStream err = System.err;
+ startTime = System.currentTimeMillis();
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ private String ms(long start, long stop) {
+ long rest = Math.abs(stop-start) / 1000;
+ long days = rest / 86400;
+ long hours = (rest -= days*86400) / 3600;
+ long mins = (rest -= hours*3600) / 60;
+ long secs = (rest -= mins*60);
+ boolean req = false;
+ String str = "";
+ if (req || days > 0) {
+ req = true;
+ str += days + " day" + (days != 1 ? "s" : "") + " ";
+ }
+ if (req || hours > 0) {
+ req = true;
+ str += hours + " hour" + (hours != 1 ? "s" : "") + " ";
+ }
+ if (req || mins > 0) {
+ req = true;
+ str += mins + " minute" + (mins != 1 ? "s" : "") + " ";
+ }
+ str += secs + " second" + (secs != 1 ? "s" : "") + " ";
+ return str;
+ }
+
+ public void run() {
+ Ajctest current = CURRENT_AJCTEST;
+ String oneLine = "warning: oneLine not set.";
+ String multiLine = "warning: multiLine not set.";
+
+ // setup oneLine
+ if (null == current) {
+ oneLine = "\nRESULT=\"ERROR\" null ACJTEST";
+ } else {
+ StringBuffer sb = new StringBuffer("\n");
+ int errs = current.allErrors.size();
+ int allFails = errs
+ + current.ajdocStats.fails
+ + current.ajcStats.fails
+ + current.runStats.fails;
+ if (1 > allFails) {
+ sb.append("RESULT=\"PASS\"\terrors=\"");
+ } else {
+ sb.append("RESULT=\"FAIL\"\terrors=\"");
+ }
+ sb.append(""+errs);
+ sb.append("\"\tajdoc.pass=\"");
+ sb.append(""+current.ajdocStats.goods);
+ sb.append("\"\tajdoc.fail=\"");
+ sb.append(""+current.ajdocStats.fails);
+ sb.append("\"\tajc.pass=\"");
+ sb.append(""+current.ajcStats.goods);
+ sb.append("\"\tajc.fail=\"");
+ sb.append(""+current.ajcStats.fails);
+ sb.append("\"\trun.pass=\"");
+ sb.append(""+current.runStats.goods);
+ sb.append("\"\trun.fail=\"");
+ sb.append(""+current.runStats.fails);
+ sb.append("\"\ttestId=\"");
+ sb.append(current.testId);
+ sb.append("\"\tproject=\"");
+ Project p = current.getProject();
+ if (null != p) sb.append(p.getName());
+ sb.append("\"\tfile=\"");
+ sb.append(""+current.getLocation());
+ sb.append("\"");
+ oneLine = sb.toString();
+ }
+
+ // setup multiLine
+ {
+ stopTime = System.currentTimeMillis();
+ String str = "";
+ str += "\n";
+ str +=
+ "===================================" +
+ "===================================" + "\n";
+ str += "Test started : " + date(startTime) + "\n";
+ str += "Test ended : " + date(stopTime) + "\n";
+ str += "Total time : " + ms(startTime, stopTime) + "\n";
+ str +=
+ "------------------------------" +
+ " Summary " +
+ "------------------------------" + "\n";
+ str += "Task\tPassed\tFailed" + "\n";
+ Object[] os = new Object[] {
+ "ajdoc", ajdocStats.goods+"", ajdocStats.fails+"",
+ "ajc", ajcStats.goods +"", ajcStats.fails +"",
+ "run", runStats.goods +"", runStats.fails +"",
+ };
+ for (int i = 0; i < os.length; i += 3) {
+ str += os[i] + "\t" + os[i+1] + "\t" + os[i+2] + "\n";
+ }
+ if (allErrors.size() > 0) {
+ str += "" + "\n";
+ str +=
+ "There " + w(allErrors) + " " +
+ allErrors.size() + " error" +
+ s(allErrors) + ":" + "\n";
+ for (int i = 0; i < allErrors.size(); i++) {
+ Failure failure = (Failure)allErrors.get(i);
+ str +=
+ "---------- Error " + i + " [" +
+ failure.testId + "]" +
+ " ------------------------------" + "\n";
+ str += " " + failure + "\n\n";
+ }
+ } else {
+ str += "No errors." + "\n";
+ }
+ str += "--------------------------" +
+ " End of Summary " +
+ "---------------------------" + "\n";
+ multiLine = str;
+ }
+
+ // print both multiLine and oneLine
+ err.println(multiLine);
+ err.println(oneLine);
+ if (dumpresults && (allErrors.size() +
+ ajdocStats.fails +
+ ajcStats.fails +
+ runStats.fails) > 0) {
+ String date = date(System.currentTimeMillis());
+ String filename = "ajc-errors";
+ for (StringTokenizer t = new StringTokenizer(date, ",: ");
+ t.hasMoreTokens();) {
+ filename += "-" + t.nextToken().trim();
+ }
+ filename += ".txt";
+ PrintWriter out = null;
+ File file = new File(filename);
+ System.err.println("dumping results to " + file);
+ try {
+ out = new PrintWriter(new FileWriter(file));
+ out.println(multiLine);
+ out.println(oneLine);
+ System.err.println("dumped results to " + file);
+ } catch (IOException ioe) {
+ if (out != null) out.close();
+ }
+ }
+ }
+ }));
+ }
+
+ private static String w(List list) { return a(list, "were", "was"); }
+ private static String s(List list) { return a(list, "s", ""); }
+
+ private static String a(List list, String some, String one) {
+ return list == null || list.size() != 1 ? some : one;
+ }
+
+ static class Failure {
+ public final Testset testset;
+ public final List args;
+ public final String msgs;
+ public final int exit;
+ public final String type;
+ public final long time;
+ public final String testId;
+ public Failure(Testset testset, List args,
+ String msgs, int exit, String type,
+ String testId) {
+ this.testset = testset;
+ this.args = args;
+ this.msgs = msgs;
+ this.exit = exit;
+ this.type = type;
+ this.time = System.currentTimeMillis();
+ this.testId = testId;
+ }
+ public String toString() {
+ String str = "testId:" + testId+ "\n";
+ str += "type:" + type + "\n";
+ str += testset + "\n";
+ if (args.size() > 0) {
+ str += " args: " + args + "\n";;
+ }
+ str += " msgs:" + msgs + "\n";
+ str += " exit:" + exit;
+ return str;
+ }
+ }
+
+ private List argcombo(List arguments) {
+ List combos = new Vector();
+ List always = new Vector();
+ for (Iterator iter = arguments.iterator(); iter.hasNext();) {
+ Argument arg = (Argument)iter.next();
+ if (arg.values.size() == 0) arg.values.add("");
+ if (!arg.always && !arg.values.contains(null)) arg.values.add(null);
+ if (arg.values.size() > 0) {
+ combos.add(arg);
+ } else if (arg.always) {
+ always.add(new Arg(arg.name, arg.values.get(0)+"", arg.isj));
+ }
+ }
+ List argcombo = combinations(combos);
+ for (Iterator iter = always.iterator(); iter.hasNext();) {
+ Arg arg = (Arg)iter.next();
+ for (Iterator comboiter = argcombo.iterator(); comboiter.hasNext();) {
+ ((List)comboiter.next()).add(arg);
+ }
+ }
+ return argcombo;
+ }
+
+ private abstract class ExecWrapper {
+ public String msgs;
+ public int run() {
+ return run(createCommandline());
+ }
+ protected abstract Commandline createCommandline();
+ protected final int run(Commandline cmd) {
+ Process process = null;
+ int exit = Integer.MIN_VALUE;
+ final StringBuffer buf = new StringBuffer();
+ Thread errPumper = null;
+ Thread outPumper = null;
+ try {
+ log("\tcalling " + cmd, Project.MSG_VERBOSE);
+ process = Runtime.getRuntime().exec(cmd.getCommandline());
+ OutputStream os = new OutputStream() {
+ StringBuffer sb = new StringBuffer();
+ public void write(int b) throws IOException {
+ final char c = (char)b;
+ buf.append(c);
+ if (c != '\n') {
+ sb.append(c);
+ } else {
+ System.err.println(sb.toString());
+ sb = new StringBuffer();
+ }
+ }
+ };
+ OutputStream los = new LogOutputStream(Ajctest.this,
+ Project.MSG_INFO);
+ outPumper = new Thread(new StreamPumper(process.getInputStream(),
+ los));
+ errPumper = new Thread(new StreamPumper(process.getErrorStream(),
+ os));
+ outPumper.setDaemon(true);
+ errPumper.setDaemon(true);
+ outPumper.start();
+ errPumper.start();
+ process.waitFor();
+ } catch (Exception e) {
+ e.printStackTrace();
+ //throw e;
+ } finally {
+ try {
+ if (outPumper != null) outPumper.join();
+ if (errPumper != null) errPumper.join();
+ } catch (InterruptedException ie) {
+ } finally {
+ outPumper = null;
+ errPumper = null;
+ }
+ exit = process.exitValue();
+ msgs = buf.toString();
+ if (exit != 0) {
+ log("Test failed with exit value: " + exit);
+ } else {
+ log("Success!", Project.MSG_VERBOSE);
+ }
+ if (process != null) process.destroy();
+ process = null;
+ System.err.flush();
+ System.out.flush();
+ }
+ return exit;
+ }
+ }
+
+ private class AjcWrapper extends JavaCommandWrapper {
+ public AjcWrapper(Testset testset, List args) {
+ super(testset, args, false);
+ if (testset.noclean) {
+ setExtraclasspath(new Path(project,
+ destdir.getAbsolutePath()));
+ }
+ }
+ String getMainClassName() {
+ return "org.aspectj.tools.ajc.Main";
+ }
+ }
+
+ private class EAjcWrapper extends JavaCommandWrapper {
+ public EAjcWrapper(Testset testset, List args) {
+ super(testset, args, false);
+ if (testset.noclean) {
+ setExtraclasspath(new Path(project,
+ destdir.getAbsolutePath()));
+ }
+ }
+ String getMainClassName() {
+ return "org.aspectj.ajdt.ajc.Main";
+ }
+ }
+
+ static List ajdocArgs(List args) {
+ List newargs = new Vector();
+ for (Iterator i = args.iterator(); i.hasNext();) {
+ String arg = i.next() + "";
+ if (arg.startsWith("-X")) {
+ newargs.add(arg);
+ } else if (arg.equals("-public") ||
+ arg.equals("-package") ||
+ arg.equals("-protected") ||
+ arg.equals("-private")) {
+ newargs.add(arg);
+ } else if (arg.equals("-d") ||
+ arg.equals("-classpath") ||
+ arg.equals("-cp") ||
+ arg.equals("-sourcepath") ||
+ arg.equals("-bootclasspath") ||
+ arg.equals("-argfile")) {
+ newargs.add(arg);
+ newargs.add(i.next()+"");
+ } else if (arg.startsWith("@")) {
+ newargs.add(arg);
+ }
+ }
+ return newargs;
+ }
+
+ private class AjdocWrapper extends JavaCommandWrapper {
+ public AjdocWrapper(Testset testset, List args) {
+ super(testset, ajdocArgs(args), true);
+ String[] cmds = testset.getAjdoc().getCommandline().getCommandline();
+ for (int i = 0; i < cmds.length; i++) {
+ this.args.add(cmds[i]);
+ }
+ }
+ String getMainClassName() {
+ return "org.aspectj.tools.ajdoc.Main";
+ }
+ }
+
+ private abstract class JavaCommandWrapper extends ExecWrapper {
+ abstract String getMainClassName();
+ protected Testset testset;
+ protected List args;
+ protected boolean needsClasspath;
+ protected Path extraClasspath;
+
+ public JavaCommandWrapper(Testset testset, List args,
+ boolean needsClasspath) {
+ this.testset = testset;
+ this.args = args;
+ this.needsClasspath = needsClasspath;
+ this.extraClasspath = testset.internalclasspath;
+ }
+ public void setExtraclasspath(Path extraClasspath) {
+ this.extraClasspath = extraClasspath;
+ }
+
+ public String toString() {
+ return LangUtil.unqualifiedClassName(getClass())
+ + "(" + getMainClassName() + ")";
+ }
+
+ protected Commandline createCommandline() {
+ Commandline cmd = new Commandline();
+ cmd.setExecutable("java");
+ cmd.createArgument().setValue("-classpath");
+ Path cp = null;
+ if (extraClasspath != null) {
+ cp = extraClasspath;
+ }
+ if (extraClasspath == null) {
+ Path aspectjBuildDir =
+ new Path(project,
+ project.getProperty("ajctest.pathelement"));
+ // todo: dependency on ant script variable name ajctest.pathelement
+ if (cp == null) cp = aspectjBuildDir;
+ else cp.append(aspectjBuildDir);
+ }
+ if (cp == null) {
+ cp = Path.systemClasspath;
+ } else {
+ cp.append(Path.systemClasspath);
+ }
+ cmd.createArgument().setPath(cp);
+ for (Iterator iter = args.iterator(); iter.hasNext();) {
+ Arg arg = (Arg)iter.next();
+ if (arg.isj) {
+ cmd.createArgument().setValue(arg.name);
+ if (!arg.value.equals("")) {
+ cmd.createArgument().setValue(arg.value);
+ }
+ }
+ }
+ cmd.createArgument().setValue(getMainClassName());
+ boolean alreadySetDestDir = false;
+ boolean alreadySetClasspath = false;
+ for (Iterator iter = args.iterator(); iter.hasNext();) {
+ Arg arg = (Arg)iter.next();
+ if (!arg.isj) {
+ cmd.createArgument().setValue(arg.name);
+ if (arg.name.equals("-d")) {
+ setDestdir(arg.value+"");
+ alreadySetDestDir = true;
+ }
+ if (arg.name.equals("-classpath")) {
+ alreadySetClasspath = true;
+ }
+ if (!arg.value.equals("")) {
+ cmd.createArgument().setValue(arg.value);
+ }
+ }
+ }
+ if (destdir == null) {
+ setDestdir(".");
+ }
+ if (!alreadySetDestDir) {
+ cmd.createArgument().setValue("-d");
+ cmd.createArgument().setFile(destdir);
+ }
+ if (!alreadySetClasspath && testset.classpath != null) {
+ cmd.createArgument().setValue("-classpath");
+ cmd.createArgument().setPath(testset.classpath);
+ } else if (needsClasspath) {
+ Path _cp = Ajctest.this.classpath != null ? Ajctest.this.classpath :
+ new Path(project, destdir.getAbsolutePath());
+ _cp.append(Path.systemClasspath);
+ cmd.createArgument().setValue("-classpath");
+ cmd.createArgument().setPath(_cp);
+ }
+ for (Iterator iter = testset.files.iterator(); iter.hasNext();) {
+ cmd.createArgument().setFile((File)iter.next());
+ }
+ for (Iterator iter = testset.argfiles.iterator(); iter.hasNext();) {
+ cmd.createArgument().setValue("-argfile");
+ cmd.createArgument().setFile((File)iter.next());
+ }
+ return cmd;
+ }
+ }
+
+ /** implement invocation of ajc */
+ private void java(Testset testset, List args) throws BuildException {
+ Java java = (Java)project.createTask("java");
+ java.setClassname("org.aspectj.tools.ajc.Main");
+ if (classpath != null) {
+ java.setClasspath(classpath);
+ }
+ for (Iterator iter = args.iterator(); iter.hasNext();) {
+ Arg arg = (Arg)iter.next();
+ if (arg.isj) {
+ java.createJvmarg().setValue(arg.name);
+ if (!arg.value.equals("")) {
+ java.createJvmarg().setValue(arg.value);
+ }
+ }
+ }
+ for (Iterator iter = args.iterator(); iter.hasNext();) {
+ Arg arg = (Arg)iter.next();
+ if (!arg.isj) {
+ java.createArg().setValue(arg.name);
+ if (!arg.value.equals("")) {
+ java.createArg().setValue(arg.value);
+ }
+ }
+ }
+ for (Iterator iter = testset.files.iterator(); iter.hasNext();) {
+ java.createArg().setFile((File)iter.next());
+ }
+ for (Iterator iter = testset.argfiles.iterator(); iter.hasNext();) {
+ java.createArg().setValue("-argfile");
+ java.createArg().setFile((File)iter.next());
+ }
+ java.setFork(true);
+ java.execute();
+ }
+
+ private void exec(Testset testset, List args) throws BuildException {
+ ExecTask exec = (ExecTask)project.createTask("exec");
+ exec.setExecutable("java");
+ if (classpath != null) {
+ exec.createArg().setValue("-classpath");
+ exec.createArg().setPath(classpath);
+ }
+ for (Iterator iter = args.iterator(); iter.hasNext();) {
+ Arg arg = (Arg)iter.next();
+ if (arg.isj) {
+ exec.createArg().setValue(arg.name);
+ if (!arg.value.equals("")) {
+ exec.createArg().setValue(arg.value);
+ }
+ }
+ }
+ exec.createArg().setValue("org.aspectj.tools.ajc.Main");
+ for (Iterator iter = args.iterator(); iter.hasNext();) {
+ Arg arg = (Arg)iter.next();
+ if (!arg.isj) {
+ exec.createArg().setValue(arg.name);
+ if (!arg.value.equals("")) {
+ exec.createArg().setValue(arg.value);
+ }
+ }
+ }
+ for (Iterator iter = testset.files.iterator(); iter.hasNext();) {
+ exec.createArg().setFile((File)iter.next());
+ }
+ for (Iterator iter = testset.argfiles.iterator(); iter.hasNext();) {
+ exec.createArg().setValue("-argfile");
+ exec.createArg().setFile((File)iter.next());
+ }
+ exec.execute();
+ }
+
+ public void handle(Throwable t) {
+ log("handling " + t);
+ if (t != null) t.printStackTrace();
+ log("done handling " + t);
+ }
+
+ private List combinations(List arglist) {
+ List result = new Vector();
+ result.add(new Vector());
+ for (Iterator iter = arglist.iterator(); iter.hasNext();) {
+ Argument arg = (Argument)iter.next();
+ int N = result.size();
+ for (int i = 0; i < N; i++) {
+ List to = (List)result.remove(0);
+ for (Iterator valiter = arg.values.iterator(); valiter.hasNext();) {
+ List newlist = new Vector(to);
+ Object val = valiter.next();
+ if (val != null) newlist.add(new Arg(arg.name, val+"", arg.isj));
+ result.add(newlist);
+ }
+ }
+ }
+ return result;
+ }
+
+ /////////////////////// GUI support //////////////////////////////
+ private static Gui gui;
+
+ private static void gui(Ajctest ajc) {
+ if (firstTime && ajc.isSet("gui")) {
+ JFrame f = new JFrame("AspectJ Test Suite");
+ f.getContentPane().add(BorderLayout.CENTER, gui = new Gui());
+ f.pack();
+ f.setVisible(true);
+ }
+ if (gui != null) {
+ ajc.bean.addPropertyChangeListener(gui);
+ }
+ firstTime = false;
+ }
+
+ private static class Gui extends JPanel implements PropertyChangeListener {
+ private FailurePanel fail = new FailurePanel();
+ private TablePanel table = new TablePanel();
+ private StatusPanel status = new StatusPanel();
+ public Gui() {
+ super(new BorderLayout());
+ JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+ split.setPreferredSize(new Dimension(500, 300));
+ split.add(JSplitPane.BOTTOM, fail);
+ split.add(JSplitPane.TOP, table);
+ split.setDividerLocation(200);
+ add(BorderLayout.CENTER, split);
+ add(BorderLayout.SOUTH, status);
+ setPreferredSize(new Dimension(640, 680));
+ }
+ public void propertyChange(PropertyChangeEvent evt) {
+ String name = evt.getPropertyName();
+ if ("ajdoc.good".equals(name)) {
+ status.ajc.goods.inc();
+ } else if ("ajc.good".equals(name)) {
+ status.ajc.goods.inc();
+ } else if ("run.good".equals(name)) {
+ status.runs.goods.inc();
+ }
+ if ("ajdoc.done".equals(name)) {
+ status.ajc.total.inc();
+ } else if ("ajc.done".equals(name)) {
+ status.ajc.total.inc();
+ } else if ("run.done".equals(name)) {
+ status.runs.total.inc();
+ }
+ if ("ajdoc.fail".equals(name)) {
+ status.ajc.fails.inc();
+ } else if ("ajc.fail".equals(name)) {
+ status.ajc.fails.inc();
+ } else if ("run.fail".equals(name)) {
+ status.runs.fails.inc();
+ }
+ }
+
+ private abstract static class TitledPanel extends JPanel {
+ public TitledPanel(LayoutManager layout, String title) {
+ super(layout);
+ setBorder(BorderFactory.createTitledBorder(title));
+ }
+ }
+
+ private static class StatusPanel extends TitledPanel {
+ StatusInnerPanel ajdoc = new StatusInnerPanel("Ajdoc");
+ StatusInnerPanel runs = new StatusInnerPanel("Runs");
+ StatusInnerPanel ajc = new StatusInnerPanel("Ajc");
+
+ public StatusPanel() {
+ super(new FlowLayout(), "Status");
+ add(ajdoc);
+ add(runs);
+ add(ajc);
+ }
+
+ private static class StatusInnerPanel extends TitledPanel {
+ IntField goods = new IntField(5, Color.green.darker());
+ IntField fails = new IntField(5, Color.red.darker());
+ IntField total = new IntField(5, Color.blue.darker());
+ public StatusInnerPanel(String str) {
+ super(null, str);
+ this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ Object[] os = new Object[] {
+ "Passed", goods,
+ "Failed", fails,
+ "Totals", total,
+ };
+ for (int i = 0; i < os.length; i += 2) {
+ JPanel p = p();
+ p.add(new JLabel(os[i]+":"));
+ p.add((Component)os[i+1]);
+ this.add(p);
+ }
+ }
+
+ private JPanel p() {
+ JPanel p = new JPanel();
+ p.setLayout(new FlowLayout(FlowLayout.LEFT));
+ return p;
+ }
+
+ private class IntField extends JTextField {
+ public IntField(int i, Color fg) {
+ super("0", i);
+ this.setBackground(StatusInnerPanel.this.getBackground());
+ this.setForeground(fg);
+ this.setEditable(false);
+ this.setBorder(BorderFactory.createEmptyBorder());
+ }
+ public void add(int i) {
+ setText((Integer.parseInt(getText().trim())+i)+"");
+ }
+ public void inc() { add(1); }
+ }
+ }
+ }
+
+ private class TablePanel extends TitledPanel {
+ private DefaultTableModel model = new DefaultTableModel();
+ private TJable table;
+ private List failures = new Vector();
+ public TablePanel() {
+ super(new BorderLayout(), "Failures");
+ Object[] names = new String[] {
+ "Task", "Type", "Number", "Time"
+ };
+ for (int i = 0; i < names.length; i++) {
+ model.addColumn(names[i]);
+ }
+ table = new TJable(model, failures);
+ this.add(new JScrollPane(table), BorderLayout.CENTER);
+ }
+
+ private class TJable extends JTable {
+ private List list;
+ public TJable(TableModel model, List list) {
+ super(model);
+ this.list = list;
+ setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ }
+ public void valueChanged(ListSelectionEvent e) {
+ super.valueChanged(e);
+ if (list == null) return;
+ int i = (e.getFirstIndex()-e.getLastIndex())/2;
+ if (list.size() > 0) {
+ Failure f = (Failure)list.get(i);
+ fail.setFailure(f);
+ }
+ }
+ }
+
+ public void add(Failure f, String taskname, String type,
+ int num, long time) {
+ model.addRow(new Object[]{taskname, type,
+ new Integer(num), date(time)});
+ failures.add(f);
+ }
+ }
+
+ private static class FailurePanel extends TitledPanel {
+ private JTextArea msgs = new JTextArea(10,50);
+ private InfoPanel info = new InfoPanel();
+
+ public FailurePanel() {
+ super(new BorderLayout(), "Failure");
+ msgs.setFont(StyleContext.getDefaultStyleContext().
+ getFont("SansSerif", Font.PLAIN, 10));
+ add(BorderLayout.NORTH, info);
+ JScrollPane sc = new JScrollPane(msgs);
+ sc.setBorder(BorderFactory.createTitledBorder("Messages"));
+ add(BorderLayout.CENTER, sc);
+ }
+
+ public void setText(String str) {
+ msgs.setText(str);
+ }
+
+ public void setFailure(Failure f) {
+ msgs.setText(f.msgs);
+ info.setText("Type" , f.type);
+ info.setText("Args" , f.args);
+ info.setText("Exit" , f.exit+"");
+ info.setText("Time" , date(f.time));
+ info.setText("Files" , f.testset.files);
+ info.setText("Classnames" , f.testset.testclasses);
+ }
+
+ private static class InfoPanel extends JPanel {
+ Map fields = new HashMap();
+ public void setText(String key, Object str) {
+ ((JTextField)fields.get(key)).setText(str+"");
+ }
+ public InfoPanel() {
+ super(new GridBagLayout());
+ LabelFieldGBC gbc = new LabelFieldGBC();
+ Object[] os = new Object[] {
+ "Type",
+ "Args",
+ "Exit",
+ "Time",
+ "Files",
+ "Classnames",
+ };
+ for (int i = 0; i < os.length; i++) {
+ String name = os[i]+"";
+ JLabel label = new JLabel(name+":");
+ JTextField comp = new JTextField(25);
+ comp.setEditable(false);
+ comp.setBackground(Color.white);
+ comp.setBorder(BorderFactory.
+ createBevelBorder(BevelBorder.LOWERED));
+ label.setLabelFor(comp);
+ fields.put(name, comp);
+ add(label, gbc.forLabel());
+ add(comp, gbc.forField());
+ }
+ add(new JLabel(), gbc.forLastLabel());
+ }
+ }
+
+ private static class LabelFieldGBC extends GridBagConstraints {
+ public LabelFieldGBC() {
+ insets = new Insets(1,3,1,3);
+ gridy = RELATIVE;
+ gridheight = 1;
+ gridwidth = 1;
+ }
+ public LabelFieldGBC forLabel() {
+ fill = NONE;
+ gridx = 0;
+ anchor = NORTHEAST;
+ weightx = 0.0;
+ return this;
+ }
+
+ public LabelFieldGBC forLastLabel() {
+ forLabel();
+ fill = VERTICAL;
+ weighty = 1.0;
+ return this;
+ }
+
+ public LabelFieldGBC forField() {
+ fill = HORIZONTAL;
+ gridx = 1;
+ anchor = CENTER;
+ weightx = 1.0;
+ return this;
+ }
+
+ public LabelFieldGBC forLastField() {
+ forField();
+ fill = BOTH;
+ weighty = 1.0;
+ return this;
+ }
+ }
+ }
+ }
+
+}
diff --git a/testing/src/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java b/testing/src/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java
new file mode 100644
index 000000000..656a284f4
--- /dev/null
+++ b/testing/src/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java
@@ -0,0 +1,181 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+
+package org.aspectj.internal.tools.ant.taskdefs;
+
+import org.aspectj.testing.util.LangUtil; // todo config management here
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.InvocationTargetException;
+import java.io.*;
+
+/**
+ * Wrapper to invoke class identified by setting VM argument.
+ * Caller must set a system property "MainWrapper.classname"
+ * to the fully-qualified name of the target class to invoke,
+ * and the target class must be resolvable from the defining
+ * class loader of this class.
+ * VM argument name is available as <code>PROP_NAME</code>, but
+ * is set by adding the following to the command line:
+ * <code>-DMainWrapper.classname="fully.qualified.Classname"</code>.
+ * This returns -1 if unable to load the main method,
+ * 1 if the invoked method throws an exception, and 0 otherwise.
+ * TODO: prelminary
+ */
+public class MainWrapper {
+ /** MUST set the fully-qualified name of class to invoke using
+ * a VM property of this name
+ * tracked in Ajctest.java */
+ public static final String PROP_NAME = "MainWrapper.classname";
+ /** May set the path to a classes diretory,
+ * to interpret class names and load classes.
+ * Tracked in Ajctest.java */
+ public static final String CLASSDIR_NAME = "MainWrapper.classdir";
+
+ /** to disable returning int via System.exit, set to boolean true value (todo: ignored) */
+ public static final String SIGNAL_EXCEPTION_NAME = "MainWrapper.signalException";
+
+ /** to disable returning via System.exit on first Throwable, set to boolean true value (todo: ignored) */
+ public static final String FAIL_ON_EXCEPTION_NAME = "MainWrapper.failOnException";
+
+ /** quit on first exception */ // todo public class controls - yuck
+ public static boolean FAIL_ON_EXCEPTION = true;
+
+ /** signal number of exceptions with int return value */
+ public static boolean SIGNAL_EXCEPTION = true;
+
+ /** redirect messages for exceptions; if null, none printed */
+ public static PrintStream OUT_STREAM = System.err;
+
+ /** result accumulated, possibly from multiple threads */
+ private static int result;
+
+ /**
+ * Run target class's main(args), doing a System.exit() with
+ * a value > 0 for the number of Throwable that
+ * the target class threw that
+ * makes it through to a top-level ThreadGroup. (This is
+ * strictly speaking not correct since applications can live
+ * after their exceptions stop a thread.)
+ * Exit with a value < 0 if there were exceptions in loading
+ * the target class. Messages are printed to OUT_STREAM.
+ */
+ public static void main(String[] args) {
+ String classname = "no property : " + PROP_NAME;
+ Method main = null;
+ // setup: this try block is for loading main method - return -1 if fail
+ try {
+ // access classname from jvm arg
+ classname = System.getProperty(PROP_NAME);
+ // this will fail if the class is not available from this classloader
+ Class cl = Class.forName(classname);
+ final Class[] argTypes = new Class[] {String[].class};
+ // will fail if no main method
+ main = cl.getMethod("main", argTypes);
+ if (!Modifier.isStatic(main.getModifiers())) {
+ PrintStream outStream = OUT_STREAM;
+ if (null != outStream) outStream.println("main is not static");
+ result = -1;
+ }
+ // if user also request loading of all classes...
+ String classesDir = System.getProperty(CLASSDIR_NAME);
+ if ((null != classesDir) && (0 < classesDir.length())) {
+ MainWrapper.loadAllClasses(new File(classesDir));
+ }
+ } catch (Throwable t) {
+ if (1 != result) result--;
+ reportException("setup Throwable invoking class " + classname, t);
+ }
+ // run: this try block is for running things - get Throwable from our thread here
+ if ((null != main) && (0 == result)) {
+ try {
+ runInOurThreadGroup(main, args);
+ } catch (Throwable t) {
+ if (result > -1) {
+ result++;
+ }
+ reportException("run Throwable invoking class " + classname, t);
+ }
+ }
+ if ((0 != result) && (SIGNAL_EXCEPTION)) {
+ System.exit(result);
+ }
+ }
+
+ static void runInOurThreadGroup(final Method main, final String[] args) {
+ final String classname = main.getDeclaringClass().getName();
+ ThreadGroup ourGroup = new ThreadGroup("MainWrapper ThreadGroup") {
+ public void uncaughtException(Thread t, Throwable e) {
+ reportException("uncaughtException invoking " + classname, e);
+ result++;
+ if (FAIL_ON_EXCEPTION) {
+ System.exit((SIGNAL_EXCEPTION ? result : 0));
+ }
+ }
+ };
+ Runnable runner = new Runnable() {
+ public void run() {
+ try {
+ main.invoke(null, new Object[] {args});
+ } catch (InvocationTargetException e) {
+ result = -1;
+ reportException("InvocationTargetException invoking " + classname, e);
+ } catch (IllegalAccessException e) {
+ result = -1;
+ reportException("IllegalAccessException invoking " + classname, e);
+ }
+ }
+ };
+ Thread newMain = new Thread(ourGroup, runner, "pseudo-main");
+ newMain.start();
+ try {
+ newMain.join();
+ } catch (InterruptedException e) {
+ result = -1; // todo: InterruptedException might be benign - retry?
+ reportException("Interrupted while waiting for to join " + newMain, e);
+ }
+ }
+
+ /**
+ * Try to load all classes in a directory.
+ * @throws Error if any failed
+ */
+ static protected void loadAllClasses(File classesDir) {
+ if (null != classesDir) {
+ String[] names = LangUtil.classesIn(classesDir);
+ StringBuffer err = new StringBuffer();
+ LangUtil.loadClasses(names, null, err);
+ if (0 < err.length()) {
+ throw new Error("MainWrapper Errors loading classes: "
+ + err.toString());
+ }
+ }
+ }
+
+ static void reportException(String context, Throwable t) {
+ PrintStream outStream = OUT_STREAM;
+ if (null != outStream) {
+ while ((null != t) &&
+ (InvocationTargetException.class.isAssignableFrom(t.getClass()))) {
+ t = ((InvocationTargetException) t).getTargetException();
+ }
+ outStream.println(" context: " + context);
+ outStream.println(" message: " + t.getMessage());
+ t.printStackTrace(outStream);
+ }
+ }
+
+} // MainWrapper
diff --git a/testing/src/org/aspectj/testing/gui/resources/gui.properties b/testing/src/org/aspectj/testing/gui/resources/gui.properties
new file mode 100644
index 000000000..56a5878ac
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/gui.properties
@@ -0,0 +1,53 @@
+gui.actions=quit delete reload open save saveas stop start
+
+
+quit.class=org.aspectj.testing.gui.Gui$QuitAction
+quit.icon=Quit24.gif
+quit.tooltip=Quit
+
+delete.class=org.aspectj.testing.gui.Gui$DeleteAction
+delete.icon=Delete24.gif
+delete.tooltip=Delete Test Cases
+
+stop.class=org.aspectj.testing.gui.Gui$StopAction
+stop.icon=Stop24.gif
+stop.tooltip=Stop running test
+
+reload.class=org.aspectj.testing.gui.Gui$ReloadAction
+reload.icon=Reload24.gif
+reload.tooltip=Reload current test
+
+open.class=org.aspectj.testing.gui.Gui$OpenAction
+open.icon=Open24.gif
+open.tooltip=Open a test
+
+save.class=org.aspectj.testing.gui.Gui$SaveAction
+save.icon=Save24.gif
+save.tooltip=Save current test
+
+saveas.class=org.aspectj.testing.gui.Gui$SaveAsAction
+saveas.icon=SaveAs24.gif
+saveas.tooltip=Save current test as...
+
+start.class=org.aspectj.testing.gui.Gui$StartAction
+start.icon=Start24.gif
+start.tooltip=Start test
+
+editor.actions=openfile savefile savefileas
+
+openfile.class=org.aspectj.testing.gui.Gui$OpenAction
+openfile.icon=Open24.gif
+openfile.tooltip=Open a file
+
+savefile.class=org.aspectj.testing.gui.Gui$SaveAction
+savefile.icon=Save24.gif
+savefile.tooltip=Save currect file
+
+savefileas.class=org.aspectj.testing.gui.Gui$SaveAsAction
+savefileas.icon=SaveAs24.gif
+savefileas.tooltip=Save current file as...
+
+gui.width=700
+gui.less.height=100
+gui.top.divider.location=0.70
+gui.center.divider.location=0.60
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Back16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Back16.gif
new file mode 100644
index 000000000..f48362d71
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Back16.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Delete24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Delete24.gif
new file mode 100644
index 000000000..d8c8c2d3d
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Delete24.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Forward16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Forward16.gif
new file mode 100644
index 000000000..d25a3f956
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Forward16.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Open16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Open16.gif
new file mode 100644
index 000000000..e3ea2f564
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Open16.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Open24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Open24.gif
new file mode 100644
index 000000000..cbb2e08ba
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Open24.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Quit24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Quit24.gif
new file mode 100644
index 000000000..90515c8ac
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Quit24.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Reload24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Reload24.gif
new file mode 100644
index 000000000..3ead627fb
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Reload24.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Save16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Save16.gif
new file mode 100644
index 000000000..954f1accd
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Save16.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Save24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Save24.gif
new file mode 100644
index 000000000..8813f46d2
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Save24.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/SaveAs16.gif b/testing/src/org/aspectj/testing/gui/resources/images/SaveAs16.gif
new file mode 100644
index 000000000..8d3929c8a
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/SaveAs16.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/SaveAs24.gif b/testing/src/org/aspectj/testing/gui/resources/images/SaveAs24.gif
new file mode 100644
index 000000000..40a3d8739
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/SaveAs24.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Start16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Start16.gif
new file mode 100644
index 000000000..a72a9ca5c
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Start16.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Start24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Start24.gif
new file mode 100644
index 000000000..63230a9e8
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Start24.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Stop16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Stop16.gif
new file mode 100644
index 000000000..491845244
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Stop16.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Stop24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Stop24.gif
new file mode 100644
index 000000000..3fae3827e
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Stop24.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Test16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Test16.gif
new file mode 100644
index 000000000..b728bcbe2
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Test16.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Testfailed16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Testfailed16.gif
new file mode 100644
index 000000000..44b9ea6d5
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Testfailed16.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Testopen16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Testopen16.gif
new file mode 100644
index 000000000..cd090c884
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Testopen16.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Testpassed16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Testpassed16.gif
new file mode 100644
index 000000000..c5689e6e9
--- /dev/null
+++ b/testing/src/org/aspectj/testing/gui/resources/images/Testpassed16.gif
Binary files differ
diff --git a/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java b/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java
new file mode 100644
index 000000000..944f096d4
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java
@@ -0,0 +1,883 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.ISourceLocation;
+import org.aspectj.bridge.MessageHandler;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.testing.run.IRunIterator;
+import org.aspectj.testing.util.BridgeUtil;
+import org.aspectj.testing.xml.IXmlWritable;
+import org.aspectj.testing.xml.SoftMessage;
+import org.aspectj.testing.xml.XMLWriter;
+import org.aspectj.util.LangUtil;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Base class for initialization of components expecting messages,
+ * options, files/paths, and source locations (resolved files),
+ * and potentially containing child Spec.
+ * <p>
+ * <u>initialization</u>: This defines bean/xml setters for all.
+ * This converts String to IMessage using
+ * {@link MessageUtil#readMessage(String)}
+ * and String to ISourceLocation using
+ * {@link BridgeUtil#makeSourceLocation(input)}.
+ * See those APIs for input form and limitations.
+ * A Spec also accepts (or rejects) runtime configuration from a parent
+ * in {@link adoptParentValues(RT, IMessageHandler)}.
+ * Since some children Spec may balk but this parent Spec continue,
+ * use {@link getChildren()} to get the full list of children Spec,
+ * but {@link getWorkingChildren()} to get the list of children that
+ * are not being skipped in accordance with runtime configuration.
+ * <p>
+ * <u>subclassing</u>: subclasses wishing other than
+ * the default behavior for reading String input should override the
+ * corresponding (bean) setter. They can also override the
+ * add{foo} methods to get notice or modify objects constructed
+ * from the input.
+ * <p>
+ * <u>bean properties</u>: because this is designed to work
+ * by standard Java bean introspection, take care to follow
+ * bean rules when adding methods. In particular, a property
+ * is illegal if the setter takes a different type than the
+ * getter returns. That means, e.g., that all List and array[] getters
+ * should be named "get{property}[List|Array]". Otherwise
+ * the XML readers will silently fail to set the property
+ * (perhaps with trace information that the property had
+ * no write method or was read-only).
+ * <p>
+ * <u>Coordination with writers</u>: because this reads the contents
+ * of values written by IXmlWritable, they should ensure their
+ * values are readable. When flattening and unflattening
+ * lists, the convention is to use the {un}flattenList(..) methods
+ * in XMLWriter.
+ * @see XMLWriter@unflattenList(String)
+ * @see XMLWriter@flattenList(List)
+ */
+abstract public class AbstractRunSpec implements IRunSpec { // XXX use MessageHandler?
+
+ /** true if we expect to use a staging directory */
+ boolean isStaging;
+
+ protected String description;
+
+ /** optional source location of the specification itself */
+ protected ISourceLocation sourceLocation;
+
+ private BitSet skipSet;
+ private boolean skipAll;
+
+ protected final String xmlElementName;
+ protected final ArrayList /*String*/ keywords;
+ protected final ArrayList /*IMessage*/ expectedMessages;
+ protected final ArrayList /*String*/ options;
+ protected final ArrayList /*String*/ paths;
+ protected final ArrayList /*ISourceLocation*/ sourceLocations; // XXX remove?
+ protected final ArrayList /*IRunSpec*/ children;
+ protected final ArrayList /*DirChanges.Spec*/ dirChanges;
+ protected XMLNames xmlNames;
+ protected String comment;
+
+ /** These options are 1:1 with spec, but set at runtime (not saved) */
+ public final RT runtime;
+
+ /** if true, then any child skip causes this to skip */
+ protected final boolean skipIfAnyChildSkipped;
+
+ public AbstractRunSpec(String xmlElementName) {
+ this(xmlElementName, true);
+ }
+
+ public AbstractRunSpec(String xmlElementName, boolean skipIfAnyChildSkipped) {
+ if (null == xmlElementName) {
+ xmlElementName = "spec";
+ }
+ this.xmlElementName = xmlElementName;
+ expectedMessages = new ArrayList();
+ options = new ArrayList();
+ paths = new ArrayList();
+ sourceLocations = new ArrayList();
+ keywords = new ArrayList();
+ children = new ArrayList();
+ dirChanges = new ArrayList();
+ xmlNames = XMLNames.DEFAULT;
+ runtime = new RT();
+ this.skipIfAnyChildSkipped = skipIfAnyChildSkipped;
+ }
+
+
+ /** @param comment ignored if null */
+ public void setComment(String comment) {
+ if (!LangUtil.isEmpty(comment)) {
+ this.comment = comment;
+ }
+ }
+
+ public void setStaging(boolean staging) {
+ isStaging = staging;
+ }
+
+ boolean isStaging() {
+ return isStaging;
+ }
+
+ // ------- description (title, label...)
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ // ------- source location of the spec
+
+ public void setSourceLocation(ISourceLocation sourceLocation) {
+ this.sourceLocation = sourceLocation;
+ }
+
+ public ISourceLocation getSourceLocation() {
+ return sourceLocation;
+ }
+
+ // ------- keywords
+ /** @param keyword added after trimming if not empty */
+ public void setKeyword(String keyword) {
+ addKeyword(keyword);
+ }
+
+ /** Add keyword if non-empty and not duplicate */
+ public void addKeyword(String keyword) {
+ if (!LangUtil.isEmptyTrimmed(keyword)) {
+ keyword = keyword.trim();
+ if (!keywords.contains(keyword)) {
+ keywords.add(keyword);
+ }
+ }
+ }
+ public void setKeywords(String items) {
+ addKeywords(items);
+ }
+
+ public void addKeywords(String items) {
+ if (null != items) {
+ addKeywords(XMLWriter.unflattenList(items));
+ }
+ }
+ public void addKeywords(String[] ra) {
+ if (null != ra) {
+ for (int i = 0; i < ra.length; i++) {
+ addKeyword(ra[i]);
+ }
+ }
+ }
+ public ArrayList getKeywordsList() {
+ return makeList(keywords);
+ }
+
+ // ------- options - String args
+
+ /** @return ArrayList of String options */
+ public ArrayList getOptionsList() {
+ return makeList(options);
+ }
+
+ /** @return String[] of options */
+ public String[] getOptionsArray() {
+ return (String[]) options.toArray(new String[0]);
+ }
+
+ public void setOption(String option) {
+ addOption(option);
+ }
+
+ public void addOption(String option) {
+ if ((null != option) && (0 < option.length())) {
+ options.add(option);
+ }
+ }
+
+ /** add options (from XML/bean) - removes any existing options */
+ public void setOptions(String items) {
+ this.options.clear();
+ addOptions(items);
+ }
+
+ /**
+ * Set options, removing any existing options.
+ * @param options String[] options to use - may be null or empty
+ */
+ public void setOptionsArray(String[] options) {
+ this.options.clear();
+ if (!LangUtil.isEmpty(options)) {
+ this.options.addAll(Arrays.asList(options));
+ }
+ }
+
+ public void addOptions(String items) {
+ if (null != items) {
+ addOptions(XMLWriter.unflattenList(items));
+ }
+ }
+
+ public void addOptions(String[] ra) {
+ if (null != ra) {
+ for (int i = 0; i < ra.length; i++) {
+ addOption(ra[i]);
+ }
+ }
+ }
+
+ // -------------- source locations
+// /** @return ArrayList of ISourceLocation sourceLocs */
+// public ArrayList getSourceLocationsList() {
+// return makeList(sourceLocations);
+// }
+//
+// /** @return ISourceLocation[] sourceLocs */
+// public ISourceLocation[] getSourceLocationsArray() {
+// return (ISourceLocation[]) sourceLocations.toArray(new ISourceLocation[0]);
+// }
+//
+//
+// public void setSourceLocation(String input) {
+// if (null != input) {
+// ISourceLocation sl = BridgeUtil.makeSourceLocation(input);
+// if (null != sl) {
+// addSourceLocation(sl);
+// }
+// // XXX need error-handling for bad input
+// }
+// }
+//
+// public void addSourceLocation(ISourceLocation sourceLoc) {
+// if (null != sourceLoc) {
+// sourceLocations.add(sourceLoc);
+// }
+// }
+
+ // --------------- (String) paths
+ /** @return ArrayList of String paths */
+ public ArrayList getPathsList() {
+ return makeList(paths);
+ }
+
+ /** @return String[] of paths */
+ public String[] getPathsArray() {
+ return (String[]) paths.toArray(new String[0]);
+ }
+
+// /** @return String[] of paths, removing any matching stripPrefix prefix */
+// public String[] getPathsArray(String stripPrefix) {
+// String[] result = getPathsArray();
+// if (!LangUtil.isEmpty(stripPrefix)) {
+// final int LEN = stripPrefix.length();
+// for (int i = 0; i < result.length; i++) {
+// if ((null != result[i]) && result[i].startsWith(stripPrefix)) {
+// result[i] = result[i].substring(LEN);
+// }
+// }
+// }
+// return result;
+// }
+//
+// /** @return ArrayList of File baseDir/{path} */
+// public ArrayList getPathsAsFile(File baseDir) {
+// if (null == baseDir) {
+// baseDir = new File(".");
+// }
+// ArrayList result = makeList(null);
+// for (Iterator iter = paths.iterator(); iter.hasNext();) {
+// String path = (String) iter.next();
+// result.add(new File(baseDir, path));
+// }
+// return result;
+// }
+
+ public void setPath(String path) {
+ addPath(path);
+ }
+
+ public void setPaths(String paths) {
+ addPaths(paths);
+ }
+
+ public void addPath(String path) {
+ if (null != path) {
+ paths.add(path);
+ }
+ }
+
+ public void addPaths(String items) {
+ if (null != items) {
+ addPaths(XMLWriter.unflattenList(items));
+ }
+ }
+
+ public void addPaths(String[] ra) {
+ if (null != ra) {
+ for (int i = 0; i < ra.length; i++) {
+ addPath(ra[i]);
+ }
+ }
+ }
+
+ public void addWrapFile(WrapFile file) {
+ if (null != file) {
+ paths.add(file.path);
+ }
+ }
+
+ // --------------------- dir changes
+ public void addDirChanges(DirChanges.Spec dirChangesSpec) {
+ if (null != dirChangesSpec) {
+ dirChanges.add(dirChangesSpec);
+ }
+ }
+
+ // --------------------- messages
+ public void setMessage(String message) {
+ addMessage(message);
+ }
+
+ public void addMessage(IMessage message) {
+ if (null != message) {
+ expectedMessages.add(message);
+ }
+ }
+
+ public void addMessage(String message) {
+ if (null != message) {
+ IMessage m = BridgeUtil.readMessage(message);
+ expectedMessages.add(m);
+ }
+ }
+
+ /** this can ONLY work if each item has no internal comma
+ */
+ public void addMessages(String items) {
+ if (null != items) {
+ String[] ra = XMLWriter.unflattenList(items);
+ for (int i = 0; i < ra.length; i++) {
+ addMessage(ra[i]);
+ }
+ }
+ }
+ public void addMessages(List messages) {
+ if (null != messages) {
+ for (Iterator iter = messages.iterator(); iter.hasNext();) {
+ Object o = iter.next();
+ if (o instanceof IMessage) {
+ addMessage((IMessage) o);
+ } else {
+ // XXX warning?
+ }
+ }
+ }
+ }
+
+ public ArrayList getMessages(IMessage.Kind kind) {
+ return makeList(MessageUtil.getMessages(expectedMessages, kind));
+ }
+
+
+ /** @return int number of message of this kind (optionally or greater */
+ public int numMessages(IMessage.Kind kind, boolean orGreater) {
+ return MessageUtil.numMessages(expectedMessages, kind, orGreater);
+ }
+
+ public ArrayList getMessages() {
+ return getMessages(null); // XXX special meaning of null
+ }
+
+
+ public void addChild(IRunSpec child) {
+ // fyi, child is added when complete (depth-first), not when initialized,
+ // so cannot affect initialization of child here
+ if (null != child) {
+ children.add(child);
+ }
+ }
+
+ /** @return copy of children list */
+ public ArrayList getChildren() {
+ return makeList(children);
+ }
+
+ /** @return copy of children list without children to skip */
+ public ArrayList getWorkingChildren() {
+ if (skipAll) {
+ return new ArrayList();
+ }
+ if (null == skipSet) {
+ return getChildren();
+ }
+ ArrayList result = new ArrayList();
+ int i = 0;
+ for (Iterator iter = children.listIterator();
+ iter.hasNext(); i++) {
+ Object child = iter.next();
+ if (!skipSet.get(i)) {
+ result.add(child);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Recursively absorb parent values if different.
+ * This implementation calls doAdoptParentValues(..)
+ * and then calls this for any children.
+ * This is when skipped children are determined.
+ * Children may elect to balk at this point, reducing the
+ * number of children or causing this spec to skip if
+ * skipIfAnyChildrenSkipped. For each test skipped, either
+ * this doAdoptParentValues(..) or the child's adoptParentValues(..)
+ * should add one info message with the reason this is being skipped.
+ * The only reason to override this would be to NOT
+ * invoke the same for children, or to do something similar
+ * for children which are not AbstractRunSpec.
+ * @param parentRuntime the RT values to adopt - ignored if null
+ * @param handler the IMessageHandler for info messages when skipping
+ * @return false if this wants to be skipped, true otherwise
+ */
+ public boolean adoptParentValues(RT parentRuntime, IMessageHandler handler) {
+ boolean skipped = false;
+ skipAll = false;
+ skipSet = new BitSet();
+ if (null != parentRuntime) {
+ skipped = !doAdoptParentValues(parentRuntime, handler);
+ if (skipped && skipIfAnyChildSkipped) { // no need to continue checking
+ skipAll = true;
+ return false;
+ }
+ int i = 0;
+ for (ListIterator iter = children.listIterator(); iter.hasNext(); i++) {
+ IRunSpec child = (IRunSpec) iter.next();
+ if (child instanceof AbstractRunSpec) { // XXX ugly instanceof
+ AbstractRunSpec arsChild = (AbstractRunSpec) child;
+ if (!arsChild.adoptParentValues(runtime, handler)) {
+ skipSet.set(i);
+ //iter.remove();
+ if (!skipped) {
+ skipped = true;
+ if (skipIfAnyChildSkipped) { // no need to continue checking
+// String m = "skipping " + toString() + " because child "
+// + arsChild + " skipped";
+// MessageUtil.info(handler, m);
+ skipAll = true;
+ //children.clear();
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Adopt parent values.
+ * This implementation makes a local copy.
+ * If we interpret (and absorb) any options, they should be removed
+ * from parentRuntime.
+ * This sets verbose if different (override)
+ * and directly adopts parentOptions if ours is null
+ * and otherwise adds any non-null options we don't already have.
+ * setting verbose and adding to parent options.
+ * Implementors override this to affect how parent values are adopted.
+ * Implementors should not recurse into children.
+ * This method may be called multiple times, so implementors
+ * should not destroy any spec information.
+ * Always add an info message when returning false to skip
+ * @param parentRuntime the RT values to adopt - never null
+ * @return false if this wants to be skipped, true otherwise
+ */
+ protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) {
+ if (runtime.verbose != parentRuntime.verbose) {
+ runtime.verbose = parentRuntime.verbose;
+ }
+ if (!LangUtil.isEmpty(runtime.parentOptions)) {
+ runtime.parentOptions.clear();
+ }
+ if (!LangUtil.isEmpty(parentRuntime.parentOptions)) {
+ runtime.parentOptions.addAll(parentRuntime.parentOptions);
+ }
+ return true;
+ }
+
+ /**
+ * Implementations call this when signalling skips to ensure consistency
+ * in message formatting
+ * @param handler the IMessageHandler sink - not null
+ * @param reason the String reason to skip - not null
+ */
+ protected void skipMessage(IMessageHandler handler, String reason) {
+ LangUtil.throwIaxIfNull(handler, "handler");
+ LangUtil.throwIaxIfNull(handler, "reason");
+ // XXX for Runs, label does not identify the test
+ String label = toString();
+ MessageUtil.info(handler, "skipping \"" + label + "\" because " + reason);
+ }
+
+ // --------------------------- writing xml - would prefer castor..
+
+ /**
+ * Control XML output by renaming or suppressing output for
+ * attributes and subelements.
+ * Subelements are skipped by setting the XMLNames booleans to false.
+ * Attributes are skipped by setting their name to null.
+ * @param names XMLNames with new names and/or suppress flags.
+ */
+ protected void setXMLNames(XMLNames names) {
+ if (null != names) {
+ xmlNames = names;
+ }
+ }
+
+ /** @return null if value is null or name="{value}" otherwise */
+ private String makeAttr(XMLWriter out, String name, String value) {
+ if (null == value) {
+ return null;
+ }
+ return out.makeAttribute(name, value);
+ }
+
+ /** @return null if list is null or empty or name="{flattenedList}" otherwise */
+ private String makeAttr(XMLWriter out, String name, List list) {
+ if (LangUtil.isEmpty(list)) {
+ return null;
+ }
+ String flat = out.flattenList(list);
+ return out.makeAttribute(name, flat);
+ }
+
+ /** @return true if writeAttributes(..) will produce any output */
+ protected boolean haveAttributes() {
+ return ((!LangUtil.isEmpty(xmlNames.descriptionName)
+ && !LangUtil.isEmpty(description))
+ || (!LangUtil.isEmpty(xmlNames.keywordsName)
+ && !LangUtil.isEmpty(keywords))
+ || (!LangUtil.isEmpty(xmlNames.optionsName)
+ && !LangUtil.isEmpty(options))
+ || (!LangUtil.isEmpty(xmlNames.pathsName)
+ && !LangUtil.isEmpty(paths)));
+ }
+
+ /**
+ * Write attributes without opening or closing elements/attributes.
+ * An attribute is written only if the value is not empty
+ * and the name in xmlNames is not empty
+ */
+ protected void writeAttributes(XMLWriter out) {
+ if (!LangUtil.isEmpty(xmlNames.descriptionName)
+ && !LangUtil.isEmpty(description)) {
+ out.printAttribute(xmlNames.descriptionName, description);
+ }
+ if (!LangUtil.isEmpty(xmlNames.keywordsName)
+ && !LangUtil.isEmpty(keywords)) {
+ out.printAttribute(xmlNames.keywordsName, out.flattenList(keywords));
+ }
+ if (!LangUtil.isEmpty(xmlNames.optionsName)
+ && !LangUtil.isEmpty(options)) {
+ out.printAttribute(xmlNames.optionsName, out.flattenList(options));
+ }
+ if (!LangUtil.isEmpty(xmlNames.pathsName)
+ && !LangUtil.isEmpty(paths)) {
+ out.printAttribute(xmlNames.pathsName, out.flattenList(paths));
+ }
+ if (!LangUtil.isEmpty(xmlNames.commentName)
+ && !LangUtil.isEmpty(comment)) {
+ out.printAttribute(xmlNames.commentName, comment);
+ }
+ }
+
+ /**
+ * The default implementation writes everything as attributes,
+ * then subelements for dirChanges, messages, then subelements for children.
+ * Subclasses that override may delegate back for any of these.
+ * Subclasses may also set XMLNames to name or suppress any attribute
+ * or subelement.
+ * @see writeMessages(XMLWriter)
+ * @see writeChildren(XMLWriter)
+ * @see IXmlWritable#writeXml(XMLWriter)
+ */
+ public void writeXml(XMLWriter out) {
+ out.startElement(xmlElementName,false);
+ writeAttributes(out);
+ out.endAttributes();
+ if (!xmlNames.skipMessages) {
+ writeMessages(out);
+ }
+ if (!xmlNames.skipChildren) {
+ writeChildren(out);
+ }
+ out.endElement(xmlElementName);
+ }
+
+ /**
+ * Write messages. Assumes attributes are closed,
+ * can write child elements of current element.
+ */
+ protected void writeMessages(XMLWriter out) {
+ if (0 < expectedMessages.size()) {
+ SoftMessage.writeXml(out, expectedMessages);
+ }
+
+ }
+
+ /**
+ * Write children. Assumes attributes are closed,
+ * can write child elements of current element.
+ */
+ protected void writeChildren(XMLWriter out) {
+ if (0 < children.size()) {
+ for (Iterator iter = children.iterator(); iter.hasNext();) {
+ IXmlWritable self = (IXmlWritable) iter.next();
+ self.writeXml(out);
+ }
+ }
+ }
+
+ // --------------------------- logging
+
+ public void printAll(PrintStream out, String prefix) {
+ out.println(prefix + toString());
+ for (Iterator iter = children.iterator(); iter.hasNext();) {
+ AbstractRunSpec child = (AbstractRunSpec) iter.next(); // IRunSpec
+ child.printAll(out, prefix + " ");
+ }
+ }
+
+ /**
+ * default implementation returns the description if not empty
+ * or the unqualified class name otherwise.
+ * Subclasses should not call toString from here unless they reimplement it.
+ * @return name of this thing or type
+ */
+ protected String getPrintName() {
+ if (!LangUtil.isEmpty(description)) {
+ return description;
+ } else {
+ return LangUtil.unqualifiedClassName(this);
+ }
+ }
+
+ /** @return summary count of spec elements */
+ public String toString() {
+ return getPrintName() + "(" + containedSummary() + ")";
+ }
+
+ /** @return String of the form (# [options|paths|locations|messages]).. */
+ protected String containedSummary() {
+ int nOptions = options.size();
+ int nPaths = paths.size();
+ int nLoc = sourceLocations.size();
+ int nMssg = expectedMessages.size();
+ return
+ ( (nOptions == 0 ? "" : nOptions + " options " )
+ + (nPaths == 0 ? "" : nPaths + " paths " )
+ + (nLoc == 0 ? "" : nLoc + " locations " )
+ + (nMssg == 0 ? "" : nMssg + " messages" )).trim();
+ }
+
+ public String toLongString() {
+ String mssg = "";
+ if (expectedMessages.size() > 0) {
+ // XXX add MessageHandler.renderCounts(IMessage[] messages)
+ MessageHandler h = new MessageHandler();
+ for (Iterator iter = expectedMessages.iterator(); iter.hasNext();) {
+ h.handleMessage((IMessage) iter.next());
+ }
+ mssg = " expected messages (" + MessageUtil.renderCounts(h) + ")";
+ }
+ return getPrintName() + containedSummary() + mssg.trim();
+ }
+
+ private ArrayList makeList(List list) {
+ ArrayList result = new ArrayList();
+ if (null != list) {
+ result.addAll(list);
+ }
+ return result;
+ }
+
+ /**
+ * Subclasses use this to rename attributes or omit attributes or subelements.
+ * To suppress output of an attribute, * pass "" as the name of the attribute.
+ * To use default entries, pass null for that entry.
+ */
+ public static class XMLNames {
+ public static final XMLNames DEFAULT =
+ new XMLNames(null, "description", "sourceLocation",
+ "keywords", "options", "paths", "comment", false, false, false);
+ final String descriptionName;
+ final String sourceLocationName;
+ final String keywordsName;
+ final String optionsName;
+ final String pathsName;
+ final String commentName;
+ final boolean skipDirChanges;
+ final boolean skipMessages;
+ final boolean skipChildren;
+ /** reset all names/behavior or pass defaultNames
+ * as the defaults for any null elements
+ */
+ XMLNames(XMLNames defaultNames,
+ String descriptionName,
+ String sourceLocationName,
+ String keywordsName,
+ String optionsName,
+ String pathsName,
+ String commentName,
+ boolean skipDirChanges,
+ boolean skipMessages,
+ boolean skipChildren) {
+ this.skipDirChanges = skipDirChanges;
+ this.skipMessages = skipMessages;
+ this.skipChildren = skipChildren;
+ if (null != defaultNames) {
+ this.descriptionName = (null != descriptionName? descriptionName : defaultNames.descriptionName);
+ this.sourceLocationName = (null != sourceLocationName ? sourceLocationName : defaultNames.sourceLocationName);
+ this.keywordsName = (null != keywordsName ? keywordsName : defaultNames.keywordsName);
+ this.optionsName = (null != optionsName ? optionsName : defaultNames.optionsName);
+ this.pathsName = (null != pathsName ? pathsName : defaultNames.pathsName);
+ this.commentName = (null != commentName ? commentName : defaultNames.commentName);
+ } else {
+ this.descriptionName = descriptionName;
+ this.sourceLocationName = sourceLocationName;
+ this.keywordsName = keywordsName;
+ this.optionsName = optionsName;
+ this.pathsName = pathsName;
+ this.commentName = commentName;
+ }
+ }
+ }
+
+
+ /** subclasses implement this to create and set up a run */
+ abstract public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator);
+
+ /** This is for separate file (sub-) elements with path attributes */
+ public static class WrapFile {
+ public String path;
+ public void setPath(String path) {
+ this.path = path;
+ }
+ }
+
+ /** segregate runtime-only state in spec */
+ public static class RT {
+ /** true if we should emit verbose messages */
+ private boolean verbose;
+
+ /** null unless parent set options for children to consider */
+ final private ArrayList /*String*/ parentOptions;
+
+ public RT() {
+ parentOptions = new ArrayList();
+ }
+
+ public boolean isVerbose() {
+ return verbose;
+ }
+
+ /**
+ * Set parent options - old options destroyed.
+ * Will result in duplicates if duplicates added.
+ * Null or empty entries are ignored
+ * @param options ignored if null or empty
+ */
+ public void setOptions(String[] options) {
+ parentOptions.clear();
+ if (!LangUtil.isEmpty(options)) {
+ for (int i = 0; i < options.length; i++) {
+ if (!LangUtil.isEmpty(options[i])) {
+ parentOptions.add(options[i]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Copy values from another RT
+ * @param toCopy the RT to copy from
+ * @throws IllegalArgumentException if toCopy is null
+ */
+ public void copy(RT toCopy) {
+ LangUtil.throwIaxIfNull(toCopy, "parent");
+ parentOptions.clear();
+ parentOptions.addAll(toCopy.parentOptions);
+ verbose = toCopy.verbose;
+ }
+
+ /**
+ * Return any parent option which has one of validOptions as a prefix,
+ * optionally absorbing (removing) the parent option.
+ * @param validOptions String[] of options to extract
+ * @param absorb if true, then remove any parent option matched
+ * @return String[] containing any validOptions[i] in parentOptions
+ * (at most once)
+ */
+ public String[] extractOptions(String[] validOptions, boolean absorb) {
+ if (LangUtil.isEmpty(validOptions) || LangUtil.isEmpty(parentOptions)) {
+ return new String[0];
+ }
+ ArrayList result = new ArrayList();
+ boolean haveOption = false;
+ for (int i = 0; i < validOptions.length; i++) {
+ String option = validOptions[i];
+ if (LangUtil.isEmpty(option)) {
+ continue;
+ }
+ for (ListIterator iter = parentOptions.listIterator(); iter.hasNext();) {
+ String parentOption = (String) iter.next();
+ if (parentOption.startsWith(option)) {
+ result.add(parentOption);
+ if (absorb) {
+ iter.remove();
+ }
+ }
+ }
+ }
+ return (String[]) result.toArray(new String[0]);
+ }
+
+ /** Get ListIterator that permits removals */
+ ListIterator getListIterator() {
+ return parentOptions.listIterator();
+ }
+
+ /**
+ * Enable verbose logging
+ * @param verbose if true, do verbose logging
+ */
+ public void setVerbose(boolean verbose) {
+ if (this.verbose != verbose) {
+ this.verbose = verbose;
+ }
+ }
+ } // class RT
+}
diff --git a/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java b/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java
new file mode 100644
index 000000000..943110df3
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java
@@ -0,0 +1,282 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.IMessageHolder;
+import org.aspectj.bridge.MessageHandler;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.testing.util.BridgeUtil;
+import org.aspectj.testing.util.Diffs;
+import org.aspectj.util.LangUtil;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Adapter to pass messages through to handler,
+ * suppress output of expected errors and warnings,
+ * and calculate differences between expected and
+ * actual messages.
+ */
+public class AjcMessageHandler extends MessageHandler {
+
+ /** Comparator for enclosed IMessage diffs, etc. */
+ public static final Comparator COMP_IMessage
+ = BridgeUtil.Comparators.WEAK_IMessage;
+
+ /** Comparator for enclosed File diffs, etc. */
+ public static final Comparator COMP_File
+ = BridgeUtil.Comparators.WEAK_File;
+
+
+ /** unmodifiable list of IMessage messages of type IMesssage.ERROR */
+ final List expectedErrors; // revert to IMessageHolder for generality?
+
+ /** unmodifiable list of IMessage messages of type IMesssage.WARNING */
+ final List expectedWarnings;
+
+ /** unmodifiable list of File expected to be recompiled */
+ final List expectedRecompiled;
+
+ /** list of File actually recompiled */
+ List actualRecompiled;
+
+ /** if true, ignore warnings when calculating diffs and passed() */
+ boolean ignoreWarnings;
+
+ /** cache expected/actual diffs, nullify if any new message */
+ transient CompilerDiffs diffs;
+
+ private boolean expectingCommandTrue;
+
+ /**
+ * @param messages List of IMessage to extract ERROR and WARNING from
+ */
+ AjcMessageHandler(List messages) {
+ this(MessageUtil.getMessages(messages, IMessage.ERROR),
+ MessageUtil.getMessages(messages, IMessage.WARNING));
+ }
+
+ /**
+ * @param errors unmodifiable List of IMessage of kind ERROR to adopt
+ * @param warnings unmodifiable List of IMessage of kind WARNING to adopt
+ */
+ AjcMessageHandler(List errors, List warnings) {
+ this(errors, warnings, null);
+ }
+
+ AjcMessageHandler(List errors, List warnings, List recompiled) {
+ this.expectedErrors = LangUtil.safeList(errors);
+ this.expectedWarnings = LangUtil.safeList(warnings);
+ this.expectedRecompiled = LangUtil.safeList(recompiled);
+ expectingCommandTrue = (0 == expectedErrors.size());
+ }
+
+ public void setIgnoreWarnings(boolean ignoreWarnings) {
+ this.ignoreWarnings = ignoreWarnings;
+ }
+
+ /** clear out any actual values to be re-run */
+ public void init() {
+ super.init();
+ actualRecompiled = null;
+ diffs = null;
+ }
+
+ /**
+ * Return true if we have this kind of
+ * message for the same line and store all messages.
+ * @see bridge.tools.impl.ErrorHandlerAdapter#doShowMessage(IMessage)
+ * @return true if message handled (processing should abort)
+ */
+ public boolean handleMessage(IMessage message) {
+ if (null == message) {
+ throw new IllegalArgumentException("null message");
+ }
+ messages.add(message);
+ IMessage.Kind kind = message.getKind();
+ return expecting(message, getExpected(kind));
+ }
+
+ /**
+ * Set the actual files recompiled.
+ * @param List of File recompiled - may be null; adopted but not modified
+ * @throws IllegalStateException if they have been set already.
+ */
+ public void setRecompiled(List list) {
+ if (null != actualRecompiled) {
+ throw new IllegalStateException("actual recompiled already set");
+ }
+ this.actualRecompiled = LangUtil.safeList(list);
+ }
+
+ /** @return immutable List of IMessage expected of this kind */
+ public List getExpected(IMessage.Kind kind) {
+ if (IMessage.ERROR == kind) {
+ return expectedErrors;
+ } else if (IMessage.WARNING == kind) {
+ return expectedWarnings;
+ } else {
+ return Collections.EMPTY_LIST;
+ }
+ }
+
+ /**
+ * Check if the message was expected, and clear diffs if not.
+ * @return true if we expect a message of this kind with this line number
+ */
+ protected boolean expecting(IMessage message, List sink) { // XXX ignores File
+ if ((null != message) && (0 < sink.size())) {
+ // XXX need to cache list: int[] errLines, warningLines;
+ for (Iterator iter = sink.iterator(); iter.hasNext();) {
+ IMessage m = (IMessage) iter.next();
+ if (0 == COMP_IMessage.compare(message, m)) {
+ return true;
+ }
+ }
+ }
+ if (null != diffs) {
+ diffs = null;
+ }
+ return false;
+ }
+
+ /** Generate differences between expected and actual errors and warnings */
+ public CompilerDiffs getCompilerDiffs() {
+ if (null == diffs) {
+ final List actualErrors = Arrays.asList(getMessages(IMessage.ERROR, IMessageHolder.EQUAL));
+ final List actualWarnings = Arrays.asList(getMessages(IMessage.WARNING, IMessageHolder.EQUAL));
+ Diffs errors = new Diffs("error", expectedErrors, actualErrors, COMP_IMessage);
+ Diffs warnings = new Diffs("warning", expectedWarnings, actualWarnings, COMP_IMessage);
+ Diffs recompiled = new Diffs("recompiled", expectedRecompiled, actualRecompiled, COMP_File);
+ diffs = new CompilerDiffs(errors, warnings, recompiled);
+ }
+ return diffs;
+ }
+
+ /** calculate passed based on default or set value for ignoreWarnings */
+ public boolean passed() {
+ return passed(ignoreWarnings);
+ }
+
+ /** @return true if we are expecting the command to fail - i.e., any expected errors */
+ public boolean expectingCommandTrue() {
+ return expectingCommandTrue;
+ }
+
+ /**
+ * Get the (current) result of this run,
+ * ignoring differences in warnings on request.
+ * Note it may return passed (true) when there are expected error messages.
+ * @return false
+ * if there are any fail or abort messages,
+ * or if the expected errors, warnings, or recompiled do not match actual.
+ *
+ */
+ public boolean passed(boolean ignoreWarnings) {
+ if (hasAnyMessage(IMessage.FAIL, IMessageHolder.EQUAL)) {
+ return false;
+ }
+
+ CompilerDiffs diffs = getCompilerDiffs();
+ if (!ignoreWarnings) {
+ return (!diffs.different);
+ } else {
+ return ((!diffs.errors.different) && (!diffs.recompiled.different));
+ }
+ }
+
+ /**
+ * Report results to a handler,
+ * adding all messages
+ * and creating fail messages for each diff.
+ */
+ public void report(IMessageHandler handler) {
+ if (null == handler) {
+ MessageUtil.debug(this, "report got null handler");
+ }
+ IMessage[] messages = getMessages(null, IMessageHolder.EQUAL);
+ for (int i = 0; i < messages.length; i++) {
+ handler.handleMessage(messages[i]);
+ }
+ CompilerDiffs diffs = getCompilerDiffs();
+ if (diffs.different) {
+ diffs.errors.report(handler, IMessage.FAIL);
+ diffs.warnings.report(handler, IMessage.FAIL);
+ diffs.recompiled.report(handler, IMessage.FAIL);
+ }
+ }
+
+ /** @return String consisting of differences and any other messages */
+ public String toString() {
+ CompilerDiffs diffs = getCompilerDiffs();
+ StringBuffer sb = new StringBuffer(super.toString());
+ final String EOL = "\n";
+ sb.append(EOL);
+ render(sb, " unexpected error ", EOL, diffs.errors.unexpected);
+ render(sb, "unexpected warning ", EOL, diffs.warnings.unexpected);
+ render(sb, " missing error ", EOL, diffs.errors.missing);
+ render(sb, " missing warning ", EOL, diffs.warnings.missing);
+ render(sb, " fail ", EOL, getList(IMessage.FAIL));
+ render(sb, " abort ", EOL, getList(IMessage.ABORT));
+ render(sb, " info ", EOL, getList(IMessage.INFO));
+ return sb.toString(); // XXX cache toString
+ }
+
+ /** @return immutable list of a given kind - use null for all kinds */
+ private List getList(IMessage.Kind kind) {
+ if ((null == kind) || (0 == numMessages(kind, IMessageHolder.EQUAL))) {
+ return Collections.EMPTY_LIST;
+ }
+ return Arrays.asList(getMessages(kind, IMessageHolder.EQUAL));
+ }
+
+ /** @return "" if no items or {prefix}{item}{suffix}... otherwise */
+ private void render( // LangUtil instead?
+ StringBuffer result,
+ String prefix,
+ String suffix,
+ List items) {
+ if ((null != items)) {
+ for (Iterator iter = items.iterator(); iter.hasNext();) {
+ result.append(prefix + iter.next() + suffix);
+ }
+ }
+ }
+
+ /** compiler results for errors, warnings, and recompiled files */
+ public static class CompilerDiffs {
+ public final Diffs errors;
+ public final Diffs warnings;
+ public final Diffs recompiled;
+ public final boolean different;
+
+ public CompilerDiffs(Diffs errors, Diffs warnings) {
+ this(errors, warnings, Diffs.NONE);
+ }
+ public CompilerDiffs(Diffs errors, Diffs warnings, Diffs recompiled) {
+ this.errors = errors;
+ this.warnings = warnings;
+ this.recompiled = recompiled;
+ different = (warnings.different || errors.different
+ || recompiled.different);
+ }
+ }
+}
diff --git a/testing/src/org/aspectj/testing/harness/bridge/AjcTest.java b/testing/src/org/aspectj/testing/harness/bridge/AjcTest.java
new file mode 100644
index 000000000..b1e9e9b1b
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/AjcTest.java
@@ -0,0 +1,371 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.ISourceLocation;
+import org.aspectj.testing.run.IRunIterator;
+import org.aspectj.testing.run.IRunStatus;
+import org.aspectj.testing.run.Runner;
+import org.aspectj.testing.xml.XMLWriter;
+import org.aspectj.util.LangUtil;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An AjcTest has child subruns (compile, [inc-compile|run]*).
+ */
+public class AjcTest extends RunSpecIterator {
+
+ /** Unwrap an AjcTest.Spec from an IRunStatus around an AjcTest */
+ public static Spec unwrapSpec(IRunStatus status) {
+ if (null != status) {
+ Object id = status.getIdentifier();
+ if (id instanceof Runner.IteratorWrapper) {
+ IRunIterator iter = ((Runner.IteratorWrapper) id).iterator;
+ if (iter instanceof AjcTest) {
+ return (Spec) ((AjcTest) iter).spec;
+ }
+ }
+ }
+ return null;
+ }
+
+ /** Unwrap initial CompilerRun.Spec from an AjcTest.Spec */
+ public static CompilerRun.Spec unwrapCompilerRunSpec(Spec spec) {
+ if (null != spec) {
+ List kids = spec.getChildren();
+ if (0 < kids.size()) {
+ Object o = kids.get(0);
+ if (o instanceof CompilerRun.Spec) {
+ return (CompilerRun.Spec) o;
+ }
+ }
+ }
+ return null;
+ }
+
+ /** The spec creates the sandbox, so we use it throughout */
+ public AjcTest(Spec spec, Sandbox sandbox, Validator validator) {
+ super(spec, sandbox, validator, true);
+ }
+
+ /**
+ * Clear the command from the sandbox, to avoid memory leaks.
+ * @see org.aspectj.testing.harness.bridge.RunSpecIterator#iterationCompleted()
+ */
+ public void iterationCompleted() {
+ super.iterationCompleted();
+ sandbox.clearCommand(this);
+ }
+
+
+ /**
+ * Specification for an ajc test.
+ * Keyword directives are global/parent options passed as
+ * <pre>-ajctest[Require|Skip]Keywords=keyword{,keyword}..</pre>.
+ */
+ public static class Spec extends AbstractRunSpec {
+ public static final String XMLNAME = "ajc-test";
+ /**
+ * do description as title, do sourceLocation,
+ * do keywords, do options, skip paths, do comment,
+ * skip dirChanges, do messages and do children
+ * (though we do children directly).
+ */
+ private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT,
+ "title", null, null, null, "", null, true, false, false);
+
+ private static final String OPTION_PREFIX = "-ajctest";
+ private static final String[] VALID_OPTIONS = new String[] { OPTION_PREFIX };
+
+ private static final String REQUIRE_KEYWORDS = "RequireKeywords=";
+ private static final String SKIP_KEYWORDS = "SkipKeywords=";
+ private static final String PICK_PR = "PR=";
+ private static final List VALID_SUFFIXES
+ = Collections.unmodifiableList(Arrays.asList(new String[]
+ { REQUIRE_KEYWORDS, SKIP_KEYWORDS, PICK_PR }));
+
+ /** base directory of the test suite - set before making run */
+ private File suiteDir;
+
+ /** path offset from suite directory to base of test directory */
+ String testDirOffset; // XXX revert to private after fixes
+
+ /** id of bug - if 0, then no bug associated with this test */
+ private int bugId;
+
+ public Spec() {
+ super(XMLNAME);
+ setXMLNames(NAMES);
+ }
+
+ public void setSuiteDir(File suiteDir) {
+ this.suiteDir = suiteDir;
+ }
+
+ public File getSuiteDir() {
+ return suiteDir;
+ }
+
+ /** @param bugId 100..9999 */
+ public void setBugId(int bugId) {
+ LangUtil.throwIaxIfFalse((bugId > 10) && (bugId < 10000), "bad bug id: " + bugId);
+ this.bugId = bugId;
+ }
+
+ public int getBugId() {
+ return bugId;
+ }
+
+ public void setTestDirOffset(String testDirOffset) {
+ if (!LangUtil.isEmpty(testDirOffset)) {
+ this.testDirOffset = testDirOffset;
+ }
+ }
+
+ public String getTestDirOffset() {
+ return (null == testDirOffset ? "" : testDirOffset);
+ }
+
+ /**
+ * @param sandbox ignored
+ * @see org.aspectj.testing.harness.bridge.AbstractRunSpec#makeAjcRun(Sandbox, Validator)
+ */
+ public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) {
+ LangUtil.throwIaxIfNull(validator, "validator");
+
+ // if no one set suiteDir, see if we have a source location
+ if (null == suiteDir) {
+ ISourceLocation loc = getSourceLocation();
+ if (!validator.nullcheck(loc, "suite file location")
+ || !validator.nullcheck(loc.getSourceFile(), "suite file")) {
+ return null;
+ }
+ File locDir = loc.getSourceFile().getParentFile();
+ if (!validator.canReadDir(locDir, "source location dir")) {
+ return null;
+ }
+ suiteDir = locDir;
+ }
+
+ // we make a new sandbox with more state for our subruns, keep that,
+ // in order to defer initialization to nextRun()
+ File testBaseDir;
+ String testDirOffset = getTestDirOffset();
+ if (LangUtil.isEmpty(testDirOffset)) {
+ testBaseDir = suiteDir;
+ } else {
+ testBaseDir = new File(suiteDir, testDirOffset);
+ if (!validator.canReadDir(testBaseDir, "testBaseDir")) {
+ return null;
+ }
+ }
+ Sandbox childSandbox = null;
+ try {
+ childSandbox = new Sandbox(testBaseDir, validator);
+ validator.registerSandbox(childSandbox);
+ } catch (IllegalArgumentException e) {
+ validator.fail(e.getMessage());
+ return null;
+ }
+ return new AjcTest(this, childSandbox, validator);
+ }
+
+ /** @see IXmlWritable#writeXml(XMLWriter) */
+ public void writeXml(XMLWriter out) {
+ out.println("");
+ String value = (null == testDirOffset? "" : testDirOffset);
+ String attr = out.makeAttribute("dir", value);
+ if (0 != bugId) {
+ attr += " " + out.makeAttribute("pr", ""+bugId);
+ }
+ out.startElement(xmlElementName, attr, false);
+ super.writeAttributes(out);
+ out.endAttributes();
+ super.writeChildren(out);
+ out.endElement(xmlElementName);
+ }
+
+ /**
+ * AjcTest overrides this to skip if
+ * <ul>
+ * <li>the spec has a keyword the parent wants to skip</li>
+ * <li>the spec does not have keyword the parent requires</li>
+ * <li>the spec does not have the bugId required</li>
+ * </ul>
+ * @return false if this wants to be skipped, true otherwise
+ * @throws Error if selected option is not of the form
+ * <pre>-ajctest[Require|Skip]Keywords=keyword{,keyword}..</pre>.
+ */
+ protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) {
+ if (!super.doAdoptParentValues(parentRuntime, handler)) {
+ return false;
+ }
+ runtime.copy(parentRuntime);
+
+ String[] globalOptions = runtime.extractOptions(VALID_OPTIONS, true);
+ for (int i = 0; i < globalOptions.length; i++) {
+ String option = globalOptions[i];
+ if (!option.startsWith(OPTION_PREFIX)) {
+ throw new Error("only expecting " + OPTION_PREFIX + "..: " + option);
+ }
+ option = option.substring(OPTION_PREFIX.length());
+ boolean keywordMustExist = false;
+ String havePr = null;
+ if (option.startsWith(REQUIRE_KEYWORDS)) {
+ option = option.substring(REQUIRE_KEYWORDS.length());
+ keywordMustExist = true;
+ } else if (option.startsWith(SKIP_KEYWORDS)) {
+ option = option.substring(SKIP_KEYWORDS.length());
+ } else if (option.startsWith(PICK_PR)) {
+ if (0 == bugId) {
+ skipMessage(handler, "bugId required, but no bugId for this test");
+ return false;
+ } else {
+ havePr = "" + bugId;
+ }
+ option = option.substring(PICK_PR.length());
+ } else {
+ throw new Error("unrecognized suffix: " + globalOptions[i]
+ + " (expecting: " + OPTION_PREFIX + VALID_SUFFIXES + "...)");
+ }
+ List specs = LangUtil.commaSplit(option);
+ // XXX also throw Error on empty specs...
+ for (Iterator iter = specs.iterator(); iter.hasNext();) {
+ String spec = (String) iter.next();
+ if (null != havePr) {
+ if (havePr.equals(spec)) { // String.equals()
+ return true;
+ }
+ } else if (keywordMustExist != keywords.contains(spec)) {
+ String reason = "keyword " + spec
+ + " was " + (keywordMustExist ? "not found" : "found");
+ skipMessage(handler, reason);
+ return false;
+ }
+ }
+ if (null != havePr) {
+ skipMessage(handler, "bugId required, but not matched for this test");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ } // AjcTest.Spec
+
+ /**
+ * A suite of AjcTest has children for each AjcTest
+ * and flows all options down as globals
+ */
+ public static class Suite extends RunSpecIterator {
+ final Spec spec;
+ public Suite(Spec spec, Sandbox sandbox, Validator validator) {
+ super(spec, sandbox, validator, false);
+ this.spec = spec;
+ }
+
+ /**
+ * While being called to make the sandbox for the child,
+ * set up the child's suite dir based on ours.
+ * @param child must be instanceof AjcTest.Spec
+ * @see org.aspectj.testing.harness.bridge.RunSpecIterator#makeSandbox(IRunSpec, Validator)
+ * @return super.makeSandbox(child, validator)
+ */
+ protected Sandbox makeSandbox(
+ IRunSpec child,
+ Validator validator) {
+ if (!(child instanceof AjcTest.Spec)) {
+ validator.fail("only expecting AjcTest children");
+ return null;
+ }
+ if (!validator.canReadDir(spec.suiteDir, "spec.suiteDir")) {
+ return null;
+ }
+ ((AjcTest.Spec) child).setSuiteDir(spec.suiteDir);
+ return super.makeSandbox(child, validator);
+ }
+
+ /**
+ * A suite spec contains AjcTest children.
+ * The suite dir or source location should be set
+ * if the tests do not each have a source location
+ * with a source file in the suite dir.
+ * XXX whether to write out suiteDir in XML?
+ */
+ public static class Spec extends AbstractRunSpec {
+ public static final String XMLNAME = "suite";
+ /**
+ * do description, do sourceLocation,
+ * do keywords, do options, skip paths, do comment
+ * skip dirChanges, skip messages and do children
+ * (though we do children directly).
+ */
+ private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT,
+ null, null, null, null, "", null, true, true, false);
+ File suiteDir;
+ public Spec() {
+ super(XMLNAME, false); // do not skip this even if children skip
+ }
+
+ /** @param suiteDirPath the String path to the base suite dir */
+ public void setSuiteDir(String suiteDirPath) {
+ if (!LangUtil.isEmpty(suiteDirPath)) {
+ this.suiteDir = new File(suiteDirPath);
+ }
+ }
+
+ /** @param suiteDirFile the File for the base suite dir */
+ public void setSuiteDirFile(File suiteDir) {
+ this.suiteDir = suiteDir;
+ }
+
+ /** @get suiteDir from any set or source location if set */
+ public File getSuiteDirFile() {
+ if (null == suiteDir) {
+ ISourceLocation loc = getSourceLocation();
+ if (null != loc) {
+ File sourceFile = loc.getSourceFile();
+ if (null != sourceFile) {
+ suiteDir = sourceFile.getParentFile();
+ }
+ }
+ }
+ return suiteDir;
+ }
+
+ /**
+ * @return
+ * @see org.aspectj.testing.harness.bridge.AbstractRunSpec#makeRunIterator(Sandbox, Validator)
+ */
+ public IRunIterator makeRunIterator(
+ Sandbox sandbox,
+ Validator validator) {
+ return new Suite(this, sandbox, validator);
+ }
+
+ public String toString() {
+ // removed nKids as misleading, since children.size() may change
+ //int nKids = children.size();
+ //return "Suite.Spec(" + suiteDir + ", " + nKids + " tests)";
+ return "Suite.Spec(" + suiteDir + ")";
+ }
+ }
+ }
+}
diff --git a/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java b/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java
new file mode 100644
index 000000000..ccb38b2f4
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java
@@ -0,0 +1,966 @@
+/* *******************************************************************
+ * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.bridge.ICommand;
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.bridge.ReflectionFactory;
+import org.aspectj.testing.run.IRunIterator;
+import org.aspectj.testing.run.IRunStatus;
+import org.aspectj.testing.run.WrappedRunIterator;
+import org.aspectj.testing.xml.SoftMessage;
+import org.aspectj.testing.xml.XMLWriter;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Run the compiler once in non-incremental mode.
+ */
+public class CompilerRun implements IAjcRun {
+ static final String[] RA_String = new String[0];
+
+ static final String[] JAR_SUFFIXES = new String[] { ".jar", ".zip" };
+
+ static final String[] SOURCE_SUFFIXES
+ = (String[]) FileUtil.SOURCE_SUFFIXES.toArray(new String[0]);
+
+ /** specifications, set on construction */
+ Spec spec;
+
+ //------------ calculated during setup
+ /** get shared stuff during setup */
+ Sandbox sandbox;
+
+ /**
+ * During run, these String are passed as the source and arg files to compile.
+ * The list is set up in setupAjcRun(..), when arg files are prefixed with "@".
+ */
+ final List /*String*/ arguments;
+
+ /**
+ * During run, these String are collapsed and passed as the injar option.
+ * The list is set up in setupAjcRun(..).
+ */
+ final List /*String*/ injars;
+
+ /**
+ * During run, these String are collapsed and passed as the aspectpath option.
+ * The list is set up in setupAjcRun(..).
+ */
+ final List /*String*/ aspectpath;
+
+ private CompilerRun(Spec spec) {
+ if (null == spec) {
+ throw new IllegalArgumentException("null spec");
+ }
+ this.spec = spec;
+ arguments = new ArrayList();
+ injars = new ArrayList();
+ aspectpath = new ArrayList();
+ }
+
+ /**
+ * This checks that the spec is reasonable and does setup:
+ * <li>calculate and set sandbox testBaseSrcDir as
+ * {Sandbox.testBaseDir}/{Spec.testSrcDirOffset}/<li>
+ * <li>the list of source File to compile as
+ * {Sandbox.testBaseSrcDir}/{Spec.getPaths..}</li>
+ * All sources must be readable at this time.
+ * @param classesDir the File
+ * @see org.aspectj.testing.harness.bridge.AjcTest.IAjcRun#setup(File, File)
+ * @throws AbortException containing IOException or IllegalArgumentException
+ * if the staging operations fail
+ */
+ public boolean setupAjcRun(Sandbox sandbox, Validator validator) {
+
+ if (!validator.nullcheck(spec.getOptionsArray(), "localOptions")
+ || !validator.nullcheck(sandbox, "sandbox")
+ || !validator.nullcheck(spec.compiler, "compilerName")
+ || !validator.canRead(Globals.F_aspectjrt_jar, "aspectjrt.jar")
+ || !validator.canRead(Globals.F_testingclient_jar, "testing-client.jar")
+ //|| !validator.canRead(Main.F_bridge_jar, "bridge.jar")
+ ) {
+ return false;
+ }
+
+ this.sandbox = sandbox;
+
+ File[] cp = new File[]
+ { sandbox.classesDir, Globals.F_aspectjrt_jar, Globals.F_testingclient_jar };
+ sandbox.setClasspath(cp, true, this);
+
+ String rdir = spec.testSrcDirOffset;
+ File testBaseSrcDir;
+ if ((null == rdir) || (0 == rdir.length())) {
+ testBaseSrcDir = sandbox.testBaseDir;
+ } else {
+ testBaseSrcDir = new File(sandbox.testBaseDir, rdir);
+ if (!validator.canReadDir(testBaseSrcDir, "sandbox.testBaseSrcDir")) {
+ return false;
+ }
+ }
+ sandbox.setTestBaseSrcDir(testBaseSrcDir, this);
+
+ arguments.clear();
+
+ // sources come as relative paths - check read, copy if staging
+
+ // this renders paths absolute before run(RunStatusI) is called
+ // for a compile run to support relative paths + source base
+ // change so the run calculates the paths (differently when staging)
+
+ final String[] injarPaths;
+ final String[] srcPaths;
+ {
+ final String[] paths = spec.getPathsArray();
+ srcPaths = LangUtil.endsWith(paths, CompilerRun.SOURCE_SUFFIXES, true);
+ injarPaths = LangUtil.endsWith(paths, CompilerRun.JAR_SUFFIXES, true);
+ }
+ // validate readable for sources
+ if (!validator.canRead(testBaseSrcDir, srcPaths, "sources")
+ || !validator.canRead(testBaseSrcDir, injarPaths, "injars")
+ || !validator.canRead(testBaseSrcDir, spec.argfiles, "argfiles")) {
+ return false;
+ }
+ int numSources = srcPaths.length + injarPaths.length + spec.argfiles.length;
+ if (numSources < 1) {
+ validator.fail("no source files, input jars, or arg files");
+ return false;
+ }
+
+ File[] srcFiles;
+ File[] argFiles;
+ File[] injarFiles;
+ File[] aspectFiles;
+ if (!spec.isStaging()) { // XXX why this? was always? || (testBaseSrcDir != sandbox.stagingDir))) {
+ srcFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, srcPaths, CompilerRun.SOURCE_SUFFIXES);
+ argFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, spec.argfiles);
+ injarFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, injarPaths);
+ aspectFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, spec.aspectpath);
+ } else { // staging - copy files
+ try {
+ srcFiles = FileUtil.copyFiles(testBaseSrcDir, srcPaths, sandbox.stagingDir);
+ argFiles = FileUtil.copyFiles(testBaseSrcDir, spec.argfiles, sandbox.stagingDir);
+ injarFiles = FileUtil.copyFiles(testBaseSrcDir, injarPaths, sandbox.stagingDir);
+ aspectFiles = FileUtil.copyFiles(testBaseSrcDir, spec.aspectpath, sandbox.stagingDir);
+ } catch (IllegalArgumentException e) {
+ validator.fail("staging - bad input", e);
+ return false;
+ } catch (IOException e) {
+ validator.fail("staging - operations", e);
+ return false;
+ }
+ // validate readable for copied sources
+ if (!validator.canRead(srcFiles, "copied paths")
+ || !validator.canRead(argFiles, "copied argfiles")) {
+ return false;
+ }
+ }
+ arguments.clear();
+ injars.clear();
+ aspectpath.clear();
+ if (!LangUtil.isEmpty(srcFiles)) {
+ arguments.addAll(Arrays.asList(FileUtil.getPaths(srcFiles)));
+ }
+ if (!LangUtil.isEmpty(injarFiles)) {
+ injars.addAll(Arrays.asList(FileUtil.getPaths(injarFiles)));
+ }
+ if (!LangUtil.isEmpty(aspectFiles)) {
+ aspectpath.addAll(Arrays.asList(FileUtil.getPaths(aspectFiles)));
+ }
+ if (!LangUtil.isEmpty(argFiles)) {
+ String[] ra = FileUtil.getPaths(argFiles);
+ for (int j = 0; j < ra.length; j++) {
+ arguments.add("@" + ra[j]);
+ }
+ if (spec.isStaging) {
+ validator.info("warning: files listed in argfiles not staged");
+ }
+ }
+
+ return true;
+ }
+// if ((null != spec.dirChanges) && (0 < spec.numMessages(IMessage.ERROR, false))) {
+// validator.info("CompilerRun warning: when expecting errors, cannot expect dir changes");
+// }
+
+
+ /**
+ * Setup result evaluation and command line, run, and evaluate result.
+ * <li>setup an AjcMessageHandler using the expected messages from
+ * {@link Spec#getMessages()}.<li>
+ * <li>heed any globals interpreted into a TestSetup by reading
+ * {@link Spec@getOptions()}. For a list of supported globals, see
+ * {@link setupArgs(ArrayList, IMessageHandler}.</li>
+ * <li>construct a command line, using as classpath
+ * {@link Sandbox.classpathToString()}<li>
+ * <li>construct a compiler using {@link Spec#compilerName}
+ * or any overriding value set in TestSetup.<li>
+ * <li>Just before running, set the compiler in the sandbox using
+ * {@link Sandbox.setCompiler(ICommand)}.<li>
+ * <li>After running, report AjcMessageHandler results to the status parameter.
+ * If the AjcMessageHandler reports a failure, then send info messages
+ * for the Spec, TestSetup, and command line.<li>
+ * XXX better to upgrade AjcMessageHandler to adopt status
+ * so the caller can control fast-fail, etc.
+ * @see org.aspectj.testing.run.IRun#run(IRunStatus)
+ */
+ public boolean run(IRunStatus status) {
+ if (null == spec.testSetup) {
+ MessageUtil.abort(status, "no test setup - adoptParentValues not called");
+ return false;
+ } else if (!spec.testSetup.result) {
+ MessageUtil.abort(status, spec.testSetup.failureReason);
+ return false;
+ }
+ AjcMessageHandler handler = new AjcMessageHandler(spec.getMessages());
+ handler.init();
+ boolean handlerResult = false;
+ boolean result = false;
+ boolean commandResult = false;
+ ArrayList argList = new ArrayList();
+ final Spec.TestSetup setupResult = spec.testSetup;
+ try {
+ argList.addAll(setupResult.commandOptions);
+ argList.add("-d");
+ String outputDirPath = sandbox.classesDir.getAbsolutePath();
+ try { // worth it to try for canonical?
+ outputDirPath = sandbox.classesDir.getCanonicalPath();
+ } catch (IOException e) {
+ MessageUtil.abort(status, "canonical " + sandbox.classesDir, e);
+ }
+ argList.add(outputDirPath);
+ argList.add("-classpath");
+ argList.add(sandbox.classpathToString(this));
+ // XXX classpath additions here?
+
+ if (0 < injars.size()) {
+ argList.add("-injars");
+ argList.add(FileUtil.flatten((String[]) injars.toArray(new String[0]), null));
+ }
+
+ if (0 < aspectpath.size()) {
+ argList.add("-aspectpath");
+ argList.add(FileUtil.flatten((String[]) aspectpath.toArray(new String[0]), null));
+ }
+
+ // add both java/aspectj and argfiles
+ argList.addAll(arguments);
+
+ // hack - do seeking on request as a side effect. reimplement as listener
+ if (null != setupResult.seek) {
+ String slopPrefix = Spec.SEEK_MESSAGE_PREFIX + " slop - ";
+ PrintStream slop = MessageUtil.handlerPrintStream(
+ status,
+ IMessage.INFO,
+ System.err,
+ slopPrefix);
+ List found = FileUtil.lineSeek(setupResult.seek, arguments, false, slop);
+ if (!LangUtil.isEmpty(found)) {
+ for (Iterator iter = found.iterator(); iter.hasNext();) {
+ MessageUtil.info(status, Spec.SEEK_MESSAGE_PREFIX + iter.next());
+ }
+ }
+ }
+
+ ICommand compiler = ReflectionFactory.makeCommand(setupResult.compilerName, status);
+ DirChanges dirChanges = null;
+ if (null == compiler) {
+ MessageUtil.fail(status, "unable to make compiler " + setupResult.compilerName);
+ return false;
+ } else {
+ if (status.aborted()) {
+ MessageUtil.debug(status, "aborted, but compiler valid?: " + compiler);
+ } else {
+ // same DirChanges handling for JavaRun, CompilerRun, IncCompilerRun
+ // XXX around advice or template method/class
+ if (!LangUtil.isEmpty(spec.dirChanges)) {
+ LangUtil.throwIaxIfFalse(1 == spec.dirChanges.size(), "expecting 0..1 dirChanges");
+ dirChanges = new DirChanges((DirChanges.Spec) spec.dirChanges.get(0));
+ if (!dirChanges.start(status, sandbox.classesDir)) {
+ return false; // setup failed
+ }
+ }
+ MessageUtil.info(status, compiler + "(" + argList + ")");
+ sandbox.setCommand(compiler, this);
+ String[] args = (String[]) argList.toArray(RA_String);
+ commandResult = compiler.runCommand(args, handler);
+ }
+ }
+ if (!setupResult.ignoreWarningsSet) {
+ handlerResult = handler.passed();
+ } else {
+ handlerResult = handler.passed(setupResult.ignoreWarnings);
+ }
+ if (!handlerResult) {
+ return false;
+ } else {
+ result = (commandResult == handler.expectingCommandTrue());
+ if (! result) {
+ String m = commandResult
+ ? "compile did not fail as expected"
+ : "compile failed unexpectedly";
+ MessageUtil.fail(status, m);
+ } else if (null != dirChanges) {
+ result = dirChanges.end(status, sandbox.testBaseDir);
+ }
+ }
+ return result;
+ } finally {
+ if (!handlerResult) { // more debugging context in case of failure
+ MessageUtil.info(handler, spec.toLongString());
+ MessageUtil.info(handler, "" + argList);
+ if (null != setupResult) {
+ MessageUtil.info(handler, "" + setupResult);
+ }
+ }
+ handler.report(status); // XXX weak - actual messages not reported in real-time, no fast-fail
+ }
+ }
+
+
+ public String toString() {
+ return "CompilerRun(" + spec + ")";
+ }
+// String[] sourcePaths = (null == this.sourcePaths ? new String[0] : this.sourcePaths);
+// List sources = (null == this.sources ? Collections.EMPTY_LIST : this.sources);
+// return "CompilerRun-" + compilerName
+// + "(" + Arrays.asList(sourcePaths)
+// + ", " + sources
+// + ", " + Arrays.asList(globalOptions)
+// + ", " + Arrays.asList(localOptions)
+// + ")";
+
+ /**
+ * Initializer/factory for CompilerRun
+ * any path or file is relative to this test base dir
+ */
+ public static class Spec extends AbstractRunSpec {
+ public static final String XMLNAME = "compile";
+ static final String SEEK_PREFIX = "-seek:";
+ static final String SEEK_MESSAGE_PREFIX = "found: ";
+
+ /** no support in the harness for these otherwise-valid options */
+ private static final String[] INVALID_OPTIONS = new String[]
+ { "-workingdir", "-argfile", "-sourceroot", "-outjar"};
+ // when updating these, update tests/harness/selectionTest.xml
+
+ /** no support in the eclipse-based compiler for these otherwise-valid options */
+ private static final String[] INVALID_ECLIPSE_OPTIONS = new String[]
+ { "-lenient", "-strict", "-usejavac", "-preprocess",
+ "-XOcodeSize", "-XSerializable", "-XaddSafePrefix",
+ "-XserializableAspects", "-XtargetNearSource" };
+
+ /** options supported by the harness */
+ private static final String[] VALID_OPTIONS = new String[]
+ {
+ SEEK_PREFIX,
+ // eajc does not support -usejavac, -preprocess
+ // testFlag() handles -ajc, -eclipse, -ignoreWarnings
+ "-usejavac", "-preprocess",
+ "-Xlint", "-lenient", "-strict",
+ "-source14", "-verbose", "-emacssym",
+ "-ajc", "-eclipse", "-ignoreWarnings",
+
+ "!usejavac", "!preprocess",
+ "!Xlint", "!lenient", "!strict",
+ "!source14", "!verbose", "!emacssym",
+ "!ajc", "!eclipse",
+
+ "^usejavac", "^preprocess",
+ "^Xlint", "^lenient", "^strict",
+ "^source14", "^verbose", "^emacssym",
+ "^ajc", "^eclipse"
+ };
+ public static final String DEFAULT_COMPILER
+ = ReflectionFactory.ECLIPSE;
+// = ReflectionFactory.OLD_AJC;
+ /**
+ * Retitle description to title, paths to files, do comment
+ * do dirChanges, and print no chidren.
+ */
+ private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT,
+ "title", null, null, null, "files", null, false, false, true);
+
+ protected String compiler;
+
+ protected TestSetup testSetup;
+
+ protected String[] argfiles = new String[0];
+ protected String[] aspectpath = new String[0];
+
+ /** src path = {suiteParentDir}/{testBaseDirOffset}/{testSrcDirOffset}/{path} */
+ protected String testSrcDirOffset;
+
+ public Spec() {
+ super(XMLNAME);
+ setXMLNames(NAMES);
+ compiler = DEFAULT_COMPILER;
+ }
+
+ public void setCompiler(String compilerName) {
+ this.compiler = compilerName;
+ }
+
+ public void setTestSrcDirOffset(String s) {
+ if (null != s) {
+ testSrcDirOffset = s;
+ }
+ }
+
+ /** override to set dirToken to Sandbox.CLASSES and default suffix to ".class" */
+ public void addDirChanges(DirChanges.Spec spec) {
+ if (null == spec) {
+ return;
+ }
+ spec.setDirToken(Sandbox.CLASSES_DIR);
+ spec.setDefaultSuffix(".class");
+ super.addDirChanges(spec);
+ }
+ protected String getPrintName() {
+ return "CompilerRun.Spec " + getShortCompilerName();
+ }
+
+ public String toLongString() {
+ return getPrintName() + "(" + super.containedSummary() + ")";
+ }
+
+ public String toString() {
+ return getPrintName() + "(" + super.containedSummary() + ")";
+ }
+
+ /** bean mapping for writers */
+ public void setFiles(String paths) {
+ addPaths(paths);
+ }
+
+ /**
+ * Set aspectpath, deleting any old ones
+ * @param files comma-delimited list of argfiles - ignored if null or empty
+ */
+ public void setAspectpath(String files) {
+ if (!LangUtil.isEmpty(files)) {
+ aspectpath = XMLWriter.unflattenList(files);
+ }
+ }
+
+ /**
+ * Set argfiles, deleting any old ones
+ * @param files comma-delimited list of argfiles - ignored if null or empty
+ */
+ public void setArgfiles(String files) {
+ if (!LangUtil.isEmpty(files)) {
+ argfiles = XMLWriter.unflattenList(files);
+ }
+ }
+
+ /** @return String[] copy of argfiles array */
+ public String[] getArgfilesArray() {
+ String[] argfiles = this.argfiles;
+ if (LangUtil.isEmpty(argfiles)) {
+ return new String[0];
+ }
+ return (String[]) LangUtil.copy(argfiles);
+ }
+
+ /**
+ * This implementation skips if:
+ * <ul>
+ * <li>incremental test, but using ajc (not eclipse)</li>
+ * <li>usejavac, but javac is not available on the classpath</li>
+ * <li>eclipse, but -usejavac or -preprocess test</li>
+ * <li>-source14, but running under 1.2 (XXX design)</li>
+ * <li>local/global option conflicts (-lenient/-strict)</li>
+ * <li>semantic conflicts (e.g., -lenient/-strict)</li>
+ * </ul>
+ * @return false if this wants to be skipped, true otherwise
+ */
+ protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) {
+ if (!super.doAdoptParentValues(parentRuntime, handler)) {
+ return false;
+ }
+ testSetup = setupArgs(handler);
+ if (!testSetup.result) {
+ skipMessage(handler, testSetup.failureReason);
+ }
+ return testSetup.result;
+ }
+
+
+ private String getShortCompilerName() {
+ String cname = compiler;
+ if (null != testSetup) {
+ cname = testSetup.compilerName;
+ }
+ if (null != cname) {
+ int loc = cname.lastIndexOf(".");
+ if (-1 != loc) {
+ cname = cname.substring(loc+1);
+ }
+ }
+ return cname;
+ }
+
+ /** @return a CompilerRun with this as spec if setup completes successfully. */
+ public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) {
+ CompilerRun run = new CompilerRun(this);
+ if (run.setupAjcRun(sandbox, validator)) {
+ // XXX need name for compilerRun
+ return new WrappedRunIterator(this, run);
+ }
+ return null;
+ }
+
+ /**
+ * Each non-incremental run, fold the global flags in with
+ * the run flags, which may involve adding or removing from
+ * either list, depending on the flag prefix:
+ * <ul>
+ * <li>-foo: use -foo unless forced off.<li>
+ * <li>^foo: (force off) remove any -foo option from the run flags</li>
+ * <li>!foo: (force on) require the -foo flag </li>
+ * </ul>
+ * If there is a force conflict, then the test is skipped
+ * ("skipping" info message, TestSetup.result is false).
+ * This means an option local to the test which was specified
+ * without forcing may be overridden by a globally-forced option.
+ * <p>
+ * There are some flags which are interpreted by the test
+ * and removed from the list of flags passed to the command
+ * (see testFlag()):
+ * <ul>
+ * <li>eclipse: use the new eclipse compiler (can force)</li>
+ * <li>ajc: use the old ajc compiler (can force)</li>
+ * <li>ignoreWarnings: ignore warnings in result evaluations (no force)</li>
+ * </ul>
+ * <p>
+ * There are some flags which are inconsistent with each other.
+ * These are treated as conflicts and the test is skipped:
+ * <ul>
+ * <li>lenient, strict</li>
+ * </ul>
+ * <p>
+ * The -source 1.4 flag should always be specified as -source14,
+ * as this will otherwise fail to process it correctly.
+ * This converts it back to -source 1.4.
+ * <p>
+ * Finally, compiler limitations are enforced here by skipping
+ * tests which the compiler cannot do:
+ * <ul>
+ * <li>eclipse does not do -lenient, -strict, -usejavac, -preprocess,
+ * -XOcodeSize, -XSerializable, XaddSafePrefix,
+ * -XserializableAspects,-XtargetNearSource</li>
+ * <li>ajc does not run in incremental (staging) mode,
+ * nor with -usejavac if javac is not on the classpath</li>
+ * </ul>
+ * <u>Errors</u>:This will remove an arg not prefixed by [-|!|^] after
+ * providing an info message.
+ * <u>TestSetup Result</u>:
+ * If this completes successfully, then TestSetup.result is true,
+ * and commandOptions is not null, and any test flags (ignore warning,
+ * compiler) are reflected in the TestSetup.
+ * If this fails, then TestSetup.result is false,
+ * and a TestSetup.failreason is set.
+ * @return TestSetup with results
+ * (TestSetup result=false if the run should not continue)
+ */
+ protected TestSetup setupArgs(IMessageHandler handler) {
+ // warning: HarnessSelectionTest checks for specific error wording
+ ArrayList argList = new ArrayList();
+ argList.addAll(getOptionsList());
+ final Spec spec = this;
+ TestSetup result = new TestSetup();
+ if (argList.contains("-source")) {
+ result.failureReason = "use -source14 for -source 1.4: " + argList;
+ return result;
+ }
+ result.compilerName = spec.compiler;
+ String[] globalOptions = spec.runtime.extractOptions(Spec.VALID_OPTIONS, true);
+ if ((null != globalOptions) && (globalOptions.length > 0)) {
+ // --- fold in globals, removing conflicts, etc.
+ for (int i = 0; i < globalOptions.length; i++) {
+ String globalArg = globalOptions[i];
+ if ((null == globalArg) || (2 > globalArg.length())) {
+ continue;
+ } else if (globalArg.startsWith(SEEK_PREFIX)) {
+ result.seek = globalArg.substring(SEEK_PREFIX.length());
+ continue;
+ } else if ("-source".equals(globalArg)) {
+ result.failureReason = "use -source14 for -source 1.4 [" + i + "]";
+ return result;
+ }
+ char first = globalArg.charAt(0);
+ globalArg = globalArg.substring(1);
+ boolean globalForceOn = (first == '!');
+ boolean globalForceOff = (first == '^');
+ boolean globalSet = (first == '-');
+ if (!globalSet && !globalForceOn && !globalForceOff) {
+ MessageUtil.info(handler, "ignoring bad global: " + globalOptions[i]);
+ continue;
+ }
+ int argIndex = indexOf(globalArg, argList);
+ if (-1 == argIndex) { // no apparent conflict - look for eclipse/ajc conflicts XXX unresolved
+ boolean ajcGlobal = true;
+ if ("ajc".equals(globalArg)) {
+ argIndex = indexOf("eclipse", argList);
+ } else if ("eclipse".equals(globalArg)) {
+ argIndex = indexOf("ajc", argList);
+ ajcGlobal = false;
+ }
+ if (-1 != argIndex) { // resolve eclipse/ajc conflict
+ String arg = ((String) argList.get(argIndex));
+ char argFirst = arg.charAt(0);
+ argList.remove(arg); // replace with resolved variant...
+ char ajcFirst;
+ char eclipseFirst;
+ if (ajcGlobal) {
+ ajcFirst = first;
+ eclipseFirst = argFirst;
+ } else {
+ ajcFirst = argFirst;
+ eclipseFirst = first;
+ }
+ if ('!' == eclipseFirst) {
+ if ('!' == ajcFirst) {
+ result.failureReason = "conflict between !eclipse and !ajc";
+ return result;
+ } else {
+ argList.add("-eclipse");
+ }
+ } else if (('!' == ajcFirst) || ('^' == eclipseFirst)) {
+ argList.add("-ajc");
+ } else if ('^' == ajcFirst) {
+ argList.add("-eclipse");
+ } else if (('-' != ajcFirst) || ('-' != eclipseFirst)) {
+ result.failureReason = "harness logic error resolving "
+ + arg + " and global " + globalArg;
+ return result;
+ } else if (ajcGlobal) {
+ argList.add("-ajc");
+ } else {
+ argList.add("-eclipse");
+ }
+ continue; // resolved
+ }
+ }
+
+ if (-1 == argIndex) { // no dup, so no conflict
+ if (!globalForceOff) {
+ argList.add("-" + globalArg);
+ }
+ } else { // have conflict - resolve
+ String arg = (String) argList.get(argIndex);
+ first = arg.charAt(0);
+ boolean localForceOn = (first == '!');
+ boolean localForceOff = (first == '^');
+ boolean localSet = (first == '-');
+ if (!localSet && !localForceOn && !localForceOff) {
+ result.failureReason = "only handling [-^!]{arg}: " + arg;
+ return result;
+ }
+ if ((localForceOn && globalForceOff)
+ || (localForceOff && globalForceOn)) {
+ result.failureReason = "force conflict between arg="
+ + arg + " and global=" + globalOptions[i];
+ return result;
+ }
+ if (globalForceOn) {
+ if (localForceOn) { // localSet is already correct, localForceOff was conflict
+ argList.remove(arg); // no !funkiness
+ argList.add("-" + globalArg);
+ }
+ } else if (globalSet) {
+ if (localSet) {
+ // do nothing - already correct
+ } else if (localForceOn) {
+ argList.remove(arg); // no !funkiness
+ argList.add("-" + globalArg);
+ }
+ } else if (globalForceOff) {
+ argList.remove(arg);
+ } else {
+ throw new Error("illegal arg state?? : " + arg);
+ //MessageUtil.info(handler, "illegal arg state??: " + arg);
+ }
+ }
+ }
+ }
+
+ // remove funky prefixes from remainder, fixup two-part flags
+ // and interpret special flags
+ boolean source14 = false;
+ ArrayList toAdd = new ArrayList();
+ for (ListIterator iter = argList.listIterator(); iter.hasNext();) {
+ String arg = (String) iter.next();
+ if (testFlag(arg, result)) {
+ iter.remove();
+ continue;
+ }
+ char c = arg.charAt(0);
+ String rest = arg.substring(1);
+ if (c == '^') {
+ iter.remove();
+ continue;
+ }
+ if (c == '!') {
+ iter.remove();
+ if (!("source14".equals(rest))) {
+ toAdd.add("-" + rest);
+ } else {
+ source14 = true;
+ }
+ rest = null;
+ } else if ("source14".equals(rest)) {
+ iter.remove();
+ source14 = true;
+ }
+ }
+ if (source14) {
+ // must run under 1.4 VM or (if ajc) set up bootclasspath
+ if (!LangUtil.supportsJava("1.4")) {
+ if (ReflectionFactory.ECLIPSE.equals(result.compilerName)) {
+ result.failureReason
+ = "eclipse must run under 1.4 to implement -source 1.4";
+ return result;
+ }
+
+ if (!FileUtil.canReadFile(Globals.J2SE14_RTJAR)) {
+ result.failureReason
+ = "unable to get 1.4 libraries to implement -source 1.4";
+ return result;
+ }
+ toAdd.add("-bootclasspath");
+ toAdd.add(Globals.J2SE14_RTJAR.getAbsolutePath());
+ }
+ toAdd.add("-source");
+ toAdd.add("1.4");
+ }
+ argList.addAll(toAdd);
+
+ // finally, check for semantic conflicts
+ String[] badOptions = LangUtil.selectOptions(argList, Spec.INVALID_OPTIONS);
+ if (!LangUtil.isEmpty(badOptions)) {
+ result.failureReason = "no support for (normally-valid) options "
+ + Arrays.asList(badOptions);
+ } else if (argList.contains("-lenient") && argList.contains("-strict")) {
+ result.failureReason = "semantic conflict -lenient | -strict";
+ } else if (ReflectionFactory.OLD_AJC.equals(result.compilerName)) {
+ if (spec.isStaging) {
+ result.failureReason = "OLD_AJC does not do incremental compiles";
+ } else if (argList.contains("-usejavac") && !haveJavac()) {
+ result.failureReason = "-usejavac but no javac on classpath";
+ } else {
+ result.result = true;
+ }
+ } else if (!ReflectionFactory.ECLIPSE.equals(result.compilerName)) {
+ result.failureReason = "unrecognized compiler: " + result.compilerName;
+ } else {
+ badOptions = LangUtil.selectOptions(argList, Spec.INVALID_ECLIPSE_OPTIONS);
+ if (!LangUtil.isEmpty(badOptions)) {
+ result.failureReason = "no support in eclipse-based compiler"
+ + " for (normally-valid) options "+ Arrays.asList(badOptions);
+ } else {
+ result.result = true;
+ }
+ }
+ if (result.result) {
+ result.commandOptions = argList;
+ }
+ return result;
+ }
+
+ /** @return true if javac is available on the classpath */
+ private boolean haveJavac() { // XXX copy/paste from JavaCWrapper.java
+ Class compilerClass = null;
+ try {
+ compilerClass = Class.forName("com.sun.tools.javac.Main");
+ } catch (ClassNotFoundException ce1) {
+ try {
+ compilerClass = Class.forName("sun.tools.javac.Main");
+ } catch (ClassNotFoundException ce2) {
+ }
+ }
+ return (null != compilerClass);
+ }
+
+
+ /**
+ * Handle flags that are interpreted by the test rather than the
+ * underlying command. These flags are to be removed from the
+ * arg list.
+ * @return true if this is a flag to remove from the arg list
+ */
+ protected boolean testFlag(String arg, TestSetup result) {
+ if ("-eclipse".equals(arg) || "!eclipse".equals(arg) || "^ajc".equals(arg)) {
+ result.compilerName = ReflectionFactory.ECLIPSE;
+ return true;
+ } else if ("-ajc".equals(arg) || "!ajc".equals(arg) || "^eclipse".equals(arg)) {
+ result.compilerName = ReflectionFactory.OLD_AJC;
+ return true;
+ } else if ("-ignoreWarnings".equals(arg)) {
+ result.ignoreWarnings = true;
+ result.ignoreWarningsSet = true;
+ return true;
+ }
+ return false;
+ }
+
+ // XXX need keys, cache...
+ /** @return index of global in argList, ignoring first char */
+ protected int indexOf(String global, ArrayList argList) {
+ int max = argList.size();
+ for (int i = 0; i < max; i++) {
+ if (global.equals(((String) argList.get(i)).substring(1))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Write this out as a compile element as defined in
+ * AjcSpecXmlReader.DOCTYPE.
+ * @see AjcSpecXmlReader#DOCTYPE
+ * @see IXmlWritable#writeXml(XMLWriter)
+ */
+ public void writeXml(XMLWriter out) {
+ // If our state is empty, we just delegate to super.
+ if (DEFAULT_COMPILER.equals(compiler)
+ && LangUtil.isEmpty(testSrcDirOffset)
+ && LangUtil.isEmpty(argfiles)) {
+ super.writeXml(out);
+ return;
+ }
+ String attr = "";
+ out.startElement(xmlElementName, false);
+ if (!DEFAULT_COMPILER.equals(compiler)) {
+ out.printAttribute("compiler", compiler);
+ }
+ if (!LangUtil.isEmpty(testSrcDirOffset)) {
+ out.printAttribute("dir", testSrcDirOffset);
+ }
+ if (!LangUtil.isEmpty(argfiles)) {
+ out.printAttribute("argfiles", out.flattenFiles(argfiles));
+ }
+ super.writeAttributes(out);
+ out.endAttributes();
+ if (!LangUtil.isEmpty(dirChanges)) {
+ DirChanges.Spec.writeXml(out, dirChanges);
+ }
+ List messages = getMessages();
+ if (!LangUtil.isEmpty(messages)) {
+ SoftMessage.writeXml(out, messages);
+ }
+ out.endElement(xmlElementName);
+ }
+
+ /**
+ * Encapsulate the directives that can be set using
+ * global arguments supplied in {@link Spec.getOptions()}.
+ * This supports changing the compiler and ignoring warnings.
+ */
+ class TestSetup {
+ /** null unless overriding the compiler to be used */
+ String compilerName;
+ /**
+ * true if we should tell AjcMessageHandler whether
+ * to ignore warnings in its result evaluation
+ */
+ boolean ignoreWarningsSet;
+
+ /** if telling AjcMessageHandler, what we tell it */
+ boolean ignoreWarnings;
+
+ /** false if setup failed */
+ boolean result;
+
+ /** if setup failed, this has the reason why */
+ String failureReason;
+
+ /** beyond running test, also seek text in sources */
+ String seek;
+
+ /** if setup completed, this has the combined global/local options */
+ ArrayList commandOptions;
+
+ public String toString() {
+ return "TestSetup("
+ + (null == compilerName ? "" : compilerName + " ")
+ + (!ignoreWarningsSet ? ""
+ : (ignoreWarnings ? "" : "do not ")
+ + "ignore warnings ")
+ + (result ? "" : "setup failed")
+ + ")";
+ }
+ }
+ } // CompilerRun.Spec
+} // CompilerRun
+
+// /**
+// * Write this out as a compile element as defined in
+// * AjcSpecXmlReader.DOCTYPE.
+// * @see AjcSpecXmlReader#DOCTYPE
+// * @see IXmlWritable#writeXml(XMLWriter)
+// */
+// public void writeXml(XMLWriter out) {
+// StringBuffer sb = new StringBuffer();
+// Spec spec = this;
+// final String elementName = "compile";
+// List list = spec.getOptionsList();
+// String args = XMLWriter.flattenList(spec.getOptionsList()).trim();
+// String argsAttr = out.makeAttribute("options", args).trim();
+// String files = XMLWriter.flattenFiles(spec.getPathsArray()).trim();
+// String filesAttr = out.makeAttribute("files", files).trim();
+// List messages = spec.getMessages();
+// int nMessages = messages.size();
+// int both = argsAttr.length() + filesAttr.length();
+// final int MAX = 55;
+//
+// // tortured logic to make more readable XML...
+// if ((both < MAX) || (0 == args.length())) {
+// // if short enough, print entire or just start
+// if (0 != args.length()) {
+// filesAttr = argsAttr + " " + filesAttr;
+// }
+// if (0 == nMessages) {
+// out.printElement(elementName, filesAttr);
+// return;
+// } else {
+// out.startElement(elementName, filesAttr, true);
+// }
+// } else if (argsAttr.length() < filesAttr.length()) {
+// out.startElement(elementName, argsAttr, false);
+// out.printAttribute("files", files);
+// out.endAttributes();
+// } else {
+// out.startElement(elementName, filesAttr, false);
+// out.printAttribute("options", args);
+// out.endAttributes();
+// }
+// if (0 < nMessages) {
+// SoftMessage.writeXml(out, messages);
+// }
+// out.endElement(elementName);
+// }
+
diff --git a/testing/src/org/aspectj/testing/harness/bridge/DirChanges.java b/testing/src/org/aspectj/testing/harness/bridge/DirChanges.java
new file mode 100644
index 000000000..73b930d71
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/DirChanges.java
@@ -0,0 +1,522 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.testing.util.TestUtil;
+import org.aspectj.testing.xml.IXmlWritable;
+import org.aspectj.testing.xml.XMLWriter;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Calculate changes in a directory tree.
+ * Usage:
+ * <ul>
+ * <li>Set up with any expected changes and/or an expected directory</li>
+ * <li>Set up with any file checker</li>
+ * <li>start(..) before changes.
+ * This issues messages for any removed files not found,
+ * which represent an error in the expected changes.</li>
+ * <li>Do whatever operations will change the directory</li>
+ * <li>end(..).
+ * This issues messages for any files not removed, added, or updated,
+ * and, if any checker was set, any checker messages for matching
+ * added or updated files</li>
+ * </ul>
+ */
+public class DirChanges {
+
+ private static final boolean EXISTS = true;
+
+ final Spec spec;
+
+ /** start time, in milliseconds - valid only from start(..)..end(..) */
+ long startTime;
+
+ /** base directory of actual files - valid only from start(..)..end(..) */
+ File baseDir;
+
+ /** if set, this is run against any resulting existing files */
+ IFileChecker fileChecker;
+
+ /** handler valid from start..end of start(..) and end(..) methods */
+ IMessageHandler handler;
+
+ /**
+ * Constructor for DirChanges.
+ */
+ public DirChanges(Spec spec) {
+ LangUtil.throwIaxIfNull(spec, "spec");
+ this.spec = spec;
+ }
+
+ /**
+ * Inspect the base dir, and issue any messages for
+ * removed files not present.
+ * @param baseDir File for a readable directory
+ * @return true if this started without sending messages
+ * for removed files not present.
+ */
+ public boolean start(IMessageHandler handler, File baseDir) {
+ FileUtil.throwIaxUnlessCanReadDir(baseDir, "baseDir");
+ final IMessageHandler oldHandler = this.handler;
+ this.handler = handler;
+ this.baseDir = baseDir;
+ startTime = 0l;
+ final boolean doCompare = false;
+ boolean result
+ = exists("at start, did not expect file to be added", !EXISTS, spec.added, doCompare);
+ result &= exists("at start, expected file to be unchanged", EXISTS, spec.unchanged, doCompare);
+ result &= exists("at start, expected file to be updated", EXISTS, spec.updated, doCompare);
+ result &= exists("at start, expected file to be removed", EXISTS, spec.removed, doCompare);
+ startTime = System.currentTimeMillis();
+ this.handler = oldHandler;
+ return result;
+ }
+
+
+ /**
+ * Inspect the base dir, issue any messages for
+ * files not added, files not updated, and files not removed,
+ * and compare expected/actual files added or updated.
+ * @throws IllegalStateException if called before start(..)
+ */
+ public boolean end(IMessageHandler handler, File srcBaseDir) {
+ FileUtil.throwIaxUnlessCanReadDir(baseDir, "baseDir");
+ if (0l == startTime) {
+ throw new IllegalStateException("called before start");
+ }
+ final IMessageHandler oldHandler = this.handler;
+ this.handler = handler;
+ final boolean doCompare = (null != fileChecker);
+ final boolean fastFail = spec.fastFail;
+ boolean result
+ = exists("at end, expected file was not added", EXISTS, spec.added, doCompare);
+ if (result || !fastFail) {
+ result &= exists("at end, expected file was not unchanged", EXISTS, spec.unchanged, doCompare, false);
+ }
+ if (result || !fastFail) {
+ result &= exists("at end, expected file was not updated", EXISTS, spec.updated, doCompare);
+ }
+ if (result || !fastFail) {
+ result &= exists("at end, file exists, was not removed", !EXISTS, spec.removed, doCompare);
+ }
+ if (result || !fastFail) {
+ result &= compareDir(srcBaseDir);
+ }
+ // XXX validate that unchanged mod-time did not change
+
+ this.handler = oldHandler;
+ baseDir = null;
+ startTime = 0l;
+ return result;
+ }
+
+ /**
+ * Verify that all files in any specified expected directory
+ * have matching files in the base directory, putting any messages
+ * in the handler (only one if the spec indicates fast-fail).
+ * @param srcBaseDir the File for the base directory of the test sources
+ * (any expected dir is specified relative to this directory)
+ * @return true if the same, false otherwise
+ */
+ private boolean compareDir(File srcBaseDir) {
+ if (null == spec.dirExpected) {
+ return true;
+ }
+ File expDir = new File(srcBaseDir, spec.dirExpected);
+ File actDir = baseDir;
+ return TestUtil.sameDirectoryContents(handler, expDir, actDir, spec.fastFail);
+ }
+
+
+ /** @param comp FileMessageComparer (if any) given matching messages to compare */
+ protected void setFileComparer(IFileChecker comp) {
+ this.fileChecker = comp;
+ }
+
+
+
+ /**
+ * Signal fail if any files do {not} exist or do {not} have last-mod-time after startTime
+ * @param handler the IMessageHandler sink for messages
+ * @param label the String infix for the fail message
+ * @param exists if true, then file must exist and be modified after start time;
+ * if false, then the file must not exist or must be modified before start time.
+ * @param pathList the List of path (without any Spec.defaultSuffix) of File
+ * in Spec.baseDir to find (or not, if !exists)
+ */
+ protected boolean exists(
+ String label,
+ boolean exists,
+ List pathList,
+ boolean doCompare) {
+ boolean expectStartEarlier = true;
+ return exists(label, exists, pathList,doCompare, true);
+ }
+ protected boolean exists(
+ String label,
+ boolean exists,
+ List pathList,
+ boolean doCompare,
+ boolean expectStartEarlier) {
+ boolean result = true;
+ if (!LangUtil.isEmpty(pathList)) {
+ final File expDir = ((!doCompare || (null == spec.dirExpected))
+ ? null
+ : new File(baseDir, spec.dirExpected));
+ for (Iterator iter = pathList.iterator(); iter.hasNext();) {
+ final String entry = (String) iter.next() ;
+ String path = entry ;
+ if (null != spec.defaultSuffix) {
+ if (".class".equals(spec.defaultSuffix)) {
+ path = path.replace('.', '/');
+ }
+ path = path + spec.defaultSuffix;
+ }
+ File actualFile = new File(baseDir, path);
+ if (exists != (actualFile.canRead() && actualFile.isFile()
+ && (expectStartEarlier
+ ? startTime <= actualFile.lastModified()
+ : startTime > actualFile.lastModified()
+ ))) {
+ failMessage(handler, exists, label, path, actualFile);
+ if (result) {
+ result = false;
+ }
+ } else if (exists && doCompare && (null != fileChecker)) {
+ if (!fileChecker.checkFile(handler, path, actualFile) && result) {
+ result = false;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Generate fail message "{un}expected {label} file {path} in {baseDir}".
+ * @param handler the IMessageHandler sink
+ * @param label String message infix
+ * @param path the path to the file
+ */
+ protected void failMessage(
+ IMessageHandler handler,
+ boolean exists,
+ String label,
+ String path,
+ File file) {
+ MessageUtil.fail(handler, label + " \"" + path + "\" in " + baseDir);
+ }
+
+
+ /** Check actual File found at a path, usually to diff expected/actual contents */
+ public static interface IFileChecker {
+ /**
+ * Check file found at path.
+ * Implementations should return false when adding fail (or worse)
+ * message to the handler, and true otherwise.
+ * @param handler IMessageHandler sink for messages, esp. fail.
+ * @param path String for normalized path portion of actualFile.getPath()
+ * @param actualFile File to file found
+ * @return false if check failed and messages added to handler
+ */
+ boolean checkFile(IMessageHandler handler, String path, File actualFile);
+ }
+
+// /**
+// * Default FileChecker compares files literally, transforming any
+// * with registered normalizers.
+// */
+// public static class FileChecker implements IFileChecker {
+// final File baseExpectedDir;
+// NormalizedCompareFiles fileComparer;
+//
+// public FileChecker(File baseExpectedDir) {
+// this.baseExpectedDir = baseExpectedDir;
+// fileComparer = new NormalizedCompareFiles();
+// }
+// public boolean checkFile(IMessageHandler handler, String path, File actualFile) {
+// if (null == baseExpectedDir) {
+// MessageUtil.error(handler, "null baseExpectedDir set on construction");
+// } else if (!baseExpectedDir.canRead() || !baseExpectedDir.isDirectory()) {
+// MessageUtil.error(handler, "bad baseExpectedDir: " + baseExpectedDir);
+// } else {
+// File expectedFile = new File(baseExpectedDir, path);
+// if (!expectedFile.canRead()) {
+// MessageUtil.fail(handler, "cannot read expected file: " + expectedFile);
+// } else {
+// return doCheckFile(handler, expectedFile, actualFile, path);
+// }
+// }
+// return false;
+// }
+//
+// protected boolean doCheckFile(
+// IMessageHandler handler,
+// File expectedFile,
+// File actualFile,
+// String path) {
+// fileComparer.setHandler(handler);
+// FileLine[] expected = fileComparer.diff();
+// return false;
+// }
+// }
+
+// /**
+// * CompareFiles implementation that pre-processes input
+// * to normalize it. Currently it reads all files except
+// * .class files, which it disassembles first.
+// */
+// public static class NormalizedCompareFiles extends CompareFiles {
+// private final static String[] NO_PATHS = new String[0];
+// private static String normalPath(File file) { // XXX util
+// if (null == file) {
+// return "";
+// }
+// return file.getAbsolutePath().replace('\\', '/');
+// }
+//
+// private String[] baseDirs;
+// private IMessageHandler handler;
+//
+// public NormalizedCompareFiles() {
+// }
+//
+// void init(IMessageHandler handler, File[] baseDirs) {
+// this.handler = handler;
+// if (null == baseDirs) {
+// this.baseDirs = NO_PATHS;
+// } else {
+// this.baseDirs = new String[baseDirs.length];
+// for (int i = 0; i < baseDirs.length; i++) {
+// this.baseDirs[i] = normalPath(baseDirs[i]) + "/";
+// }
+// }
+// }
+//
+// private String getClassName(File file) {
+// String result = null;
+// String path = normalPath(file);
+// if (!path.endsWith(".class")) {
+// MessageUtil.error(handler,
+// "NormalizedCompareFiles expected "
+// + file
+// + " to end with .class");
+// } else {
+// path = path.substring(0, path.length()-6);
+// for (int i = 0; i < baseDirs.length; i++) {
+// if (path.startsWith(baseDirs[i])) {
+// return path.substring(baseDirs[i].length()).replace('/', '.');
+// }
+// }
+// MessageUtil.error(handler,
+// "NormalizedCompareFiles expected "
+// + file
+// + " to start with one of "
+// + LangUtil.arrayAsList(baseDirs));
+// }
+//
+// return result;
+// }
+//
+// /**
+// * Read file as normalized lines, sending handler any messages
+// * ERROR for input failures and FAIL for processing failures.
+// * @return NOLINES on error or normalized lines from file otherwise
+// */
+// public FileLine[] getFileLines(File file) {
+// FileLineator capture = new FileLineator();
+// InputStream in = null;
+// try {
+// if (!file.getPath().endsWith(".class")) {
+// in = new FileInputStream(file);
+// FileUtil.copyStream(
+// new BufferedReader(new InputStreamReader(in)),
+// new PrintWriter(capture));
+// } else {
+// String name = getClassName(file);
+// if (null == name) {
+// return new FileLine[0];
+// }
+// String path = normalPath(file);
+// path = path.substring(0, path.length()-name.length());
+// // XXX sole dependency on bcweaver/bcel
+// LazyClassGen.disassemble(path, name, capture);
+// }
+// } catch (IOException e) {
+// MessageUtil.fail(handler,
+// "NormalizedCompareFiles IOException reading " + file, e);
+// return null;
+// } finally {
+// if (null != in) {
+// try { in.close(); }
+// catch (IOException e) {} // ignore
+// }
+// capture.flush();
+// capture.close();
+// }
+// String missed = capture.getMissed();
+// if (!LangUtil.isEmpty(missed)) {
+// MessageUtil.fail(handler,
+// "NormalizedCompareFiles missed input: "
+// + missed);
+// return null;
+// } else {
+// return capture.getFileLines();
+// }
+// }
+//
+//
+// }
+
+ /**
+ * Specification for a set of File added, removed, or updated
+ * in a given directory, or for a directory base for a tree of expected files.
+ * If defaultSuffix is specified, entries may be added without it.
+ */
+ public static class Spec implements IXmlWritable {
+ public static final String XMLNAME = "dir-changes";
+ String dirToken;
+ String defaultSuffix;
+ String dirExpected;
+ /** if true, ok to fail on first mis-match */
+ boolean fastFail;
+ final ArrayList added;
+ final ArrayList removed;
+ final ArrayList updated;
+ final ArrayList unchanged;
+
+ /**
+ * @param dirToken the symbol name of the base directory
+ * @param clipSuffix the String suffix, if any, to clip automatically
+ */
+ public Spec() {
+ added = new ArrayList();
+ removed = new ArrayList();
+ updated = new ArrayList();
+ unchanged = new ArrayList();
+ }
+
+ /**
+ * @param dirToken the symbol name of the base directory (classes, run)
+ */
+ public void setDirToken(String dirToken) {
+ this.dirToken = dirToken;
+ }
+
+ /**
+ * Set the directory containing the expected files.
+ * Currently this only is used to verify files that are expected
+ * and found after the process completes; it does not stand on its
+ * own as a specification of files.
+ * @param expectedDirRelativePath path relative to the test base
+ * of the directory containing expected results for the output dir.
+ */
+ public void setExpDir(String expectedDirRelativePath) {
+ }
+
+ /**
+ * @param clipSuffix the String suffix, if any, to clip automatically
+ */
+ public void setDefaultSuffix(String defaultSuffix) {
+ this.defaultSuffix = defaultSuffix;
+ }
+
+ public void setAdded(String items) {
+ XMLWriter.addFlattenedItems(added, items);
+ }
+
+ public void setRemoved(String items) {
+ XMLWriter.addFlattenedItems(removed, items);
+ }
+
+ public void setUpdated(String items) {
+ XMLWriter.addFlattenedItems(updated, items);
+ }
+
+ public void setUnchanged(String items) {
+ XMLWriter.addFlattenedItems(unchanged, items);
+ }
+ public void setFastfail(boolean fastFail) {
+ this.fastFail = fastFail; // XXX support in XML
+ }
+
+ /** @return true if some list was specified */
+ private boolean hasFileList() {
+ return (!LangUtil.isEmpty(added)
+ || !LangUtil.isEmpty(removed)
+ || !LangUtil.isEmpty(updated)
+ || !LangUtil.isEmpty(unchanged)
+ );
+ }
+
+ /** this writes nothing if there are no added, removed, or changed */
+ public void writeXml(XMLWriter out) {
+ if (!hasFileList()) {
+ return;
+ }
+ String attr = "";
+ // XXX need to permit defaults here...
+ if (null != dirToken) {
+ attr = out.makeAttribute("dirToken", dirToken) + " ";
+ }
+ if (null != defaultSuffix) {
+ attr += out.makeAttribute("defaultSuffix", defaultSuffix);
+ }
+ out.startElement(XMLNAME, attr, false);
+ if (!LangUtil.isEmpty(added)) {
+ out.printAttribute("added", XMLWriter.flattenList(added));
+ }
+ if (!LangUtil.isEmpty(removed)) {
+ out.printAttribute("removed", XMLWriter.flattenList(removed));
+ }
+ if (!LangUtil.isEmpty(updated)) {
+ out.printAttribute("updated", XMLWriter.flattenList(updated));
+ }
+ if (!LangUtil.isEmpty(unchanged)) {
+ out.printAttribute("unchanged", XMLWriter.flattenList(unchanged));
+ }
+ out.endElement(XMLNAME);
+ }
+
+ /**
+ * Write list as elements to XMLWriter.
+ * @param out XMLWriter output sink
+ * @param dirChanges List of DirChanges.Spec to write
+ */
+ public static void writeXml(XMLWriter out, List dirChanges) {
+ if (LangUtil.isEmpty(dirChanges)) {
+ return;
+ }
+ LangUtil.throwIaxIfNull(out, "out");
+ for (Iterator iter = dirChanges.iterator(); iter.hasNext();) {
+ DirChanges.Spec spec = (DirChanges.Spec) iter.next();
+ if (null == spec) {
+ continue;
+ }
+ spec.writeXml(out);
+ }
+ }
+
+} // class Spec
+
+}
diff --git a/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java b/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java
new file mode 100644
index 000000000..dbf5dd259
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java
@@ -0,0 +1,377 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.bridge.AbortException;
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.ISourceLocation;
+import org.aspectj.bridge.Message;
+import org.aspectj.bridge.SourceLocation;
+import org.aspectj.bridge.IMessage.Kind;
+import org.aspectj.testing.util.BridgeUtil;
+import org.aspectj.testing.util.ObjectChecker;
+import org.aspectj.testing.util.SFileReader;
+import org.aspectj.testing.util.StandardObjectChecker;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+import org.aspectj.util.LineReader;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * SFileReader.Maker implementation to read tests
+ * XXX supports iterative but not yet incremental compiles
+ */
+public class FlatSuiteReader implements SFileReader.Maker {
+ public static final String[] RA_String = new String[0];
+ public static final FlatSuiteReader ME = new FlatSuiteReader();
+ private static final SFileReader READER = new SFileReader(ME);
+
+ static boolean isNumber(String s) { // XXX costly
+ if ((null == s) || (0 == s.length())) {
+ return false;
+ }
+ try {
+ Integer.valueOf(s);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ /** if true, clean up records before returning from make */
+ public boolean clean;
+
+ private FlatSuiteReader() {
+ }
+
+ /**
+ * @see org.aspectj.testing.harness.bridge.SFileReader.Maker#getType()
+ */
+ public Class getType() {
+ return AjcTest.Spec.class;
+ }
+
+ /**
+ * This constructs an AjcTest.Spec assuming we are at the start of a
+ * test definition in reader and taking the parent directory of
+ * the reader as the base directory for the test suite root.
+ * @return the next AjcTest in reader, or null
+ * @see org.aspectj.testing.harness.bridge.SFileReader.Maker#make(LineReader)
+ */
+ public Object make(final LineReader reader)
+ throws AbortException, IOException {
+ final AjcTest.Spec result = new AjcTest.Spec();
+ boolean usingEclipse = false; // XXX
+ /** handle read errors by throwing AbortException with context info */
+ class R {
+ public String read(String context) throws IOException {
+ return read(context, true);
+ }
+ public String read(String context, boolean required)
+ throws IOException {
+ final boolean skipEmpties = false;
+ String result = reader.nextLine(skipEmpties);
+ if ((null != result) && (0 == result.length())) {
+ result = null;
+ }
+ if ((null == result) && required) {
+ String s = "expecting " + context + " at " + reader;
+ throw new AbortException(s);
+ }
+ return result;
+ }
+ }
+
+ final R r = new R();
+ //final String baseDir = reader.getFile().getParent();
+ String line;
+ String[] words;
+ boolean isRequired = true;
+
+ final int startLine = reader.getLineNumber() - 1;
+
+ // description first - get from last line read
+ // XXX permits exactly one blank line between test records?
+ result.description = reader.lastLine();
+ if (null == result.description) {
+ throw new AbortException("expecting description at " + reader);
+ }
+
+ // next line is baseDir {option..}
+ line = r.read("baseDir {option..}");
+ words = LangUtil.split(line);
+ if ((null == words) || (0 == words.length)) {
+ throw new AbortException(
+ "expecting dir {option..} at " + reader);
+ }
+ // XXX per-test (shared) root
+ //final File sourceRoot = new File(baseDir, words[0]);
+ result.setTestDirOffset(words[0]);
+
+ String[] compileOptions = new String[words.length - 1];
+ System.arraycopy(words, 1, compileOptions, 0, words.length - 1);
+
+ // next are 1..n source lines: source...
+ CompilerRun.Spec lastCompileSpec = null;
+ // save last source file as default for error/warning line
+ File lastFile = null; // XXX per-compiler-run errors
+ while (null != (line = r.read("source.."))) {
+ words = LangUtil.split(line);
+ if (0 == FileUtil.sourceSuffixLength(words[0])) { // XXX
+ break;
+ } else {
+ lastCompileSpec = new CompilerRun.Spec();
+ lastCompileSpec.testSrcDirOffset = null;
+ // srcs are in test base for old
+ lastCompileSpec.addOptions(compileOptions);
+ lastCompileSpec.addPaths(words);
+ lastFile = new File(words[words.length - 1]);
+ result.addChild(lastCompileSpec);
+ }
+ }
+ if (null == lastCompileSpec) {
+ throw new AbortException("expected sources at " + reader);
+ }
+
+ ArrayList exp = new ArrayList();
+ // !compile || noerrors || className {runOption..}
+ String first = words[0];
+ if ("!compile".equals(first)) {
+ //result.className = words[0];
+ //result.runOptions = new String[words.length-1];
+ //System.arraycopy(words, 0, result.runOptions, 0, words.length-1);
+ } else if ("noerrors".equals(first)) {
+ // className is null, but no errors expected
+ // so compile succeeds but run not attempted
+ //result.errors = Main.RA_ErrorLine;
+ // result.runOptions = Main.RA_String;
+ } else if (isNumber(first) || (-1 != first.indexOf(":"))) {
+ exp.addAll(makeMessages(IMessage.ERROR, words, 0, lastFile));
+ } else {
+ String[] args = new String[words.length - 1];
+ System.arraycopy(words, 0, args, 0, args.length);
+ JavaRun.Spec spec = new JavaRun.Spec();
+ spec.className = first;
+ spec.addOptions(args);
+ //XXXrun.runDir = sourceRoot;
+ result.addChild(spec);
+ }
+
+ // optional: warnings, eclipse.warnings, eclipse.errors
+ // XXX unable to specify error in eclipse but not ajc
+ boolean gotErrors = false;
+ while (null
+ != (line =
+ r.read(
+ " errors, warnings, eclipse.warnings, eclipse.error",
+ false))) {
+ words = LangUtil.split(line);
+ first = words[0];
+ if ("eclipse.warnings:".equals(first)) {
+ if (usingEclipse) {
+ exp.addAll(
+ makeMessages(
+ IMessage.WARNING,
+ words,
+ 0,
+ lastFile));
+ }
+ } else if ("eclipse.errors:".equals(first)) {
+ if (usingEclipse) {
+ exp.addAll(
+ makeMessages(IMessage.ERROR, words, 0, lastFile));
+ }
+ } else if ("warnings:".equals(first)) {
+ exp.addAll(
+ makeMessages(IMessage.WARNING, words, 0, lastFile));
+ } else if (gotErrors) {
+ exp.addAll(
+ makeMessages(IMessage.WARNING, words, 0, lastFile));
+ } else {
+ exp.addAll(
+ makeMessages(IMessage.ERROR, words, 0, lastFile));
+ gotErrors = true;
+ }
+ }
+ lastCompileSpec.addMessages(exp);
+
+ int endLine = reader.getLineNumber();
+ File sourceFile = reader.getFile();
+ ISourceLocation sl =
+ new SourceLocation(sourceFile, startLine, endLine, 0);
+ result.setSourceLocation(sl);
+
+ if (clean) {
+ cleanup(result, reader);
+ }
+ return result;
+ }
+
+ /** post-process result
+ * - use file name as keyword
+ * - clip / for dir offsets
+ * - extract purejava keyword variants
+ * - extract bugID
+ * - convert test options to force-options
+ * - detect illegal xml characters
+ */
+ private void cleanup(AjcTest.Spec result, LineReader lineReader) {
+ LangUtil.throwIaxIfNull(result, "result");
+ LangUtil.throwIaxIfNull(lineReader, "lineReader");
+
+ File suiteFile = lineReader.getFile();
+ String name = suiteFile.getName();
+ if (!name.endsWith(".txt")) {
+ throw new Error("unexpected name: " + name);
+ }
+ result.addKeyword("from-" + name.substring(0,name.length()-4));
+
+ final String dir = result.testDirOffset;
+ if (dir.endsWith("/")) {
+ result.testDirOffset = dir.substring(0,dir.length()-1);
+ }
+
+ StringBuffer description = new StringBuffer(result.description);
+ if (strip(description, "PUREJAVA")) {
+ result.addKeyword("purejava");
+ }
+ if (strip(description, "PUREJAVE")) {
+ result.addKeyword("purejava");
+ }
+ if (strip(description, "[purejava]")) {
+ result.addKeyword("purejava");
+ }
+ String input = description.toString();
+ int loc = input.indexOf("PR#");
+ if (-1 != loc) {
+ String prefix = input.substring(0, loc).trim();
+ String pr = input.substring(loc+3, loc+6).trim();
+ String suffix = input.substring(loc+6).trim();
+ description.setLength(0);
+ description.append((prefix + " " + suffix).trim());
+ try {
+ result.setBugId(Integer.valueOf(pr).intValue());
+ } catch (NumberFormatException e) {
+ throw new Error("unable to convert " + pr + " for " + result
+ + " at " + lineReader);
+ }
+ }
+ input = description.toString();
+ String error = null;
+ if (-1 != input.indexOf("&")) {
+ error = "char &";
+ } else if (-1 != input.indexOf("<")) {
+ error = "char <";
+ } else if (-1 != input.indexOf(">")) {
+ error = "char >";
+ } else if (-1 != input.indexOf("\"")) {
+ error = "char \"";
+ }
+ if (null != error) {
+ throw new Error(error + " in " + input + " at " + lineReader);
+ }
+ result.description = input;
+
+ ArrayList newOptions = new ArrayList();
+ ArrayList optionsCopy = result.getOptionsList();
+ for (Iterator iter = optionsCopy.iterator(); iter.hasNext();) {
+ String option = (String) iter.next();
+ if (option.startsWith("-")) {
+ newOptions.add("!" + option.substring(1));
+ } else {
+ throw new Error("non-flag option? " + option);
+ }
+ }
+ result.setOptionsArray((String[]) newOptions.toArray(new String[0]));
+ }
+
+ private boolean strip(StringBuffer sb, String infix) {
+ String input = sb.toString();
+ int loc = input.indexOf(infix);
+ if (-1 != loc) {
+ String prefix = input.substring(0, loc);
+ String suffix = input.substring(loc+infix.length());
+ input = (prefix.trim() + " " + suffix.trim()).trim();
+ sb.setLength(0);
+ sb.append(input);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Generate list of expected messages of this kind.
+ * @param kind any non-null kind, but s.b. IMessage.WARNING or ERROR
+ * @param words
+ * @param start index in words where to start
+ * @param lastFile default file for source location if the input does not specify
+ * @return List
+ */
+ private List makeMessages(// XXX weak - also support expected exceptions, etc.
+ Kind kind, String[] words, int start, File lastFile) {
+ ArrayList result = new ArrayList();
+ for (int i = start; i < words.length; i++) {
+ ISourceLocation sl =
+ BridgeUtil.makeSourceLocation(words[i], lastFile);
+ if (null == sl) { // XXX signalling during make
+ // System.err.println(...);
+ //MessageUtil.debug(handler, "not a source location: " + words[i]);
+ } else {
+ String text =
+ (("" + sl.getLine()).equals(words[i]) ? "" : words[i]);
+ result.add(new Message(text, kind, null, sl));
+ }
+ }
+ return (0 == result.size() ? Collections.EMPTY_LIST : result);
+ }
+
+ /**
+ * Read suite spec from a flat .txt file.
+ * @throws AbortException on failure
+ * @return AjcTest.Suite.Spec with any AjcTest.Spec as children
+ */
+ public AjcTest.Suite.Spec readSuite(File suiteFile) {
+ LangUtil.throwIaxIfNull(suiteFile, "suiteFile");
+ if (!suiteFile.isAbsolute()) {
+ suiteFile = suiteFile.getAbsoluteFile();
+ }
+ final AjcTest.Suite.Spec result = new AjcTest.Suite.Spec();
+ result.setSuiteDirFile(suiteFile.getParentFile());
+ ObjectChecker collector = new StandardObjectChecker(IRunSpec.class) {
+ public boolean doIsValid(Object o) {
+ result.addChild((IRunSpec) o);
+ return true;
+ }
+ };
+ boolean abortOnError = true;
+ try {
+ READER.readNodes(
+ suiteFile,
+ collector,
+ abortOnError,
+ System.err);
+ } catch (IOException e) {
+ IMessage m = Message.fail("reading " + suiteFile, e);
+ throw new AbortException(m);
+ }
+
+ return result;
+ }
+}
diff --git a/testing/src/org/aspectj/testing/harness/bridge/Globals.java b/testing/src/org/aspectj/testing/harness/bridge/Globals.java
new file mode 100644
index 000000000..7c4f524a5
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/Globals.java
@@ -0,0 +1,94 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+ package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+import java.io.File;
+
+/**
+ */
+public class Globals {
+ /** name/key of the System property to set to define library dir */
+ public static final String LIBDIR_NAME = "harness.libdir";
+
+ /** name/key of the System property to set to define J2SE_HOME dir */
+ public static final String J2SE14_RTJAR_NAME = "j2se14.rtjar";
+
+ /** assumed relative location of a library with required jars */
+ public static final String LIBDIR = getSystemProperty(LIBDIR_NAME, "../lib/test");
+
+ /** Path to J2SE_HOME */
+ public static final File J2SE14_RTJAR;
+
+ /** array of parameter types for main(String[]) */
+ public static final Class[] MAIN_PARM_TYPES = new Class[] {String[].class};
+ public static final String S_testingclient_jar = LIBDIR + "/testing-client.jar";
+ public static final String S_aspectjrt_jar = LIBDIR + "/aspectjrt.jar";
+ public static final String S_bridge_jar = LIBDIR + "/bridge.jar";
+ public static final File F_testingclient_jar = new File(S_testingclient_jar);
+ public static final File F_aspectjrt_jar = new File(S_aspectjrt_jar);
+ public static final File F_bridge_jar = new File(S_bridge_jar);
+ public static final boolean globalsValid;
+
+ static {
+ File j2seJar = null;
+ String path = getSystemProperty(J2SE14_RTJAR_NAME, "c:/home/apps/jdk14");
+ File j2seHome = new File(path);
+ if (j2seHome.exists() && j2seHome.isDirectory()) {
+ File rtjar = new File(j2seHome.getAbsolutePath() + "/jre/lib/rt.jar");
+ if (rtjar.canRead() && rtjar.isFile()) {
+ j2seJar = rtjar;
+ }
+ }
+ J2SE14_RTJAR = j2seJar;
+ globalsValid =
+ (FileUtil.canReadFile(F_testingclient_jar)
+ && FileUtil.canReadFile(F_aspectjrt_jar)
+ && FileUtil.canReadFile(F_bridge_jar)
+ && FileUtil.canReadFile(J2SE14_RTJAR)
+ );
+ }
+
+ /**
+ *
+ * @return null if not found, or
+ * String with class path for compiler to load J2SE 1.4 classes from.
+ */
+ public static String get14Bootclasspath() {
+ return null;
+ }
+
+ /**
+ * Get System property completely safely.
+ * @param propertyName the String name of the property to get
+ * @param defaultValue the String default value to return value is null or empty
+ * @return String value or defaultValue if not available.
+ */
+ private static String getSystemProperty(
+ String propertyName,
+ String defaultValue) {
+ String result = defaultValue;
+ try {
+ String value = System.getProperty(propertyName);
+ if (!LangUtil.isEmpty(value)) {
+ result = value;
+ }
+ } catch (Throwable t) {}
+ return result;
+ }
+
+}
diff --git a/testing/src/org/aspectj/testing/harness/bridge/IAjcRun.java b/testing/src/org/aspectj/testing/harness/bridge/IAjcRun.java
new file mode 100644
index 000000000..061fde4d1
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/IAjcRun.java
@@ -0,0 +1,44 @@
+/* *******************************************************************
+ * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.testing.run.IRun;
+import org.aspectj.testing.run.IRunStatus;
+import org.aspectj.testing.xml.XMLWriter;
+
+// XXX candidate to be subsumed in class/constructors, since inner spec does setup
+// at the same time it constructs the run.
+public interface IAjcRun extends IRun {
+ boolean setupAjcRun(Sandbox sandbox, Validator validator);
+ // XXX add for result eval? ArrayList getExpectedMessages();
+
+ /** this IAjcRun does nothing, returning true always */
+ public static final IAjcRun NULLRUN = new IAjcRun() {
+ public boolean setupAjcRun(Sandbox sandbox, Validator validator) {
+ return true;
+ }
+ public boolean run(IRunStatus status) {
+ if (!status.started()) {
+ status.start();
+ }
+ status.finish(IRunStatus.PASS);
+ return true;
+ }
+
+ public void writeXml(XMLWriter out) {
+ throw new UnsupportedOperationException("unimplemented");
+ }
+ public String toString() { return "IAjcRun.NULLRUN"; }
+ };
+
+}
diff --git a/testing/src/org/aspectj/testing/harness/bridge/IRunSpec.java b/testing/src/org/aspectj/testing/harness/bridge/IRunSpec.java
new file mode 100644
index 000000000..7cb00765b
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/IRunSpec.java
@@ -0,0 +1,24 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.testing.run.IRunIterator;
+import org.aspectj.testing.xml.IXmlWritable;
+
+/**
+ * A run spec can make a run iterator and write itself as XML.
+ */
+public interface IRunSpec extends IXmlWritable {
+ IRunIterator makeRunIterator(Sandbox sandbox, Validator validator);
+}
diff --git a/testing/src/org/aspectj/testing/harness/bridge/IncCompilerRun.java b/testing/src/org/aspectj/testing/harness/bridge/IncCompilerRun.java
new file mode 100644
index 000000000..efdd089fe
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/IncCompilerRun.java
@@ -0,0 +1,444 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.bridge.ICommand;
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.testing.run.IRunIterator;
+import org.aspectj.testing.run.IRunStatus;
+import org.aspectj.testing.run.WrappedRunIterator;
+import org.aspectj.testing.xml.SoftMessage;
+import org.aspectj.testing.xml.XMLWriter;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An incremental compiler run takes an existing compiler commmand
+ * from the sandbox, updates the staging directory, and recompiles.
+ * The staging directory is updated by prefix/suffix rules applied
+ * to files found below Sandbox.testBaseSrcDir.
+ * Files with suffix .{tag}.java are owned by this run
+ * and are copied to the staging directory
+ * unless they are prefixed "delete.", in which case the
+ * corresponding file is deleted. Any "owned" file is passed to
+ * the compiler as the list of changed files.
+ * The files entry contains the expected files recompiled. XXX underinclusive
+ * XXX prefer messages for expected files?
+ * XXX later: also support specified paths, etc.
+ */
+public class IncCompilerRun implements IAjcRun {
+
+ final Spec spec; // nonfinal later to make re-runnable
+ Sandbox sandbox;
+
+ /**
+ * @param handler must not be null, but may be reused in the same thread
+ */
+ public IncCompilerRun(Spec spec) {
+ LangUtil.throwIaxIfNull(spec, "spec");
+ this.spec = spec;
+ }
+
+ /**
+ * Initialize this from the sandbox, using compiler and changedFiles.
+ * @param sandbox the Sandbox setup for this test, including copying
+ * any changed files, etc.
+ * @see org.aspectj.testing.harness.bridge.AjcTest.IAjcRun#setup(File, File)
+ * @throws AbortException containing IOException or IllegalArgumentException
+ * if the staging operations fail
+ */
+ public boolean setupAjcRun(Sandbox sandbox, Validator validator) {
+ LangUtil.throwIaxIfNull(validator, "validator");
+ if (!validator.nullcheck(sandbox, "sandbox")
+ || !validator.nullcheck(spec, "spec")
+ || !validator.nullcheck(spec.tag, "fileSuffix")) {
+ return false;
+ }
+ File srcDir = sandbox.getTestBaseSrcDir(this);
+ File destDir = sandbox.stagingDir;
+ if (!validator.canReadDir(srcDir, "testBaseSrcDir")
+ || !validator.canReadDir(destDir, "stagingDir")) {
+ return false;
+ }
+
+ this.sandbox = sandbox;
+ return doStaging(validator);
+ }
+
+ /**
+ * Handle copying and deleting of files per tag.
+ * This returns false unless some file was copied or deleted successfully
+ * and there were no failures copying or deleting files.
+ * @return true if staging completed successfully
+ */
+ boolean doStaging(final Validator validator) {
+ boolean result = false;
+ try {
+ //ArrayList changed = new ArrayList();
+ final String toSuffix = ".java";
+ final String fromSuffix = "." + spec.tag + toSuffix;
+ // copy our tagged generation of files to the staging directory,
+ // deleting any with ChangedFilesCollector.DELETE_SUFFIX
+ class intHolder {
+ int numCopies;
+ int numDeletes;
+ int numFails;
+ }
+ final intHolder holder = new intHolder();
+ FileFilter deleteOrCount = new FileFilter() {
+ final String clip = ".delete" + toSuffix;
+ /** do copy unless file should be deleted */
+ public boolean accept(File file) {
+ boolean doCopy = true;
+ String path = file.getAbsolutePath();
+ if (!path.endsWith(clip)) {
+ holder.numCopies++;
+ } else {
+ doCopy = false;
+ path = path.substring(0, path.length()-clip.length()) + toSuffix;
+ File toDelete = new File(path);
+ if (toDelete.delete()) {
+ holder.numDeletes++;
+ } else {
+ validator.fail("unable to delete file: " + path);
+ holder.numFails++;
+ }
+ }
+ return doCopy;
+ }
+
+ };
+ File srcDir = sandbox.getTestBaseSrcDir(this);
+ File destDir = sandbox.stagingDir;
+ FileUtil.copyDir(srcDir, destDir, fromSuffix, toSuffix, deleteOrCount);
+ if ((0 == holder.numCopies) && (0 == holder.numDeletes)) {
+ validator.fail("no files changed??");
+ } else {
+ result = (0 == holder.numFails);
+ }
+ } catch (NullPointerException npe) {
+ validator.fail("staging - input", npe);
+ } catch (IOException e) {
+ validator.fail("staging - operations", e);
+ }
+ return result;
+ }
+
+ /**
+ * @see org.aspectj.testing.run.IRun#run(IRunStatus)
+ */
+ public boolean run(IRunStatus status) {
+
+ ICommand compiler = sandbox.getCommand(this);
+ if (null == compiler) {
+ MessageUtil.abort(status, "null compiler");
+ }
+
+// // This is a list of expected classes (in File-normal form
+// // relative to base class/src dir, without .class suffix
+// // -- like "org/aspectj/tools/ajc/Main")
+// // A preliminary list is generated in doStaging.
+// ArrayList expectedClasses = doStaging(status);
+// if (null == expectedClasses) {
+// return false;
+// }
+//
+// // now add any (additional) expected-class entries listed in the spec
+// // normalize to a similar file path (and do info messages for redundancies).
+//
+// List alsoChanged = spec.getPathsAsFile(sandbox.stagingDir);
+// for (Iterator iter = alsoChanged.iterator(); iter.hasNext();) {
+// File f = (File) iter.next();
+//
+// if (expectedClasses.contains(f)) {
+// // XXX remove old comment changed.contains() works b/c getPathsAsFile producing both File
+// // normalizes the paths, and File.equals(..) compares these lexically
+// String s = "specification of changed file redundant with tagged file: ";
+// MessageUtil.info(status, s + f);
+// } else {
+// expectedClasses.add(f);
+// }
+// }
+//
+// // now can create handler, use it for reporting
+// List errors = spec.getMessages(IMessage.ERROR);
+// List warnings = spec.getMessages(IMessage.WARNING);
+// AjcMessageHandler handler = new AjcMessageHandler(errors, warnings, expectedClasses);
+
+ // same DirChanges handling for JavaRun, CompilerRun, IncCompilerRun
+ // XXX around advice or template method/class
+ DirChanges dirChanges = null;
+ if (!LangUtil.isEmpty(spec.dirChanges)) {
+ LangUtil.throwIaxIfFalse(1 == spec.dirChanges.size(), "expecting only 1 dirChanges");
+ dirChanges = new DirChanges((DirChanges.Spec) spec.dirChanges.get(0));
+ if (!dirChanges.start(status, sandbox.classesDir)) {
+ return false; // setup failed
+ }
+ }
+ List errors = spec.getMessages(IMessage.ERROR);
+ List warnings = spec.getMessages(IMessage.WARNING);
+ List expectRecompiled = Collections.EMPTY_LIST;
+ AjcMessageHandler handler = new AjcMessageHandler(errors, warnings, expectRecompiled);
+ boolean handlerResult = false;
+ boolean commandResult = false;
+ boolean result = false;
+ boolean report = false;
+ try {
+ handler.init();
+ final long startTime = System.currentTimeMillis();
+ commandResult = compiler.repeatCommand(handler);
+ // XXX disabled LangUtil.throwIaxIfNotAllAssignable(actualRecompiled, File.class, "recompiled");
+ report = true;
+ // handler does not verify sandbox...
+ handlerResult = handler.passed();
+ if (!handlerResult) {
+ result = false;
+ } else {
+ result = (commandResult == handler.expectingCommandTrue());
+ if (! result) {
+ String m = commandResult
+ ? "incremental compile command did not return false as expected"
+ : "incremental compile command returned false unexpectedly";
+ MessageUtil.fail(status, m);
+ } else if (null != dirChanges) {
+ result = dirChanges.end(status, sandbox.testBaseDir);
+ }
+ }
+ } finally {
+ if (!result || spec.runtime.isVerbose()) { // more debugging context in case of failure
+ MessageUtil.info(handler, "spec: " + spec.toLongString());
+ MessageUtil.info(handler, "sandbox: " + sandbox);
+ String[] classes = FileUtil.listFiles(sandbox.classesDir);
+ MessageUtil.info(handler, "sandbox.classes: " + Arrays.asList(classes));
+ }
+ // XXX weak - actual messages not reported in real-time, no fast-fail
+ if (report) {
+ handler.report(status);
+ }
+ }
+ return result;
+ }
+
+ private boolean hasFile(ArrayList changed, File f) {
+ return changed.contains(f); // d
+ }
+
+
+ public String toString() {
+ return "" + spec;
+ // return "IncCompilerRun(" + spec + ")"; // XXX
+ }
+
+// /** @see IXmlWritable#writeXml(XMLWriter) */
+// public void writeXml(XMLWriter out) {
+// String elementName = "inc-compile";
+// String tagAttr = out.makeAttribute("tag", spec.tag);
+// List messages = spec.getMessages();
+// int nMessages = messages.size();
+// if (0 == nMessages) {
+// out.printElement(elementName, tagAttr);
+// } else {
+// out.startElement(elementName, tagAttr, true);
+// SoftMessage.writeXml(out, messages);
+// out.endElement(elementName);
+// }
+// }
+//
+ /**
+ * initializer/factory for IncCompilerRun.
+ */
+ public static class Spec extends AbstractRunSpec {
+ public static final String XMLNAME = "inc-compile";
+
+ protected ArrayList classesAdded;
+ protected ArrayList classesRemoved;
+ protected ArrayList classesUpdated;
+
+ /**
+ * skip description, skip sourceLocation,
+ * do keywords, skip options, do paths as classes, do comment,
+ * do dirChanges, do messages but skip children.
+ */
+ private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT,
+ "", "", null, "", "classes", null, false, false, true);
+
+ /** identifies files this run owns, so {name}.{tag}.java maps to {name}.java */
+ String tag;
+
+ public Spec() {
+ super(XMLNAME);
+ setStaging(true);
+ classesAdded = new ArrayList();
+ classesRemoved = new ArrayList();
+ classesUpdated = new ArrayList();
+ }
+
+ public void setTag(String input) {
+ tag = input;
+ }
+ public String toString() {
+ return "IncCompile.Spec(" + tag + ", " + super.toString() + ")";
+ }
+
+ /** override to set dirToken to Sandbox.CLASSES and default suffix to ".class" */
+ public void addDirChanges(DirChanges.Spec spec) { // XXX copy/paste of CompilerRun.Spec...
+ if (null == spec) {
+ return;
+ }
+ spec.setDirToken(Sandbox.CLASSES_DIR);
+ spec.setDefaultSuffix(".class");
+ super.addDirChanges(spec);
+ }
+
+ /** @return a IncCompilerRun with this as spec if setup completes successfully. */
+ public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) {
+ IncCompilerRun run = new IncCompilerRun(this);
+ if (run.setupAjcRun(sandbox, validator)) {
+ // XXX need name
+ return new WrappedRunIterator(this, run);
+ }
+ return null;
+ }
+
+ /**
+ * Write this out as a compile element as defined in
+ * AjcSpecXmlReader.DOCTYPE.
+ * @see AjcSpecXmlReader#DOCTYPE
+ * @see IXmlWritable#writeXml(XMLWriter)
+ */
+ public void writeXml(XMLWriter out) {
+ String attr = out.makeAttribute("tag", tag);
+ out.startElement(xmlElementName, attr, false);
+ super.writeAttributes(out);
+ out.endAttributes();
+ if (!LangUtil.isEmpty(dirChanges)) {
+ DirChanges.Spec.writeXml(out, dirChanges);
+ }
+ List messages = getMessages();
+ if (0 < messages.size()) {
+ SoftMessage.writeXml(out, messages);
+ }
+ out.endElement(xmlElementName);
+ }
+
+ public void setClassesAdded(String items) {
+ addItems(classesAdded, items);
+ }
+
+ public void setClassesUpdated(String items) {
+ addItems(classesUpdated, items);
+ }
+
+ public void setClassesRemoved(String items) {
+ addItems(classesRemoved, items);
+ }
+
+ private void addItems(ArrayList list, String items) {
+ if (null != items) {
+ String[] classes = XMLWriter.unflattenList(items);
+ if (!LangUtil.isEmpty(classes)) {
+ for (int i = 0; i < classes.length; i++) {
+ if (!LangUtil.isEmpty(classes[i])) {
+ list.add(classes[i]);
+ }
+ }
+ }
+ }
+ }
+ } // class IncCompilerRun.Spec
+}
+// // XXX replaced with method-local class - revisit if useful
+//
+// /**
+// * This class collects the list of all changed files and
+// * deletes the corresponding file for those prefixed "delete."
+// */
+// static class ChangedFilesCollector implements FileFilter {
+// static final String DELETE_SUFFIX = ".delete.java";
+// static final String REPLACE_SUFFIX = ".java";
+// final ArrayList changed;
+// final Validator validator;
+// /** need this to generate paths by clipping */
+// final File destDir;
+//
+// /** @param changed the sink for all files changed (full paths) */
+// public ChangedFilesCollector(ArrayList changed, File destDir, Validator validator) {
+// LangUtil.throwIaxIfNull(validator, "ChangedFilesCollector - handler");
+// this.changed = changed;
+// this.validator = validator;
+// this.destDir = destDir;
+// }
+//
+// /**
+// * This converts the input File to normal String path form
+// * (without any source suffix) and adds it to the list changed.
+// * If the name of the file is suffixed ".delete..", then
+// * delete the corresponding file, and return false (no copy).
+// * Return true otherwise (copy file).
+// * @see java.io.FileFilter#accept(File)
+// */
+// public boolean accept(File file) {
+// final String aname = file.getAbsolutePath();
+// String name = file.getName();
+// boolean doCopy = true;
+// boolean failed = false;
+// if (name.endsWith(DELETE_SUFFIX)) {
+// name = name.substring(0,name.length()-DELETE_SUFFIX.length());
+// file = file.getParentFile();
+// file = new File(file, name + REPLACE_SUFFIX);
+// if (!file.canWrite()) {
+// validator.fail("file to delete is not writable: " + file);
+// failed = true;
+// } else if (!file.delete()) {
+// validator.fail("unable to delete file: " + file);
+// failed = true;
+// }
+// doCopy = false;
+// }
+// if (!failed && doCopy) {
+// int clip = FileUtil.sourceSuffixLength(file);
+// if (-1 != clip) {
+// name.substring(0, name.length()-clip);
+// }
+// if (null != destDir) {
+// String path = destDir.getPath();
+// if (!LangUtil.isEmpty(path)) {
+// // XXX incomplete
+// if (name.startsWith(path)) {
+// } else {
+// int loc = name.lastIndexOf(path);
+// if (-1 == loc) { // sigh
+//
+// } else {
+//
+// }
+// }
+// }
+// }
+// name = FileUtil.weakNormalize(name);
+// changed.add(file);
+// }
+// return doCopy;
+// }
+// };
+
diff --git a/testing/src/org/aspectj/testing/harness/bridge/JavaRun.java b/testing/src/org/aspectj/testing/harness/bridge/JavaRun.java
new file mode 100644
index 000000000..5d7dd7010
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/JavaRun.java
@@ -0,0 +1,298 @@
+/* *******************************************************************
+ * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.testing.Tester;
+import org.aspectj.testing.run.IRunIterator;
+import org.aspectj.testing.run.IRunStatus;
+import org.aspectj.testing.run.WrappedRunIterator;
+import org.aspectj.testing.util.TestClassLoader;
+import org.aspectj.testing.xml.SoftMessage;
+import org.aspectj.testing.xml.XMLWriter;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Run a class in this VM using reflection.
+ */
+public class JavaRun implements IAjcRun {
+ Spec spec;
+ private Sandbox sandbox;
+
+ /** programmatic initialization per spec */
+ public JavaRun(Spec spec) {
+ this.spec = spec;
+ }
+ // XXX init(Spec)
+
+ /**
+ * This checks the spec for a class name
+ * and checks the sandbox for a readable test source directory,
+ * a writable run dir, and (non-null, possibly-empty) lists
+ * of readable classpath dirs and jars.
+ * @return true if all checks pass
+ * @see org.aspectj.testing.harness.bridge.AjcTest.IAjcRun#setup(File, File)
+ */
+ public boolean setupAjcRun(Sandbox sandbox, Validator validator) {
+ this.sandbox = sandbox;
+ return (validator.nullcheck(spec.className, "class name")
+ && validator.nullcheck(sandbox, "sandbox")
+ && validator.canReadDir(sandbox.getTestBaseSrcDir(this), "testBaseSrc dir")
+ && validator.canWriteDir(sandbox.runDir, "run dir")
+ && validator.canReadFiles(sandbox.getClasspathJars(true, this), "classpath jars")
+ && validator.canReadDirs(sandbox.getClasspathDirectories(true, this), "classpath dirs")
+ );
+
+ }
+
+ /** caller must record any exceptions */
+ public boolean run(IRunStatus status)
+ throws IllegalAccessException,
+ InvocationTargetException,
+ ClassNotFoundException,
+ NoSuchMethodException {
+
+ boolean completedNormally = false;
+ Class targetClass = null;
+ if (!LangUtil.isEmpty(spec.dirChanges)) {
+ MessageUtil.info(status, "XXX dirChanges not implemented in JavaRun");
+ }
+ try {
+ final boolean readable = true;
+ File[] libs = sandbox.getClasspathJars(readable, this);
+ URL[] urls = FileUtil.getFileURLs(libs);
+ File[] dirs = sandbox.getClasspathDirectories(readable, this);
+ ClassLoader loader = new TestClassLoader(urls, dirs);
+ // make the following load test optional
+ // Class testAspect = loader.loadClass("org.aspectj.lang.JoinPoint");
+ targetClass = loader.loadClass(spec.className);
+ Method main = targetClass.getMethod("main", Globals.MAIN_PARM_TYPES);
+ setupTester(sandbox.getTestBaseSrcDir(this), loader, status);
+ main.invoke(null, new Object[] { spec.getOptionsArray() });
+ completedNormally = true;
+ } catch (ClassNotFoundException e) {
+ String[] classes = FileUtil.listFiles(sandbox.classesDir);
+ MessageUtil.info(status, "sandbox.classes: " + Arrays.asList(classes));
+ MessageUtil.fail(status, null, e);
+ } finally {
+ if (!completedNormally) {
+ MessageUtil.info(status, spec.toLongString());
+ MessageUtil.info(status, "targetClass: " + targetClass);
+ MessageUtil.info(status, "" + sandbox);
+ }
+ }
+ return completedNormally;
+ }
+
+ /**
+ * Clear (static) testing state and setup base directory,
+ * unless spec.skipTesting.
+ * @return null if successful, error message otherwise
+ */
+ protected void setupTester(File baseDir, ClassLoader loader, IMessageHandler handler) {
+ if (null == loader) {
+ setupTester(baseDir, handler);
+ return;
+ }
+ File baseDirSet = null;
+ try {
+ if (!spec.skipTester) {
+ Class tc = loader.loadClass("org.aspectj.testing.Tester");
+ // Tester.clear();
+ Method m = tc.getMethod("clear", new Class[0]);
+ m.invoke(null, new Object[0]);
+ // Tester.setMessageHandler(handler);
+ m = tc.getMethod("setMessageHandler", new Class[] {IMessageHandler.class});
+ m.invoke(null, new Object[] { handler});
+
+ //Tester.setBASEDIR(baseDir);
+ m = tc.getMethod("setBASEDIR", new Class[] {File.class});
+ m.invoke(null, new Object[] { baseDir});
+
+ //baseDirSet = Tester.getBASEDIR();
+ m = tc.getMethod("getBASEDIR", new Class[0]);
+ baseDirSet = (File) m.invoke(null, new Object[0]);
+
+ if (!baseDirSet.equals(baseDir)) {
+ String l = "AjcScript.setupTester() setting "
+ + baseDir + " returned " + baseDirSet;
+ MessageUtil.debug(handler, l);
+ }
+ }
+ } catch (Throwable t) {
+ MessageUtil.abort(handler, "baseDir=" + baseDir, t);
+ }
+ }
+
+ /**
+ * Clear (static) testing state and setup base directory,
+ * unless spec.skipTesting.
+ * This implementation assumes that Tester is defined for the
+ * same class loader as this class.
+ * @return null if successful, error message otherwise
+ */
+ protected void setupTester(File baseDir, IMessageHandler handler) {
+ File baseDirSet = null;
+ try {
+ if (!spec.skipTester) {
+ Tester.clear();
+ Tester.setMessageHandler(handler);
+ Tester.setBASEDIR(baseDir);
+ baseDirSet = Tester.getBASEDIR();
+ if (!baseDirSet.equals(baseDir)) {
+ String l = "AjcScript.setupTester() setting "
+ + baseDir + " returned " + baseDirSet;
+ MessageUtil.debug(handler, l);
+ }
+ }
+ } catch (Throwable t) {
+ MessageUtil.abort(handler, "baseDir=" + baseDir, t);
+ }
+ }
+ public String toString() {
+ return "JavaRun(" + spec + ")";
+ }
+
+ /**
+ * Initializer/factory for JavaRun.
+ * The classpath is not here but precalculated in the Sandbox. XXX libs?
+ */
+ public static class Spec extends AbstractRunSpec {
+ public static final String XMLNAME = "run";
+ /**
+ * skip description, skip sourceLocation,
+ * do keywords, do options, skip paths, do comment,
+ * do dirChanges, do messages but skip children.
+ */
+ private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT,
+ "", "", null, null, "", null, false, false, true);
+
+ /** fully-qualified name of the class to run */
+ protected String className;
+
+ /** minimum required version of Java, if any */
+ protected String javaVersion;
+
+ /** if true, skip Tester setup (e.g., if Tester n/a) */
+ protected boolean skipTester;
+
+ public Spec() {
+ super(XMLNAME);
+ setXMLNames(NAMES);
+ }
+
+ /**
+ * @param version "1.1", "1.2", "1.3", "1.4"
+ * @throws IllegalArgumentException if version is not recognized
+ */
+ public void setJavaVersion(String version) {
+ LangUtil.supportsJava(version);
+ this.javaVersion = version;
+ }
+
+ /** @className fully-qualified name of the class to run */
+ public void setClassName(String className) {
+ this.className = className;
+ }
+
+ /** @param skip if true, then do not set up Tester */
+ public void setSkipTester(boolean skip) {
+ skipTester = skip;
+ }
+
+ /** override to set dirToken to Sandbox.RUN_DIR */
+ public void addDirChanges(DirChanges.Spec spec) {
+ if (null == spec) {
+ return;
+ }
+ spec.setDirToken(Sandbox.RUN_DIR);
+ super.addDirChanges(spec);
+ }
+
+ /** @return a JavaRun with this as spec if setup completes successfully. */
+ public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) {
+ JavaRun run = new JavaRun(this);
+ if (run.setupAjcRun(sandbox, validator)) {
+ // XXX need name for JavaRun
+ return new WrappedRunIterator(this, run);
+ }
+ return null;
+ }
+
+ /**
+ * Write this out as a run element as defined in
+ * AjcSpecXmlReader.DOCTYPE.
+ * @see AjcSpecXmlReader#DOCTYPE
+ * @see IXmlWritable#writeXml(XMLWriter)
+ */
+ public void writeXml(XMLWriter out) {
+ String attr = out.makeAttribute("class", className);
+ out.startElement(xmlElementName, attr, false);
+ if (skipTester) {
+ out.printAttribute("skipTester", "true");
+ }
+ if (null != javaVersion) {
+ out.printAttribute("vm", javaVersion);
+ }
+ super.writeAttributes(out);
+ out.endAttributes();
+ if (!LangUtil.isEmpty(dirChanges)) {
+ DirChanges.Spec.writeXml(out, dirChanges);
+ }
+ List messages = getMessages();
+ if (0 < messages.size()) {
+ SoftMessage.writeXml(out, messages);
+ }
+ out.endElement(xmlElementName);
+ }
+ public String toLongString() {
+ return toString() + "[" + super.toLongString() + "]";
+ }
+
+ public String toString() {
+ if (skipTester) {
+ return "JavaRun(" + className + ", skipTester)";
+ } else {
+ return "JavaRun(" + className + ")";
+ }
+ }
+
+ /**
+ * This implementation skips if:
+ * <ul>
+ * <li>current VM is not at least any specified javaVersion </li>
+ * </ul>
+ * @return false if this wants to be skipped, true otherwise
+ */
+ protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) {
+ if (!super.doAdoptParentValues(parentRuntime, handler)) {
+ return false;
+ }
+ if ((null != javaVersion) && (!LangUtil.supportsJava(javaVersion))) {
+ skipMessage(handler, "requires Java version " + javaVersion);
+ return false;
+ }
+ return true;
+ }
+ }
+}
+
diff --git a/testing/src/org/aspectj/testing/harness/bridge/RunSpecIterator.java b/testing/src/org/aspectj/testing/harness/bridge/RunSpecIterator.java
new file mode 100644
index 000000000..ebc4c14c6
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/RunSpecIterator.java
@@ -0,0 +1,256 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.Message;
+import org.aspectj.testing.run.IRun;
+import org.aspectj.testing.run.IRunIterator;
+import org.aspectj.testing.run.Runner;
+import org.aspectj.testing.run.WrappedRunIterator;
+import org.aspectj.util.LangUtil;
+
+import java.util.ArrayList;
+
+
+/**
+ * This wraps an AbstractRunSpec, which has children that
+ * return IRunIterator, the results of which we return
+ * from nextRun(..)
+ * We extract global options from the AbstractRunSpec options
+ * and set the global options in the AbstractRunSpec,
+ * which is responsible for setting them in any children
+ * during makeRun(..).
+ */
+public class RunSpecIterator implements IRunIterator {
+ /*
+ * When constructed, this gets its own spec
+ * and a sandbox to be used for making all children.
+ * In nextRun() this uses the spec's child specs
+ * and the sandbox to create the next child run iterator.
+ * This returns all the run provided by that child iterator
+ * before going to the next child.
+ */
+
+ /** spec for this test */
+ public final AbstractRunSpec spec; // XXX reconsider public after debugging
+
+ /** current sandbox by default shared by all children */
+ Sandbox sandbox;
+
+ /** keep our copy to avoid recopying */
+ ArrayList childSpecs;
+
+ /** index into child specs of next run */
+ int specIndex;
+
+ /** child creation until the start of each run */
+ final Validator validator;
+
+ /** current child iterator */
+ IRunIterator childIterator;
+
+ final boolean haltOnFailure;
+
+ private int numIncomplete;
+
+ private final IMessage.Kind failureKind;
+
+ private boolean didCleanup;
+
+ /**
+ * Create a RunSpecIterator.
+ * Failure messages are of type IMessage.ABORT if abortOnFailure is true,
+ * or IMessage.ERROR otherwise.
+ * @param spec the AbstractRunSpec whose children we iterate - not null
+ * @param sandbox the default Sandbox to use for children to make runs - may be null
+ * @param haltOnFailure if true, stop after any failure in providing runs
+ */
+ public RunSpecIterator(
+ AbstractRunSpec spec,
+ Sandbox sandbox,
+ Validator validator,
+ boolean haltOnFailure) {
+ this(spec, sandbox, validator, haltOnFailure,
+ (haltOnFailure ? IMessage.ABORT : IMessage.ERROR));
+ }
+
+ /**
+ * Create a RunSpecIterator, specifying any failure message kind.
+ * @param spec the AbstractRunSpec whose children we iterate - not null
+ * @param sandbox the default Sandbox to use for children to make runs - may be null
+ * @param haltOnFailure if true, stop after any failure in providing runs
+ * @param failureKind the IMessage.Kind for any failure messages - if null, no messages sent
+ */
+ public RunSpecIterator(
+ AbstractRunSpec spec,
+ Sandbox sandbox,
+ Validator validator,
+ boolean haltOnFailure,
+ IMessage.Kind failureKind) {
+ LangUtil.throwIaxIfNull(spec, "spec");
+ LangUtil.throwIaxIfNull(sandbox, "sandbox");
+ LangUtil.throwIaxIfNull(validator, "validator");
+ this.sandbox = sandbox;
+ this.spec = spec;
+ this.validator = validator;
+ this.haltOnFailure = haltOnFailure;
+ this.failureKind = failureKind;
+ reset();
+ }
+
+ /**
+ * @return value set on construction for abortOnError
+ * @see org.aspectj.testing.run.IRunIterator#abortOnFailure()
+ */
+ public boolean abortOnFailure() {
+ return haltOnFailure;
+ }
+
+ /** reset to start at the beginning of the child specs. */
+ public void reset() {
+ specIndex = 0;
+ childSpecs = spec.getWorkingChildren();
+ childIterator = null;
+ numIncomplete = 0;
+ }
+
+ /** @return int number of child run attempts that did not produce IRun */
+ public int getNumIncomplete() {
+ return numIncomplete;
+ }
+
+ /**
+ * @see org.aspectj.testing.run.IRunIterator#hasNextRun()
+ */
+ public boolean hasNextRun() {
+ return ((specIndex < childSpecs.size())
+ || ((null != childIterator)
+ && childIterator.hasNextRun()));
+ }
+
+ /**
+ * Get the next child IRunIterator as an IRun.
+ * In case of failure to get the next child,
+ * numIncomplete is incremented, and
+ * a message of type failureKind is passed to the handler
+ * (if failureKind was defined in the contructor).
+ * @return next child IRunIterator wrapped as an IRun
+ */
+ public IRun nextRun(final IMessageHandler handler, Runner runner) {
+ validator.pushHandler(handler);
+ try {
+ IRun result = null;
+ IRunSpec childSpec = null;
+ String error = null;
+ String specLabel = "getting run for child of \"" + spec + "\" ";
+ while ((null == result) && hasNextRun() && (null == error)) {
+ if (null == childIterator) {
+ childSpec = (IRunSpec) childSpecs.get(specIndex++);
+ if (null == childSpec) {
+ error = "unexpected - no more child specs at " + --specIndex;
+ } else {
+ Sandbox sandbox = makeSandbox(childSpec, validator);
+ if (null == sandbox) {
+ error = "unable to make sandbox for \"" + childSpec + "\"";
+ childIterator = null;
+ } else {
+ IRunIterator iter = childSpec.makeRunIterator(sandbox, validator);
+ if (null == iter) {
+ // client should read reason why from validator
+ error = "child \"" + childSpec + "\".makeRunIterator(..) returned null";
+ } else {
+ // hoist: result not wrapped but single IRun
+ if ((iter instanceof WrappedRunIterator)) {
+ if (!iter.hasNextRun()) {
+ error = "child \"" + childSpec + "\".hasNextRun()"
+ + " is not true - should be exactly one run";
+ } else {
+ result = iter.nextRun(handler, runner);
+ if (null == result) {
+ error = "child \"" + childSpec + "\".nextRun()"
+ + " returned null - should be exactly one run";
+ } else {
+ childIterator = null;
+ return result;
+ }
+ }
+ } else {
+ childIterator = iter;
+ }
+ }
+ }
+ }
+ }
+ if (null != childIterator) {
+ result = runner.wrap(childIterator, null);
+ childIterator = null;
+ } else if (null != error) {
+ numIncomplete++;
+ if (null != failureKind) {
+ handler.handleMessage(new Message(specLabel + error, failureKind, null, null));
+ }
+ if (!haltOnFailure) {
+ error = null;
+ } else if (result != null) {
+ result = null; // do not return result if halting due to failure
+ }
+ }
+ }
+ return result;
+ } finally {
+ validator.popHandler(handler);
+ }
+ }
+
+ /**
+ * @see org.aspectj.testing.run.IRunIterator#iterationCompleted()
+ */
+ public void iterationCompleted() {
+ }
+
+ public String toString() {
+ return "" + spec;
+ //return "RunSpecIterator(" + specIndex + ", " + spec + ")" ;
+ }
+
+ /*
+ * Subclasses may:
+ * - set the sandbox on construction
+ * - lazily set it on first use
+ * - set it for each child
+ */
+
+ /**
+ * Create the sandbox used for each child.
+ * This implementation always uses the sandbox set on construction.
+ * Subclasses may decide to create one sandbox per child iterator.
+ */
+ protected Sandbox makeSandbox(IRunSpec child, Validator validator) {
+ return getSandbox();
+ }
+
+ /** Get the sandbox currently in use */
+ protected Sandbox getSandbox() {
+ return sandbox;
+ }
+
+ /** Set the sandbox currently in use */
+ protected void setSandbox(Sandbox sandbox) {
+ LangUtil.throwIaxIfNull(sandbox, "sandbox");
+ this.sandbox = sandbox;
+ }
+
+}
diff --git a/testing/src/org/aspectj/testing/harness/bridge/Sandbox.java b/testing/src/org/aspectj/testing/harness/bridge/Sandbox.java
new file mode 100644
index 000000000..6f4984bac
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/Sandbox.java
@@ -0,0 +1,386 @@
+/* *******************************************************************
+ * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.bridge.ICommand;
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.testing.util.Diffs;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+import java.io.File;
+import java.util.ArrayList;
+
+/**
+ * A sandbox holds state shared by AjcTest sub-runs,
+ * mostly directories relevant to testing.
+ * It permits a limited amount of coordination and
+ * setup/cleanup operations (todo XXX).
+ * <p>
+ * AjcTest creates the Sandbox and initializes the final fields.
+ * To coordinate with each other, run components may set and get values,
+ * with the sources running first and the sinks second.
+ * To make the interactions clear
+ * (and to avoid accidentally violating these semantics),
+ * setters/getters for a coordinated property are constrained two ways:
+ * <li>Both have an extra (typed) "caller" parameter which must not
+ * be null, authenticating that the caller is known & valid.</li>
+ * <li>A getter throws IllegalStateException if called before the setter</li>
+ * <li>A setter throws IllegalStateException if called after the getter<li>
+ * XXX subclass more general sandbox?
+ */
+public class Sandbox {
+ /** classes directory token for DirChanges.Spec */
+ public static final String RUN_DIR = "run";
+
+ /** run directory token for DirChanges.Spec */
+ public static final String CLASSES_DIR = "classes";
+
+ private static boolean canRead(File dir) {
+ return ((null != dir) && dir.isDirectory() && dir.canRead());
+ }
+
+ private static boolean canWrite(File dir) {
+ return ((null != dir) && dir.isDirectory() && dir.canWrite());
+ }
+
+ private static void iaxWrite(File dir, String label) {
+ if (!canWrite(dir)) {
+ throw new IllegalArgumentException(label + " - " + dir);
+ }
+ }
+
+ private static void iaxRead(File dir, String label) {
+ if (!canRead(dir)) {
+ throw new IllegalArgumentException(label + " - " + dir);
+ }
+ }
+
+ /** @throws IllegalStateException(message) if test */
+ private static void assertState(boolean test, String message) {
+ if (!test) {
+ throw new IllegalStateException(message);
+ }
+ }
+
+ /**
+ * The (read-only) base of the test sources (which may or may not
+ * be the base of the java sources)
+ */
+ public final File testBaseDir;
+
+ /** the parent of a temporary workspace, probably includes some others */
+ public final File sandboxDir;
+
+ /** a shared working dir */
+ public final File workingDir;
+
+ /** a shared classes dir */
+ public final File classesDir;
+
+ /** a run dir (which will be ignored in non-forking runs) */
+ public final File runDir;
+
+ /** staging directory for IAjcRun requiring files be copied, deleted, etc. */
+ public final File stagingDir;
+
+ /**
+ * This manages creation and deletion of temporary directories.
+ * We hold a reference so that our clients can signal whether
+ * this should be deleted.
+ */
+ private final Validator validator; // XXX required after completing tests?
+
+ /** original base of the original java sources, set by CompileRun.setup(..) */
+ private File testBaseSrcDir;
+
+ /** directories and libraries on the classpath, set by CompileRun.setup(..) */
+ private File[] classpath;
+
+ /** track whether classpath getter ran */
+ private boolean gotClasspath;
+
+ /** command shared between runs using sandbox - i.e., compiler */
+ private ICommand command;
+
+ /** track whether command getter ran */
+ private boolean gotCommand;
+
+ /** cache results of rendering final fields */
+ private transient String toStringLeader;
+
+ /** @throws IllegalArgumentException unless validator validates
+ * testBaseDir as readable
+ */
+ public Sandbox(File testBaseDir, Validator validator) {
+ LangUtil.throwIaxIfNull(validator, "validator");
+ this.validator = validator;
+ Sandbox.iaxRead(testBaseDir, "testBaseDir");
+ this.testBaseDir = testBaseDir;
+
+ sandboxDir = FileUtil.getTempDir("Sandbox");
+ Sandbox.iaxWrite(sandboxDir, "sandboxDir"); // XXX not really iax
+
+ workingDir = FileUtil.makeNewChildDir(sandboxDir, "workingDir");
+ Sandbox.iaxWrite(workingDir, "workingDir");
+
+ classesDir = FileUtil.makeNewChildDir(sandboxDir, "classes");
+ Sandbox.iaxWrite(classesDir, "classesDir");
+
+ runDir = FileUtil.makeNewChildDir(sandboxDir, "run");
+ Sandbox.iaxWrite(runDir, "runDir");
+
+ stagingDir = FileUtil.makeNewChildDir(sandboxDir, "staging");
+ Sandbox.iaxWrite(stagingDir, "stagingDir");
+
+ validator.registerSandbox(this);
+ }
+
+ private String getToStringLeader() {
+ if (null == toStringLeader) {
+ toStringLeader = "Sandbox(" + sandboxDir.getName()
+ + ", " + testBaseSrcDir.getName();
+ }
+ return toStringLeader;
+ }
+
+ /** @return "Sandbox(sandbox, src, classes)" with names only */
+ public String toString() {
+ return getToStringLeader() + ", " + classesDir.getName() + ")";
+ }
+
+ /** @return "Sandbox(sandbox, src, classes)" with paths */
+ public String toLongString() {
+ return getToStringLeader() + ", " + classesDir.getPath()
+ + (null == command ? ", (null command)" : ", " + command) + ")";
+ }
+
+ void setCommand(ICommand command, CompilerRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ LangUtil.throwIaxIfNull(command, "command");
+ LangUtil.throwIaxIfFalse(!gotCommand, "no command");
+ this.command = command;
+ }
+
+ /** When test is completed, clear the compiler to avoid memory leaks */
+ void clearCommand(AjcTest caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ if (null != command) {
+ command = null;
+ }
+ }
+
+// /**
+// * Populate the staging directory by copying any files in the
+// * source directory ending with fromSuffix
+// * to the staging directory, after renaming them with toSuffix.
+// * If the source file name starts with "delete", then the
+// * corresponding file in the staging directory is deleting.
+// * @return a String[] of the files copied or deleted
+// * (path after suffix changes and relative to staging dir)
+// * @throws Error if no File using fromSuffix are found
+// */
+// String[] populateStagingDir(String fromSuffix, String toSuffix, IAjcRun caller) {
+// LangUtil.throwIaxIfNull(fromSuffix, "fromSuffix");
+// LangUtil.throwIaxIfNull(toSuffix, "toSuffix");
+// LangUtil.throwIaxIfNull(caller, "caller");
+//
+// ArrayList result = new ArrayList();
+// FileUtil.copyDir(
+// srcBase,
+// targetSrc,
+// fromSuffix,
+// toSuffix,
+// collector);
+//
+// final String canonicalFrom = srcBase.getCanonicalPath();
+// final Definition[] defs = getDefinitions(srcBase);
+// if ((null == defs) || (defs.length < 9)) {
+// throw new Error("did not get definitions");
+// }
+// MessageHandler compilerMessages = new MessageHandler();
+// StringBuffer commandLine = new StringBuffer();
+// for (int i = 1; result && (i < 10); i++) {
+// String fromSuffix = "." + i + "0.java";
+// // copy files, collecting as we go...
+// files.clear();
+// if (0 == files.size()) { // XXX detect incomplete?
+// break;
+// }
+//
+//
+// return (String[]) result.toArray(new String[0]);
+// }
+
+ // XXX move to more general in FileUtil
+ void reportClassDiffs(
+ final IMessageHandler handler,
+ IncCompilerRun caller,
+ long classesDirStartTime,
+ String[] expectedSources) {
+ LangUtil.throwIaxIfFalse(0 < classesDirStartTime, "0 >= " + classesDirStartTime);
+ boolean acceptPrefixes = true;
+ Diffs diffs = org.aspectj.testing.util.FileUtil.dirDiffs(
+ "classes",
+ classesDir,
+ classesDirStartTime,
+ ".class",
+ expectedSources,
+ acceptPrefixes);
+ diffs.report(handler, IMessage.ERROR);
+ }
+
+// // XXX replace with IMessage-based implementation
+// // XXX move to more general in FileUtil
+// void reportClassesDirDiffs(final IMessageHandler handler, IncCompilerRun caller,
+// String[] expectedSources) {
+// // normalize sources to ignore
+// final ArrayList sources = new ArrayList();
+// if (!LangUtil.isEmpty(expectedSources)) {
+// for (int i = 0; i < expectedSources.length; i++) {
+// String srcPath = expectedSources[i];
+// int clip = FileUtil.sourceSuffixLength(srcPath);
+// if (0 != clip) {
+// srcPath = srcPath.substring(0, srcPath.length() - clip);
+// sources.add(FileUtil.weakNormalize(srcPath));
+// } else if (srcPath.endsWith(".class")) {
+// srcPath = srcPath.substring(0, srcPath.length() - 6);
+// sources.add(FileUtil.weakNormalize(srcPath));
+// } else {
+// MessageUtil.info(handler, "not source file: " + srcPath);
+// }
+// }
+// }
+//
+// // gather, normalize paths changed
+// final ArrayList changed = new ArrayList();
+// FileFilter touchedCollector = new FileFilter() {
+// public boolean accept(File file) {
+// if (file.lastModified() > classesDirTime) {
+// String path = file.getPath();
+// if (!path.endsWith(".class")) {
+// MessageUtil.info(handler, "changed file not a class: " + file);
+// } else {
+// String classPath = path.substring(0, path.length() - 6);
+// classPath = FileUtil.weakNormalize(classPath);
+// if (sources.contains(classPath)) {
+// sources.remove(classPath);
+// } else {
+// changed.add(classPath);
+// }
+// }
+// }
+// return false;
+// }
+// };
+// classesDir.listFiles(touchedCollector);
+//
+// // report any unexpected changes
+// Diffs diffs = new Diffs("classes", sources, changed, String.CASE_INSENSITIVE_ORDER);
+// diffs.report(handler, IMessage.ERROR);
+// }
+
+ ICommand getCommand(IncCompilerRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ assertState(null != command, "command never set");
+ return command;
+ }
+
+ File getTestBaseSrcDir(IncCompilerRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ return testBaseSrcDir;
+ }
+
+ File getTestBaseSrcDir(JavaRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ return testBaseSrcDir;
+ }
+
+ /** @throws IllegalArgumentException unless a readable directory */
+ void setTestBaseSrcDir(File dir, CompilerRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ if ((null == dir) || !dir.isDirectory() || !dir.canRead()) {
+ throw new IllegalArgumentException("bad test base src dir: " + dir);
+ }
+ testBaseSrcDir = dir;
+ }
+
+ /** @param readable if true, then throw IllegalArgumentException if not readable */
+ void setClasspath(File[] files, boolean readable, CompilerRun caller) {
+ LangUtil.throwIaxIfNull(files, "files");
+ LangUtil.throwIaxIfNull(caller, "caller");
+ assertState(!gotClasspath, "classpath already read");
+ classpath = new File[files.length];
+ for (int i = 0; i < files.length; i++) {
+ LangUtil.throwIaxIfNull(files[i], "files[i]");
+ if (readable && !files[i].canRead()) {
+ throw new IllegalArgumentException("bad classpath entry: " + files[i]);
+ }
+ classpath[i] = files[i];
+ }
+ }
+
+ File[] getClasspath(JavaRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ assertState(null != classpath, "classpath not set");
+
+ File[] result = new File[classpath.length];
+ System.arraycopy(classpath, 0, result, 0, result.length);
+ return result;
+ }
+
+ /** @param readable if true, omit non-readable directories */
+ File[] getClasspathDirectories(boolean readable, JavaRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ assertState(null != classpath, "classpath not set");
+ ArrayList result = new ArrayList();
+ File[] src = classpath;
+ for (int i = 0; i < src.length; i++) {
+ File f = src[i];
+ if ((null != f) && (f.isDirectory()) && (!readable || f.canRead())) {
+ result.add(f);
+ }
+ }
+ return (File[]) result.toArray(new File[0]);
+ }
+
+ /** @param readable if true, omit non-readable directories */
+ File[] getClasspathJars(boolean readable, JavaRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ assertState(null != classpath, "classpath not set");
+ ArrayList result = new ArrayList();
+ File[] src = classpath;
+ for (int i = 0; i < src.length; i++) {
+ File f = src[i];
+ if (FileUtil.hasZipSuffix(f) && (!readable || f.canRead())) {
+ result.add(f);
+ }
+ }
+ return (File[]) result.toArray(new File[0]);
+ }
+
+ /** @return String of classpath entries delimited internally by File.pathSeparator */
+ String classpathToString(CompilerRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ assertState(null != classpath, "classpath not set");
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < classpath.length; i++) {
+ if (i > 0) {
+ sb.append(File.pathSeparator);
+ }
+ sb.append(classpath[i].getAbsolutePath());
+ }
+ return sb.toString();
+ }
+}
diff --git a/testing/src/org/aspectj/testing/harness/bridge/Validator.java b/testing/src/org/aspectj/testing/harness/bridge/Validator.java
new file mode 100644
index 000000000..55c3962e8
--- /dev/null
+++ b/testing/src/org/aspectj/testing/harness/bridge/Validator.java
@@ -0,0 +1,557 @@
+/* *******************************************************************
+ * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.bridge.AbortException;
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.ListIterator;
+import java.util.Stack;
+
+/**
+ * Check input and implement defaults.
+ * This handles failure messaging and collecting temp directories
+ * for later cleanup.
+ * The default behavior is to send a fail message to the message
+ * handler. Clients may instead:
+ * <li>Toggle abortOnException to throw AbortException on failure</li>
+ * <li>push their own message handler to redirect messages
+ * (pop to remove if any pushed)</li>
+ * <li>subclass this to reimplement <code>fail(String)</code>
+ * <p>
+ * A component given this to do validation should
+ * not change the reporting scheme established by the caller,
+ * so the caller may lock and unlock the error handling policy.
+ * When the policy is locked, this silently ignores attempts
+ * to toggle exceptions, or delete temporary files.
+ * XXX callers cannot prevent others from pushing other error handlers.
+ */
+public class Validator {
+
+ /** stack of handlers */
+ private final Stack handlers;
+
+ /** list of File registered for deletion on demand */
+ private final ArrayList tempFiles; // deleteTempFiles requires ListIterator.remove()
+
+ /** list of Sandboxes registered for cleanup on demand */
+ private final ArrayList sandboxes;
+
+ /** if true, throw AbortException on failure */
+ boolean abortOnFailure;
+
+ /** this object prevents any changes to error-handling policy */
+ private Object locker;
+
+ public Validator(IMessageHandler handler) {
+ tempFiles = new ArrayList();
+ sandboxes = new ArrayList();
+ handlers = new Stack();
+ pushHandler(handler);
+ }
+
+ /**
+ * Push IMessageHandler onto stack,
+ * so it will be used until the next push or pop
+ * @param handler not null
+ *
+ */
+ public void pushHandler(IMessageHandler handler) {
+ LangUtil.throwIaxIfNull(handler, "handler");
+ handlers.push(handler);
+ }
+
+ /** @throws IllegalStateException if handler is not on top */
+ public void popHandler(IMessageHandler handler) {
+ LangUtil.throwIaxIfNull(handler, "handler");
+ if (handler != handlers.peek()) {
+ throw new IllegalStateException("not current handler");
+ }
+ handlers.pop();
+ }
+
+ /** @return true if this requestor now has locked the error handling policy */
+ public boolean lock(Object requestor) {
+ if (null == locker) {
+ locker = requestor;
+ }
+ return (locker == requestor);
+ }
+
+ /** @return true if the error handling policy is now unlocked */
+ public boolean unlock(Object requestor) {
+ if (requestor == locker) {
+ locker = null;
+ }
+ return (locker == null);
+ }
+
+ public void setAbortOnFailure(boolean abortOnFailure) {
+ if (null == locker) {
+ if (this.abortOnFailure != abortOnFailure) {
+ this.abortOnFailure = abortOnFailure;
+ }
+ }
+ }
+
+ /**
+ * May fail with any of the messages
+ * <li>{null check} array<li>
+ * <li>{null check} {message}[#}<li>
+ */
+ public boolean nullcheck(Object[] ra, String message) {
+ return ((nullcheck((Object) ra, message + " array"))
+ && nullcheck(Arrays.asList(ra), message));
+ }
+
+ /**
+ * Like nullcheck(Collection, message), except adding lower and upper bound
+ * @param atLeast fail if list size is smaller than this
+ * @param atMost fail if list size is greater than this
+ */
+ public boolean nullcheck(Collection list, int atLeast, int atMost, String message) {
+ if (nullcheck(list, message)) {
+ int size = list.size();
+ if (size < atLeast) {
+ fail(message + ": " + size + "<" + atLeast);
+ } else if (size > atMost) {
+ fail(message + ": " + size + ">" + atMost);
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * May fail with any of the messages
+ * <li>{null check} list<li>
+ * <li>{null check} {message}[#}<li>
+ */
+ public boolean nullcheck(Collection list, String message) {
+ if (nullcheck((Object) list, message + " list")) {
+ int i = 0;
+ for (Iterator iter = list.iterator(); iter.hasNext();) {
+ if (!nullcheck(iter.next(), message + "[" + i++ + "]")) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * May fail with the message "null {message}"
+ * if o and the def{ault} are null
+ * @return o if not null or default otherwise
+ */
+ public Object nulldefault(Object o, String message, Object def) {
+ if (null == o) {
+ o = def;
+ }
+ nullcheck(o, message);
+ return o;
+ }
+
+ /** may fail with the message "null {message}" */
+ public boolean nullcheck(Object o, String message) {
+ if (null == o) {
+ if (null == message) message = "object";
+ fail("null " + message);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Verify that all paths are readable relative to baseDir.
+ * may fail with the message "cannot read {file}"
+ */
+ public boolean canRead(File baseDir, String[] paths, String message) {
+ if (!canRead(baseDir, "baseDir - " + message)
+ || !nullcheck(paths, "paths - " + message)) {
+ return false;
+ }
+ final String dirPath = baseDir.getPath();
+ File[] files = FileUtil.getBaseDirFiles(baseDir, paths);
+ for (int j = 0; j < files.length; j++) {
+ if (!canRead(files[j], "{" + dirPath + "} " + files[j])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Verify that all paths are readable relative to baseDir.
+ * may fail with the message "cannot read {file}"
+ */
+ public boolean canRead(File[] files, String message) {
+ if (!nullcheck(files, message)) {
+ return false;
+ }
+ for (int j = 0; j < files.length; j++) {
+ if (!canRead(files[j], files[j].getPath())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** may fail with the message "cannot read {file}" */
+ public boolean canRead(File file, String message) {
+ if (nullcheck(file, message)) {
+ if (file.canRead()) {
+ return true;
+ } else {
+ fail("cannot read " + file);
+ }
+ }
+ return false;
+ }
+
+ /** may fail with the message "cannot write {file}" */
+ public boolean canWrite(File file, String message) {
+ if (nullcheck(file, message)) {
+ if (file.canRead()) {
+ return true;
+ } else {
+ fail("cannot write " + file);
+ }
+ }
+ return false;
+ }
+
+ /** may fail with the message "not a directory {file}" */
+ public boolean canReadDir(File file, String message) {
+ if (canRead(file, message)) {
+ if (file.isDirectory()) {
+ return true;
+ } else {
+ fail("not a directory " + file);
+ }
+ }
+ return false;
+ }
+
+ /** may fail with the message "not a directory {file}" */
+ public boolean canWriteDir(File file, String message) {
+ if (canWrite(file, message)) {
+ if (file.isDirectory()) {
+ return true;
+ } else {
+ fail("not a directory " + file);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * May fail with any of the messages
+ * <li>{null check} dir array<li>
+ * <li>"#: not a File {file}"<li>
+ * <li>{canRead} {message}[#}<li>
+ */
+ public boolean canReadFiles(Object[] dirs, String message) {
+ return ((nullcheck((Object) dirs, message + " dir array"))
+ && canReadFiles(Arrays.asList(dirs), message));
+ }
+
+ /**
+ * May fail with any of the messages
+ * <li>{null check} files<li>
+ * <li>"#: not a File {file}"<li>
+ * <li>{canRead} {message}[#}<li>
+ */
+ public boolean canReadFiles(Collection dirs, String message) {
+ if (nullcheck((Object) dirs, message + " files")) {
+ int i = 0;
+ for (Iterator iter = dirs.iterator(); iter.hasNext();) {
+ Object o = iter.next();
+ if (! (o instanceof File)) {
+ fail(i + ": not a file " + o);
+ }
+ if (!canRead((File) o, message + "[" + i++ + "]")) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ /**
+ * May fail with any of the messages
+ * <li>{null check} dir array<li>
+ * <li>"#: not a File {file}"<li>
+ * <li>{canReadDir} {message}[#}<li>
+ */
+ public boolean canReadDirs(Object[] dirs, String message) {
+ return ((nullcheck((Object) dirs, message + " dir array"))
+ && canReadDirs(Arrays.asList(dirs), message));
+ }
+
+ /**
+ * May fail with any of the messages
+ * <li>{null check} dirs<li>
+ * <li>"#: not a File {file}"<li>
+ * <li>{canReadDir} {message}[#}<li>
+ */
+ public boolean canReadDirs(Collection dirs, String message) {
+ if (nullcheck((Object) dirs, message + " dirs")) {
+ int i = 0;
+ for (Iterator iter = dirs.iterator(); iter.hasNext();) {
+ Object o = iter.next();
+ if (! (o instanceof File)) {
+ fail(i + ": not a file " + o);
+ }
+ if (!canReadDir((File) o, message + "[" + i++ + "]")) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * May fail with any of the messages
+ * <li>{null check} dir array<li>
+ * <li>"#: not a File {file}"<li>
+ * <li>{canWrite} {message}[#}<li>
+ */
+ public boolean canWriteFiles(Object[] dirs, String message) {
+ return ((nullcheck((Object) dirs, message + " dir array"))
+ && canWriteFiles(Arrays.asList(dirs), message));
+ }
+
+ /**
+ * May fail with any of the messages
+ * <li>{null check} files<li>
+ * <li>"#: not a File {file}"<li>
+ * <li>{canWrite} {message}[#}<li>
+ */
+ public boolean canWriteFiles(Collection dirs, String message) {
+ if (nullcheck((Object) dirs, message + " files")) {
+ int i = 0;
+ for (Iterator iter = dirs.iterator(); iter.hasNext();) {
+ Object o = iter.next();
+ if (! (o instanceof File)) {
+ fail(i + ": not a file " + o);
+ }
+ if (!canWrite((File) o, message + "[" + i++ + "]")) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * May fail with any of the messages
+ * <li>{null check} dir array<li>
+ * <li>"#: not a File {file}"<li>
+ * <li>{canWriteDir} {message}[#}<li>
+ */
+ public boolean canWriteDirs(Object[] dirs, String message) {
+ return ((nullcheck((Object) dirs, message + " dir array"))
+ && canWriteDirs(Arrays.asList(dirs), message));
+ }
+
+ /**
+ * May fail with any of the messages
+ * <li>{null check} dirs<li>
+ * <li>"#: not a File {file}"<li>
+ * <li>{canWriteDir} {message}[#}<li>
+ */
+ public boolean canWriteDirs(Collection dirs, String message) {
+ if (nullcheck((Object) dirs, message + " dirs")) {
+ int i = 0;
+ for (Iterator iter = dirs.iterator(); iter.hasNext();) {
+ Object o = iter.next();
+ if (! (o instanceof File)) {
+ fail(i + ": not a file " + o);
+ }
+ if (!canWriteDir((File) o, message + "[" + i++ + "]")) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Send an info message to any underlying handler
+ * @param message ignored if null
+ */
+ public void info(String message) {
+ if (null != message) {
+ IMessageHandler handler = getHandler();
+ MessageUtil.info(handler, message);
+ }
+ }
+
+ /** Fail via message or AbortException */
+ public void fail(String message) {
+ fail(message, (Throwable) null);
+ }
+
+ /**
+ * Fail via message or AbortException.
+ * All failure messages go through here,
+ * so subclasses may override to control
+ * failure-handling.
+ */
+ public void fail(String message, Throwable thrown) {
+ if ((null == message) && (null == thrown)) {
+ message = "<Validator:no message>";
+ }
+ IMessage m = MessageUtil.fail(message, thrown);
+ if (abortOnFailure) {
+ throw new AbortException(m);
+ } else {
+ IMessageHandler handler = getHandler();
+ handler.handleMessage(m);
+ }
+ }
+
+ /**
+ * Register a file temporary, i.e., to be
+ * deleted on completion. The file need not
+ * exist (yet or ever) and may be a duplicate
+ * of existing files registered.
+ */
+ public void registerTempFile(File file) {
+ if (null != file) {
+ tempFiles.add(file);
+ }
+ }
+
+ /**
+ * Get a writable {possibly-empty} directory.
+ * If the input dir is null, then try to create a temporary
+ * directory using name.
+ * If the input dir is not null, this tries to
+ * create it if it does not exist.
+ * Then if name is not null,
+ * it tries to get a temporary directory
+ * under this using name.
+ * If name is null, "Validator" is used.
+ * If deleteContents is true, this will try to delete
+ * any existing contents. If this is unable to delete
+ * the contents of the input directory, this may return
+ * a new, empty temporary directory.
+ * If register is true, then any directory returned is
+ * saved for later deletion using <code>deleteTempDirs()</code>,
+ * including the input directory.
+ * When this is unable to create a result, if failMessage
+ * is not null then this will fail; otherwise it returns false;
+ */
+ public File getWritableDir(File dir, String name,
+ boolean deleteContents, boolean register, String failMessage) {
+ // check dir
+ if (null == dir) {
+ if (null == name) {
+ name = "Validator";
+ }
+ dir = FileUtil.getTempDir(name);
+ } else {
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+ }
+ // fail if necessary
+ if ((null == dir) || (!dir.exists())) {
+ if (null != failMessage) {
+ fail(failMessage + ": unable to get parent " + dir);
+ }
+ } else {
+ File result = FileUtil.makeNewChildDir(dir, name);
+ if (deleteContents) {
+ FileUtil.deleteContents(dir);
+ }
+ if (register) {
+ tempFiles.add(dir);
+ }
+ }
+ return dir;
+ }
+
+ /**
+ * Delete any temp sandboxes, files or directories saved,
+ * optionally reporting failures in the normal way.
+ * This will be ignored unless the failure policy
+ * is unlocked.
+ */
+ public void deleteTempFiles(boolean reportFailures) {
+ if (null == locker) {
+ for (ListIterator iter = tempFiles.listIterator(); iter.hasNext();) {
+ deleteFile((File) iter.next(), reportFailures);
+ }
+ for (ListIterator iter = sandboxes.listIterator(); iter.hasNext();) {
+ Sandbox sandbox = (Sandbox) iter.next();
+ // XXX assumes all dirs are in sandboxDir
+ deleteFile(sandbox.sandboxDir, reportFailures);
+ }
+ }
+ }
+
+ /**
+ * Manage temp files and directories of registered sandboxes.
+ * Note that a sandbox may register before it is initialized,
+ * so this must do nothing other than save the reference.
+ * @param sandbox the uninitialized Sandbox to track
+ */
+ public void registerSandbox(Sandbox sandbox) {
+ sandboxes.add(sandbox);
+ }
+
+
+ private void deleteFile(File file, boolean reportFailures) {
+ if (null == file) {
+ if (reportFailures) {
+ fail("unable to delete null file");
+ }
+ return;
+ }
+ FileUtil.deleteContents(file);
+ if (file.exists()) {
+ file.delete();
+ }
+ if (reportFailures && file.exists()) {
+ fail("unable to delete " + file);
+ }
+ }
+
+ /** @throws IllegalStateException if handler is null */
+ private IMessageHandler getHandler() {
+ IMessageHandler handler = (IMessageHandler) handlers.peek();
+ if (null == handler) {
+ throw new IllegalStateException("no handler");
+ }
+ return handler;
+ }
+
+} // class Validator
+
diff --git a/testing/src/org/aspectj/testing/run/IRun.java b/testing/src/org/aspectj/testing/run/IRun.java
new file mode 100644
index 000000000..8cc33f619
--- /dev/null
+++ b/testing/src/org/aspectj/testing/run/IRun.java
@@ -0,0 +1,62 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+/**
+ * A run is a Runnable that may know how to set its own status.
+ * @author isberg
+ */
+public interface IRun {
+ public static final IRun[] RA_IRun = new IRun[0];
+
+ /** Positive wrapper for the status parameter */
+ public static final IRun OK
+ = new IRun() {
+ /** This returns false when the status is null
+ * or runResult is false */
+ public boolean run(IRunStatus status) {
+ return ((null != status) && status.runResult());
+ }
+ public IRunStatus makeStatus() { return null; }
+ };
+
+ /** Negative wrapper for the status parameter */
+ public static final IRun NOTOK
+ = new IRun() {
+ public boolean run(IRunStatus status) {
+ return ((null == status) || !status.runResult());
+ }
+ public IRunStatus makeStatus() { return null; }
+ };
+
+ /**
+ * Run process, setting any known status.
+ * Normally the caller starts the process
+ * and the callee completes it, so that
+ * status.isCompleted() returns true after
+ * the call completes. However, responsible
+ * callees ensure starting, and responsible
+ * callers ensure completed after the call.
+ * Anyone setting completion should ensure it
+ * is set recursively for all children,
+ * and anyone starting child runs should
+ * ensure children are registered and initialized
+ * appropriately.
+ * @param status the IRunStatus representing the
+ * outcome of the process (collecting parameter).
+ * @see Runners
+ */
+ boolean run(IRunStatus status) throws Exception; // IMessageHandler?
+}
diff --git a/testing/src/org/aspectj/testing/run/IRunIterator.java b/testing/src/org/aspectj/testing/run/IRunIterator.java
new file mode 100644
index 000000000..34bc12d76
--- /dev/null
+++ b/testing/src/org/aspectj/testing/run/IRunIterator.java
@@ -0,0 +1,62 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+import org.aspectj.bridge.IMessageHandler;
+
+/**
+ * Iterator for IRun.
+ * IRunIterator are useful if the underlying components
+ * can use a generic IRunStatus and a single listener.
+ * It is a requirement of any component runnin the IRunIterator
+ * that they call iterationCompleted() when done, to permit
+ * the IRunIterator to clean up.
+ * @see Runner#runIterator(IRunIterator, IRunStatus, IRunListener)
+ */
+public interface IRunIterator {
+
+ /**
+ * @return true if nextRun() would return something non-null
+ * @throws IllegalStateException if called after
+ * <code>iterationCompleted()</code>
+ */
+ boolean hasNextRun();
+
+ /**
+ * Get the next run.
+ * IRunIterator which contains child IRunIterator may either return
+ * the children IRun or wrap them using
+ * Runner.wrap(IRunIterator, IRunListener)
+ * @param handler the IMessageHandler to use for error and other messages
+ * @param runnere the Runner to use to wrap any nested IRunIterator as IRun.
+ * @return the next run, or null if there are no more.
+ * @throws IllegalStateException if called after
+ * <code>iterationCompleted()</code>
+ * @see Runner#wrap(IRunIterator, IRunListener)
+ */
+ IRun nextRun(IMessageHandler handler, Runner runner);
+
+ /**
+ * Signal a runner that further runs should be aborted. Runners
+ * should check this after each failure.
+ * @return true if the runner should stop iterating when an IRun fails
+ * @throws IllegalStateException if called after
+ * <code>iterationCompleted()</code>
+ */
+ boolean abortOnFailure(); // XXX supply IRun or IRunStatus?
+
+ /** called when hasNextRun() and nextRun() will no longer be called */
+ void iterationCompleted();
+}
diff --git a/testing/src/org/aspectj/testing/run/IRunListener.java b/testing/src/org/aspectj/testing/run/IRunListener.java
new file mode 100644
index 000000000..85ab6148b
--- /dev/null
+++ b/testing/src/org/aspectj/testing/run/IRunListener.java
@@ -0,0 +1,38 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+/**
+ * Listen to events in the run lifecycle -
+ * birth, death, and procreation.
+ * @author isberg
+ */
+public interface IRunListener {
+
+ /**
+ * Called when run is about to be started.
+ */
+ void runStarting(IRunStatus run);
+
+ /**
+ * Called when run is has completed.
+ */
+ void runCompleted(IRunStatus run);
+
+ /**
+ * Called when adding a child to a parent run
+ */
+ void addingChild(IRunStatus parent, IRunStatus child);
+}
diff --git a/testing/src/org/aspectj/testing/run/IRunStatus.java b/testing/src/org/aspectj/testing/run/IRunStatus.java
new file mode 100644
index 000000000..6651c42f1
--- /dev/null
+++ b/testing/src/org/aspectj/testing/run/IRunStatus.java
@@ -0,0 +1,175 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHolder;
+import org.aspectj.bridge.MessageUtil;
+
+/**
+ * Encapsulate status and results for a run.
+ * A run starts and then completes normally
+ * (finished(Object result)),
+ * abruptly (thrown(Throwable thrown)),
+ * or by user request (abort(Object request)).
+ * @author isberg
+ */
+public interface IRunStatus extends IMessageHolder {
+ /** clients use this when signalling completion without a specific result */
+ public static final Object VOID = Boolean.TRUE;
+
+ /** result object for successful (unset) boolean run result */
+ public static final Boolean PASS = Boolean.TRUE;
+
+ /** result object for failed (unset) boolean run result */
+ public static final Boolean FAIL = Boolean.FALSE;
+
+ /** clients use this when signalling abort without any specific request */
+ public static final Object ABORT = Boolean.FALSE;
+
+ /** clients use this when signalling abort because no object to run */
+ public static final Object ABORT_NORUN = MessageUtil.ABORT_NOTHING_TO_RUN;
+
+ /** returned from getChildren when there are no children */
+ public static final IRunStatus[] EMPTY_NEST = new IRunStatus[0];
+
+ //------------------- process controls
+ /**
+ * Set identifier associated with this run, if any
+ * @throws IllegalArgumentException if id is null
+ * @throws IllegalStateException if id has already been set
+ */
+ void setIdentifier(Object id);
+
+ //------------------- process controls
+ /**
+ * Call before any start() or after isCompleted() would return true
+ * to reset this to its pre-start state
+ * @throws IllegalStateException if start() has been called
+ * and isCompleted() is not true.
+ */
+ void reset();
+
+ /**
+ * Call only once to signal this run has started.
+ * @throws IllegalStateException if start() has been called
+ */
+ void start();
+
+ /**
+ * Call this or thrown only once after start()
+ * to signal this run has ended.
+ * If this represents a void process, use VOID.
+ * @param result the Object returned by this run.
+ * @throws IllegalStateException if start() was not called first
+ * or if either completed(Object) or thrown(Throwable) have been called.
+ */
+ void finish(Object result);
+
+ /**
+ * Call to signal this run is ending by request.
+ * If there is no message, use ABORT.
+ * @param request the Object request to abort,
+ * or ABORT if none is available.
+ * @throws IllegalStateException if start() was not called first
+ * or if either completed(Object) or thrown(Throwable) have been called.
+ */
+ void abort(Object request);
+
+ /**
+ * Call this or completed only once after start()
+ * to signal this run has ended.
+ * @throws IllegalStateException if start() was not called first
+ * or if either completed(Object) or thrown(Throwable) have been called.
+ */
+ void thrown(Throwable thrown);
+
+
+ /**
+ * Call this for the status to throw an unchecked exception
+ * of the type that its controller understands.
+ * It is an error for a IRunStatus to continue normally
+ * after this is invoked.
+ */
+ void completeAbruptly();
+ //------------------- process messages
+ /**
+ * Call this any time to signal any messages.
+ * (In particular, the IRun caller may use this to register messages
+ * about the mishandling of the run by the ResultStatusI by the callee.)
+ * This is a shortcut for getMessageHandler().handleMessage(..);
+ */
+ //boolean handleMessage(IMessage message);
+
+ //------------------- process display
+ /** @return true if this run has started */
+ boolean started();
+
+ /** @return true if one of the result, abort request, or thrown is available */
+ boolean isCompleted();
+
+ /** @return true if this got an abort request */
+ boolean aborted();
+
+ /**
+ * @return true if completed and not aborted and no thrown
+ * or messages with kind ABORT or FAIL or ERROR
+ */
+ boolean runResult();
+
+ /** get the invoker for any subruns */
+ Runner getRunner();
+
+ /** @return the Object result, if any, of this run */
+ Object getResult();
+
+ /** @return the Object abort request, if any, of this run */
+ Object getAbortRequest();
+
+ /** @return the Throwable thrown, if any, by this run */
+ Throwable getThrown();
+
+ /** @return any Message[] signalled, or SILENCE if none */
+ IMessage[] getMessages();
+
+ /** @return the identifier set for this run, if any */
+ Object getIdentifier();
+
+ //------------------- subprocess
+ /**
+ * Add a record for a child run
+ * and install self as parent.
+ * @throws IllegalArgumentException if child is null
+ */
+ void addChild(IRunStatus child);
+
+ /**
+ * Register this as the run parent.
+ * (Any run that does addChild(IRunStatus) should register as parent.)
+ * @throws IllegalArgumentException if parent is null
+ * @throws IllegalStateException if parent exists already
+ */
+ void registerParent(IRunStatus parent);
+
+ /**
+ * @return the current children of this run, or EMPTY_NEST if none
+ */
+ IRunStatus[] getChildren();
+
+ /**
+ * @return the currently-registered parent, or null if none
+ */
+ IRunStatus getParent();
+}
diff --git a/testing/src/org/aspectj/testing/run/IRunValidator.java b/testing/src/org/aspectj/testing/run/IRunValidator.java
new file mode 100644
index 000000000..29f73f103
--- /dev/null
+++ b/testing/src/org/aspectj/testing/run/IRunValidator.java
@@ -0,0 +1,28 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+/**
+ * These check whether particular runs have passed.
+ * @author isberg
+ */
+public interface IRunValidator {
+ /**
+ * Evaluate whether a run has passed.
+ * @param run the IRunStatus to see if it passed.
+ * @return true if run has passed
+ */
+ boolean runPassed(IRunStatus run);
+}
diff --git a/testing/src/org/aspectj/testing/run/RunIterator.java b/testing/src/org/aspectj/testing/run/RunIterator.java
new file mode 100644
index 000000000..37e3187ed
--- /dev/null
+++ b/testing/src/org/aspectj/testing/run/RunIterator.java
@@ -0,0 +1,136 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.util.LangUtil;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Adapt IRun or Run[] or List or ListIterator to RunIteratorI.
+ */
+public class RunIterator implements IRunIterator {
+
+ protected String name;
+ protected ListIterator iter;
+ protected IRun run;
+
+ public RunIterator(String name, IRun run) {
+ init(name, run);
+ }
+
+ public RunIterator(String name, List list) {
+ init(name, list);
+ }
+
+ public RunIterator(String name, IRun[] runs) {
+ init(name, Arrays.asList(runs).listIterator());
+ }
+
+ public RunIterator(String name, ListIterator iterator) {
+ init(name, iterator);
+ }
+
+ public void init(String name, List list) {
+ init(name, list.listIterator());
+ }
+
+ public void init(String name, IRun[] runs) {
+ init(name, Arrays.asList(runs).listIterator());
+ }
+
+ /** @return true if the first IRun from nextRun can be the sole IRun */
+ public boolean isHoistable() {
+ return (null != run);
+ }
+
+ /**
+ * @param name if null, use iterator.toString();
+ * @param iterator not null
+ * @throws IllegalArgumentException if iterator is null
+ */
+ public void init(String name, ListIterator iterator) {
+ LangUtil.throwIaxIfNull(iterator, "iterator");
+ iter = iterator;
+ name = (null != name? name : iterator.toString());
+ run = null;
+ }
+
+ /**
+ * @param name if null, use run();
+ * @param run not null
+ * @throws IllegalArgumentException if iterator is null
+ */
+ public void init(String name, IRun run) {
+ LangUtil.throwIaxIfNull(run, "run");
+ this.run = run;
+ name = (null != name? name : run.toString());
+ iter = null;
+ }
+
+ /**
+ * @return false always
+ * @see org.aspectj.testing.run.IRunIterator#abortOnFailure()
+ */
+ public boolean abortOnFailure() {
+ return false;
+ }
+
+ /**
+ * @see org.aspectj.testing.run.RunIteratorI#hasNextRun()
+ */
+ public boolean hasNextRun() {
+ return ((null != run) || ((null != iter) && (iter.hasNext())));
+ }
+
+ /**
+ * @see org.aspectj.testing.run.IRunIterator#iterationCompleted()
+ */
+ public void iterationCompleted() {
+ }
+
+
+ /**
+ * @see org.aspectj.testing.run.RunIteratorI#nextRun(IMessageHandler, Runner)
+ */
+ public IRun nextRun(IMessageHandler handler, Runner runner) {
+ if (null != run) {
+ IRun result = run;
+ run = null;
+ return result;
+ }
+ if (null != iter) {
+ for (Object o = iter.next(); iter.hasNext();) {
+ if (o instanceof IRunIterator) {
+ return runner.wrap((IRunIterator) o, null);
+ } else if (o instanceof IRun) {
+ return (IRun) o;
+ } else {
+ MessageUtil.error(handler, "not IRun or IRunIterator: " + o);
+ }
+ }
+ }
+ return null;
+ }
+
+ /** @return name */
+ public String toString() {
+ return name;
+ }
+}
diff --git a/testing/src/org/aspectj/testing/run/RunListener.java b/testing/src/org/aspectj/testing/run/RunListener.java
new file mode 100644
index 000000000..523862991
--- /dev/null
+++ b/testing/src/org/aspectj/testing/run/RunListener.java
@@ -0,0 +1,116 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+import org.aspectj.bridge.AbortException;
+import org.aspectj.bridge.MessageUtil;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * A generic RunListener for easier partial implementations.
+ * It can take a RunI selector (called on completion)
+ * and/or a List to accumulate complete IRunStatus
+ * (if the selector is null or returns true).
+ * It can also take a PrintWriter and String to print traces of each event
+ * as "{prefix} [addingChild|runStarting|runCompleted]({IRunStatus})"
+ */
+public class RunListener implements IRunListener {
+ protected final List list;
+ protected final IRun selector;
+ protected final PrintWriter writer;
+ protected final String prefix;
+
+ protected RunListener() {
+ this((List) null, (IRun) null, (PrintWriter) null, (String) null);
+ }
+
+ /**
+ * @param sink the List sink for any IRunStatus if the selector is null
+ * or returns true for run(IRunStatus) - ignored if null.
+ * @param selector the IRun called on completion,
+ * perhaps to select those to be accumulated
+ * (should NOT throw Exception)
+ */
+ public RunListener(List sink, IRun selector) {
+ this(sink, selector, (PrintWriter) null, (String) null);
+ }
+
+ /**
+ * @param writer the PrintWriter to print events to - may be null
+ * @param prefix the String prefixing any printing - if null, ""
+ */
+ public RunListener(PrintWriter writer, String prefix) {
+ this((List) null, (IRun) null, writer, prefix);
+ }
+
+ /**
+ * @param sink the List sink for any IRunStatus if the selector is null
+ * or returns true for run(IRunStatus) - ignored if null.
+ * @param selector the IRun called on completion,
+ * perhaps to select those to be accumulated
+ * (should NOT throw Exception)
+ * @param writer the PrintWriter to print events to - may be null
+ * @param prefix the String prefixing any printing - if null, ""
+ */
+ public RunListener(List sink, IRun selector, PrintWriter writer, String prefix) {
+ this.prefix = (null == prefix ? "" : prefix);
+ this.writer = writer;
+ this.selector = selector;
+ list = sink;
+ }
+
+ /**
+ * @see org.aspectj.testing.harness.run.IRunListener#addingChild(IRunStatus, IRunStatus)
+ */
+ public void addingChild(IRunStatus parent, IRunStatus child) {
+ if (null != writer) {
+ writer.println(prefix + " addingChild(\"" + parent
+ + "\", \"" + child + "\")");
+ }
+ }
+
+ /**
+ * @see org.aspectj.testing.harness.run.IRunListener#runStarting(IRunStatus)
+ */
+ public void runStarting(IRunStatus run) {
+ if (null != writer) {
+ writer.println(prefix + " runStarting(\"" + run + "\")");
+ }
+ }
+
+ /**
+ * Print to writer (if any), run selector (if any), and add to list
+ * (if any and if selector is null or returns true).
+ * @see org.aspectj.testing.harness.run.IRunListener#runCompleted(IRunStatus)
+ * @throws AbortException wrapping any Exception thrown by any selector
+ * (error for selector to throw Exception)
+ */
+ public void runCompleted(IRunStatus run) {
+ if (null != writer) {
+ writer.println(prefix + " runCompleted(\"" + run + "\")");
+ }
+ try {
+ if (((null == selector) || selector.run(run)) && (null != list)) {
+ list.add(run);
+ }
+ } catch (Throwable e) {
+ String m = "Selectors should not throw exceptions!";
+ throw new AbortException(MessageUtil.abort(m, e));
+ }
+ }
+
+}
diff --git a/testing/src/org/aspectj/testing/run/RunListeners.java b/testing/src/org/aspectj/testing/run/RunListeners.java
new file mode 100644
index 000000000..9b27d8605
--- /dev/null
+++ b/testing/src/org/aspectj/testing/run/RunListeners.java
@@ -0,0 +1,89 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Aggregate listeners into one and run synchronously in order added.
+ * @author isberg
+ */
+public class RunListeners extends RunListener implements IRunListener {
+
+ ArrayList listeners;
+ public RunListeners() {
+ listeners = new ArrayList();
+ }
+
+ public void addListener(IRunListener listener) {
+ if (null != listener) {
+ listeners.add(listener);
+ }
+ }
+
+ public void removeListener(IRunListener listener) {
+ if (null != listener) {
+ listeners.remove(listener);
+ }
+ }
+
+
+ /**
+ * Run all listeners with the given status.
+ * @see org.aspectj.testing.harness.run.IRunListener#runStarting(IRunStatus)
+ */
+ public final void runStarting(IRunStatus status) {
+ if (null == status) {
+ throw new IllegalArgumentException("null RunStatusI");
+ }
+ Iterator iter = listeners.iterator();
+ while(!status.aborted() && iter.hasNext()) {
+ IRunListener element = (IRunListener) iter.next();
+ element.runStarting(status);
+ }
+ }
+
+ /**
+ * Signal all listeners with the given status.
+ * @see org.aspectj.testing.harness.run.IRunListener#runCompleted(IRunStatus)
+ */
+ public final void runCompleted(IRunStatus status) {
+ if (null == status) {
+ throw new IllegalArgumentException("null RunStatusI");
+ }
+ Iterator iter = listeners.iterator();
+ while(!status.aborted() && iter.hasNext()) {
+ IRunListener element = (IRunListener) iter.next();
+ element.runCompleted(status);
+ }
+ }
+ /**
+ * @see org.aspectj.testing.harness.run.IRunListener#addingChild(IRunStatus, IRunStatus)
+ */
+ public final void addingChild(IRunStatus parent, IRunStatus child) {
+ if (null == child) {
+ throw new IllegalArgumentException("null child");
+ }
+ if (null == parent) {
+ throw new IllegalArgumentException("null parent");
+ }
+ Iterator iter = listeners.iterator();
+ while(!parent.aborted() && ! child.aborted() && iter.hasNext()) {
+ IRunListener element = (IRunListener) iter.next();
+ element.addingChild(parent, child);
+ }
+ }
+}
diff --git a/testing/src/org/aspectj/testing/run/RunStatus.java b/testing/src/org/aspectj/testing/run/RunStatus.java
new file mode 100644
index 000000000..0a53080e1
--- /dev/null
+++ b/testing/src/org/aspectj/testing/run/RunStatus.java
@@ -0,0 +1,408 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHolder;
+import org.aspectj.bridge.MessageHandler;
+import org.aspectj.testing.util.BridgeUtil;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Default implementation of {@link IRunStatus}.
+ * @author isberg
+ */
+public class RunStatus implements IRunStatus {
+ private static int INDEX;
+
+ private final String name = "RunStatus[" + INDEX++ +"]";
+
+ /** true after isCompleted() evaluated true */
+ private boolean evaluated;
+
+ /** true after starting() called */
+ private boolean started; // set only in starting()
+
+ /** true after finished(int) or thrown(Throwable) called */
+ private boolean completed; // set only in completed(boolean)
+
+ /** contains any id set */
+ private Object id;
+
+ /** after finished(Object) called, contains that parameter */
+ private Object result;
+
+ /** after aborted(Object) called, contains that parameter */
+ private Object abortRequest;
+
+ /** use to set exception thrown, if any */
+ private Throwable thrown;
+
+ /** list of any messages submitted */
+ private IMessageHolder messageHolder;
+
+ /** list of any child status */
+ private ArrayList children;
+
+ /** parent of this status */
+ private IRunStatus parent;
+
+ /** invoker for any subruns */
+ private Runner runner;
+
+ /** controls runResult() */
+ private IRunValidator validator;
+
+// public RunStatus() {
+// reset();
+// validator = RunValidator.NORMAL;
+// }
+
+ public RunStatus(IMessageHolder holder, Runner runner) {
+ reset(holder, runner);
+ validator = RunValidator.NORMAL;
+ }
+
+ //------------------- process controls
+
+ /**
+ * Set identifier associated with this run, if any
+ * @throws IllegalArgumentException if id is null
+ * @throws IllegalStateException if id has already been set
+ */
+ public void setIdentifier(Object id) {
+ if (null == id) {
+ throw new IllegalArgumentException("null id");
+ } else if ((null != this.id) && (id != this.id)) {
+ throw new IllegalStateException(
+ "attempt to set id " + this.id + " to " + id);
+ }
+ this.id = id;
+ }
+
+ /**
+ * Set the current validator.
+ * @param delegate the RunValidatorI to use when calculating runStatus
+ * @throws IllegalArgumentException if delegate is null
+ */
+ public void setValidator(IRunValidator delegate) {
+ if (null == delegate) {
+ throw new IllegalArgumentException("null delegate");
+ }
+ if (validator != delegate) {
+ validator = delegate;
+ }
+ }
+
+ /**
+ * Call before any start() or after isCompleted() would return true
+ * to reset this to its pre-start state
+ * @throws IllegalStateException if start() has been called
+ * and isCompleted() is not true.
+ */
+ public void reset() {
+ reset((IMessageHolder) null, (Runner) null);
+ }
+
+ /**
+ * Call before any start() or after isCompleted() would return true
+ * to reset this to its pre-start state. Does not affect validator.
+ * @param holder the IMessageHolder to use after resetting.
+ * @throws IllegalStateException if start() has been called
+ * and isCompleted() is not true.
+ */
+ public void reset(IMessageHolder holder, Runner runner) {
+ if (null == runner) {
+ throw new IllegalArgumentException("null runner");
+ }
+ if (started && (!isCompleted())) {
+ throw new IllegalStateException("no reset() until isCompleted");
+ }
+ started = false;
+ completed = false;
+ result = null;
+ abortRequest = null;
+ thrown = null;
+ parent = null;
+ id = null;
+ messageHolder = (null != holder ? holder : new MessageHandler());
+ if (null != children) {
+ children.clear();
+ }
+ this.runner = runner;
+ evaluated = false;
+ }
+
+ /**
+ * Call only once to signal this run has started.
+ * @throws IllegalStateException if start() has been called
+ */
+ public void start() {
+ if (started) {
+ throw new IllegalStateException("started already");
+ } else if (isCompleted()) {
+ throw new IllegalStateException("start after completed (do reset)");
+ }
+ started = true;
+ }
+
+ /**
+ * Call this or thrown only once after start()
+ * to signal this run has ended.
+ * If this represents a void process, use VOID.
+ * @param result the Object returned by this run.
+ * @throws IllegalStateException if start() was not called first
+ * or if either completed(Object) or thrown(Throwable) have been called.
+ */
+ public void finish(Object result) {
+ if (null == result) {
+ throw new IllegalArgumentException("null result");
+ } else if (isCompleted()) {
+ throw new IllegalStateException(
+ "completed then finish " + result);
+ }
+ this.result = result;
+ }
+
+ /**
+ * Call to signal this run is ending by request.
+ * If this represents a void process, use VOID.
+ * If there is no message, use ABORT.
+ * @param request the Object request to abort,
+ * or ABORT if none is available.
+ * @throws IllegalStateException if start() was not called first
+ * or if either completed(Object) or thrown(Throwable) have been called.
+ */
+ public void abort(Object request) {
+ if (null == request) {
+ throw new IllegalArgumentException("null request");
+ } else if (isCompleted()) {
+ throw new IllegalStateException(
+ "completed then abort " + request);
+ }
+ this.abortRequest = request;
+ }
+
+ /**
+ * Call this or completed only once after start()
+ * to signal this run has ended.
+ * @throws IllegalStateException if start() was not called first
+ * or if either completed(Object) or thrown(Throwable) have been called.
+ */
+ public void thrown(Throwable thrown) {
+ if (null == thrown) {
+ throw new IllegalArgumentException("null thrown");
+ } else if (isCompleted()) {
+ throw new IllegalStateException(
+ "completed then thrown " + thrown);
+ }
+ this.thrown = thrown;
+ }
+
+ public void completeAbruptly() {
+ throw new Error("completing abruptly"); // XXX configurable
+ }
+
+ /**
+ * @return true if completed, not aborted, no thrown, no
+ * messages of kind ERROR, FAIL or ABORT, and
+ * result object is not IRunStatus.FAIL.
+ * @see org.aspectj.testing.harness.newbridge.IRunStatus#runResult()
+ */
+ public boolean runResult() {
+ return validator.runPassed(this);
+ }
+
+ //------------------- process messages
+ /**
+ * Call this any time before isCompleted() would return true
+ * to signal any messages.
+ * @throws IllegalStateException if isCompleted().
+ */
+ public boolean handleMessage(IMessage message) {
+ return messageHolder.handleMessage(message);
+ }
+ public boolean isIgnoring(IMessage.Kind kind) {
+ return messageHolder.isIgnoring(kind);
+ }
+
+ /**
+ * @see org.aspectj.bridge.IMessageHolder#hasAnyMessage(Kind, boolean)
+ */
+ public boolean hasAnyMessage(IMessage.Kind kind, boolean orGreater) {
+ return messageHolder.hasAnyMessage(kind, orGreater);
+ }
+
+ /**
+ * @see org.aspectj.bridge.IMessageHolder#getMessages(Kind)
+ */
+ public IMessage[] getMessages(IMessage.Kind kind, boolean orGreater) {
+ return messageHolder.getMessages(kind, orGreater);
+ }
+
+ /**
+ * @see org.aspectj.bridge.IMessageHolder#numMessages(Kind)
+ */
+ public int numMessages(IMessage.Kind kind, boolean orGreater) {
+ return messageHolder.numMessages(kind, orGreater);
+ }
+
+ //------------------- process display
+ /** @return true if this run has started */
+ public boolean started() {
+ return started;
+ }
+
+ /** @return true if one of the result, abort request, or thrown is available */
+ public boolean isCompleted() {
+ if (!evaluated) {
+ if (started
+ && ((null != thrown)
+ || (null != result)
+ || (null != abortRequest))) {
+ completed = true;
+ evaluated = true;
+ }
+ }
+ return completed;
+ }
+
+ /** @return true if this got an abort request */
+ public boolean aborted() {
+ return (completed && (null != abortRequest));
+ }
+
+ /** @return the Object result, if any, of this run */
+ public Object getResult() {
+ return result;
+ }
+
+ /** @return the Object abort request, if any, of this run */
+ public Object getAbortRequest() {
+ return abortRequest;
+ }
+
+ /** @return the Throwable thrown, if any, by this run */
+ public Throwable getThrown() {
+ return thrown;
+ }
+
+ /**
+ * @see org.aspectj.bridge.IMessageHolder#getUnmodifiableListView()
+ */
+ public List getUnmodifiableListView() {
+ return messageHolder.getUnmodifiableListView();
+ }
+
+ /** @return any Message[] signalled, or IMessage.NONE if none */
+ public IMessage[] getMessages() {
+ return messageHolder.getMessages(null, IMessageHolder.EQUAL);
+ }
+
+ /** @return the identifier set for this run, if any */
+ public Object getIdentifier() {
+ return id;
+ }
+
+ /**
+ * @see org.aspectj.bridge.IMessageHolder#clearMessages()
+ * @throws UnsupportedOperationException always
+ */
+ public void clearMessages() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("use reset");
+ }
+
+ //------------------- subprocess
+
+ /** get the invoker for any subrunners */
+ public Runner getRunner() {
+ return runner;
+ }
+
+ /**
+ * Add a record for a child run
+ * and install self as parent.
+ * @throws IllegalArgumentException if child is null
+ */
+ public void addChild(IRunStatus child) {
+ if (null == child) {
+ throw new IllegalArgumentException("null child");
+ }
+ if (null == children) {
+ children = new ArrayList();
+ }
+ children.add(child);
+ }
+
+ /**
+ * Register this as the run parent.
+ * (Any run that does addChild(IRunStatus) should register as parent.)
+ * @throws IllegalArgumentException if parent is null
+ * @throws IllegalStateException if parent exists already
+ */
+ public void registerParent(IRunStatus parent) {
+ if (null == parent) {
+ throw new IllegalArgumentException("null parent");
+ } else if (null != this.parent) {
+ throw new IllegalStateException(
+ "adding parent " + parent + " to parent " + this.parent);
+ }
+ this.parent = parent;
+ }
+
+ /**
+ * @return the current children of this run, or EMPTY_NEST if none
+ */
+ public IRunStatus[] getChildren() {
+ if ((null == children) || (0 == children.size())) {
+ return EMPTY_NEST;
+ } else {
+ return (IRunStatus[]) children.toArray(EMPTY_NEST);
+ }
+ }
+
+ /**
+ * @return the currently-registered parent, or null if none
+ */
+ public IRunStatus getParent() {
+ return parent;
+ }
+
+ public String toString() {
+ return BridgeUtil.toShortString(this);
+ }
+
+ public String toLongString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(BridgeUtil.toShortString(this));
+ if ((null != children) && (0 < children.size())) {
+ String label = "### --------- " + name;
+ int index = 0;
+ for (Iterator iter = children.iterator(); iter.hasNext();) {
+ IRunStatus childStatus = (IRunStatus) iter.next();
+ String childLabel =
+ "\n" + label + " child[" + index++ +"] "
+ + childStatus.getIdentifier();
+ sb.append(childLabel + " ---- start\n");
+ sb.append(childStatus.toString());
+ sb.append(childLabel + " ---- end\n");
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/testing/src/org/aspectj/testing/run/RunValidator.java b/testing/src/org/aspectj/testing/run/RunValidator.java
new file mode 100644
index 000000000..9cf91e37a
--- /dev/null
+++ b/testing/src/org/aspectj/testing/run/RunValidator.java
@@ -0,0 +1,206 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHolder;
+import org.aspectj.testing.util.IntRange;
+import org.aspectj.testing.util.ObjectChecker;
+
+/**
+ * This checks if a run status passes, as follows:
+ * <li>state: fail unless completed and not aborted</li>
+ * <li>messages: fail if any of type ABORT, FAIL - permit ERROR, WARNING, DEBUG...
+ * (which permits expected compiler errors and warnings) </li>
+ * <li>thrown: if type required, fail unless type thrown;
+ * if type permitted, fail if wrong type thrown</li>
+ * <li>result: fail unless no ObjectChecker or it validates
+ * and the result is not IRunStatus.FAIL.</li>
+ * <li>otherwise delegates to any subclass doPassed()<li>
+ * Client setup the expected and permitted exception classes
+ * and the result object checker, and may also subclass to
+ * query the IRunStatus more carefully.
+ * <p>
+ * Note that IRunStatus states can be out of sync with messages,
+ * e.g., as underlying components signal ABORT without using abort(...).
+ */
+public class RunValidator implements IRunValidator {
+ /** expect normal completion with any non-null result object */
+ public static final IRunValidator NORMAL
+ = new RunValidator(ObjectChecker.ANY);
+
+ /** expect normal completion and Integer result object with value 0 */
+ public static final IRunValidator ZERO_STATUS
+ = new RunValidator(IntRange.ZERO);
+
+ /** expect finished(IRunStatus.PASS) and no thrown, fail, etc. */
+ public static final IRunValidator PASS
+ = new RunValidator(new ObjectChecker() {
+ public boolean isValid(Object o) {
+ return (o == IRunStatus.PASS);
+ }
+ });
+
+ /** expect finished(IRunStatus.FAIL) */
+ public static final IRunValidator FAIL
+ = new RunValidator(new ObjectChecker() {
+ public boolean isValid(Object o) {
+ return (o == IRunStatus.FAIL);
+ }
+ });
+
+ /** range of status values required for passing */
+ private ObjectChecker resultChecker;
+
+ // XXX replace two exc. classes with one, plus boolean for how to interpret?
+ /** if non-null, passed() permits any thrown assignable to this class */
+ private Class permittedExceptionsClass;
+
+ /** if non-null, passed() requires some thrown assignable to this class */
+ private Class requiredExceptionsClass;
+
+ /** Create result validator that expects a certain int status */
+ public RunValidator(ObjectChecker resultChecker) {
+ this(resultChecker, null, null);
+ }
+
+ /**
+ * Create result validator that passes only when completed abruptly by
+ * a Throwable assignable to the specified class.
+ * @throws illegalArgumentException if requiredExceptionsClass is not Throwable
+ */
+ public RunValidator(Class requiredExceptionsClass) {
+ this(null, null, requiredExceptionsClass);
+ }
+
+ /**
+ * Create a result handler than knows how to evaluate {@link #passed()}.
+ * You cannot specify both permitted and required exception class,
+ * and any exception class specified must be assignable to throwable.
+ *
+ * @param resultChecker {@link #passed()} will return false if
+ * the int status is not accepted by this int validator - if null,
+ * any int status result is accepted.
+ * @param fastFailErrorClass an Error subclass with a (String) constructor to use to
+ * construct and throw Error from fail(String). If null, then fail(String)
+ * returns normally.
+ * @param permittedExceptionsClass if not null and any exceptions thrown are
+ * assignable to this class, {@link #passed()} will not return
+ * false as it normally does when exceptions are thrown.
+ * @param requiredExceptionsClass if not null, {@link #passed()} will return false
+ * unless some exception was thrown that is assignable to this class.
+ * @throws illegalArgumentException if any exception class is not Throwable
+ * or if fast fail class is illegal (can't make String constructor)
+ */
+ protected RunValidator(
+ ObjectChecker resultChecker,
+ Class permittedExceptionsClass,
+ Class requiredExceptionsClass) {
+ init(resultChecker,permittedExceptionsClass, requiredExceptionsClass);
+ }
+
+ /** same as init with existing values */
+ protected void reset() {
+ init(resultChecker, permittedExceptionsClass,
+ requiredExceptionsClass);
+ }
+
+ /** subclasses may use this to re-initialize this for re-use */
+ protected void init(
+ ObjectChecker resultChecker,
+ Class permittedExceptionsClass,
+ Class requiredExceptionsClass) {
+ this.permittedExceptionsClass = permittedExceptionsClass;
+ this.requiredExceptionsClass = requiredExceptionsClass;
+
+ if (null != resultChecker) {
+ this.resultChecker = resultChecker;
+ } else {
+ this.resultChecker = IntRange.ANY;
+ }
+
+ if (null != permittedExceptionsClass) {
+ if (!Throwable.class.isAssignableFrom(permittedExceptionsClass)) {
+ String e = "permitted not throwable: " + permittedExceptionsClass;
+ throw new IllegalArgumentException(e);
+ }
+ }
+ if (null != requiredExceptionsClass) {
+ if (!Throwable.class.isAssignableFrom(requiredExceptionsClass)) {
+ String e = "required not throwable: " + requiredExceptionsClass;
+ throw new IllegalArgumentException(e);
+ }
+ }
+ if ((null != permittedExceptionsClass)
+ && (null != requiredExceptionsClass) ) {
+ String e = "define at most one of required or permitted exceptions";
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /** @return true if this result passes per this validator */
+ public final boolean runPassed(IRunStatus result) {
+ if (null == result) {
+ throw new IllegalArgumentException("null result");
+ }
+ // After the result has completed, the result is stored.
+ if (!result.isCompleted()) {
+ return false;
+ }
+ if (result.aborted()) {
+ return false;
+ }
+ if (null != result.getAbortRequest()) {
+ return false;
+ }
+ Object resultObject = result.getResult();
+ if (!resultChecker.isValid(resultObject)) {
+ return false;
+ }
+ if (resultObject == IRunStatus.FAIL) {
+ return false;
+ }
+ // need MessageHandler.getMessage(...)
+ if (result.hasAnyMessage(IMessage.FAIL, IMessageHolder.ORGREATER)) {
+ return false;
+ }
+ Throwable thrown = result.getThrown();
+ if (null == thrown) {
+ if (null != requiredExceptionsClass) {
+ return false;
+ }
+ } else {
+ Class c = thrown.getClass();
+ // at most one of the ExceptionsClass set
+ if (null != requiredExceptionsClass) {
+ if (!requiredExceptionsClass.isAssignableFrom(c)) {
+ return false;
+ }
+ } else if (null != permittedExceptionsClass) {
+ if (!permittedExceptionsClass.isAssignableFrom(c)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ return dopassed();
+ }
+
+ /** subclasses implement subclass-specific behavior for passed() here */
+ protected boolean dopassed() {
+ return true;
+ }
+}
diff --git a/testing/src/org/aspectj/testing/run/Runner.java b/testing/src/org/aspectj/testing/run/Runner.java
new file mode 100644
index 000000000..6cb9e75fa
--- /dev/null
+++ b/testing/src/org/aspectj/testing/run/Runner.java
@@ -0,0 +1,510 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.IMessageHolder;
+import org.aspectj.bridge.Message;
+import org.aspectj.bridge.MessageHandler;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.util.LangUtil;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * Run IRun, setting status and invoking listeners
+ * for simple and nested runs.
+ * <p>
+ * This manages baseline IRun status reporting:
+ * any throwables are caught and reported, and
+ * the status is started before running and
+ * (if not already completed) completed after.
+ * <p>
+ * This runs any IRunListeners specified directly in the
+ * run*(..., IRunListener) methods
+ * as well as any specified indirectly by registering listeners per-type
+ * in {@link registerListener(Class, IRunListener)}
+ * <p>
+ * For correct handling of nested runs, this sets up
+ * status parent/child relationships.
+ * It uses the child result object supplied directly in the
+ * runChild(..., IRunStatus childStatus,..) methods,
+ * or (if that is null) one obtained from the child IRun itself,
+ * or (if that is null) a generic IRunStatus.
+ * <p>
+ * For IRunIterator, this uses IteratorWrapper to wrap the
+ * iterator as an IRun. Runner and IteratorWrapper coordinate
+ * to handle fast-fail (aborting further iteration when an IRun fails).
+ * The IRunIterator itself may specify fast-fail by returning true
+ * from {@link IRunIterator#abortOnFailure()}, or clients can
+ * register IRunIterator by Object or type for fast-failure using
+ * {@link registerFastFailIterator(IRunIterator)} or
+ * {@link registerFastFailIterator(Class)}.
+ * This also ensures that
+ * {@link IRunIterator#iterationCompleted()} is
+ * called after the iteration process has completed.
+ */
+public class Runner {
+ // XXX need to consider wiring in a logger - sigh
+
+ private static final IMessage FAIL_NORUN
+ = MessageUtil.fail("Null IRun parameter to Runner.run(IRun..)");
+ private static final IMessage FAIL_NORUN_ITERATOR
+ = MessageUtil.fail("Null IRunterator parameter to Runner.run(IRunterator...)");
+
+ public Runner() {
+ }
+
+ /**
+ * Do the run, setting run status, invoking
+ * listener, and aborting as necessary.
+ * If the run is null, the status is
+ * updated, but the listener is never run.
+ * If the listener is null, then the runner does a lookup
+ * for the listeners of this run type.
+ * Any exceptions thrown by the listener(s) are added
+ * to the status messages and processing continues.
+ * unless the status is aborted.
+ * The status will be completed when this method completes.
+ * @param run the IRun to run - if null, issue a FAIL message
+ * to that effect in the result.
+ * @throws IllegalArgumentException if status is null
+ * @return boolean result returned from IRun
+ * or false if IRun did not complete normally
+ * or status.runResult() if aborted.
+ */
+ /* XXX later permit null status
+ * If the status is null, this tries to complete
+ * the run without a status. It ignores exceptions
+ * from the listeners, but does not catch any from the run.
+ */
+ public boolean run(IRun run, IRunStatus status,
+ IRunListener listener) {
+ return run(run, status, listener, (Class) null);
+ }
+
+ public boolean run(IRun run, IRunStatus status,
+ IRunListener listener, Class exceptionClass) {
+ if (!precheck(run, status)) {
+ return false;
+ }
+ RunListeners listeners = getListeners(run, listener);
+ return runPrivate(run, status, listeners, exceptionClass);
+
+ }
+
+ /**
+ * Run child of parent, handling interceptor registration, etc.
+ * @throws IllegalArgumentException if parent or child status is null
+ */
+ public boolean runChild(IRun child,
+ IRunStatus parentStatus,
+ IRunStatus childStatus,
+ IRunListener listener) {
+ return runChild(child, parentStatus, childStatus, listener, null);
+ }
+
+ /**
+ * Run child of parent, handling interceptor registration, etc.
+ * If the child run is supposed to throw an exception, then pass
+ * the exception class.
+ * After this returns, the childStatus is guaranteed to be completed.
+ * If an unexpected exception is thrown, an ABORT message
+ * is passed to childStatus.
+ * @param parentStatus the IRunStatus for the parent - must not be null
+ * @param childStatus the IRunStatus for the child - default will be created if null
+ * @param exceptionClass the Class of any expected exception
+ * @throws IllegalArgumentException if parent status is null
+ */
+ public boolean runChild(IRun child,
+ IRunStatus parentStatus,
+ IRunStatus childStatus,
+ IRunListener listener,
+ Class exceptionClass) {
+ if (!precheck(child, parentStatus)) {
+ return false;
+ }
+ if (null == childStatus) {
+ childStatus = new RunStatus(new MessageHandler(), this);
+ }
+ installChildStatus(child, parentStatus, childStatus);
+ if (!precheck(child, childStatus)) {
+ return false;
+ }
+ RunListeners listeners = getListeners(child, listener);
+ if (null != listeners) {
+ try {
+ listeners.addingChild(parentStatus, childStatus);
+ } catch (Throwable t) {
+ String m = "RunListenerI.addingChild(..) exception " + listeners;
+ parentStatus.handleMessage(MessageUtil.abort(m, t)); // XXX
+ }
+ }
+ boolean result = false;
+ try {
+ result = runPrivate(child, childStatus, listeners, exceptionClass);
+ } finally {
+ if (!childStatus.isCompleted()) {
+ childStatus.finish(result ? IRunStatus.PASS : IRunStatus.FAIL);
+ childStatus.handleMessage(MessageUtil.debug("XXX parent runner set completion"));
+ }
+ }
+ boolean childResult = childStatus.runResult();
+ if (childResult != result) {
+ childStatus.handleMessage(MessageUtil.info("childResult != result=" + result));
+ }
+ return childResult;
+ }
+
+ public IRunStatus makeChildStatus(IRun run, IRunStatus parent, IMessageHolder handler) {
+ return installChildStatus(run, parent, new RunStatus(handler, this));
+ }
+
+ /**
+ * Setup the child status before running
+ * @param run the IRun of the child process (not null)
+ * @param parent the IRunStatus parent of the child status (not null)
+ * @param child the IRunStatus to install - if null, a generic one is created
+ * @return the IRunStatus child status (as passed in or created if null)
+ */
+ public IRunStatus installChildStatus(IRun run, IRunStatus parent, IRunStatus child) {
+ if (null == parent) {
+ throw new IllegalArgumentException("null parent");
+ }
+ if (null == child) {
+ child = new RunStatus(new MessageHandler(), this);
+ }
+ child.setIdentifier(run); // XXX leak if ditching run...
+ parent.addChild(child);
+ return child;
+ }
+
+ /**
+ * Do a run by running all the subrunners provided by the iterator,
+ * creating a new child status of status for each.
+ * If the iterator is null, the result is
+ * updated, but the interceptor is never run.
+ * @param iterator the IRunteratorI for all the IRun to run
+ * - if null, abort (if not completed) or message to status.
+ * @throws IllegalArgumentException if status is null
+ */
+ public boolean runIterator(IRunIterator iterator, IRunStatus status,
+ IRunListener listener) {
+ LangUtil.throwIaxIfNull(status, "status");
+ if (status.aborted()) {
+ return status.runResult();
+ }
+ if (null == iterator) {
+ if (!status.isCompleted()) {
+ status.abort(IRunStatus.ABORT_NORUN);
+ } else {
+ status.handleMessage(FAIL_NORUN);
+ }
+ return false;
+ }
+ IRun wrapped = wrap(iterator, listener);
+ return run(wrapped, status, listener);
+ }
+
+ /**
+ * Signal whether to abort on failure for this run and iterator,
+ * based on the iterator and any fast-fail registrations.
+ * @param iterator the IRunIterator to stop running if this returns true
+ * @param run the IRun that failed
+ * @return true to halt further iterations
+ */
+ private boolean abortOnFailure(IRunIterator iterator, IRun run) {
+ return ((null == iterator) || iterator.abortOnFailure()); // XxX not complete
+ }
+
+
+ /**
+ * Tell Runner to stop iterating over IRun for an IRunIterator
+ * if any IRun.run(IRunStatus) fails.
+ * This overrides a false result from IRunIterator.abortOnFailure().
+ * @param iterator the IRunIterator to fast-fail - ignored if null.
+ * @see IRunIterator#abortOnFailure()
+ */
+ public void registerFastFailIterator(IRunIterator iterator) { // XXX unimplemented
+ throw new UnsupportedOperationException("ignoring " + iterator);
+ }
+
+ /**
+ * Tell Runner to stop iterating over IRun for an IRunIterator
+ * if any IRun.run(IRunStatus) fails,
+ * if the IRunIterator is assignable to iteratorType.
+ * This overrides a false result from IRunIterator.abortOnFailure().
+ * @param iteratorType the IRunIterator Class to fast-fail
+ * - ignored if null, must be assignable to IRunIterator
+ * @see IRunIterator#abortOnFailure()
+ */
+ public void registerFastFailIteratorTypes(Class iteratorType) { // XXX unimplemented
+ throw new UnsupportedOperationException("ignoring " + iteratorType);
+ }
+
+ /**
+ * Register a run listener for any run assignable to type.
+ * @throws IllegalArgumentException if either is null
+ */
+ public void registerListener(Class type, IRunListener listener) { // XXX unregister
+ if (null == type) {
+ throw new IllegalArgumentException("null type");
+ }
+ if (null == listener) {
+ throw new IllegalArgumentException("null listener");
+ }
+ ClassListeners.addListener(type, listener);
+ }
+
+ /**
+ * Wrap this IRunIterator.
+ * This wrapper takes care of calling
+ * <code>iterator.iterationCompleted()</code> when done, so
+ * after running this, clients should not invoker IRunIterator
+ * methods on this iterator.
+ * @return the iterator wrapped as a single IRun
+ */
+ public IRun wrap(IRunIterator iterator, IRunListener listener) {
+ LangUtil.throwIaxIfNull(iterator, "iterator");
+ return new IteratorWrapper(this, iterator, listener);
+ }
+
+ /**
+ * This gets any listeners registered for the run
+ * based on the class of the run (or of the wrapped iterator,
+ * if the run is an IteratorWrapper).
+ * @return a listener with all registered listener and parm,
+ * or null if parm is null and there are no listeners
+ */
+ protected RunListeners getListeners(IRun run, IRunListener listener) {
+ if (null == run) {
+ throw new IllegalArgumentException("null run");
+ }
+ Class runClass = run.getClass();
+ if (runClass == IteratorWrapper.class) {
+ IRunIterator it = ((IteratorWrapper) run).iterator;
+ if (null != it) {
+ runClass = it.getClass();
+ }
+ // fyi expecting: listener == ((IteratorWrapper) run).listener
+ }
+ RunListeners listeners = ClassListeners.getListeners(runClass);
+ if (null != listener) {
+ listeners.addListener(listener);
+ }
+ return listeners; // XXX implement registration
+ }
+
+ /** check status and run before running */
+ private boolean precheck(IRun run, IRunStatus status) {
+ if (null == status) {
+ throw new IllegalArgumentException("null status");
+ }
+ // check abort request coming in
+ if (status.aborted()) {
+ return status.runResult();
+ } else if (status.isCompleted()) {
+ throw new IllegalStateException("status completed before starting");
+ }
+
+ if (!status.started()) {
+ status.start();
+ }
+ return true;
+ }
+
+ /** This assumes precheck has happened and listeners have been obtained */
+ private boolean runPrivate(IRun run, IRunStatus status,
+ RunListeners listeners, Class exceptionClass) {
+ IRunListener listener = listeners;
+ if (null != listener) {
+ try {
+ listener.runStarting(status);
+ } catch (Throwable t) {
+ String m = listener + " RunListenerI.runStarting(..) exception";
+ IMessage mssg = new Message(m, IMessage.WARNING, t, null);
+ // XXX need IMessage.EXCEPTION - WARNING is ambiguous
+ status.handleMessage(mssg);
+ }
+ }
+ // listener can set abort request
+ if (status.aborted()) {
+ return status.runResult();
+ }
+ if (null == run) {
+ if (!status.isCompleted()) {
+ status.abort(IRunStatus.ABORT_NORUN);
+ } else {
+ status.handleMessage(MessageUtil.FAIL_INCOMPLETE);
+ }
+ return false;
+ }
+
+ boolean result = false;
+ try {
+ result = run.run(status);
+ if (!status.isCompleted()) {
+ status.finish(result?IRunStatus.PASS: IRunStatus.FAIL);
+ }
+ } catch (Throwable thrown) {
+ if (!status.isCompleted()) {
+ status.thrown(thrown);
+ } else {
+ String m = "run status completed but run threw exception";
+ status.handleMessage(MessageUtil.abort(m, thrown));
+ result = false;
+ }
+ } finally {
+ if (!status.isCompleted()) {
+ // XXX should never get here... - hides errors to set result
+ status.finish(result ? IRunStatus.PASS : IRunStatus.FAIL);
+ if (!status.isCompleted()) {
+ status.handleMessage(MessageUtil.debug("child set of status failed"));
+ }
+ }
+ }
+
+
+ try {
+ if ((null != listener) && !status.aborted()) {
+ listener.runCompleted(status);
+ }
+ } catch (Throwable t) {
+ String m = listener + " RunListenerI.runCompleted(..) exception";
+ status.handleMessage(MessageUtil.abort(m, t));
+ }
+ return result;
+ }
+
+ //---------------------------------- nested classes
+ /**
+ * Wrap an IRunIterator as a IRun, coordinating
+ * fast-fail IRunIterator and Runner
+ */
+ public static class IteratorWrapper implements IRun {
+ final Runner runner;
+ public final IRunIterator iterator;
+ final IRunListener listener;
+
+ public IteratorWrapper(Runner runner, IRunIterator iterator, IRunListener listener) {
+ LangUtil.throwIaxIfNull(iterator, "iterator");
+ LangUtil.throwIaxIfNull(runner, "runner");
+ this.runner = runner;
+ this.iterator = iterator;
+ this.listener = listener;
+ }
+
+ /** @return null */
+ public IRunStatus makeStatus() {
+ return null;
+ }
+
+ /** @return true unless one failed */
+ public boolean run(IRunStatus status) {
+ boolean result = true;
+ try {
+ int i = 0;
+ int numMessages = status.numMessages(IMessage.FAIL, IMessageHolder.ORGREATER);
+ while (iterator.hasNextRun()) {
+ IRun run = iterator.nextRun((IMessageHandler) status, runner);
+ if (null == run) {
+ MessageUtil.debug(status, "null run " + i + " from " + iterator);
+ continue;
+ }
+
+ int newMessages = status.numMessages(IMessage.FAIL, IMessageHolder.ORGREATER);
+ if (newMessages > numMessages) {
+ numMessages = newMessages;
+ String m = "run " + i + " from " + iterator
+ + " not invoked, due to fail(+) message(s) ";
+ MessageUtil.debug(status, m);
+ continue;
+ }
+ RunStatus childStatus = null; // let runChild create
+ if (!runner.runChild(run, status, childStatus, listener)) {
+ if (result) {
+ result = false;
+ }
+ if (iterator.abortOnFailure()
+ || runner.abortOnFailure(iterator, run)) {
+ break;
+ }
+ }
+ i++;
+ }
+ return result;
+ } finally {
+ iterator.iterationCompleted();
+ }
+ }
+
+ /** @return iterator, clipped to 75 char */
+ public String toString() {
+ String s = "" + iterator;
+ if (s.length() > 75) {
+ s = s.substring(0, 72) + "...";
+ }
+ return s;
+ }
+ }
+
+ /** per-class collection of IRun */
+ static class ClassListeners extends RunListeners {
+ private static final Hashtable known = new Hashtable();
+
+ static RunListeners getListeners(Class c) { // XXX costly and stupid
+ Enumeration keys = known.keys();
+ RunListeners many = new RunListeners();
+ while (keys.hasMoreElements()) {
+ Class kc = (Class) keys.nextElement();
+ if (kc.isAssignableFrom(c)) {
+ many.addListener((IRunListener) known.get(kc));
+ }
+ }
+ return many;
+ }
+
+ private static RunListeners makeListeners(Class c) {
+ RunListeners result = (ClassListeners) known.get(c);
+ if (null == result) {
+ result = new ClassListeners(c);
+ known.put(c, result);
+ }
+ return result;
+ }
+
+ static void addListener(Class c, IRunListener listener) {
+ if (null == listener) {
+ throw new IllegalArgumentException("null listener");
+ }
+ if (null == c) {
+ c = IRun.class;
+ }
+ makeListeners(c).addListener(listener);
+ }
+
+ Class clazz;
+
+ ClassListeners(Class clazz) {
+ this.clazz = clazz;
+ }
+
+ public String toString() {
+ return clazz.getName() + " ClassListeners: " + super.toString();
+ }
+ }
+}
diff --git a/testing/src/org/aspectj/testing/run/WrappedRunIterator.java b/testing/src/org/aspectj/testing/run/WrappedRunIterator.java
new file mode 100644
index 000000000..e8c8bc064
--- /dev/null
+++ b/testing/src/org/aspectj/testing/run/WrappedRunIterator.java
@@ -0,0 +1,72 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.run;
+
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.util.LangUtil;
+
+/** Adapt IRun to IRunIterator in a way that can be detected for hoisting. */
+public class WrappedRunIterator implements IRunIterator {
+ protected final Object id;
+ protected IRun run;
+
+ /**
+ * @param id the Object used for toString(), if set
+ * @param run the IRun returned from the first call to
+ * nextRun(IMessageHandler handler, Runner runner)
+ */
+ public WrappedRunIterator(Object id, IRun run) {
+ LangUtil.throwIaxIfNull(run, "run");
+ this.id = id;
+ this.run = run;
+ }
+
+ /** @return false always - we run only once anyway */
+ public boolean abortOnFailure() {
+ return false;
+ }
+
+ /**
+ * @return true until nextRun() completes
+ * @see org.aspectj.testing.run.RunIteratorI#hasNextRun()
+ */
+ public boolean hasNextRun() {
+ return (null != run);
+ }
+
+ /**
+ * @see org.aspectj.testing.run.IRunIterator#iterationCompleted()
+ */
+ public void iterationCompleted() {
+ }
+
+ /**
+ * @return the only IRun we have, and null thereafter
+ * @see org.aspectj.testing.run.RunIteratorI#nextRun(IMessageHandler, Runner)
+ */
+ public IRun nextRun(IMessageHandler handler, Runner runner) {
+ if (null == run) {
+ return null;
+ } else {
+ IRun result = run;
+ run = null;
+ return result;
+ }
+ }
+
+ /** @return name */
+ public String toString() {
+ return (null == id ? run : id).toString();
+ }
+}
diff --git a/testing/src/org/aspectj/testing/util/AccumulatingFileFilter.java b/testing/src/org/aspectj/testing/util/AccumulatingFileFilter.java
new file mode 100644
index 000000000..5ef0ecf34
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/AccumulatingFileFilter.java
@@ -0,0 +1,53 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2000 Xerox Corporation.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.util;
+
+import java.io.File;
+import java.util.Vector;
+
+/**
+ * A FileFilter that accumulates the results when called if they exist.
+ * Subclasses override accumulate to determine whether it should be
+ * accumulated.
+ */
+public class AccumulatingFileFilter extends ValidFileFilter {
+ Vector files = new Vector();
+ public final boolean accept(File f) {
+ if (super.accept(f) && (accumulate(f))) {
+ files.add(f);
+ }
+ return true;
+ }
+
+ /**
+ * This implementation accumulates everything.
+ * Subclasses should override to implement filter
+ * @param file a File guaranteed to exist
+ * @return true if file should be accumulated.
+ */
+ public boolean accumulate(File f) {
+ return true;
+ }
+ /**
+ * @return list of files currently accumulated
+ */
+ public File[] getFiles() {
+ int numFiles = files.size();
+ File[] result = new File[numFiles];
+ if (0 < numFiles) {
+ files.copyInto(result);
+ }
+ return result;
+ }
+}
diff --git a/testing/src/org/aspectj/testing/util/BridgeUtil.java b/testing/src/org/aspectj/testing/util/BridgeUtil.java
new file mode 100644
index 000000000..8e4b60fca
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/BridgeUtil.java
@@ -0,0 +1,439 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.ISourceLocation;
+import org.aspectj.bridge.Message;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.bridge.SourceLocation;
+import org.aspectj.testing.run.IRunStatus;
+import org.aspectj.testing.run.RunValidator;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LineReader;
+
+import java.io.File;
+import java.util.Comparator;
+
+/**
+ *
+ */
+public class BridgeUtil {
+
+ private static final String INDENT = " ";
+
+ /** result value when writeMessage is passed null */
+ private static final String NULL_MESSAGE_OUTPUT = "<null message output>";
+
+ /** result value when readMessage is passed null */
+ private static final IMessage NULL_MESSAGE_INPUT = null;
+
+ private static final String KIND_DELIM = ": \"";
+ private static final String MESSAGE_DELIM = "\" - ";
+
+
+ public static ISourceLocation makeSourceLocation(LineReader reader) {
+ LangUtil.throwIaxIfNull(reader, "reader");
+ int line = reader.getLineNumber();
+
+ return new SourceLocation(reader.getFile(), line, line, 0);
+ }
+
+
+ /**
+ * Method readSourceLocation.
+ * @param sourceLocStr
+ * @return ISourceLocation
+ */
+ private static ISourceLocation readSourceLocation(String sourceLocStr) {
+ return BridgeUtil.makeSourceLocation(sourceLocStr);
+ }
+ public static IMessage makeMessage(String message, IMessage.Kind kind,
+ Throwable thrown, LineReader reader) {
+ ISourceLocation sl = (null == reader ? null : MessageUtil.makeSourceLocation(reader));
+ if (null == kind) kind = IMessage.INFO;
+ return new Message(message, kind, thrown, sl);
+ }
+
+ /**
+ * Read a message from a string written by writeMessage(IMessage).
+ * Does not handle exceptions at all or source location well. XXX
+ * @param message the String representation of a message
+ * @return IMessage
+ */
+ public static IMessage readMessage(String message) {
+ if (null == message) {
+ return NULL_MESSAGE_INPUT;
+ }
+ if (NULL_MESSAGE_OUTPUT.equals(message)) {
+ return null;
+ }
+ int kindEnd = message.indexOf(KIND_DELIM);
+ int messageEnd = message.indexOf(MESSAGE_DELIM);
+ int messageStart = kindEnd+KIND_DELIM.length();
+ int sourceLocStart = messageEnd+MESSAGE_DELIM.length();
+ String kindStr = message.substring(0, kindEnd);
+ String text = message.substring(messageStart, messageEnd);
+ String sourceLocStr = message.substring(sourceLocStart);
+ IMessage.Kind kind = MessageUtil.getKind(kindStr);
+ ISourceLocation loc = readSourceLocation(sourceLocStr);
+ return new Message(text, kind, null, loc);
+ }
+
+
+ /**
+ * Write a message to a string to be read by readMessage(String)
+ * @param message the String representation of a message
+ * @return IMessage
+ */
+ public static String writeMessage(IMessage message) {
+ if (null == message) {
+ return NULL_MESSAGE_OUTPUT;
+ }
+ return message.getKind()
+ + KIND_DELIM
+ + message.getMessage()
+ + MESSAGE_DELIM
+ + message.getISourceLocation(); // XXX implement
+ }
+
+
+ public static class Comparators {
+ /**
+ * This is a weak ordering
+ * so use only for sorts, not to maintain maps.
+ * It returns 0 if one file path is a suffix of the other
+ * or a case-insensitive string comparison otherwise.
+ */
+ public static final Comparator WEAK_File = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ File one = (File) o1;
+ File two = (File) o2;
+ if (null == one) {
+ return (null == two ? 0 : 1);
+ } else if (null == two) {
+ return -1;
+ } else if (one == two) {
+ return 0;
+ } else {
+ String s1 = FileUtil.weakNormalize(one.getPath());
+ String s2 = FileUtil.weakNormalize(two.getPath());
+ if (s1.endsWith(s2) || s2.endsWith(s1)) {
+ return 0;
+ } else {
+ return String.CASE_INSENSITIVE_ORDER.compare(s1, s2);
+ }
+ }
+ }};
+ /**
+ * Ordering ignores filename,
+ * so use only for sorts, not to maintain maps
+ */
+ public static final Comparator WEAK_ISourceLocation = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ ISourceLocation one = (ISourceLocation) o1;
+ ISourceLocation two = (ISourceLocation) o2;
+ if (null == one) {
+ return (null == two ? 0 : 1);
+ } else if (null == two) {
+ return -1;
+ } else if (one == two) {
+ return 0;
+ } else { // XXX start with File to make strong
+ int i1 = one.getLine();
+ int i2 = two.getLine();
+ return i1 - i2;
+// // defer subcomparisons until messages complete
+// if (i1 != i2) {
+// return i1 - i2;
+// } else {
+// i1 = one.getColumn();
+// i2 = two.getColumn();
+// if (i1 != i2) {
+// return i1 - i2;
+// } else {
+// i1 = one.getEndLine();
+// i2 = two.getEndLine();
+// return i1 - i2;
+// }
+// }
+ }
+ }};
+
+ /**
+ * Ordering ignores filename and message
+ * so use only for sorts, not to maintain maps
+ */
+ public static final Comparator WEAK_IMessage = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ IMessage one = (IMessage) o1;
+ IMessage two = (IMessage) o2;
+ if (null == one) {
+ return (null == two ? 0 : 1);
+ } else if (null == two) {
+ return -1;
+ } else if (one == two) {
+ return 0;
+ } else {
+ IMessage.Kind kind1 = one.getKind();
+ IMessage.Kind kind2= two.getKind();
+ if (kind1 != kind2) {
+ return IMessage.Kind.COMPARATOR.compare(kind1, kind2);
+ } else {
+ ISourceLocation sl1 = one.getISourceLocation();
+ ISourceLocation sl2 = two.getISourceLocation();
+ return WEAK_ISourceLocation.compare(sl1, sl2);
+ }
+ }
+ }};
+
+ }
+
+ public static SourceLocation makeSourceLocation(String input) { // XXX only for testing, not production
+ return makeSourceLocation(input, (File) null);
+ }
+
+ public static SourceLocation makeSourceLocation(String input, String path) {
+ return makeSourceLocation(input, (null == path ? null : new File(path)));
+ }
+
+ /** attempt to create a source location from the input */
+ public static SourceLocation makeSourceLocation(String input, File defaultFile) {
+ /*
+ * Forms interpreted:
+ * # - line
+ * file - file
+ * file:# - file, line
+ * #:# - if defaultFile is not null, then file, line, column
+ * file:#:# - file, line, column
+ * file:#:#:? - file, line, column, message
+ */
+ SourceLocation result = null;
+ if ((null == input) || (0 == input.length())) {
+ if (null == defaultFile) {
+ return null;
+ } else {
+ return new SourceLocation(defaultFile, 0, 0, 0);
+ }
+ }
+ input = input.trim();
+
+ String path = null;
+ int line = 0;
+ int endLine = 0;
+ int column = 0;
+ String message = null;
+
+ // first try line only
+ line = convert(input);
+ if (-1 != line) {
+ return new SourceLocation(defaultFile, line, line, 0);
+ }
+
+ // if not a line - must be > 2 characters
+ if (3 > input.length()) {
+ return null; // throw new IllegalArgumentException("too short: " + input);
+ }
+ final String fixTag = "FIXFIX";
+ if (input.charAt(1) == ':') { // windows drive ambiguates ":" file:line:col separator
+ input = fixTag + input.substring(0,1) + input.substring(2);
+ }
+ // expecting max: path:line:column:message
+ // if 1 colon, delimits line (to second colon or end of string)
+ // if 2 colon, delimits column (to third colon or end of string)
+ // if 3 colon, delimits column (to fourth colon or end of string)
+ // todo: use this instead??
+ final int colon1 = input.indexOf(":",2); // 2 to get past windows drives...
+ final int colon2 = (-1 == colon1?-1:input.indexOf(":", colon1+1));
+ final int colon3 = (-1 == colon2?-1:input.indexOf(":", colon2+1));
+ String s;
+ if (-1 == colon1) { // no colon; only path (number handled above)
+ path = input;
+ } else { // 1+ colon => file:line // XXX later or line:column
+ path = input.substring(0, colon1);
+ s = input.substring(colon1+1,(-1!=colon2?colon2:input.length())).trim();
+ line = convert(s);
+ if (-1 == line) {
+ return null;
+ //line = "expecting line(number) at \"" + line + "\" in " + input;
+ //throw new IllegalArgumentException(line);
+ } else if (-1 != colon2) { // 2+ colon => col
+ s = input.substring(colon2+1,(-1!=colon3?colon3:input.length())).trim();
+ column = convert(s);
+ if (-1 == column) {
+ return null;
+ //col = "expecting col(number) at \"" + col + "\" in " + input;
+ //throw new IllegalArgumentException(col);
+ } else if (-1 != colon3) { // 3 colon => message
+ message = input.substring(colon3+1); // do not trim message
+ }
+ }
+ }
+
+ if (path.startsWith(fixTag)) {
+ int len = fixTag.length();
+ path = path.substring(len, 1+len) + ":" +
+ path.substring(1+len);
+ }
+ if ((endLine == 0) && (line != 0)) {
+ endLine = line;
+ }
+ // XXX removed message/comment
+ return new SourceLocation(new File(path), line, endLine, column);
+ }
+
+ // XXX reconsider convert if used in production code
+ /**
+ * Convert String to int using ascii and optionally
+ * tolerating text
+ * @param s the String to convert
+ * @param permitText if true, pick a sequence of numbers
+ * within a possibly non-numeric String
+ * @param last if permitText, then if this is true the
+ * last sequence is used - otherwise the first is used
+ * XXX only default u.s. encodings..
+ * @return -1 or value if a valid, totally-numeric positive string 0..MAX_WIDTH
+ */
+ private static int convert(String s) {
+ return convert(s, false, false);
+ }
+
+ // XXX reconsider convert if used in production code
+ /**
+ * Convert String to int using ascii and optionally
+ * tolerating text
+ * @param s the String to convert
+ * @param permitText if true, pick a sequence of numbers
+ * within a possibly non-numeric String
+ * @param last if permitText, then if this is true the
+ * last sequence is used - otherwise the first is used
+ * XXX only default u.s. encodings..
+ * @return -1 or value if a valid, positive string 0..MAX_WIDTH
+ */
+ private static int convert(String s, boolean permitText,
+ boolean first) {
+ int result = -1;
+ int last = -1;
+ int max = s.length();
+ boolean reading = false;
+ for (int i = 0; i < max; i++) {
+ char c = s.charAt(i);
+ if ((c >= '0') && (c <= '9')) {
+ if (-1 == result) { // prefix loop
+ result = 0;
+ reading = true;
+ }
+ result = ((result * 10) + (c - '0'));
+ } else if (!permitText) {
+ return -1;
+ } else if (reading) { // from numeric -> non-numeric
+ if (first) {
+ return result;
+ } else {
+ last = result;
+ }
+ reading = false;
+ }
+ }
+ if (permitText && !first && (-1 != last) && (-1 == result)) {
+ result = last;
+ }
+ return ((0 < result) && (result < ISourceLocation.MAX_LINE) ? result : -1);
+ }
+
+ private BridgeUtil() {}
+
+ /** @return String for status header, counting children passed/failed */
+ public static String childString(IRunStatus runStatus, int numSkips, int numIncomplete) {
+ if (null == runStatus) {
+ return "((RunStatus) null)";
+ }
+ if (0 > numSkips) {
+ numSkips = 0;
+ }
+ if (0 > numIncomplete) {
+ numIncomplete = 0;
+ }
+ StringBuffer sb = new StringBuffer();
+ if (RunValidator.NORMAL.runPassed(runStatus)) {
+ sb.append("PASS ");
+ } else {
+ sb.append("FAIL ");
+ }
+ Object id = runStatus.getIdentifier();
+ if (null != id) {
+ sb.append(id.toString() + " ");
+ }
+ IRunStatus[] children = runStatus.getChildren();
+ final int numChildren = (null == children ? 0 : children.length);
+ final int numTests = numIncomplete + numChildren + numSkips;
+ int numFails = 0;
+ if (!LangUtil.isEmpty(children)) {
+ for (int i = 0; i < children.length; i++) {
+ if (!RunValidator.NORMAL.runPassed(children[i])) {
+ numFails++;
+ }
+ }
+ }
+ final int numPass = children.length - numFails;
+ sb.append(numTests + " tests");
+ if (0 < numTests) {
+ sb.append(" (");
+ }
+ if (0 < numSkips) {
+ sb.append(numSkips + " skipped");
+ if (0 < (numFails + numPass + numIncomplete)) {
+ sb.append(", ");
+ }
+ }
+ if (0 < numIncomplete) {
+ sb.append(numIncomplete + " incomplete");
+ if (0 < (numFails + numPass)) {
+ sb.append(", ");
+ }
+ }
+ if (0 < numFails) {
+ sb.append(numFails + " failed");
+ if (0 < numPass) {
+ sb.append(", ");
+ }
+ }
+ if (0 < numPass) {
+ sb.append(numPass + " passed)");
+ } else if (0 < numTests) {
+ sb.append(")");
+ }
+ return sb.toString().trim();
+ }
+
+ /** @return String for status header */
+ public static String toShortString(IRunStatus runStatus) {
+ if (null == runStatus) {
+ return "((RunStatus) null)";
+ }
+ StringBuffer sb = new StringBuffer();
+ if (RunValidator.NORMAL.runPassed(runStatus)) {
+ sb.append("PASS ");
+ } else {
+ sb.append("FAIL ");
+ }
+ Object id = runStatus.getIdentifier();
+ if (null != id) {
+ sb.append(id.toString() + " ");
+ }
+ sb.append(MessageUtil.renderCounts(runStatus));
+ return sb.toString().trim();
+ }
+
+}
diff --git a/testing/src/org/aspectj/testing/util/CollectorFileFilter.java b/testing/src/org/aspectj/testing/util/CollectorFileFilter.java
new file mode 100644
index 000000000..ef268bcaa
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/CollectorFileFilter.java
@@ -0,0 +1,88 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2000 Xerox Corporation.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.util;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Wrap FileFilter to collect any accepted
+ */
+public class CollectorFileFilter implements FileFilter {
+ /** returned from getFiles() when there are no files to get */
+ public static final List EMPTY
+ = Collections.unmodifiableList(new ArrayList(0));
+
+ /** used for collecting filters */
+ protected ArrayList files;
+
+ /** filter delegate - may be null */
+ protected final FileFilter filter;
+
+ /** return false from accept only when !alwaysTrue
+ * and filter is null or fails
+ */
+ protected final boolean alwaysTrue;
+
+ /** this(null, true) */
+ public CollectorFileFilter() {
+ this(null, true);
+ }
+
+ /*
+ * @param filter the FileFilter delegate - may be null
+ * @param alwaysTrue return false from accept only when !alwaysTrue
+ * and filter is null or fails
+ */
+ public CollectorFileFilter(FileFilter filter, boolean alwaysTrue){
+ this.filter = filter;
+ this.alwaysTrue = alwaysTrue;
+ }
+
+ /**
+ * Accept file into collection if filter is null or passes.
+ * @return false only when !alwaysTrue and filter fails.
+ */
+ public boolean accept(File f) {
+ if ((null == filter) || filter.accept(f)) {
+ add(f);
+ return true;
+ }
+ return alwaysTrue;
+ }
+
+ /** gather files */
+ protected synchronized void add(File f) {
+ if (null != f) {
+ if (null == files) {
+ files = new ArrayList();
+ }
+ files.add(f);
+ }
+ }
+
+ /**
+ * return clone of gathered-files
+ * @return EMPTY if no files or a clone of the collection otherwise
+ */
+ public synchronized List getFiles() {
+ if ((null == files) || (0 == files.size())) {
+ return EMPTY;
+ }
+ return (List) files.clone();
+ }
+}
diff --git a/testing/src/org/aspectj/testing/util/Diffs.java b/testing/src/org/aspectj/testing/util/Diffs.java
new file mode 100644
index 000000000..1adf40924
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/Diffs.java
@@ -0,0 +1,103 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.MessageUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+/** result struct for expected/actual diffs for Collection */
+public class Diffs {
+
+ // XXX List -> Collection b/c comparator orders
+ public static final Diffs NONE
+ = new Diffs("NONE", Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+
+ /** name of the thing being diffed - used only for reporting */
+ public final String label;
+
+ /** immutable List */
+ public final List missing;
+
+ /** immutable List */
+ public final List unexpected;
+
+ /** true if there are any missing or unexpected */
+ public final boolean different;
+
+ private Diffs(String label, List missing, List unexpected) {
+ this.label = label;
+ this.missing = missing;
+ this.unexpected = unexpected;
+ different = ((0 != this.missing.size())
+ || (0 != this.unexpected.size()));
+ }
+
+ public Diffs(String label, List expected, List actual, Comparator comparator) {
+ label = label.trim();
+ if (null == label) {
+ label = ": ";
+ } else if (!label.endsWith(":")) {
+ label += ": ";
+ }
+ this.label = " " + label;
+ ArrayList miss = new ArrayList();
+ ArrayList unexpect = new ArrayList();
+
+ org.aspectj.testing.util.LangUtil.makeSoftDiffs(expected, actual, miss, unexpect, comparator);
+ missing = Collections.unmodifiableList(miss);
+ unexpected = Collections.unmodifiableList(unexpect);
+ different = ((0 != this.missing.size())
+ || (0 != this.unexpected.size()));
+ }
+
+ /**
+ * Report missing and extra items to handler.
+ * For each item in missing or unexpected, this creates a {kind} IMessage with
+ * the text "{missing|unexpected} {label}: {message}"
+ * where {message} is the result of
+ * <code>MessageUtil.renderMessage(IMessage)</code>.
+ * @param handler where the messages go - not null
+ * @param kind the kind of message to construct - not null
+ * @param label the prefix for the message text - if null, "" used
+ * @see MessageUtil#renderMessage(IMessage)
+ */
+ public void report(IMessageHandler handler, IMessage.Kind kind) {
+ LangUtil.throwIaxIfNull(handler, "handler");
+ LangUtil.throwIaxIfNull(kind, "kind");
+ if (different) {
+ for (Iterator iter = missing.iterator(); iter.hasNext();) {
+ String s = MessageUtil.renderMessage((IMessage) iter.next());
+ MessageUtil.fail(handler, "missing " + label + s);
+ }
+ for (Iterator iter = unexpected.iterator(); iter.hasNext();) {
+ String s = MessageUtil.renderMessage((IMessage) iter.next());
+ MessageUtil.fail(handler, "unexpected " + label + s);
+ }
+ }
+ }
+
+ /** @return "{label}: (unexpected={#}, missing={#})" */
+ public String toString() {
+ return label + "(unexpected=" + unexpected.size()
+ + ", missing=" + missing.size() + ")";
+ }
+}
+
diff --git a/testing/src/org/aspectj/testing/util/FileUtil.java b/testing/src/org/aspectj/testing/util/FileUtil.java
new file mode 100644
index 000000000..2caa5e203
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/FileUtil.java
@@ -0,0 +1,745 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2000 Xerox Corporation.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.util;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringBufferInputStream;
+import java.io.StringWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Vector;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * misc file utilities
+ */
+public class FileUtil {
+
+ /** default filename if URL has none (i.e., a directory URL): index.html */
+ public static final String DEFAULT_URL_FILENAME = "index.html";
+
+ /**
+ * @param args the String[]
+ * <code>{ "-copy", "-srcFile" | "-srcUrl", {src}, "-destFile", {destFile} }</code>
+ */
+ public static void main (String[] args) {
+ if (null == args) return;
+ for (int i = 0; (i+4) < args.length; i++) {
+ if ("-copy".equals(args[i])) {
+ String arg = args[++i];
+ String src = null;
+ String destFile = null;
+ boolean srcIsFile = ("-srcFile".equals(arg));
+ if (srcIsFile) {
+ src = args[++i];
+ } else if ("-srcUrl".equals(arg)) {
+ src = args[++i];
+ }
+ if ((null != src) && ("-destFile".equals(args[++i]))) {
+ destFile = args[++i];
+ StringBuffer errs = new StringBuffer();
+ if (srcIsFile) {
+ copyFile(new File(src), new File(destFile), errs);
+ } else {
+ URL url = null;
+ try { url = new URL(src) ; }
+ catch (MalformedURLException e) { render(e, errs); }
+ if (null != url) {
+ copyURL(url, new File(destFile), errs);
+ }
+ }
+ if (0 < errs.length()) {
+ System.err.println("Error copying " + src + " to " + destFile);
+ System.err.println(errs.toString());
+
+ }
+ }
+ } // ("-copy".equals(args[i])){
+ }
+ } // end of main ()
+
+ /**
+ * Generate a list of missing and extra files by comparison to a
+ * timestamp, optionally excluding certain files.
+ * This is a call to select all files after a given time:
+ *
+ * <pre>Diffs d = dirDiffs(dir, givenTime, null, null, null);</pre>
+ *
+ * Given files
+ * <pre>classes/Foo.class
+ * classes/bar/Bash.class
+ * classes/Old.class
+ * classes/one/Unexpected.class
+ * classes/start.gif</pre>
+ * where only Old.class predated startTime, this is a call that
+ * reports "one/Unexpected.class" as unexpected and "Foo"
+ * as missing:
+ * <pre>String requireSuffix = ".class";
+ * String[] expectedPaths = new String[] { "Foo", "bar/Bas" };
+ * File file = new File("classes");
+ * Diffs d = dirDiffs(dir, startTime, requireSuffix,expectedPaths, true);</pre>
+ *
+ * @param label the String to use for the Diffs label
+ * @param dir the File for the dir to search
+ * @param startTime collect files modified after this time
+ * (ignored if less than 0)
+ * @param requireSuffix ignore all actual files without this suffix
+ * (ignored if null)
+ * @param expectedPaths paths (relative to dir) of the expected files
+ * (if null, none expected)
+ * @param acceptFilePrefix if true, then accept a file which
+ * differs from an expected file name only by a suffix
+ * (which need not begin with ".").
+ */
+ public static Diffs dirDiffs( // XXX too complicated, weak prefix checking
+ final String label,
+ final File dir,
+ final long startTime,
+ final String requiredSuffix,
+ final String[] expectedPaths,
+ final boolean acceptFilePrefix) {
+
+ LangUtil.throwIaxIfNull(dir, "dir");
+ final boolean checkExpected = !LangUtil.isEmpty(expectedPaths);
+
+ // normalize sources to ignore
+ final ArrayList expected = (!checkExpected ? null : new ArrayList());
+ if (checkExpected) {
+ for (int i = 0; i < expectedPaths.length; i++) {
+ String srcPath = expectedPaths[i];
+ if (!LangUtil.isEmpty(srcPath)) {
+ expected.add(org.aspectj.util.FileUtil.weakNormalize(srcPath));
+ }
+ }
+ }
+
+ // gather, normalize paths changed
+ FileFilter touchedCollector = new FileFilter() {
+ /**
+ * For files complying with time and suffix rules,
+ * return true (accumulate - unexpected)
+ * unless they match expected files,
+ * (deleting any matches from sources
+ * so the remainder is missing).
+ * @return true for unexpected files after date */
+ public boolean accept(File file) {
+ if (file.isFile()
+ && ((0 > startTime)
+ || (startTime < file.lastModified()))) {
+ String path = file.getPath();
+ if ((null == requiredSuffix) || path.endsWith(requiredSuffix)) {
+ path = org.aspectj.util.FileUtil.weakNormalize(path);
+ if (checkExpected) {
+ if (!acceptFilePrefix) {
+ // File.equals(..) does lexical compare
+ if (expected.contains(path)) {
+ expected.remove(path);
+ // found - do not add to unexpected
+ return false;
+ }
+ } else {
+ for (Iterator iter = expected.iterator();
+ iter.hasNext();
+ ) {
+ String exp = (String) iter.next();
+ if (path.startsWith(exp)) {
+ String suffix = path.substring(exp.length());
+ if (-1 == suffix.indexOf("/")) { // normalized...
+ expected.remove(path);
+ // found - do not add to unexpected
+ return false;
+ }
+ }
+ }
+ }
+ }
+ // add if is file, right time, and have or don't need suffix
+ return true;
+ }
+ }
+ // skip if not file or not right time
+ return false;
+ }
+ };
+ ArrayList unexp = new ArrayList();
+ unexp.addAll(Arrays.asList(dir.listFiles(touchedCollector)));
+
+ // report any unexpected changes
+ return new Diffs(label, expected, unexp, String.CASE_INSENSITIVE_ORDER);
+ }
+
+
+ /**
+ * Visit the entries in a zip file, halting when visitor balks.
+ * Errors are silently ignored.
+ * @throws IllegalArgumentException if zipfile or visitor is null
+ */
+ public static void visitZipEntries(ZipFile zipfile, StringVisitor visitor) {
+ visitZipEntries(zipfile, visitor, (StringBuffer) null);
+ }
+
+ /**
+ * Visit the entries in a zip file, halting when visitor balks.
+ * Errors are reported in errs, if not null.
+ * @throws IllegalArgumentException if zipfile or visitor is null
+ */
+ public static void visitZipEntries(ZipFile zipfile, StringVisitor visitor,
+ StringBuffer errs) {
+ if (null == zipfile) throw new IllegalArgumentException("null zipfile");
+ if (null == visitor) throw new IllegalArgumentException("null visitor");
+ int index = 0;
+ try {
+ Enumeration enum = zipfile.entries();
+ while (enum.hasMoreElements()) {
+ ZipEntry entry = (ZipEntry) enum.nextElement();
+ index++;
+ if (! visitor.accept(entry.getName())) {
+ break;
+ }
+ }
+ } catch (Throwable e) {
+ if (null != errs) {
+ errs.append("FileUtil.visitZipEntries error accessing entry " + index
+ + ": " + e.getMessage());
+ StringWriter sw = new StringWriter();
+ e.printStackTrace(new PrintWriter(sw));
+ errs.append(sw.toString());
+ }
+ } finally {
+ if (null != zipfile) {
+ try { zipfile.close(); }
+ catch (IOException x) {} // ignore
+ }
+ }
+ }
+
+ /**
+ * descend filesystem tree, invoking FileFilter.accept() on files.
+ * E.g., To list files from current directory:
+ * <code><pre>descendFileTree(new File("."), new FileFilter() {
+ * public boolean accept(File f){
+ * System.out.println(f.getAbsolutePath());
+ * return true;
+ * }});</code></pre>
+ * @param file root/starting point. If a file, the only one visited.
+ * @param filter supplies accept(File) routine
+ */
+ public static void descendFileTree(File file, FileFilter filter) {
+ descendFileTree(file, filter, false);
+ }
+
+ /**
+ * Descend filesystem tree, invoking FileFilter.accept() on files
+ * and, if userRecursion, on dirs. If userRecursion, accept() must
+ * call descendFileTree() again to recurse down directories.
+ * This calls fileFilter.accept(File) on all files before doing any dirs.
+ * E.g., To list only files from Unix root:
+ * <code><pre>descendFileTree(new File("/"), new FileFilter() {
+ * public boolean run(File f){
+ * System.out.println(f.getAbsolutePath());
+ * return true;
+ * }}, false);</code></pre>
+ * To list files/dir from root using user recursion:
+ * <code><pre>descendFileTree(new File("/"), new FileFilter() {
+ * public boolean run(File f){
+ * System.out.println(f.getAbsolutePath());
+ * if (f.isDirectory() && (-1 == f.getName().indexOf("CVS")))
+ * return descendFileTree(f, this, true);
+ * return true;
+ * }}, true);</code></pre>
+ * @param file root/starting point. If a file, the only one visited.
+ * @param filter supplies boolean accept(File) method
+ * @param userRecursion - if true, do accept() on dirs; else, recurse
+ * @return false if any fileFilter.accept(File) did.
+ * @throws IllegalArgumentException if file or fileFilter is null
+ */
+ public static boolean descendFileTree(File file, FileFilter fileFilter,
+ boolean userRecursion) {
+ if (null == file) {throw new IllegalArgumentException("parm File"); }
+ if (null == fileFilter){throw new IllegalArgumentException("parm FileFilter");}
+
+ if (!file.isDirectory()) {
+ return fileFilter.accept(file);
+ } else if (file.canRead()) {
+ // go through files first
+ File[] files = file.listFiles(ValidFileFilter.FILE_EXISTS);
+ if (null != files) {
+ for (int i = 0; i < files.length; i++) {
+ if (!fileFilter.accept(files[i])) {
+ return false;
+ }
+ }
+ }
+ // now recurse to handle directories
+ File[] dirs = file.listFiles(ValidFileFilter.DIR_EXISTS);
+ if (null != dirs) {
+ for (int i = 0; i < dirs.length; i++) {
+ if (userRecursion) {
+ if (!fileFilter.accept(dirs[i])) {
+ return false;
+ }
+ } else {
+ if (!descendFileTree(dirs[i], fileFilter,userRecursion)) {
+ return false;
+ }
+ }
+ }
+ }
+ } // readable directory (ignore unreadable ones)
+ return true;
+ } // descendFiles
+
+ /**
+ * Return the names of all files below a directory.
+ * If file is a directory, then all files under the directory
+ * are returned. If file is absolute or relative, all the files are.
+ * If file is a zip or jar file, then all entries in the zip or jar
+ * are listed. Entries inside those jarfiles/zipfiles are not listed.
+ * There are no guarantees about ordering.
+ * @param dir the File to list for
+ * @param results the Collection to use for the results (may be null)
+ * @throws IllegalArgumentException if null == dir
+ * @return a Collection of String of paths, including paths inside jars
+ */
+ public static Collection directoryToString(File dir, Collection results) {
+ if (null == dir) throw new IllegalArgumentException("null dir");
+ final Collection result = (results != null? results : new Vector());
+ if (isZipFile(dir)) {
+ zipFileToString(dir, result);
+ } else if (!dir.isDirectory()) {
+ throw new IllegalArgumentException("not a dir: " + dir);
+ } else {
+ AccumulatingFileFilter acFilter = new AccumulatingFileFilter() {
+ public boolean accumulate(File file) {
+ String name = file.getPath();
+ result.add(name);
+ if (isZipFile(file)) {
+ zipFileToString(file, result);
+ }
+ return true;
+ }
+ };
+ descendFileTree(dir, acFilter, false);
+ }
+ return result;
+ } // directoryToString
+
+ /**
+ * Render as String the entries in a zip or jar file,
+ * converting each to String beforehand (as jarpath!jarentry)
+ * applying policies for whitespace, etc.
+ * @param file the File to enumerate ZipEntry for
+ * @param results the Colection to use to return the FileLine - may be null
+ * @return FileLines with string as text and
+ * canonical as string modified by any canonicalizing policies.
+ */
+ public static Collection zipFileToString(final File zipfile, Collection results) {
+ Collection result = (results != null ? results : new Vector());
+ ZipFile zip = null;
+ try {
+ zip = new ZipFile(zipfile); // ZipFile.OPEN_READ| ZipFile.OPEN_DELETE); delete is 1.3 only
+ Enumeration enum = zip.entries();
+ while (enum.hasMoreElements()) {
+ results.add(renderZipEntry(zipfile, (ZipEntry) enum.nextElement()));
+ }
+ zip.close();
+ zip = null;
+ } catch (Throwable t) {
+ String err = "Error opening " + zipfile + " attempting to continue...";
+ System.err.println(err);
+ t.printStackTrace(System.err);
+ } finally {
+ if (null != zip) {
+ try { zip.close(); }
+ catch (IOException e) {
+ e.printStackTrace(System.err);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return true if file represents an existing file with a zip extension
+ */
+ public static boolean isZipFile(File f) {
+ String s = null;
+ if ((null == f) || (null == (s = f.getPath()))) {
+ return false;
+ } else {
+ return (f.canRead() && !f.isDirectory()
+ && (s.endsWith(".zip")
+ || (s.endsWith(".jar"))));
+ }
+ }
+
+ /**
+ * Render a zip/entry combination to String
+ */
+ public static String renderZipEntry(File zipfile, ZipEntry entry) {
+ String filename = (null == zipfile ? "null File" : zipfile.getName());
+ String entryname = (null == entry ? "null ZipEntry" : entry.getName());
+ return filename + "!" + entryname;
+ }
+
+ /**
+ * Write all files in directory out to jarFile
+ * @param jarFile the File to create and write to
+ * @param directory the File representing the directory to read
+ * @param mainClass the value of the main class attribute - may be null
+ */
+ public static boolean createJarFile(File jarFile, File directory,
+ String mainClass, FileFilter filter) {
+ String label = "createJarFile("+jarFile
+ +","+directory +","+mainClass +","+filter + "): ";
+ Log.signal(label + " start");
+ if (null == directory)
+ throw new IllegalArgumentException("null directory");
+ Manifest manifest = createManifest(mainClass);
+ Log.signal(label + " manifest=" + manifest);
+ JarOutputStream out = null;
+ try {
+ File jarFileDir = jarFile.getParentFile();
+ if (null == jarFileDir) {
+ Log.signal(label + " null jarFileDir");
+ } else if (!jarFileDir.exists() && !jarFileDir.mkdirs()) { // XXX convert to Error
+ Log.signal(label + " unable to create jarFileDir: " + jarFileDir);
+ }
+ OutputStream os = new FileOutputStream(jarFile);
+ out = (null == manifest ? new JarOutputStream(os)
+ : new JarOutputStream(os, manifest));
+ Log.signal(label + " out=" + out);
+ ZipAccumulator reader = new ZipAccumulator(directory, out, filter);
+ Log.signal(label + " reader=" + reader);
+ FileUtil.descendFileTree(directory, reader);
+ out.closeEntry();
+ return true;
+ } catch (IOException e) {
+ e.printStackTrace(System.err); // todo
+ } finally {
+ if (null != out) {
+ try { out.close();}
+ catch (IOException e) {} // todo ignored
+ }
+ }
+
+ return false;
+ }
+
+ protected static Manifest createManifest(String mainClass) {
+ final String mainKey = "Main-Class";
+ Manifest result = null;
+ if (null != mainClass) {
+ String entry = "Manifest-Version: 1.0\n"
+ + mainKey + ": " + mainClass + "\n";
+ try {
+ result = new Manifest(new StringBufferInputStream(entry));
+ Attributes attributes = result.getMainAttributes();
+ String main = attributes.getValue(mainKey);
+ if (null == main) {
+ attributes.putValue(mainKey, mainClass);
+ main = attributes.getValue(mainKey);
+ if (null == main) {
+ Log.signal("createManifest unable to set main "
+ + mainClass);
+ }
+ }
+ } catch (IOException e) { // todo ignoring
+ Log.signal(e, " IOException creating manifest with " + mainClass);
+ }
+ }
+ return result;
+ }
+
+
+ /** read a file out to the zip stream */
+ protected static void addFileToZip(File in, File parent,
+ ZipOutputStream out)
+ throws IOException {
+ String path = in.getCanonicalPath();
+ String parentPath = parent.getCanonicalPath();
+ if (!path.startsWith(parentPath)) {
+ throw new Error("not parent: " + parentPath + " of " + path);
+ } else {
+ path = path.substring(1+parentPath.length());
+ path = path.replace('\\', '/'); // todo: use filesep
+ }
+ ZipEntry entry = new ZipEntry(path);
+ entry.setTime(in.lastModified());
+ // todo: default behavior is DEFLATED
+
+ out.putNextEntry(entry);
+
+ InputStream input = null;
+ try {
+ input = new FileInputStream(in);
+ byte[] buf = new byte[1024];
+ int count;
+ while (0 < (count = input.read(buf, 0, buf.length))) {
+ out.write(buf, 0, count);
+ }
+ } finally {
+ if (null != input) input.close();
+ }
+ }
+
+
+ public static void returnTempDir(File dir) {
+ deleteDirectory(dir);
+ }
+
+ /** @return true if path ends with gif, properties, jpg */
+ public static boolean isResourcePath(String path) {
+ if (null == path) return false;
+ path = path.toLowerCase();
+ return (path.endsWith(".gif")
+ || path.endsWith(".properties")
+ || path.endsWith(".jpg")
+ || path.endsWith(".jpeg")
+ || path.endsWith(".props")
+ );
+ }
+
+ public static void render(Throwable t, StringBuffer err) { // todo: move
+ String name = t.getClass().getName();
+ int loc = name.lastIndexOf(".");
+ name = name.substring(1+loc);
+ err.append(name + ": " + t.getMessage() + "\n"); // todo
+ StringWriter sw = new StringWriter();
+ t.printStackTrace(new PrintWriter(sw));
+ err.append(sw.toString());
+ }
+
+ private static boolean report(StringBuffer err, String context, String status,
+ Throwable throwable) {
+ boolean failed = ((null != status) || (null != throwable));
+ if ((null != err) && (failed)) {
+ if (null != context) {
+ err.append(context);
+ }
+ if (null != status) {
+ err.append(status);
+ }
+ if (null != throwable) {
+ render(throwable, err);
+ }
+ }
+ return failed;
+ }
+
+ /**
+ * Copy file.
+ * @param src the File to copy - must exist
+ * @param dest the File for the target file or directory (will not create directories)
+ * @param err the StringBuffer for returning any errors - may be null
+ **/
+ public static boolean copyFile(File src, File dest, StringBuffer err) {
+ boolean result = false;
+ String label = "start";
+ Throwable throwable = null;
+ try {
+ if (!ValidFileFilter.FILE_EXISTS.accept(src)) {
+ label = "src file does not exist";
+ } else {
+ if (dest.isDirectory()) {
+ dest = new File(dest, src.getName());
+ }
+ if (ValidFileFilter.FILE_EXISTS.accept(dest)) {
+ label = "dest file exists";
+ }
+ boolean closeWhenDone = true;
+ result = copy(new FileInputStream(src),
+ new FileOutputStream(dest),
+ closeWhenDone);
+ }
+ label = null;
+ } catch (Throwable t) {
+ throwable = t;
+ }
+ String context = "FileUtil.copyFile(src, dest, err)";
+ boolean report = report(err, context, label, throwable);
+ return (result && report);
+ }
+
+ /**
+ * Copy URL to file.
+ * @param src the URL to copy - must exist
+ * @param dest the File for the target file or directory (will not create directories)
+ * @param err the StringBuffer for returning any errors - may be null
+ **/
+ public static boolean copyURL(URL url, File dest, StringBuffer err) { // todo untested.
+ boolean result = false;
+ String label = "start";
+ Throwable throwable = null;
+ try {
+ if (dest.isDirectory()) {
+ String filename = url.getFile();
+ if ((null == filename) || (0 == filename.length())) {
+ filename = DEFAULT_URL_FILENAME;
+ }
+ dest = new File(dest, filename);
+ }
+ if (ValidFileFilter.FILE_EXISTS.accept(dest)) {
+ label = "dest file exists";
+ }
+ boolean closeWhenDone = true;
+ result = copy(url.openConnection().getInputStream(),
+ new FileOutputStream(dest),
+ closeWhenDone);
+ label = null;
+ } catch (Throwable t) {
+ throwable = t;
+ }
+ String context = "FileUtil.copyURL(src, dest, err)"; // add actual parm to labels?
+ boolean report = report(err, context, label, throwable);
+ return (result && report);
+ }
+
+ /**
+ * Copy input to output - does not close either
+ * @param src the InputStream to copy - must exist
+ * @param dest the OutputStream for the target
+ * @param close if true, close when done
+ */
+ public static boolean copy(InputStream src, OutputStream dest,
+ boolean close)
+ throws IOException {
+ boolean result = false;
+ IOException throwable = null;
+ try {
+ byte[] buf = new byte[8*1024];
+ int count;
+ while (0 < (count = src.read(buf, 0, buf.length))) {
+ dest.write(buf, 0, count);
+ }
+ result = true;
+ } catch (IOException t) {
+ throwable = t;
+ } finally {
+ if (close) {
+ try { if (null != src) src.close(); }
+ catch (IOException e) {
+ if (null == throwable) { throwable = e; }
+ }
+ try { if (null != dest) dest.close(); }
+ catch (IOException i) {
+ if (null == throwable) { throwable = i; }
+ }
+ }
+ }
+ if (null != throwable) throw throwable;
+ return result;
+ }
+
+ /**
+ * @return true if dir was an existing directory that is now deleted
+ */
+ protected static boolean deleteDirectory(File dir) {
+ return ((null != dir)
+ && dir.exists()
+ && dir.isDirectory()
+ && FileUtil.descendFileTree(dir, DELETE_FILES, false)
+ && FileUtil.descendFileTree(dir, DELETE_DIRS, true)
+ && dir.delete());
+ }
+
+ public static String[] getPaths(File[] files) { // util
+ String[] result = new String[files.length];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = files[i].getPath(); // preserves absolute?
+ }
+ return result;
+ }
+
+ //-------- first-order, input and visible interface
+
+ protected static final FileFilter DELETE_DIRS = new FileFilter() {
+ public boolean accept(File file) {
+ return ((null != file) && file.isDirectory()
+ && file.exists() && file.delete());
+ }
+ };
+ protected static final FileFilter DELETE_FILES = new FileFilter() {
+ public boolean accept(File file) {
+ return ((null != file) && !file.isDirectory()
+ && file.exists() && file.delete());
+ }
+ };
+
+} // class FileUtil
+
+/**
+ * Localize FileUtil log/signals for now
+ * ordinary signals are ignored,
+ * but exceptions are printed to err
+ * and errors are thrown as Error
+ */
+class Log {
+ /** ordinary logging - may be suppressed */
+ public static final void signal(String s) {
+ //System.err.println(s);
+ }
+ /** print stack trace to System.err */
+ public static final void signal(Throwable t, String s) {
+ System.err.println(s);
+ t.printStackTrace(System.err);
+ }
+ /** @throws Error(s) always */
+ public static final void error(String s) {
+ throw new Error(s);
+ }
+}
+
+/** read each file out to the zip file */
+class ZipAccumulator implements FileFilter {
+ final File parentDir;
+ final ZipOutputStream out;
+ final FileFilter filter;
+ public ZipAccumulator(File parentDir, ZipOutputStream out,
+ FileFilter filter) {
+ this.parentDir = parentDir;
+ this.out = out;
+ this.filter = filter;
+ }
+ public boolean accept(File f) {
+ if ((null != filter) && (!filter.accept(f))) {
+ return false;
+ }
+ try {
+ FileUtil.addFileToZip(f, parentDir, out);
+ return true;
+ } catch (IOException e) {
+ e.printStackTrace(System.err); // todo
+ }
+ return false;
+ }
+}
+
diff --git a/testing/src/org/aspectj/testing/util/IntRange.java b/testing/src/org/aspectj/testing/util/IntRange.java
new file mode 100644
index 000000000..80d547e47
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/IntRange.java
@@ -0,0 +1,118 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2000 Xerox Corporation.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.util;
+
+
+import java.io.Serializable;
+
+/**
+ * imutable class to enforce an integer range
+ */
+public class IntRange implements IntValidator, ObjectChecker, Serializable {
+ /** no values permitted */
+ public static final IntRange NONE = new IntRange(0, 0);
+ /** 0 permitted */
+ public static final IntRange ZERO = new IntRange(0, 1);
+ /** 1 permitted */
+ public static final IntRange ONE = new IntRange(1, 2);
+ /** 0..1 permitted */
+ public static final IntRange OPTIONAL = new IntRange(0, 2);
+ /** 1..1000 permitted */
+ public static final IntRange MANY = new IntRange(1, 1001);
+
+ /** all positive numbers permitted except Integer.MAX_VALUE */
+ public static final IntRange POSITIVE = new IntRange(1, Integer.MAX_VALUE);
+ /** all negative numbers permitted */
+ public static final IntRange NEGATIVE = new IntRange(Integer.MIN_VALUE, 0);
+ /** any int number permitted except Integer.MAX_VALUE */
+ public static final IntRange ANY = new IntRange(Integer.MIN_VALUE, Integer.MAX_VALUE);
+
+ /**
+ * Make an IntRange that accepts this value
+ * (using existing if available).
+ * @throws IllegalArgumentException if value is Integer.MAX_VALUE.
+ */
+ public static final IntRange make(int value) {
+ switch (value) {
+ case (1) : return ONE;
+ case (0) : return ZERO;
+ case (Integer.MAX_VALUE) :
+ throw new IllegalArgumentException("illegal " + value);
+ default :
+ return new IntRange(value, value + 1);
+ }
+ }
+
+ public final int min;
+ public final int max;
+ private transient String cache;
+
+ /** use only for serialization
+ * @deprecated IntRange(int, int)
+ */
+ protected IntRange() {
+ min = 0;
+ max = 0;
+ }
+
+ /**
+ * @param min minimum permitted value, inclusive
+ * @param max maximum permitted value, exclusive
+ */
+ public IntRange(int min, int max) {
+ this.min = min;
+ this.max = max;
+ if (min > max) {
+ throw new IllegalArgumentException( min + " > " + max);
+ }
+ toString(); // create cache to view during debugging
+ }
+
+ /** @return true if integer instanceof Integer with acceptable intValue */
+ public final boolean isValid(Object integer) {
+ return ((integer instanceof Integer)
+ && (acceptInt(((Integer) integer).intValue())));
+ }
+
+ /** @return true if min <= value < max */
+ public final boolean acceptInt(int value) {
+ return ((value >= min) && (value < max));
+ }
+
+
+ /**
+ * @deprecated acceptInt(int)
+ * @return true if min <= value < max
+ */
+ public final boolean inRange(int value) {
+ return acceptInt(value);
+ }
+ /**
+ * @return true if, for any int x s.t. other.inRange(x)
+ * is true, this.inRange(x) is also true
+ */
+ public final boolean inRange(IntRange other) {
+ return ((null != other) && (other.min >= min)
+ && (other.max <= max));
+ }
+
+ // XXX equals(Object)
+
+ public String toString() {
+ if (null == cache) {
+ cache = "IntRange [" + min + ".." + max + "]";
+ }
+ return cache;
+ }
+}
diff --git a/testing/src/org/aspectj/testing/util/IntValidator.java b/testing/src/org/aspectj/testing/util/IntValidator.java
new file mode 100644
index 000000000..ea3074d0e
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/IntValidator.java
@@ -0,0 +1,21 @@
+/* *******************************************************************
+ * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+/**
+ * @author isberg
+ */
+public interface IntValidator {
+ /** @return true if this is a valid value */
+ public boolean acceptInt(int value);
+}
diff --git a/testing/src/org/aspectj/testing/util/IteratorWrapper.java b/testing/src/org/aspectj/testing/util/IteratorWrapper.java
new file mode 100644
index 000000000..266acb13b
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/IteratorWrapper.java
@@ -0,0 +1,147 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This iterates in order through the permutations of Lists.
+ * Order and numericity depend on the underlying list iterators
+ * and the order in which the lists are supplied to the constructor.
+ * @author isberg
+ */
+public class IteratorWrapper implements Iterator {
+
+ final List[] lists;
+ final Iterator[] iterators;
+ final Object[] current;
+ Object[] next;
+
+ /** number of elements in each array */
+ final int maxDepth;
+
+ /**
+ * Current level being iterated.
+ * Set to 0 whenever depth is incremented.
+ * Incremented when iterator for the level has no more elements.
+ */
+ int currentLevel;
+
+ /**
+ * Maximum depth iterated-to thus far.
+ * Set to 0 on initialization.
+ * Incremented when incrementing currentLevel brings it past depth.
+ * Run completes when depth = maxDepth or any new iterator has no elements
+ */
+ int depth;
+
+
+ /** @throws IllegalArgumentException if lists or any element null */
+ public IteratorWrapper(List[] lists) {
+ if (null == lists) {
+ throw new IllegalArgumentException("null lists");
+ }
+ maxDepth = lists.length;
+ currentLevel = 0;
+ depth = 0;
+ List[] temp = new List[maxDepth];
+ System.arraycopy(lists, 0, temp, 0, temp.length);
+ for (int i = 0; i < maxDepth; i++) {
+ if (null == temp[i]) {
+ throw new IllegalArgumentException("null List[" + i + "]");
+ }
+ }
+ this.lists = temp;
+ current = new Object[maxDepth];
+ iterators = new Iterator[maxDepth];
+ reset();
+ }
+
+ /** Reset to the initial state of the iterator */
+ public void reset() {
+ next = null;
+ for (int i = 0; i < lists.length; i++) {
+ iterators[i] = lists[i].iterator();
+ if (!iterators[i].hasNext()) { // one iterator is empty - never go
+ depth = maxDepth;
+ break;
+ } else {
+ current[i] = iterators[i].next();
+ }
+ }
+ if (depth < maxDepth) {
+ next = getCurrent();
+ }
+ }
+
+ /** @throws UnsupportedOperationException always */
+ public void remove() {
+ throw new UnsupportedOperationException("operation ambiguous");
+ }
+
+ public boolean hasNext() {
+ return (null != next);
+ }
+
+ /**
+ * @return Object[] with each element from the iterator of the
+ * corresponding list which was passed to the constructor for this.
+ */
+ public Object next() {
+ Object result = next;
+ next = getNext();
+ return result;
+ }
+
+ private Object[] getCurrent() {
+ Object[] result = new Object[maxDepth];
+ System.arraycopy(current, 0, result, 0, maxDepth);
+ return result;
+ }
+
+ private Object[] getNext() {
+ int initialLevel = currentLevel;
+ while (depth < maxDepth) {
+ if (iterators[currentLevel].hasNext()) {
+ current[currentLevel] = iterators[currentLevel].next();
+ if (currentLevel > initialLevel) {
+ currentLevel = 0;
+ }
+ return getCurrent();
+ } else { // pop
+ // reset this level
+ iterators[currentLevel] = lists[currentLevel].iterator();
+ if (!iterators[currentLevel].hasNext()) { // empty iterator - quit
+ depth = maxDepth;
+ return null;
+ }
+ current[currentLevel] = iterators[currentLevel].next();
+
+ // do the next level
+ currentLevel++;
+ if (currentLevel > depth) {
+ depth++;
+ }
+ }
+ }
+ return null;
+ }
+ /** @return "IteratorWrapper({{field}={value}}..)" for current, ceiling, and max */
+ public String toString() {
+ return "IteratorWrapper(currentLevel=" + currentLevel
+ + ", maxLevel=" + depth
+ + ", size=" + maxDepth + ")";
+ }
+}
diff --git a/testing/src/org/aspectj/testing/util/LangUtil.java b/testing/src/org/aspectj/testing/util/LangUtil.java
new file mode 100644
index 000000000..472d35a31
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/LangUtil.java
@@ -0,0 +1,1192 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2000 Xerox Corporation.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.util;
+
+import org.aspectj.bridge.AbortException;
+import org.aspectj.bridge.IMessage;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+
+/**
+ * misc lang utilities
+ */
+public class LangUtil {
+
+ /** Delimiter used by split(String) (and ArrayList.toString()?) */
+ public static final String SPLIT_DELIM = ", ";
+
+ /** prefix used by split(String) (and ArrayList.toString()?) */
+ public static final String SPLIT_START = "[";
+
+ /** suffix used by split(String) (and ArrayList.toString()?) */
+ public static final String SPLIT_END = "]";
+
+ /** system-dependent classpath separator */
+ public static final String CLASSPATH_SEP;
+
+ private static final String[] NONE = new String[0];
+
+ /** bad: hard-wired unix, windows, mac path separators */
+ private static final char[] SEPS = new char[] { '/', '\\', ':' };
+
+ static {
+ // XXX this has to be the wrong way to get system-dependent classpath separator
+ String ps = ";";
+ try {
+ ps = System.getProperty("path.separator");
+ if (null == ps) {
+ ps = ";";
+ String cp = System.getProperty("java.class.path");
+ if (null != cp) {
+ if (-1 != cp.indexOf(";")) {
+ ps = ";";
+ } else if (-1 != cp.indexOf(":")) {
+ ps = ":";
+ }
+ // else warn?
+ }
+ }
+ } catch (Throwable t) { // ignore
+ } finally {
+ CLASSPATH_SEP = ps;
+ }
+ }
+
+ /**
+ * @return input if any are empty or no target in input,
+ * or input with escape prefixing all original target
+ */
+ public static String escape(String input, String target, String escape) {
+ if (isEmpty(input) || isEmpty(target) || isEmpty(escape)) {
+ return input;
+ }
+ StringBuffer sink = new StringBuffer();
+ escape(input, target, escape, sink);
+ return sink.toString();
+ }
+
+
+ /**
+ * Append escaped input to sink.
+ * Cheap form of arbitrary escaping does not escape the escape String
+ * itself, but unflatten treats it as significant only before the target.
+ * (so this fails with input that ends with target).
+ */
+ public static void escape(String input, String target, String escape, StringBuffer sink) {
+ if ((null == sink) || isEmpty(input) || isEmpty(target) || isEmpty(escape)) {
+ return;
+ } else if (-1 == input.indexOf(target)) { // avoid StringTokenizer construction
+ sink.append(input);
+ return;
+ }
+ throw new Error("unimplemented");
+ }
+
+ /** flatten list per spec to sink */
+ public static void flatten(List list, FlattenSpec spec, StringBuffer sink) {
+ throwIaxIfNull(spec, "spec");
+ final FlattenSpec s = spec;
+ flatten(list, s.prefix, s.nullFlattened, s.escape, s.delim, s.suffix, sink);
+ }
+
+ /**
+ * Flatten a List to String by first converting to String[]
+ * (using toString() if the elements are not already String)
+ * and calling flatten(String[]...).
+ */
+ public static void flatten(
+ List list,
+ String prefix,
+ String nullFlattened,
+ String escape,
+ String delim,
+ String suffix,
+ StringBuffer sink) {
+ throwIaxIfNull(list, "list");
+ Object[] ra = list.toArray();
+ String[] result;
+ if (String.class == ra.getClass().getComponentType()) {
+ result = (String[]) ra;
+ } else {
+ result = new String[ra.length];
+ for (int i = 0; i < result.length; i++) {
+ if (null != ra[i]) {
+ result[i] = ra[i].toString();
+ }
+ }
+ }
+ flatten(result, prefix, nullFlattened, escape, delim, suffix, sink);
+ }
+
+
+
+
+ /** flatten String[] per spec to sink */
+ public static void flatten(String[] input, FlattenSpec spec, StringBuffer sink) {
+ throwIaxIfNull(spec, "spec");
+ final FlattenSpec s = spec;
+ flatten(input, s.prefix, s.nullFlattened, s.escape, s.delim,s.suffix, sink);
+ }
+
+
+
+
+ /**
+ * Flatten a String[] to String by writing strings to sink,
+ * prefixing with leader (if not null),
+ * using nullRendering for null entries (skipped if null),
+ * escaping any delim in entry by prefixing with escape (if not null),
+ * separating entries with delim (if not null),
+ * and suffixing with trailer (if not null).
+ * Note that nullFlattened is not processed for internal delim,
+ * and strings is not copied before processing.
+ * @param strings the String[] input - not null
+ * @param prefix the output starts with this if not null
+ * @param nullFlattened the output of a null entry - entry is skipped (no delim) if null
+ * @param escape any delim in an item will be prefixed by escape if not null
+ * @param delim two items in the output will be separated by delim if not null
+ * @param suffix the output ends with this if not null
+ * @param sink the StringBuffer to use for output
+ * @return null if sink is not null (results added to sink) or rendering otherwise
+ */
+ public static void flatten(
+ String[] strings,
+ String prefix,
+ String nullFlattened,
+ String escape,
+ String delim,
+ String suffix,
+ StringBuffer sink) {
+ throwIaxIfNull(strings, "strings");
+ if (null == sink) {
+ return;
+ }
+ final boolean haveDelim = (!isEmpty(delim));
+ final boolean haveNullFlattened = (null != nullFlattened);
+ final boolean escaping = (haveDelim && (null != escape));
+ final int numStrings = (null == strings ? 0 : strings.length);
+ if (null != prefix) {
+ sink.append(prefix);
+ }
+ for (int i = 0; i < numStrings; i++) {
+ String s = strings[i];
+ if (null == s) {
+ if (!haveNullFlattened) {
+ continue;
+ }
+ if (haveDelim && (i > 0)) {
+ sink.append(delim);
+ }
+ sink.append(nullFlattened);
+ } else {
+ if (haveDelim && (i > 0)) {
+ sink.append(delim);
+ }
+
+ if (escaping) {
+ escape(s, delim, escape, sink);
+ } else {
+ sink.append(s);
+ }
+ }
+ }
+ if (null != suffix) {
+ sink.append(suffix);
+ }
+ }
+
+ /** @return ((null == ra) || (0 == ra.length)) */
+ public static boolean isEmpty(Object[] ra) {
+ return ((null == ra) || (0 == ra.length));
+ }
+
+ /** @return ((null == s) || (0 == s.length())); */
+ public static boolean isEmpty(String s) {
+ return ((null == s) || (0 == s.length()));
+ }
+
+ /**
+ * Shorthand for "if false, throw IllegalArgumentException"
+ * @throws IllegalArgumentException "{message}" if test is false
+ */
+ public static final void throwIaxIfFalse(final boolean test, final String message) {
+ if (!test) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Shorthand for "if null, throw IllegalArgumentException"
+ * @throws IllegalArgumentException "null {name}" if o is null
+ */
+ public static final void throwIaxIfNull(final Object o, final String name) {
+ if (null == o) {
+ String message = "null " + (null == name ? "input" : name);
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ public static ArrayList unflatten(String input, FlattenSpec spec) {
+ throwIaxIfNull(spec, "spec");
+ final FlattenSpec s = spec;
+ return unflatten(input,s.prefix, s.nullFlattened, s.escape, s.delim, s.suffix, s.emptyUnflattened);
+ }
+
+ /**
+ * Unflatten a String to String[] by separating elements at delim,
+ * handling prefixes, suffixes, escapes, etc.
+ * Any prefix or suffix is stripped from the input
+ * (or, if not found, an IllegalArgumentException is thrown).
+ * If delim is null or empty or input contains no delim,
+ * then return new String[] {stripped input}.
+ *
+ * XXX fix comments
+ * prefixing with leader (if not null),
+ * using nullRendering for null entries (skipped if null),
+ * escaping any delim in entry by prefixing with escape (if not null),
+ * separating entries with delim (if not null),
+ * and suffixing with trailer (if not null).
+ * Note that nullRendering is not processed for internal delim,
+ * and strings is not copied before processing.
+ * @param strings the String[] input - not null
+ * @param prefix the output starts with this if not null
+ * @param nullRendering the output of a null entry - entry is skipped (no delim) if null
+ * @param escape any delim in an item will be prefixed by escape if not null
+ * @param delim two items in the output will be separated by delim if not null
+ * @param suffix the output ends with this if not null
+ * @param sink the StringBuffer to use for output
+ * @return null if sink is not null (results added to sink) or rendering otherwise
+ * @throws IllegalArgumentException if input is null
+ * or if any prefix does not start the input
+ * or if any suffix does not end the input
+ */
+ public static ArrayList unflatten(
+ String input,
+ String prefix,
+ String nullFlattened,
+ String escape,
+ String delim,
+ String suffix,
+ String emptyUnflattened) {
+ throwIaxIfNull(input, "input");
+ final boolean haveDelim = (!isEmpty(delim));
+ final boolean haveNullFlattened = (null != nullFlattened);
+ final boolean escaping = (haveDelim && (null != escape));
+ if (!isEmpty(prefix)) {
+ if (input.startsWith(prefix)) {
+ input = input.substring(prefix.length());
+ } else {
+ String s = "expecting \"" + prefix + "\" at start of " + input + "\"";
+ throw new IllegalArgumentException(s);
+ }
+ }
+ if (!isEmpty(suffix)) {
+ if (input.endsWith(suffix)) {
+ input = input.substring(0, input.length() - suffix.length());
+ } else {
+ String s = "expecting \"" + suffix + "\" at end of " + input + "\"";
+ throw new IllegalArgumentException(s);
+ }
+ }
+
+ final ArrayList result = new ArrayList();
+ if (isEmpty(input)) {
+ return result;
+ }
+ if ((!haveDelim) || (-1 == input.indexOf(delim))) {
+ result.add(input);
+ return result;
+ }
+
+ StringTokenizer st = new StringTokenizer(input, delim, true);
+ StringBuffer cur = new StringBuffer();
+ boolean lastEndedWithEscape = false;
+ boolean lastWasDelim = false;
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ System.out.println("reading " + token);
+ if (delim.equals(token)) {
+ } else {
+ result.add(token);
+ }
+ }
+ return result;
+ }
+
+ /** combine two string arrays, removing null and duplicates
+ * @return concatenation of both arrays, less null in either or dups in two
+ * @see Util#combine(Object[], Object[])
+ */
+ public static String[] combine(String[] one, String[] two) {
+ ArrayList twoList = new ArrayList();
+ twoList.addAll(org.aspectj.util.LangUtil.arrayAsList(two));
+ ArrayList result = new ArrayList();
+ if (null != one) {
+ for (int i = 0; i < one.length; i++) {
+ if (null != one[i]) {
+ twoList.remove(one[i]);
+ result.add(one[i]);
+ }
+ }
+ }
+ for (Iterator iterator = twoList.iterator(); iterator.hasNext(); ) {
+ String element = (String) iterator.next();
+ if (null != element) {
+ result.add(element);
+ }
+ }
+ return (String[]) result.toArray(NONE);
+ }
+
+ public static Properties combine(Properties dest, Properties add, boolean respectExisting) { // XXX
+ if (null == add) return dest;
+ if (null == dest) return add;
+ for (Iterator iterator = add.keySet().iterator(); iterator.hasNext(); ) {
+ String key = (String) iterator.next();
+ if (null == key) {
+ continue;
+ }
+ String value = add.getProperty(key);
+ if (null == value) {
+ continue;
+ }
+ if (! respectExisting || (null == dest.getProperty(key))) {
+ dest.setProperty(key, value);
+ }
+ }
+ return dest;
+ }
+
+ public static List arrayAsList(Object[] ra) {
+ return org.aspectj.util.LangUtil.arrayAsList(ra);
+ }
+
+ /**
+ * return the fully-qualified class names
+ * inferred from the file names in dir
+ * assuming dir is the root of the source tree
+ * and class files end with ".class".
+ * @throws Error if dir is not properly named as prefix
+ * of class files found in dir.
+ */
+ public static String[] classesIn(File dir) {
+ boolean alwaysTrue = true;
+ FileFilter filter = ValidFileFilter.CLASS_FILE;
+ CollectorFileFilter collector = new CollectorFileFilter(filter, alwaysTrue);
+ FileUtil.descendFileTree(dir, collector);
+ List list = collector.getFiles();
+ String[] result = new String[list.size()];
+ Iterator it = list.iterator();
+ String dirPrefix = dir.getPath();
+ for (int i = 0; i < result.length; i++) {
+ if (!it.hasNext()) {
+ throw new Error("unexpected end of list at " + i);
+ }
+ result[i] = fileToClassname((File) it.next(), dirPrefix);
+ }
+ return result;
+ }
+
+ /**
+ * Convert String[] to String by using conventions for
+ * split. Will ignore any entries containing SPLIT_DELIM
+ * (and write as such to errs if not null).
+ * @param input the String[] to convert
+ * @param errs the StringBuffer for error messages (if any)
+ */
+ public static String unsplit(String[] input, StringBuffer errs) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(SPLIT_START);
+ for (int i = 0; i < input.length; i++) {
+ if (-1 != input[i].indexOf(SPLIT_DELIM)) {
+ if (null != errs) {
+ errs.append("\nLangUtil.unsplit(..) - item " + i + ": \"" + input[i]
+ + " contains \"" + SPLIT_DELIM + "\"");
+ }
+ } else {
+ sb.append(input[i]);
+ if (1+i < input.length) {
+ sb.append(SPLIT_DELIM);
+ }
+ }
+ }
+ sb.append(SPLIT_END);
+ return sb.toString();
+
+ }
+
+ /**
+ * Split input into substrings on the assumption that it is
+ * either only one string or it was generated using List.toString(),
+ * with tokens
+ * <pre>SPLIT_START {string} { SPLIT_DELIM {string}} SPLIT_END<pre>
+ * (e.g., <code>"[one, two, three]"</code>).
+ */
+ public static String[] split(String s) {
+ if (null == s) {
+ return null;
+ }
+ if ((!s.startsWith(SPLIT_START)) || (!s.endsWith(SPLIT_END))) {
+ return new String[] { s };
+ }
+ s = s.substring(SPLIT_START.length(),s.length()-SPLIT_END.length());
+ final int LEN = s.length();
+ int start = 0;
+ final ArrayList result = new ArrayList();
+ final String DELIM = ", ";
+ int loc = s.indexOf(SPLIT_DELIM, start);
+ while ((start < LEN) && (-1 != loc)) {
+ result.add(s.substring(start, loc));
+ start = DELIM.length() + loc;
+ loc = s.indexOf(SPLIT_DELIM, start);
+ }
+ result.add(s.substring(start));
+ return (String[]) result.toArray(new String[0]);
+ }
+
+ public static String[] strip(String[] src, String[] toStrip) {
+ if (null == toStrip) {
+ return strip(src, NONE);
+ } else if (null == src) {
+ return strip(NONE, toStrip);
+ }
+ List slist = org.aspectj.util.LangUtil.arrayAsList(src);
+ List tlist = org.aspectj.util.LangUtil.arrayAsList(toStrip);
+ slist.removeAll(tlist);
+ return (String[]) slist.toArray(NONE);
+ }
+
+ /**
+ * Load all classes specified by args, logging success to out
+ * and fail to err.
+ */
+ public static void loadClasses(String[] args, StringBuffer out,
+ StringBuffer err) {
+ if (null != args) {
+ for (int i = 0; i < args.length; i++) {
+ try {
+ Class c = Class.forName(args[i]);
+ if (null != out) {
+ out.append("\n");
+ out.append(args[i]);
+ out.append(": ");
+ out.append(c.getName());
+ }
+ } catch (Throwable t) {
+ if (null != err) {
+ err.append("\n");
+ FileUtil.render(t, err);
+ }
+ }
+ }
+
+ }
+ }
+
+ private static String fileToClassname(File f, String prefix) {
+ // this can safely assume file exists, starts at base, ends with .class
+ // this WILL FAIL if full path with drive letter on windows
+ String path = f.getPath();
+ if (!path.startsWith(prefix)) {
+ String err = "!\"" + path + "\".startsWith(\"" + prefix + "\")";
+ throw new IllegalArgumentException(err);
+ }
+ int length = path.length() - ".class".length();
+ path = path.substring(prefix.length()+1, length);
+ for (int i = 0; i < SEPS.length; i++) {
+ path = path.replace(SEPS[i], '.');
+ }
+ return path;
+ }
+
+ public static void main (String[] args) { // todo remove as testing
+ StringBuffer err = new StringBuffer();
+ StringBuffer out = new StringBuffer();
+ for (int i = 0; i < args.length; i++) {
+ String[] names = classesIn(new File(args[i]));
+ System.err.println(args[i] + " -> " + render(names));
+ loadClasses(names, out, err);
+ }
+ if (0 < err.length()) {
+ System.err.println(err.toString());
+ }
+ if (0 < out.length()) {
+ System.out.println(out.toString());
+ }
+ }
+
+ public static String render (String[] args) { // todo move as testing
+ if ((null == args) || (1 > args.length)) {
+ return "[]";
+ }
+ boolean longFormat = (args.length < 10);
+ String sep = (longFormat ? ", " : "\n\t");
+ StringBuffer sb = new StringBuffer();
+ if (!longFormat) sb.append("[");
+ for (int i = 0; i < args.length; i++) {
+ if (0 < i) sb.append(sep);
+ sb.append(args[i]);
+ }
+ sb.append(longFormat ? "\n" : "]");
+ return sb.toString();
+ }
+
+
+ /**
+ * @param thrown the Throwable to render
+ */
+ public static String debugStr(Throwable thrown) {
+ if (null == thrown) {
+ return "((Throwable) null)";
+ } else if (thrown instanceof InvocationTargetException) {
+ return debugStr(((InvocationTargetException)thrown).getTargetException());
+ } else if (thrown instanceof AbortException) {
+ IMessage m = ((AbortException) thrown).getIMessage();
+ if (null != m) {
+ return "" + m;
+ }
+ }
+ StringWriter buf = new StringWriter();
+ PrintWriter writer = new PrintWriter(buf);
+ writer.println(thrown.getMessage());
+ thrown.printStackTrace(writer);
+ try { buf.close(); }
+ catch (IOException ioe) {}
+ return buf.toString();
+ }
+
+ /**
+ * <code>debugStr(o, false);</code>
+ * @param source the Object to render
+ */
+ public static String debugStr(Object o) {
+ return debugStr(o, false);
+ }
+
+ /**
+ * Render standard debug string for an object in normal, default form.
+ * @param source the Object to render
+ * @param recurse if true, then recurse on all non-primitives unless rendered
+ */
+ public static String debugStr(Object o, boolean recurse) {
+ if (null == o) {
+ return "null";
+ } else if (recurse) {
+ ArrayList rendering = new ArrayList();
+ rendering.add(o);
+ return debugStr(o, rendering);
+ } else {
+ Class c = o.getClass();
+ Field[] fields = c.getDeclaredFields();
+ Object[] values = new Object[fields.length];
+ String[] names = new String[fields.length];
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ names[i] = field.getName();
+ try {
+ values[i] = field.get(o);
+ if (field.getType().isArray()) {
+ List list = org.aspectj.util.LangUtil.arrayAsList((Object[]) values[i]);
+ values[i] = list.toString();
+ }
+ } catch (IllegalAccessException e) {
+ values[i] = "<IllegalAccessException>";
+ }
+ }
+ return debugStr(c, names, values);
+ }
+ }
+
+ /**
+ * recursive variant avoids cycles.
+ * o added to rendering before call.
+ */
+ private static String debugStr(Object o, ArrayList rendering) {
+ if (null == o) {
+ return "null";
+ } else if (!rendering.contains(o)) {
+ throw new Error("o not in rendering");
+ }
+ Class c = o.getClass();
+ if (c.isArray()) {
+ Object[] ra = (Object[]) o;
+ StringBuffer sb = new StringBuffer();
+ sb.append("[");
+ for (int i = 0; i < ra.length; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ rendering.add(ra[i]);
+ sb.append(debugStr(ra[i], rendering));
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+ Field[] fields = nonStaticFields(c.getFields());
+ Object[] values = new Object[fields.length];
+ String[] names = new String[fields.length];
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ names[i] = field.getName();
+ // collapse to String
+ Object value = privilegedGetField(field,o);
+ if (null == value) {
+ values[i] = "null";
+ } else if (rendering.contains(value)) {
+ values[i] = "<recursion>";
+ } else {
+ rendering.add(value);
+ values[i] = debugStr(value, rendering);
+ }
+ }
+ return debugStr(c, names, values);
+ }
+
+ /** incomplete - need protection domain */
+ private static Object privilegedGetField(final Field field, final Object o) {
+ try {
+ return AccessController.doPrivileged(new PrivilegedExceptionAction() {
+ public Object run() {
+ try {
+ return field.get(o);
+ } catch(IllegalAccessException e) {
+ return "<IllegalAccessException>";
+ }
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ return "<IllegalAccessException>";
+ }
+ }
+
+ private static Field[] nonStaticFields(Field[] fields) {
+ if (null == fields) {
+ return new Field[0];
+ }
+ int to = 0;
+ int from = 0;
+ while (from < fields.length) {
+ if (!Modifier.isStatic(fields[from].getModifiers())) {
+ if (to != from) {
+ fields[to] = fields[from];
+ }
+ to++;
+ }
+ from++;
+ }
+ if (to < from) {
+ Field[] result = new Field[to];
+ if (to > 0) {
+ System.arraycopy(fields, 0, result, 0, to);
+ }
+ fields = result;
+ }
+ return fields;
+ }
+
+ /** <code> debugStr(source, names, items, null, null, null, null)<code> */
+ public static String debugStr(Class source, String[] names, Object[] items) {
+ return debugStr(source, null, names, null, items, null, null);
+ }
+
+ /**
+ * Render standard debug string for an object.
+ * This is the normal form and an example with the default values:<pre>
+ * {className}{prefix}{{name}{infix}{value}{delimiter}}..{suffix}
+ * Structure[head=root, tail=leaf]</pre>
+ * Passing null for the formatting entries provokes the default values,
+ * so to print nothing, you should pass "". Default values:<pre>
+ * prefix: "[" SPLIT_START
+ * infix: "="
+ * delimiter: ", " SPLIT_DELIM
+ * suffix: "]" SPLIT_END
+ * @param source the Class prefix to render unqualified - omitted if null
+ * @param names the String[] (field) names of the items - omitted if null
+ * @param items the Object[] (field) values
+ * @param prefix the String to separate classname and start of name/values
+ * @param delimiter the String to separate name/value instances
+ * @param infix the String to separate name and value
+ * used only if both name and value exist
+ * @param suffix the String to delimit the end of the name/value instances
+ * used only if classname exists
+ */
+ public static String debugStr(Class source, String prefix, String[] names,
+ String infix, Object[] items, String delimiter, String suffix) {
+
+ if (null == delimiter) {
+ delimiter = SPLIT_DELIM;
+ }
+ if (null == prefix) {
+ prefix = SPLIT_START;
+ }
+ if (null == infix) {
+ infix = "=";
+ }
+ if (null == suffix) {
+ suffix = SPLIT_END;
+ }
+ StringBuffer sb = new StringBuffer();
+ if (null != source) {
+ sb.append(org.aspectj.util.LangUtil.unqualifiedClassName(source));
+ }
+ sb.append(prefix);
+ if (null == names) {
+ names = NONE;
+ }
+ if (null == items) {
+ items = NONE;
+ }
+ final int MAX
+ = (names.length > items.length ? names.length : items.length);
+ for (int i = 0; i < MAX; i++) {
+ if (i > 0) {
+ sb.append(delimiter);
+ }
+ if (i < names.length) {
+ sb.append(names[i]);
+ }
+ if (i < items.length) {
+ if (i < names.length) {
+ sb.append(infix);
+ }
+ sb.append(items[i] + "");
+ }
+ }
+ sb.append(suffix);
+ return sb.toString();
+ }
+
+ /**
+ * @return a String with the unqualified class name of the object (or "null")
+ */
+ public static String unqualifiedClassName(Object o) {
+ return unqualifiedClassName(null == o ? null : o.getClass());
+ }
+
+ /**
+ * @return a String with the unqualified class name of the class (or "null")
+ */
+ public static String unqualifiedClassName(Class c) {
+ if (null == c) {
+ return "null";
+ }
+ String name = c.getName();
+ int loc = name.lastIndexOf(".");
+ if (-1 != loc)
+ name = name.substring(1 + loc);
+ return name;
+ }
+
+ /**
+ * Calculate exact diffs and report missing and extra items.
+ * This assumes the input List are not modified concurrently.
+ * @param expectedListIn the List of expected results - treated as empty if null
+ * @param actualListIn the List of actual results - treated as empty if null
+ * @param extraListOut the List for any actual results not expected - ignored if null
+ * @param missingListOut the List for any expected results not found - ignored if null
+ * */
+ public static void makeDiffs(
+ List expectedListIn,
+ List actualListIn,
+ List missingListOut,
+ List extraListOut) {
+ if ((null == missingListOut) && (null == extraListOut)) {
+ return;
+ }
+ if (null == expectedListIn) {
+ expectedListIn = Collections.EMPTY_LIST;
+ }
+ if (null == actualListIn) {
+ actualListIn = Collections.EMPTY_LIST;
+ }
+ if ((0 == actualListIn.size()) && (0 == expectedListIn.size()) ) {
+ return;
+ }
+ BitSet actualExpected = new BitSet();
+ for (int i = 0; i < expectedListIn.size(); i++) {
+ Object expect = expectedListIn.get(i);
+ int loc = actualListIn.indexOf(expect);
+ if (-1 == loc) {
+ if (null != missingListOut) {
+ missingListOut.add(expect);
+ }
+ } else {
+ actualExpected.set(loc);
+ }
+ }
+ if (null != extraListOut) {
+ for (int i = 0; i < actualListIn.size(); i++) {
+ if (!actualExpected.get(i)) {
+ extraListOut.add(actualListIn.get(i));
+ }
+ }
+ }
+ }
+ // XXX unit test for makeSoftDiffs
+ /**
+ * Calculate potentially "soft" diffs using
+ * Comparator.compare(expected, actual).
+ * This shallow-copies and sorts the input Lists.
+ * @param expectedListIn the List of expected results - treated as empty if null
+ * @param actualListIn the List of actual results - treated as empty if null
+ * @param extraListOut the List for any actual results not expected - ignored if null
+ * @param missingListOut the List for any expected results not found - ignored if null
+ * @param comparator the Comparator for comparisons - not null
+ * @throws IllegalArgumentException if comp is null
+ */
+ public static void makeSoftDiffs( // XXX no intersect or union on collections???
+ List expectedListIn,
+ List actualListIn,
+ List missingListOut,
+ List extraListOut,
+ Comparator comparator) {
+ if ((null == missingListOut) && (null == extraListOut)) {
+ return;
+ }
+ if (null == comparator) {
+ throw new IllegalArgumentException("null comparator");
+ }
+ if (null == expectedListIn) {
+ expectedListIn = Collections.EMPTY_LIST;
+ }
+ if (null == actualListIn) {
+ actualListIn = Collections.EMPTY_LIST;
+ }
+ if ((0 == actualListIn.size()) && (0 == expectedListIn.size()) ) {
+ return;
+ }
+
+ ArrayList expected = new ArrayList();
+ expected.addAll(expectedListIn);
+ Collections.sort(expected, comparator);
+ Iterator expectedIter = expected.iterator();
+ Object exp = null;
+
+ ArrayList actual = new ArrayList();
+ actual.addAll(actualListIn);
+ Collections.sort(actual, comparator);
+ Iterator actualIter = actual.iterator();
+ Object act = null;
+
+ while (((null != act) || actualIter.hasNext())
+ && ((null != exp) || expectedIter.hasNext())) {
+ if (null == act) {
+ act = actualIter.next();
+ }
+ if (null == exp) {
+ exp = expectedIter.next();
+ }
+ int diff = comparator.compare(exp, act);
+ if (0 > diff) { // exp < act
+ if (null != missingListOut) {
+ missingListOut.add(exp);
+ exp = null;
+ }
+ } else if (0 < diff) { // exp > act
+ if (null != extraListOut) {
+ extraListOut.add(act);
+ act = null;
+ }
+ } else { // got match of actual to expected
+ // absorb all actual matching expected (duplicates)
+ while ((0 == diff) && actualIter.hasNext()) {
+ act = actualIter.next();
+ diff = comparator.compare(exp, act);
+ }
+ if (0 == diff) {
+ act = null;
+ }
+ exp = null;
+ }
+ }
+ if (null != missingListOut) {
+ if (null != exp) {
+ missingListOut.add(exp);
+ }
+ while (expectedIter.hasNext()) {
+ missingListOut.add(expectedIter.next());
+ }
+ }
+ if (null != extraListOut) {
+ if (null != act) {
+ extraListOut.add(act);
+ }
+ while (actualIter.hasNext()) {
+ extraListOut.add(actualIter.next());
+ }
+ }
+ }
+ public static class FlattenSpec {
+ /**
+ * This tells unflatten(..) to throw IllegalArgumentException
+ * if it finds two contiguous delimiters.
+ */
+ public static final String UNFLATTEN_EMPTY_ERROR
+ = "empty items not permitted when unflattening";
+ /**
+ * This tells unflatten(..) to skip empty items when unflattening
+ * (since null means "use null")
+ */
+ public static final String UNFLATTEN_EMPTY_AS_NULL
+ = "unflatten empty items as null";
+
+ /**
+ * This tells unflatten(..) to skip empty items when unflattening
+ * (since null means "use null")
+ */
+ public static final String SKIP_EMPTY_IN_UNFLATTEN
+ = "skip empty items when unflattening";
+
+ /**
+ * For Ant-style attributes: "item,item" (with escaped commas).
+ * There is no way when unflattening to distinguish
+ * values which were empty from those which were null,
+ * so all are unflattened as empty.
+ */
+ public static final FlattenSpec COMMA
+ = new FlattenSpec(null, "", "\\", ",", null, "") {
+ public String toString() { return "FlattenSpec.COMMA"; }
+ };
+
+ /** this attempts to mimic ((List)l).toString() */
+ public static final FlattenSpec LIST
+ = new FlattenSpec("[", "", null, ", ", "]", UNFLATTEN_EMPTY_ERROR) {
+ public String toString() { return "FlattenSpec.LIST"; }
+ };
+
+ /** how toString renders null values */
+ public static final String NULL = "<null>";
+ private static String r(String s) {
+ return (null == s ? NULL : s);
+ }
+
+ public final String prefix;
+ public final String nullFlattened;
+ public final String escape;
+ public final String delim;
+ public final String suffix;
+ public final String emptyUnflattened;
+ private transient String toString;
+
+ public FlattenSpec(
+ String prefix,
+ String nullRendering,
+ String escape,
+ String delim,
+ String suffix,
+ String emptyUnflattened) {
+ this.prefix = prefix;
+ this.nullFlattened = nullRendering;
+ this.escape = escape;
+ this.delim = delim;
+ this.suffix = suffix;
+ this.emptyUnflattened = emptyUnflattened;
+ throwIaxIfNull(emptyUnflattened, "use UNFLATTEN_EMPTY_AS_NULL");
+ }
+
+ public String toString() {
+ if (null == toString) {
+ toString = "FlattenSpec("
+ + "prefix=" + r(prefix)
+ + ", nullRendering=" + r(nullFlattened)
+ + ", escape=" + r(escape)
+ + ", delim=" + r(delim)
+ + ", suffix=" + r(suffix)
+ + ", emptyUnflattened=" + r(emptyUnflattened)
+ + ")";
+ }
+ return toString;
+ }
+ }
+} // class LangUtil
+
+// --------- java runs using Ant
+// /**
+// * Run a Java command separately.
+// * @param className the fully-qualified String name of the class
+// * with the main method to run
+// * @param classpathFiles the File to put on the classpath
+// * @param args to the main method of the class
+// * @param outSink the PrintStream for the output stream - may be null
+// */
+// public static void oldexecuteJava(
+// String className,
+// File[] classpathFiles,
+// String[] args,
+// PrintStream outSink) {
+// Project project = new Project();
+// project.setName("LangUtil.executeJava(" + className + ")");
+// Path classpath = new Path(project, classpathFiles[0].getAbsolutePath());
+// for (int i = 1; i < classpathFiles.length; i++) {
+// classpath.addExisting(new Path(project, classpathFiles[i].getAbsolutePath()));
+// }
+//
+// Commandline cmds = new Commandline();
+// cmds.addArguments(new String[] {className});
+// cmds.addArguments(args);
+//
+// ExecuteJava runner = new ExecuteJava();
+// runner.setClasspath(classpath);
+// runner.setJavaCommand(cmds);
+// if (null != outSink) {
+// runner.setOutput(outSink); // XXX todo
+// }
+// runner.execute(project);
+// }
+
+// public static void executeJava(
+// String className,
+// File dir,
+// File[] classpathFiles,
+// String[] args,
+// PrintStream outSink) {
+// StringBuffer sb = new StringBuffer();
+//
+// sb.append("c:/apps/jdk1.3.1/bin/java.exe -classpath \"");
+// for (int i = 0; i < classpathFiles.length; i++) {
+// if (i < 0) {
+// sb.append(";");
+// }
+// sb.append(classpathFiles[i].getAbsolutePath());
+// }
+// sb.append("\" -verbose " + className);
+// for (int i = 0; i < args.length; i++) {
+// sb.append(" " + args[i]);
+// }
+// Exec exec = new Exec();
+// Project project = new Project();
+// project.setProperty("ant.home", "c:/home/wes/aj/aspectj/modules/lib/ant");
+// System.setProperty("ant.home", "c:/home/wes/aj/aspectj/modules/lib/ant");
+// exec.setProject(new Project());
+// exec.setCommand(sb.toString());
+// exec.setDir(dir.getAbsolutePath());
+// exec.execute();
+// }
+// public static void execJavaProcess(
+// String className,
+// File dir,
+// File[] classpathFiles,
+// String[] args,
+// PrintStream outSink) throws Throwable {
+// StringBuffer sb = new StringBuffer();
+//
+// sb.append("c:\\apps\\jdk1.3.1\\bin\\java.exe -classpath \"");
+// for (int i = 0; i < classpathFiles.length; i++) {
+// if (i > 0) {
+// sb.append(";");
+// }
+// sb.append(classpathFiles[i].getAbsolutePath());
+// }
+// sb.append("\" -verbose " + className);
+// for (int i = 0; i < args.length; i++) {
+// sb.append(" " + args[i]);
+// }
+// String command = sb.toString();
+// System.err.println("launching process: " + command);
+// Process process = Runtime.getRuntime().exec(command);
+// // huh? err/out
+// InputStream errStream = null;
+// InputStream outStream = null;
+// Throwable toThrow = null;
+// int result = -1;
+// try {
+// System.err.println("waiting for process: " + command);
+// errStream = null; // process.getErrorStream();
+// outStream = null; // process.getInputStream(); // misnamed - out
+// result = process.waitFor();
+// System.err.println("Done waiting for process: " + command);
+// process.destroy();
+// } catch (Throwable t) {
+// toThrow = t;
+// } finally {
+// if (null != outStream) {
+// FileUtil.copy(outStream, System.out, false);
+// try { outStream.close(); }
+// catch (IOException e) {}
+// }
+// if (null != errStream) {
+// FileUtil.copy(errStream, System.err, false);
+// try { errStream.close(); }
+// catch (IOException e) {}
+// }
+// }
+// if (null != toThrow) {
+// throw toThrow;
+// }
+// }
+// try {
+// // show the command
+// log(command, Project.MSG_VERBOSE);
+//
+// // exec command on system runtime
+// Process proc = Runtime.getRuntime().exec(command);
+//
+// if (out != null) {
+// fos = new PrintWriter(new FileWriter(out));
+// log("Output redirected to " + out, Project.MSG_VERBOSE);
+// }
+//
+// // copy input and error to the output stream
+// StreamPumper inputPumper =
+// new StreamPumper(proc.getInputStream(), Project.MSG_INFO);
+// StreamPumper errorPumper =
+// new StreamPumper(proc.getErrorStream(), Project.MSG_WARN);
+//
+// // starts pumping away the generated output/error
+// inputPumper.start();
+// errorPumper.start();
+//
+// // Wait for everything to finish
+// proc.waitFor();
+// inputPumper.join();
+// errorPumper.join();
+// proc.destroy();
+//
+// // close the output file if required
+// logFlush();
+//
+// // check its exit value
+// err = proc.exitValue();
+// if (err != 0) {
+// if (failOnError) {
+// throw new BuildException("Exec returned: " + err, getLocation());
+// } else {
+// log("Result: " + err, Project.MSG_ERR);
+// }
+// }
+// } catch (IOException ioe) {
+// throw new BuildException("Error exec: " + command, ioe, getLocation());
+// } catch (InterruptedException ex) {}
+//
+
+
diff --git a/testing/src/org/aspectj/testing/util/LineReader.java b/testing/src/org/aspectj/testing/util/LineReader.java
new file mode 100644
index 000000000..67f971022
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/LineReader.java
@@ -0,0 +1,204 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+/*
+ * LineReader.java created on May 3, 2002
+ *
+ */
+package org.aspectj.testing.util;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.util.ArrayList;
+
+/** LineNumberReader which absorbs our lines and renders as file:line */
+public class LineReader extends LineNumberReader {
+ /** delimited multi-line output of readToBlankLine */
+ public static final String RETURN= "\n\r";
+
+ private static final String[] NONE = new String[0];
+ private static final String cSCRIPT = "#";
+ private static final String cJAVA = "//";
+ private static final String[] TESTER_LEAD = new String[] {cSCRIPT, cJAVA};
+
+ /**
+ * Convenience factory for tester suite files
+ * @return null if IOException or IllegalArgumentException thrown
+ */
+ public static final LineReader createTester(File file) {
+ return create(file, TESTER_LEAD, null);
+ }
+
+ /**
+ * convenience factory
+ * @return null if IOException or IllegalArgumentException thrown
+ */
+ public static final LineReader create(File file,
+ String[] leadComments, String[] eolComments) {
+ try {
+ FileReader reader = new FileReader(file);
+ return new LineReader(reader, file, leadComments, eolComments);
+ } catch (IllegalArgumentException e) {
+ } catch (IOException e) {
+ }
+ return null;
+ }
+
+ final private File file;
+ final private String[] eolComments;
+ final private String[] leadComments;
+
+ /**
+ * @param file the File used to open the FileReader
+ * @param leadComments the String[] to be taken as the start of
+ * comments when they are the first non-blank text on a line -
+ * pass null to signal none.
+ * @param leadComments the String[] to be taken as the start of
+ * comment anywhere on a line - pass null to signal none.
+ *@throws IllegalArgumentException if any String in
+ * leadComments or eolComments is null.
+ */
+ public LineReader(FileReader reader, File file,
+ String[] leadComments, String[] eolComments) {
+ super(reader);
+ this.file = file;
+ this.eolComments = normalize(eolComments);
+ this.leadComments = normalize(leadComments);
+ }
+ public LineReader(FileReader reader, File file) {
+ this(reader, file, null, null);
+ }
+
+ /** @return file:line */
+ public String toString() {
+ return file.getPath() + ":" + getLineNumber();
+ }
+
+ /** @return underlying file */
+ public File getFile() { return file; }
+
+ /**
+ * Reader first..last (inclusive) and return in String[].
+ * This will return (1+(last-first)) elements only if this
+ * reader has not read past the first line and there are last lines
+ * and there are no IOExceptions during reads.
+ * @param first the first line to read - if negative, use 0
+ * @param last the last line to read (inclusive)
+ * - if less than first, use first
+ * @return String[] of first..last (inclusive) lines read or
+ */
+ public String[] readLines(int first, int last) {
+ if (0 > first) first = 0;
+ if (first > last) last = first;
+ ArrayList list = new ArrayList();
+ try {
+ String line = null;
+ while (getLineNumber() < first) {
+ line = readLine();
+ if (null == line) {
+ break;
+ }
+ }
+ if (getLineNumber() > first) {
+ // XXX warn? something else read past line
+ }
+ if ((null != line) && (first == getLineNumber())) {
+ list.add(line);
+ while (last >= getLineNumber()) {
+ line = readLine();
+ if (null == line) {
+ break;
+ }
+ list.add(line);
+ }
+ }
+ } catch (IOException e) {
+ return NONE;
+ }
+ return (String[]) list.toArray(NONE);
+ }
+
+ /** Skip to next blank line
+ * @return the String containing all lines skipped (delimited with RETURN)
+ */
+ public String readToBlankLine() throws IOException {
+ StringBuffer sb = new StringBuffer();
+ String input;
+ while (null != (input = nextLine(false))) { // get next empty line to restart
+ sb.append(input);
+ sb.append(RETURN);// XXX verify/ignore/correct
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Get the next line from the input stream, stripping eol and
+ * leading comments.
+ * If emptyLinesOk is true, then this reads past lines which are
+ * empty after omitting comments and trimming until the next non-empty line.
+ * Otherwise, this returns null on reading an empty line.
+ * (The input stream is not exhausted until this
+ * returns null when emptyLines is true.)
+ * @param emptyLinesOk if false, return null if the line is empty
+ * @return next non-null, non-empty line in reader,
+ * ignoring comments
+ */
+ public String nextLine(boolean emptyLinesOk) throws IOException {
+ int len = 0;
+ String result = null;
+ do {
+ result = readLine();
+ if (result == null)
+ return null;
+ result = result.trim();
+ for (int i = 0; i < eolComments.length; i++) {
+ int loc = result.indexOf(eolComments[i]);
+ if (-1 != loc) {
+ result = result.substring(0, loc);
+ break;
+ }
+ }
+ len = result.length();
+ if (0 < len) {
+ for (int i = 0; i < leadComments.length; i++) {
+ if (result.startsWith(leadComments[i])) {
+ result = "";
+ break;
+ }
+ }
+ len = result.length();
+ }
+ len = result.length();
+ if (!emptyLinesOk && (0 == len))
+ return null;
+ } while (0 == len);
+ return result;
+ }
+
+ private String[] normalize(String[] input) {
+ if ((null == input) || (0 == input.length)) return NONE;
+ String[] result = new String[input.length];
+ System.arraycopy(input, 0, result, 0, result.length);
+ for (int i = 0; i < result.length; i++) {
+ if ((null == result[i]) || (0 == result[i].length())) {
+ throw new IllegalArgumentException("empty input at [" + i + "]");
+ }
+ }
+ return result;
+ }
+
+}
+
diff --git a/testing/src/org/aspectj/testing/util/Node.java b/testing/src/org/aspectj/testing/util/Node.java
new file mode 100644
index 000000000..11d197eb2
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/Node.java
@@ -0,0 +1,162 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+/*
+ * Node.java created on May 14, 2002
+ *
+ */
+package org.aspectj.testing.util;
+
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A node in a tree containing other Node or SpecElements items.
+ */
+public class Node { // XXX render
+ public static final Node[] EMPTY_NODES = new Node[0];
+ public static final Object[] EMPTY_ITEMS = new Object[0];
+
+ /**
+ * Visit all the SpecElements (and Node) reachable from node
+ * in depth-first order, halting if checker objects.
+ * @param node the Node to pass to checker
+ * @param itemChecker the ObjectChecker to pass items to
+ * @param nodeVisitor if not null, then use instead of recursing
+ * @return false on first objection, true otherwise
+ * @throws IllegalArgumentExcpetion if checker is null
+ */
+ public static final boolean visit(Node node, ObjectChecker itemChecker,
+ ObjectChecker nodeVisitor) {
+ if (null == node) {
+ return (null == itemChecker ? true : itemChecker.isValid(null));
+ }
+ boolean result = true;
+
+ Node[] nodes = node.getNodes();
+ for (int i = 0; result && (i < nodes.length); i++) {
+ result = (null == nodeVisitor
+ ? visit(nodes[i], itemChecker, null)
+ : nodeVisitor.isValid(nodes[i]));
+ }
+ if (result) {
+ Object[] elements = node.getItems();
+ for (int i = 0; result && (i < elements.length); i++) {
+ result = itemChecker.isValid(elements[i]);
+ }
+ }
+
+ return result;
+ }
+
+ public final String name;
+ public final Class type;
+ public final Object key;
+ protected final Object[] typeArray;
+ protected final List nodes;
+ protected final List items;
+
+ public Node() {
+ this("Node");
+ }
+
+ public Node(String name) {
+ this(name, null);
+ }
+
+ /** use the name as the key */
+ public Node(String name, Class type) {
+ this(name, type, name);
+ }
+ /** */
+ public Node(String name, Class type, Object key) {
+ if (null == name) {
+ throw new IllegalArgumentException("null name");
+ }
+ if (null == key) {
+ throw new IllegalArgumentException("null key");
+ }
+ this.name = name;
+ this.type = type;
+ this.key = key;
+ nodes = new ArrayList();
+ items = new ArrayList();
+ if (type == null) {
+ type = Object.class;
+ }
+ typeArray = (Object[]) Array.newInstance(type, 0);
+ }
+
+ /**
+ * clear all items and nodes.
+ */
+ public void clear() { // XXX synchronize
+ nodes.clear();
+ items.clear();
+ }
+
+ /**
+ * Add item to list of items
+ * unless it is null, of the wrong type, or the collection fails to add
+ * @return true if added
+ */
+ public boolean add(Object item) {
+ if (null == item)
+ throw new IllegalArgumentException("null item");
+ if ((null != type) && (!type.isAssignableFrom(item.getClass()))) {
+ return false;
+ }
+ return items.add(item);
+ }
+
+ /**
+ * Add node to list of nodes
+ * unless it is null, of the wrong type, or the collection fails to add
+ * @return true if added
+ */
+ public boolean addNode(Node node) {
+ if (null == node) {
+ throw new IllegalArgumentException("null node");
+ }
+ return nodes.add(node);
+ }
+
+ /**
+ * Get the current list of nodes - never null
+ */
+ public Node[] getNodes() {
+ if ((null == nodes) || (1 > nodes.size())) {
+ return EMPTY_NODES;
+ }
+ return (Node[]) nodes.toArray(EMPTY_NODES);
+ }
+
+ /**
+ * Get the current list of items - never null
+ * @return items in current list, cast to type[] if type was not null
+ */
+ public Object[] getItems() {
+ if ((null == items) || (1 > items.size())) {
+ return EMPTY_ITEMS;
+ }
+ return items.toArray(typeArray);
+ }
+
+ /** @return name */
+ public String toString() {
+ return name;
+ }
+}
diff --git a/testing/src/org/aspectj/testing/util/NullPrintStream.java b/testing/src/org/aspectj/testing/util/NullPrintStream.java
new file mode 100644
index 000000000..7ba8e28f9
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/NullPrintStream.java
@@ -0,0 +1,95 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+/*
+ * NullPrintStream.java created on May 29, 2002
+ *
+ */
+package org.aspectj.testing.util;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * Ignore any output to a NullPrintStream.
+ * Clients use singleton NULL_PrintStream or NULL_OutputStream.
+ * @author isberg
+ */
+public final class NullPrintStream extends PrintStream {
+
+ public static final OutputStream NULL_OutputStream = NullOutputStream.ME;
+ public static final PrintStream NULL_PrintStream = new NullPrintStream();
+
+ private NullPrintStream() {
+ super(NULL_OutputStream);
+ }
+ public void write(int b) {
+ }
+ public void write(byte[] b) {
+ }
+ public void write(byte[] b, int off, int len) {
+ }
+ public void print(boolean arg0) {
+ }
+ public void print(char arg0) {
+ }
+ public void print(char[] arg0) {
+ }
+ public void print(double arg0) {
+ }
+ public void print(float arg0) {
+ }
+ public void print(int arg0) {
+ }
+ public void print(long arg0) {
+ }
+ public void print(Object arg0) {
+ }
+ public void print(String arg0) {
+ }
+ public void println() {
+ }
+ public void println(boolean arg0) {
+ }
+ public void println(char arg0) {
+ }
+ public void println(char[] arg0) {
+ }
+ public void println(double arg0) {
+ }
+ public void println(float arg0) {
+ }
+ public void println(int arg0) {
+ }
+ public void println(long arg0) {
+ }
+ public void println(Object arg0) {
+ }
+ public void println(String arg0) {
+ }
+
+}
+
+final class NullOutputStream extends OutputStream {
+ static final OutputStream ME = new NullOutputStream();
+
+ private NullOutputStream() {
+ }
+ public void write(int b) {
+ }
+ public void write(byte[] b) {
+ }
+ public void write(byte[] b, int off, int len) {
+ }
+}
diff --git a/testing/src/org/aspectj/testing/util/ObjectChecker.java b/testing/src/org/aspectj/testing/util/ObjectChecker.java
new file mode 100644
index 000000000..e7d42755a
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/ObjectChecker.java
@@ -0,0 +1,38 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.util;
+
+/**
+ * Check input for validity.
+ */
+public interface ObjectChecker {
+ /** this returns true for any input, even if null */
+ public static final ObjectChecker ANY = new ObjectChecker() {
+ public final boolean isValid(Object input) { return true; }
+ public final String toString() { return "ObjectChecker.ANY"; }
+ };
+ /** this returns true for any non-null object */
+ public static final ObjectChecker NOT_NULL = new ObjectChecker() {
+ public boolean isValid(Object input) { return (null != input); }
+ public String toString() { return "ObjectChecker.MOT_NULL"; }
+ };
+
+ /**
+ * Check input for validity.
+ * @param input the Object to check
+ * @return true if input is ok
+ */
+ public boolean isValid(Object input);
+}
diff --git a/testing/src/org/aspectj/testing/util/ProxyPrintStream.java b/testing/src/org/aspectj/testing/util/ProxyPrintStream.java
new file mode 100644
index 000000000..378f58e36
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/ProxyPrintStream.java
@@ -0,0 +1,104 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+/** Wrap a delegate PrintStream, permitting output to be suppressed. */
+public class ProxyPrintStream extends PrintStream {
+
+ private final PrintStream delegate;
+ private boolean hiding;
+ public ProxyPrintStream(PrintStream delegate ) {
+ super(NullPrintStream.NULL_OutputStream);
+ LangUtil.throwIaxIfNull(delegate, "delegate");
+ this.delegate = delegate;
+ }
+ public void hide() {
+ hiding = true;
+ }
+ public void show() {
+ hiding = false;
+ }
+ public boolean isHiding() {
+ return hiding;
+ }
+ public void write(int b) {
+ if (!hiding) delegate.write(b);
+ }
+ public void write(byte[] b) throws IOException {
+ if (!hiding) delegate.write(b);
+ }
+ public void write(byte[] b, int off, int len) {
+ if (!hiding) delegate.write(b, off, len);
+ }
+ public void print(boolean arg0) {
+ if (!hiding) delegate.print(arg0);
+ }
+ public void print(char arg0) {
+ if (!hiding) delegate.print(arg0);
+ }
+ public void print(char[] arg0) {
+ if (!hiding) delegate.print(arg0);
+ }
+ public void print(double arg0) {
+ if (!hiding) delegate.print(arg0);
+ }
+ public void print(float arg0) {
+ if (!hiding) delegate.print(arg0);
+ }
+ public void print(int arg0) {
+ if (!hiding) delegate.print(arg0);
+ }
+ public void print(long arg0) {
+ if (!hiding) delegate.print(arg0);
+ }
+ public void print(Object arg0) {
+ if (!hiding) delegate.print(arg0);
+ }
+ public void print(String arg0) {
+ if (!hiding) delegate.print(arg0);
+ }
+ public void println() {
+ if (!hiding) delegate.println();
+ }
+ public void println(boolean arg0) {
+ if (!hiding) delegate.println(arg0);
+ }
+ public void println(char arg0) {
+ if (!hiding) delegate.println(arg0);
+ }
+ public void println(char[] arg0) {
+ if (!hiding) delegate.println(arg0);
+ }
+ public void println(double arg0) {
+ if (!hiding) delegate.println(arg0);
+ }
+ public void println(float arg0) {
+ if (!hiding) delegate.println(arg0);
+ }
+ public void println(int arg0) {
+ if (!hiding) delegate.println(arg0);
+ }
+ public void println(long arg0) {
+ if (!hiding) delegate.println(arg0);
+ }
+ public void println(Object arg0) {
+ if (!hiding) delegate.println(arg0);
+ }
+ public void println(String arg0) {
+ if (!hiding) delegate.println(arg0);
+ }
+}
diff --git a/testing/src/org/aspectj/testing/util/RunUtils.java b/testing/src/org/aspectj/testing/util/RunUtils.java
new file mode 100644
index 000000000..febfac442
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/RunUtils.java
@@ -0,0 +1,368 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.IMessageHolder;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.bridge.MessageUtil.IMessageRenderer;
+import org.aspectj.testing.harness.bridge.AbstractRunSpec;
+import org.aspectj.testing.harness.bridge.IRunSpec;
+import org.aspectj.testing.run.IRunStatus;
+import org.aspectj.testing.run.RunValidator;
+import org.aspectj.util.LangUtil;
+
+import java.io.PrintStream;
+import java.util.Iterator;
+
+/**
+ *
+ */
+public class RunUtils {
+
+ /** enable verbose for this an any related AbstractRunSpec children */
+ public static void enableVerbose(AbstractRunSpec spec) { // instanceof hack
+ LangUtil.throwIaxIfNull(spec, "spec");
+ spec.runtime.setVerbose(true);
+ for (Iterator iter = spec.getChildren().iterator(); iter.hasNext();) {
+ IRunSpec child = (IRunSpec) iter.next();
+ if (child instanceof AbstractRunSpec) {
+ enableVerbose((AbstractRunSpec) child);
+ }
+ }
+ }
+
+ /**
+ * Calculate failures for this status.
+ * If the input status has no children and failed, the result is 1.
+ * If it has children and recurse is false, then
+ * the result is the number of children whose status has failed
+ * (so a failed status with some passing and no failing children
+ * will return 0).
+ * If it has children and recurse is true,
+ * then return the number of leaf failures in the tree,
+ * ignoring (roll-up) node failures.
+ * @return number of failures in children of this status
+ */
+ public static int numFailures(IRunStatus status, boolean recurse) {
+ int numFails = 0;
+ IRunStatus[] children = status.getChildren();
+ int numChildren = (null == children? 0 : children.length);
+ if (0 == numChildren) {
+ if (!RunValidator.NORMAL.runPassed(status)) {
+ return 1;
+ }
+ } else {
+ int i = 0;
+ for (int j = 0; j < children.length; j++) {
+ if (recurse) {
+ numFails += numFailures(children[j], recurse);
+ } else {
+ if (!RunValidator.NORMAL.runPassed(children[j])) {
+ numFails++;
+ }
+ }
+ }
+ }
+ return numFails;
+ }
+
+ // ------------------------ printing status
+ public static void printShort(PrintStream out, IRunStatus status) {
+ if ((null == out) || (null == status)) {
+ return;
+ }
+ printShort(out, "", status);
+ }
+
+ public static void printShort(PrintStream out, String prefix, IRunStatus status) {
+ int numFails = numFailures(status, true);
+ String fails = (0 == numFails ? "" : " - " + numFails + " failures");
+ out.println(prefix + toShortString(status) + fails);
+ IRunStatus[] children = status.getChildren();
+ int numChildren = (null == children? 0 : children.length);
+ if (0 < numChildren) {
+ int i = 0;
+ for (int j = 0; j < children.length; j++) {
+ printShort(out, prefix + "[" + LangUtil.toSizedString(i++, 3) + "]: ", children[j]);
+ if (!RunValidator.NORMAL.runPassed(children[j])) {
+ numFails++;
+ }
+ }
+ }
+ }
+
+ public static void print(PrintStream out, IRunStatus status) {
+ if ((null == out) || (null == status)) {
+ return;
+ }
+ print(out, "", status);
+ }
+
+ public static void print(PrintStream out, String prefix, IRunStatus status) {
+ print(out, prefix, status, MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ALL);
+ }
+
+ public static void print(PrintStream out, String prefix, IRunStatus status,
+ IMessageRenderer renderer, IMessageHandler selector) {
+ String label = status.getIdentifier()
+ + (status.runResult() ? "PASS" : "FAIL");
+ out.println(prefix + label);
+ out.println(prefix + debugString(status));
+ IMessageHolder messageHolder = status;
+ if ((null != messageHolder) && (0 < messageHolder.numMessages(null, true))) {
+ MessageUtil.print(out, messageHolder, prefix, renderer, selector);
+ }
+ Throwable thrown = status.getThrown();
+ if (null != thrown) {
+ out.println(prefix + "--- printing stack trace for thrown");
+ thrown.printStackTrace(out);
+ }
+ IRunStatus[] children = status.getChildren();
+ int numChildren = (null == children? 0 : children.length);
+ int numFails = 0;
+ if (0 < numChildren) {
+ out.println(prefix + "--- printing children [" + numChildren + "]");
+ int i = 0;
+ for (int j = 0; j < children.length; j++) {
+ print(out, prefix + "[" + LangUtil.toSizedString(i++, 3) + "]: ", children[j]);
+ if (!RunValidator.NORMAL.runPassed(children[j])) {
+ numFails++;
+ }
+ }
+ }
+ if (0 < numFails) {
+ label = numFails + " fails " + label;
+ }
+ out.println("");
+ }
+
+
+ public static String debugString(IRunStatus status) {
+ if (null == status) {
+ return "null";
+ }
+ final String[] LABELS =
+ new String[] {
+ "runResult",
+ "id",
+ "result",
+ "numChildren",
+ "completed",
+ //"parent",
+ "abort",
+ "started",
+ "thrown",
+ "messages" };
+ String runResult = status.runResult() ? "PASS" : "FAIL";
+ Throwable thrown = status.getThrown();
+ String thrownString = LangUtil.unqualifiedClassName(thrown);
+ IRunStatus[] children = status.getChildren();
+ String numChildren = (null == children? "0" : ""+children.length);
+ String numMessages = ""+status.numMessages(null, IMessageHolder.EQUAL);
+ Object[] values =
+ new Object[] {
+ runResult,
+ status.getIdentifier(),
+ status.getResult(),
+ numChildren,
+ new Boolean(status.isCompleted()),
+ //status.getParent(), // costly if parent printing us
+ status.getAbortRequest(),
+ new Boolean(status.started()),
+ thrownString,
+ numMessages };
+ return org.aspectj.testing.util.LangUtil.debugStr(status.getClass(), LABELS, values);
+ }
+
+ public static String toShortString(IRunStatus status) {
+ if (null == status) {
+ return "null";
+ }
+ String runResult = status.runResult() ? " PASS: " : " FAIL: ";
+ return (runResult + status.getIdentifier());
+ }
+
+ /** renderer for IRunStatus */
+ public static interface IRunStatusPrinter {
+ void printRunStatus(PrintStream out, IRunStatus status);
+ }
+
+ public static final IRunStatusPrinter VERBOSE_PRINTER = new IRunStatusPrinter() {
+ public String toString() { return "VERBOSE_PRINTER"; }
+ /** Render IRunStatus produced by running an AjcTest */
+ public void printRunStatus(PrintStream out, IRunStatus status) {
+ printRunStatus(out, status, "");
+ }
+ private void printRunStatus(PrintStream out, IRunStatus status, String prefix) {
+ LangUtil.throwIaxIfNull(out, "out");
+ LangUtil.throwIaxIfNull(status, "status");
+ String label = (status.runResult() ? " PASS: " : " FAIL: ")
+ + status.getIdentifier();
+ out.println(prefix + "------------ " + label);
+ out.println(prefix + "--- result: " + status.getResult());
+ if (0 < status.numMessages(null, true)) {
+ out.println(prefix + "--- messages ");
+ MessageUtil.print(out, status, prefix, MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ALL);
+ }
+ Throwable thrown = status.getThrown();
+ if (null != thrown) {
+ out.println(prefix + "--- thrown");
+ thrown.printStackTrace(out);
+ }
+ IRunStatus[] children = status.getChildren();
+ for (int i = 0; i < children.length; i++) {
+ String number = "[" + LangUtil.toSizedString(i,3) + "] ";
+ printRunStatus(out, children[i], prefix + number);
+ }
+ }
+ };
+
+ /** print only status and fail/abort messages */
+ public static final IRunStatusPrinter TERSE_PRINTER = new IRunStatusPrinter() {
+ public String toString() { return "TERSE_PRINTER"; }
+
+ /** print only status and fail messages */
+ public void printRunStatus(PrintStream out, IRunStatus status) {
+ printRunStatus(out, status, "");
+ }
+ private void printRunStatus(PrintStream out, IRunStatus status, String prefix) {
+ LangUtil.throwIaxIfNull(out, "out");
+ LangUtil.throwIaxIfNull(status, "status");
+ String label = (status.runResult() ? "PASS: " : "FAIL: ")
+ + status.getIdentifier();
+ out.println(prefix + label);
+ Object result = status.getResult();
+ if ((null != result) && (IRunStatus.PASS != result) && (IRunStatus.FAIL != result)) {
+ out.println(prefix + "--- result: " + status.getResult());
+ }
+ if (0 < status.numMessages(IMessage.FAIL, true)) {
+ MessageUtil.print(out, status, prefix, MessageUtil.MESSAGE_ALL, MessageUtil.PICK_FAIL_PLUS);
+ }
+ Throwable thrown = status.getThrown();
+ if (null != thrown) {
+ out.println(prefix + "--- thrown: " + LangUtil.renderException(thrown, true));
+ }
+ IRunStatus[] children = status.getChildren();
+ for (int i = 0; i < children.length; i++) {
+ if (!children[i].runResult()) {
+ String number = "[" + LangUtil.toSizedString(i,3) + "] ";
+ printRunStatus(out, children[i], prefix + number);
+ }
+ }
+ out.println("");
+ }
+ };
+
+ /** Render IRunStatus produced by running an AjcTest.Suite. */
+ public static final IRunStatusPrinter AJCSUITE_PRINTER = new IRunStatusPrinter() {
+ public String toString() { return "AJCSUITE_PRINTER"; }
+
+ /**
+ * Render IRunStatus produced by running an AjcTest.Suite.
+ * This renders only test failures and
+ * a summary at the end.
+ */
+ public void printRunStatus(PrintStream out, IRunStatus status) {
+ LangUtil.throwIaxIfNull(out, "out");
+ LangUtil.throwIaxIfNull(status, "status");
+ final String prefix = "";
+ final boolean failed = status.runResult();
+ String label = (status.runResult() ? "PASS: " : "FAIL: ")
+ + status.getIdentifier();
+ out.println(prefix + label);
+ // print all messages - these are validator comments
+ if (0 < status.numMessages(null, true)) {
+ MessageUtil.print(out, status, "init", MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ALL);
+ }
+ // XXX ignore thrown if failed - will be printed as message anyway?
+ Throwable thrown = status.getThrown();
+ if ((null != thrown) && !failed) {
+ out.println(prefix + "--- printing stack trace for thrown");
+ thrown.printStackTrace(out);
+ }
+ IRunStatus[] children = status.getChildren();
+ int numChildren = (null == children? 0 : children.length);
+ int numFails = 0;
+ if (0 < numChildren) {
+ for (int j = 0; j < children.length; j++) {
+ if (!RunValidator.NORMAL.runPassed(children[j])) {
+ numFails++;
+ }
+ }
+ }
+ if (0 < numFails) {
+ out.println(prefix + "--- " + numFails + " failures when running " + children.length + " tests");
+ for (int j = 0; j < children.length; j++) {
+ if (!RunValidator.NORMAL.runPassed(children[j])) {
+ print(out, prefix + "[" + LangUtil.toSizedString(j, 3) + "]: ", children[j]);
+ out.println("");
+ }
+ }
+ }
+ label = "ran " + children.length + " tests"
+ + (numFails == 0 ? "" : "(" + numFails + " fails)");
+ out.println("");
+ }
+
+ };
+ /** Render IRunStatus produced by running an AjcTest (verbose) */
+ public static final IRunStatusPrinter AJCTEST_PRINTER = VERBOSE_PRINTER;
+
+ /** print this with messages, then children using AJCRUN_PRINTER */
+ public static final IRunStatusPrinter AJC_PRINTER = new IRunStatusPrinter() {
+ public String toString() { return "AJC_PRINTER"; }
+ /** Render IRunStatus produced by running an AjcTest */
+ public void printRunStatus(PrintStream out, IRunStatus status) {
+ LangUtil.throwIaxIfNull(out, "out");
+ LangUtil.throwIaxIfNull(status, "status");
+ String label = (status.runResult() ? " PASS: " : " FAIL: ")
+ + status.getIdentifier();
+ out.println("------------ " + label);
+ MessageUtil.print(out, status, "", MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ALL);
+ IRunStatus[] children = status.getChildren();
+ for (int i = 0; i < children.length; i++) {
+ AJCRUN_PRINTER.printRunStatus(out, children[i]);
+ }
+ //out.println("------------ END " + label);
+ out.println("");
+ }
+ };
+
+
+ /** print only fail messages */
+ public static final IRunStatusPrinter AJCRUN_PRINTER = new IRunStatusPrinter() {
+ public String toString() { return "AJCRUN_PRINTER"; }
+ /** Render IRunStatus produced by running an AjcTest child */
+ public void printRunStatus(PrintStream out, IRunStatus status) {
+ LangUtil.throwIaxIfNull(out, "out");
+ LangUtil.throwIaxIfNull(status, "status");
+ final boolean orGreater = false;
+ int numFails = status.numMessages(IMessage.FAIL, orGreater);
+ if (0 < numFails) {
+ out.println("--- " + status.getIdentifier());
+ IMessage[] fails = status.getMessages(IMessage.FAIL, orGreater);
+ for (int i = 0; i < fails.length; i++) {
+ out.println("[fail " + LangUtil.toSizedString(i, 3) + "]: "
+ + MessageUtil.MESSAGE_ALL.renderToString(fails[i]));
+ }
+ }
+ }
+ };
+
+ private RunUtils() {
+ }
+
+}
diff --git a/testing/src/org/aspectj/testing/util/SFileReader.java b/testing/src/org/aspectj/testing/util/SFileReader.java
new file mode 100644
index 000000000..f2b57e13d
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/SFileReader.java
@@ -0,0 +1,185 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import org.aspectj.bridge.AbortException;
+import org.aspectj.bridge.IMessage;
+import org.aspectj.util.LangUtil;
+import org.aspectj.util.LineReader;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+
+/**
+ * This reads a structured (config) file, which may have
+ * lines with @ signalling a recursive read
+ * and EOL comments # or //.
+ * This duplicates ConfigFileUtil in some sense.
+ */
+public class SFileReader {
+ // XXX move into LineReader, but that forces util to depend on AbortException?
+ // Formerly in SuiteReader
+
+ /**
+ * Read args as config files and echo to stderr.
+ * @param args String[] of fully-qualified paths to config files
+ */
+ public static void main(String[] args) throws IOException {
+ ArrayList result = new ArrayList();
+ ObjectChecker collector = new StandardObjectChecker(String.class, result);
+ SFileReader me = new SFileReader(null);
+ for (int i = 0; i < args.length; i++) {
+ Node node = me.readNodes(new File(args[i]), null, true, System.err);
+ if (!Node.visit(node, collector, null)) {
+ System.err.println("halted during copy of " +args[i]);
+ } else {
+ String s = org.aspectj.testing.util.LangUtil.debugStr(null, "\n ", null,
+ null, result.toArray(), "\n ", "");
+ System.err.println(args[i] + ": " + s);
+ }
+ }
+ }
+
+ /*
+ * readSuite(..) reads .txt file, and for each test case specification
+ * creates a spec using readTestSpecifications
+ * and (if the specifications match the constraints)
+ * creates a test case using creatTestCase.
+ */
+
+ /** pass this to protected methods requiring String[] if you have none */
+ protected static final String[] NONE = new String[0];
+
+ final Maker maker;
+
+ /** @param maker the Maker factory to use - if null, use Maker.ECHO */
+ public SFileReader(Maker maker) {
+ this.maker = (null == maker ? Maker.ECHO : maker);
+ }
+
+ /**
+ * Creates a (potentially recursive) tree of node
+ * by reading from the file and constructing using the maker.
+ * Clients may read results in Node tree form when complete
+ * or snoop the selector for a list of objects made.
+ * The selector can prevent collection in the node by
+ * returning false.
+ * Results are guaranteed by the Maker to be of the Maker's type.
+ * @param file an absolute path to a structured file
+ * @param selector determines whether not to keep an object made.
+ * (if null, then all are kept)
+ * @return Node with objects available from getItems()
+ * and sub-suite Node available from getNodes()
+ * @throws Error on any read error if abortOnReadError (default)
+ */
+ public Node readNodes(
+ final File file,
+ final ObjectChecker selector,
+ final boolean abortOnReadError,
+ final PrintStream err)
+ throws IOException {
+ final Node result = new Node(file.getPath(), maker.getType());
+ if (null == file) {
+ throw new IllegalArgumentException("null file");
+ } else if (!file.isAbsolute()) {
+ throw new IllegalArgumentException("file not absolute");
+ }
+ LineReader reader = null;
+ try {
+ reader = LineReader.createTester(file);
+ if (null == reader) {
+ throw new IOException("no reader for " + file);
+ }
+ final String baseDir = file.getParent();
+
+ String line;
+ boolean skipEmpties = true;
+ while (null != (line = reader.nextLine(skipEmpties))) {
+ if (line.charAt(0) == '@') {
+ if (line.length() > 1) {
+ String newFilePath = line.substring(1).trim();
+ File newFile = new File(newFilePath);
+ if (!newFile.isAbsolute()) {
+ newFile = new File(baseDir, newFilePath);
+ }
+ Node node = readNodes(newFile, selector, abortOnReadError, err);
+ if (!result.addNode(node)) {
+ // XXX signal error?
+ System.err.println("warning: unable to add node: " + node);
+ break;
+ }
+ }
+ } else {
+ try {
+ Object made = maker.make(reader);
+ if ((null == selector) || (selector.isValid(made))) {
+ if (!result.add(made)) {
+ break; // XXX signal error?
+ }
+ }
+ } catch (AbortException e) {
+ if (abortOnReadError) { // XXX todo - verify message has context?
+ throw e;
+ }
+ if (null != err) {
+ String m;
+ IMessage mssg = e.getIMessage();
+ if (null != mssg) {
+ m = "Message: " + mssg;
+ } else {
+ m = LangUtil.unqualifiedClassName(e) + "@" + e.getMessage();
+ }
+ err.println(m);
+ }
+ reader.readToBlankLine();
+ }
+ }
+ }
+ } finally {
+ try {
+ if (null != reader) {
+ reader.close();
+ }
+ } catch (IOException e) {
+ } // ignore
+ }
+
+ return result;
+ }
+
+ /** factory produces objects by reading LineReader */
+ public interface Maker {
+ /**
+ * Make the result using the input from the LineReader,
+ * starting with lastLine().
+ */
+ Object make(LineReader reader) throws AbortException, IOException;
+
+ /** @return type of the Object made */
+ Class getType();
+
+ /** This echoes each line, prefixed by the reader.
+ * @return file:line: {line}
+ */
+ static final Maker ECHO = new Maker() {
+ public Object make(LineReader reader) {
+ return reader + ": " + reader.lastLine();
+ }
+ public Class getType() { return String.class; }
+ };
+ }
+}
diff --git a/testing/src/org/aspectj/testing/util/StandardObjectChecker.java b/testing/src/org/aspectj/testing/util/StandardObjectChecker.java
new file mode 100644
index 000000000..ee5a466f5
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/StandardObjectChecker.java
@@ -0,0 +1,127 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+/*
+ * StandardObjectChecker.java created on May 7, 2002
+ *
+ */
+package org.aspectj.testing.util;
+
+import java.util.List;
+
+/**
+ * Superclass for checkers that require non-null input
+ * of a given type.
+ * Clients may supply delegator for further checks,
+ * or a list to collect results.
+ * Subclasses may instead implement doIsValid().
+ * @author isberg
+ */
+public class StandardObjectChecker implements ObjectChecker {
+
+ public final Class type;
+ private final ObjectChecker delegate;
+ private final List collector;
+ private final boolean collectionResult;
+
+ /**
+ * Create one with no delegate.
+ * @param type the Class of the type required of the input
+ */
+ public StandardObjectChecker(Class type) {
+ this(type, ANY, (List) null, true);
+ }
+
+ /**
+ * @param type the Class of the type required of the input
+ * @param delegate the ObjectChecker to delegate to after
+ * checking for non-null input of the correct type.
+ */
+ public StandardObjectChecker(Class type, ObjectChecker delegate) {
+ this(type, delegate, null, true);
+ }
+
+ /**
+ * same as StandardObjectChecker(type, collector, true)
+ * @param type the Class of the type required of the input
+ * @param collector the list to collect valid entries
+ */
+ public StandardObjectChecker(Class type, List collector) {
+ this(type, ANY, collector, true);
+ }
+
+ /**
+ * @param type the Class of the type required of the input
+ * @param collector the list to collect valid entries
+ * @param collectionResult the value to return when entry was added
+ */
+ public StandardObjectChecker(Class type, List collector, boolean collectionResult) {
+ this(type, ANY, collector, collectionResult);
+ }
+
+ /**
+ * @param type the Class of the type required of the input
+ * @param collector the list to collect valid entries
+ */
+ public StandardObjectChecker(Class type, ObjectChecker delegate,
+ List collector, boolean collectionResult) {
+ if (null == type) throw new IllegalArgumentException("null type");
+ this.type = type;
+ this.delegate = delegate;
+ this.collector = collector;
+ this.collectionResult = collectionResult;
+ }
+
+ /**
+ * Check if object is valid by confirming is is non-null and of the
+ * right type, then delegating to any delegate or calling doIsValid(),
+ * then (if true) passing to any collector, and returning
+ * false if the collector failed or the collection result otherwise.
+ * @see ObjectChecker#isValid(Object)
+ * @return true unless input is null or wrong type
+ * or if one of subclass (doIsValid(..)) or delegates
+ * (list, collector) returns false.
+ */
+ public final boolean isValid(Object input) {
+ if ((null == input) || (!(type.isAssignableFrom(input.getClass())))) {
+ return false;
+ } else if (null != delegate) {
+ if (!delegate.isValid(input)) {
+ return false;
+ }
+ }
+ if (!doIsValid(input)) {
+ return false;
+ }
+ if (null == collector) {
+ return true;
+ } else if (!collector.add(input)) {
+ return false;
+ } else {
+ return collectionResult;
+ }
+ }
+
+ /**
+ * Delegate of isValid guarantees that the input
+ * is not null as is assignable to the specified type.
+ * Subclasses implement their funtionality here.
+ * This implementation returns true;
+ * @return true
+ */
+ public boolean doIsValid(Object input) {
+ return true;
+ }
+
+}
diff --git a/testing/src/org/aspectj/testing/util/StreamSniffer.java b/testing/src/org/aspectj/testing/util/StreamSniffer.java
new file mode 100644
index 000000000..8b740aa2b
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/StreamSniffer.java
@@ -0,0 +1,76 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+/*
+ * StreamGrabber.java created on May 16, 2002
+ *
+ */
+package org.aspectj.testing.util;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Listen to a stream using StringBuffer.
+ * Clients install and remove buffer to enable/disable listening.
+ * Does not affect data passed to underlying stream
+ */
+public class StreamSniffer extends FilterOutputStream {
+ StringBuffer buffer;
+ /** have to use delegate, not super, because super we will double-count input */
+ final OutputStream delegate;
+
+ public StreamSniffer(OutputStream stream) {
+ super(stream);
+ delegate = stream;
+ }
+
+ /** set to null to stop copying */
+ public void setBuffer(StringBuffer sb) {
+ buffer = sb;
+ }
+
+ //---------------- FilterOutputStream
+ public void write(int b) throws IOException {
+ StringBuffer sb = buffer;
+ if (null != sb) {
+ if ((b > Character.MAX_VALUE)
+ || (b < Character.MIN_VALUE)) {
+ throw new Error("don't know double-byte"); // XXX
+ } else {
+ sb.append((char) b);
+ }
+ }
+ delegate.write(b);
+ }
+
+ public void write(byte[] b) throws IOException {
+ StringBuffer sb = buffer;
+ if (null != sb) {
+ String s = new String(b);
+ sb.append(s);
+ }
+ delegate.write(b);
+ }
+
+ public void write(byte[] b, int offset, int length) throws IOException {
+ StringBuffer sb = buffer;
+ if (null != sb) {
+ String s = new String(b, offset, length);
+ sb.append(s);
+ }
+ delegate.write(b, offset, length);
+ }
+}
diff --git a/testing/src/org/aspectj/testing/util/StreamsHandler.java b/testing/src/org/aspectj/testing/util/StreamsHandler.java
new file mode 100644
index 000000000..bd550ada7
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/StreamsHandler.java
@@ -0,0 +1,217 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import java.io.PrintStream;
+
+/**
+ * Manage system err and system out streams.
+ * Clients can suppress stream output during StreamsHandler lifecycle
+ * and intermittantly listen to both streams if signalled on construction.
+ * To print to the underlying streams (without hiding or listening),
+ * use either the log methods (which manage lineation)
+ * or the out and err fields.
+ * <pre>
+ * boolean hideStreams = true;
+ * boolean listen = true;
+ * StreamsHander streams = new StreamsHander(hideStreams, listen);
+ * streams.startListening();
+ * ...
+ * streams.out.println("this goes out to without listening"); XXX verify
+ * StreamsHandler.Result result = streams.stopListening();
+ * streams.restoreStreams();
+ * System.out.println("Suppressed output stream follows");
+ * System.out.print(result.out);
+ * System.out.println("Suppressed error stream follows");
+ * System.out.print(result.err);
+ * </pre>
+ * Warning: does not distinguish streams from different threads.
+ */
+public class StreamsHandler {
+
+ /** real output stream and sink for log if logToOut */
+ public final PrintStream out;
+
+ /** real error stream and sink for log if !logToOut */
+ public final PrintStream err;
+
+ /** if true, then can listen using startListening() */
+ protected final boolean listening;
+
+ /** if logToOut, then out, else err */
+ private final PrintStream log;
+
+ /** true if the last logged item was a newline */
+ private boolean loggedLine;
+
+ /** sniffs stream to gather test output to System.out */
+ protected StreamSniffer outSniffer;
+
+ /** sniffs stream to gather test output to System.err */
+ protected StreamSniffer errSniffer;
+
+ /** permits us to hide output stream (after sniffing by outSniffer */
+ protected ProxyPrintStream outDelegate;
+
+ /** permits us to hide error stream (after sniffing by errSniffer */
+ protected ProxyPrintStream errDelegate;
+
+ /** when sniffing, this has sniffed contents of output stream */
+ protected StringBuffer outListener;
+
+ /** when sniffing, this has sniffed contents of error stream */
+ protected StringBuffer errListener;
+
+ /** @param hide if true, then suppress stream output (can still listen) */
+ public StreamsHandler(boolean listen) {
+ this(listen, false);
+ }
+
+ /**
+ * @param listen possible to sniff streams only if true
+ * @param logToOut if true, then log methods go to System.out -- otherwise, System.err.
+ */
+ public StreamsHandler(
+ boolean listen,
+ boolean logToOut) {
+ this.err = System.err;
+ this.out = System.out;
+ outDelegate = new ProxyPrintStream(System.out);
+ errDelegate = new ProxyPrintStream(System.err);
+ this.listening = listen;
+ final PrintStream HIDE = NullPrintStream.NULL_PrintStream;
+ outSniffer = new StreamSniffer(outDelegate);
+ System.setOut(new PrintStream(outSniffer));
+ errSniffer = new StreamSniffer(errDelegate);
+ System.setErr(new PrintStream(errSniffer));
+ log = (logToOut ? this.out : this.err);
+ loggedLine = true;
+ }
+
+ /** render output and error streams (after sniffing) */
+ public void show() {
+ outDelegate.show();
+ errDelegate.show();
+ }
+
+ /** suppress output and error streams (after sniffing) */
+ public void hide() {
+ outDelegate.hide();
+ errDelegate.hide();
+ }
+
+ /** restore streams. Do not use this after restoring. */
+ public void restoreStreams() {
+ if (null != outSniffer) {
+ outSniffer = null;
+ errSniffer = null;
+ System.setOut(out);
+ System.setErr(err);
+ }
+ }
+
+ /** @return PrintStream used for direct logging */
+ public PrintStream getLogStream() {
+ return log;
+ }
+
+ /** log item without newline. */
+ public void log(String s) {
+ log.print(s);
+ if (loggedLine) {
+ loggedLine = false;
+ }
+ }
+
+ /**
+ * Log item with newline.
+ * If previous log did not have a newline,
+ * then this prepends a newline.
+ */
+ public void lnlog(String s) {
+ if (!loggedLine) {
+ log.println("");
+ }
+ log.println(s);
+ }
+
+ /**
+ * Start listening to both streams.
+ * Tosses any old data captured.
+ * (Has no effect if not listening.)
+ * @throws IllegalStateException if called after restoreStreams()
+ * @see endListening()
+ */
+ public void startListening() {
+ if (null == outSniffer) {
+ throw new IllegalStateException("no listening after restore");
+ }
+ if (listening) {
+ if (null != outListener) {
+ outListener.setLength(0);
+ errListener.setLength(0);
+ } else {
+ outListener = new StringBuffer();
+ outSniffer.setBuffer(outListener);
+ errListener = new StringBuffer();
+ errSniffer.setBuffer(errListener);
+ }
+ }
+ }
+
+ /**
+ * End listening to both streams and return data captured.
+ * Must call startListening() first.
+ * @throws IllegalStateException if called when not listening
+ * @return Result with sniffed output and error String
+ * @see startListening()
+ */
+ public Result endListening() {
+ return endListening(true);
+ }
+
+ /**
+ * End listening to both streams and return data captured.
+ * Must call startListening() first.
+ * @param getResult if false, return Result.EMPTY
+ * and avoid converting buffer to String.
+ * @throws IllegalStateException if called when not listening
+ * @return Result with sniffed output and error String
+ * @see startListening()
+ */
+ public Result endListening(boolean getResult) {
+ if (!listening) {
+ return Result.EMPTY;
+ }
+ if (null == outListener) {
+ throw new IllegalStateException("listening not started");
+ }
+ Result result = (!getResult ? Result.EMPTY
+ : new Result(outListener.toString(), errListener.toString()));
+ errListener = null;
+ outListener = null;
+ return result;
+ }
+
+ /** output and error String */
+ public static class Result {
+ static final Result EMPTY = new Result(null, null);
+ public final String out;
+ public final String err;
+ private Result(String out, String err) {
+ this.out = out;
+ this.err = err;
+ }
+ }
+}
diff --git a/testing/src/org/aspectj/testing/util/StringAccumulator.java b/testing/src/org/aspectj/testing/util/StringAccumulator.java
new file mode 100644
index 000000000..ecff4fabc
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/StringAccumulator.java
@@ -0,0 +1,99 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+/*
+ * StringAccumulator.java created on May 14, 2002
+ *
+ */
+package org.aspectj.testing.util;
+
+
+
+/**
+ * Accumulate String with delimiters.
+ */
+public class StringAccumulator implements ObjectChecker {
+
+ private final String prefix;
+ private final String infix;
+ private final String suffix;
+ private final String nullString;
+ private final StringBuffer sb;
+ private int index;
+
+ /**
+ * Accumulate string with delimiter between elements,
+ * treaing null elements as "".
+ */
+ public StringAccumulator(String delimiter) {
+ this(delimiter, null, null, "");
+ }
+
+ /**
+ * Constructor for StringAccumulator which specifies how to
+ * process each result, optionally postfixing or prefixing
+ * or infixing (adding index plus infix to prefix). e.g.,
+ * for prefix="[", infix="]\"", postfix="\"\n", then each entry
+ * becomes a line: <pre>"[{index}]"{entry}"\n</pre>
+ *
+ * @param prefix if not null, prepend to each result
+ * @param infix if not null, the add index and infix before each result, after prefix
+ * @param postfix if not null, append to each result
+ * @param nullString if null, ignore null completely (no index); otherwise render null as nullString
+ * @param type
+ */
+ public StringAccumulator(String prefix, String infix, String suffix, String nullString) {
+ this.prefix = prefix;
+ this.infix = infix;
+ this.suffix = suffix;
+ this.nullString = nullString;
+ sb = new StringBuffer();
+ }
+
+ /** Clear buffer and index */
+ public synchronized void clear() {
+ sb.setLength(0);
+ index = 0;
+ }
+
+ /**
+ * Accumulate input.toString into
+ * @return true
+ * @see StandardObjectChecker#doIsValid(Object)
+ */
+ public synchronized boolean isValid(Object input) {
+ if (input == null) {
+ if (nullString == null) return true; // ignore
+ input = nullString;
+ }
+ if (null != prefix) sb.append(prefix);
+ if (null != infix) {
+ sb.append(index++ + infix);
+ }
+ sb.append(input.toString());
+ if (null != suffix) sb.append(suffix);
+ return true;
+ }
+
+ /** @return result accumulated so far */
+ public String toString() {
+ return sb.toString();
+ }
+ /** @return result accumulated so far */
+ public String debugString() {
+ return "StringAccumulator prefix=" + prefix + " infix=" + infix + " suffix=" + suffix
+ + " nullString=" + nullString + " index=" + index + " toString=" + toString();
+ }
+
+}
diff --git a/testing/src/org/aspectj/testing/util/StringVisitor.java b/testing/src/org/aspectj/testing/util/StringVisitor.java
new file mode 100644
index 000000000..3d14cb3f6
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/StringVisitor.java
@@ -0,0 +1,28 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+// todo: non-distribution license?
+
+package org.aspectj.testing.util;
+
+/**
+ * Visitor interface for String
+*/
+public interface StringVisitor {
+ /**
+ * @param input the String to evaluate - may be null
+ * @return true if input is accepted and/or process should continue
+ */
+ public boolean accept(String input);
+}
+
diff --git a/testing/src/org/aspectj/testing/util/TestClassLoader.java b/testing/src/org/aspectj/testing/util/TestClassLoader.java
new file mode 100644
index 000000000..2af325e32
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/TestClassLoader.java
@@ -0,0 +1,154 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Load classes as File from File[] dirs or URL[] jars.
+ */
+public class TestClassLoader extends URLClassLoader {
+
+ List /*File*/ dirs;
+
+ public TestClassLoader(URL[] urls, File[] dirs) {
+ super(urls);
+ if (null == dirs) {
+ throw new IllegalArgumentException("null dir");
+ }
+ for (int i = 0; i < dirs.length; i++) {
+ if (null == dirs[i]) {
+ throw new IllegalArgumentException("null dir[" + i + "]");
+ }
+ }
+ ArrayList dcopy = new ArrayList();
+
+ if ((null != dirs) && (0 < dirs.length)) {
+ dcopy.addAll(Arrays.asList(dirs));
+ }
+ this.dirs = Collections.unmodifiableList(dcopy);
+ }
+
+
+ public URL getResource(String name) {
+ return ClassLoader.getSystemResource(name);
+ }
+
+ public InputStream getResourceAsStream(String name) {
+ return ClassLoader.getSystemResourceAsStream(name);
+ }
+
+ /** We don't expect test classes to have prefixes java, org, or com */
+ protected boolean maybeTestClassName(String name) {
+ return (null != name)
+ && !name.startsWith("java")
+ && !name.startsWith("org.")
+ && !name.startsWith("com.");
+ }
+
+ public synchronized Class loadClass(String name, boolean resolve)
+ throws ClassNotFoundException {
+ // search the cache, our dirs (if maybe test),
+ // the system, the superclass (URL[]),
+ // and our dirs again (if not maybe test)
+ ClassNotFoundException thrown = null;
+ final boolean maybeTestClass = maybeTestClassName(name);
+
+ Class result = findLoadedClass(name);
+ if (null != result) {
+ resolve = false;
+ } else if (maybeTestClass) {
+ // subvert the dominant paradigm...
+ byte[] data = readClass(name);
+ if (data != null) {
+ result = defineClass(name, data, 0, data.length);
+ } // handle ClassFormatError?
+ }
+ if (null == result) {
+ try {
+ result = findSystemClass(name);
+ } catch (ClassNotFoundException e) {
+ thrown = e;
+ }
+ }
+ if (null == result) {
+ try {
+ result = super.loadClass(name, resolve);
+ } catch (ClassNotFoundException e) {
+ thrown = e;
+ }
+ if (null != result) { // resolved by superclass
+ return result;
+ }
+ }
+ if ((null == result) && !maybeTestClass) {
+ byte[] data = readClass(name);
+ if (data != null) {
+ result = defineClass(name, data, 0, data.length);
+ } // handle ClassFormatError?
+ }
+
+ if (null == result) {
+ throw (null != thrown ? thrown : new ClassNotFoundException(name));
+ }
+ if (resolve) {
+ resolveClass(result);
+ }
+ return result;
+ }
+
+ private byte[] readClass(String className) throws ClassNotFoundException {
+ byte[] data= null;
+ final String fileName = className.replace('.', '/')+".class";
+ for (Iterator iter = dirs.iterator(); iter.hasNext();) {
+ File file = new File((File) iter.next(), fileName);
+ if (file.canRead()) {
+ return getClassData(file);
+ }
+ if (data != null) {
+ return data;
+ }
+ }
+ throw new ClassNotFoundException(className); // expensive - fix?
+ }
+
+ private byte[] getClassData(File f) {
+ try {
+ FileInputStream stream= new FileInputStream(f);
+ ByteArrayOutputStream out= new ByteArrayOutputStream(1000);
+ byte[] b= new byte[4096];
+ int n;
+ while ((n= stream.read(b)) != -1) {
+ out.write(b, 0, n);
+ }
+ stream.close();
+ out.close();
+ return out.toByteArray();
+ } catch (IOException e) {
+ }
+ return null;
+ }
+}
+
diff --git a/testing/src/org/aspectj/testing/util/TestDiffs.java b/testing/src/org/aspectj/testing/util/TestDiffs.java
new file mode 100644
index 000000000..2a847f039
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/TestDiffs.java
@@ -0,0 +1,360 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import org.aspectj.util.LangUtil;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Calculated differences between two test runs
+ * based on their output files
+ * assuming that tests are logged with prefix [PASS|FAIL]
+ * (as they are when using <tt>-traceTestsMin</tt> with the Harness).
+ * @see org.aspectj.testing.drivers.Harness
+ */
+public class TestDiffs { // XXX pretty dumb implementation
+
+ /** @param args expected, actual test result files */
+ public static void main(String[] args) {
+ if ((null == args) || (2 > args.length)) {
+ System.err.println("java " + TestDiffs.class.getName() + " expectedFile actualFile {test}");
+ return;
+ }
+ File expected = new File(args[0]);
+ File actual = new File(args[1]);
+
+ TestDiffs result = compareResults(expected, actual);
+
+ System.out.println("## Differences between test runs");
+ print(System.out, result.added, "added");
+ print(System.out, result.missing, "missing");
+ print(System.out, result.fixed, "fixed");
+ print(System.out, result.broken, "broken");
+
+ System.out.println("## Summary");
+ System.out.println(" # expected " + result.expected.size() + " tests: " + args[0] );
+ System.out.println(" # actual " + result.actual.size() + " tests: " + args[1]);
+ StringBuffer sb = new StringBuffer();
+ append(sb, result.added, " added");
+ append(sb, result.missing, " missing");
+ append(sb, result.broken, " broken");
+ append(sb, result.fixed, " fixed");
+ append(sb, result.stillPassing, " still passing");
+ append(sb, result.stillFailing, " still failing");
+ System.out.println(" # diffs: " + sb);
+ }
+
+ /**
+ * @param expected the expected/old File results with Harness -traceTestsMin lines
+ * @param actual the actual/new File results with Harness -traceTestsMin lines
+ * @return TestDiffs null if error, valid otherwise
+ */
+ public static TestDiffs compareResults(File expected, File actual) {
+ ArrayList exp = null;
+ ArrayList act = null;
+ File reading = expected;
+ try {
+ exp = TestDiffs.readTestResults(expected, expected.getPath());
+ reading = actual;
+ act = TestDiffs.readTestResults(actual, actual.getPath());
+
+ Diffs tests = new Diffs("tests", exp, act, TestResult.BY_NAME);
+ // remove missing/unexpected (removed, added) tests from results
+ // otherwise, unexpected-[pass|fail] look like [fixes|broken]
+ ArrayList expResults = trimByName(exp, tests.missing);
+ ArrayList actResults = trimByName(act, tests.unexpected);
+
+ Diffs results = new Diffs("results", expResults, actResults, TestResult.BY_PASSNAME);
+
+ // broken tests show up in results as unexpected-fail or missing-pass
+ // fixed tests show up in results as unexpected-pass or missing-fail
+ ArrayList broken = new ArrayList();
+ ArrayList fixed = new ArrayList();
+ split(results.unexpected, fixed, broken);
+
+ return new TestDiffs(
+ exp,
+ act,
+ tests.missing,
+ tests.unexpected,
+ broken,
+ fixed);
+ } catch (IOException e) {
+ System.err.println("error reading " + reading);
+ e.printStackTrace(System.err); // XXX
+ return null;
+ }
+ }
+
+ private static void append(StringBuffer sb, List list, String label) {
+ if (!LangUtil.isEmpty(list)) {
+ if (0 < sb.length()) {
+ sb.append(" ");
+ }
+ sb.append(list.size() + label);
+ }
+ }
+
+ private static void print(PrintStream out, List list, String label) {
+ if ((null == out) || LangUtil.isEmpty(list)) {
+ return;
+ }
+ int i = 0;
+ final String suffix = " " + label;
+ final String LABEL = list.size() + suffix;
+ out.println("## START " + LABEL);
+ for (Iterator iter = list.iterator(); iter.hasNext();) {
+ TestResult result = (TestResult) iter.next();
+ out.println(" " + result.test + " ## " + suffix);
+ }
+ out.println("## END " + LABEL);
+ }
+
+ /**
+ * Create ArrayList with input TestResult list
+ * but without elements in trim list,
+ * comparing based on test name only.
+ * @param input
+ * @param trim
+ * @return ArrayList with all input except those in trim (by name)
+ */
+ private static ArrayList trimByName(List input, List trim) {
+ ArrayList result = new ArrayList();
+ result.addAll(input);
+ if (!LangUtil.isEmpty(input) && !LangUtil.isEmpty(trim)) {
+ for (ListIterator iter = result.listIterator(); iter.hasNext();) {
+ TestResult inputItem = (TestResult) iter.next();
+ for (Iterator iterator = trim.iterator();
+ iterator.hasNext();
+ ) {
+ TestResult trimItem = (TestResult) iterator.next();
+ if (inputItem.test.equals(trimItem.test)) {
+ iter.remove();
+ break;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+
+ /** split input List by whether the TestResult element passed or failed */
+ private static void split(List input, ArrayList pass, ArrayList fail) {
+ for (ListIterator iter = input.listIterator(); iter.hasNext();) {
+ TestResult result = (TestResult) iter.next();
+ if (result.pass) {
+ pass.add(result);
+ } else {
+ fail.add(result);
+ }
+ }
+ }
+
+ /**
+ * Read a file of test results,
+ * defined as lines starting with [PASS|FAIL]
+ * (produced by Harness option <tt>-traceTestsmin</tt>).
+ * @return ArrayList of TestResult, one for every -traceTestsMin line in File
+ */
+ private static ArrayList readTestResults(File file, String config) throws IOException {
+ LangUtil.throwIaxIfNull(file, "file");
+ if (null == config) {
+ config = file.getPath();
+ }
+ ArrayList result = new ArrayList();
+ FileReader in = null;
+ try {
+ in = new FileReader(file);
+ BufferedReader input = new BufferedReader(in);
+ String line;
+ // XXX handle stream interleaving more carefully
+ // XXX clip trailing ()
+ // XXX fix elision in test name rendering by -traceTestsMin?
+ while (null != (line = input.readLine())) {
+ boolean pass = line.startsWith("PASS");
+ boolean fail = false;
+ if (!pass) {
+ fail = line.startsWith("FAIL");
+ }
+ if (pass || fail) {
+ String test = line.substring(4).trim();
+ result.add(new TestResult(test, config, pass));
+ }
+ }
+ } finally {
+ if (null != in) {
+ try { in.close(); }
+ catch (IOException e) {} // ignore
+ }
+ }
+ return result;
+ }
+
+ private static List safeList(List list) {
+ return (null == list
+ ? Collections.EMPTY_LIST
+ : Collections.unmodifiableList(list));
+ }
+
+ /** List of TestResult results from expected run. */
+ public final List expected;
+
+ /** List of TestResult results from actual run. */
+ public final List actual;
+
+ /** List of TestResult tests disappeared from test suite between expected and actual runs. */
+ public final List missing;
+
+ /** List of TestResult tests added to test suite between expected and actual runs. */
+ public final List added;
+
+ /** List of TestResult tests in both runs, expected to pass but actually failed */
+ public final List broken;
+
+ /** List of TestResult tests in both runs, expected to fail but actually passed */
+ public final List fixed;
+
+ /** List of TestResult passed tests in expected run */
+ public final List expectedPassed;
+
+ /** List of TestResult failed tests in expected run */
+ public final List expectedFailed;
+
+ /** List of TestResult passed tests in actual run */
+ public final List actualPassed;
+
+ /** List of TestResult tests failed in actual run */
+ public final List actualFailed;
+
+ /** List of TestResult tests passed in both expected and actual run */
+ public final List stillPassing;
+
+ /** List of TestResult tests failed in both expected and actual run */
+ public final List stillFailing;
+
+ private TestDiffs(
+ List expected,
+ List actual,
+ List missing,
+ List added,
+ List broken,
+ List fixed) {
+ this.expected = safeList(expected);
+ this.actual = safeList(actual);
+ this.missing = safeList(missing);
+ this.added = safeList(added);
+ this.broken = safeList(broken);
+ this.fixed = safeList(fixed);
+ // expected[Passed|Failed]
+ ArrayList passed = new ArrayList();
+ ArrayList failed = new ArrayList();
+ split(this.expected, passed, failed);
+ expectedPassed = safeList(passed);
+ expectedFailed = safeList(failed);
+
+ // actual[Passed|Failed]
+ passed = new ArrayList();
+ failed = new ArrayList();
+ split(this.actual, passed, failed);
+ actualPassed = safeList(passed);
+ actualFailed = safeList(failed);
+
+ // stillPassing: expected.passed w/o broken, missingPasses
+ passed = new ArrayList();
+ passed.addAll(expectedPassed);
+ passed = trimByName(passed, this.broken);
+ ArrayList missingPasses = new ArrayList();
+ ArrayList missingFails = new ArrayList();
+ split(this.missing, missingPasses, missingFails);
+ passed = trimByName(passed, missingPasses);
+ stillPassing = safeList(passed);
+
+ // stillFailing: expected.failed w/o fixed, missingFails
+ failed = new ArrayList();
+ failed.addAll(expectedFailed);
+ failed = trimByName(failed, this.fixed);
+ failed = trimByName(failed, missingFails);
+ stillFailing = safeList(failed);
+ }
+
+ /** results of a test */
+ public static class TestResult {
+ public static final Comparator BY_PASSNAME = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ if (o1 == o2) {
+ return 0;
+ }
+ TestResult lhs = (TestResult) o1;
+ TestResult rhs = (TestResult) o2;
+ return (lhs.pass == rhs.pass
+ ? lhs.test.compareTo(rhs.test)
+ : (lhs.pass ? 1 : -1 ));
+ }
+
+ public boolean equals(Object lhs, Object rhs) {
+ return (0 == compare(lhs, rhs));
+ }
+ };
+
+ public static final Comparator BY_NAME = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ if (o1 == o2) {
+ return 0;
+ }
+ TestResult lhs = (TestResult) o1;
+ TestResult rhs = (TestResult) o2;
+ return lhs.test.compareTo(rhs.test);
+ }
+
+ public boolean equals(Object lhs, Object rhs) {
+ return (0 == compare(lhs, rhs));
+ }
+ };
+
+ //private static final ArrayList TESTS = new ArrayList();
+ public static final String FIELDSEP = "\t";
+
+ public final String test;
+ public final String config;
+ public final boolean pass;
+ private final String toString;
+
+ public TestResult(String test, String config, boolean pass) {
+ LangUtil.throwIaxIfNull(test, "test");
+ LangUtil.throwIaxIfNull(test, "config");
+ this.test = test;
+ this.config = config;
+ this.pass = pass;
+ toString = (pass ? "PASS" : "FAIL") + FIELDSEP + test + FIELDSEP + config;
+
+ }
+
+ /** @return [PASS|FAIL]{FIELDSEP}test{FIELDSEP}config */
+ public String toString() {
+ return toString;
+ }
+ }
+}
diff --git a/testing/src/org/aspectj/testing/util/ValidFileFilter.java b/testing/src/org/aspectj/testing/util/ValidFileFilter.java
new file mode 100644
index 000000000..ba9f74407
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/ValidFileFilter.java
@@ -0,0 +1,123 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2000 Xerox Corporation.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import java.io.File;
+import java.io.FileFilter;
+
+/**
+ * FileFilter that accepts existing files
+ * with static singleton variants
+ * made from inner subclasses.
+ */
+public class ValidFileFilter implements FileFilter {
+ //----------------------------- singleton variants
+ public static final FileFilter EXIST = new ValidFileFilter();
+ public static final FileFilter FILE_EXISTS = new FilesOnlyFilter();
+ public static final FileFilter DIR_EXISTS = new DirsOnlyFilter();
+ public static final FileFilter CLASS_FILE = new ClassOnlyFilter();
+ public static final FileFilter JAVA_FILE = new JavaOnlyFilter();
+ public static final FileFilter RESOURCE = new ResourcesOnlyFilter();
+
+ //----------------------------- members
+ protected final FileFilter delegate;
+ protected ValidFileFilter(){
+ this(null);
+ }
+ protected ValidFileFilter(FileFilter delegate){
+ this.delegate = delegate;
+ }
+
+ /**
+ * Implement <code>FileFilter.accept(File)</code> by checking
+ * taht input is not null, exists, and is accepted by any delegate.
+ */
+ public boolean accept(File f) {
+ return ((null != f) && (f.exists())
+ && ((null == delegate) || delegate.accept(f)));
+ }
+
+ //----------------------------- inner subclasses
+ static class FilesOnlyFilter extends ValidFileFilter {
+ public boolean accept(File f) {
+ return (super.accept(f) && (!f.isDirectory()));
+ }
+ }
+ static class ResourcesOnlyFilter extends FilesOnlyFilter {
+ public boolean accept(File f) {
+ return (super.accept(f) && (FileUtil.isResourcePath(f.getPath())));
+ }
+ }
+ static class DirsOnlyFilter extends ValidFileFilter {
+ public final boolean accept(File f) {
+ return (super.accept(f) && (f.isDirectory()));
+ }
+ }
+ // todo: StringsFileFilter, accepts String[] variants for each
+ static class StringFileFilter extends ValidFileFilter {
+ public static final boolean IGNORE_CASE = true;
+ protected final String prefix;
+ protected final String substring;
+ protected final String suffix;
+ protected final boolean ignoreCase;
+ /** true if one of the String specifiers is not null */
+ protected final boolean haveSpecifier;
+ public StringFileFilter(String prefix, String substring,
+ String suffix, boolean ignoreCase) {
+ this.ignoreCase = ignoreCase;
+ this.prefix = preprocess(prefix);
+ this.substring = preprocess(substring);
+ this.suffix = preprocess(suffix);
+ haveSpecifier = ((null != prefix) || (null != substring)
+ || (null != suffix));
+ }
+ private final String preprocess(String input) {
+ if ((null != input) && ignoreCase) {
+ input = input.toLowerCase();
+ }
+ return input;
+ }
+ public boolean accept(File f) {
+ if (!(super.accept(f))) {
+ return false;
+ } else if (haveSpecifier) {
+ String path = preprocess(f.getPath());
+ if ((null == path) || (0 == path.length())) {
+ return false;
+ }
+ if ((null != prefix) && (!(path.startsWith(prefix)))) {
+ return false;
+ }
+ if ((null != substring) && (-1 == path.indexOf(substring))) {
+ return false;
+ }
+ if ((null != suffix) && (!(path.endsWith(suffix)))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ } // class StringFileFilter
+
+ static class ClassOnlyFilter extends StringFileFilter {
+ ClassOnlyFilter() {
+ super(null, null, ".class", IGNORE_CASE);
+ }
+ }
+ static class JavaOnlyFilter extends StringFileFilter {
+ JavaOnlyFilter() {
+ super(null, null, ".java", IGNORE_CASE);
+ }
+ }
+} // class ValidFileFilter
+
diff --git a/testing/src/org/aspectj/testing/util/WebInstall.java b/testing/src/org/aspectj/testing/util/WebInstall.java
new file mode 100644
index 000000000..4f8c96934
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/WebInstall.java
@@ -0,0 +1,208 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2000 Xerox Corporation.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.util;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/**
+ * Install programmatically using http URL.
+ * (Very strange that java tool classpath does not accept http URL's.)
+ *
+ * Example:
+ * <p><code>java -classpath aj-testing.jar org.aspectj.testing.util.WebInstall
+ * http://aspectj.org/download/distribution/aspectj10-tools.jar -text
+ * install.properties</code>
+ *
+ * <p>You can omit the <code>-text install.properties</code> if there is
+ * a file called "install.properties" in the current directory.
+ *
+ * <p>The properties file must define the properties
+ * <code>output.dir</code> and <code>context.javaPath</code>
+ * in properties- and platform specifie ways.
+ * For property values, a backslash must be escaped with another backslash,
+ * and directory separators should be valid. E.g., on Windows:
+ * <pre>output.dir=c:\\output\\dir
+ * context.javaPath=c:\\apps\\jdk1.3.1</pre>
+ *
+ * For an installer to complete programmatically,
+ * the output directory must be empty of colliding files.
+ * This will fail with a stack trace if anything goes wrong, except for
+ * simple input errors.
+ *
+ * <p>You may also use this as a driver for the known installers
+ * by specifying the following options (-baseurl must be first):<pre>
+ * -baseurl {baseurl}
+ * -version {version}
+ * -context.javaPath {path to JDK} (properties form)
+ * -output.dir {path to outDir} (properties form, including trailing /)
+ * -outdir {path to outDir} (actual form) </pre>
+ * such that URL=
+ * <code>{baseurl}<packagePrefix>{version}<packageSuffix>.jar</code>
+ * and paths to context.javaPath and output.dir are specified in
+ * properties-compliant format
+ *
+ * @see ant script test-product.xml for example of installing from files
+ * which can be driven from the command-line.
+ */
+public class WebInstall {
+ private static final String EOL = "\n"; // todo where is this defined?
+ public static final String SYNTAX
+ = "java WebInstall url {args}" + EOL
+ + " url - to installer" + EOL
+ + " args - normally -text install.properties" + EOL
+ + " (if n/a, use install.properties)" + EOL;
+
+ /** default arguments assume file <code>install.properties</code>
+ * is in current directory */
+ private static final String[] ARGS = new String[]
+ { "-text", "install.properties" };
+
+ /** @param args the String[] <code>{ "<url>" {, "-text", "<propsPath>" }</code> */
+ public static void main(String[] args) throws Exception {
+ if ((null != args) && (args.length > 0)
+ && ("-baseurl".equals(args[0]))) {
+ driver(args);
+ } else {
+ try {
+ new WebInstall().install(args);
+ } catch (Throwable t) {
+ System.err.println("Error installing args ");
+ for (int i = 0; i < args.length; i++) {
+ System.err.println(" " + i + ": " + args[i]);
+ }
+ t.printStackTrace(System.err);
+ }
+ }
+ }
+
+ /** known .jar packages {(prefix, suffix}...} */
+ protected static String[] packages = new String[]
+ { "aspectj-tools-", ""
+ , "aspectj-docs-", ""
+ , "ajde-forteModule-", ""
+ , "ajde-jbuilderOpenTool-", ""
+ };
+
+ /**
+ * Drive install of all jar-based installers.
+ * @param args the String[] containing<pre>
+ * -baseurl {baseurl}
+ * -version {version}
+ * -context.javaPath {path to JDK} (properties form)
+ * -output.dir {path to outDir} (properties form, including trailing /)
+ * -outdir {path to outDir} (actual form) </pre>
+ * such that URL=
+ * <code>{baseurl}<packagePrefix>{version}<packageSuffix>.jar</code>
+ * and paths to context.javaPath and output.dir are specified in
+ * properties-compliant format
+ */
+ protected static void driver(String[] args) throws Exception {
+ String baseurl = null;
+ String version = null;
+ String outputDir = null;
+ File outdir = null;
+ String jdk = null;
+ for (int i = 0; i < args.length; i++) {
+ if ("-baseurl".equals(args[i])) {
+ baseurl = args[++i];
+ } else if ("-version".equals(args[i])) {
+ version = args[++i];
+ } else if ("-context.javaPath".equals(args[i])) {
+ jdk = args[++i];
+ } else if ("-output.dir".equals(args[i])) {
+ outputDir=args[++i];
+ } else if ("-outdir".equals(args[i])) {
+ outdir = new File(args[++i]).getCanonicalFile();
+ if (!outdir.isDirectory()) {
+ outdir.mkdir();
+ }
+ }
+ }
+ final File props = File.createTempFile("WebInstall", null);
+ final String[] ARGS = new String [] {null, "-text", props.getCanonicalPath()};
+ for (int i = 0; i < packages.length; i++) {
+ String name = packages[i++] + version + packages[i];
+ File outDir = new File(outdir, name);
+ FileWriter fw = null;
+ try {
+ if (!outDir.isDirectory()) {
+ outDir.mkdir();
+ }
+ fw = new FileWriter(props);
+ fw.write("output.dir=" + outputDir + name + "\n");
+ fw.write("context.javaPath=" + jdk + "\n");
+ fw.close();
+ fw = null;
+ ARGS[0] = baseurl + name + ".jar";
+ main(ARGS);
+ } finally {
+ try { if (null != fw) fw.close(); }
+ catch (java.io.IOException e) {} // ignore
+ }
+ }
+ if (props.exists()) props.delete();
+ } // driver
+
+ private static boolean printError(String err) {
+ if (null != err) System.err.println(err);
+ System.err.println(SYNTAX);
+ return (null != err);
+ }
+
+ /**
+ * Create a classloader using the first argument (presumed to be URL for classloader),
+ * construct the installer, and invoke it using remaining arguments (or default args).
+ */
+ protected void install(String[] args) throws Exception {
+ if ((null == args) || (args.length < 1)
+ || (null == args[0]) || (1 > args[0].length())) {
+ if (printError("expecting installer URL")) return;
+ }
+ URL[] urls = new URL[] { new URL(args[0]) };
+ //System.err.println("before: " + render(args));
+ args = getArgs(args);
+ //System.err.println("after: " + render(args));
+ URLClassLoader cl = new URLClassLoader(urls);
+ Class c = cl.loadClass("$installer$.org.aspectj.Main"); // todo: dependency on class name
+ Method ms = c.getMethod("main", new Class[]{String[].class});
+ ms.invoke(null, new Object[] { args });
+ }
+ public static final String render(String[] args) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("[");
+ for (int i = 0; i < args.length; i++) {
+ if (0 < i) sb.append(", ");
+ sb.append("" + args[i]);
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /** @return args less args[0] or default args if less than 3 arguments */
+
+ protected String[] getArgs(String[] args) {
+ if ((null == args) || (args.length < 3)) {
+ return ARGS;
+ } else {
+ String[] result = new String[args.length-1];
+ System.arraycopy(args, 1, result, 0, result.length);
+ return result;
+ }
+ }
+
+}
diff --git a/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java b/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java
new file mode 100644
index 000000000..cc484f82d
--- /dev/null
+++ b/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java
@@ -0,0 +1,432 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.xml;
+
+import org.apache.commons.digester.Digester;
+import org.aspectj.bridge.AbortException;
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.ISourceLocation;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.testing.harness.bridge.AbstractRunSpec;
+import org.aspectj.testing.harness.bridge.AjcTest;
+import org.aspectj.testing.harness.bridge.CompilerRun;
+import org.aspectj.testing.harness.bridge.DirChanges;
+import org.aspectj.testing.harness.bridge.IncCompilerRun;
+import org.aspectj.testing.harness.bridge.JavaRun;
+import org.aspectj.testing.util.RunUtils;
+import org.aspectj.util.LangUtil;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Read an ajc test specification in xml form.
+ * Input files should comply with DOCTYPE
+ */
+public class AjcSpecXmlReader {
+ /*
+ * To add new elements or attributes:
+ * - update the DOCTYPE
+ * - update setupDigester(..)
+ * - new sub-elements should be created
+ * - new attributes should have values set as bean properties
+ * (possibly by mapping names)
+ * - new sub-elements should be added to parents
+ * => the parents need the add method - e.g., add{foo}({foo} toadd)
+ * - add tests
+ * - add compile-time checks for mapping APIs in
+ * setupDigesterComipileTimeCheck
+ * - when adding an attribute set by bean introspection,
+ * add to the list returned by expectedProperties()
+ * - update any client writers referring to the DOCTYPE, as necessary.
+ * - the parent IXmlWriter should delegate to the child component
+ * as IXmlWriter (or write the subelement itself)
+ */
+
+ private static final String EOL = "\n";
+
+ /** presumed relative-path to dtd file for any XML files written by writeSuiteToXmlFile */
+ public static final String DTD_PATH = "../tests/ajcTestSuite.dtd";
+
+ /** expected doc type of AjcSpec XML files */
+ public static final String DOCTYPE = "<!DOCTYPE "
+ + AjcTest.Suite.Spec.XMLNAME + " SYSTEM \"" + DTD_PATH + "\">";
+
+ /** xml leader */
+ public static final String FILE_LEADER
+ = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
+
+ /** @return a String suitable as an inlined DOCTYPE statement */
+ public static String inlineDocType() {
+ return "<!DOCTYPE "
+ + AjcTest.Suite.Spec.XMLNAME
+ + " ["
+ + AjcSpecXmlReader.getDocType()
+ + EOL + " ]>";
+ }
+
+ /**
+ * @return the elements of a document type as a String,
+ * using EOL as a line delimiter
+ */
+ public static String getDocType() {
+ if (true) {
+ throw new Error("XXX using ajcTestSuite.dtd");
+ }
+ StringBuffer r = new StringBuffer();
+ final String suiteX = AjcTest.Suite.Spec.XMLNAME;
+ final String ajctestX = AjcTest.Spec.XMLNAME;
+ final String compileX = CompilerRun.Spec.XMLNAME;
+ final String inccompileX = IncCompilerRun.Spec.XMLNAME;
+ final String runX = JavaRun.Spec.XMLNAME;
+ final String dirchangesX = DirChanges.Spec.XMLNAME;
+ final String messageX = SoftMessage.XMLNAME;
+
+ r.append(EOL + " <!ELEMENT " + suiteX + " (" + ajctestX + "+)>");
+ r.append(EOL + " <!ATTLIST " + suiteX + " suiteDir CDATA #IMPLIED >");
+ r.append(EOL + "");
+ r.append(EOL + " <!ELEMENT " + ajctestX + " (" + compileX + ", (" + compileX + " | " + inccompileX + " | " + runX + ")*)>");
+ r.append(EOL + " <!ATTLIST " + ajctestX + " title CDATA #REQUIRED >");
+ r.append(EOL + " <!ATTLIST " + ajctestX + " dir CDATA #REQUIRED >");
+ r.append(EOL + " <!ATTLIST " + ajctestX + " pr CDATA #IMPLIED >");
+ r.append(EOL + " <!ATTLIST " + ajctestX + " keywords CDATA #IMPLIED >");
+ r.append(EOL + "");
+ r.append(EOL + " <!ELEMENT " + compileX + " (" + dirchangesX + "*,file*," + messageX + "*)>"); // deprecate file?
+ r.append(EOL + " <!ATTLIST " + compileX + " staging CDATA #IMPLIED >"); // if precursor to incremental
+ r.append(EOL + " <!ATTLIST " + compileX + " files CDATA #IMPLIED >");
+ r.append(EOL + " <!ATTLIST " + compileX + " options CDATA #IMPLIED >");
+ r.append(EOL + "");
+ r.append(EOL + " <!ELEMENT " + inccompileX + " (" + dirchangesX + "*," + messageX + "*)>"); // add file* if not deprecated
+ r.append(EOL + " <!ATTLIST " + inccompileX + " tag CDATA #REQUIRED >");
+ r.append(EOL + "");
+ r.append(EOL + " <!ELEMENT " + runX + " (" + dirchangesX + "*," + messageX + "*)>");
+ r.append(EOL + " <!ATTLIST " + runX + " class CDATA #REQUIRED >");
+ r.append(EOL + " <!ATTLIST " + runX + " skipTester CDATA #IMPLIED >");
+ r.append(EOL + " <!ATTLIST " + runX + " options CDATA #IMPLIED >");
+ r.append(EOL + "");
+ r.append(EOL + " <!ELEMENT file (#PCDATA)>"); // deprecate?
+ r.append(EOL + " <!ATTLIST file path CDATA #IMPLIED >");
+ r.append(EOL + "");
+ r.append(EOL + " <!ELEMENT " + messageX + " (#PCDATA)>");
+ r.append(EOL + " <!ATTLIST " + messageX + " kind (error | warning | info | Xlint) #REQUIRED >");
+ r.append(EOL + " <!ATTLIST " + messageX + " line CDATA #REQUIRED >");
+ r.append(EOL + " <!ATTLIST " + messageX + " text CDATA #IMPLIED >"); // but Message requires non-null...
+ r.append(EOL + " <!ATTLIST " + messageX + " file CDATA #IMPLIED >");
+ r.append(EOL + "");
+ r.append(EOL + " <!ELEMENT " + dirchangesX + " (#PCDATA)>");
+ r.append(EOL + " <!ATTLIST " + dirchangesX + " dirToken (classes | run) #IMPLIED >");
+ r.append(EOL + " <!ATTLIST " + dirchangesX + " defaultSuffix (.class) #IMPLIED >");
+ r.append(EOL + " <!ATTLIST " + dirchangesX + " added CDATA #IMPLIED >");
+ r.append(EOL + " <!ATTLIST " + dirchangesX + " removed CDATA #IMPLIED >");
+ r.append(EOL + " <!ATTLIST " + dirchangesX + " updated CDATA #IMPLIED >");
+ r.append(EOL + " <!ATTLIST " + dirchangesX + " unchanged CDATA #IMPLIED >");
+ r.append(EOL + "");
+ return r.toString();
+ }
+
+ private static final AjcSpecXmlReader ME
+ = new AjcSpecXmlReader();
+
+ /** @return shared instance */
+ public static final AjcSpecXmlReader getReader() {
+ return ME;
+ }
+
+ public static void main(String[] a) throws IOException {
+ writeDTD(new File("c:/home/wes/aj/aspectj/modules/tests/ajcTestSuite2.dtd"));
+ }
+ /**
+ * Write a DTD to dtdFile.
+ * @param dtdFile the File to write to
+ */
+ public static void writeDTD(File dtdFile) throws IOException {
+ LangUtil.throwIaxIfNull(dtdFile, "dtdFile");
+ PrintWriter out = new PrintWriter(new FileWriter(dtdFile));
+ try {
+ out.println("<!-- document type for ajc test suite - see "
+ + AjcSpecXmlReader.class.getName() + " -->");
+ out.println(getDocType());
+ } finally {
+ out.close();
+ }
+ }
+
+ private static final String[] LOG = new String[] {"info", "debug", "trace" };
+
+ private int logLevel;
+
+ private AjcSpecXmlReader() {}
+
+ /** @param level 0..2, info..trace */
+ public void setLogLevel(int level) {
+ if (level < 0) {
+ level = 0;
+ }
+ if (level > 2) {
+ level = 2;
+ }
+ logLevel = level;
+ }
+
+ /**
+ * Print an IXmlWritable to the output file
+ * with our leader and DOCTYPE.
+ * @param output the File to write to - overwritten
+ * @param tests the List of IXmlWritable to write
+ * @return null if no warnings detected, warnings otherwise
+ */
+ public String writeSuiteToXmlFile(File output, IXmlWritable topNode) throws IOException {
+ PrintWriter writer = new PrintWriter(new FileOutputStream(output));
+ XMLWriter printSink = new XMLWriter(writer);
+ writer.println("");
+ writer.println(AjcSpecXmlReader.DOCTYPE);
+ writer.println("");
+ topNode.writeXml(printSink);
+ writer.close();
+ String parent = output.getParent();
+ if (null == parent) {
+ parent = ".";
+ }
+ String dtdPath = parent + "/" + DTD_PATH;
+ File dtdFile = new File(dtdPath);
+ if (!dtdFile.canRead()) {
+ return "expecting dtd file: " + dtdFile.getPath();
+ }
+ return null;
+ }
+
+
+ /**
+ * Read the specifications for a suite of AjcTest from an XML file.
+ * This also sets the suite dir in the specification.
+ * @param file the File must be readable, comply with DOCTYPE.
+ * @return AjcTest.Suite.Spec read from file
+ * @see setLogLevel(int)
+ */
+ public AjcTest.Suite.Spec readAjcSuite(File file) throws IOException, AbortException {
+ // setup loggers for digester and beanutils...
+ System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog"); // XXX
+ System.setProperty("org.apache.commons.logging.simplelog.defaultlog", LOG[logLevel]); // trace debug XXX
+
+ final Digester digester = new Digester();
+ setupDigester(digester);
+
+ SuiteHolder holder = new SuiteHolder();
+ digester.push(holder);
+ FileInputStream input = new FileInputStream(file);
+ try {
+ digester.parse(input);
+ } catch (SAXException e) {
+ MessageUtil.fail("parsing " + file, e);
+ } finally {
+ if (null != input) {
+ input.close();
+ input = null;
+ }
+ }
+ AjcTest.Suite.Spec result = holder.spec;
+ if (null != result) {
+ File suiteDir = file.getParentFile();
+ if (null == suiteDir) {
+ suiteDir = new File("."); // user.dir?
+ }
+ result.setSuiteDirFile(suiteDir);
+ if (result.runtime.isVerbose()) { // XXX hack fixup
+ RunUtils.enableVerbose(result);
+ }
+ }
+ return result;
+ }
+
+ /** set up the mapping between the xml and Java. */
+ private void setupDigester(Digester digester) {
+ // XXX supply sax parser to ignore white space?
+ digester.setValidating(true);
+
+ // element names come from the element components
+ final String suiteX = AjcTest.Suite.Spec.XMLNAME;
+ final String ajctestX = suiteX + "/" + AjcTest.Spec.XMLNAME;
+ final String compileX = ajctestX + "/" + CompilerRun.Spec.XMLNAME;
+ final String inccompileX = ajctestX + "/" + IncCompilerRun.Spec.XMLNAME;
+ final String runX = ajctestX + "/" + JavaRun.Spec.XMLNAME;
+ final String dirchangesX = "*/" + DirChanges.Spec.XMLNAME;
+ final String messageX = "*/" + SoftMessage.XMLNAME;
+ final String messageSrcLocX = messageX + "/source-location";
+
+ // ---- each sub-element needs to be created
+ // handle messages the same at any level
+ digester.addObjectCreate(suiteX, AjcTest.Suite.Spec.class.getName());
+ digester.addObjectCreate(ajctestX, AjcTest.Spec.class.getName());
+ digester.addObjectCreate(compileX, CompilerRun.Spec.class.getName());
+ digester.addObjectCreate(compileX + "/file", AbstractRunSpec.WrapFile.class.getName());
+ digester.addObjectCreate(inccompileX, IncCompilerRun.Spec.class.getName());
+ digester.addObjectCreate(runX, JavaRun.Spec.class.getName());
+ digester.addObjectCreate(messageX, SoftMessage.class.getName());
+ digester.addObjectCreate(messageSrcLocX, SoftSourceLocation.class.getName());
+ digester.addObjectCreate(dirchangesX, DirChanges.Spec.class.getName());
+
+ // ---- set bean properties for sub-elements created automatically
+ // -- some remapped - warnings
+ // - if property exists, map will not be used
+ digester.addSetProperties(suiteX); // ok to have suite messages and global suite options, etc.
+ digester.addSetProperties(ajctestX,
+ new String[] { "title", "dir", "pr"},
+ new String[] { "description", "testDirOffset", "bugId"});
+ digester.addSetProperties(compileX,
+ new String[] { "files", "argfiles"},
+ new String[] { "paths", "argfiles"});
+ digester.addSetProperties(compileX + "/file");
+ digester.addSetProperties(inccompileX, "classes", "paths");
+ digester.addSetProperties(runX, //"class", "className");
+ new String[] { "class", "vm", "skipTester"},
+ new String[] { "className", "javaVersion", "skipTester"});
+ digester.addSetProperties(dirchangesX);
+ digester.addSetProperties(messageX);
+ digester.addSetProperties(messageSrcLocX);
+ digester.addSetProperties(messageX, "kind", "kindAsString");
+ digester.addSetProperties(messageX, "line", "lineAsString");
+ // only file subelement of compile uses text as path... XXX vestigial
+ digester.addCallMethod(compileX + "/file", "setFile", 0);
+
+ // ---- when subelements are created, add to parent
+ // add ajctest to suite, runs to ajctest, files to compile, messages to any parent...
+ // the method name (e.g., "addSuite") is in the parent (SuiteHolder)
+ // the class (e.g., AjcTest.Suite.Spec) refers to the type of the object created
+ digester.addSetNext(suiteX, "addSuite", AjcTest.Suite.Spec.class.getName());
+ digester.addSetNext(ajctestX, "addChild", AjcTest.Spec.class.getName());
+ digester.addSetNext(compileX, "addChild", CompilerRun.Spec.class.getName());
+ digester.addSetNext(inccompileX, "addChild", IncCompilerRun.Spec.class.getName());
+ digester.addSetNext(runX, "addChild", JavaRun.Spec.class.getName());
+ digester.addSetNext(compileX + "/file", "addWrapFile", AbstractRunSpec.WrapFile.class.getName());
+ digester.addSetNext(messageX, "addMessage", IMessage.class.getName());
+ digester.addSetNext(messageSrcLocX, "setSourceLocation", ISourceLocation.class.getName());
+ digester.addSetNext(dirchangesX, "addDirChanges", DirChanges.Spec.class.getName());
+
+ // can set parent, but prefer to have "knows-about" flow down only...
+ }
+
+ // ------------------------------------------------------------ testing code
+ /**
+ * Get expected bean properties for introspection tests.
+ * This should return an expected property for every attribute in DOCTYPE,
+ * using any mapped-to names from setupDigester.
+ */
+ static BProps[] expectedProperties() {
+ return new BProps[]
+ {
+ new BProps(AjcTest.Suite.Spec.class,
+ new String[] { "suiteDir"}), // verbose removed
+ new BProps(AjcTest.Spec.class,
+ new String[] { "description", "testDirOffset", "bugId"}),
+ // mapped from { "title", "dir", "pr"}
+ new BProps(CompilerRun.Spec.class,
+ new String[] { "files", "options"}),
+ new BProps(IncCompilerRun.Spec.class,
+ new String[] { "tag" }),
+ new BProps(JavaRun.Spec.class,
+ new String[] { "className", "skipTester", "options"}),
+ // mapped from { "class", ...}
+ new BProps(DirChanges.Spec.class,
+ new String[] { "added", "removed", "updated", "unchanged", "dirToken", "defaultSuffix"}),
+ new BProps(AbstractRunSpec.WrapFile.class,
+ new String[] { "path"}),
+ new BProps(SoftMessage.class,
+ new String[] { "kindAsString", "lineAsString", "text", "file"})
+ // mapped from { "kind", "line", ...}
+ };
+ }
+
+ /**
+ * This is only to do compile-time checking for the APIs impliedly
+ * used in setupDigester(..).
+ * The property setter checks are redundant with tests based on
+ * expectedProperties().
+ */
+ private static void setupDigesterCompileTimeCheck() {
+ if (true) { throw new Error("never invoked"); }
+ AjcTest.Suite.Spec suite = new AjcTest.Suite.Spec();
+ AjcTest.Spec test = new AjcTest.Spec();
+// AjcTest test = new AjcTest();
+// test.addRunSpec((AbstractRunSpec) null);
+//// test.makeIncCompilerRun((IncCompilerRun.Spec) null);
+//// test.makeJavaRun((JavaRun.Spec) null);
+// test.setDescription((String) null);
+// test.setTestBaseDirOffset((String) null);
+// test.setBugId((String) null);
+// test.setTestSourceLocation((ISourceLocation) null);
+
+ CompilerRun.Spec crunSpec = new CompilerRun.Spec();
+ crunSpec.addMessage((IMessage) null);
+ // XXX crunSpec.addSourceLocation((ISourceLocation) null);
+ crunSpec.addWrapFile((AbstractRunSpec.WrapFile) null);
+ crunSpec.setOptions((String) null);
+ crunSpec.setPaths((String) null);
+
+ IncCompilerRun.Spec icrunSpec = new IncCompilerRun.Spec();
+ icrunSpec.addMessage((IMessage) null);
+ icrunSpec.setTag((String) null);
+
+ JavaRun.Spec jrunspec = new JavaRun.Spec();
+ jrunspec.addMessage((IMessage) null);
+ jrunspec.setClassName((String) null);
+ jrunspec.addMessage((IMessage) null);
+ // input s.b. interpretable by Boolean.valueOf(String)
+ jrunspec.setSkipTester(true);
+
+ DirChanges.Spec dcspec = new DirChanges.Spec();
+ dcspec.setAdded((String) null);
+ dcspec.setRemoved((String) null);
+ dcspec.setUpdated((String) null);
+ dcspec.setDefaultSuffix((String) null);
+ dcspec.setDirToken((String) null);
+
+ SoftMessage m = new SoftMessage();
+ m.setSourceLocation((ISourceLocation) null);
+ m.setText((String) null);
+ m.setKindAsString((String) null);
+
+ SoftSourceLocation sl = new SoftSourceLocation();
+ sl.setFile((String) null);
+ sl.setLine((String) null);
+ sl.setColumn((String) null);
+ sl.setEndLine((String) null);
+
+ // add attribute setters to validate?
+ }
+
+ /** top element on Digester stack holds the test suite */
+ public static class SuiteHolder {
+ AjcTest.Suite.Spec spec;
+ public void addSuite(AjcTest.Suite.Spec spec) {
+ this.spec = spec;
+ }
+ }
+
+ /** hold class/properties association for testing */
+ static class BProps {
+ final Class cl;
+ final String[] props;
+ BProps(Class cl, String[] props) {
+ this.cl = cl;
+ this.props = props;
+ }
+ }
+}
+
+
diff --git a/testing/src/org/aspectj/testing/xml/IXmlWritable.java b/testing/src/org/aspectj/testing/xml/IXmlWritable.java
new file mode 100644
index 000000000..52f43c389
--- /dev/null
+++ b/testing/src/org/aspectj/testing/xml/IXmlWritable.java
@@ -0,0 +1,25 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.xml;
+
+/**
+ *
+ */
+public interface IXmlWritable {
+ /**
+ * Write self out to XML.
+ * @param out the XMLWriter to write to
+ */
+ void writeXml(XMLWriter out);
+}
diff --git a/testing/src/org/aspectj/testing/xml/SoftMessage.java b/testing/src/org/aspectj/testing/xml/SoftMessage.java
new file mode 100644
index 000000000..e57a4f3de
--- /dev/null
+++ b/testing/src/org/aspectj/testing/xml/SoftMessage.java
@@ -0,0 +1,287 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.xml;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.ISourceLocation;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.bridge.SourceLocation;
+import org.aspectj.util.LangUtil;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Implement messages.
+ * This implementation is immutable if ISourceLocation is immutable.
+ */
+public class SoftMessage implements IMessage { // XXX mutable dup of Message
+ public static String XMLNAME = "message";
+ public static final File NO_FILE = ISourceLocation.NO_FILE;
+ private String message;
+ private IMessage.Kind kind;
+ private Throwable thrown;
+ private ISourceLocation sourceLocation;
+
+
+ //private ISourceLocation pseudoSourceLocation; // set directly
+ // collapse enclosed source location for shorter, property-based xml
+ private String file;
+ private int line = Integer.MAX_VALUE;
+
+ /** convenience for constructing failure messages */
+ public static SoftMessage fail(String message, Throwable thrown) {
+ return new SoftMessage(message, IMessage.FAIL, thrown, null);
+ }
+
+ /**
+ * Print messages.
+ * @param messages List of IMessage
+ */
+ public static void writeXml(XMLWriter out, List messages) {
+ if ((null == out) || (null == messages)) {
+ return;
+ }
+ for (Iterator iter = messages.iterator(); iter.hasNext();) {
+ writeXml(out, (IMessage) iter.next());
+
+ }
+ }
+
+ /**
+ * Print messages.
+ * @param messages IMessage[]
+ */
+ public static void writeXml(XMLWriter out, IMessage[] messages) {
+ if ((null == out) || (null == messages)) {
+ return;
+ }
+ for (int i = 0; i < messages.length; i++) {
+ writeXml(out, messages[i]);
+ }
+ }
+
+ /** print message as an element */
+ public static void writeXml(XMLWriter out, IMessage message) { // XXX short form only, no files
+ if ((null == out) || (null == message)) {
+ return;
+ }
+ final String elementName = XMLNAME;
+ String kindStr = message.getKind().toString();
+ String kindAttr = out.makeAttribute("kind",kindStr);
+ String mStr = message.getMessage();
+ if ((null != mStr) && (0 == mStr.length())) {
+ mStr = null;
+ }
+ String mAttr = (null == mStr ? " " : " " + out.makeAttribute("text", mStr));
+ int mAttrLen = (null == mStr ? 0 : mAttr.length());
+ ISourceLocation sl = message.getISourceLocation();
+ String lineStr = (null == sl ? null : "" + sl.getLine());
+ String lineAttr = (null == lineStr ? " " : " " + out.makeAttribute("line", lineStr));
+ int lineAttrLen = (null == lineStr ? 0 : lineAttr.length());
+ int len = (kindAttr.length() + mAttrLen + lineAttrLen);
+ if (len < 65) {
+ String s = kindAttr + " "
+ + ((null == lineStr ? "" : lineAttr)
+ + (null == mStr ? "" : " " + mAttr)).trim();
+ out.printElement(elementName, s);
+ } else {
+ out.startElement(elementName, kindAttr + lineAttr, false);
+ if (0 < mStr.length()) {
+ out.printAttribute("text", mStr);
+ }
+ out.endAttributes();
+ out.endElement(elementName);
+ }
+ }
+
+
+
+ public SoftMessage() {} // XXX programmatic only
+
+ /**
+ * Create a (compiler) error or warning message
+ * @param message the String used as the underlying message
+ * @param sourceLocation the ISourceLocation, if any, associated with this message
+ * @param isError if true, use IMessage.ERROR; else use IMessage.WARNING
+ */
+ public SoftMessage(String message, ISourceLocation location, boolean isError) {
+ this(message, (isError ? IMessage.ERROR : IMessage.WARNING), null,
+ location);
+ }
+
+ /**
+ * Create a message, handling null values for message and kind
+ * if thrown is not null.
+ * @param message the String used as the underlying message
+ * @param kind the IMessage.Kind of message - not null
+ * @param thrown the Throwable, if any, associated with this message
+ * @param sourceLocation the ISourceLocation, if any, associated with this message
+ * @throws IllegalArgumentException if message is null and
+ * thrown is null or has a null message, or if kind is null
+ * and thrown is null.
+ */
+ public SoftMessage(String message, IMessage.Kind kind, Throwable thrown,
+ ISourceLocation sourceLocation) {
+ this.message = message;
+ this.kind = kind;
+ this.thrown = thrown;
+ this.sourceLocation = sourceLocation;
+ if (null == message) {
+ if (null != thrown) {
+ message = thrown.getMessage();
+ }
+ if (null == message) {
+ throw new IllegalArgumentException("null message");
+ }
+ }
+ if (null == kind) {
+ throw new IllegalArgumentException("null kind");
+ }
+ }
+
+ /** @return the kind of this message */
+ public IMessage.Kind getKind() {
+ return kind;
+ }
+
+ /** @return true if kind == IMessage.ERROR */
+ public boolean isError() {
+ return kind == IMessage.ERROR;
+ }
+
+ /** @return true if kind == IMessage.WARNING */
+ public boolean isWarning() {
+ return kind == IMessage.WARNING;
+ }
+
+ /** @return true if kind == IMessage.DEBUG */
+ public boolean isDebug() {
+ return kind == IMessage.DEBUG;
+ }
+
+ /**
+ * @return true if kind == IMessage.INFO
+ */
+ public boolean isInfo() {
+ return kind == IMessage.INFO;
+ }
+
+ /** @return true if kind == IMessage.ABORT */
+ public boolean isAbort() {
+ return kind == IMessage.ABORT;
+ }
+
+ /**
+ * @return true if kind == IMessage.FAIL
+ */
+ public boolean isFailed() {
+ return kind == IMessage.FAIL;
+ }
+
+ /** @return non-null String with simple message */
+ final public String getMessage() {
+ return message;
+ }
+
+ /** @return Throwable associated with this message, or null if none */
+ final public Throwable getThrown() {
+ return thrown;
+ }
+
+ /**
+ * This returns any ISourceLocation set or a mock-up
+ * if file and/or line were set.
+ * @return ISourceLocation associated with this message,
+ * a mock-up if file or line is available, or null if none
+ */
+ final public ISourceLocation getISourceLocation() {
+ if ((null == sourceLocation)
+ && ((null != file) || (line != Integer.MAX_VALUE))) {
+ File f = (null == file ? NO_FILE : new File(file));
+ int line = (this.line == Integer.MAX_VALUE ? 0 : this.line);
+ sourceLocation = new SourceLocation(f, line);
+ }
+ return sourceLocation;
+ }
+
+ /** set the kind of this message */
+ public void setMessageKind(IMessage.Kind kind) {
+ this.kind = (null == kind ? IMessage.ERROR : kind);
+ }
+
+
+ /** set the file for the underlying source location of this message
+ * @throws IllegalStateException if source location was set directly
+ * or indirectly by calling getSourceLocation after setting
+ * file or line.
+ */
+ public void setFile(String path) {
+ LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(path), "empty path");
+ if (null != sourceLocation) {
+ throw new IllegalStateException("cannot set line after creating source location");
+ }
+ this.file = path;
+ }
+
+ /** set the kind of this message */
+ public void setKindAsString(String kind) {
+ setMessageKind(MessageUtil.getKind(kind));
+ }
+
+ public void setSourceLocation(ISourceLocation sourceLocation) {
+ this.sourceLocation = sourceLocation;
+ }
+
+ /**
+ * Set the line for the underlying source location.
+ * @throws IllegalStateException if source location was set directly
+ * or indirectly by calling getSourceLocation after setting
+ * file or line.
+ */
+ public void setLineAsString(String line) {
+ if (null != sourceLocation) {
+ throw new IllegalStateException("cannot set line after creating source location");
+ }
+ this.line = Integer.valueOf(line).intValue();
+ SourceLocation.validLine(this.line);
+ }
+
+ public void setText(String text) {
+ this.message = (null == text ? "" : text);
+ }
+
+ public String toString() {
+ StringBuffer result = new StringBuffer();
+
+ result.append(getKind().toString());
+
+ String messageString = getMessage();
+ if (!LangUtil.isEmpty(messageString)) {
+ result.append(messageString);
+ }
+
+ ISourceLocation loc = getISourceLocation();
+ if ((null != loc) && (loc != ISourceLocation.NO_FILE)) {
+ result.append(" at " + loc);
+ }
+ if (null != thrown) {
+ result.append(" -- " + LangUtil.renderExceptionShort(thrown));
+ }
+ return result.toString();
+ }
+}
diff --git a/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java b/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java
new file mode 100644
index 000000000..2b3dfdad7
--- /dev/null
+++ b/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java
@@ -0,0 +1,89 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.xml;
+
+
+import org.aspectj.bridge.ISourceLocation;
+
+import java.io.File;
+
+/**
+ * Immutable source location.
+ * This guarantees that the source file is not null
+ * and that the numeric values are positive and line <= endLine.
+ * @see org.aspectj.lang.reflect.SourceLocation
+ * @see org.aspectj.compiler.base.parser.SourceInfo
+ * @see org.aspectj.tools.ide.SourceLine
+ * @see org.aspectj.testing.harness.ErrorLine
+ */
+public class SoftSourceLocation implements ISourceLocation { // XXX endLine?
+ public static final File NONE = new File("SoftSourceLocation.NONE");
+ private File sourceFile;
+ private int line;
+ private int column;
+ private int endLine;
+
+ public SoftSourceLocation() {
+ }
+
+ public File getSourceFile() {
+ return (null != sourceFile ? sourceFile : NONE);
+ }
+ public int getLine() {
+ return line;
+ }
+
+ public int getColumn() {
+ return column;
+ }
+
+ public int getEndLine() {
+ return line;
+ }
+
+ public void setFile(String sourceFile) {
+ this.sourceFile = new File(sourceFile);
+ }
+
+ public void setLine(String line) {
+ this.line = convert(line);
+ if (0 == endLine) {
+ endLine = this.line;
+ }
+ }
+
+ public void setColumn(String column) {
+ this.column = convert(column);
+ }
+
+ public void setEndLine(String line) {
+ this.endLine = convert(line);
+ }
+
+
+ private int convert(String in) {
+ return Integer.valueOf(in).intValue();
+ }
+
+ public String getLocationContext() {
+ return null;
+ }
+
+ /** @return String : file:line:column */
+ public String toString() {
+ return getSourceFile().getPath() + ":" + getLine() + ":" + getColumn();
+ }
+
+
+}
diff --git a/testing/src/org/aspectj/testing/xml/XMLWriter.java b/testing/src/org/aspectj/testing/xml/XMLWriter.java
new file mode 100644
index 000000000..fc2eda919
--- /dev/null
+++ b/testing/src/org/aspectj/testing/xml/XMLWriter.java
@@ -0,0 +1,353 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC).
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Common Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.xml;
+
+import org.aspectj.util.LangUtil;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * Manage print stream to an XML document.
+ * This tracks start/end elements and signals on error,
+ * and optionally lineates buffer by accumulating
+ * up to a maximum width (including indent).
+ * This also has utilities for un/flattening lists and
+ * rendering buffer.
+ */
+public class XMLWriter {
+ static final String SP = " ";
+ static final String TAB = SP + SP;
+
+ /** maximum value for maxWidth, when flowing buffer */
+ public static final int MAX_WIDTH = 8000;
+
+ /** default value for maxWidth, when flowing buffer */
+ public static final int DEFAULT_WIDTH = 80;
+
+ /** currently this just strips quotes and ampersands */
+ public static String attributeValue(String input) {
+ input = input.replace('"', '~');
+ input = input.replace('&', '=');
+ return input;
+ }
+
+ /** @return name="{attributeValue({value})" */
+ public static String makeAttribute(String name, String value) {
+ return (name + "=\"" + attributeValue(value) + "\"");
+ }
+
+ /** same as flattenList, except also normalize \ -> / */
+ public static String flattenFiles(String[] strings) {
+ return flattenList(strings).replace('\\', '/');
+ }
+
+ /** same as flattenList, except also normalize \ -> / */
+ public static String flattenFiles(List paths) {
+ return flattenList(paths).replace('\\', '/');
+ }
+
+ /**
+ * Expand comma-delimited String into list of values, without trimming
+ * @param list List of items to print - null is silently ignored,
+ * so for empty items use ""
+ * @return String[]{} for null input, String[] {input} for input without comma,
+ * or new String[] {items..} otherwise
+ * @throws IllegalArgumentException if {any item}.toString() contains a comma
+ */
+ public static String[] unflattenList(String input) {
+ return (String[]) LangUtil.commaSplit(input).toArray(new String[0]);
+ }
+
+ /** flatten input and add to list */
+ public static void addFlattenedItems(List list, String input) {
+ LangUtil.throwIaxIfNull(list, "list");
+ if (null != input) {
+ String[] items = XMLWriter.unflattenList(input);
+ if (!LangUtil.isEmpty(items)) {
+ for (int i = 0; i < items.length; i++) {
+ if (!LangUtil.isEmpty(items[i])) {
+ list.add(items[i]);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Collapse list into a single comma-delimited value (e.g., for list buffer)
+ * @param list List of items to print - null is silently ignored,
+ * so for empty items use ""
+ * @return item{,item}...
+ * @throws IllegalArgumentException if {any item}.toString() contains a comma
+ */
+ public static String flattenList(List list) {
+ if ((null == list) || (0 == list.size())) {
+ return "";
+ }
+ return flattenList(list.toArray());
+ }
+
+
+ /**
+ * Collapse list into a single comma-delimited value (e.g., for list buffer)
+ * @param list the String[] items to print - null is silently ignored,
+ * so for empty items use ""
+ * @return item{,item}...
+ * @throws IllegalArgumentException if list[i].toString() contains a comma
+ */
+ public static String flattenList(Object[] list) {
+ StringBuffer sb = new StringBuffer();
+ if (null != list) {
+ boolean printed = false;
+ for (int i = 0; i < list.length; i++) {
+ Object o = list[i];
+ if (null != o) {
+ if (printed) {
+ sb.append(",");
+ } else {
+ printed = true;
+ }
+ String s = o.toString();
+ if (-1 != s.indexOf(",")) {
+ throw new IllegalArgumentException("comma in " + s);
+ }
+ sb.append(s);
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /** output sink */
+ PrintWriter out;
+
+ /** stack of String element names */
+ Stack stack = new Stack();
+
+ /** false if doing attributes */
+ boolean attributesDone = true;
+
+ /** current element prefix */
+ String indent = "";
+
+ /** maximum width (in char) of indent and buffer when flowing */
+ int maxWidth;
+
+ /**
+ * Current text being flowed.
+ * length() is always less than maxWidth.
+ */
+ StringBuffer buffer;
+
+ /** @param out PrintWriter to print to - not null */
+ public XMLWriter(PrintWriter out) {
+ LangUtil.throwIaxIfNull(out, "out");
+ this.out = out;
+ buffer = new StringBuffer();
+ maxWidth = DEFAULT_WIDTH;
+ }
+
+ /**
+ * Set maximum width (in chars) of buffer to accumulate.
+ * @param maxWidth int 0..MAX_WIDTH for maximum number of char to accumulate
+ */
+ public void setMaxWidth(int maxWidth) {
+ if (0 > maxWidth) {
+ this.maxWidth = 0;
+ } else if (MAX_WIDTH < maxWidth) {
+ this.maxWidth = MAX_WIDTH;
+ } else {
+ this.maxWidth = maxWidth;
+ }
+ }
+
+ /** shortcut for entire element */
+ public void printElement(String name, String attributes) {
+ if (!attributesDone) throw new IllegalStateException("finish attributes");
+ if (0 != buffer.length()) { // output on subelement
+ outPrintln(buffer + ">");
+ buffer.setLength(0);
+ }
+ String oldIndent = indent;
+ if (0 < stack.size()) {
+ indent += TAB;
+ ((StackElement) stack.peek()).numChildren++;
+ }
+ outPrintln(indent + "<" + name + " " + attributes + "/>");
+ indent = oldIndent;
+ }
+
+ /**
+ * Start element only
+ * @param name the String label of the element
+ * @param closeTag if true, delimit the end of the starting tag
+ */
+ public void startElement(String name, boolean closeTag) {
+ startElement(name, "", closeTag);
+ }
+
+ /**
+ * Start element with buffer on the same line.
+ * This does not flow buffer.
+ * @param name String tag for the element
+ * @param attr {name="value"}.. where value
+ * is a product of attributeValue(String)
+ */
+ public void startElement(String name, String attr, boolean closeTag) {
+ if (!attributesDone) throw new IllegalStateException("finish attributes");
+ LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(name), "empty name");
+
+ if (0 != buffer.length()) { // output on subelement
+ outPrintln(buffer + ">");
+ buffer.setLength(0);
+ }
+ if (0 < stack.size()) {
+ indent += TAB;
+ }
+ StringBuffer sb = new StringBuffer();
+ sb.append(indent);
+ sb.append("<");
+ sb.append(name);
+
+ if (!LangUtil.isEmpty(attr)) {
+ sb.append(" ");
+ sb.append(attr.trim());
+ }
+ attributesDone = closeTag;
+ if (closeTag) {
+ sb.append(">");
+ outPrintln(sb.toString());
+ } else if (maxWidth <= sb.length()) {
+ outPrintln(sb.toString());
+ } else {
+ if (0 != this.buffer.length()) {
+ throw new IllegalStateException("expected empty attributes starting " + name);
+ }
+ this.buffer.append(sb.toString());
+ }
+ if (0 < stack.size()) {
+ ((StackElement) stack.peek()).numChildren++;
+ }
+ stack.push(new StackElement(name));
+ }
+
+ /**
+ * @param name should be the same as that given to start the element
+ * @throws IllegalStateException if start element does not match
+ */
+ public void endElement(String name) {
+ int level = stack.size();
+ String err = null;
+ StackElement element = null;
+ if (0 == stack.size()) {
+ err = "empty stack";
+ } else {
+ element = (StackElement) stack.pop();
+ if (!element.name.equals(name)) {
+ err = "expecting element " + element.name;
+ }
+ }
+ if (null != err) {
+ err = "endElement(" + name + ") " + stack + ": " + err;
+ throw new IllegalStateException(err);
+ }
+ if (0 < element.numChildren) {
+ outPrintln(indent + "</" + name + ">");
+ } else if (0 < buffer.length()) {
+ outPrintln(buffer + "/>");
+ buffer.setLength(0);
+ } else {
+ outPrintln(indent + "/>");
+ }
+ if (!attributesDone) {
+ attributesDone = true;
+ }
+ if (0 < stack.size()) {
+ indent = indent.substring(0, indent.length() - TAB.length());
+ }
+ }
+
+
+ /**
+ * Print name=value if neither is null and name is not empty after trimming,
+ * accumulating these until they are greater than maxWidth or buffer are
+ * terminated with endAttributes(..) or endElement(..).
+ * @param value the String to convert as attribute value - ignored if null
+ * @param name the String to use as the attribute name
+ * @throws IllegalArgumentException if name is null or empty after trimming
+ */
+ public void printAttribute(String name, String value) {
+ if (attributesDone) throw new IllegalStateException("not in attributes");
+ if (null == value) {
+ return;
+ }
+ if ((null == name) || (0 == name.trim().length())) {
+ throw new IllegalArgumentException("no name=" + name + "=" + value);
+ }
+
+ String newAttr = name + "=\"" + attributeValue(value) + "\"";
+ int indentLen = indent.length();
+ int bufferLen = buffer.length();
+ int newAttrLen = (0 == bufferLen ? indentLen : 0) + newAttr.length();
+
+ if (maxWidth > (bufferLen + newAttrLen)) {
+ buffer.append(" ");
+ buffer.append(newAttr);
+ } else { // at least print old attributes; maybe also new
+ if (0 < bufferLen) {
+ outPrintln(buffer.toString());
+ buffer.setLength(0);
+ }
+ buffer.append(indent + SP + newAttr);
+ }
+ }
+
+ public void endAttributes() {
+ if (attributesDone) throw new IllegalStateException("not in attributes");
+ attributesDone = true;
+ }
+
+ public void printComment(String comment) {
+ if (!attributesDone) throw new IllegalStateException("in attributes");
+ outPrintln(indent + "<!-- " + comment + "-->");
+ }
+
+ public void close() {
+ if (null != out) {
+ out.close();
+ }
+
+ }
+
+ public void println(String string) {
+ outPrintln(string);
+ }
+
+ private void outPrintln(String s) {
+ if (null == out) {
+ throw new IllegalStateException("used after close");
+ }
+ out.println(s);
+ }
+ static class StackElement {
+ String name;
+ int numChildren;
+ public StackElement(String name) {
+ this.name = name;
+ }
+ }
+
+}